前言
Triple 是 Dubbo3 主推的新协议,基于 HTTP2 ,完全兼容 gRPC,旨在解决 Dubbo2 私有协议带来的互通性问题。
本文分析一下 Triple 协议客户端调用源码,加深协议的理解。
ChannelPipeline
在网络传输层,Dubbo 使用的是Netty框架,核心要看 Dubbo 对 ChannelPipeline 的配置。
因为 HTTP2 支持多路复用,在同一个物理连接上,可以同时创建多个逻辑上的流Stream。而 Netty 对于每一个Stream 都会创建一个子 Channel,子 Channel 也有自己的 ChannelPipeline。
在 Connection 上,ChannelPipeline 的配置如下:
SslClientTlsHandler(可选 加密传输)
Http2FrameCodec HTTP2 Frame编解码器
Http2MultiplexHandler HTTP2多路复用的支持
TripleClientHandler
TripleTailHandler 避免内存泄漏
在 Stream 上,ChannelPipeline 的配置如下:
TripleCommandOutBoundHandler 命令输出
TripleHttp2ClientResponseHandler 对服务端响应的处理
TripleProtocol
要想调用远程服务,首先得引用服务,创建对应的Invoker。
Triple 服务的引用代码是TripleProtocol#refer(),我把关键节点整理如下:
服务引用
org.apache.dubbo.rpc.protocol.tri.TripleProtocol#refer() 服务引用
org.apache.dubbo.rpc.protocol.tri.TripleInvoker#TripleInvoker() 实例化Invoker
org.apache.dubbo.remoting.api.Connection#Connection() 实例化Connection
org.apache.dubbo.remoting.api.Connection#create() 构建Bootstrap
org.apache.dubbo.remoting.api.Connection#connect() 建立连接
服务调用
org.apache.dubbo.rpc.protocol.tri.TripleInvoker#doInvoke() 服务调用
org.apache.dubbo.rpc.protocol.tri.TripleInvoker#invokeUnary() UNARY调用
org.apache.dubbo.rpc.protocol.tri.TripleInvoker#createRequest() 请求元数据
org.apache.dubbo.rpc.protocol.tri.stream.TripleClientStream#TripleClientStream() 开启Stream
org.apache.dubbo.rpc.protocol.tri.observer.ClientCallToObserverAdapter#onNext()
org.apache.dubbo.rpc.protocol.tri.call.TripleClientCall#sendMessage() 发送请求
org.apache.dubbo.rpc.protocol.tri.stream.Stream#sendHeader() 发送Headers帧
org.apache.dubbo.rpc.model.PackableMethod#packRequest() 打包消息 序列化
org.apache.dubbo.rpc.protocol.tri.stream.ClientStream#sendMessage() 发送DATA帧
org.apache.dubbo.rpc.protocol.tri.observer.ClientCallToObserverAdapter#onCompleted()
org.apache.dubbo.rpc.protocol.tri.stream.ClientStream#halfClose() 发送endStream Stream半关闭状态
处理响应
org.apache.dubbo.rpc.protocol.tri.transport.TripleHttp2ClientResponseHandler#channelRead0()
org.apache.dubbo.rpc.protocol.tri.stream.TripleClientStream.ClientTransportListener#onHeaderReceived() 接收Headers
org.apache.dubbo.rpc.protocol.tri.stream.TripleClientStream.ClientTransportListener#onData() 接收Data帧
org.apache.dubbo.rpc.protocol.tri.frame.TriDecoder#deframe() 解帧
org.apache.dubbo.rpc.model.PackableMethod#parseResponse() 消息解包 反序列化
org.apache.dubbo.rpc.protocol.tri.DeadlineFuture#received() 受到响应结果 线程唤醒
TripleInvoker
Triple 协议服务的引用过程,就是创建 TripleInvoker 的过程。
在 TripleInvoker 的构造函数中,会创建 Connection 连接对象。在 Connection 的构造函数中,又会创建 Bootstrap。
org.apache.dubbo.remoting.api.Connection#create()
private Bootstrap create() {
final Bootstrap bootstrap = new Bootstrap();
bootstrap.group(NettyEventLoopFactory.NIO_EVENT_LOOP_GROUP.get())
.option(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
.remoteAddress(remote)
.channel(socketChannelClass());
final ConnectionHandler connectionHandler = new ConnectionHandler(this);
bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
final ChannelPipeline pipeline = ch.pipeline();
SslContext sslContext = null;
if (getUrl().getParameter(SSL_ENABLED_KEY, false)) {
pipeline.addLast("negotiation", new SslClientTlsHandler(url));
}
pipeline.addLast(connectionHandler);
protocol.configClientPipeline(url, pipeline, sslContext);
}
});
return bootstrap;
}
调用TripleHttp2Protocol#configClientPipeline()对 ChannelPipeline 进行配置。
public void configClientPipeline(URL url, ChannelPipeline pipeline, SslContext sslContext) {
Configuration config = ConfigurationUtils.getGlobalConfiguration(url.getOrDefaultApplicationModel());
final Http2FrameCodec codec = Http2FrameCodecBuilder.forClient()
.gracefulShutdownTimeoutMillis(10000)
.initialSettings(new Http2Settings().headerTableSize(
config.getInt(H2_SETTINGS_HEADER_TABLE_SIZE_KEY, DEFAULT_SETTING_HEADER_LIST_SIZE))
.pushEnabled(config.getBoolean(H2_SETTINGS_ENABLE_PUSH_KEY, false))
.maxConcurrentStreams(
config.getInt(H2_SETTINGS_MAX_CONCURRENT_STREAMS_KEY, Integer.MAX_VALUE))
.initialWindowSize(
config.getInt(H2_SETTINGS_INITIAL_WINDOW_SIZE_KEY, DEFAULT_WINDOW_INIT_SIZE))
.maxFrameSize(config.getInt(H2_SETTINGS_MAX_FRAME_SIZE_KEY, DEFAULT_MAX_FRAME_SIZE))
.maxHeaderListSize(config.getInt(H2_SETTINGS_MAX_HEADER_LIST_SIZE_KEY,
DEFAULT_MAX_HEADER_LIST_SIZE)))
.frameLogger(CLIENT_LOGGER)
.build();
final Http2MultiplexHandler handler = new Http2MultiplexHandler(
new TripleClientHandler(frameworkModel));
pipeline.addLast(codec, handler, new TripleTailHandler());
}
Http2FrameCodec 是 Netty 提供的 HTTP2 Frame的编解码器,让开发者不用关心 Frame 的细节。
Http2MultiplexHandler 是 Netty 对 HTTP2 多路复用的支持,给每个 Stream 也创建了一个 Channel。
建立连接后,发起 Triple RPC 调用,实际触发的是TripleInvoker#doInvoke()。
- 从 Invocation 解析出 ServiceDescriptor 和 MethodDescriptor,明确要调用的服务和方法。
- 实例化 TripleClientCall,用于发起客户端调用。
- 根据 RPC 调用类型执行不同方法,最常用的是 UNARY,就像调用本地方法一样,线程会阻塞等待结果。
protected Result doInvoke(final Invocation invocation) {
if (!connection.isAvailable()) {
CompletableFuture<AppResponse> future = new CompletableFuture<>();
RpcException exception = TriRpcStatus.UNAVAILABLE.withDescription(
String.format("upstream %s is unavailable", getUrl().getAddress()))
.asException();
future.completeExceptionally(exception);
return new AsyncRpcResult(future, invocation);
}
ConsumerModel consumerModel = (ConsumerModel) (invocation.getServiceModel() != null
? invocation.getServiceModel() : getUrl().getServiceModel());
// 服务描述符
ServiceDescriptor serviceDescriptor = consumerModel.getServiceModel();
// 方法描述符
final MethodDescriptor methodDescriptor = serviceDescriptor.getMethod(
invocation.getMethodName(),
invocation.getParameterTypes());
// 客户端调用对象
ClientCall call = new TripleClientCall(connection, streamExecutor,
getUrl().getOrDefaultFrameworkModel());
AsyncRpcResult result;
try {
// RPC调用类型 支持4种 UNARY最常用
switch (methodDescriptor.getRpcType()) {
case UNARY:
result = invokeUnary(methodDescriptor, invocation, call);
break;
case SERVER_STREAM:
result = invokeServerStream(methodDescriptor, invocation, call);
break;
case CLIENT_STREAM:
case BI_STREAM:
result = invokeBiOrClientStream(methodDescriptor, invocation, call);
break;
default:
throw new IllegalStateException("Can not reach here");
}
return result;
} catch (Throwable t) {
final TriRpcStatus status = TriRpcStatus.INTERNAL.withCause(t)
.withDescription("Call aborted cause client exception");
RpcException e = status.asException();
try {
call.cancelByLocal(e);
} catch (Throwable t1) {
LOGGER.error(PROTOCOL_FAILED_REQUEST, "", "", "Cancel triple request failed", t1);
}
CompletableFuture<AppResponse> future = new CompletableFuture<>();
future.completeExceptionally(e);
return new AsyncRpcResult(future, invocation);
}
}
以最常用的 UNARY 调用为例:
- 基于 Invocation 创建请求元数据对象 RequestMetadata。
- 因为网络调用是异步的,创建一个 DeadlineFuture 等待结果。
- 开启Stream、发送请求、发送endStream,请求结束。
AsyncRpcResult invokeUnary(MethodDescriptor methodDescriptor, Invocation invocation,
ClientCall call) {
// 回调线程池
ExecutorService callbackExecutor = getCallbackExecutor(getUrl(), invocation);
// 超时时间
int timeout = RpcUtils.calculateTimeout(getUrl(), invocation, invocation.getMethodName(), 3000);
if (timeout <= 0) {
return AsyncRpcResult.newDefaultAsyncResult(new RpcException(RpcException.TIMEOUT_TERMINATE,
"No time left for making the following call: " + invocation.getServiceName() + "."
+ invocation.getMethodName() + ", terminate directly."), invocation);
}
invocation.setAttachment(TIMEOUT_KEY, String.valueOf(timeout));
// 网络调用是异步的,创建一个DeadlineFuture等待结果
final AsyncRpcResult result;
DeadlineFuture future = DeadlineFuture.newFuture(getUrl().getPath(),
methodDescriptor.getMethodName(), getUrl().getAddress(), timeout, callbackExecutor);
// 创建请求元数据
RequestMetadata request = createRequest(methodDescriptor, invocation, timeout);
// 实参
final Object pureArgument;
if (methodDescriptor instanceof StubMethodDescriptor) {
pureArgument = invocation.getArguments()[0];
} else {
pureArgument = invocation.getArguments();
}
result = new AsyncRpcResult(future, invocation);
FutureContext.getContext().setCompatibleFuture(future);
result.setExecutor(callbackExecutor);
ClientCall.Listener callListener = new UnaryClientCallListener(future);
// 开启Stream 发起调用
final StreamObserver<Object> requestObserver = call.start(request, callListener);
requestObserver.onNext(pureArgument);// 发送请求
requestObserver.onCompleted();// 发送endStream 请求结束
return result;
}
发送请求的方法是TripleClientCall#sendMessage():
- 发送Headers帧
- 请求消息打包
- 发送Data帧
public void sendMessage(Object message) {
if (canceled) {
throw new IllegalStateException("Call already canceled");
}
// 先发送Headers帧,再发送Data帧
if (!headerSent) {
headerSent = true;
stream.sendHeader(requestMetadata.toHeaders());
}
final byte[] data;
try {
// 消息打包 Protobuf 或者 Wrap
data = requestMetadata.packableMethod.packRequest(message);
// 是否要压缩
int compressed =
Identity.MESSAGE_ENCODING.equals(requestMetadata.compressor.getMessageEncoding())
? 0 : 1;
final byte[] compress = requestMetadata.compressor.compress(data);
// 发送Data帧
stream.sendMessage(compress, compressed, false)
.addListener(f -> {
if (!f.isSuccess()) {
cancelByLocal(f.cause());
}
});
} catch (Throwable t) {
LOGGER.error(PROTOCOL_FAILED_SERIALIZE_TRIPLE, "", "", String.format("Serialize triple request failed, service=%s method=%s",
requestMetadata.service,
requestMetadata.method), t);
cancelByLocal(t);
listener.onClose(TriRpcStatus.INTERNAL.withDescription("Serialize request failed")
.withCause(t), null);
}
}
请求到这一步还没完,没发送 endStream 服务端不知道请求已经结束。所以紧接着还要调用ClientCallToObserverAdapter#onCompleted()来发送一个 endStream 消息代表请求结束。
public void onCompleted() {
if (terminated) {
return;
}
// 流半关闭状态 即发送endStream
call.halfClose();
this.terminated = true;
}
所谓的流半关闭,其实就是发送一个空的Data帧,只是 endStream 标记为true。
public ChannelFuture halfClose() {
// 发送一个空的Data帧 endStream=true
final EndStreamQueueCommand cmd = EndStreamQueueCommand.create();
return this.writeQueue.enqueue(cmd);
}
至此,请求就结束了,客户端只要坐等服务端响应即可。
对服务端响应结果的处理器是TripleHttp2ClientResponseHandler:
protected void channelRead0(ChannelHandlerContext ctx, Http2StreamFrame msg) throws Exception {
if (msg instanceof Http2HeadersFrame) {
final Http2HeadersFrame headers = (Http2HeadersFrame) msg;
transportListener.onHeader(headers.headers(), headers.isEndStream());
} else if (msg instanceof Http2DataFrame) {
final Http2DataFrame data = (Http2DataFrame) msg;
transportListener.onData(data.content(), data.isEndStream());
} else {
super.channelRead(ctx, msg);
}
}
public void onHeader(Http2Headers headers, boolean endStream) {
executor.execute(() -> {
if (endStream) {
if (!halfClosed) {
writeQueue.enqueue(CancelQueueCommand.createCommand(Http2Error.CANCEL),
true);
}
// 无结果响应
onTrailersReceived(headers);
} else {
// 还有Data帧 先接收Headers
onHeaderReceived(headers);
}
});
}
RPC 方法返回的结果封装在 Data 帧里,交给 TriDecoder 按照 Protobuf 的方式解帧。
解帧后再交给 PackableMethod#parseResponse()解包消息,也就是反序列化,拿到结果对象。
最后把结果写入 DeadlineFuture,业务线程拿到结果后唤醒,继续执行,正哥 RPC 调用结束。