本文转载自微信公众号《11:30的小黑》作者小黑哥楼下。转载本文请联系小黑11:30公众号。大家好,我是楼下的小黑哥~今天想分析一个朋友在大厂面试遇到的问题:“Dubbo异步调用的底层原理是什么?”之前其实听说过Dubbo的异步调用。但是还没有在实际业务中使用过,所以使用方法比较陌生。另外,Dubbo2.7版本对异步调用做了一些修改,网上找到的一些资料比较陈旧,所以今天写一篇文章,介绍一下Dubbo2.7版本以后异步调用的使用方法。后面我们将从源码入手介绍Dubbo的底层原理。异步调用我们大多数人通常使用Dubbo的同步调用,即调用Dubbo请求后,调用线程会阻??塞,直到服务提供者返回结果。相反,Dubbo的异步调用不会阻塞调用线程,我们可以在服务提供者返回结果的同时执行其他业务逻辑。下面我们通过代码示例学习如何使用Dubbo进行异步调用。PS:下例中的Dubbo版本为2.7。Dubbo异步调用的第一种方式是针对方法级别的,所以我们需要在引用接口中对指定的方法做一些特殊的配置。异步调用的配置其实和普通的xml服务引用配置类似,但是我们还需要增加一个dubbo:method来将指定的方法配置为异步调用。示例xml配置如下:/dubbo:reference>服务引用配置完成后,如果此时直接调用该方法,会立即返回null,内部异步执行服务端调用逻辑。//这个调用会返回nullStringworld=asyncService.sayHello("world");//画一个时序图如果我们需要获取服务提供者返回的结果,这时候就需要用到RpcContext。这个类在Dubbo中专门用于保存“RPC”调用过程中的一些关键信息。因此,我们可以通过这个类来获取大量的“RPC”信息。这次我们主要使用下面的方法来获取CompletableFuture。RpcContext.getContext().getCompletableFuture()CompletableFuture是JDK1.8之后提供的异步任务增强类,我们可以直接调用它的get方法直接获取返回结果。//这个调用会立即返回nullStringworld=asyncService.sayHello("world");//获取调用的Future引用,返回结果时会通知并设置到这个FutureCompletableFuturehelloFuture=RpcContext。getContext().getCompletableFuture();你好未来.get();这里有一点需要注意。调用get方法后,线程会被阻塞,“直到服务器返回结果或者服务调用超时”。另外,如果我们不希望线程被阻塞,我们可以使用whenComplete,添加一个回调方法,然后异步处理返回的结果。//这个调用会立即返回nullStringworld=asyncService.sayHello("world");//获取调用的Future引用,返回结果时会通知并设置到这个FutureCompletableFuturehelloFuture=RpcContext。getContext().getCompletableFuture();//给Future添加回调helloFuture.whenComplete((retValue,exception)->{if(exception==null){System.out.println("returnvalue:"+retValue);}else{exception.printStackTrace();}});从上面的例子我们可以看出,Dubbo消费者的异步调用使用了JDK提供的CompletableFuture。这个类非常强大,提供了很多方法。小黑哥之前写过一篇文章,比较完整的介绍了CompletableFuture的用法。如果你有兴趣,你可以了解更多。//上面TODO文章中我们使用的是xml引用服务,但是现在很多同学应该是直接使用Dubbo注解引用服务。如果想直接使用注解方法,其实很简单,使用@Method注解即可。配置方法如下:@Reference(interfaceClass=AsyncService.class,timeout=1000,methods={@Method(name="sayHello",async=true)})privateAsyncServiceasyncService;第二种方法和第一种方法需要额外修改dubbo相关配置,比较繁琐。第二种方法不需要额外配置,只要使用RpcContext#asyncCall就可以直接完成异步调用。示例代码如下://使用asyncCall异步调用CompletableFuturef=RpcContext.getContext().asyncCall(()->asyncService.sayHello("asynccallrequest"));//get会被阻塞直到serverreturns,或者直到服务调用超时System.out.println("asynccallreturned:"+f.get());//异步调用,不关心server返回)->{asyncService.sayHello("onewaycallrequest1");});该方法返回一个CompletableFuture对象,操作方法同第一种方法。第三种方法终于是最后一种了。这种方法不同于上述两种方法。完全不用RpcContext就可以完成,开发过程和普通的Dubbo服务一样。首先,服务提供者需要提前定义带有CompletableFuture签名的服务:publicinterfaceAsyncService{CompletableFuturesayHello(Stringname);}》注意接口的返回类型是CompletableFuture。服务器接口实现逻辑如下:);}catch(InterruptedExceptione){e.printStackTrace();}return"asyncresponsefromprovider.";});}}服务器需要使用CompletableFuture来完成业务逻辑。此时消费者不需要使用RpcContext,直接调用服务提供者即可。//调用直接返回CompletableFutureCompletableFuturefuture=asyncService.sayHello("asynccallrequest");//添加回调future.whenComplete((v,t)->{if(t!=null){t.printStackTrace();}else{System.out.println("Response:"+v);}});//比result早输出需要引入其他对象,可以用同步的方式使用异步调用。其他参数上面介绍了Dubbo异步调用的三种使用方式。下面主要介绍异步调用涉及的其他参数。sent我们可以在dubbo:method中设置:也可以在annotation中设置:@Reference(interfaceClass=XXX.class,version=AnnotationConstants.VERSION,timeout=1000,methods={@Method(name="greeting",timeout=3000,retries=1,sent=false)})默认sent=false,Dubbo会将消息放入IO队列,然后立即返回。如果此时崩溃,则消息可能无法发送到服务器。那么如果我们设置为sent=true,Dubbo会等待消息发送完毕再返回,否则会抛出异常。returnDubbo异步调用会默认创建一个Future对象,然后将其设置为RpcContext。如果我们不关心返回值,只想异步执行,那么我们可以配置return="false"来降低创建和管理Future对象的成本。总结今天的文章介绍三种使用Dubbo异步调用的方式:第一种需要修改Dubboxml配置文件或者注解,然后通过RpcContext获取一个异步Future对象。第二种不需要修改任何配置文件。我们可以直接通过RpcContext#asyncCall异步完成方法调用,然后获取异步Future对象。第三种不需要修改任何配置文件,也不需要使用RpcContext。我们需要定义一个返回值是CompletableFuture方法,然后才能正常开发server和consumer。这三种方式中,第三种最方便消费者使用,但个人感觉服务商开发起来比较麻烦。第二种方法等同于第一种方法,不需要修改配置文件。个人觉得还是比较方便的,所以还是比较喜欢用第二种方式。好了,今天的文章就到这里,下次我们会详细说说Dubbo的异步调用的原理。