有三個應用serviceA,serviceB,serviceC,在確保消費沒有錯亂的前提下(都只有單個服務提供者),指望其調用關係爲java
sequenceDiagram serviceA-->>serviceB:serviceA 異步調用serviceB serviceB->>serviceC:serviceB 同步調用serviceC serviceC->>serviceB:同步返回調用結果true
serviceA dubbo消費serviceB 配置爲異步消費async="true",配置以下:異步
// serviceA 異步消費serviceB 配置以下 <dubbo:reference id="serviceB" interface="cn.ServiceB" version="1.0.0" check="false"> <dubbo:method name="reRunJob" async="true"/> </dubbo:reference>
// serviceB 同步消費serviceC配置以下 <dubbo:reference id="serviceC" interface="cn.ServiceC" version="1.0.0" check="false"> <dubbo:method name="xxx"/> </dubbo:reference>
然而,上面配置後,實際調用關係變爲下圖async
sequenceDiagram serviceA-->>serviceB:serviceA 異步調用serviceB serviceB-->>serviceC:serviceB 異步調用serviceC serviceC->>serviceB: 返回null,致使指望值boolean永遠爲false
如上所述,因爲B->C 因爲dubbo異步配置的傳遞性,致使變爲了異步調用,結果返回了null,致使指望的同步調用結果異常, 可是B第二次調用C會正常返回
現象是因爲dubbo異步調用,而後服務提供者內部又有dubbo嵌套調用,==因此咱們須要找出dubbo的內部嵌套調用是否存在異步傳遞性==,那麼既然是傳遞,就須要上下文環境,進而,咱們想到了dubbo中的上下文環境==RpcContext==,咱們推測是由此類傳遞了異步參數ide
RpcContext 是一個 ThreadLocal 的臨時狀態記錄器,當接收到 RPC 請求,或發起 RPC 請求時,RpcContext 的狀態都會變化。 <br/>
好比:A調B,B再調C,則B機器上,在B調C以前,==RpcContext記錄的是A調B的信息==,在B調C以後,RpcContext記錄的是B調C的信息。咱們知道dubbo的方法調用,都是由invoker代理調用的,咱們找到AbstractInvoker,查看底層的invoke方法,源碼以下:.net
public Result invoke(Invocation inv) throws RpcException { ··· 省略無關代碼 // 這裏的代碼在此處對咱們的serviceB-->serviceC時,會取出RpcContext,語句上面的RpcContext知識儲備,咱們知道,這裏的context存的是serviceA->serviceB的上下文,也就是說是異步的,到這裏謎團解開 Map<String, String> context = RpcContext.getContext().getAttachments(); if (context != null) { invocation.addAttachmentsIfAbsent(context); } // 注意此處代碼,若是提供者方法配置了異步參數 if (getUrl().getMethodParameter(invocation.getMethodName(), Constants.ASYNC_KEY, false)){ // 會將異步參數值設置到當前調用對象中 invocation.setAttachment(Constants.ASYNC_KEY, Boolean.TRUE.toString()); } // 最後再將異步參數存入上下文 RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation); try { return doInvoke(invocation); } catch (InvocationTargetException e) { ··· } catch (RpcException e) { ··· } catch (Throwable e) { ··· } }
這裏涉及到一個Filter ConsumerContextFilter 源碼以下:線程
@Activate(group = Constants.CONSUMER, order = -10000) public class ConsumerContextFilter implements Filter { public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { RpcContext.getContext() .setInvoker(invoker) .setInvocation(invocation) .setLocalAddress(NetUtils.getLocalHost(), 0) .setRemoteAddress(invoker.getUrl().getHost(), invoker.getUrl().getPort()); if (invocation instanceof RpcInvocation) { ((RpcInvocation)invocation).setInvoker(invoker); } try { return invoker.invoke(invocation); } finally { // ①注意這裏 ,進行了上下文清理 RpcContext.getContext().clearAttachments(); } } }
==如上代碼,serviceB第一次調用serviceC結束時,在consumer的filter chain中,有一個ConsumerContextFilter,在調用結束後會執行RpcContext.getContext().clearAttachments()方法,清除RpcContext中的信息,也就清除了async標識==,因此,第二次調用serviceC,就正常==同步==調用了,至此,咱們的疑問獲得解釋代理
分析了問題產生的緣由後,在不修改dubbo源碼的狀況,能夠有一下幾種處理方式。code
轉載請註明出處 阿布的夏天xml