本文主要以最简易最快速的方式介绍RPC调用核心流程,文中以Dubbo为例。同时,会写一个简易的RPC调用代码,方便理解和记忆核心组件和核心流程。

1、核心思想

RPC调用过程中,最粗矿的核心组件3个:Registry、Provider、Consumer。最粗矿的流程4个:注册、订阅、通知、调用。最简单的流程图就1个:

本文会继续细粒度地拆解以上流程,拆解之前,请牢记这段话:

RPC调用,不管中间流程多么复杂,不管代码多么复杂,所有的努力也只为做2件事情:

  1. 在Consumer端,将ReferenceConfig配置的类转换成Proxy代理。
  1. 在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。
  • Protocol:服务协议,它相当于一个中间层,用于与注册中心打交道 和 封装 RPC 调用。它在初始化时会创建Client模块 与 服务端建立连接,也会生成用于远程调用的Invoker。
  • Cluster:服务集群,主要用于路由、负载均衡、服务容错等。
  • Invoker:服务调用者。
    • Consumer的服务调用者主要是利用Client模块发起远程调用,然后等待Provider返回结果。
    • Provider的服务调用者主要是根据接收到的消息利用反射生成本地代理,然后执行方法,再将结果返回到Consumer。
  • Client:客户端模块,默认是Netty实现,主要用于客户端和服务端通讯(主要是服务调用),比如将请求的接口、参数、请求ID等封装起来发给Server端。
  • Server:服务端模拟,默认是Netty实现。主要是用于客户端和服务端通讯。

3、核心流程

3.1、Consumer流程

流程:

Consumer的流程实际上就是一个从ReferenceConfig 生成Proxy代理的过程。核心事情由Protocol完成。

  1. 根据ReferenceConfig生成代理
  2. 注册到注册中心、订阅注册中心事件
  3. 建立NettyClient,并且与NettyServer建立连接
  4. 生成客户端的ClientInvoker
  5. 选择负载均衡和集群容错
  6. ClientInvoker发起网络调用和等待结果

流程图:

3.2、Provider流程

流程:

Provider的流程实际上就是个从ServiceConfig生成Proxy代理的过程。核心事情由PorxyProtocol完成。

  1. 根据ServiceConfig生成本地代理
  2. 注册到注册中心
  3. 启动NettyServer等待客户端连接
  4. 生成服务端Invoker
  5. Invoker监听调用请求
  6. 接收到请求后新建任务丢入到线程池去执行
  7. 执行时会生成本地代理执行(比如通过反射去调用具体的方法),再将返回结果写出去

流程图:

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、项目地址

https://github.com/yclxiao/rpc-demo.git

5、总结

本文主要以Dubbo为例介绍了RPC调用核心流程,同时,写了个简易的RPC调用代码。

记住以上的流程图即可搞明白整个调用流程。然后再记住最核心的2句话:

  • 所有的努力都是为了能在Consumer端和Provider端生成功能丰富的Proxy。核心事情由Protocol完成。
  • 核心的5个部件:Registry、Proxy、Protocol、Invoker、Client、Server。

本篇完结!欢迎点赞 关注 收藏!!!

原文链接:https://mp.weixin.qq.com/s/9fF2weLLBR7SChOxPEEqEA

======>>>>>> 关于我 <<<<<<======

