最简最快了解RPC核心流程
本文主要以最简易最快速的方式介绍RPC调用核心流程,文中以Dubbo为例。同时,会写一个简易的RPC调用代码,方便理解和记忆核心组件和核心流程。
1、核心思想
RPC调用过程中,最粗矿的核心组件3个:Registry、Provider、Consumer。最粗矿的流程4个:注册、订阅、通知、调用。最简单的流程图就1个:

本文会继续细粒度地拆解以上流程,拆解之前,请牢记这段话:
RPC调用,不管中间流程多么复杂,不管代码多么复杂,所有的努力也只为做2件事情:
- 在Consumer端,将ReferenceConfig配置的类转换成Proxy代理。
- 在Provider端,将ServiceConfig配置的类转换成Proxy代理。
2、核心组件
为了能在Consumer端和Provider端生成各自的Proxy代理,并且发起调用和响应,需要如下核心组件:
- Registry:注册中心,主要是为了实现 Provider接口注册、Consumer订阅接口、接口变更通知、接口查找等功能。
- Proxy:服务代理,核心中的核心,一切的努力都是为了生成合适的Proxy服务代理。
- Consumer的Proxy:Consumer端根据
ReferenceConfig生成Proxy,此Proxy主要用于找到合适的Provider接口,然后发起网络调用。 - Provider的Proxy:Provider端根据
ServiceConfig生成Proxy,此Proxy主要作用是通过类似反射的方法调用本地代码,再将结果返回给Consumer。
- Consumer的Proxy:Consumer端根据
- Protocol:服务协议,它相当于一个中间层,用于与注册中心打交道 和 封装 RPC 调用。它在初始化时会创建
Client模块 与 服务端建立连接,也会生成用于远程调用的Invoker。 - Cluster:服务集群,主要用于路由、负载均衡、服务容错等。
- Invoker:服务调用者。
- Consumer的服务调用者主要是利用
Client模块发起远程调用,然后等待Provider返回结果。 - Provider的服务调用者主要是根据接收到的消息利用反射生成本地代理,然后执行方法,再将结果返回到Consumer。
- Consumer的服务调用者主要是利用
- Client:客户端模块,默认是Netty实现,主要用于客户端和服务端通讯(主要是服务调用),比如将请求的接口、参数、请求ID等封装起来发给Server端。
- Server:服务端模拟,默认是Netty实现。主要是用于客户端和服务端通讯。
3、核心流程
3.1、Consumer流程
流程:
Consumer的流程实际上就是一个从ReferenceConfig 生成Proxy代理的过程。核心事情由Protocol完成。
- 根据
ReferenceConfig生成代理 - 注册到注册中心、订阅注册中心事件
- 建立NettyClient,并且与NettyServer建立连接
- 生成客户端的ClientInvoker
- 选择负载均衡和集群容错
- ClientInvoker发起网络调用和等待结果
流程图:

3.2、Provider流程
流程:
Provider的流程实际上就是个从ServiceConfig生成Proxy代理的过程。核心事情由PorxyProtocol完成。
- 根据
ServiceConfig生成本地代理 - 注册到注册中心
- 启动NettyServer等待客户端连接
- 生成服务端Invoker
- Invoker监听调用请求
- 接收到请求后新建任务丢入到线程池去执行
- 执行时会生成本地代理执行(比如通过反射去调用具体的方法),再将返回结果写出去
流程图:

3.3、整体流程图

4、简易代码实现
4.1、核心代码介绍
客户端Proxy
/**
* 获取代理Service
*/
@SuppressWarnings("unchecked")
public <T> T getService(Class clazz) throws Exception {
return (T) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{clazz}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if ("equals".equals(methodName) || "hashCode".equals(methodName)) {
throw new IllegalAccessException("不能访问" + methodName + "方法");
}
if ("toString".equals(methodName)) {
return clazz.getName() + "#" + methodName;
}
List<RegistryInfo> registryInfoList = interfaceMethodsRegistryInfoMap.get(clazz);
if (registryInfoList == null) {
throw new RuntimeException("无法找到对应的服务提供者");
}
LoadBalancer loadBalancer = new RandomLoadBalancer();
RegistryInfo registryInfo = loadBalancer.choose(registryInfoList);
ChannelHandlerContext ctx = registryChannelMap.get(registryInfo);
String identity = InvokeUtils.buildInterfaceMethodIdentify(clazz, method);
String requestId;
synchronized (ProxyProtocol.this) {
requestIdWorker.increment();
requestId = String.valueOf(requestIdWorker.longValue());
}
ClientInvoker clientInvoker = new DefaultClientInvoker(method.getReturnType(), ctx, requestId, identity);
inProcessInvokerMap.put(identity + "#" + requestId, clientInvoker);
return clientInvoker.invoke(args);
}
});
}
服务端Proxy
private class RpcInvokerTask implements Runnable {
private RpcRequest rpcRequest;
public RpcInvokerTask(RpcRequest rpcRequest) {
this.rpcRequest = rpcRequest;
}
@Override
public void run() {
try {
ChannelHandlerContext ctx = rpcRequest.getCtx();
String interfaceIdentity = rpcRequest.getInterfaceIdentity();
String requestId = rpcRequest.getRequestId();
Map<String, Object> parameterMap = rpcRequest.getParameterMap();
//interfaceIdentity组成:接口类+方法+参数类型
Map<String, String> interfaceIdentityMap = string2Map(interfaceIdentity);
//拿出是哪个类
String interfaceName = interfaceIdentityMap.get("interface");
Class interfaceClass = Class.forName(interfaceName);
Object o = interfaceInstanceMap.get(interfaceClass);
//拿出是哪个方法
Method method = interfaceMethodMap.get(interfaceIdentity);
//反射执行
Object result = null;
String parameterStr = interfaceIdentityMap.get("parameter");
if (parameterStr != null && parameterStr.length() > 0) {
String[] parameterTypeClasses = parameterStr.split(",");//接口方法参数参数可能有多个,用,号隔开
Object[] parameterInstance = new Object[parameterTypeClasses.length];
for (int i = 0; i < parameterTypeClasses.length; i++) {
parameterInstance[i] = parameterMap.get(parameterTypeClasses[i]);
}
result = method.invoke(o, parameterInstance);
} else {
result = method.invoke(o);
}
//将结果封装成rcpResponse
RpcResponse rpcResponse = RpcResponse.create(JSONObject.toJSONString(result), interfaceIdentity, requestId);
//ctx返回执行结果
String resultStr = JSONObject.toJSONString(rpcResponse) + DELIMITER_STR;
ByteBuf byteBuf = Unpooled.copiedBuffer(resultStr.getBytes());
ctx.writeAndFlush(byteBuf);
System.out.println("响应给客户端:" + resultStr);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Protocol
public ProxyProtocol(String registryUrl, List<ServiceConfig> serviceConfigList, List<ReferenceConfig> referenceConfigList, int port) throws Exception {
this.serviceConfigList = serviceConfigList == null ? new ArrayList<>() : serviceConfigList;
this.registryUrl = registryUrl;
this.port = port;
this.referenceConfigList = referenceConfigList == null ? new ArrayList<>() : referenceConfigList;
//1、初始化注册中心
initRegistry(this.registryUrl);
//2、将服务注册到注册中心
InetAddress addr = InetAddress.getLocalHost();
String hostName = addr.getHostName();
String hostAddr = addr.getHostAddress();
registryInfo = new RegistryInfo(hostName, hostAddr, this.port);
doRegistry(registryInfo);
//3、初始化nettyServer,启动nettyServer
if (!this.serviceConfigList.isEmpty()) {
nettyServer = new NettyServer(this.serviceConfigList, this.interfaceMethodMap);
nettyServer.init(this.port);
}
//如果是客户端引用启动,则初始化处理线程
if (!this.referenceConfigList.isEmpty()) {
initProcessor();
}
}
客户端Invoker
@Override
public T invoke(Object[] args) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("interfaces", identity);
JSONObject param = new JSONObject();
if (args != null) {
for (Object obj : args) {
param.put(obj.getClass().getName(), obj);
}
}
jsonObject.put("parameter", param);
jsonObject.put("requestId", requestId);
String msg = jsonObject.toJSONString() + Constants.DELIMITER_STR;
System.out.println("发送给服务端JSON为:" + msg);
ByteBuf byteBuf = Unpooled.copiedBuffer(msg.getBytes());
ctx.writeAndFlush(byteBuf);
wait4Result();
return result;
}
private void wait4Result() {
synchronized (this) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void setResult(String result) {
synchronized (this) {
this.result = (T) JSONObject.parseObject(result, returnType);
notifyAll();
}
}
服务端Invoker
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String message = (String) msg;
System.out.println("提供者收到消息:" + message);
//解析消费者发来的消息
RpcRequest rpcRequest = RpcRequest.parse(message, ctx);
//接受到消息,启动线程池处理消费者发过来的请求
threadPoolExecutor.execute(new RpcInvokerTask(rpcRequest));
}
/**
* 处理消费者发过来的请求
*/
private class RpcInvokerTask implements Runnable {
private RpcRequest rpcRequest;
public RpcInvokerTask(RpcRequest rpcRequest) {
this.rpcRequest = rpcRequest;
}
@Override
public void run() {
try {
ChannelHandlerContext ctx = rpcRequest.getCtx();
String interfaceIdentity = rpcRequest.getInterfaceIdentity();
String requestId = rpcRequest.getRequestId();
Map<String, Object> parameterMap = rpcRequest.getParameterMap();
//interfaceIdentity组成:接口类+方法+参数类型
Map<String, String> interfaceIdentityMap = string2Map(interfaceIdentity);
//拿出是哪个类
String interfaceName = interfaceIdentityMap.get("interface");
Class interfaceClass = Class.forName(interfaceName);
Object o = interfaceInstanceMap.get(interfaceClass);
//拿出是哪个方法
Method method = interfaceMethodMap.get(interfaceIdentity);
//反射执行
Object result = null;
String parameterStr = interfaceIdentityMap.get("parameter");
if (parameterStr != null && parameterStr.length() > 0) {
String[] parameterTypeClasses = parameterStr.split(",");//接口方法参数参数可能有多个,用,号隔开
Object[] parameterInstance = new Object[parameterTypeClasses.length];
for (int i = 0; i < parameterTypeClasses.length; i++) {
parameterInstance[i] = parameterMap.get(parameterTypeClasses[i]);
}
result = method.invoke(o, parameterInstance);
} else {
result = method.invoke(o);
}
//将结果封装成rcpResponse
RpcResponse rpcResponse = RpcResponse.create(JSONObject.toJSONString(result), interfaceIdentity, requestId);
//ctx返回执行结果
String resultStr = JSONObject.toJSONString(rpcResponse) + DELIMITER_STR;
ByteBuf byteBuf = Unpooled.copiedBuffer(resultStr.getBytes());
ctx.writeAndFlush(byteBuf);
System.out.println("响应给客户端:" + resultStr);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Client
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer() {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024 * 1024, Constants.DELIMITER));
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new NettyClientHandler());
System.out.println("initChannel - " + Thread.currentThread().getName());
}
});
ChannelFuture cf = bootstrap.connect(ip, port).sync();
// cf.channel().closeFuture().sync();
System.out.println("客户端启动成功");
} catch (Exception e) {
e.printStackTrace();
group.shutdownGracefully();
}
Server
public NettyServer(List<ServiceConfig> serviceConfigList, Map<String, Method> interfaceMethodMap) {
this.serviceConfigList = serviceConfigList;
this.interfaceMethodMap = interfaceMethodMap;
}
public int init(int port) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(Channel channel) throws Exception {
channel.pipeline().addLast(new DelimiterBasedFrameDecoder(1024 * 1024, DELIMITER));
channel.pipeline().addLast(new StringDecoder());
channel.pipeline().addLast(new RpcInvokeHandler(serviceConfigList, interfaceMethodMap));
}
});
ChannelFuture cf = bootstrap.bind(port).sync();
System.out.println("启动NettyServer,端口为:" + port);
return port;
}
4.2、项目地址
5、总结
本文主要以Dubbo为例介绍了RPC调用核心流程,同时,写了个简易的RPC调用代码。
记住以上的流程图即可搞明白整个调用流程。然后再记住最核心的2句话:
- 所有的努力都是为了能在Consumer端和Provider端生成功能丰富的Proxy。核心事情由Protocol完成。
- 核心的5个部件:Registry、Proxy、Protocol、Invoker、Client、Server。
本篇完结!欢迎点赞 关注 收藏!!!
原文链接:https://mp.weixin.qq.com/s/9fF2weLLBR7SChOxPEEqEA
======>>>>>> 关于我 <<<<<<======

