高并发系统设计(十八):【RPC框架】10万QPS下如何实现毫秒级的服务调用?

在做了服务化拆分之后,把业务逻辑都拆分到了单独部署的服务中,那么假设在完成一次完整的请求时,需要调用4~5次服务,计算下来,RPC服务需要承载大概每秒10万次的请求。那么,你该如何设计RPC框架,来承载如此大的请求量呢?你要做的是:

选择合适的网络模型,有针对性地调整网络参数,以优化网络传输性能;
选择合适的序列化方式,以提升封包、解包的性能。

  1. 服务拆分单独部署后,引入的服务跨网络通信的问题;
  2. 在拆分成多个小服务之后,服务如何治理的问题。

RPC框架就封装了网络调用的细节,让你像调用本地服务一样,调用远程部署的服务。Dubbo、Grpc、Thrift这些新兴的框架才算是RPC框架。

另一个你可能听过的技术是Web Service,它也可以认为是RPC的一种实现方式。它的优势是,使用HTTP+SOAP协议,保证了调用可以跨语言,跨平台。只要你支持HTTP协议,可以解析XML,那么就能够使用Web Service。它由于使用XML封装数据,数据包大,性能还是比较差。

比方说,电商系统中,商品详情页面需要商品数据、评论数据还有店铺数据,如果在一体化的架构中,你只需要从商品库,评论库和店铺库获取数据就可以了,不考虑缓存的情况下有三次网络请求。
但是,如果独立出商品服务、评论服务和店铺服务之后,那么就需要分别调用这三个服务,而这三个服务又会分别调用各自的数据库,这就是六次网络请求。如果你服务拆分的更细粒度,那么多出的网络调用就会越多,请求的延迟就会更长,而这就是你为了提升系统的扩展性,在性能上所付出的代价。

如果优化RPC的性能,从而尽量减少网络调用,对于性能的影响呢?在这里,你首先需要了解一次RPC的调用都经过了哪些步骤,因为这样,你才可以针对这些步骤中可能存在的性能瓶颈点提出优化方案。步骤如下:

  1. 在一次RPC调用过程中,客户端首先会将调用的类名、方法名、参数名、参数值等信息,序列化成二进制流;
  2. 然后客户端将二进制流,通过网络发送给服务端;
  3. 服务端接收到二进制流之后,将它反序列化,得到需要调用的类名、方法名、参数名和参数值,再通过动态代理的方式,调用对应的方法得到返回值;
  4. 服务端将返回值序列化,再通过网络发送给客户端;
  5. 客户端对结果反序列化之后,就可以得到调用的结果了。

从这张图中你可以看到,有网络传输的过程,也有将请求序列化和反序列化的过程, 所以,如果要提升RPC框架的性能,需要从网络传输和序列化两方面来优化。

如何提升网络传输性能

在网络传输优化中,你首要做的,是选择一种高性能的I/O模型。所谓I/O模型,就是我们处理I/O的方式。而一般单次I/O请求会分为两个阶段,每个阶段对于I/O的处理方式是不同的。

首先,I/O会经历一个等待资源的阶段,比方说,等待网络传输数据可用,在这个过程中我们对I/O会有两种处理方式:

阻塞。指的是在数据不可用时,I/O请求一直阻塞,直到数据返回;
非阻塞。指的是数据不可用时,I/O请求立即返回,直到被通知资源可用为止。
然后是使用资源的阶段,比如说从网络上接收到数据,并且拷贝到应用程序的缓冲区里面。在这个阶段我们也会有两种处理方式:

同步处理。指的是I/O请求在读取或者写入数据时会阻塞,直到读取或者写入数据完成;
异步处理。指的是I/O请求在读取或者写入数据时立即返回,当操作系统处理完成I/O请求,并且将数据拷贝到用户提供的缓冲区后,再通知应用I/O请求执行完成。
将这两个阶段的四种处理方式,做一些排列组合,再做一些补充,就得到了我们常见的五种I/O模型:

  1. 同步阻塞I/O
  2. 同步非阻塞I/O
  3. 同步多路I/O复用
  4. 信号驱动I/O
  5. 异步I/O

这五种I/O模型,你需要理解它们的区别和特点,不过在理解上你可能会有些难度,所以我来做个比喻,方便你理解。

我们来把I/O过程比喻成烧水倒水的过程,等待资源(就是烧水的过程),使用资源(就是倒水的过程):

  1. 如果你站在炤台边上一直等着(等待资源)水烧开,然后倒水(使用资源),那么就是同步阻塞I/O;
  2. 如果你偷点儿懒,在烧水的时候躺在沙发上看会儿电视(不再时时刻刻等待资源),但是还是要时不时的去看看水开了没有,一旦水开了,马上去倒水(使用资源),那么这就是同步非阻塞I/O;
  3. 如果你想要洗澡,需要同时烧好多壶水,那你就在看电视的间隙去看看哪壶水开了(等待多个资源),哪一壶开了就先倒哪一壶,这样就加快了烧水的速度,这就是同步多路I/O复用;
  4. 不过你发现自己总是跑厨房去看水开了没,太累了,于是你考虑给你的水壶加一个报警器(信号),只要水开了就马上去倒水,这就是信号驱动I/O;
  5. 最后一种就高级了,你发明了一个智能水壶,在水烧好后自动就可以把水倒好,这就是异步I/O。
  6. 这五种I/O模型中最被广泛使用的是多路I/O复用,Linux系统中的select、epoll等系统调用都是支持多路I/O复用模型的,Java中的高性能网络框架Netty默认也是使用这种模型。所以,我们可以选择它。

总结:

网络I/O模型和序列化方式的选择,它们是实现高并发RPC框架的要素,总结起来有三个要点:

  • 1.选择高性能的I/O模型,这里我推荐使用同步多路I/O复用模型;
  • 2.调试网络参数,这里面有一些经验值的推荐。比如将tcp_nodelay设置为true,也有一些参数需要在运行中来调试,比如接受缓冲区和发送缓冲区的大小,客户端连接请求缓冲队列的大小(back log)等等;
  • 3.序列化协议依据具体业务来选择。如果对性能要求不高,可以选择JSON,否则可以从Thrift和Protobuf中选择其一。
原文地址:https://www.cnblogs.com/wt645631686/p/13538590.html