文章目录
什么是RPC通信
RPC
RPC(Remote Process Call),即远程服务调用,是通过网络请求远程计算机程序服务的一种思想。也可以理解为一类行为得统称.比如常见得Http请求,或是直接socket长连接实现得请求都是RPC.
RPC框架
RPC 框架封装好了底层网络通信、序列化等技术,我们只需要在项目中引入各个服务的接口包,就可以实现在代码中调用 RPC 服务同调用本地方法一样。正因为这种方便、透明的远程调用,RPC 被广泛应用于当下企业级以及互联网项目中,是实现分布式系统的核心。
常见得RPC框架有:Thrift、gRPC
这里再简单介绍一下SOA.经常有人会搞混这两个东西.
SOA
SOA(Service-Oriented Architecture),即面向服务架构, 是一种架构思想,这种架构思想其实还是拆,怎么去拆?面向服务去拆,以服务为核心,通过服务或者服务组件来实现更高层次的复用,解耦.
而为了解决各个服务间的通信协议,序列化协议等区别,调用方式不同,通常会设定一个总线,这个总线去协调各个层次的服务调用,进行服务治理.
RPC通信得重要性
目前大型的服务架构基本都是微服务架构,而微服务的核心就是远程通信和服务治理.远程通信将各个服务联系起来,而服务治理则提供了服务的后勤保障.
我们知道服务的拆分增加了通信的成本,特别是在一些抢购或者促销的业务场景中,如果服务之间存在方法调用,比如,抢购成功之后需要调用订单系统、支付系统、券包系统等,这种远程通信就很容易成为系统的瓶颈。所以,在满足一定的服务治理需求的前提下,对远程通信的性能需求就是主要影响因素。
目前,很多微服务框架中的服务通信是基于 RPC 通信实现的,在没有进行组件扩展的前提下,SpringCloud 是基于 Http+Json 序列化实现实现的 RPC 通信(http1.0版本默认是短链接,而在http1.0以后默认是保持连接的,但只是一个单向的长连接,默认情况下只保持60s)
Dubbo 是基于 SPI 扩展了很多 RPC 通信框架,包括 RMI、Dubbo、Hessian 等 RPC 通信框架(默认是 Dubbo协议+Hessian 序列化)。这里的Dubbo协议底层其实是Socket长连接,即首次访问建立连接以后,后续网络请求使用相同的网络通道.
所以Dubbo默认的RPC通信会比SpringCloud默认的RPC通信效率更高.当然,怎样去选择还是要看具体的业务场景.
以下是基于 Dubbo:2.6.4 版本进行的简单的性能测试。分别测试 Dubbo+Protobuf 序列化以及 Http+Json 序列化的通信性能(这里主要模拟单一 TCP 长连接 +Protobuf 序列化和短连接的 Http+Json 序列化的性能对比)。为了验证在数据量不同的情况下二者的性能表现,我分别准备了小对象和大对象的性能压测,通过这样的方式我们也可以间接地了解下二者在 RPC 通信方面的水平。
通过以上测试结果可以发现:无论从响应时间还是吞吐量上来看,单一 TCP 长连接 +Protobuf 序列化实现的 RPC 通信框架都有着非常明显的优势。
在高并发场景下,我们选择后端服务框架或者中间件部门自行设计服务框架时,RPC 通信是重点优化的对象。
具体优化措施
SpringCloud 是基于 Http 通信协议(短连接)和 Json 序列化实现的,在高并发场景下并没有优势。 那么,在瞬时高并发的场景下,我们又该如何去优化一个 RPC 通信呢?RPC 通信包括了建立通信、实现报文、传输协议以及传输数据编解码等操作,接下来我们就从每一层的优化出发,逐步实现整体的性能优化。
1.扩展其他RPC框架.
如果业务架构已经使用了SpringCloud,底层使用的是Feign实现的远程调用,该如何优化呢?
这个最直接的就是尝试扩展其他RPC框架,比如说Google的grpc框架,也是基于Netty实现,基于protobuf实现的序列化.
接下来就涉及到优化的细节部分了,如以下关键点
2.选择合适的通信协议
网络通信是两台设备之间实现数据流交换的过程,是基于网络传输协议和传输数据的编解码来实现的。其中网络传输协议有 TCP、UDP 协议,这两个协议都是基于 Socket 编程接口之上,为某类应用场景而扩展出的传输协议。
基于 TCP 协议实现的 Socket 通信是有连接的,而传输数据是要通过三次握手来实现数据传输的可靠性,且传输数据是没有边界的,采用的是字节流模式。
基于 UDP 协议实现的 Socket 通信,客户端不需要建立连接,只需要创建一个套接字发送数据报给服务端,这样就不能保证数据报一定会达到服务端,所以在传输数据方面,基于 UDP 协议实现的 Socket 通信具有不可靠性。UDP 发送的数据采用的是数据报模式,每个 UDP 的数据报都有一个长度,该长度将与数据一起发送到服务端。
所以,为了保证数据传输的可靠性,针对RPC通信,我们通常会采用TCP协议.
3.使用单一长连接
如果我们使用TCP协议实现Socket通信,我们还能做哪些优化呢?
服务端之间的通信不同于客户端和服务端之间的通信,客户端和服务端的通信数量比较多,使用短链接可以避免长时间的占用连接,导致系统资源浪费.
而服务之间的通信所占的连接数量不会向客户端那么多, 同时需要经常频繁的通信,那么我们使用长连接就可以省去大量的建立连接和关闭连接的操作,节省很多时间.
4.优化Socket通信.
建立两台机器之间的通信,我们通常使用Java的Socket编程实现一个TCP连接,如果使用传统的Socket通信通常存在I/0阻塞,线程模型缺陷等问题,所以可以选用目前成熟的网络通信框架,比如Netty,Netty在如下方面都做了很成熟的改进,很多开源的框架底层也使用的是Netty.
- 实现非阻塞I/O
Netty使用了多路复用器 Selector 实现了非阻塞 I/O 通信,即NIO. - 高效的Reactor线程模型
Netty 使用了主从 Reactor 多线程模型,服务端接收客户端请求连接是用了一个主线程,这个主线程用于客户端的连接请求操作,一旦连接建立成功,将会监听 I/O 事件,监听到事件后会创建一个链路请求。
链路请求将会注册到负责 I/O 操作的 I/O 工作线程上,由 I/O 工作线程负责后续的 I/O 操作。利用这种线程模型,可以解决在高负载、高并发的情况下,由于单个 NIO 线程无法监听海量客户端和满足大量 I/O 操作造成的问题。 - 串行设计
服务端在接收消息之后,存在着编码、解码、读取和发送等链路操作。如果这些操作都是基于并行去实现,无疑会导致严重的锁竞争,进而导致系统的性能下降。为了提升性能,Netty 采用了串行无锁化完成链路操作,Netty 提供了 Pipeline 实现链路的各个操作在运行期间不进行线程切换。 - 零拷贝
一个数据从内存发送到网络中,存在着两次拷贝动作,先是从用户空间拷贝到内核空间,再是从内核空间拷贝到网络 I/O 中。而 NIO 提供的 ByteBuffer 可以使用 Direct Buffers 模式,直接开辟一个非堆物理内存,不需要进行字节缓冲区的二次拷贝,可以直接将数据写入到内核空间。
5.高性能的序列化协议
对于实现一个好的网络通信协议来说,兼容优秀的序列化框架是非常重要的。如果只是单纯的数据对象传输,我们可以选择性能相对较好的 Protobuf 序列化,有利于提高网络通信的性能。
6.量身定做报文格式
接下来就是实现报文,我们需要设计一套报文,用于描述具体的校验、操作、传输数据等内容。为了提高传输的效率,我们可以根据自己的业务和架构来考虑设计,尽量实现报体小、满足功能、易解析等特性。我们可以参考下面的数据格式:
今天的分享就到这里了,有问题可以在评论区留言,均会及时回复呀
我是bling,未来不会太差,只要我们不要太懒就行, 咱们下期见