前書き
実際、クライアント側のサービス呼び出しプロセスは、サーバー側の要求処理プロセスと非常によく似ています。要求が通過する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つの状況があります。
- 戻り値が必要
- 値を返す必要はありません
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);
}
});
}
要約すると、リクエストを開始するプロセスは次のとおりです。
- NettyClientHandler#write
- NettyClient(AbstractPeer#sent)
- MultiMessageHandler(AbstractChannelHandlerDelegate#sent)
- HeartbeatHandler#sent
- AllChannelHandler(WrappedChannelHandler#sent)(ここでは、デフォルトはAllChannelHandlerであり、スレッドモデルとスレッドプール戦略はSPIを介して決定できます)
- DecodeHandler(AbstractChannelHandlerDelegate#sent)
- HeaderExchangeHandler#sent
- ExchangeHandlerAdapter#sent(DubboProtocolの匿名の内部クラス。これは空の実装です)
応答を受け取る
クライアントがメッセージを受信したハンドラー
- NettyClientHandler#channelRead
- NettyClient(AbstractPeer#received)
- MultiMessageHandler#received
- HeartbeatHandler#received
- AllChannelHandle#received(デフォルトはここではAllChannelHandlerです)
- DecodeHandler#received
- HeaderExchangeHandler#received(ここで終了)
- DubboProtocol中的ExchangeHandlerAdapter#received
詳細なプロセスは、「Dubboソースコード分析:Dubboは同期および非同期呼び出しをどのようにサポートしますか?」にあります。"分析
リファレンスブログ
タイムアウト例外
[1] https://juejin.im/post/6844903857416323079