网络通信优化之通信协议:如何优化RPC网络通信?
网络通信优化之通信协议:如何优化RPC网络通信?
在分布式系统和微服务架构中,RPC(远程过程调用)通信是服务间交互的核心。特别是在高并发场景下,如何优化RPC通信以提升系统性能成为了一个重要课题。本文将从RPC通信的基本概念出发,深入探讨其优化方法和实践。
在微服务架构中,SpringCloud和Dubbo是最常用的两个框架。虽然SpringCloud凭借其完整的微服务生态体系备受青睐,但在某些特定场景下,Dubbo因其在RPC通信方面的优势而成为更好的选择。
RPC通信是大型服务框架的核心
微服务的核心在于远程通信和服务治理。远程通信提供了服务之间通信的桥梁,服务治理则提供了服务的后勤保障。在服务拆分后,通信成本显著增加,特别是在抢购或促销等高并发场景中,服务间的频繁调用很容易成为系统的瓶颈。因此,在满足一定的服务治理需求的前提下,对远程通信的性能需求成为技术选型的主要影响因素。
目前,很多微服务框架中的服务通信是基于RPC通信实现的。SpringCloud主要使用Feign组件实现RPC通信(基于Http+Json序列化),而Dubbo则支持多种RPC通信框架,包括RMI、Dubbo、Hessian等(默认是Dubbo+Hessian序列化)。不同的业务场景下,RPC通信的选择和优化标准也不同。
例如,在高并发场景下,Dubbo中的Dubbo协议就表现出了明显的优势。以下是基于Dubbo:2.6.4版本进行的简单的性能测试。分别测试Dubbo+Protobuf序列化以及Http+Json序列化的通信性能(这里主要模拟单一TCP长连接+Protobuf序列化和短连接的Http+Json序列化的性能对比)。为了验证在数据量不同的情况下二者的性能表现,分别准备了小对象和大对象的性能压测。
通过以上测试结果可以发现:无论从响应时间还是吞吐量上来看,单一TCP长连接+Protobuf序列化实现的RPC通信框架都有着非常明显的优势。
什么是RPC通信
RPC(Remote Process Call),即远程服务调用,是通过网络请求远程计算机程序服务的通信技术。RPC框架封装好了底层网络通信、序列化等技术,我们只需要在项目中引入各个服务的接口包,就可以实现在代码中调用RPC服务同调用本地方法一样。正因为这种方便、透明的远程调用,RPC被广泛应用于当下企业级以及互联网项目中,是实现分布式系统的核心。
RMI(Remote Method Invocation)是JDK中最先实现了RPC通信的框架之一,RMI的实现对建立分布式Java应用程序至关重要,是Java体系非常重要的底层技术,很多开源的RPC通信框架也是基于RMI实现原理设计出来的,包括Dubbo框架中也接入了RMI框架。
RMI:JDK自带的RPC通信框架
RMI远程代理对象是RMI中最核心的组件,除了对象本身所在的虚拟机,其它虚拟机也可以调用此对象的方法。而且这些虚拟机可以不在同一个主机上,通过远程代理对象,远程应用可以用网络协议与服务进行通信。
RMI的实现原理
RMI远程代理对象是RMI中最核心的组件,除了对象本身所在的虚拟机,其它虚拟机也可以调用此对象的方法。而且这些虚拟机可以不在同一个主机上,通过远程代理对象,远程应用可以用网络协议与服务进行通信。
RMI在高并发场景下的性能瓶颈
- Java默认序列化:RMI的序列化采用的是Java默认的序列化方式,性能并不是很好,而且其它语言框架也暂时不支持Java序列化。
- TCP短连接:由于RMI是基于TCP短连接实现,在高并发情况下,大量请求会带来大量连接的创建和销毁,这对于系统来说无疑是非常消耗性能的。
- 阻塞式网络I/O:在高并发场景下基于短连接实现的网络通信就很容易产生I/O阻塞,性能将会大打折扣。
一个高并发场景下的RPC通信优化路径
RPC通信包括了建立通信、实现报文、传输协议以及传输数据编解码等操作,接下来我们就从每一层的优化出发,逐步实现整体的性能优化。
1.选择合适的通信协议
要实现不同机器间的网络通信,我们先要了解计算机系统网络通信的基本原理。网络通信是两台设备之间实现数据流交换的过程,是基于网络传输协议和传输数据的编解码来实现的。其中网络传输协议有TCP、UDP协议,这两个协议都是基于Socket编程接口之上,为某类应用场景而扩展出的传输协议。
基于TCP协议实现的Socket通信是有连接的,而传输数据是要通过三次握手来实现数据传输的可靠性,且传输数据是没有边界的,采用的是字节流模式。
基于UDP协议实现的Socket通信,客户端不需要建立连接,只需要创建一个套接字发送数据报给服务端,这样就不能保证数据报一定会达到服务端,所以在传输数据方面,基于UDP协议实现的Socket通信具有不可靠性。UDP发送的数据采用的是数据报模式,每个UDP的数据报都有一个长度,该长度将与数据一起发送到服务端。
通过对比,我们可以得出优化方法:为了保证数据传输的可靠性,通常情况下我们会采用TCP协议。如果在局域网且对数据传输的可靠性没有要求的情况下,我们也可以考虑使用UDP协议,毕竟这种协议的效率要比TCP协议高。
2.使用单一长连接
如果是基于TCP协议实现Socket通信,我们还能做哪些优化呢?
服务之间的通信不同于客户端与服务端之间的通信。客户端与服务端由于客户端数量多,基于短连接实现请求可以避免长时间地占用连接,导致系统资源浪费。
但服务之间的通信,连接的消费端不会像客户端那么多,但消费端向服务端请求的数量却一样多,我们基于长连接实现,就可以省去大量的TCP建立和关闭连接的操作,从而减少系统的性能消耗,节省时间。
3.优化Socket通信
建立两台机器的网络通信,我们一般使用Java的Socket编程实现一个TCP连接。传统的Socket通信主要存在I/O阻塞、线程模型缺陷以及内存拷贝等问题。我们可以使用比较成熟的通信框架,比如Netty。Netty4对Socket通信编程做了很多方面的优化:
- 实现非阻塞I/O:在08讲中,我们提到了多路复用器Selector实现了非阻塞I/O通信。
- 高效的Reactor线程模型:Netty使用了主从Reactor多线程模型,服务端接收客户端请求连接是用了一个主线程,这个主线程用于客户端的连接请求操作,一旦连接建立成功,将会监听I/O事件,监听到事件后会创建一个链路请求。
- 链路请求将会注册到负责I/O操作的I/O工作线程上,由I/O工作线程负责后续的I/O操作。利用这种线程模型,可以解决在高负载、高并发的情况下,由于单个NIO线程无法监听海量客户端和满足大量I/O操作造成的问题。
- 串行设计:服务端在接收消息之后,存在着编码、解码、读取和发送等链路操作。如果这些操作都是基于并行去实现,无疑会导致严重的锁竞争,进而导致系统的性能下降。为了提升性能,Netty采用了串行无锁化完成链路操作,Netty提供了Pipeline实现链路的各个操作在运行期间不进行线程切换。
- 零拷贝:在08讲中,我们提到了一个数据从内存发送到网络中,存在着两次拷贝动作,先是从用户空间拷贝到内核空间,再是从内核空间拷贝到网络I/O中。而NIO提供的ByteBuffer可以使用Direct Buffers模式,直接开辟一个非堆物理内存,不需要进行字节缓冲区的二次拷贝,可以直接将数据写入到内核空间。
除了以上这些优化,我们还可以针对套接字编程提供的一些TCP参数配置项,提高网络吞吐量,Netty可以基于ChannelOption来设置这些参数。
- TCP_NODELAY:TCP_NODELAY选项是用来控制是否开启Nagle算法。Nagle算法通过缓存的方式将小的数据包组成一个大的数据包,从而避免大量的小数据包发送阻塞网络,提高网络传输的效率。我们可以关闭该算法,优化对于时延敏感的应用场景。
- SO_RCVBUF和SO_SNDBUF:可以根据场景调整套接字发送缓冲区和接收缓冲区的大小。
- SO_BACKLOG:backlog参数指定了客户端连接请求缓冲队列的大小。服务端处理客户端连接请求是按顺序处理的,所以同一时间只能处理一个客户端连接,当有多个客户端进来的时候,服务端就会将不能处理的客户端连接请求放在队列中等待处理。
- SO_KEEPALIVE:当设置该选项以后,连接会检查长时间没有发送数据的客户端的连接状态,检测到客户端断开连接后,服务端将回收该连接。我们可以将该时间设置得短一些,来提高回收连接的效率。
4.量身定做报文格式
接下来就是实现报文,我们需要设计一套报文,用于描述具体的校验、操作、传输数据等内容。为了提高传输的效率,我们可以根据自己的业务和架构来考虑设计,尽量实现报体小、满足功能、易解析等特性。我们可以参考下面的数据格式:
5.编码、解码
在09讲中,我们分析过序列化编码和解码的过程,对于实现一个好的网络通信协议来说,兼容优秀的序列化框架是非常重要的。如果只是单纯的数据对象传输,我们可以选择性能相对较好的Protobuf序列化,有利于提高网络通信的性能。
6.调整Linux的TCP参数设置选项
如果RPC是基于TCP短连接实现的,我们可以通过修改Linux TCP配置项来优化网络通信。开始TCP配置项的优化之前,我们先来了解下建立TCP连接的三次握手和关闭TCP连接的四次握手,这样有助后面内容的理解。
- 三次握手
- 四次握手
我们可以通过sysctl -a | grep net.xxx命令运行查看Linux系统默认的的TCP参数设置,如果需要修改某项配置,可以通过编辑 vim/etc/sysctl.conf,加入需要修改的配置项, 并通过sysctl -p命令运行生效修改后的配置项设置。通常我们会通过修改以下几个配置项来提高网络吞吐量和降低延时。
以上就是我们从不同层次对RPC优化的详解,除了最后的Linux系统中TCP的配置项设置调优,其它的调优更多是从代码编程优化的角度出发,最终实现了一套RPC通信框架的优化路径。
弄懂了这些,你就可以根据自己的业务场景去做技术选型了,还能很好地解决过程中出现的一些性能问题。
总结
在现在的分布式系统中,特别是系统走向微服务化的今天,服务间的通信就显得尤为频繁,掌握服务间的通信原理和通信协议优化,是你的一项的必备技能。
在一些并发场景比较多的系统中,我更偏向使用Dubbo实现的这一套RPC通信协议。Dubbo协议是建立的单一长连接通信,网络I/O为NIO非阻塞读写操作,更兼容了Kryo、FST、Protobuf等性能出众的序列化框架,在高并发、小对象传输的业务场景中非常实用。
在企业级系统中,业务往往要比普通的互联网产品复杂,服务与服务之间可能不仅仅是数据传输,还有图片以及文件的传输,所以RPC的通信协议设计考虑更多是功能性需求,在性能方面不追求极致。其它通信框架在功能性、生态以及易用、易入门等方面更具有优势。