|
|
任性的砖头 · 【Go】解析X509_x509 ...· 3 年前 · |
|
|
瘦瘦的猴子 · python - socket ...· 3 年前 · |
|
|
眼睛小的西装 · 立即创建 Azure 免费帐户 | ...· 3 年前 · |
Dubbo 的远程调用中大致可以分为以上 4 种调用方式:
注意: Dubbo 中的调用方式可以分为两大类,oneway 和 twoway ,对于 Dubbo 协议来说,会对这两种方式做分别处理,对于非 Dubbo 协议不会特别区分。
Dubbo 2.6.x 的异步实现是针对消费端异步,只需指定调用方式为异步调用,并在需要结果的地方从 RpcContext 中取出 Future 获取结果即可。
1 |
<!-- 设置 async = true,表示异步调用。默认是 false,同步调用 --> |
1 |
String hello = demoService.sayHello("world"); // call remote method |
Dubbo 2.6.x 提供了一定的异步编程能力,但其异步方式存在以下问题:
Dubbo 2.7.x 对异步实现进行了改造,引入了相关的接口和实现类,异步实现需要这些相关的基础模型配合完成。下面我们先对基础模型进行介绍。
在 Dubbo 2.6.x 中统一使用
RpcResult
表示调用结果,Dubbo 2.7.x 中废弃了
RpcResult
,采用以下三个对象表示结果状态。
表示的是一个异步的、未完成的RPC调用,是在调用链中实际传递的对象。
AppResponse 表示的是服务端返回的具体响应,相当于 Dubbo 2.6.x 中的 RpcResult 。其子类是 DecodeableRpcResult。
CompletableFuture 表示的是服务端返回的结果,由调用端创建,用于封装 AppResponse 对象。其中 DefaultFuture 继承该类。
三者关系:AppResponse -> CompletableFuture -> AsyncRpcResult ,下面我们对其进行介绍。
AppResponse
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53 public class AppResponse implements Result {
private static final long serialVersionUID = -6925924956850004727L;
// 响应结果
private Object result;
// 返回的异常信息
private Throwable exception;
// 返回的附加信息
private Map<String, Object> attachments = new HashMap<>();
public AppResponse() {
}
public AppResponse(Object result) {
this.result = result;
}
public AppResponse(Throwable exception) {
this.exception = exception;
}
@Override
public Object recreate() throws Throwable {
// 1 异常处理
if (exception != null) {
// fix issue#619
try {
// get Throwable class
Class clazz = exception.getClass();
while (!clazz.getName().equals(Throwable.class.getName())) {
clazz = clazz.getSuperclass();
}
// get stackTrace value
Field stackTraceField = clazz.getDeclaredField("stackTrace");
stackTraceField.setAccessible(true);
Object stackTrace = stackTraceField.get(exception);
if (stackTrace == null) {
exception.setStackTrace(new StackTraceElement[0]);
}
} catch (Exception e) {
// ignore
}
throw exception;
}
// 2 结果
return result;
}
// 省略其它方法 getter/setter 方法
}
AppResponse是调用的实际返回类型,相当于 Dubbo 2.6.x 中的RpcResult,理论上不需要实现Result接口,这样做是为了兼容。DecodeableRpcResult
属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 public class DecodeableRpcResult extends AppResponse implements Codec, Decodeable {
private static final Logger log = LoggerFactory.getLogger(DecodeableRpcResult.class);
// 通道
private Channel channel;
// 序列化类型
private byte serializationType;
// 序列化相关的输入流
private InputStream inputStream;
// 响应对象
private Response response;
// 调用信息
private Invocation invocation;
// 标志是否已经解码
private volatile boolean hasDecoded;
public DecodeableRpcResult(Channel channel, Response response, InputStream is, Invocation invocation, byte id) {
Assert.notNull(channel, "channel == null");
Assert.notNull(response, "response == null");
Assert.notNull(is, "inputStream == null");
this.channel = channel;
this.response = response;
this.inputStream = is;
this.invocation = invocation;
this.serializationType = id;
}
}解码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97 +--- DecodeableRpcResult
@Override
public void decode() throws Exception {
// 没有解码,则进行解码
if (!hasDecoded && channel != null && inputStream != null) {
try {
// 解码
decode(channel, inputStream);
} catch (Throwable e) {
if (log.isWarnEnabled()) {
log.warn("Decode rpc result failed: " + e.getMessage(), e);
}
response.setStatus(Response.CLIENT_ERROR);
response.setErrorMessage(StringUtils.toString(e));
} finally {
hasDecoded = true;
}
}
}
---
@Override
public Object decode(Channel channel, InputStream input) throws IOException {
if (log.isDebugEnabled()) {
Thread thread = Thread.currentThread();
log.debug("Decoding in thread -- [" + thread.getName() + "#" + thread.getId() + "]");
}
// 1 确定序列化方式,用于反序列化
ObjectInput in = CodecSupport.getSerialization(channel.getUrl(), serializationType)
.deserialize(channel.getUrl(), input);
// 2 读取一个 byte 的标志位,其值可能有 6 种
byte flag = in.readByte();
// 3 根据标志位判断当前结果中包含的信息,并调用不同的方法进行处理
switch (flag) {
case DubboCodec.RESPONSE_NULL_VALUE:
break;
case DubboCodec.RESPONSE_VALUE:
handleValue(in);
break;
case DubboCodec.RESPONSE_WITH_EXCEPTION:
handleException(in);
break;
case DubboCodec.RESPONSE_NULL_VALUE_WITH_ATTACHMENTS:
handleAttachment(in);
break;
case DubboCodec.RESPONSE_VALUE_WITH_ATTACHMENTS:
// 根据 RpcInvocation 中记录的返回值类型读取返回结果,并设置到当前类的 result 字段
handleValue(in);
// 读取附加信息并设置到当前类的 attachmetns 中
handleAttachment(in);
break;
case DubboCodec.RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS:
handleException(in);
handleAttachment(in);
break;
default:
throw new IOException("Unknown result flag, expect '0' '1' '2' '3' '4' '5', but received: " + flag);
}
if (in instanceof Cleanable) {
((Cleanable) in).cleanup();
}
return this;
}
---
/**
* 处理结果
*
* @param in
* @throws IOException
*/
private void handleValue(ObjectInput in) throws IOException {
try {
// 1 返回结果类型
Type[] returnTypes;
if (invocation instanceof RpcInvocation) {
returnTypes = ((RpcInvocation) invocation).getReturnTypes();
} else {
returnTypes = RpcUtils.getReturnTypes(invocation);
}
// 2 根据返回结果类型获取结果
Object value = null;
if (ArrayUtils.isEmpty(returnTypes)) {
// This almost never happens?
value = in.readObject();
} else if (returnTypes.length == 1) {
value = in.readObject((Class<?>) returnTypes[0]);
} else {
value = in.readObject((Class<?>) returnTypes[0], returnTypes[1]);
}
// 3 设置结果 result
setValue(value);
} catch (ClassNotFoundException e) {
rethrow(e);
}
}
DecodeableRpcResult主要对响应结果进行解码,从字节流中获取数据对象。CompletableFuture
AsyncRpcResult
AsyncRpcResult是在调用链中实际传递的对象,表示一个异步的,未完成的RPC调用。注意,它并不是实际的调用结果,AppResponse才是业务结果。属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 public class AsyncRpcResult implements Result {
private static final Logger logger = LoggerFactory.getLogger(AsyncRpcResult.class);
// 当回调发生时,RpcContext 可能已经被更改。即执行 AsyncRpcResult 上添加的回调方法的线程可能先后处理过多个不同的 AsyncRpcResult 。
// 因此,我们应该保留当前RpcContext实例的引用,并在回调执行之前恢复它。
private RpcContext storedContext;
private RpcContext storedServerContext;
// 此次 RPC 调用关联的线程池。该值用来作为判断是否是 ThreadlessExecutor 行为
// oneway 一般不会设置该值,twoway 会设置该值。
private Executor executor;
// 此次 RPC 调用关联的 Invocation 对象
private Invocation invocation;
// 请求返回的对象 (由使用 AsyncRpcResult 方创建)
private CompletableFuture<AppResponse> responseFuture;
// 在构造方法中除了接收发送请求返回的 CompletableFuture<AppResponse> 对象,还会保存当前的 RPC 上下文
public AsyncRpcResult(CompletableFuture<AppResponse> future, Invocation invocation) {
this.responseFuture = future;
this.invocation = invocation;
this.storedContext = RpcContext.getContext();
this.storedServerContext = RpcContext.getServerContext();
}
}需要说明的是,
responseFuture属性不仅针对 Dubbo 协议,HTTP等协议调用返回结果也是CompletableFuture对象,都是由调用端创建。区别在于 Dubbo 协议一般返回的是DefaultFuture对象,而 HTTP 等协议会构造一个CompletableFuture对象,我们会在下面内容提到。此外,针对 oneway 调用方式,由于不关心调用结果,因此会创建一个完整状态的CompletableFuture对象。而针对 twoway 调用方式,由于关心调用结果,因此在调用的时候会创建DefaultFuture对象。获取结果
获取 CompletableFuture
1
2
3
4 +--- AsyncRpcResult
public CompletableFuture<AppResponse> getResponseFuture() {
return responseFuture;
}获取 AppResponse
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 +--- AsyncRpcResult
public Result getAppResponse() {
try {
// 如果完成,则获取 AppResponse
if (responseFuture.isDone()) {
return responseFuture.get();
}
} catch (Exception e) {
// This should not happen in normal request process;
logger.error("Got exception when trying to fetch the underlying result from AsyncRpcResult.");
throw new RpcException(e);
}
// 获取默认的 AppResponse
return createDefaultValue(invocation);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 +--- AsyncRpcResult
/**
* 该方法将始终在最大 timeout 等待之后返回:
* 1. 如果value在超时前返回,则正常返回。
* 2. 如果timeout之后没有返回值,则抛出TimeoutException。
*
* @return
* @throws InterruptedException
* @throws ExecutionException
*/
@Override
public Result get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
// 针对 ThreadlessExecutor 的特殊处理,这里调用 waitAndDrain() 等待响应
if (executor != null && executor instanceof ThreadlessExecutor) {
ThreadlessExecutor threadlessExecutor = (ThreadlessExecutor) executor;
threadlessExecutor.waitAndDrain();
}
return responseFuture.get(timeout, unit);
}
@Override
public Result get() throws InterruptedException, ExecutionException {
if (executor != null && executor instanceof ThreadlessExecutor) {
ThreadlessExecutor threadlessExecutor = (ThreadlessExecutor) executor;
threadlessExecutor.waitAndDrain();
}
return responseFuture.get();
}其中
ThreadlessExecutor是一个特殊的线程池,主要用来解决同步调用模式下的响应,是对线程模型的优化,我们在下一篇文章中进行详细说明。获取结果
1
2
3
4
5
6
7
8
9
10
11
12 +--- AsyncRpcResult
@Override
public Object recreate() throws Throwable {
RpcInvocation rpcInvocation = (RpcInvocation) invocation;
// 1 如果是服务端的异步实现,则从上下文中取。
// 为什么? 因为接口返回的结果是 CompletableFuture,属于异步范畴(服务端的异步),和消费端异步类似。
if (InvokeMode.FUTURE == rpcInvocation.getInvokeMode()) {
return RpcContext.getContext().getFuture();
}
// 2 获取 AppResponse 中的结果
return getAppResponse().recreate();
}
AsyncRpcResult.recreate()方法是获取结果的方法,也就是从AppResponse中获取结果。添加回调
回调是 Dubbo 2.7.x 异步改造的重要角色,
AsyncRpcResult支持添加回调方法,而这个回调方法会被包装一层并注册到 responseFuture 上,具体实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13 +--- AsyncRpcResult
public Result whenCompleteWithContext(BiConsumer<Result, Throwable> fn) {
// 在responseFuture之上注册回调
this.responseFuture = this.responseFuture.whenComplete((v, t) -> {
// 将当前线程的 RpcContext 记录到临时属性中,然后将构造函数中存储的 RpcContext 设置到当前线程中,为后面的回调执行做准备
beforeContext.accept(v, t);
// 执行回调 (使用的 RpcContext 是回调所属服务方法的调用线程的 RpcContext)
fn.accept(v, t);
// 恢复线程原有的 RpcContext
afterContext.accept(v, t);
});
return this;
}在添加回调时,需要使用 beforeContext 和 afterContext 来保证执行回调的线程的 RpcContext 是最初创建
AsyncRpcResult对象的线程对应的 RpcContext,执行完回调后需要将执行回调的线程的 RpcContext 恢复到原有值。其中 beforeContext 用于保存执行回调线程的 RpcContext,并将最初创建AsyncRpcResult对象的线程的 RpcContext 临时设置到执行回调用线程中,为执行回调做准备。 afterContext 用于恢复执行回调用的线程原有的 RpcContext 。具体实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 +--- AsyncRpcResult
private RpcContext tmpContext;
private RpcContext tmpServerContext;
private BiConsumer<Result, Throwable> beforeContext = (appResponse, t) -> {
// 将当前线程的 RpcContext 记录到 tmpContext 中
tmpContext = RpcContext.getContext();
tmpServerContext = RpcContext.getServerContext();
// 将构造函数中存储的 RpcContext (也就是创建 AsyncRpcResult 线程的 RpcContext) 设置到当前线程中
RpcContext.restoreContext(storedContext);
RpcContext.restoreServerContext(storedServerContext);
};
private BiConsumer<Result, Throwable> afterContext = (appResponse, t) -> {
// 将当前线程的 RpcContext 恢复到原始值
RpcContext.restoreContext(tmpContext);
RpcContext.restoreServerContext(tmpServerContext);
};如此一来,
AsyncRpcResult就可以随意添加回调,无需担心 RpcContext 被污染。
AsyncRpcResult整个是为异步请求设计的,但是 Dubbo 中默认的请求方式是同步的,那么 Dubbo 又是如何支持同步调用的呢?Dubbo 进行服务引用时,在AbstractProtocol.refer()方法中,Dubbo 会将AbstractProtocol.protocolBindingRefer()方法实现返回的 Invoker 对象使用AsyncToSyncInvoker封装一层,该对象中的调用逻辑会对同步调用专门处理,我们在下面的内容中进行介绍。相比较而言,Dubbo 2.6.x 在 Dubbo 协议做了异步转同步处理,就是在调用时拿到DefaultFuture后立即阻塞等待结果。HTTP 协议就没有异步调用支持,而 Dubbo 2.7.x 使用了AbstractInvoker对 Future 功能进行统一支持,也就是 HTTP 协议也基本上支持了调用异步。Invoker
AsyncToSyncInvoker
AsyncToSyncInvoker描述了异步转同步的逻辑,是对AsyncRpcResult获取结果的补充,触发时机是在执行调用的时候。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53 public class AsyncToSyncInvoker<T> implements Invoker<T> {
// 服务引用的 Invoker
private Invoker<T> invoker;
public AsyncToSyncInvoker(Invoker<T> invoker) {
this.invoker = invoker;
}
@Override
public Class<T> getInterface() {
return invoker.getInterface();
}
@Override
public Result invoke(Invocation invocation) throws RpcException {
Result asyncResult = invoker.invoke(invocation);
try {
// 如果是同步调用,则调用 get() 方法,阻塞等待响应返回。
// 调用的是 AsyncRpcResult.get 方法,其底层调用的是 CompletableFuture 的 get 方法
if (InvokeMode.SYNC == ((RpcInvocation) invocation).getInvokeMode()) {
/**
* NOTICE!
* must call {@link java.util.concurrent.CompletableFuture#get(long, TimeUnit)} because
* {@link java.util.concurrent.CompletableFuture#get()} 被证明有严重的性能下降。
*/
asyncResult.get(Integer.MAX_VALUE, TimeUnit.MILLISECONDS);
}
} catch (InterruptedException e) {
throw new RpcException("Interrupted unexpectedly while waiting for remote result to return! method: " +
invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
} catch (ExecutionException e) {
Throwable t = e.getCause();
if (t instanceof TimeoutException) {
throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "Invoke remote method timeout. method: " +
invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
} else if (t instanceof RemotingException) {
throw new RpcException(RpcException.NETWORK_EXCEPTION, "Failed to invoke remote method: " +
invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
} else {
throw new RpcException(RpcException.UNKNOWN_EXCEPTION, "Fail to invoke remote method: " +
invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
}
} catch (Throwable e) {
throw new RpcException(e.getMessage(), e);
}
// 非同步调用直接返回调用结果 AsyncRpcResult
return asyncResult;
}
// 省略其它代码
}
AsyncToSyncInvoker是 Invoker 的装饰器,负责将异步调用转换成同步调用,也就是调用AsyncRpcResult中的CompletableFuture.get方法实现同步等待。相比 Dubbo 2.6.x 还是有很大区别的,Dubbo 2.6.x 使用 Future.get 功能阻塞等待,业务线程将处于阻塞等待状态,返回结果时需要消费端 Dubbo 线程池将结果写到 DefaultFuture 中,业务线程才能取出并返回。Dubbo 2.7.x 彻底优化了这种线程模型,关于优化的背景和实现会在下一篇文章中进行介绍,这里先了解即可。AbstractProxyInvoker
AbstractInvoker
构造方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 public abstract class AbstractInvoker<T> implements Invoker<T> {
protected final Logger logger = LoggerFactory.getLogger(getClass());
// 当前 Invoker 对象封装的业务接口类型
private final Class<T> type;
// 当前 Invoker 关联的 URL 对象
private final URL url;
// 当前 Invoker 关联的一些附加信息
private final Map<String, Object> attachment;
// 标志 Invoker 的状态
private volatile boolean available = true;
private AtomicBoolean destroyed = new AtomicBoolean(false);
// 构造方法
public AbstractInvoker(Class<T> type, URL url) {
this(type, url, (Map<String, Object>) null);
}
public AbstractInvoker(Class<T> type, URL url, String[] keys) {
this(type, url, convertAttachment(url, keys));
}
public AbstractInvoker(Class<T> type, URL url, Map<String, Object> attachment) {
if (type == null) {
throw new IllegalArgumentException("service type == null");
}
if (url == null) {
throw new IllegalArgumentException("service url == null");
}
this.type = type;
this.url = url;
this.attachment = attachment == null ? null : Collections.unmodifiableMap(attachment);
}
}invoke 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64 +--- AbstractInvoker
@Override
public Result invoke(Invocation inv) throws RpcException {
// if invoker is destroyed due to address refresh from registry, let's allow the current invoke to proceed
if (destroyed.get()) {
logger.warn("Invoker for service " + this + " on consumer " + NetUtils.getLocalHost() + " is destroyed, "
+ ", dubbo version is " + Version.getVersion() + ", this invoker should not be used any longer");
}
RpcInvocation invocation = (RpcInvocation) inv;
invocation.setInvoker(this);
if (CollectionUtils.isNotEmptyMap(attachment)) {
invocation.addObjectAttachmentsIfAbsent(attachment);
}
// 1 从上下文中取出 附加信息
Map<String, Object> contextAttachments = RpcContext.getContext().getObjectAttachments();
if (CollectionUtils.isNotEmptyMap(contextAttachments)) {
invocation.addObjectAttachments(contextAttachments);
}
// 2 设置调用模式 SYNC, ASYNC, FUTURE 。注意,oneway 调用方式
// 根据以下方式确定调用模式:
// 1) 根据返回值类型是否是 CompletableFuture ,或方法名是 $invokeAsync,则是 FUTURE 模式。这个属于服务端异步。
// 2) 根据 async 属性,如果设置 async=true ,则是 ASYNC 模式
// 3) 默认是 SYNC 模式
invocation.setInvokeMode(RpcUtils.getInvokeMode(url, invocation));
// 3 如果是异步调用的模式,则给本次调用添加一个唯一id (FUTURE 模式不属于)
RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
AsyncRpcResult asyncResult;
try {
// 4 调用子类实现的 doInvoker() 方法
asyncResult = (AsyncRpcResult) doInvoke(invocation);
// 对调用异常的处理
// 4.1 创建 CompletableFuture 对象,使用该对象包装 AppResponse 对象
// 4.2 使用 AppResponse 对象包装异常信息
// 4.3 使用 AsyncRpcResult 最后包装 CompletableFuture 对象
} catch (InvocationTargetException e) { // biz exception
Throwable te = e.getTargetException();
if (te == null) {
asyncResult = AsyncRpcResult.newDefaultAsyncResult(null, e, invocation);
} else {
if (te instanceof RpcException) {
((RpcException) te).setCode(RpcException.BIZ_EXCEPTION);
}
asyncResult = AsyncRpcResult.newDefaultAsyncResult(null, te, invocation);
}
} catch (RpcException e) {
if (e.isBiz()) {
asyncResult = AsyncRpcResult.newDefaultAsyncResult(null, e, invocation);
} else {
throw e;
}
} catch (Throwable e) {
asyncResult = AsyncRpcResult.newDefaultAsyncResult(null, e, invocation);
}
// 5 使用 FutureContext 保存 FutureAdapter,FutureAdapter 中会封装 AsyncRpcResult 中的 CompletableFuture 对象
RpcContext.getContext().setFuture(new FutureAdapter(asyncResult.getResponseFuture()));
return asyncResult;
}
AbstractInvoker的 invoke 方法是调用服务的模版方法,具体调用细节交给具体子类实现。设置附加信息到调用信息 Invocation 中。 设置调用模式
- FUTURE: 根据返回值类型是否是
CompletableFuture,或方法名是否是$invokeAsync,则是 FUTURE 模式。这个属于服务端异步。- ASYNC: 根据 async 属性,如果设置 async=true ,则是 ASYNC 模式。
- SYNC: 默认调用模式。
- 异步调用时,给本地调用添加一个唯一id,并设置到附加属性中。
- 调用具体子类 Invoker 对象的
doInvoke方法,不管是哪个子类实现,调用的结果都是AsyncRpcResult类型。- 如果调用异常,则对异常进行处理。
- 使用上下文保存 FutureAdapter ,其中 FutureAdapter 中会封装 AsyncRpcResult 中的 CompletableFuture 对象。在后续的链路中可以使用 Future 的异步功能。
相比 Dubbo 2.6.x,
AbstractInvoker的模版方法中实现了异步逻辑,也就是任何协议的服务调用都支持Future的异步功能(这里统一在上下文中设置 Future)。在 Dubbo 2.6.x 中非 Dubbo 协议大都不支持异步调用特性。Dubbo协议异步实现
消费端
DubboInvoker是 Dubbo 协议在消费端创建的 Invoker 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68 +--- DubboInvoker
@Override
protected Result doInvoke(final Invocation invocation) throws Throwable {
RpcInvocation inv = (RpcInvocation) invocation;
// 1 获取此次调用的方法名
final String methodName = RpcUtils.getMethodName(invocation);
// 2 向 Invocation 中添加附加信息,这里将 URL 的 path 和 version 添加到附加信息中
inv.setAttachment(PATH_KEY, getUrl().getPath());
inv.setAttachment(VERSION_KEY, version);
// 3 选择一个 ExchangeClient 实例
ExchangeClient currentClient;
if (clients.length == 1) {
currentClient = clients[0];
} else {
currentClient = clients[index.getAndIncrement() % clients.length];
}
try {
// 4 判断是否是 oneway 调用,不关心服务端的响应结果。调用后直接返回一个空 AsyncRpcResult
boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
// 根据调用方法名和配置,计算此次调用的超时时间
int timeout = calculateTimeout(invocation, methodName);
// 5 request() 方法会相应创建 DefaultFuture 对象以及检测超时的定时任务,而 send() 方法则不会创建这些东西。
if (isOneway) {
// 是否等待底层 NIO 将请求发出,等待时间默认 1s,1s未发送则抛出异常
boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
currentClient.send(inv, isSent);
// 返回已完成状态的 AsynRpcResult 即 AsynRpcResult -> CompletableFuture(已完成状态) -> AppResponse(空结果)
return AsyncRpcResult.newDefaultAsyncResult(invocation);
// 需要关注返回值的请求
} else {
// 5 获取处理响应的线程池
ExecutorService executor = getCallbackExecutor(getUrl(), inv);
// 6 调用 ExchangeClient.request() 方法,将 Invocation 包装成 Request 请求发送出去,同时会创建相应的 DefaultFuture 返回。
CompletableFuture<AppResponse> appResponseFuture =
// currentClient.request 返回的是 DefaultFuture,DefaultFuture 继承了 CompletableFuture 。
currentClient.request(inv, timeout, executor)
// 增加了一个回调,取出其中的 AppResponse 对象。
// thenApply 是一个回调,obj 是 上一个任务的结果。返回的 AppResponse 表示的是 服务端返回的具体响应。
.thenApply(obj -> (AppResponse) obj);
// save for 2.6.x compatibility, for example, TraceFilter in Zipkin uses com.alibaba.xxx.FutureAdapter
FutureContext.getContext().setCompatibleFuture(appResponseFuture);
// 7 这里将 CompletableFuture (其实是 DefaultFuture) 封装成 AsyncRpcResult 并返回
AsyncRpcResult result = new AsyncRpcResult(appResponseFuture, inv);
// 8 设置处理响应的线程池
result.setExecutor(executor);
// 9 返回调用
return result;
}
} catch (TimeoutException e) {
throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "Invoke remote method timeout. method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
} catch (RemotingException e) {
throw new RpcException(RpcException.NETWORK_EXCEPTION, "Failed to invoke remote method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
DubboInvoker进行远程调用时,会分别对 oneway 和 twoway 进行处理。- 处理 oneway 调用方式
不需要响应结果,直接使用客户端的 send 方法即可,该方法不会返回服务端的响应。
DubboInvoker会返回一个空结果的AsyncRpcResult对象给业务方。- 处理 twoway 调用方式
需要响应结果,使用客户端的 request 方法发送请求,该方法会创建并返回本次调用的 DefaultFuture 对象,当服务端响应时会更新 DefaultFuture 中的结果信息。此外,Dubbo 对线程模型进行了优化,可以指定处理响应的线程池,特别是同步调用的线程池,这个我们在下一篇文章中详细介绍。
相比较 Dubbo 2.6.x 中的
DefaultFuture,引入了CompletableFuture可以支持多异步场景,并且支持Future间的相互协调,此外提供了良好的回调方法,避免等待响应而阻塞。这是对ExchangeClient的改造,将 Dubbo 2.6.x 中异步编程接口都替换成了CompletableFuture。介绍完消费端的异步实现后,下面我们来看看服务端的异步实现。服务端
异步实现
服务提供端异步执行将阻塞的业务从 Dubbo 内部线程池切换到业务自定义线程,在一定程度上避免 Dubbo 线程池的过度占用,有助于避免不同服务间的互相影响。
定义
CompletableFuture签名接口
- 服务接口定义
1
2
3 public interface AsyncService {
CompletableFuture<String> sayHello(String name);
}- 服务实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 public class AsyncServiceImpl implements AsyncService {
@Override
public CompletableFuture<String> sayHello(String name) {
RpcContext savedContext = RpcContext.getContext();
// 建议为supplyAsync提供自定义线程池,避免使用JDK公用线程池
return CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "async response from provider.";
});
}
}通过
return CompletableFuture.supplyAsync(),业务执行已从 Dubbo 线程切换到业务线程,避免了对 Dubbo 线程池的阻塞。服务消费方
CompletableFuture签名接口是服务提供方的异步实现,消费端同普通消费一致,Dubbo 内部会根据调用的方法返回值类型等方式确定 调用模式 ,具体的在后面的源码部分介绍。结果
Dubbo 2.7 虽然支持了服务端的异步,但 Dubbo 的线程模型本身就是异步处理的方式,因此服务端的异步特性相对还是有点鸡肋的。
了解了服务端异步实现后,下面我们从代码层面分析,Dubbo 如何就
CompletableFuture签名服务接口方法实现异步的。HeaderExchangeHandler 处理请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 +--- HeaderExchangeHandler
void handleRequest(final ExchangeChannel channel, Request req) throws RemotingException {
Response res = new Response(req.getId(), req.getVersion());
// 省略异常处理代码
Object msg = req.getData();
try {
// 1 使用上层通道处理器处理消息,其实就是 DubboProtocol 中的 ExchangeHandler
CompletionStage<Object> future = handler.reply(channel, msg);
// 2 请求处理完成回调,将结果发送到对端
future.whenComplete((appResult, t) -> {
try {
if (t == null) {
res.setStatus(Response.OK);
res.setResult(appResult);
} else {
res.setStatus(Response.SERVICE_ERROR);
res.setErrorMessage(StringUtils.toString(t));
}
// 3 将处理后的结果发送到对端
channel.send(res);
} catch (RemotingException e) {
logger.warn("Send result to consumer failed, channel is " + channel + ", msg is " + e);
}
});
} catch (Throwable e) {
res.setStatus(Response.SERVICE_ERROR);
res.setErrorMessage(StringUtils.toString(e));
channel.send(res);
}
}相比较与 Dubbo 2.6.x 中的处理请求逻辑,这里使用了
CompletableFuture的完成回调,避免了阻塞等待请求完成。这得益于对通道处理 ExchangeHandler 的异步方法的改造,也就是 DubboProtocol 中的 ExchangeHandler 的实现。DubboProtocol 的处理器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 +--- DubboProtocol
private ExchangeHandler requestHandler = new ExchangeHandlerAdapter() {
@Override
public CompletableFuture<Object> reply(ExchangeChannel channel, Object message) throws RemotingException {
Invocation inv = (Invocation) message;
// 获取暴露的 Invoker,这里是 AbstractProxyInvoker
Invoker<?> invoker = getInvoker(channel, inv);
RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress());
// 如果服务端异步实现,这里返回的 result 是一个 AsyncRpcResult 类型对象,其中的 AppResonse 中的值并非 CompletableFuture 类型,而是 CompletableFuture 封装的 AppResponse
Result result = invoker.invoke(inv);
return result.thenApply(Function.identity());
}
// 省略其它代码
}DubboProtocol 中的 ExchangeHandler 的请求处理方法返回的是 CompletableFuture 对象,这同样是 Dubbo 2.7.x 中的改造,服务方法的结果统一包装成
CompletableFuture类型,在服务端的 Invoker 的执行逻辑中就可以体现这一点,下面我们就来看AbstractProxyInvoker。AbstractProxyInvoker
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39 +--- AbstractProxyInvoker
@Override
public Result invoke(Invocation invocation) throws RpcException {
try {
// 1 执行服务方法,如 DemoService.sayHello
Object value = doInvoke(proxy, invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments());
// 2 将方法结果以 CompletableFuture 对象形式进行封装。
// 注意:如果服务方法返回类型是 CompletableFuture ,则无需再使用 CompletableFuture 包装。这个针对服务端的异步实现。
CompletableFuture<Object> future = wrapWithFuture(value);
// 3 执行 future 逻辑
CompletableFuture<AppResponse> appResponseFuture = future.handle((obj, t) -> {
// 4 使用 AppResponse 封装实际结果
AppResponse result = new AppResponse();
if (t != null) {
if (t instanceof CompletionException) {
result.setException(t.getCause());
} else {
result.setException(t);
}
} else {
result.setValue(obj);
}
return result;
});
// 5 统一包装成 AsyncRpcResult 对象
return new AsyncRpcResult(appResponseFuture, invocation);
} catch (InvocationTargetException e) {
if (RpcContext.getContext().isAsyncStarted() && !RpcContext.getContext().stopAsync()) {
logger.error("Provider async started, but got an exception from the original method, cannot write the exception back to consumer because an async result may have returned the new thread.", e);
}
return AsyncRpcResult.newDefaultAsyncResult(null, e.getTargetException(), invocation);
} catch (Throwable e) {
throw new RpcException("Failed to invoke remote proxy method " + invocation.getMethodName() + " to " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
AbstractProxyInvoker的执行逻辑主要有四点,下面进行总结:- 执行服务接口实例方法,如 demoService.sayhello 方法。
- 将服务实例方法的调用结果包装成
CompletableFuture, 如果是服务端异步实现(服务接口方法返回类型是 CompletableFuture),则无需对结果进行包装,直接使用返回的CompletableFuture即可。- 执行
CompletableFuture的回调方法,将实际结果封装到 AppResponse 中。- 将返回结果包装成 AsyncRpcResult 对象。
HTTP 协议实现
消费端
Dubbo 中的 HTTP 协议在消费端创建的 Invoker 是一个
AbstractInvoker匿名对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 @Override
+--- AbstractProxyProtocol
protected <T> Invoker<T> protocolBindingRefer(final Class<T> type, final URL url) throws RpcException {
// 1 调用子类实现的 doRefer() 方法返回一个目标服务接口的代理对象
// 2 使用 ProxyFactory.getInvoker() 方法将服务接口的代理对象封装成一个 Invoker ,类型是 AbstractProxyInvoker。
final Invoker<T> target = proxyFactory.getInvoker(doRefer(type, url), type, url);
// 3 创建 AbstractInvoker 的匿名对象
Invoker<T> invoker = new AbstractInvoker<T>(type, url) {
@Override
protected Result doInvoke(Invocation invocation) throws Throwable {
try {
// 4 调用 AbstractProxyInvoker.invoke 方法,返回的结果是 AsyncRpcResult
Result result = target.invoke(invocation);
// FIXME result is an AsyncRpcResult instance.
Throwable e = result.getException();
if (e != null) {
for (Class<?> rpcException : rpcExceptions) {
if (rpcException.isAssignableFrom(e.getClass())) {
throw getRpcException(type, url, invocation, e);
}
}
}
return result;
} catch (RpcException e) {
if (e.getCode() == RpcException.UNKNOWN_EXCEPTION) {
e.setCode(getErrorCode(e.getCause()));
}
throw e;
} catch (Throwable e) {
throw getRpcException(type, url, invocation, e);
}
}
};
invokers.add(invoker);
return invoker;
}Dubbo 协议下的服务调用不仅引入 CompletableFuture ,还对方法等进行改造,如
HeaderExchangeClient.request方法。 HTTP 协议下的服务调用异步改造力度相对不大 ,异步实现主要依赖引入的 CompletableFuture ,以及在AbstractInvoker中统一的Future异步功能。特别说明:
为什么将
doRefer返回的代理对象通过ProxyFactory.getInvoker包装成AbstractProxyInvoker对象?因为此代理对象具备和远程服务通信的能力,原则上可以使用该代理对象调用服务接口方法,但是调用信息是存在Invocation中,将该代理对象包装成AbstractProxyInvoker可以根据Invocation中的信息动态选择目标服务方法。本质上和 DubboInvoker 类似,DubboInvoker 和远程服务通信需要使用ExchangeClient,将调用信息Invocation交给它即可实现目标服务的调用,这里的代理对象就相当于 DubboInvoker 中的ExchangeClient。服务端
使用
JsonRpcServer暴露服务,具体过程可参考: HTTP协议服务暴露 。
1
2
3
4 +--- HttpProtocol.doExport
// 5 创建 JsonRpcServer,暴露服务
JsonRpcServer skeleton = new JsonRpcServer(impl, type);
JsonRpcServer genericServer = new JsonRpcServer(impl, GenericService.class);其中的具有服务能力的 impl 是
ProxyFactory#getProxy(org.apache.dubbo.rpc.Invoker<T>, boolean)创建的代理对象,具体逻辑如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 +--- AbstractProxyProtocol
// 省略无关代码
public <T> Exporter<T> export(final Invoker<T> invoker) throws RpcException {
final String uri = serviceKey(invoker.getUrl());
Exporter<T> exporter = (Exporter<T>) exporterMap.get(uri);
if (exporter != null) {
// When modifying the configuration through override, you need to re-expose the newly modified service.
if (Objects.equals(exporter.getInvoker().getUrl(), invoker.getUrl())) {
return exporter;
}
}
// 其中的 invoker 是由 ProxyFactory#getInvoker 创建
final Runnable runnable = doExport(proxyFactory.getProxy(invoker, true), invoker.getInterface(), invoker.getUrl());
exporter = new AbstractExporter<T>(invoker) {
@Override
public void unexport() {
super.unexport();
exporterMap.remove(uri);
if (runnable != null) {
try {
runnable.run();
} catch (Throwable t) {
logger.warn(t.getMessage(), t);
}
}
}
};
exporterMap.put(uri, exporter);
return exporter;
}不难看出最终处理请求的还是上面方法传入的
invoker对象,该对象中包含真正的服务实例。而传入的invoker对象是由ProxyFactory#getInvoker创建的,对象类型是AbstractProxyInvoker,下面以JavassistProxyFactory工厂为例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 public class JavassistProxyFactory extends AbstractProxyFactory {
// 省略无关代码
@Override
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
// TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
return new AbstractProxyInvoker<T>(proxy, type, url) {
@Override
protected Object doInvoke(T proxy, String methodName,
Class<?>[] parameterTypes,
Object[] arguments) throws Throwable {
return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
}
};
}
}我们把前文的
AbstractProxyInvoker的代码实现粘贴过来。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39 +--- AbstractProxyInvoker
@Override
public Result invoke(Invocation invocation) throws RpcException {
try {
// 1 执行服务方法,如 DemoService.sayHello
Object value = doInvoke(proxy, invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments());
// 2 将方法结果以 CompletableFuture 对象形式进行封装。
// 注意:如果服务方法返回类型是 CompletableFuture ,则无需再使用 CompletableFuture 包装。这个针对服务端的异步实现。
CompletableFuture<Object> future = wrapWithFuture(value);
// 3 执行 future 逻辑
CompletableFuture<AppResponse> appResponseFuture = future.handle((obj, t) -> {
// 4 使用 AppResponse 封装实际结果
AppResponse result = new AppResponse();
if (t != null) {
if (t instanceof CompletionException) {
result.setException(t.getCause());
} else {
result.setException(t);
}
} else {
result.setValue(obj);
}
return result;
});
// 5 统一包装成 AsyncRpcResult 对象
return new AsyncRpcResult(appResponseFuture, invocation);
} catch (InvocationTargetException e) {
if (RpcContext.getContext().isAsyncStarted() && !RpcContext.getContext().stopAsync()) {
logger.error("Provider async started, but got an exception from the original method, cannot write the exception back to consumer because an async result may have returned the new thread.", e);
}
return AsyncRpcResult.newDefaultAsyncResult(null, e.getTargetException(), invocation);
} catch (Throwable e) {
throw new RpcException("Failed to invoke remote proxy method " + invocation.getMethodName() + " to " + getUrl() + ", cause: " + e.getMessage(), e);
}
}对于使用 HTTP 协议的服务实现,更多的是在形式上保持统一,和 Dubbo 协议的服务实现类似。
小结