RPC框架支持异步
前言
如何提升RPC调用的吞吐量?影响到RPC调用的吞吐量的根本原因是什么呢?
在使用RPC的过程中,性能和吞吐量上不去根本原因就是由于处理 RPC 请求比较耗时,因此 CPU 大部分的时间都在等待而没有去计算,从而导致 CPU 的利用率不够。
要提升吞吐量,其实关键就两个字:“异步”。我们的 RPC 框架要做到完全异步化,实现全异步 RPC。换句话说 RPC 框架需要支持完全的异步调用!
RPC 框架的异步策略主要是调用端异步与服务端异步。调用端的异步就是通过 Future 方式实现异步,调用端发起一次异步请求并且从请求上下文中拿到一个 Future,之后通过 Future 的 get 方法获取结果,如果业务逻辑中同时调用多个其它的服务,则可以通过 Future 的方式减少业务逻辑的耗时,提升吞吐量。服务端异步则需要一种回调方式,让业务逻辑可以异步处理,之后调用 RPC 框架提供的回调接口,将最终结果异步通知给调用端。
调用端异步
调用端最常用的方式就是返回Future对象的Future方式,或者入参为Callback对象的回调方式。而Future方式可以说是最简单的一种异步方式了。我们发起一次异步请求并且从请求上下文中拿到一个Future,之后我们就可以调用Future的get方法获取结果。这是怎么实现的呢?
一次RPC调用的本质就是调用端向服务端发送一条请求消息,服务端收到消息后进行处理,处理之后响应给调用端一条响应消息,调用端收到响应消息之后再进行处理,最后将最终的返回值返回给动态代理。
对于调用端来说,向服务端发送请求消息与接收服务端发送过来的响应消息,这两个处理过程是两个完全独立的过程,这两个过程甚至在大多数情况下都不在一个线程中进行。因此对于 RPC 框架,无论是同步调用还是异步调用,调用端的内部实现都是异步的。一般调用端发送的每条消息都一个唯一的消息标识,实际上调用端向服务端发送请求消息之前会先创建一个 Future,并会存储这个消息标识与这个 Future 的映射,动态代理所获得的返回值最终就是从这个 Future 中获取的;当收到服务端响应的消息时,调用端会根据响应消息的唯一标识,通过之前存储的映射找到对应的 Future,将结果注入给那个 Future,再进行一系列的处理逻辑,最后动态代理从 Future 中获得到正确的返回值。
所谓的同步调用,不过是 RPC 框架在调用端的处理逻辑中主动执行了这个 Future 的 get 方法,让动态代理等待返回值;而异步调用则是 RPC 框架没有主动执行这个 Future 的 get 方法,用户可以从请求上下文中得到这个 Future,自己决定什么时候执行这个 Future 的 get 方法。
服务端异步
RPC服务端接收到请求的二进制消息之后会根据协议进行拆包解包,之后将完整的消息进行解码并反序列化,获得到入参参数之后再通过反射执行业务逻辑。对二进制消息数据包拆解包的处理一般在处理网络IO的线程中,如果网络通信框架使用的是Netty框架,那么对二进制包的处理、解码与反序列化等过程是在IO线程中,而服务端的业务逻辑应该交给专门的业务线程池处理,以防止由于业务逻辑处理得过慢而影响到网络IO的处理。
服务端的异步可以让RPC框架支持CompletableFuture,实现RPC调用在调用端与服务端之间完全异步。
CompletableFuture是Java8原生支持的。试想一下,假如RPC框架能够支持CompletableFuture,我现在发布一个RPC服务,服务接口定义的返回值是CompletableFuture对象,整个调用过程会分为这样几步:
- 服务调用方发起RPC调用,直接拿到返回值CompletableFuture对象,之后就不需要任何额外的与RPC框架相关的操作了(如我刚才讲解Future方式时需要通过请求上下文获取Future的操作),直接就可以进行异步处理;
- 在服务端的业务逻辑中创建一个返回值CompletableFuture对象,之后服务端真正的业务逻辑完全可以在一个线程池中异步处理,业务逻辑完成之后再调用这个CompletableFuture对象的complete方法,完成异步通知;
- 调用端在收到服务端发送过来的响应之后,RPC框架再自动地调用调用端拿到的那个返回值CompletableFuture对象的complete方法,这样一次异步调用就完成了。
通过对CompletableFuture的支持,RPC框架可以真正地做到在调用端与服务端之间完全异步,同时提升了调用端与服务端的两端的单机吞吐量,并且CompletableFuture是Java8原生支持,业务逻辑中没有任何代码入侵性,这是不是很酷炫了?
扩展
RPC 框架也可以有其它的异步策略,比如集成 RxJava,再比如 gRPC 的 StreamObserver 入参对象,但CompletableFuture 是 Java8 原生提供的,无代码入侵性,并且在使用上更加方便。如果是 Java 开发,让 RPC 框架支持CompletableFuture 可以说是最佳的异步解决方案。