1分钟了解响应式编程 | 合适的架构调整

作者:Mars酱

声明:本文章由Mars酱编写,部分内容来源于网络,如有疑问请联系本人。

转载:欢迎转载,转载前先请联系我!

前言

上篇我们讲到了dubbo官方的例子,根据响应式的基本概念,dubbo 的 StreamObserver 并不提供内置背压支持。

毕竟RPC的设计初衷不是为了响应式而设计的,看下差异吧

RPC 和 响应式 的差异

RPC 响应式
是否阻塞 ⭕️
返回值 单值(一次调用,一次返回值) 多值(一次调用,流式返回值)
背压 不支持。客户端请求,服务端响应并推送,不管客户端是否有能力消费 支持。客户端请求,服务端推送,客户端消费可以控制消费速度,再向服务端拿。

既然有差异,那如何完美处理

建立工程

首先,我们确认一下目标:

  1. 工程是dubbo框架;
  2. 工程支持 Flowable 背压式响应

于是,我们创建一个dubbo工程,经典的consumer层、facade层、provider层三层结构,像是下面这样:

dubbo provider

这是服务提供者工程,提供者给消费者提供每秒发送一个序列号,序列号是时间的毫秒数,上码:

java 复制代码
@DubboService(protocol = "tri") // ← 显式指定端口
public class EventServiceImpl implements EventService {

    @Override
    public Flux<Event> streamRealTimeEvents() {
        return Flux.interval(Duration.ofSeconds(1))
                .map(seq -> new Event("id-" + seq, "Hello at " + System.currentTimeMillis()));
    }
}

dubbo对外提供 Flux 流,Flux是reactor-core 这个jar包中的类,它的优势是和spring家族的集成比较好,比如:spring webflux。它是天然支持背压的对象。

dubbo consumer

调用的地方跟我们调用本地接口一样,这是RPC的规范,因此我们在consumer端写一个单元测试区调用,上码:

java 复制代码
@SpringBootTest(classes = DubboConsumerApplication.class)
public class EventServiceTest {
    private static final Logger log = LoggerFactory.getLogger(EventServiceTest.class);
    // 👇 关键:通过 url 直连,不走注册中心
    @DubboReference(
            protocol = "tri",
            url = "tri://127.0.0.1:50051"  // ← 直连地址
    )
    private EventService eventService;

    @Test
    public void testDubbo() {
        Flux<Event> eventFlux = eventService.streamRealTimeEvents();
        eventFlux
                .take(5) // 只取前5个事件
                .doOnNext(data -> {
                    log.info(">> data:{}", data);
                })
                .blockLast(); // 阻塞直到最后一个元素到达或流完成
    }
}

好了,我们运行检验吧!

工程主要使用 spring-boot + dubbo-triple ,先启动provider工程,当出现

这样的日志的时候,表示启动已经成功了。那么,我们开始流式调用吧,运行consumer中的那个单元测试,结果...

出错了

运行之后,报错!错误提示说是因为 reactor.core.publisher.FluxMap 对象没有实现 Serializable 接口。

为什么会这样?因为RPC的传输协议中对于序列化的检查是有要求的

就算我们通过配置yaml文件的序列化检查关闭掉、把包加入白名单,都是无济于事无法解决这个问题。

那我又想有背压,又想使用dubbo,怎么办?

完美的解决方案有吗?

有!需要修改结构!

其实,这是一个非常典型的 "内部响应式 + 外部 RPC 同步" 架构问题。你的目标需要修改为:

  • provider 层:只传输简单 JavaBean,不暴露 Flux/Flowable;
  • consumer 层:接收到 provider层的JavaBean之后,再封装为Flux/Flowable 流;
  • 如果还有对外层,比如web层的controller:对外提供 Flux/Flowable 接口(例如用于 SSE、流式响应等);

总之,不应该在RPC的传输层去传输Flux/Flowable对象,RPC的接口只返回正常的POJO对象,比如:

java 复制代码
// 错误:暴露 内部类型
Flux<Event> streamRealTimeEvents();

// 正确:返回普通对象
List<Event> listRealTimeEvents();

修改后的provider是这样,上码:

