ToB企服应用市场:ToB评测及商务社交产业平台

标题: 12437字,带你深入探究RPC通讯原理 [打印本页]

作者: 王海鱼    时间: 2022-9-4 02:46
标题: 12437字,带你深入探究RPC通讯原理
为什么要学习RPC

如下是Http请求案例:

请求过程会有3次握手4次挥手:
  1. 1:浏览器请求服务器(订单服务),请求建立链接                                                      1次握手
  2. 2:服务器(订单服务)响应浏览器,可以建立链接,并询问浏览器是否可以建立链接      2次握手
  3. 3:浏览器响应服务器(订单服务),可以建立链接                                                              3次握手
  4. ------开始传输数据------
  5. 1:浏览器向服务端(订单服务)发起请求,要求断开链接                                                    1次挥手
  6. 2:服务器(订单服务)回应浏览器,数据还在传输中                                                          2次挥手
  7. 3:服务器(订单服务)接收完数据后,向浏览器发消息要求断开链接                                         3次挥手
  8. 4:浏览器收到服务器消息后,回复服务器(订单服务)同意断开链接                                         4次挥手
复制代码
1.1  PRC概述

RPC 的主要功能目标是让构建分布式计算(应用)更容易,在提供强大的远程调用能力时不损失本地调用的语义简洁性。为实现该目标,RPC 框架需提供一种透明调用机制,让使用者不必显式的区分本地调用和远程调用。
RPC的优点:
RPC框架优势:
1.2 RPC框架

主流RPC框架:
1.3 应用场景

应用例举:
2. 深入RPC原理

2.1  设计与调用流程


具体调用过程:
所涉及的技术:
2.2 RPC深入解析

2.2.1 序列化技术


  1. ExchangeCodec的encode方法:
  2. ```java
  3.         @Override
  4.     public void encode(Channel channel, ChannelBuffer buffer, Object msg) throws IOException {
  5.         if (msg instanceof Request) {
  6.             encodeRequest(channel, buffer, (Request) msg);
  7.         } else if (msg instanceof Response) {
  8.             encodeResponse(channel, buffer, (Response) msg);
  9.         } else {
  10.             super.encode(channel, buffer, msg);
  11.         }
  12.     }
  13. ```
复制代码
  1. 源码:
  2. ExchangeCodec的decode方法:
  3. ```java
  4.     @Override
  5.     public Object decode(Channel channel, ChannelBuffer buffer) throws IOException {
  6.         int readable = buffer.readableBytes();
  7.         byte[] header = new byte[Math.min(readable, HEADER_LENGTH)];
  8.         buffer.readBytes(header);
  9.         return decode(channel, buffer, readable, header);
  10.     }
  11. ```
  12. ExchangeCodec的decodeBody方法:
  13. ```java
  14. protected Object decodeBody(Channel channel, InputStream is, byte[] header) throws IOException {
  15.     ...
  16.     } else {
  17.             // decode request.
  18.             Request req = new Request(id);
  19.             req.setVersion(Version.getProtocolVersion());
  20.             req.setTwoWay((flag & FLAG_TWOWAY) != 0);
  21.             if ((flag & FLAG_EVENT) != 0) {
  22.                 req.setEvent(true);
  23.             }
  24.             try {
  25.                 ObjectInput in = CodecSupport.deserialize(channel.getUrl(), is,                                 proto);   
  26.             }
  27.     ...
  28. }
  29. ```
复制代码
2.2.2 动态代理



JDK内部如何处理?
反编译生成的代理类可以知道,代理类 $Proxy里面会定义相同签名的接口(也就是上面代码User的job接口),然后内部会定义一个变量绑定JDKProxy代理对象,当调用User.job接口方法,实质上调用的是JDKProxy.invoke()方法,从而实现了接口的动态代理。

Byte Buddy > CGLIB  > Javassist> JDK。
源码剖析

核心源码:

2.2.3 服务注册发现


服务注册:在服务提供方启动的时候,将对外暴露的接口注册到注册中心内,注册中心将这个服务节点的 IP 和接口等连接信息保存下来。为了检测服务的服务端的有效状态,一般会建立双向心跳机制。
服务订阅:在服务调用方启动的时候,客户端去注册中心查找并订阅服务提供方的 IP,然后缓存到本地,并用于后续的远程调用。如果注册中心信息发生变化, 一般会采用推送的方式做更新。

