Dubbo3之Triple协议Streaming通信-CSDN博客

181 阅读4分钟

前言

Dubbo3 推出的 Triple 协议开始支持 Streaming 流式通信,流式通信适用于大文件、大数据传输、直播流推送等场景。
Stream 分为三种类型:客户端流、服务端流、双向流。由于 Java 语言的限制,客户端流和双向流实现方案是一样的。

Client端源码

客户端服务调用的入口是TripleInvoker#doInvoke(),Dubbo 会根据 RPC 调用类型选择对应的方法。
可以看到,客户端流和双向流采用同一种实现方案。

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");
}

对于服务端流,方法的参数有严格限制。第0个参数是请求参数,第1个参数必须是 StreamObserver ,它会作为服务端响应流。

  1. 创建请求元数据对象
  2. 解析响应流对象 StreamObserver
  3. 发起流式调用,在接收到响应消息时触发 StreamObserver
  4. 发送请求参数,发送 endStream,客户端流半关闭,等待响应
AsyncRpcResult invokeServerStream(MethodDescriptor methodDescriptor, Invocation invocation,
                                  ClientCall call) {
    RequestMetadata request = createRequest(methodDescriptor, invocation, null);
    // 参数严格要求 第0个是请求参数 第1个是响应流
    StreamObserver<Object> responseObserver = (StreamObserver<Object>) invocation.getArguments()[1];
    // 流式调用 收到消息会触发responseObserver
    final StreamObserver<Object> requestObserver = streamCall(call, request, responseObserver);
    // 发送请求 客户端流半关闭
    requestObserver.onNext(invocation.getArguments()[0]);
    requestObserver.onCompleted();
    return new AsyncRpcResult(CompletableFuture.completedFuture(new AppResponse()), invocation);
}

Dubbo 会将响应流对象 StreamObserver 封装成ObserverToClientCallListenerAdapter,客户端接收到响应消息时,会触发ObserverToClientCallListenerAdapter#onMessage(),也就是触发onNext()

/**
 * 收到响应消息时
 * 对于UNARY调用,是设置Result
 * 对于Stream调用,是触发onNext
 * @param message message received
 */
@Override
public void onMessage(Object message) {
    delegate.onNext(message);
    if (call.isAutoRequest()) {
        call.request(1);
    }
}

再看客户端流和双向流,因为实现方案是一样的。Dubbo 会将第0个参数作为响应流,当客户端接收到响应消息时,触发它的onNext(),和服务端流是一样的。然后创建一个请求流 StreamObserver 对象作为结果返回,我们可以通过它给服务端持续的发消息。

AsyncRpcResult invokeBiOrClientStream(MethodDescriptor methodDescriptor, Invocation invocation,
                                      ClientCall call) {
    final AsyncRpcResult result;
    RequestMetadata request = createRequest(methodDescriptor, invocation, null);
    // 第0个参数是响应流
    StreamObserver<Object> responseObserver = (StreamObserver<Object>) invocation.getArguments()[0];
    final StreamObserver<Object> requestObserver = streamCall(call, request, responseObserver);
    // 请求流对象作为结果返回
    result = new AsyncRpcResult(
        CompletableFuture.completedFuture(new AppResponse(requestObserver)), invocation);
    return result;
}

当我们调用requestObserver.onNext(),实际上就是在给服务端发消息。

@Override
public void onNext(Object data) {
    if (terminated) {
        throw new IllegalStateException(
            "Stream observer has been terminated, no more data is allowed");
    }
    call.sendMessage(data);
}

Server端源码

对于服务端而言,核心是:

  • 对于服务端流,把 responseObserver 设置到invocation.arguments,透传给业务实现,允许业务自行推送数据给客户端。

  • 对于双向流而言

    • 方法返回结果是 requestObserver,当接受到客户端消息时,触发它的onNext()
    • 方法的请求参数是 responseObserver,业务可以调用它给客户端推送消息。

服务端发起本地调用时,会触发AbstractServerCall#startInternalCall(),不论是哪种 RPC 调用方式,都需要构建一个 responseObserver 对象来给客户端响应结果。区别是 UNARY 调用不用将 responseObserver 透传给业务,Streaming 调用需要透传给业务。

switch (methodDescriptor.getRpcType()) {
    case UNARY:
        listener = new UnaryServerCallListener(invocation, invoker, responseObserver);
        request(2);
        break;
    // 对于Streaming调用,responseObserver需要透传到业务实现
    case SERVER_STREAM:
        listener = new ServerStreamServerCallListener(invocation, invoker,
            responseObserver);
        request(2);
        break;
    case BI_STREAM:
    case CLIENT_STREAM:
        listener = new BiStreamServerCallListener(invocation, invoker,
            responseObserver);
        request(1);
        break;
    default:
        throw new IllegalStateException("Can not reach here");
}

透传以后,业务实现可以自行调用 responseObserver 给客户端推送数据,返回结果 Dubbo 就不管了。

对于双向流还有 一个点需要处理,Dubbo 需要把服务返回结果设为 requestObserver,且在收到客户端消息时触发监听。
关键方法是AbstractServerCallListener#onReturn(),对于结果值的返回:

  • UNARY 调用时,调用responseObserver.onNext返回结果,再关闭流。
  • 服务端流,不做任何处理,由业务自行返回。
  • 双向流,把返回结果设为 requestObserver,靠它来监听客户端流推送。
@Override
public void onReturn(Object value) {
    // 双向流 需要把返回值设为requestObserver,靠它监听客户端流推送
    this.requestObserver = (StreamObserver<Object>) value;
}

如此一来,服务提供方就可以通过返回的 StreamObserver 对象来监听客户端流推送;调用参数里的 StreamObserver 对象来给客户端推送消息。