之前简单写了一下dubbo线程模型,提到了Dubbo底层是基于NIO的Netty框架实现的,通过IO线程池和Work线程池实现了请求和业务处理之间的异步从而提升性能。
这篇文章要写的是Dubbo对于消费端调用和服务端接口业务逻辑处理的异步,在2.7版本中Dubbo实现了基于CompletableFuture的异步支持。
Dubbo消费端实现
Dubbo调用的实现——DubboInvoker
其实无论是否是异步调用,Dubbo消费者底层都是会使用CompletableFuture实现对服务端接口的异步调用。
那为什么我们开发的时候默认都是阻塞调用呢?下面的AsyncToSyncInvoker是关键
Dubbo异步调用实现——AsyncToSyncInvoker
- 如果开启了异步调用Dubbo底层代码中会直接调用CompletableFuture.get()方法阻塞当前调用线程等待结果返回。
- 不开启异步调用时,并没有阻塞调用get方法而是直接返回异步结果,后面就需要手动调用Future.get()方法获取结果。
如何使用异步调用
在@DubboReference注解中配置async=true即可,该属性默认为false。开启异步后调用Dubbo接口服务时,服务会立刻返回结果,但是结果为null。我们需要通过RpcContext获取到该服务调用的CompletableFuture实例,然后在需要使用结果时显示调用CompletableFuture.get()方法阻塞等待获取真正的结果。
需要注意,在调用接口后立刻获取该接口CompletableFuture,因为如果后续还有异步调用,该Future会被覆盖
@DubboReference(async = true)
private EOrderFacade eOrderFacade;
public void test() throws InterruptedException {
Result<OOrder> result = eOrderFacade.getOrder(GetOrderParam.builder().orderId(String.valueOf(8095950037325722953l)).build());
CompletableFuture<Result<OOrder>> future = RpcContext.getContext().getCompletableFuture();
Result<OOrder> realResult = future.get();
}
Dubbo服务端异步
服务端异步为用户提供了一种解决ThreadPool Full的方案,简单说只有在当我们的服务会因为大量请求发生IO阻塞导致ThreadPool Full的情况下使用服务端接口异步才有用。但其实对于Dubbo这底层种基于Event-Driven实现的框架而言本身请求和业务执行就是非阻塞的纯异步方式,所以盲目使用服务端异步反而可能变成画蛇添足,毕竟引入额外的业务线程池后又多了两次线程上下文的切换。
当然,如果我们的系统真的发生了ThreadPool Full的情况,经过分析后判断发生瓶颈的不是CPU,那么可以选择直接调高Dubbo worker的线程数目,或者可以尝试对耗时比较高的接口采用服务端异步来提升性能。