A. 先在 ZooKeeper 中创建一个服务根路径,可以根据接口名命名(例如:/dubbo/com.itcast.xxService),在这个路径再创建服务提供方与调用方目录(providers、consumers),分别用来存储服务提供方和调用方的节点信息。
B. 服务端发起注册时,会在服务提供方目录中创建一个临时节点,节点中存储注册信   息,比如IP,端口,服务名称等等。
C. 客户端发起订阅时,会在服务调用方目录中创建一个临时节点,节点中存储调用方的信息,同时watch 服务提供方的目录(/dubbo/com.itcast.xxService/providers)中所有的服务节点数据。当服务端产生变化时,比如下线或宕机等,ZooKeeper 就会通知给订阅的客户端。
ZooKeeper方案的特点:
ZooKeeper 的一大特点就是强一致性,ZooKeeper 集群的每个节点的数据每次发生更新操作,都会通知其它 ZooKeeper 节点同时执行更新。它要求保证每个节点的数据能够实时的完全一致,这样也就会导致ZooKeeper 集群性能上的下降,ZK是采用CP模式(保证强一致性),如果要注重性能, 可以考虑采用AP模式(保证最终一致)的注册中心组件, 比如Nacos等。
2.2.4 网络IO模型


通常由一个独立的 Acceptor 线程负责监听客户端的连接。一般通过在while(true) 循环中服务端会调用 accept() 方法等待接收客户端的连接的方式监听请求,请求一旦接收到一个连接请求,就可以建立通信套接字,在这个通信套接字上进行读写操作,此时不能再接收其他客户端连接请求,直到客户端的操作执行完成。
系统内核处理 IO 操作分为两个阶段——等待数据和拷贝数据。而在这两个阶段中,应用进程中 IO 操作的线程会一直都处于阻塞状态,如果是基于 Java 多线程开发,那么每一个 IO 操作都要占用线程,直至 IO 操作结束。

程序刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误。如果此时去打开一个新的文件,它的文件描述符会是3。
三者的区别:
selectpollepoll操作方式遍历遍历回调底层实现bitmap数组红黑树IO效率每次调用都进行线性遍历,时间复杂度为O(n)每次调用都进行线性遍历,时间复杂度为O(n)事件通知方式,每当fd就绪,系统注册的回调函数就会被调用,将就绪fd放到readyList里面,时间复杂度O(1)最大连接数1024(x86)或2048(x64)无上限无上限fd拷贝每次调用select,都需要把fd集合从用户态拷贝到内核态每次调用poll,都需要把fd集合从用户态拷贝到内核态调用epoll_ctl时拷贝进内核并保存,之后每次epoll_wait不拷贝select/poll处理流程:
此处是动图
epoll的处理流程:
此处是动图
当连接有I/O流事件产生的时候,epoll就会去告诉进程哪个连接有I/O流事件产生,然后进程就去处理这个进程。这样性能相比要高效很多!
epoll 可以说是I/O 多路复用最新的一个实现,epoll 修复了poll 和select绝大部分问题, 比如
epoll 是线程安全的。
epoll 不仅告诉你sock组里面的数据,还会告诉你具体哪个sock连接有数据,不用进程独自轮询查找。


有些场景下,可能还需要更细粒度的路由方式,比如说根据SESSIONID要落到相同的服务节点上以保持会话的有效性;
可以考虑采用参数化路由


RPC 负载均衡策略一般包括轮询、随机、权重、最少连接等。Dubbo默认就是使用随机负载均衡策略。
3.3 熔断限流(了解)

我们后面课程会详细讲解熔断限流组件Sentinel高级用法、源码剖析、策略机制,但是RPC需要考虑熔断限流机制,我们一起来了解一下。
在Dubbo框架中, 可以通过Sentinel来实现更为完善的熔断限流功能,服务端是具体如何实现限流逻辑的?
方法有很多种, 最简单的是计数器,还有平滑限流的滑动窗口、漏斗算法以及令牌桶算法等等。Sentinel采用的是滑动窗口来实现的限流。

windowStart: 时间窗口的开始时间,单位是毫秒
windowLength: 时间窗口的长度,单位是毫秒
value: 时间窗口的内容
初始的时候arrays数组中只有一个窗口,每个时间窗口的长度是500ms,这就意味着只要当前时间与时间窗口的差值在500ms之内,时间窗口就不会向前滑动。

时间继续往前走,当超过500ms时,时间窗口就会向前滑动到下一个,这时就会更新当前窗口的开始时间:

在当前时间点中进入的请求,会被统计到当前时间所对应的时间窗口中。

熔断机制:
熔断器的工作机制主要是关闭、打开和半打开这三个状态之间的切换。
Sentinel 熔断降级组件它可以支持以下降级策略:
更多资料,参考Sentinel官方文档
本文由传智教育博学谷 - 狂野架构师教研团队发布
如果本文对您有帮助,欢迎关注和点赞;如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力
转载请注明出处!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4