最简最快了解RPC核心流程的更多相关文章

  1. dubbo核心流程一览

    整体设计 图中从下至上分为十层,各层均为单向依赖,右边的黑色箭头代表层之间的依赖关系,每一层都可以剥离上层被复用,其中,Service 和 Config 层为 API,其它各层均为 SPI. Serv ...

  2. 2017.3.31 spring mvc教程(二)核心流程及配置详解

    学习的博客:http://elf8848.iteye.com/blog/875830/ 我项目中所用的版本:4.2.0.博客的时间比较早,11年的,学习的是Spring3 MVC.不知道版本上有没有变 ...

  3. Nginx核心流程及模块介绍

    Nginx核心流程及模块介绍 1. Nginx简介以及特点 Nginx简介: Nginx (engine x) 是一个高性能的web服务器和反向代理服务器,也是一个IMAP/POP3/SMTP服务器 ...

  4. paip.刮刮卡砸金蛋抽奖概率算法跟核心流程.

    paip.刮刮卡砸金蛋抽奖概率算法跟核心流程. #---抽奖算法需要满足的需求如下: 1 #---抽奖核心流程 1 #---问题???更好的算法 2 #---实际使用的扩展抽奖算法(带奖品送完判断和每 ...

  5. iOS-动画效果(首尾式动画,代码快动画,核心动画,序列帧动画)

    一.各个动画的优缺点 1.首尾动画:如果只是修改空间的属性,使用首尾动画比较方便,如果在动画结束后做后续处理,就不是那么方面了. 2.核心动画:有点在于对后续的处理方便. 3.块动画: (1)在实际的 ...

  6. 实例模拟struts核心流程

    Struts,经典框架之一,每个java  web 开发人员都应该晓得它的大名.这里,我就用一个简单实例来模拟一下struts的核心流程.具体实例如下: 主界面: 点击提交后,程序根据具体的actio ...

  7. ibatis源码学习1_整体设计和核心流程

    背景介绍ibatis实现之前,先来看一段jdbc代码: Class.forName("com.mysql.jdbc.Driver"); String url = "jdb ...

  8. KVM Run Process之KVM核心流程

    在"KVM Run Process之Qemu核心流程"一文中讲到Qemu通过KVM_RUN调用KVM提供的API发起KVM的启动,从这里进入到了内核空间执行,本文主要讲述内核中KV ...

  9. 【Spring专场】「IOC容器」不看源码就带你认识核心流程以及运作原理

    这是史上最全面的Spring的核心流程以及运作原理的分析指南 [Spring核心专题]「IOC容器篇」不看繁琐的源码就带你浏览Spring的核心流程以及运作原理 [Spring核心专题]「AOP容器篇 ...

  10. 【Spring专场】「MVC容器」不看源码就带你认识核心流程以及运作原理

    前提回顾 之前已经写了很多问斩针对于SpringMVC的的执行原理和核心流程,在此再进行冗余介绍就没有任何意义了,所以我们主要考虑的就是针对于SpringMVC还没但大框架有介绍的相关内容解析分析和说 ...

随机推荐

  1. [转帖]linux中的set -e 与set -o pipefail

    https://www.cnblogs.com/xingmuxin/p/8431970.html 1.set -e "Exit immediately if a simple command ...

  2. Harbor镜像仓库的导出与整理之二

    Harbor镜像仓库的导出与整理之二 背景 前几天参照大神的blog进行了一下harbor的镜像列表的获取与下载. 当时发现一个很诡异的问题. 实际上镜像仓库里面的镜像很多. 但是导出和列表里面的却很 ...

  3. [转帖]通过配置优化KingbaseES服务器性能

    目录 1. 概述 2. 数据库应用类型 3. 服务器参数 3.1. max_connections 3.2. shared_buffers 3.3. effective_cache_size 3.4. ...

  4. [转贴]中国铁塔发布2020年中期财报:营收、利润双增,高效支撑5G规模建设

      中国铁塔发布2020年中期财报:营收.利润双增,高效支撑5G规模建设 https://tech.sina.com.cn/roll/2020-08-11/doc-iivhuipn8046256.sh ...

  5. 从 WebStorm 转到 VSCode!使用一周体验报告

    前言 最近我的 Jetbrains 开源项目授权到期了,想要续订的时候发现 Jetbrains 提高了开源项目申请门槛,我的 StarBlog 项目因为名字里包含 blog 这个词无法申请,虽然我在 ...

  6. 小白学k8s(1)二进制部署k8s

    二进制部署k8s 前言 准备工作 关闭防火墙 关闭 swap 分区 关闭 SELinux 更新系统时间 秘钥免密码 设置主机名称 服务器角色 安装etcd 创建证书 生成证书 部署Etcd 在Node ...

  7. 【Jmeter】基础介绍-详细

    最近做压测时使用到Jmeter,为什么用它,之前也做过部分压测,不是很系统,使用的是Apache Bench,虽然效率高,但是功能比较简单,不太适合本次压测场景,另外Jmeter能更好的利用压测机的多 ...

  8. Redis 数据库配置与应用

    Redis 是一个key-value存储系统.Redis是一个开源的使用ANSI C语言编写.遵守BSD协议.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,并提供多种语言的API. ...

  9. 遥感图像处理笔记之【Land use/Land cover classification with Deep Learning】

    遥感图像处理学习(1) 前言 遥感图像处理方向的学习者可以参考或者复刻 本文初编辑于2023年12月14日CSDN平台 2024年1月24日搬运至本人博客园平台 文章标题:Land use/Land ...

  10. IDEA破解(无限重启激活时间版)

    下载地址[将下载的目录打成zip压缩包后使用]:「ide-eval-resetter」https://www.aliyundrive.com/s/UFHpDX5d6Xv 点击链接保存,或者复制本段内容 ...