Dubbo 同步调用太慢,也许你可以试试异步处理

开发 前端
异步调用配置其实与普通 xml服务引用配置类似,只不过我们还需要增加一个 dubbo:method将指定方法配置成异步调用。

[[378981]]

 本文转载自微信公众号「小黑十一点半」,作者楼下小黑哥 。转载本文请联系小黑十一点半公众号。

Hello,大家好,我是楼下小黑哥~

今天原本是想解析一道朋友在大厂面试的时候碰到问题:

「Dubbo 异步调用的底层原理是什么?」

之前其实听说过 Dubbo 异步调用,但是没有在实际业务中使用过,所以使用方法比较陌生。

再加上 Dubbo 2.7 版本对于异步调用进行了一些修改,网上找到的一些资料也比较老,所以今天先写一篇介绍一下 Dubbo 2.7 版本之后的异步调用使用方式。

后续我们从源码出发再介绍一下 Dubbo 底层原理。

异步调用

我们平常大部分都是使用 Dubbo 的同步调用,即调用 Dubbo 请求之后,调用线程将会阻塞,直到服务提供者返回结果。

那相反,Dubbo 异步调用就不会阻塞调用线程,那么在服务提供者返回结果这段时间,我们就可以执行其他业务逻辑。

下面我们从代码示例,来学习一下如何使用 Dubbo 异步调用。

PS:下面例子 Dubbo 版本为 2.7。

第一种方式

Dubbo 异步调用是针对方法级别,所以我们需要对引用接口中指定方法做一些专门的配置。

异步调用配置其实与普通 xml服务引用配置类似,只不过我们还需要增加一个 dubbo:method将指定方法配置成异步调用。

示例 xml 配置如下:

  1. <dubbo:reference id="asyncService" interface="org.apache.dubbo.samples.governance.api.AsyncService"
  2.       <dubbo:method name="sayHello" async="true" /> 
  3. </dubbo:reference> 

服务引用配置完成之后,此时如果直接调用这个方法,将会立即返回 null,内部将会异步执行服务端调用逻辑。

  1. // 此调用会立即返回null 
  2. String world = asyncService.sayHello("world"); 

// 画个时序图

如果我们需要获取服务提供者返回的结果,那么此时需要借助 RpcContext。这个类是 Dubbo 中专门用于保存 「RPC」 调用过程中一些关键信息。

因此我们可以借助这个类可以获取到 「RPC」 很多信息,这次我们主要使用下面的方法获取 CompletableFuture。

  1. RpcContext.getContext().getCompletableFuture() 

CompletableFuture 是 JDK1.8 之后提供的异步任务增强类,我们可以直接调用其 get 方法直接获取返回结果。

  1. // 此调用会立即返回null 
  2. String world = asyncService.sayHello("world"); 
  3. // 拿到调用的Future引用,当结果返回后,会被通知和设置到此Future 
  4. CompletableFuture<String> helloFuture = RpcContext.getContext().getCompletableFuture(); 
  5. helloFuture.get(); 

这里需要注意一点。调用get 方法之后,线程就会被阻塞,「直到服务端返回结果或者服务调用超时」。

另外如果不想线程被阻塞,我们可以使用 whenComplete,添加回调方法,然后异步处理返回结果。

  1. // 此调用会立即返回null 
  2. String world = asyncService.sayHello("world"); 
  3. // 拿到调用的Future引用,当结果返回后,会被通知和设置到此Future 
  4. CompletableFuture<String> helloFuture = RpcContext.getContext().getCompletableFuture(); 
  5. // 为Future添加回调 
  6. helloFuture.whenComplete((retValue, exception) -> { 
  7.     if (exception == null) { 
  8.         System.out.println("return value: " + retValue); 
  9.     } else { 
  10.         exception.printStackTrace(); 
  11.     } 
  12. }); 

从上面的例子我们可以看到, Dubbo 消费端异步调用借助了JDK 提供的 CompletableFuture,这个类非常强大,提供的方法也非常多。

小黑哥之前写过一篇文章,比较完整的介绍了 CompletableFuture的用法,感兴趣可以深入学习一下。

// TODO 文章

上面的方式我们使用 xml引用服务,不过现在很多同学应该直接使用 Dubbo 注解引用服务。

如果想直接使用注解方式,其实也非常简单,只要使用 @Method注解即可。

配置方法如下:

  1. @Reference(interfaceClass = AsyncService.class, 
  2.         timeout = 1000, 
  3.         methods = {@Method(name = "sayHello", async = true)}) 
  4. private AsyncService asyncService; 

第二种方式

第一种方式我们还需要额外修改 Dubbo 相关配置,相对来说比较繁琐。那第二种方式就不需要做额外配置了,它只要使用 RpcContext#asyncCall就可以直接完成异步调用。

示例代码如下:

  1. // 使用  asyncCall 异步调用 
  2. CompletableFuture<String> f = RpcContext.getContext().asyncCall(() -> asyncService.sayHello("async call request")); 
  3. // get 将会一直阻塞到服务端返回,或者直到服务调用超时 
  4. System.out.println("async call returned: " + f.get()); 
  5.  
  6. // 异步调用,不关心服务端返回 
  7. RpcContext.getContext().asyncCall(() -> { 
  8.     asyncService.sayHello("one way call request1"); 
  9. }); 

这种方式返回依然是 CompletableFuture对象,操作方式就如同第一种方式。

第三种方式

终于到了最后一种方式了,这种方式与上面两种方式都不太一样,其完全不需要借助RpcContext就可以完成,开发流程与普通 Dubbo 服务一样。

首先需要服务提供者事先定义 CompletableFuture 签名的服务:

  1. public interface AsyncService { 
  2.     CompletableFuture<String> sayHello(String name); 

「注意接口的返回类型是 CompletableFuture。」

服务端接口实现逻辑如下:

  1. public class AsyncServiceImpl implements AsyncService { 
  2.     private static Logger logger = LoggerFactory.getLogger(AsyncServiceImpl.class); 
  3.  
  4.     @Override 
  5.     public CompletableFuture<String> sayHello(String name) { 
  6.         return CompletableFuture.supplyAsync(() -> { 
  7.             try { 
  8.                 Thread.sleep(10000); 
  9.             } catch (InterruptedException e) { 
  10.                 e.printStackTrace(); 
  11.             } 
  12.             return "async response from provider."
  13.         }); 
  14.     } 
  15.  

服务端需要使用 CompletableFuture 完成业务逻辑。

消费端这时就不需要借助了 RpcContext,可以直接调用服务提供者。

  1. // 调用直接返回CompletableFuture 
  2. CompletableFuture<String> future = asyncService.sayHello("async call request"); 
  3. // 增加回调 
  4. future.whenComplete((v, t) -> { 
  5.     if (t != null) { 
  6.         t.printStackTrace(); 
  7.     } else { 
  8.         System.out.println("Response: " + v); 
  9.     } 
  10. }); 
  11. // 早于结果输出 
  12. System.out.println("Executed before response return."

这种方式对于调用者来就比较方便,无需引入其他对象,可以像使用同步的方式使用异步调用。

其他参数

上面介绍了三种的 Dubbo 异步调用的使用方式,下面主要介绍一下异步调用涉及其他参数。

sent

我们可以在 dubbo:method 设置:

  1. <dubbo:method name="findFoo" async="true" sent="true" /> 

也可以在注解中设置:

  1. @Reference(interfaceClass = XXX.class, 
  2.         version = AnnotationConstants.VERSION, 
  3.         timeout = 1000, 
  4.         methods = {@Method(name = "greeting", timeout = 3000, retries = 1, sent = false)}) 

默认情况下sent=false, Dubbo 将会把消息放入 IO 队列,然后立刻返回。那这时如果宕机,消息就有可能没有发送给服务端。

那如果我们将其设置成 sent=true,Dubbo 将会等待消息发送发出才会返回,否则将会抛出异常。

return

Dubbo 异步调用默认将会创建 Future 对象,然后设置到 RpcContext 中。那我们如果不关心返回值,只想单纯的异步执行,那我们可以配置 return="false",以此减少 Future 对象的创建和管理成本。

  1. <dubbo:method name="findFoo" async="true" return="false" /> 

总结

今天的文章介绍三种 Dubbo 异步调用的使用方式:

第一种需要修改 Dubbo xml 配置文件或者注解,然后再通过 RpcContext获取异步 Future对象。

第二种无需修改任何配置文件,我们可以直接通过RpcContext#asyncCall异步完成方法调用,然后获取异步 Future对象。

第三种无需修改任何配置文件,也无需使用 RpcContext,我们需要定义一个返回值是 CompletableFuture方法,然后服务端与消费端正常开发即可。

这三种方式,第三种对于消费者使用起来最方便,不过个人觉得服务提供者开发起来比较麻烦。

第二种相当于第一种,无需修改配置文件,个人觉得还是比较方便的,所以小黑哥还是倾向于使用第二种方式。

好了,今天的文章就到这里了,下次我们详细聊聊 Dubbo 异步调用的原理。

 

责任编辑:武晓燕 来源: 小黑十一点半
相关推荐

2009-10-20 16:48:30

C#委托

2022-07-01 08:14:28

Dubbo异步代码

2009-12-21 14:10:26

WCF异步调用

2009-11-09 10:50:30

WCF异步调用

2009-07-01 13:58:00

JavaScript异

2009-11-06 15:54:15

WCF异步调用

2009-07-01 14:31:01

JavaScript异

2009-07-01 14:37:14

JavaScript异

2009-07-01 14:23:46

JavaScript异

2010-02-22 13:28:05

WCF异步调用

2009-07-01 14:05:23

JavaScript异

2009-08-21 11:24:16

C#异步调用

2009-12-07 14:35:42

WCF异步调用

2012-10-29 10:59:27

Windows 8

2009-12-07 14:26:47

WCF异步调用

2010-01-11 17:24:19

VB.NET异步调用

2011-03-02 08:57:22

jQueryJavaScript

2021-03-29 09:26:44

SpringBoot异步调用@Async

2022-06-17 11:10:43

PandasPolarsPython

2020-11-18 19:11:26

iOSFlutterNative
点赞
收藏

51CTO技术栈公众号