最简最快了解RPC核心流程的更多相关文章
- dubbo核心流程一览
整体设计 图中从下至上分为十层,各层均为单向依赖,右边的黑色箭头代表层之间的依赖关系,每一层都可以剥离上层被复用,其中,Service 和 Config 层为 API,其它各层均为 SPI. Serv ...
- 2017.3.31 spring mvc教程(二)核心流程及配置详解
学习的博客:http://elf8848.iteye.com/blog/875830/ 我项目中所用的版本:4.2.0.博客的时间比较早,11年的,学习的是Spring3 MVC.不知道版本上有没有变 ...
- Nginx核心流程及模块介绍
Nginx核心流程及模块介绍 1. Nginx简介以及特点 Nginx简介: Nginx (engine x) 是一个高性能的web服务器和反向代理服务器,也是一个IMAP/POP3/SMTP服务器 ...
- paip.刮刮卡砸金蛋抽奖概率算法跟核心流程.
paip.刮刮卡砸金蛋抽奖概率算法跟核心流程. #---抽奖算法需要满足的需求如下: 1 #---抽奖核心流程 1 #---问题???更好的算法 2 #---实际使用的扩展抽奖算法(带奖品送完判断和每 ...
- iOS-动画效果(首尾式动画,代码快动画,核心动画,序列帧动画)
一.各个动画的优缺点 1.首尾动画:如果只是修改空间的属性,使用首尾动画比较方便,如果在动画结束后做后续处理,就不是那么方面了. 2.核心动画:有点在于对后续的处理方便. 3.块动画: (1)在实际的 ...
- 实例模拟struts核心流程
Struts,经典框架之一,每个java web 开发人员都应该晓得它的大名.这里,我就用一个简单实例来模拟一下struts的核心流程.具体实例如下: 主界面: 点击提交后,程序根据具体的actio ...
- ibatis源码学习1_整体设计和核心流程
背景介绍ibatis实现之前,先来看一段jdbc代码: Class.forName("com.mysql.jdbc.Driver"); String url = "jdb ...
- KVM Run Process之KVM核心流程
在"KVM Run Process之Qemu核心流程"一文中讲到Qemu通过KVM_RUN调用KVM提供的API发起KVM的启动,从这里进入到了内核空间执行,本文主要讲述内核中KV ...
- 【Spring专场】「IOC容器」不看源码就带你认识核心流程以及运作原理
这是史上最全面的Spring的核心流程以及运作原理的分析指南 [Spring核心专题]「IOC容器篇」不看繁琐的源码就带你浏览Spring的核心流程以及运作原理 [Spring核心专题]「AOP容器篇 ...
- 【Spring专场】「MVC容器」不看源码就带你认识核心流程以及运作原理
前提回顾 之前已经写了很多问斩针对于SpringMVC的的执行原理和核心流程,在此再进行冗余介绍就没有任何意义了,所以我们主要考虑的就是针对于SpringMVC还没但大框架有介绍的相关内容解析分析和说 ...
随机推荐
- idb单副本时-TiKV节点损坏后有损数据恢复的方法
Tidb单副本时-TiKV节点损坏后有损数据恢复的方法 背景 UAT环境下,为了减少存储. 搭建了一套单副本的TiDB集群 但是随着数据量的增多, UAT上面的数据可以丢失,但是表结构等信息是无法接受 ...
- [转帖]ramdisk三种实现方式
https://www.jianshu.com/p/c14cee74fa0a Ramdisk/ramfs/tmpfs Ramdisk:大小固定,默认4096k.在编译内核的时候需将block devi ...
- [转帖]Shell字符串拼接(连接、合并)
http://c.biancheng.net/view/1114.html 在脚本语言中,字符串的拼接(也称字符串连接或者字符串合并)往往都非常简单,例如: 在 PHP 中,使用.即可连接两个字符串: ...
- shell补遗_一个巨简单的保证服务存活的脚本
Shell补遗 背景 公司一台机器总是会在没有更新补丁的情况下启动失败. 查看所有的配置都没有问题. 但是就是不启动 没办法,准备写一个检查进行启动. 最近写shell很少. 所以总结一下. 思路 判 ...
- [转帖]docker编译speccpu2017
实验步骤: 1.下载docker和speccpu2017 2.docker下载镜像,创建容器 3.将下载的宿主机speccpu2017拷贝到docker创建的容器中(docker cp) 4.在doc ...
- 是否开启raid卡缓存的影响
开启raid卡缓存 Write back 对IO性能的影响 背景 公司买了一台服务器. 想进行一下升级 但是因为管理员担心数据丢失, 使用了write through + (raid6 + hotsp ...
- Sysbench简单测试数据库性能
摘要 先进行了一个PG数据库的测试. Mysql数据库的测试稍后跟上. 紧接着上一篇的安装, 部分文件可能需要特定路径才可以. sysbench 测试的说明 一个参数 这里稍微说一下参数的问题 sys ...
- Flask闪现
目录 九.闪现 9.1 什么是闪现? 九.闪现 9.1 什么是闪现? -设置:flash('aaa') -取值:get_flashed_message() - -假设在a页面操作出错,跳转到b页面,在 ...
- OpenIM集群(非k8s)部署文档
自行部署etcd/zookeeper/mysql/kafka/mongo/redis集群,可以根据此性能评估服务器需求. 以下是针对一台华为云主机s3的压测数据:8核16G内存,普通磁盘(非SSD)( ...
- vim 从嫌弃到依赖(15)——寄存器
在计算机里面也有寄存器,计算机中的寄存器是看得见,摸得着的实体,寄存器中存储需要经常访问的一些数据.而vim中也有寄存器的概念,vim中的寄存器是一个虚拟的概念,更像是一块专门用来存储数据的内存缓冲区 ...