1. 问题描述
在我运营 RPC 的过程中,“如何提升吞吐量”是我与业务团队经常讨论的问题。
记得之前业务团队反馈过这样一个问题:我们的 TPS 始终上不去,压测的时候 CPU 压到
40%~50% 就再也压不上去了,TPS 也不会提高,问我们这里有没有什么解决方案可以提
升业务的吞吐量?
之后我是看了下他们服务的业务逻辑,发现他们的业务逻辑在执行较为耗时的业务逻辑的基
础上,又同步调用了好几个其它的服务。由于这几个服务的耗时较长,才导致这个服务的业
务逻辑耗时也长,CPU 大部分的时间都在等待,并没有得到充分地利用,因此 CPU 的利用
率和服务的吞吐量当然上不去了。
2. 如何解决
在 RPC 框架中,使用 CompletableFuture 可以提高吞吐量,因为它允许我们以非阻塞的方式编写并发代码。CompletableFuture 是 Java 8 引入的一种异步编程工具,它实现了 Future 接口并提供了一系列流式操作来组合和处理多个异步任务。以下是在 RPC 框架中使用 CompletableFuture 提高吞吐量的一些建议:
- 异步调用:在调用远程服务时,使用 CompletableFuture 发起异步调用而不是同步调用。这样,客户端线程不需要等待远程服务响应,可以立即返回并处理其他任务。例如:
CompletableFuture<Response> futureResponse = rpcClient.callAsync(request);
- 并行执行:利用 CompletableFuture.allOf 方法并行执行多个远程调用。这样,客户端可以同时等待多个服务的响应,从而提高整体吞吐量。例如:
CompletableFuture<Response> futureResponse1 = rpcClient.callAsync(request1);
CompletableFuture<Response> futureResponse2 = rpcClient.callAsync(request2);
CompletableFuture<Void> combinedFuture = CompletableFuture.allOf(futureResponse1, futureResponse2);
- 流式操作:使用 CompletableFuture 提供的 thenApply、thenCompose 和 exceptionally 等方法对异步任务进行流式操作。这有助于简化代码结构,提高代码可读性。例如:
futureResponse
.thenApply(response -> processResponse(response))
.exceptionally(ex -> handleException(ex));
- 异步回调:在远程调用返回结果后,使用 thenAccept、thenRun 或者 whenComplete 方法注册回调函数,而不是在调用线程中等待结果。这样,客户端线程可以继续执行其他任务,提高吞吐量。例如:
futureResponse
.thenAccept(response -> processResponse(response))
.exceptionally(ex -> handleException(ex));
- 优化线程池:合理设置线程池的大小以适应不同的负载情况。例如,可以根据硬件资源和服务的特点为 RPC 客户端和服务器分别配置合适的线程池。此外,可以为 CompletableFuture 提供自定义的 Executor 来执行异步任务,以更好地控制并发行为。例如:
ExecutorService executor = Executors.newFixedThreadPool(10);
CompletableFuture.supplyAsync(() -> rpcClient.call(request), executor);
通过以上方法,RPC 框架中的 CompletableFuture 可以更好地利用并发性能,从而提高吞吐量。在实际应用中,具体实现可能会根据所使用的 RPC 框架和具体需求有所不同。
3. 总结
今天我们主要讲解了如果通过 RPC 的异步去压榨单机的吞吐量。
影响到 RPC 调用的吞吐量的主要原因就是服务端的业务逻辑比较耗时,并且 CPU 大部分
时间都在等待而没有去计算,导致 CPU 利用率不够,而提升单机吞吐量的最好办法就是使
用异步 RPC。
RPC 框架的异步策略主要是调用端异步与服务端异步。调用端的异步就是通过 Future 方式
实现异步,调用端发起一次异步请求并且从请求上下文中拿到一个 Future,之后通过Future 的 get 方法获取结果,如果业务逻辑中同时调用多个其它的服务,则可以通过
Future 的方式减少业务逻辑的耗时,提升吞吐量。服务端异步则需要一种回调方式,让业
务逻辑可以异步处理,之后调用 RPC 框架提供的回调接口,将最终结果异步通知给调用
端。
另外,我们可以通过对 CompletableFuture 的支持,实现 RPC 调用在调用端与服务端之
间的完全异步,同时提升两端的单机吞吐量。
其实,RPC 框架也可以有其它的异步策略,比如集成 RxJava,再比如 gRPC 的
StreamObserver 入参对象,但 CompletableFuture 是 Java8 原生提供的,无代码入侵
性,并且在使用上更加方便。如果是 Java 开发,让 RPC 框架支持 CompletableFuture 可
以说是最佳的异步解决方案。