作者:Mars酱
声明:本文章由Mars酱编写,部分内容来源于网络,如有疑问请联系本人。
转载:欢迎转载,转载前先请联系我!
前言
因为最近AI很火,所以跟着火起来的还有"响应式编程",而有些地方叫"流式编程",我系统是Dubbo的微服务架构,在对接AI的时候遇到了一些问题,所以,借着对接的机会也就简单的学习下这个东西,顺便做个笔记记录。那么,我们先从概念入手吧,一分钟快速了解这些玩意。
什么是响应式编程
搜索了一下,给出了下面的定义:
响应式编程(Reactive Programming,简称 RP)是一种 以"数据流"和"变化传播"为核心的编程范式------简单说,就是把程序中的"数据变化"或"事件"封装成可观察的"流"(Stream),通过声明式的方式定义"数据流的处理逻辑",让系统自动响应数据流中的每一个元素(如事件、数据项、状态变更),并高效处理异步、并发场景。
它的核心目标是:优雅处理异步非阻塞操作、简化并发代码、应对高吞吐/低延迟场景(比如实时数据推送、高并发接口、流式数据处理)
那么,这不就是 和 Dubbo 等分布式框架的核心价值是一致的吗?给的这个概念很难懂,我东搜西搜,自己总结了一下
举个生活中的例子
我是这么理解响应式过程的:
我在瑞幸点了一杯9.9的美式加浓(等于触发"订单事件")→ 最近的门店的服务员接收订单(订阅事件)→ 咖啡吧台制作咖啡(处理数据流)→ 咖啡做好后通知我取餐(推送结果)→ 我取餐(消费结果) → 完成
在这个过程中,我并不需要在吧台点餐,然后盯着服务员制作,做完之后再喊我取餐,整个这个过程是无阻塞的,当我的咖啡做好后会"主动响应"我 ------ 这就是响应式的核心:事件驱动、被动响应、非阻塞等待。
其实,它就是个异步的过程,对应到我们的编程中,基本可概括为 3 点:
- 数据流:所有"变化"(如:排队几个人、剩余多少倍、等待要多久等等事件情况)都被封装成"可观察的流"(Observable Stream),流中可以包含 0 个、1 个或多个元素(数据/事件)
- 变化自动化:当数据流产生新的变化(如:从剩余3杯到 给我制作的结果、从制作完成到等待取餐的结果),系统会自动将变化传递到整个处理链路,无需手动轮询、阻塞等待
- 声明式:开发者只需关心如何处理数据流,而不用关心"数据何时到达"、"如何异步等待",这些都会由底层框架自行处理线程调度、异步逻辑
响应式编程那不就是异步编程?
看完上面那个例子,很多人会觉得:这不就是异步编程?搞个MQ队列不就能实现?复杂点的话自己写个异步线程不就行了?额....
响应式编程并非单纯的"异步编程",而是一套标准化的规范
又搜了一下,主要是具备 4 个关键特性,也是它能应对高并发场景的核心:
| 特性 | 含义 |
|---|---|
| 响应式(Responsive) | 系统能快速响应请求/变化(比如数据流到达后立即处理),保证低延迟。 |
| 弹性(Resilient) | 系统在高负载、部分组件故障时仍能正常响应(比如通过背压控制流量、故障隔离)。 |
| 弹性(Elastic) | 系统能根据负载自动伸缩(比如数据流激增时动态调整处理资源)。 |
| 消息驱动(Message-Driven) | 组件间通过异步消息(数据流)通信,避免直接耦合,支持非阻塞交互。 |
(以上特性参考 Reactive Manifesto )其中最关键的两个技术特性是:
1. 非阻塞
非租塞这个概念应该不用我介绍了,概念可以看我之前的文章 《Java | 一分钟掌握异步编程 | 1 - 基本概念》 这篇。
传统同步编程中,调用一个操作,比如 Dubbo 服务调用、数据库查询,会阻塞当前线程,直到结果返回, ---这会导致线程资源浪费,高并发下其实是容易出现线程耗尽。
响应式编程中,耗时操作会立即返回一个"流对象"(如 Flux/Flowable),当前线程可以继续处理其他任务;当结果产生时,流会通过回调通知线程处理结果------全程无阻塞,线程利用率大幅提升。
示例对比:
- 同步编程(阻塞式):
java
// 调用后阻塞线程,直到结果返回
String result = marsDubboService.getData();
System.out.println(result); // 必须等上面执行完才会执行
- 响应式编程(非阻塞式):
java
// 调用后立即返回 Flux,不阻塞线程
Flux<String> resultFlux = marsReactiveDubboService.streamData();
// 声明"结果到达后如何处理",当前线程可继续做其他事
resultFlux.subscribe(source -> System.out.println(source));
// 或者
Flowable<String> resultFlowable = marsReactiveDubboService.streamData();
resultFlowable.subscribe(source -> System.out.println(source));
2. 背压
背压在响应式编程中是很重要的概念。当"数据流"生产者的速度超过"消费者"处理数据的速度时,会导致数据堆积、内存溢出,而背压就是解决这个问题的机制:消费者可以主动告诉生产者"我能处理多少数据",生产者根据消费者的处理能力调整数据发送速度。
比如 Flowable(RxJava)、Flux(WebFlux)都原生支持背压,它们会:
- 消费者处理能力弱时,会通知生产者"暂停发送",直到自己处理完缓存数据;
- 消费者处理完后,再通知生产者"继续发送",避免数据溢出。
响应式编程 vs 传统异步编程
传统异步编程(如 Java 的 Runnable、Future)存在明显痛点,而响应式编程正是为解决这些痛点而生:
1. 传统异步编程的问题:回调地狱
当多个异步操作存在依赖关系时(如"先调用 Dubbo 服务 A,再用 A 的结果调用服务 B,再用 B 的结果调用服务 C"),会出现嵌套多层回调的代码,可读性、可维护性极差,这就是回调地狱,代码如下:
java
// 传统 Future 异步回调
marsDubboServiceA.getData().addCallback(a -> {
marsDubboServiceB.process(a).addCallback(b -> {
marsDubboServiceC.save(b).addCallback(c -> {
System.out.println("最终结果:" + c);
}, error -> error.printStackTrace());
}, error -> error.printStackTrace());
}, error -> error.printStackTrace());
2. 响应式编程的解决:链式调用解决地狱问题
响应式框架提供了丰富的"操作符"(如 map、flatMap、filter),可以将多个异步操作串联成一条"处理流水线",能够解决回调地狱:
less
// 响应式链式调用
marsDubboServiceA.reactiveGetData() // 返回 Mono<String>
.flatMap(a -> marsDubboServiceB.reactiveProcess(a)) // 依赖 A 的结果调用 B
.flatMap(b -> marsDubboServiceC.reactiveSave(b)) // 依赖 B 的结果调用 C
.subscribe(
c -> System.out.println("最终结果:" + c),
error -> error.printStackTrace()
);
常见响应式框架
既然能解决地狱问题,那么响应式编程有哪些框架支持呢,以下是 Java 生态中最常用的框架:
- RxJava :最早成熟的响应式框架,支持
Flowable(背压)、Observable(无背压)、Single(单结果)等,适合需要精细控制数据流的场景 - Spring WebFlux :Spring 生态的响应式 Web 框架,基于 Reactor 实现,提供
Flux(多结果流)、Mono(单结果流),与 Spring 生态(如 Spring Boot、Spring Cloud)无缝集成,是 Dubbo 响应式调用的首选 - Reactor :Spring WebFlux 的底层依赖,是 JDK 9+
Flow API的实现,专为高并发、低延迟设计,性能优秀
响应式编程的核心组件
不管是 RxJava、Spring WebFlux 还是其他响应式框架,都遵循一套通用的组件模型,对应"生产者-消费者"的交互流程:
| 组件 | 作用 | 对应实例(框架) |
|---|---|---|
| 发布者**(Publisher)** | 数据/事件的生产者,负责生成并发送数据流(可发送 0~N 个元素)。 | RxJava 的 Flowable/Observable、WebFlux 的 Flux/Mono |
| 订阅者**(Subscriber)** | 数据/事件的消费者,订阅发布者的数据流,接收并处理数据。 | RxJava 的 Subscriber、WebFlux 的 Subscriber |
| 订阅**(Subscription)** | 连接发布者和订阅者的桥梁,订阅者通过它请求数据、取消订阅(背压核心)。 | RxJava 的 Subscription、JDK 9+ 的 Flow.Subscription |
| 处理器**(Processor)** | 既是发布者也是订阅者,用于转换、过滤、合并数据流(如"将字符串转成整数")。 | RxJava 的 Processor、WebFlux 的 Processor |
核心流程:
- 订阅者通过
subscribe()订阅发布者; - 发布者创建
Subscription并返回给订阅者; - 订阅者通过
Subscription向发布者请求数据(如"请求 10 条数据"); - 发布者根据请求发送数据给订阅者(触发
onNext()回调); - 数据流结束时,发布者触发
onComplete()回调; - 发生异常时,发布者触发
onError()回调。
响应式编程的适用场景
响应式编程这么牛逼,它并不是所有场景都适合的,只适合以下场景:
- 高并发、低延迟需求:如秒杀系统、实时交易系统、高吞吐接口(Dubbo 响应式调用场景)。
- 流式数据处理 :如实时日志分析、物联网设备数据推送、大数据分批传输(对应 Dubbo 的
Flux/Flowable流式调用)。 - 多异步操作依赖:如多个 Dubbo 服务调用、数据库查询存在依赖关系(用链式调用替代回调地狱)。
- 资源有限的场景:如微服务网关(需要高效利用线程资源处理大量请求)。
不适合场景:
- 同步业务:如普通 CRUD 操作,响应式编程的"声明式语法"会增加不必要的复杂度;
- 强事务依赖场景:响应式编程的异步特性会增加事务管理的难度
总结
概念搜了一大堆,我反正是看懂了,下个章节简单讲讲Dubbo给的官方的响应式编程示例,我工程的架构是用的dubbo,好了,下个一分钟再见。