人生如逆旅,我亦是行人。前文为大家分享了Dubbo的线程模型以及线程池支持,趁热打铁,今天就继续和大家一起分享下Dubbo的同步、异步请求原理。
一、异步请求demo
public class AsyncConsumerApplication {
public static void main(String[] args) {
ReferenceConfig<DemoService> reference = new ReferenceConfig<>();
reference.setInterface(DemoService.class);
// 开始异步请求
reference.setAsync(true);
reference.setTimeout(5000);
DubboBootstrap bootstrap = DubboBootstrap.getInstance();
bootstrap
.application(new ApplicationConfig("dubbo-demo-api-consumer"))
.registry(new RegistryConfig("zookeeper://127.0.0.1:2181"))
.reference(reference)
.start();
// 发起rpc调用,此时获取到的返回值为null
String message = ReferenceConfigCache.getCache().get(reference).sayHello("dubbo");
System.out.println("result::" + message);
CompletableFuture<Object> future = RpcContext.getContext().getCompletableFuture();
future.whenComplete((v, t) -> {
if (t != null) {
t.printStackTrace();
} else {
// 异步请求完成后,则会打印"Hello dubbo"
System.out.println("async result::" + v);
}
});
}
}
为了讲解方便,依然使用API发起异步请求,首先通过reference.setAsync(true)设置开启异步请求,通过ReferenceConfigCache正常的发起RPC请求,此时获取到的结果message为空。通过RpcContext获取到异步结果对应的CompletableFuture,当future complete后,打印返回值。看到这,大家应该都很熟悉了,没错,就是java8的CompletableFuture,支持很多特性,这里就不展示讲了,值得注意的是,Dubbo其实是在2.7版本以上才支持CompletableFuture,本文基于2.7.5版本。
二、异步调用流程分析
2.1. RpcContext.getContext().getCompletableFuture()
查看RpcContext.getContext().getCompletableFuture()实现如下:
public <T> CompletableFuture<T> getCompletableFuture() {
return FutureContext.getContext().getCompletableFuture();
}
可以看到内部是通过FutureContext.getContext().getCompletableFuture()方法获取到对应的异步future,debug如下:
这里的future类型为FutureAdapter
2.2. FutureAdapter
查看FutureAdapter实现如下:
public FutureAdapter(CompletableFuture<AppResponse> future) {
this.appResponseFuture = future;
future.whenComplete((appResponse, t) -> {
if (t != null) {
if (t instanceof CompletionException) {
t = t.getCause();
}
this.completeExceptionally(t);
} else {
if (appResponse.hasException()) {
this.completeExceptionally(appResponse.getException());
} else {
this.complete((V) appResponse.getValue());
}
}
});
}
可以看到其实没做什么工作,只是对future进行了一层封装,查看FutureAdapter调用链可以发现,是在AbstractInvoker invoke中被调用的。
2.3. AbstractInvoker-invoke
public Result invoke(Invocation inv) throws RpcException {
AsyncRpcResult asyncResult= (AsyncRpcResult) doInvoke(invocation);
RpcContext.getContext().setFuture(new FutureAdapter(asyncResult.getResponseFuture()));
return asyncResult;
}
protected abstract Result doInvoke(Invocation invocation) throws Throwable;
可以看到,invoke方法中,先调用抽象方法doInvoke获取到result,然后对Result中的ResponseFuture进行封装(说实话这里没看太懂,为啥不直接返回ResponseFuture呢),查看doInvke实现如下:
又看到熟悉的面孔了,DubboInvker,前文讲解客户端调用流程时曾提到过,主要用来进行服务调用的,下面就让我们一起再回顾下其doInvoke方法
2.4. DubboInvker-doInvoke
@Override
protected Result doInvoke(final Invocation invocation) throws Throwable {
... ...
boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
int timeout = getUrl().getMethodPositiveParameter(methodName, TIMEOUT_KEY, DEFAULT_TIMEOUT);
if (isOneway) {
boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
currentClient.send(inv, isSent);
return AsyncRpcResult.newDefaultAsyncResult(invocation);
} else {
ExecutorService executor = getCallbackExecutor(getUrl(), inv);
CompletableFuture<AppResponse> appResponseFuture =
currentClient.request(inv, timeout, executor).thenApply(obj -> (AppResponse) obj);
FutureContext.getContext().setCompatibleFuture(appResponseFuture);
AsyncRpcResult result = new AsyncRpcResult(appResponseFuture, inv);
result.setExecutor(executor);
return result;
}
}
- 首先通过isOneway参数用来做逻辑判断,这个参数用来判断需要不需要回调,即是否是单向请求。
- 然后通过getCallbackExecutor方法获取执行请求的线程池。
- 最后把请求响应结果交给放入AsyncRpcResult中。
看到这不知道大家有没有一个疑惑,为什么没看到判断当前RPC请求是否是异步的代码呢?难道Dubbo所有的请求都是异步的?当然不是。
2.5. getCallbackExecutor
protected ExecutorService getCallbackExecutor(URL url, Invocation inv) {
ExecutorService sharedExecutor = ExtensionLoader.getExtensionLoader(ExecutorRepository.class).getDefaultExtension().getExecutor(url);
if (InvokeMode.SYNC == RpcUtils.getInvokeMode(getUrl(), inv)) {
return new ThreadlessExecutor(sharedExecutor);
} else {
return sharedExecutor;
}
}
当前如果是同步请求,使用ThreadlessExecutor线程池,否则使用sharedExecutor。
2.6. ThreadlessExecutor
关于ThreadlessExecutor有必要介绍下它的特点,官方文档说明如下:
- 和其他的线程池最大的区别是它不管理任何线程。
- 提交给它的任务不会由单独的线程去调度执行。
- 被存放在阻塞队列中的任务只有被调用了waitAndDrain()才会执行,并且执行任务的线程是当前调用了waitAndDrain()方法的线程。
什么意思呢?也就是说,哪个线程来调用AsyncRpcResult get方法获取RPC调用结果,就由哪个线程来执行线程池的里的任务。查看AsyncRpcResult get实现如下:
@Override
public Result get() throws InterruptedException, ExecutionException {
if (executor != null && executor instanceof ThreadlessExecutor) {
ThreadlessExecutor threadlessExecutor = (ThreadlessExecutor) executor;
threadlessExecutor.waitAndDrain();
}
return responseFuture.get();
}
可以看到,当前线程池类型如果是ThreadlessExecutor时候,调用threadlessExecutor.waitAndDrain()来获取RPC调用结果,否则调用 responseFuture.get()获取异步请求结果。
三、小结
本篇文章主要为大家分析了Dubbo 2.7.5版本异步RCP的实现流程,同时顺带着讲解下了同步调用的原理,由于Dubbo底层采用Netty通信,本质上都是异步的I/O请求,是不会有同步调用的实现。Dubbo内部借助了ThreadlessExecutor线程池,实现了同步阻塞调用(其实也可以换成其他阻塞队列)。其他RPC不知道大家没有没了解过是怎么实现同步调用的呢?欢迎评论区讨论分享。