

Dubbo 同步调用太慢,也许你可以试试异步处理
source link: http://developer.51cto.com/art/202101/643383.htm
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Hello,大家好,我是楼下小黑哥~
今天原本是想解析一道朋友在大厂面试的时候碰到问题:
「Dubbo 异步调用的底层原理是什么?」
之前其实听说过 Dubbo 异步调用,但是没有在实际业务中使用过,所以使用方法比较陌生。
再加上 Dubbo 2.7 版本对于异步调用进行了一些修改,网上找到的一些资料也比较老,所以今天先写一篇介绍一下 Dubbo 2.7 版本之后的异步调用使用方式。
后续我们从源码出发再介绍一下 Dubbo 底层原理。
异步调用
我们平常大部分都是使用 Dubbo 的同步调用,即调用 Dubbo 请求之后,调用线程将会阻塞,直到服务提供者返回结果。
那相反,Dubbo 异步调用就不会阻塞调用线程,那么在服务提供者返回结果这段时间,我们就可以执行其他业务逻辑。
下面我们从代码示例,来学习一下如何使用 Dubbo 异步调用。
PS:下面例子 Dubbo 版本为 2.7。
第一种方式
Dubbo 异步调用是针对方法级别,所以我们需要对引用接口中指定方法做一些专门的配置。
异步调用配置其实与普通 xml服务引用配置类似,只不过我们还需要增加一个 dubbo:method将指定方法配置成异步调用。
示例 xml 配置如下:
服务引用配置完成之后,此时如果直接调用这个方法,将会立即返回 null,内部将会异步执行服务端调用逻辑。
// 此调用会立即返回null
String world = asyncService.sayHello("world");
// 画个时序图
如果我们需要获取服务提供者返回的结果,那么此时需要借助 RpcContext。这个类是 Dubbo 中专门用于保存 「RPC」 调用过程中一些关键信息。
因此我们可以借助这个类可以获取到 「RPC」 很多信息,这次我们主要使用下面的方法获取 CompletableFuture。
RpcContext.getContext().getCompletableFuture()
CompletableFuture 是 JDK1.8 之后提供的异步任务增强类,我们可以直接调用其 get 方法直接获取返回结果。
// 此调用会立即返回null
String world = asyncService.sayHello("world");
// 拿到调用的Future引用,当结果返回后,会被通知和设置到此Future
CompletableFuture helloFuture = RpcContext.getContext().getCompletableFuture();
helloFuture.get();
这里需要注意一点。调用get 方法之后,线程就会被阻塞,「直到服务端返回结果或者服务调用超时」。
另外如果不想线程被阻塞,我们可以使用 whenComplete,添加回调方法,然后异步处理返回结果。
// 此调用会立即返回null
String world = asyncService.sayHello("world");
// 拿到调用的Future引用,当结果返回后,会被通知和设置到此Future
CompletableFuture helloFuture = RpcContext.getContext().getCompletableFuture();
// 为Future添加回调
helloFuture.whenComplete((retValue, exception) -> {
if (exception == null) {
System.out.println("return value: " + retValue);
} else {
exception.printStackTrace();
}
});
从上面的例子我们可以看到, Dubbo 消费端异步调用借助了JDK 提供的 CompletableFuture,这个类非常强大,提供的方法也非常多。
小黑哥之前写过一篇文章,比较完整的介绍了 CompletableFuture的用法,感兴趣可以深入学习一下。
// TODO 文章
上面的方式我们使用 xml引用服务,不过现在很多同学应该直接使用 Dubbo 注解引用服务。
如果想直接使用注解方式,其实也非常简单,只要使用 @Method注解即可。
配置方法如下:
@Reference(interfaceClass = AsyncService.class,
timeout = 1000,
methods = {@Method(name = "sayHello", async = true)})
private AsyncService asyncService;
第二种方式
第一种方式我们还需要额外修改 Dubbo 相关配置,相对来说比较繁琐。那第二种方式就不需要做额外配置了,它只要使用 RpcContext#asyncCall就可以直接完成异步调用。
示例代码如下:
// 使用 asyncCall 异步调用
CompletableFuture f = RpcContext.getContext().asyncCall(() -> asyncService.sayHello("async call request"));
// get 将会一直阻塞到服务端返回,或者直到服务调用超时
System.out.println("async call returned: " + f.get());
// 异步调用,不关心服务端返回
RpcContext.getContext().asyncCall(() -> {
asyncService.sayHello("one way call request1");
});
这种方式返回依然是 CompletableFuture对象,操作方式就如同第一种方式。
第三种方式
终于到了最后一种方式了,这种方式与上面两种方式都不太一样,其完全不需要借助RpcContext就可以完成,开发流程与普通 Dubbo 服务一样。
首先需要服务提供者事先定义 CompletableFuture 签名的服务:
public interface AsyncService {
CompletableFuture sayHello(String name);
}
「注意接口的返回类型是 CompletableFuture 。」
服务端接口实现逻辑如下:
public class AsyncServiceImpl implements AsyncService {
private static Logger logger = LoggerFactory.getLogger(AsyncServiceImpl.class);
@Override
public CompletableFuture sayHello(String name) {
return CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "async response from provider.";
});
}
}
服务端需要使用 CompletableFuture 完成业务逻辑。
消费端这时就不需要借助了 RpcContext,可以直接调用服务提供者。
// 调用直接返回CompletableFuture
CompletableFuture future = asyncService.sayHello("async call request");
// 增加回调
future.whenComplete((v, t) -> {
if (t != null) {
t.printStackTrace();
} else {
System.out.println("Response: " + v);
}
});
// 早于结果输出
System.out.println("Executed before response return.")
这种方式对于调用者来就比较方便,无需引入其他对象,可以像使用同步的方式使用异步调用。
其他参数
上面介绍了三种的 Dubbo 异步调用的使用方式,下面主要介绍一下异步调用涉及其他参数。
sent
我们可以在 dubbo:method 设置:
也可以在注解中设置:
@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 将会等待消息发送发出才会返回,否则将会抛出异常。
return
Dubbo 异步调用默认将会创建 Future 对象,然后设置到 RpcContext 中。那我们如果不关心返回值,只想单纯的异步执行,那我们可以配置 return="false",以此减少 Future 对象的创建和管理成本。
总结
今天的文章介绍三种 Dubbo 异步调用的使用方式:
第一种需要修改 Dubbo xml 配置文件或者注解,然后再通过 RpcContext获取异步 Future对象。
第二种无需修改任何配置文件,我们可以直接通过RpcContext#asyncCall异步完成方法调用,然后获取异步 Future对象。
第三种无需修改任何配置文件,也无需使用 RpcContext,我们需要定义一个返回值是 CompletableFuture方法,然后服务端与消费端正常开发即可。
这三种方式,第三种对于消费者使用起来最方便,不过个人觉得服务提供者开发起来比较麻烦。
第二种相当于第一种,无需修改配置文件,个人觉得还是比较方便的,所以小黑哥还是倾向于使用第二种方式。
好了,今天的文章就到这里了,下次我们详细聊聊 Dubbo 异步调用的原理。
Recommend
-
49
价值 | 思考 | 共鸣 简评:对于 Go 来说一直以来依赖包的版本控制上没有一个好的方案,尽管社区诞生了不下十余个解决该问题的工具,但一直以来没有一个...
-
16
在平时的开发当中我们总是会遇到各种各样的问题,比如说内存泄漏、死锁、CPU等。遇到问题不可怕,关键是我们如何去排查这些...
-
13
值得一看:C#同步方法中如何调用异步方法?前言我在写代码的时候(.net core)有时候会碰到void方法里,调用async方法并且Wait,而且我还看到别人这么写了。而且我这么写的时候,编译器没有提示任何警告。但是看了dudu的文章:
-
10
TJ君最近有点惆怅,为啥呢?最近TJ君发现GitHub越来越难上了,有时候经常出现无法访问的情况: 想必很多人也会跟TJ君有同样的烦恼,毕竟GitHub是一个学习编程的好地方,这样断断续续的使用,极度影响吾等程序猿的用户体验,那,该怎么办!? 既然...
-
7
NEJ Build太慢怎么办?试试MOOC NEJ吧,只需两步,提升70%构建性能! 由于历史包袱,中国大学MOOC(简称中M)的主站工程生产构建时间大约在21分钟,构建采用NEJ Build,由于NEJ当前已无人维护且在部门内应用较多,因此中M通过fork原工程在保留N...
-
9
计算速度太慢?试试 lru_cache 装饰器发布于 今天 14:14 众所周知,python语言是相当好用的,但是它的执行性能也是相对其他语言比较慢的。还好python提供了一个非常...
-
3
dotnet 在析构函数调用 ThreadLocal 也许会抛出对方已释放 我在不自量力做一个数组池,就是为了减少使用 System.Buffers.dll 程序集,然而在数组池里面,所用的 ThreadLocal 类型,在我对象析构函数进行归还数组时,抛出了无法访问已释放对象...
-
5
在日常的开发工作中,我们经常会有一些任务需要异步执行。那么如何获取异步任务的结果呢?常见的解决方案有两种:1,任务异步执行,调用方同步获取结果(本篇文章需要讨论的);2,通过Callback 或 Listener进行处理。 针对需要同步获取异步计算结果的需求,...
-
5
asp.net 应用程序中同步方法调用异步方法无响应解决方法 ...
-
6
在py3里同步/异步混合使用 httpx 调用 Posted 2023-07-05 | stdout 比如开发需求是请求一个 http API,得到数据,解析一下返回,那么一般的做法是封装一个方法,比如 import httpx...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK