ダボソースコード分析8:クライアントサービス呼び出しプロセス

ここに画像の説明を挿入

前書き

実際、クライアント側のサービス呼び出しプロセスは、サーバー側の要求処理プロセスと非常によく似ています。要求が通過するChannelhandlerを理解していれば、半分以上を理解できます。

Dubboソースコード分析を読んでいない場合
:サービスプロバイダーはリクエストを受け取り、結果を返します

DubboとNettyChannelhandlerの違いを理解できるように、最初に確認することをお勧めします。

NettyのChannelhandlerはChainof Responsibilityモードを使用し、DubboのChannelhandlerはデコレータモードを使用します

一般的に使用されるハンドラーは次のとおりです

ChannelHandler 効果
NettyClientHandler 接続、切断、読み取り、書き込み、例外などのNettyクライアントイベントを処理します
NettyServerHandler 接続、切断、読み取り、書き込み、例外などのNettyサーバーイベントを処理します
MultiMessageHandler マルチメッセージバッチ処理
HeartbeatHandler ハートビート処理
AllChannelHandler すべてのNettyリクエストを処理のためにビジネススレッドプールに入れます
DecodeHandler メッセージをデコードします
HeaderExchangeHandler パッケージ処理要求/応答、およびtelnet要求
ExchangeHandlerAdapter サービスメソッドを見つけて呼び出します

その中で、NettyClientHandlerとNettyServerHandlerはNettyでChannelHandlerインターフェースを実装し、その他はDubboでChannelHandlerインターフェースを実装します。

リモート通話を開始する

サービスの導入に関する記事で、最終的に返されるプロキシオブジェクトは、JavassistProxyFactory#getProxyメソッドを呼び出すことによって取得されると述べました。

// JavassistProxyFactory
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
    
    
    return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}

したがって、プロキシオブジェクトによって実行されるメソッドはすべてInvokerInvocationHandler#invokeに入ります

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
    String methodName = method.getName();
    Class<?>[] parameterTypes = method.getParameterTypes();
    // 拦截定义在 Object 类中的方法(未被子类重写),比如 wait/notify
    if (method.getDeclaringClass() == Object.class) {
    
    
        return method.invoke(invoker, args);
    }
    // 如果 toString、hashCode 和 equals 等方法被子类重写了,这里也直接调用
    if ("toString".equals(methodName) && parameterTypes.length == 0) {
    
    
        return invoker.toString();
    }
    if ("hashCode".equals(methodName) && parameterTypes.length == 0) {
    
    
        return invoker.hashCode();
    }
    if ("equals".equals(methodName) && parameterTypes.length == 1) {
    
    
        return invoker.equals(args[0]);
    }
    // 将 method 和 args 封装到 RpcInvocation 中,并执行后续的调用
    return invoker.invoke(createInvocation(method, args)).recreate();
}

実行リンクは次のとおり
ここに画像の説明を挿入
です。MockClusterInvoker:サービスの低下
FailoverClusterInvoker:クラスターのフォールトトレランス
DubboInvoker:ネットワーク呼び出しを開始して結果を取得する

MockClusterInvoker

MockClusterInvokerは主にサービスの低下に使用されます。つまり、リクエストの前に特定の値を返し、実際の呼び出しを開始しないか、呼び出しが失敗した後、例外をスローせずに特定の値を返します。

公式ドキュメントを参照できます:
http //dubbo.apache.org/zh/docs/v2.7/user/examples/service-downgrade/

DubboInvoker

// DubboInvoker
protected Result doInvoke(final Invocation invocation) throws Throwable {
    
    
    // 设置附加属性
    RpcInvocation inv = (RpcInvocation) invocation;
    final String methodName = RpcUtils.getMethodName(invocation);
    // 设置路径,版本
    inv.setAttachment(Constants.PATH_KEY, getUrl().getPath());
    inv.setAttachment(Constants.VERSION_KEY, version);

    // 获取客户端,发起实际调用
    // 构造DubboInvoker的时候已经初始化好了
    ExchangeClient currentClient;
    if (clients.length == 1) {
    
    
        currentClient = clients[0];
    } else {
    
    
        currentClient = clients[index.getAndIncrement() % clients.length];
    }
    try {
    
    
        // 是否为异步
        boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);
        // 是否为future方式异步
        boolean isAsyncFuture = RpcUtils.isReturnTypeFuture(inv);
        // isOneway 为 true,表示“单向”通信
        boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
        // 超时等待时间
        int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
        // 不需要响应的请求
        if (isOneway) {
    
    
            boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
            // 发起网络调用
            currentClient.send(inv, isSent);
            RpcContext.getContext().setFuture(null);
            return new RpcResult();
        } else if (isAsync) {
    
    
            // 异步有返回值
            // 同步:框架调用ResponseFuture#get()
            // 异步:用户调用ResponseFuture#get()
            ResponseFuture future = currentClient.request(inv, timeout);
            // For compatibility
            // FutureAdapter 是将ResponseFuture与jdk中的Future进行适配
            // 当用户调用Future的get方法时,经过FutureAdapter的适配,最终会调用DefaultFuture的get方法
            FutureAdapter<Object> futureAdapter = new FutureAdapter<>(future);
            RpcContext.getContext().setFuture(futureAdapter);

            Result result;
            // 返回值是否是 CompletableFuture
            if (isAsyncFuture) {
    
    
                // register resultCallback, sometimes we need the async result being processed by the filter chain.
                result = new AsyncRpcResult(futureAdapter, futureAdapter.getResultFuture(), false);
            } else {
    
    
                result = new SimpleAsyncRpcResult(futureAdapter, futureAdapter.getResultFuture(), false);
            }
            return result;
        } else {
    
    
            // 同步有返回值
            RpcContext.getContext().setFuture(null);
            // 发送请求,得到一个 ResponseFuture 实例,并调用该实例的 get 方法进行等待
            // 本质上还是通过异步的代码来实现同步调用
            return (Result) currentClient.request(inv, timeout).get();
        }
    } 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#doInvokeこのメソッドは、実際に呼び出しを開始するメソッドです。
呼び出す場合、2つの状況があります。

  1. 戻り値が必要
  2. 値を返す必要はありません
    2.1同期リクエスト、フレームワークはDefaultFutureのgetメソッドを呼び出します
    2.2非同期リクエスト、ユーザーはDefaultFutureのgetメソッドを呼び出します

同期呼び出しと非同期呼び出しのプロセスについては、後の別の記事で分析します。

ネットワークリクエストを送信する

実際、クライアントが要求を送信して応答を受信するプロセスは、サーバーのプロセスと似ています。コンストラクターでハンドラーを初期化し、doOpenメソッドを呼び出して接続を開きます。

古いルーチンに従って分析し、NettyClientがリクエストを送受信するハンドラーを確認します。
ここに画像の説明を挿入
NettyClientHandlerとNettyClientを除いて、残りのハンドラーとサーバーは同じであることがわかります。

// 
protected void doOpen() throws Throwable {
    
    
    // 执行业务逻辑的handler
    final NettyClientHandler nettyClientHandler = new NettyClientHandler(getUrl(), this);
    bootstrap = new Bootstrap();
    bootstrap.group(nioEventLoopGroup)
            .option(ChannelOption.SO_KEEPALIVE, true)
            .option(ChannelOption.TCP_NODELAY, true)
            .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
            //.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, getTimeout())
            .channel(NioSocketChannel.class);

    if (getConnectTimeout() < 3000) {
    
    
        bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000);
    } else {
    
    
        bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, getConnectTimeout());
    }

    bootstrap.handler(new ChannelInitializer() {
    
    

        @Override
        protected void initChannel(Channel ch) throws Exception {
    
    
            int heartbeatInterval = UrlUtils.getHeartbeat(getUrl());
            NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyClient.this);
            ch.pipeline()//.addLast("logging",new LoggingHandler(LogLevel.INFO))//for debug
                    .addLast("decoder", adapter.getDecoder())
                    .addLast("encoder", adapter.getEncoder())
                    .addLast("client-idle-handler", new IdleStateHandler(heartbeatInterval, 0, 0, MILLISECONDS))
                    .addLast("handler", nettyClientHandler);
        }
    });
}

要約すると、リクエストを開始するプロセスは次のとおりです。

  1. NettyClientHandler#write
  2. NettyClient(AbstractPeer#sent)
  3. MultiMessageHandler(AbstractChannelHandlerDelegate#sent)
  4. HeartbeatHandler#sent
  5. AllChannelHandler(WrappedChannelHandler#sent)(ここでは、デフォルトはAllChannelHandlerであり、スレッドモデルとスレッドプール戦略はSPIを介して決定できます)
  6. DecodeHandler(AbstractChannelHandlerDelegate#sent)
  7. HeaderExchangeHandler#sent
  8. ExchangeHandlerAdapter#sent(DubboProtocolの匿名の内部クラス。これは空の実装です)

応答を受け取る

クライアントがメッセージを受信したハンドラー

  1. NettyClientHandler#channelRead
  2. NettyClient(AbstractPeer#received)
  3. MultiMessageHandler#received
  4. HeartbeatHandler#received
  5. AllChannelHandle#received(デフォルトはここではAllChannelHandlerです)
  6. DecodeHandler#received
  7. HeaderExchangeHandler#received(ここで終了)
  8. DubboProtocol中的ExchangeHandlerAdapter#received

詳細なプロセスは、「Dubboソースコード分析:Dubboは同期および非同期呼び出しをどのようにサポートしますか?」にあります。"分析

リファレンスブログ

タイムアウト例外
[1] https://juejin.im/post/6844903857416323079

おすすめ

転載: blog.csdn.net/zzti_erlie/article/details/109473813