java 复制代码
@Override
public List<Event> listRealTimeEvents() {
	List<Event> eventList = new ArrayList<>();
	for (int i = 0; i < 5; i++) {
		eventList.add(new Event("id-" + i, "Hello at " + System.currentTimeMillis()));
		try {
			Thread.sleep(1000); // 每秒添加一个
		} catch (InterruptedException e) {
			Thread.currentThread().interrupt();
			break;
		}
	}
	return eventList;
}

修改后的 consumer是这样,上码:

java 复制代码
@Test
public void testDubbo2() {
	List<Event> events = eventService.listRealTimeEvents();

	// 将 List 转换成 Flux 流
	Flux<Event> eventFlux = Flux.fromIterable(events);
}

进阶理解

如果你的工程像我这样,有时间上的紧密关联,那么这么做是有问题的!

因为provider层每秒产生一组序列号,返回给consumer层之后,有时间消耗,但是如果你的场景和时间无关的,那么上面这种方式就满足你的场景了,如果场景和时间有关,那么根据你的场景最合适的方式可能就是在consumer层去做Flux/Flowable的流处理,provider则负责处理普通crud逻辑。

因为 Dubbo 是 "请求-响应"模型,必须等 listRealTimeEvents() 完全执行完才能返回 List,中间过程无法实时推送给 consumer,理论上不算是真正的"实时流式传输"

真正的流式方案

如果你必须实时推送每个 chunk (如大模型逐字输出),则 不能依赖 Dubbo 作为中间层,以下有几个建议:

建议1:绕过 Dubbo,consumer 直接流式处理

java 复制代码
@Test
public void testDubbo2() {
	Flux.interval(Duration.ofSeconds(1))
                .map(seq -> new Event("id-" + seq, "Hello at " + System.currentTimeMillis()));
}

对,你没看错,本来provider的处理逻辑直接搬到consumer层,这样做

优点:真正实时;

缺点:失去 Dubbo 的服务治理能力(负载均衡、熔断等)。

建议2:使用消息队列 or WebSocket 做异步通知

  1. Dubbo 接收请求后,提交任务到 MQ;
  2. 异步消费 MQ,调用 Coze SDK;
  3. 每收到一个 chunk,通过 WebSocket/SSE 推送给前端;
  4. Dubbo 接口只返回任务 ID。

建议3:升级到支持响应式 RPC 的框架

  • 如 gRPC + Reactor(支持 server streaming);
  • 或 Spring Cloud Gateway + WebFlux 直连后端服务。

总结:如何选择?

需求 推荐方案
只需最终结果 Dubbo 返回 Event 普通JavaBean,consumer 直接返回
需要所有事件(可接受延迟) Dubbo 返回 List,consumer 转Flowable.fromIterable()
必须实时流式输出 consumer 直接输出(绕过 Dubbo)或改用消息推送

最后提醒

  • 不要试图让 Flux/Flowable 穿透 Dubbo ------ 这违背了 RPC 的设计原则;
  • 响应式编程应在单机边界内完成,跨网络用数据,不用流。
相关推荐
向上的车轮17 小时前
为什么.NET(C#)转 Java 开发时常常在“吐槽”Java:checked exception
java·c#·.net
Dragon Wu17 小时前
Spring Security Oauth2.1 授权码模式实现前后端分离的方案
java·spring boot·后端·spring cloud·springboot·springcloud
跳动的梦想家h18 小时前
环境配置 + AI 提效双管齐下
java·vue.js·spring
坚持就完事了18 小时前
Java中的集合
java·开发语言
wjhx18 小时前
QT中对蓝牙权限的申请,整理一下
java·数据库·qt
一个有梦有戏的人18 小时前
Python3基础:进阶基础,筑牢编程底层能力
后端·python
YCY^v^18 小时前
JeecgBoot 项目运行指南
java·学习
人间打气筒(Ada)18 小时前
jenkins基于Pipeline发布项目
java·pipeline·jenkins·流水线·ci·cd·cicd
爬山算法18 小时前
Hibernate(88)如何在负载测试中使用Hibernate?
java·后端·hibernate
自不量力的A同学18 小时前
Solon AI v3.9 正式发布:全能 Skill 爆发
java·网络·人工智能