react调用api等待返回结果_程序员:RPC远程调用原理浅析
RPC
基本概念
RPC(Remote Procedure Call)远程过程调用,简单的理解是一个节点请求另一个节点提供的服务
本地过程调用:如果需要将本地student对象的age+1,可以实现一个addAge()方法,将student对象传入,对年龄进行更新之后返回即可,本地方法调用的函数体通过函数指针来指定。
远程过程调用:上述操作的过程中,如果addAge()这个方法在服务端,执行函数的函数体在远程机器上,如何告诉机器需要调用这个方法呢?
- 首先客户端需要告诉服务器,需要调用的函数,这里函数和进程ID存在一个映射,客户端远程调用时,需要查一下函数,找到对应的ID,然后执行函数的代码。
- 客户端需要把本地参数传给远程函数,本地调用的过程中,直接压栈即可,但是在远程调用过程中不再同一个内存里,无法直接传递函数的参数,因此需要客户端把参数转换成字节流,传给服务端,然后服务端将字节流转换成自身能读取的格式,是一个序列化和反序列化的过程。
- 备好了之后,如何进行传输?网络传输层需要把调用的ID和序列化后的参数传给服务端,然后把计算好的结果序列化传给客户端,因此TCP层即可完成上述过程,gRPC中采用的是HTTP2协议。
总结一下上述过程:
// Client端
// Student student = Call(ServerAddr, addAge, student)
1. 将这个调用映射为Call ID。
2. 将Call ID,student(params)序列化,以二进制形式打包
3. 把2中得到的数据包发送给ServerAddr,这需要使用网络传输层
4. 等待服务器返回结果
5. 如果服务器调用成功,那么就将结果反序列化,并赋给student,年龄更新
// Server端
1. 在本地维护一个Call ID到函数指针的映射call_id_map,可以用Map callIdMap
2. 等待服务端请求
3. 得到一个请求后,将其数据包反序列化,得到Call ID
4. 通过在callIdMap中查找,得到相应的函数指针
5. 将student(params)反序列化后,在本地调用addAge()函数,得到结果
6. 将student结果序列化后通过网络返回给Client
- 在微服务的设计中,一个服务A如果访问另一个Module下的服务B,可以采用HTTP REST传输数据,并在两个服务之间进行序列化和反序列化操作,服务B把执行结果返回过来。
- 由于HTTP在应用层中完成,整个通信的代价较高,远程过程调用中直接基于TCP进行远程调用,数据传输在传输层TCP层完成,更适合对效率要求比较高的场景,RPC主要依赖于客户端和服务端之间建立Socket链接进行,底层实现比REST更复杂。
创建三个maven项目
服务者
消费者
API
让服务者和消费者都依赖API
在消费者创建ConsumerApp类
使用代理对象
具体代码在ProxyUtils中
public class ConsumerApp {
public static void main(String[] args) {
//while死循环是为了测试调用提供者是否为随机
while (true) {
try {
Thread.sleep(2000);
// 获得代理对象
AddService addService = ProxyUtils.getProxy(AddService.class);
// 只要调用方法就会进入代理对象invoke方法
int result = addService.add(15, 684);
System.out.println(result);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
API中创建Request,AddService,ProxyUtils,ZkUtils
创建Request(该类为传输对象,必须实现序列化)
public class Request implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
private String interfaceName;
private String methodName;
private Object[] args;
public String getInterfaceName() {
return interfaceName;
}
public void setInterfaceName(String interfaceName) {
this.interfaceName = interfaceName;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public Object[] getArgs() {
return args;
}
public void setArgs(Object[] args) {
this.args = args;
}
@Override
public String toString() {
return "Request [interfaceName=" + interfaceName + ", methodName=" + methodName + ", args="
+ Arrays.toString(args) + "]";
}
}
创建AddService
package com.chenlei.service;
public interface AddService {
public int add(Integer a, Integer b);
}
创建ProxyUtils(重点)
public class ProxyUtils {
private static Random RDM = new Random();
@SuppressWarnings("unchecked")
public static T getProxy(Class interfaces) {
T proxy = (T) Proxy.newProxyInstance(ProxyUtils.class.getClassLoader(), new Class>[] { interfaces },
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if ("toString".equals(methodName)) {
return interfaces.getClass().getName() + "$Proxy";
}
if ("hashCode".equals(methodName)) {
return Object.class.hashCode();
}
if ("equals".equals(methodName)) {
return Object.class.equals(this);
}
// 消费者发送过去
Request request = new Request();
request.setInterfaceName(interfaces.getName());
request.setMethodName(methodName);
request.setArgs(args);
// 找到interfaces下的所有节点
List serverList = ZkUtils.discover(interfaces.getName());
String one = randomOne(serverList);// 拿到的结果为ip:port 如127.0.0.1:8888
String[] split = one.split(":");
String address = split[0];
Integer port = Integer.valueOf(split[1]);
Socket socket = null;
// 打开书出管道,发送请求
Object result = null;
OutputStream outputStream = null;
ObjectOutputStream objectOutputStream = null;
InputStream inputStream = null;
ObjectInputStream objectInputStream = null;
try {
socket = new Socket(address, port);
outputStream = socket.getOutputStream();
objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(request);
inputStream = socket.getInputStream();
objectInputStream = new ObjectInputStream(inputStream);
result = objectInputStream.readObject();
System.out.println("本次调用的是======" + port);
} catch (Exception e) {
e.printStackTrace();
} finally {
closeResources(objectInputStream, inputStream, objectOutputStream, outputStream, socket);
}
return result;
}
});
return proxy;
}
/**
* 从节点中随机找出一个
*
* @param serverList
* @return
*/
private static String randomOne(List serverList) {
if (null == serverList || 0 == serverList.size()) {
return null;
}
int index = RDM.nextInt(serverList.size());
return serverList.get(index);
}
/**
* 关闭资源的方法
*/
public static void closeResources(Closeable... resources) {
for (Closeable resource : resources) {
if (null != resource) {
try {
resource.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
resource = null;
}
}
}
}
}
创建ZkUtils(zookeeper注册和发现,另加缓存解决脏读)
在API项目中导入zkclient的依赖
public class ZkUtils {
private static final String ZK_URL = "自己的域名:2181";
private static ZkClient zkClient = null;
//创建zookeeper缓存
private static Map> cache = new HashMap>();
static {
zkClient = new ZkClient(ZK_URL, 10000, 10000);
}
/**
* 服务节点向zookeeper的注册
*
* @param serverName
* @param serverPort
*/
public static void register(String serverName, String serverPort) {
if (null == serverName || "".equals(serverName)) {
throw new RuntimeException("服务名不能为空");
}
if (null == serverPort || "".equals(serverPort)) {
throw new RuntimeException("服务ip和端口不能为空");
}
if (!zkClient.exists("/" + serverName)) {
zkClient.createPersistent("/" + serverName);
}
if (!zkClient.exists("/" + serverName + "/" + serverPort)) {
zkClient.createEphemeral("/" + serverName + "/" + serverPort);
}
System.out.println("注册一个服务节点为" + "/" + serverName + "/" + serverPort);
}
/**
* 向zookeeper发现服务节点
*
* @param serverName
* @return
*/
public static List discover(String serverName) {
if (null == serverName || "".equals(serverName)) {
throw new RuntimeException("服务名不能为空");
}
// 先从缓存里找
if (cache.containsKey(serverName)) {
System.out.println("在缓存中找到" + serverName + "节点");
}
// 如果该节点在zookeeper中不存在,直接返回空
if (!zkClient.exists("/" + serverName)) {
return null;
}
zkClient.subscribeChildChanges("/" + serverName, new IZkChildListener() {
@Override
public void handleChildChange(String parentPath, List currentChilds) throws Exception {
// 一旦进入此方法,证明有节点改变
cache.put(serverName, currentChilds);
System.out.println(serverName + "节点有变化-----" + "缓存完成更新");
}
});
return zkClient.getChildren("/" + serverName);
}
}
写提供者代码
创建AddServiceImpl
注意类名最好是AddService+Impl,并且类全路径也要对应com.chenlei.service.impl.AddServiceImpl,否则代码需要调整
package com.chenlei.service.impl;
import com.chenlei.service.AddService;
public class AddServiceImpl implements AddService {
@Override
public int add(Integer a, Integer b) {
return a + b;
}
}
创建ProviderApp(重点)
public class ProviderApp {
public static void main(String[] args) {
Integer port = 7777;
ServerSocket serverSocket = bind(port);
// 向zookeeper注册
ZkUtils.register(AddService.class.getName(), "127.0.0.1" + ":" + port);
// 监听+处理请求
listener(serverSocket);
}
/**
* 监听和处理请求
*
* @param serverSocket
*/
private static void listener(ServerSocket serverSocket) {
//此处死循环是为了让次提供者一直处于工作状态
while (true) {
Socket socket = null;
InputStream inputStream = null;
ObjectInputStream objectInputStream = null;
OutputStream outputStream = null;
ObjectOutputStream objectOutputStream = null;
try {
socket = serverSocket.accept();
inputStream = socket.getInputStream();
objectInputStream = new ObjectInputStream(inputStream);
Request request = (Request) objectInputStream.readObject();
Object answer = invoker(request);
outputStream = socket.getOutputStream();
objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(answer);
} catch (Exception e) {
e.printStackTrace();
} finally {
ProxyUtils.closeResources(objectOutputStream, outputStream, objectInputStream, inputStream, socket);
}
}
}
/**
* 处理请求返回结果
*
* @param request
* @return
*/
private static Object invoker(Request request) {
// 获得从消费者传过来的信息
String interfaceName = request.getInterfaceName();
String methodName = request.getMethodName();
Object[] args = request.getArgs();
// 获得对应实现类全名
String className = getClassNameByInterfaceName(interfaceName);
Object answer = null;
try {
// 找到该类
Class> clazz = Class.forName(className);
// 创建一个对象
Object object = clazz.newInstance();
Class>[] argsType = new Class>[args.length];
if (null != args || 0 != args.length) {
for (int i = 0; i < args.length; i++) {
argsType[i] = args[i].getClass();
}
}
Method method = clazz.getMethod(methodName, argsType);
answer = method.invoke(object, args);
} catch (Exception e) {
e.printStackTrace();
}
return answer;
}
/**
* 通过请求者传来的类信息,获得对应实现类的所有信息,并返回实现类的全名
*
* @param interfaceName
* @return
*/
private static String getClassNameByInterfaceName(String interfaceName) {
// 传过来的接口名为com.chenlei.service.AddService
int index = interfaceName.lastIndexOf(".");
StringBuilder sb = new StringBuilder();
// com.chenlei.service
sb.append(interfaceName.subSequence(0, index));
// com.chenlei.service.impl.
sb.append(".impl.");
// com.chenlei.service.impl.AddService
sb.append(interfaceName.substring(index + 1)).append("Impl");
return sb.toString();
}
/**
* 绑定一个端口
*
* @param port
* @return
*/
private static ServerSocket bind(Integer port) {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(port);
} catch (IOException e) {
e.printStackTrace();
}
return serverSocket;
}
/**
* 测试代码
*/
//public static void main(String[] args) {
// String interfaceName = "com.chenlei.service.AddService";
// int index = interfaceName.lastIndexOf(".");
// StringBuilder sb = new StringBuilder();
// // com.chenlei.service
// sb.append(interfaceName.subSequence(0, index));
// // com.chenlei.service.impl.
// sb.append(".impl.");
// // com.chenlei.service.impl.AddService
// sb.append(interfaceName.substring(index + 1)).append("Impl");
// System.out.println(sb.toString());
//}
}
更改提供者端口,分别启动三个提供者
再启动消费者,查看结果
其他错误和注意事项
写代码思路
react调用api等待返回结果_程序员:RPC远程调用原理浅析相关推荐
- 某个软件调用目标异常_线上RPC远程调用频繁超时问题排查,大功臣Arthas
来源于公众号Java艺术 , 作者wujiuye 两耳不闻窗外事,一心只读圣贤书.又是一个美好的周末,一觉睡到自然醒,写写文章看看书!这周原计划是写Dubbo注册中心的,但这周先说故事. 上次服务雪崩 ...
- react调用api等待返回结果_React新Context API在前端状态管理的实践
### React新Context API在前端状态管理的实践 众所周知,React的单向数据流模式导致状态只能一级一级的由父组件传递到子组件,在大中型应用中较为繁琐不好管理,通常我们需要使用Redu ...
- shell调用python获取返回值_[linux的shell脚本调用python脚本的返回值][shell调python][ubuntu的shell调用python脚本得到返回值]...
最近搞个东西,需要写shell脚本,但在shell脚本里需要调用python并得到变量值,网上找了很多都是python调shell的,最后请教了一下郭总 知道了sys.argv的用法后才搞定了,在此感 ...
- zookeeper 密码_「附源码」Dubbo+Zookeeper 的 RPC 远程调用框架
技术博文,及时送达 作者 | 码农云帆哥 链接 | blog.csdn.net/sinat_27933301 上一篇:从零搭建创业公司后台技术栈 这是一个基于Dubbo+Zookeeper 的 RPC ...
- powershell 调用API显示或隐藏指定程序的主窗口
powershell 调用API显示或隐藏指定程序的主窗口 001. 前言 一同事碰上这种情况:某个单实例的程序不知为何挂在后台运行,托盘中也找不到图标(一般是有的),但可以在任务管理器中看到该程序处 ...
- python rpc调用_从0到1:全面理解 RPC 远程调用
上一篇关于 WSGI 的硬核长文,不知道有多少同学,能够从头看到尾的,不管你们有没有看得很过瘾,反正我是写得很爽,总有一种将一样知识吃透了的错觉. 今天我又给自己挖坑了,打算将 rpc 远程调用的知识 ...
- json java 数据类型_程序员都应该了解的一种数据格式之 JSON
原标题:程序员都应该了解的一种数据格式之 JSON 作者 | 猪哥 责编 | maozz JSON的诞生原因是因为XML整合到HTML中各个浏览器实现的细节不尽相同,所以道格拉斯·克罗克福特(Doug ...
- java socket 远程调用_使用Socket反射Java流操作进行方法的远程调用(模拟RPC远程调用)...
写在前面 阅读本文首先得具备基本的Socket.反射.Java流操作的基本API使用知识:否则本文你可能看不懂... 服务端的端口监听 进行远程调用,那就必须得有客户端和服务端.服务端负责提供服务,客 ...
- java语言情话_程序员的浪漫:用 java 实现每天给对象法发情话
一.引言 最近看到一篇用js代码实现表白的文章,深有感触. 然后发现自己也可以用java代码实现,然后就开始写代码了,发现还挺有意思的,话不多说开搞 实现思路: 使用HttpClient远程获取彩虹屁 ...
最新文章
- php发送邮件smtp源码,php下使用SMTP发邮件的代码
- centos6.8安装docker,kong-dashboard并实现页面访问
- 深入理解RocketMQ是如何做到高性能的?
- Rust 生命周期太难学、最想实现与 C++ 互操作,Rust 2020 调查报告发布!
- 远程登录服务器哪个工具好,远程登录服务器,有什么比较好用的工具?
- 自编一个从指定位置开始查找字符串的Python代码
- 春节快过腻了?不妨关心下太空探索
- 计算机的记事本和写字板的功能,写字板和记事本的异同
- 求解线性同余方程--扩展欧几里得
- win10“User Profile Service 服务未能登录,无法加载用户配置文件问题
- 联发科p60和骁龙710哪个好_骁龙710、麒麟710和联发科P60哪个好 性能对比测试 (全文)...
- PHP之linux(一)linux基础
- selected 操作
- 结束拒绝访问的进程 cmd下结束进程 强行结束进程
- vue 网页滚动到指定位置显示动画效果
- MySQL中支持的字符集和排序规则
- 一本通1612特别行动队
- 人造的风景 --- 东部华侨城一日游感想与收获
- HEVC码率控制算法1TEncRateCtrl
- 计算机一级考试操作题基础操作,计算机一级考试基础操作题.doc
热门文章
- Vue学习笔记之09-v-model双向绑定
- php 函数频率,这是一些使用频率比较高的php函数……
- 怎么把苹果手机通讯录导入华为手机_苹果手机资料快速导入华为手机。苹果的ios系统也可以把资料导入安卓!...
- ret2dir:Rethinking Kernel Isolation(翻译)
- 通用 PE 工具箱1.9.6(XP内核)by Uepon(李培聪)
- nyoj 600——花儿朵朵——【离散化、线段树插线问点】
- 从win到linux的小问题集锦(不断更新中)
- POJ 3087 Shuffle'm Up(水题)
- IOS 归档 即序列化与反序列化
- WinAPI: MoveWindow - 改变窗口的位置与大小