ステップSシリーズの--dubbo非同期呼出伝達性により、ネスト呼び出しがnull値のbugに戻る。

4291 ワード

一、現象
三つのアプリケーションがあります。serviceA、serviceB、serviceCは、消費が乱れていないことを確保する前提で、(いずれも単一のサービスプロバイダだけです。)その呼び出し関係は
sequenceDiagram 
    serviceA-->>serviceB:serviceA     serviceB
    serviceB->>serviceC:serviceB     serviceC
    serviceC->>serviceB:        true
serviceA dubb消費serviceBは非同期消費async=「true」に構成されています。構成は以下の通りです。
// serviceA     serviceB     

        
    
// serviceB     serviceC    

        
    
しかし、上記配置後、実際の呼び出し関係は下図になります。
sequenceDiagram 
    serviceA-->>serviceB:serviceA     serviceB
    serviceB-->>serviceC:serviceB     serviceC
    serviceC->>serviceB:   null,     boolean   false
上述したように、B−Cは、dubbo非同期構成の伝達性により非同期呼出しとなり、結果としてnullに戻り、所望の同期呼出結果が異常となり、
ただし、Bが2回目の呼び出しCは正常に戻ります。
二、問題の根源を探します。ソースコードです。
1.私たちの考え方を調べる
現象はdubboの非同期呼び出しによって、そしてサービスプロバイダ内部にdubboの入れ子コールがあります。==だから、dubboの内部入れ子コールに非同期の伝達性があるかどうかを確認したいです。伝達するからには、コンテキスト環境が必要です。さらに、dubboのコンテキスト環境==RpcContext==を思い出しました。
2.予備知識:RpcContect概要
RpcContectは、ThreadLocalの一時状態レコーダであり、RPC要求を受信したり、RPC要求を開始したりすると、RpcContectの状態は変化します。
例えば、A調B、BがCを再調整すると、Bマシンでは、B調Cの前に、==RpcContectがA調Bの情報=、B調Cの後、RpcContectがB調Cの情報を記録します。
我々はdubboのメソッド呼び出しを知っています。すべてinvokerのエージェントによって呼び出されました。私たちはAbstractInvokerを見つけました。下の階のinvoke方法を調べてみます。ソースは以下の通りです。
public Result invoke(Invocation inv) throws RpcException {
        ···       
        
        //             serviceB-->serviceC ,   RpcContext,     RpcContext    ,    ,   context   serviceA->serviceB    ,        ,       
        Map 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) {
            ···
        }
    }
3.上にはまだ小さな問題がありますが、serviceBがServiceCを2回目に呼び出すと、ある方法が正常に戻ってきます。これはなぜですか?
ここでFilter ConsmerContertextFilterのソースコードは以下の通りです。
@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に、ConsmerContertext Filterがあります。呼び出しが終了したらRpcContact.get Contxt()を実行します。clearAttachments()方法で、RpcContxt=をクリアします。私たちの疑問は説明されます。
三、解決方法
問題発生の原因を分析した後、dubboソースの状況を修正しないで、いくつかの処理方法があります。
  • serviceBを同期呼出しに変更します。業務上に非同期呼出しが必要な場合、以下の2つの処理方法
  • があります。
  • serviceBの方法は、リターン値を必要とせずに、Onewayの方式を採用することができる(消費者側にdubboを配置する:methodでreturn=false)
  • には戻り値があり、非同期が必要であり、最も簡単な方法は、実現においてスレッドプールを使用してトラフィックを実行することである。
  • Provider端のFilterを追加して、filterチェーンの最後に、方法を実行する前に、atachmentのasyncフラグをクリアすることを保証します。同じ効果を達成することもできます。
    転載は出典アブの夏を明記してください。