gitbook/Java性能调优实战/docs/100355.md
2022-09-03 22:05:03 +08:00

167 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 10 | 网络通信优化之通信协议如何优化RPC网络通信
你好,我是刘超。今天我将带你了解下服务间的网络通信优化。
上一讲中我提到了微服务框架其中SpringCloud和Dubbo的使用最为广泛行业内也一直存在着对两者的比较很多技术人会为这两个框架哪个更好而争辩。
我记得我们部门在搭建微服务框架时也在技术选型上纠结良久还曾一度有过激烈的讨论。当前SpringCloud炙手可热具备完整的微服务生态得到了很多同事的票选但我们最终的选择却是Dubbo这是为什么呢
## RPC通信是大型服务框架的核心
我们经常讨论微服务,首要应该了解的就是微服务的核心到底是什么,这样我们在做技术选型时,才能更准确地把握需求。
就我个人理解,我认为微服务的核心是远程通信和服务治理。远程通信提供了服务之间通信的桥梁,服务治理则提供了服务的后勤保障。所以,我们在做技术选型时,更多要考虑的是这两个核心的需求。
我们知道服务的拆分增加了通信的成本,特别是在一些抢购或者促销的业务场景中,如果服务之间存在方法调用,比如,抢购成功之后需要调用订单系统、支付系统、券包系统等,这种远程通信就很容易成为系统的瓶颈。所以,在满足一定的服务治理需求的前提下,对远程通信的性能需求就是技术选型的主要影响因素。
目前很多微服务框架中的服务通信是基于RPC通信实现的在没有进行组件扩展的前提下SpringCloud是基于Feign组件实现的RPC通信基于Http+Json序列化实现Dubbo是基于SPI扩展了很多RPC通信框架包括RMI、Dubbo、Hessian等RPC通信框架默认是Dubbo+Hessian序列化。不同的业务场景下RPC通信的选择和优化标准也不同。
例如开头我提到的我们部门在选择微服务框架时选择了Dubbo。当时的选择标准就是RPC通信可以支持抢购类的高并发在这个业务场景中请求的特点是瞬时高峰、请求量大和传入、传出参数数据包较小。而Dubbo中的Dubbo协议就很好地支持了这个请求。
**以下是基于Dubbo:2.6.4版本进行的简单的性能测试。**分别测试Dubbo+Protobuf序列化以及Http+Json序列化的通信性能这里主要模拟单一TCP长连接+Protobuf序列化和短连接的Http+Json序列化的性能对比。为了验证在数据量不同的情况下二者的性能表现我分别准备了小对象和大对象的性能压测通过这样的方式我们也可以间接地了解下二者在RPC通信方面的水平。
![](https://static001.geekbang.org/resource/image/dc/54/dc950f3a5ff15253e101fac90c192f54.jpg)
![](https://static001.geekbang.org/resource/image/20/b1/20814a2a87057fdc03af699454f703b1.jpg)
这个测试是我之前的积累,基于测试环境比较复杂,这里我就直接给出结果了,如果你感兴趣的话,可以留言和我讨论。
通过以上测试结果可以发现:**无论从响应时间还是吞吐量上来看单一TCP长连接+Protobuf序列化实现的RPC通信框架都有着非常明显的优势。**
在高并发场景下我们选择后端服务框架或者中间件部门自行设计服务框架时RPC通信是重点优化的对象。
其实目前成熟的RPC通信框架非常多如果你们公司没有自己的中间件团队也可以基于开源的RPC通信框架做扩展。在正式进行优化之前我们不妨简单回顾下RPC。
## 什么是RPC通信
一提到RPC你是否还想到MVC、SOA这些概念呢如果你没有经历过这些架构的演变这些概念就很容易混淆。**你可以通过下面这张图来了解下这些架构的演变史。**
![](https://static001.geekbang.org/resource/image/e4/a5/e43a8f81d76927948a73a9977643daa5.jpg)
无论是微服务、SOA、还是RPC架构它们都是分布式服务架构都需要实现服务之间的互相通信我们通常把这种通信统称为RPC通信。
RPCRemote Process Call即远程服务调用是通过网络请求远程计算机程序服务的通信技术。RPC框架封装好了底层网络通信、序列化等技术我们只需要在项目中引入各个服务的接口包就可以实现在代码中调用RPC服务同调用本地方法一样。正因为这种方便、透明的远程调用RPC被广泛应用于当下企业级以及互联网项目中是实现分布式系统的核心。
RMIRemote Method Invocation是JDK中最先实现了RPC通信的框架之一RMI的实现对建立分布式Java应用程序至关重要是Java体系非常重要的底层技术很多开源的RPC通信框架也是基于RMI实现原理设计出来的包括Dubbo框架中也接入了RMI框架。接下来我们就一起了解下RMI的实现原理看看它存在哪些性能瓶颈有待优化。
## RMIJDK自带的RPC通信框架
目前RMI已经很成熟地应用在了EJB以及Spring框架中是纯Java网络分布式应用系统的核心解决方案。RMI实现了一台虚拟机应用对远程方法的调用可以同对本地方法的调用一样RMI帮我们封装好了其中关于远程通信的内容。
### RMI的实现原理
RMI远程代理对象是RMI中最核心的组件除了对象本身所在的虚拟机其它虚拟机也可以调用此对象的方法。而且这些虚拟机可以不在同一个主机上通过远程代理对象远程应用可以用网络协议与服务进行通信。
我们可以通过一张图来详细地了解下整个RMI的通信过程
![](https://static001.geekbang.org/resource/image/11/4f/1113e44dd62591ce68961e017c11ed4f.jpg)
### RMI在高并发场景下的性能瓶颈
* Java默认序列化
RMI的序列化采用的是Java默认的序列化方式我在09讲中详细地介绍过Java序列化我们深知它的性能并不是很好而且其它语言框架也暂时不支持Java序列化。
* TCP短连接
由于RMI是基于TCP短连接实现在高并发情况下大量请求会带来大量连接的创建和销毁这对于系统来说无疑是非常消耗性能的。
* 阻塞式网络I/O
在08讲中我提到了网络通信存在I/O瓶颈如果在Socket编程中使用传统的I/O模型在高并发场景下基于短连接实现的网络通信就很容易产生I/O阻塞性能将会大打折扣。
## 一个高并发场景下的RPC通信优化路径
SpringCloud的RPC通信和RMI通信的性能瓶颈就非常相似。SpringCloud是基于Http通信协议短连接和Json序列化实现的在高并发场景下并没有优势。 那么在瞬时高并发的场景下我们又该如何去优化一个RPC通信呢
RPC通信包括了建立通信、实现报文、传输协议以及传输数据编解码等操作接下来我们就从每一层的优化出发逐步实现整体的性能优化。
### 1.选择合适的通信协议
要实现不同机器间的网络通信我们先要了解计算机系统网络通信的基本原理。网络通信是两台设备之间实现数据流交换的过程是基于网络传输协议和传输数据的编解码来实现的。其中网络传输协议有TCP、UDP协议这两个协议都是基于Socket编程接口之上为某类应用场景而扩展出的传输协议。通过以下两张图我们可以大概了解到基于TCP和UDP协议实现的Socket网络通信是怎样的一个流程。
![](https://static001.geekbang.org/resource/image/2c/0b/2c7c373963196a30e9d4fdc524a92d0b.jpg)
基于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.量身定做报文格式
接下来就是实现报文,我们需要设计一套报文,用于描述具体的校验、操作、传输数据等内容。为了提高传输的效率,我们可以根据自己的业务和架构来考虑设计,尽量实现报体小、满足功能、易解析等特性。我们可以参考下面的数据格式:
![](https://static001.geekbang.org/resource/image/6d/c1/6dc21193a6ffbf94a7dd8e5a0d2302c1.jpg)
![](https://static001.geekbang.org/resource/image/f3/ae/f3bb46ed6ece4a8a9bcc3d9e9df84cae.jpg)
### 5.编码、解码
在09讲中我们分析过序列化编码和解码的过程对于实现一个好的网络通信协议来说兼容优秀的序列化框架是非常重要的。如果只是单纯的数据对象传输我们可以选择性能相对较好的Protobuf序列化有利于提高网络通信的性能。
### 6.调整Linux的TCP参数设置选项
如果RPC是基于TCP短连接实现的我们可以通过修改Linux TCP配置项来优化网络通信。开始TCP配置项的优化之前我们先来了解下建立TCP连接的三次握手和关闭TCP连接的四次握手这样有助后面内容的理解。
* 三次握手
![](https://static001.geekbang.org/resource/image/32/de/32381d3314bd982544f69e4d3faba1de.jpg)
* 四次握手
![](https://static001.geekbang.org/resource/image/df/91/df9f4e3f3598a7e160c899f552a59391.jpg)
我们可以通过sysctl -a | grep net.xxx命令运行查看Linux系统默认的的TCP参数设置如果需要修改某项配置可以通过编辑 vim/etc/sysctl.conf加入需要修改的配置项 并通过sysctl -p命令运行生效修改后的配置项设置。通常我们会通过修改以下几个配置项来提高网络吞吐量和降低延时。
![](https://static001.geekbang.org/resource/image/9e/bc/9eb01fe017b267367b11170a864bd0bc.jpg)
以上就是我们从不同层次对RPC优化的详解除了最后的Linux系统中TCP的配置项设置调优其它的调优更多是从代码编程优化的角度出发最终实现了一套RPC通信框架的优化路径。
弄懂了这些,你就可以根据自己的业务场景去做技术选型了,还能很好地解决过程中出现的一些性能问题。
## 总结
在现在的分布式系统中,特别是系统走向微服务化的今天,服务间的通信就显得尤为频繁,掌握服务间的通信原理和通信协议优化,是你的一项的必备技能。
在一些并发场景比较多的系统中我更偏向使用Dubbo实现的这一套RPC通信协议。Dubbo协议是建立的单一长连接通信网络I/O为NIO非阻塞读写操作更兼容了Kryo、FST、Protobuf等性能出众的序列化框架在高并发、小对象传输的业务场景中非常实用。
在企业级系统中业务往往要比普通的互联网产品复杂服务与服务之间可能不仅仅是数据传输还有图片以及文件的传输所以RPC的通信协议设计考虑更多是功能性需求在性能方面不追求极致。其它通信框架在功能性、生态以及易用、易入门等方面更具有优势。
## 思考题
目前实现Java RPC通信的框架有很多实现RPC通信的协议也有很多除了Dubbo协议以外你还使用过其它RPC通信协议吗通过这讲的学习你能对比谈谈各自的优缺点了吗
期待在留言区看到你的见解。也欢迎你点击“请朋友读”,把今天的内容分享给身边的朋友,邀请他一起学习。