7

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

 3 years ago
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 异步调用的原理。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK