作者:Mars酱
声明:本文章由Mars酱编写,部分内容来源于网络,如有疑问请联系本人。
转载:欢迎转载,转载前先请联系我!
前言
上篇搜了一堆概念,拼凑了一些例子,现今我们的应用已经不是单例架构,一般都是分布式微服务架构,微服务架构情况下框架也很多,SpringCloud、Dubbo等等
我的工程是dubbo的,那我们看看Dubbo官方示例给出的响应式编程的例子,毕竟我看Dubbo官文说天然支持流式编程,好了,继续。
Dubbo对流式处理的支持
| 协议 | 性能 | 网关友好 | 流式通信 | 多语言支持 | 编程API | 说明 |
|---|---|---|---|---|---|---|
| triple | 高 | 高 | 支持,客户端流、服务端流、双向流 | 支持(Java、Go、Node.js、JavaScript、Rust) | Java Interface、Protobuf(IDL) | 在多语言兼容、性能、网关、Streaming、gRPC 等方面最均衡的协议实现,官方推荐。 |
| dubbo | 高 | 低 | 不支持 | 支持(Java、Go) | Java Interface | 性能最高的私有协议,但前端流量接入、多语言支持等成本较高 |
| rest | 低 | 高 | 不支持 | 支持 | Java Interface | rest 协议在前端接入、互通等方面具备最高的灵活性,但对比 rpc 存在性能、弱类型等缺点。注意,rest 在 dubbo3 中仅是 triple 协议的一种发布形式 |
从官方的文档中找到,Dubbo是支持流式编程的,但是需要使用triple协议,而不是其他协议。
从git上下载Dubbo Samples
bash
# 从git上下载 dubbo的官方示例,这是示例的git地址
https://github.com/apache/dubbo-samples.git
下载之后,导入到你的idea中,知道了triple协议支持流式通信,那么从导入好的idea中找到triple的stream相关的代码,如下图:

Dubbo服务端支持的两种流式处理
Dubbo通过triple协议支持了两种流式处理,一种是服务端流,另一种是双向流。
服务端流
首先我们看服务端流的示例代码,代码位置在dubbo-samples-triple-streaming工程中的GreeterImpl这个文件,我抽取了服务端流的方式,如下:
java
@Override
public void serverStream(GreeterRequest request, StreamObserver<GreeterReply> responseObserver) {
LOGGER.info("receive request: {}", request.getName());
for (int i = 0; i < 10; i++) {
GreeterReply reply = GreeterReply.newBuilder().setMessage("reply from serverStream. " + i).build();
responseObserver.onNext(reply);
}
responseObserver.onCompleted();
}
可以看到serversStream方法是没有void,没有返回值的,它通过GreeterRequest对象传递参数,然后把业务逻辑数据依次循环写入到StreamObserver对像中。
GreeterRequest对象可以是我们自己定义的业务请求对象,StreamObserver对象是dubbo框架对于流式处理的接口定义,如图:

响应结果被封装在这个接口对象中,实现了流式数据写入到消费者端的业务场景。
双向流
这是双向流的官方示例:
java
@Override
public StreamObserver<GreeterRequest> biStream(StreamObserver<GreeterReply> responseObserver) {
return new StreamObserver<GreeterRequest>() {
@Override
public void onNext(GreeterRequest data) {
GreeterReply resp = GreeterReply.newBuilder().setMessage("reply from biStream " + data.getName()).build();
responseObserver.onNext(resp);
}
@Override
public void onError(Throwable throwable) {
}
@Override
public void onCompleted() {
}
};
}
可以看到,双向流方法 binStream 的参数和返回值都是 StreamObserver 类型。
但需要注意的是,它与我们传统方法定义中参数是请求值、返回值是响应的理解是反过来的,在这里,函数的入参 StreamObserver responseObserver 是响应,跟之前一样,是在业务逻辑中不停的写数据到 回响应中。
这个函数也通过new了一个 StreamObserver 不断的在onNext函数中写入了请求流数据,而且这里 请求流 与 响应流 是独立的,我们在写回响应流数据的过程时,随时可能有请求流到达,对于每个流而言,值都是有序的。
Dubbo客户端的调用示例
dubbo对于两种流的处理方式是不同的调用实现
服务端流的客户端调用
调用 serverStream() 传入能够处理流式响应的 SampleStreamObserver 对象,调用发起后即快速返回,之后流式响应会不停的发送到 SampleStreamObserver 。
java
private static void serverStream(Greeter greeter) {
GreeterRequest request = GreeterRequest.newBuilder().setName("server stream request.").build();
greeter.serverStream(request, new SampleStreamObserver());
}
// 声名上面调用类的SampleStreamObserver实现
private static class SampleStreamObserver implements StreamObserver<GreeterReply> {
@Override
public void onNext(GreeterReply data) {
LOGGER.info("stream <- reply:{}", data);
}
@Override
public void onError(Throwable throwable) {
LOGGER.error("stream onError", throwable);
throwable.printStackTrace();
}
@Override
public void onCompleted() {
LOGGER.info("stream completed");
}
}
双向流的客户端调用
java
private static void biStream(Greeter greeter) {
// SampleStreamObserver的定义在 '服务端流的客户端调用示例' 中已经给出
StreamObserver<GreeterRequest> requestStreamObserver = greeter.biStream(new SampleStreamObserver());
for (int i = 0; i < 10; i++) {
GreeterRequest request = GreeterRequest.newBuilder().setName("name-" + i).build();
requestStreamObserver.onNext(request);
}
requestStreamObserver.onCompleted();
}
调用 greeter.biStream() 方法会立即返回一个请求流 requestStreamObserver 对象,同时,需要为方法传入一个能处理响应的 observer 对象new SampleStreamObserver()。
接下来,我们就可以用才刚才返回值中得到的 requestStreamObserver 持续发送请求 requestStreamObserver .onNext(reqeust), 此时,如果有响应返回,则会由 SampleStreamObserver 对象接收处理,
官例好像有问题
其实没有任何问题,只是dubbo支持的是observer的流式处理,这是一种无背压方式的流式处理。背压是一种流控机制 ,dubbo 的 StreamObserver 不提供内置背压支持
- 它的 onNext 方法是通过官方的示例就知道它属于阅后即焚风格;
- 调用 onNext 会立即把数据写入网络缓冲区(或本地队列),不会等待消费者处理完成;
- 如果消费者消费得太慢,而 producer 持续高速发送,有没有可能 导致 OOM 或者其他问题?没试过,存疑
这与 Reactive Streams 规范 貌似不同,并没有背压特征,但是RxJava框架不也是支持背压、无背压方式的吗?所以,应该根据自己的场景来选择是否需要背压方式,超时了,下个1分钟看怎么用dubbo处理背压特征的响应式编程吧。