Dubbo3之Triple协议客户端调用源码分析

176 阅读4分钟

前言

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()

  1. 从 Invocation 解析出 ServiceDescriptor 和 MethodDescriptor,明确要调用的服务和方法。
  2. 实例化 TripleClientCall,用于发起客户端调用。
  3. 根据 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 调用为例:

  1. 基于 Invocation 创建请求元数据对象 RequestMetadata。
  2. 因为网络调用是异步的,创建一个 DeadlineFuture 等待结果。
  3. 开启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()

  1. 发送Headers帧
  2. 请求消息打包
  3. 发送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 调用结束。