目录
二、RPC(Remote Procedure Call)远程过程调用
一、场景分析
来思考这样一个场景: 你的垂直电商系统的QPS已经达到了每秒2万次,在做了服务化拆分之后,由于我们把业务逻辑都拆分到了单独部署的服务中,那么假设你在完成一次完整的请求时需要调用4~5次服务,计算下来,RPC服务需要承载大概每秒10万次的请求。而你该如何设计RPC框架承载如此大的请求量呢?
- 选择合适的网络模型,有针对性地调整网络参数优化网络传输性能;
- 选择合适的序列化方式,以提升封包、解包的性能
二、RPC(Remote Procedure Call)远程过程调用
1.RMI
也许觉得只有像Dubbo、Grpc、Thrift这些新兴的框架才算是RPC框架,其实严格来说,Java原生就有一套远程调用框架叫做RMI(Remote Method Invocation),它可以让Java程序通过网络调用另一台机器上的Java对象的方法。它是一种远程调用的方法,也是J2EE时代大名鼎鼎的EJB的实现基础。
2.Web Service
它也可以认为是RPC的一种实现方式。它的优势是使用HTTP+SOAP协议,保证了调用可以跨语言、跨平台。只要你支持HTTP协议,可以解析XML,那么就能够使用Web Service。在我看来,由于它使用XML封装数据,数据包大,性能还是比较差。
3.调用分析
电商系统中商品详情页面需要商品数据、评论数据还有店铺数据,如果在一体化的架构中,你只需要从商品库、评论库和店铺库获取数据就可以了,不考虑缓存的情况下有三次网络请求。
但是如果独立出商品服务、评论服务和店铺服务之后,那么就需要分别调用这三个服务,而这三个服务又会分别调用各自的数据库,这就是六次网络请求。如果你服务拆分得更细粒度,那么多出的网络调用就会越多,请求的延迟就会更长,而这就是你为了提升系统的扩展性在性能上所付出的代价。
RPC步骤如下
- 在一次RPC调用过程中,客户端首先会将调用的类名、方法名、参数名、参数值等信息,序列化成二进制流;
- 然后客户端将二进制流通过网络发送给服务端;
- 服务端接收到二进制流之后将它反序列化,得到需要调用的类名、方法名、参数名和参数值,再通过动态代理的方式调用对应的方法得到返回值;
- 服务端将返回值序列化,再通过网络发送给客户端;
- 客户端对结果反序列化之后,就可以得到调用的结果了
三、如何提升网络传输性能
1.等待资源的阶段
等待资源的阶段I/O会经历一个等待资源的阶段,在这个过程中我们对I/O会有两种处理方式:
- 阻塞。指的是在数据不可用时I/O请求一直阻塞,直到数据返回;
- 非阻塞。指的是数据不可用时I/O请求立即返回,直到被通知资源可用为止
2.使用资源的阶段
比如说从网络上接收到数据,并且拷贝到应用程序的缓冲区里面。在这个阶段我们也会有两种处理方式:
- 同步处理。指的是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复用;
- 不过你发现自己总是跑厨房去看水开了没,太累了,于是你考虑给你的水壶加一个报警器(信号),只要水开了就马上去倒水,这就是信号驱动I/O;
- 最后一种就高级了,你发明了一个智能水壶,在水烧好后自动就可以把水倒好,这就是异步I/O
这五种I/O模型中最被广泛使用的是多路I/O复用, Linux系统中的select、epoll等系统调用都是支持多路I/O复用模型的,Java中的高性能网络框架Netty默认也是使用这种模型。
四、选择合适的序列化方式
- 如果对于性能要求不高,在传输数据占用带宽不大的场景下可以使用JSON作为序列化协议;
- 如果对于性能要求比较高,那么使用Thrift或者Protobuf都可以。而Thrift提供了配套的RPC框架,所以想要一体化的解决方案,你可以优先考虑Thrift;
- 在一些存储的场景下,比如说你的缓存中存储的数据占用空间较大,那么你可以考虑使用Protobuf替换JSON作为存储数据的序列化方式