Dubbo是一個分布式服務框架,是阿里巴巴SOA服務化治理方案的核心框架。
?使用Dubbo進行服務化后速缨,現有如下場景時序圖:
場景描述:客戶端遠程異步調用ServiceA,ServiceA在處理客戶端請求的過程中需要遠程同步調用ServiceB旬牲,ServiceA從ServiceB的響應中取數據時,得到的是null摩骨,對就是這個坑唤冈。
使用DEBUG模式,分析Dubbo源碼得到問題的起因啄栓。
分析過程如下:
客戶端和服務端通信奖亚,配置使用netty進行網絡傳輸淳梦,通過
NettyHandler進行具體的消息收發(fā)操作,所以從此入手進行源碼分析昔字。
client到ServiceA的遠程方法異步調用爆袍,會在RpcContext(RpcContext是一個臨時狀態(tài)記錄器,當接收到RPC請求李滴,或發(fā)起RPC請求時螃宙,RpcContext的狀態(tài)都會變化。比如:A調B所坯,B再調C谆扎,則B機器上,在B調C之前芹助,RpcContext記錄的是A調B的信息堂湖,在B調C之后闲先,RpcContext記錄的是B調C的信息)的attachments(Map結構)屬性中添加async=true的鍵值對,同時也會在RpcInvocation的attachments(Map結構)中添加async=true的鍵值對无蜂。
經過一系列的Filter伺糠,程序運行到AbstractInvoker的invoke方法,注意該方法中的如下代碼段斥季,
Map<String, String> context = RpcContext.getContext().getAttachments();
if (context != null) {
invocation.addAttachmentsIfAbsent(context);
}
這里會把當前RpcContext中的attachments添加到調用ServiceB的RpcInvocation中训桶,這時候async=true已經添加了,接著執(zhí)行如下代碼段酣倾,
if (getUrl().getMethodParameter(invocation.getMethodName(), Constants.ASYNC_KEY, false)){
invocation.setAttachment(Constants.ASYNC_KEY, Boolean.TRUE.toString());
}
上面代碼判斷調用ServiceB的URL中是否含有async=true舵揭,如果有將設置async=true到RpcInvocation的attachments中,這時候是不包含的躁锡。
繼續(xù)跟蹤代碼午绳,運行到DubboInvoker中,調用doInvoke方法映之,該方法中有如下的代碼段拦焚,boolean isAsync = RpcUtils.isAsync(getUrl(), invocation),這個isAsync方法具體聲明如下杠输,
public static boolean isAsync(URL url, Invocation inv) {
boolean isAsync ;
//如果Java代碼中設置優(yōu)先.
if (Boolean.TRUE.toString().equals(inv.getAttachment(Constants.ASYNC_KEY))) {
isAsync = true;
} else {
isAsync = url.getMethodParameter(getMethodName(inv), Constants.ASYNC_KEY, false);
}
return isAsync;
}
上面方法首先判斷RpcInvocation的attachments中async=true是否成立赎败,如果成立則這是一次異步調用,否則判斷請求URL中async=true是否成立抬伺,如果成立則是一次異步調用螟够,否則是一次同步調用,根據上面?zhèn)鬟f的參數峡钓,此時isAsync方法返回的是true妓笙,ServiceA同步調用ServiceB變成了異步調用,繼續(xù)看下面的異步調用能岩,代碼段如下寞宫,
else if (isAsync) {
ResponseFuture future = currentClient.request(inv, timeout) ;
RpcContext.getContext().setFuture(new FutureAdapter<Object>(future));
return new RpcResult();
}
這里直接返回了一個RpcResult對象,沒有數據內容拉鹃,所以到這里辈赋,這個案子也就破了,ServiceA想從響應中取目標數據得到的當然是null膏燕。再延伸一下钥屈,如果ServiceB再同步調用ServiceC,這是可以正常同步調用的坝辫,因為ServiceA調用完ServiceB后篷就,ConsumerContextFilter的invoke方法會清除attachements,所以ServiceB可以正常同步調用ServiceC了近忙。
對于上面的問題竭业,解決辦法有三個:
1.方法調用兩次
ServiceA調用ServiceB的地方寫兩次一樣的調用智润,這個方法原理就像ServiceB調用ServiceC一樣,即清除attachements未辆,這個方法最簡單窟绷,但是可能對不了解的人來說,這塊業(yè)務代碼寫重復了咐柜,會不小心刪除掉兼蜈,而且從寫代碼的角度來說,這個很雞肋炕桨,所以不推薦饭尝。
2.修改Dubbo源碼
修改AbstractInvoker第137行,改成每次都對async進行實際賦值献宫,
boolean isAsync = getUrl().getMethodParameter(invocation.getMethodName(), Constants.ASYNC_KEY, false);
invocation.setAttachment(Constants.ASYNC_KEY, String.valueOf(isAsync));
3.自定義Filter
實現com.alibaba.dubbo.rpc.Filter,在RpcContext中清除這個async实撒,
@Activate(group = {Constants.PROVIDER})
public class AsyncFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
RpcContext.getContext().getAttachments().remove(Constants.ASYNC_KEY);
return invoker.invoke(invocation);
}
}
同時在src/main/resources/META-INF/dubbo/下添加com.alibaba.dubbo.rpc.Filter文件姊途,內容文件如下:
asyncFilter=com.abc.filter.AsyncFilter