Dubbo框架中的异步调用是发生在服务消费端的,异步调用实现是基于 NIO 的非阻塞能力实现并行调用,服务消费端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小,如下图8-2-2-1是Dubbo异步调用链路概要流程:
图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:
图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线程会接受到执行结果,然后回调注册的回调函数;可知整个异步调用过程中调用线程不会被阻塞,在发起远程调用后,就可以做自己的事情去了。