Dubbo框架中的异步调用是发生在服务消费端的,异步调用实现是基于 NIO 的非阻塞能力实现并行调用,服务消费端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小,如下图8-2-2-1是Dubbo异步调用链路概要流程:




dubbo 异步调用返回空_channelread0会被调用两次


图8-2-2-1 异步调用链路图

  • 如上图图8-2-2-1步骤1当服务消费端发起RPC调用时候使用的用户线程,步骤2请求会被转换为IO线程具体向远程服务提供方发起远程调用
  • 步骤2的IO线程使用NIO发起远程调用,用户线程使用步骤3创建了一个Future对象,然后使用步骤4把其设置到RpcContext中。
  • 然后步骤5用户线程则可以在某个时间从RpcContext中获取设置的Futrue对象,并且使用步骤6设置回调函数,然后用户线程就返回了。
  • 步骤7当服务提供方返回结果后,调用方线程模型中的线程池中线程则会把结果使用步骤8写入到Future,然后就会回调注册的回调函数。

如上介绍调用线程异步调用发起后会马上返回一个Future,并在Future上设置一个回调函数,然后调用线程就可以忙自己的事情去了,不需要同步等待服务提供方返回结果;当某时刻服务提供方返回结果时候,调用方的IO线程会把响应结果传递给Dubbo框架内部的线程池中的线程,后者则会回调注册的回调函数,可知整个过程中发起异步调用的用户线程是不会被阻塞的。

首先考虑在一个线程A中通过rpc请求获取服务B和服务C的数据然后基于两者结果做一些事情;在同步rpc调用情况下,线程A需要调用服务B后需要等待服务B结果返回后,才可以对服务C发起调用,然后等服务C结果返回后才可以结合服务B和C的结果做一件事,如下图图8-2-2-2:


dubbo 异步调用返回空_dubbo 异步调用返回空_02


图8-2-2-2 同步调用图

如上图8-2-2-2线程A同步获取服务B结果后,在同步调用服务C获取结果,可见在同步调用情况下线程A必须顺序的对多个服务请求进行调用,可知调用线程必须要等待,这显然浪费资源,在Dubbo中使用异步调用可以避免这个问题,代码示例如下:

public class APiAsyncConsumerForCompletableFuture2 {    public static void main(String[] args) throws InterruptedException, ExecutionException {        // 1.创建服务引用对象实例        ReferenceConfig referenceConfig = new ReferenceConfig();        // 2.设置应用程序信息        referenceConfig.setApplication(new ApplicationConfig("first-dubbo-consumer"));        // 3.设置服务注册中心        referenceConfig.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));        // 4.设置服务接口和超时时间        referenceConfig.setInterface(GreetingService.class);        referenceConfig.setTimeout(5000);        // 5.设置服务分组与版本        referenceConfig.setVersion("1.0.0");        referenceConfig.setGroup("dubbo");        // 6. 设置为异步        referenceConfig.setAsync(true);        // 7.引用服务        GreetingService greetingService = referenceConfig.get();        // 8.异步执行,并设置回调        System.out.println(greetingService.sayHello("hello"));        CompletableFuture future1 = RpcContext.getContext().getCompletableFuture();        future1.whenComplete((v, t) -> {            if (t != null) {                t.printStackTrace();            } else {                System.out.println(Thread.currentThread().getName() + " " + v);            }        });        // 9.异步执行,并设置回调        System.out.println(greetingService.sayHello("world"));        CompletableFuture future2 = RpcContext.getContext().getCompletableFuture();        future2.whenComplete((v, t) -> {            if (t != null) {                t.printStackTrace();            } else {                System.out.println(Thread.currentThread().getName() + " " + v);            }        });        // 10. 挂起线程        Thread.currentThread().join();    }}
  • 如上代码6我们设置调用为异步,代码8则发起远程过程调用,该方法会马上返回null值;然后我们通过RpcContext.getContext().getCompletableFuture()来获取该调用的实际结果所在的future对象,然后在其上通过whenComplete方法设置了一个回调函数;
  • 代码9同理也发起一个远程过程调用,然后获取该次请求对应的future对象,然后在其上设置了回调函数;
  • 需要注意的是在异步调用时候代码8和9都是会马上返回的,不会阻塞调用线程,所以这里两次远程过程调用时并行进行的;
  • 当远端返回执行结果后,消费端的IO线程会接受到执行结果,然后回调注册的回调函数;可知整个异步调用过程中调用线程不会被阻塞,在发起远程调用后,就可以做自己的事情去了。