前言
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 ,它会作为服务端响应流。
- 创建请求元数据对象
- 解析响应流对象 StreamObserver
- 发起流式调用,在接收到响应消息时触发 StreamObserver
- 发送请求参数,发送 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,业务可以调用它给客户端推送消息。
- 方法返回结果是 requestObserver,当接受到客户端消息时,触发它的
服务端发起本地调用时,会触发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 对象来给客户端推送消息。