dubbo 剖析:記一個異步方法調用的坑

注:文章中的坑出現在2.5.4版本之前,這個坑在2.5.4版本已經得到修復。

一、問題描述

dubbo 剖析:記一個異步方法調用的坑

問題描述

場景描述,如上圖所示:

客戶端 遠程異步調用 服務A服務A 在處理客戶端請求的過程中需要遠程同步調用 服務B服務A服務B 的響應中取數據時,得到的是 null

!!!

二、原因分析

dubbo 剖析:記一個異步方法調用的坑

RPC請求響應參數傳遞過程

2.1 Client的請求發送過程

1)Client在發起RPC調用請求前,將請求參數構建成 RpcInvocation ;

2)Client在發起RPC調用請求前,會經過Filter處理:

  • ConsumerContextFilter 會將請求信息,如invoker、invocation、Address等,寫入 RpcContext

@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();}}}

3)Client在發起RPC調用請求前,會經過AbstractInvoker:

  • AbstractInvoker 會將 RpcContext 中的 attachments 內容寫入到 RpcInvocation ,以實現附加參數的傳遞;

Map 
context = RpcContext.getContext().getAttachments(); if (context != null) {invocation.addAttachmentsIfAbsent(context);}
  • AbstractInvoker 會從RPC請求參數 URLASYNC_KEY 的值,並設置到 RpcInvocationattachment 中;

if (getUrl().getMethodParameter(invocation.getMethodName(), Constants.ASYNC_KEY, false)) { invocation.setAttachment(Constants.ASYNC_KEY, Boolean.TRUE.toString());}

4)Client在發起RPC調用請求時,會經過DubboInvoker:

  • DubboInvoker會優先從 RpcInvocationattachment 中獲取並判斷 ASYNC_KEY 是否為true,以實現消費端的異步調用;

public static boolean isAsync(URL url, Invocation inv) { boolean isAsync; //如果Java代碼中設置優先.if (Boolean.TRUE.toString().equals(inv.getAttachment(Constants.ASYNC_KEY))) {isAsync = true;} else {isAsync = url.getMethodParameter(getMethodName(inv), Constants.ASYNC_KEY, false);} return isAsync;}

5)Client在發起RPC調用請求時,會將 RpcInvocation 作為調用參數傳遞給服務提供方:

  • RpcInvocation 中的擴展屬性 attachments ,實現了請求調用擴展信息傳遞的功能;

2.2 服務A的請求接收和執行過程

1)服務端在接收到RPC請求,調用真正實現接口前,會經過 ContextFilter

  • ContextFilter 會將請求信息,如invoker、invocation、Address等,寫入 RpcContext

  • ContextFilter 會將請求參數 RpcInvocationattachments 擴展信息取出,過濾掉某些特定KEY之後,將其餘擴展屬性設置到當前 RpcContextattachments 中;

Map attachments = invocation.getAttachments(); if (attachments != null) {attachments = new HashMap(attachments);attachments.remove(Constants.PATH_KEY);attachments.remove(Constants.GROUP_KEY);attachments.remove(Constants.VERSION_KEY);attachments.remove(Constants.DUBBO_VERSION_KEY);attachments.remove(Constants.TOKEN_KEY);attachments.remove(Constants.TIMEOUT_KEY);attachments.remove(Constants.ASYNC_KEY);//清空消費端的異步參數,2.5.4版本才新加進去的}RpcContext.getContext().setInvoker(invoker).setInvocation(invocation).setAttachments(attachments).setLocalAddress(invoker.getUrl().getHost(),invoker.getUrl().getPort());

其中 attachments.remove(Constants.ASYNC_KEY);//清空消費端的異步參數 這行代碼是在dubbo的2.5.4版本才加進去的,也就是之前的版本中並沒有這行代碼。

2)在2.5.4版本之前,對於 Client 發來的異步調用請求,其 RpcInvocation 參數中包含了 ASYNC=trueattachment 擴展信息:

  • 此時 ASYNC=true 的這個擴展信息就會被設置到服務A的 RpcContext 的擴展屬性中;

  • 服務A 處理RPC調用,執行實際接口實現類的邏輯時,因為依賴的 服務B ,所以會繼續發送RPC調用請求給

    服務B

  • 服務A 調用 服務B 時, 服務ARpcContext 的擴展屬性會被寫入到 A -> BRpcInvocation 參數中,這就導致 ASYNC=true 的擴展屬性參數被誤傳到 A -> BRpcInvocation 參數中,進而導致在服務A發起RPC請求調用時觸發了錯誤的異步調用邏輯;

  • 此時 服務A 獲取到的RPC執行結果 RpcResult 的內容當然是個空;

else if (isAsync) { //1. 異步,有返回值ResponseFuture future = currentClient.request(inv, timeout);RpcContext.getContext().setFuture(new FutureAdapter(future)); return new RpcResult();} else { //3. 異步->同步(默認的通信方式)RpcContext.getContext().setFuture(null); return (Result) currentClient.request(inv, timeout).get();}

以上就是這個坑的產生原因

三、解決方法

自己寫了個Filter,添加到Dubbo服務提供方接收請求後、實際處理請求前的Filter執行鏈中。

從請求參數 URL 中解析出 ASYNC 擴展參數標識,而不依賴 RpcInvocation 中的值。

@Activate(group = {Constants.PROVIDER}, order = -999)public class DubboSyncFilter implements Filter { @Overridepublic Result invoke(Invoker> invoker, Invocation invocation) throws RpcException { //避免RpcContext透傳,使用配置文件的asyncboolean isAsync = invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.ASYNC_KEY, false);RpcContext.getContext().setAttachment(Constants.ASYNC_KEY, String.valueOf(isAsync)); return invoker.invoke(invocation);}}


分享到:


相關文章: