Flux+WebFlux+Netty 响应式流式业务完整总结(SSE生产级)
约 1974 字大约 7 分钟
2026-08-12
前言
本文基于生产级 AI 流式 SSE 代码,从底层原理、线程模型、信号机制、两层 Flux 嵌套、WebClient 工作机制、背压、订阅发布、常见坑点做终极闭环总结。解决绝大多数 Java 响应式编程最核心、最容易混淆的认知误区。
核心定位:Spring WebFlux + Project Reactor + Netty IO多路复用 完整链路原理。
一、最核心底层认知(必须牢记)
1. Reactor 与 Netty 不是同一个东西
Netty Reactor模式:网络层 IO 模型(主从 Reactor、多路复用、事件驱动),负责收发包、管理连接。
Project Reactor:业务层响应式编程框架(Flux/Mono、操作符、背压、订阅发布),负责数据流编排。
关系:Netty 负责“数据怎么收进来”,Reactor 负责“数据怎么流下去”。
2. Flux 本质:不是容器,不是队列
Flux/Mono 不存任何数据,只是一套声明式流水线规则。
创建 Flux = 定义流程(懒加载、冷流)
订阅 Flux = 真正执行、触发生产、触发网络 IO
数据只会在
onNext回调瞬间流动,不会持久存在 Flux 对象中
3. 双信号方向(90% 困惑的根源)
订阅信号:自下而上(下游 ➜ 上游)
数据信号:自上而下(上游 ➜ 下游)
二、两大线程切换核心:subscribeOn / publishOn
1. subscribeOn(控制订阅信号)
作用方向:向上游生效
控制内容:源头初始化、阻塞逻辑、整个流的启动线程
有无队列:无队列,纯线程切换
典型用途:隔离阻塞初始化逻辑(查库、限流、创建客户端)
特点:无论写在链路哪个位置,最终以最上游的 subscribeOn 为准
你的代码中:Flux.create 内部所有初始化逻辑,全部被外层 subscribeOn 切到 boundedElastic,释放 Tomcat 线程。
2. publishOn(控制数据信号)
作用方向:向下游生效
控制内容:后续所有 onNext、doOnNext、业务逻辑线程
有无队列:有内部无锁队列(跨线程必须缓冲)
典型用途:保护 Netty IO 线程,把耗时业务切走
特点:位置敏感,写在哪,从哪开始切换线程
3. 黄金铁律
subscribeOn 管源头、管启动、管阻塞前置
publishOn 管下游、管业务、管 IO 线程保护
WebClient 这种异步源头,不需要 subscribeOn,只会增加开销
publishOn 是唯一能改变 onNext 执行线程的操作符
三、WebClient + Netty 真实线程流转(重点)
1. WebClient 创建 Flux 阶段
所有链式调用(stream()、chatClientResponse())只组装、不执行、无网络IO。运行在当前业务线程。
2. 订阅后真正触发网络
订阅信号上传到 WebClient 底层,任务交给 Netty IO EventLoop 线程:
建立连接、发送请求、监听 TCP
收到 SSE 分片 ➜ 解析数据 ➜IO 线程直接调用下游 onNext
3. 线程分界(你的代码精准结构)
publishOn 上游:Netty IO 线程(只做网络解析,极快)
publishOn 下游:boundedElastic 线程(执行业务、转换事件、埋点)
意义:绝不阻塞 IO 线程,保证网关、长连接、海量连接不雪崩。
四、两层 Flux 嵌套模型(你代码的核心架构)
你的代码是标准生产级双层冷流嵌套:外层 Flux.create + 内层 WebClient Flux
1. 内层 Flux(WebClient)
作用:真实对接大模型 SSE 数据流
驱动:Netty IO 线程驱动 onNext
冷流:订阅才发请求
2. 外层 Flux(Flux.create)
作用:业务封装层
承载限流、trace、异常包装、自定义事件、生命周期管控
手动通过 emitter.next() 向下游推送标准化 SSE 事件
3. 为什么必须两层?
直接返回 WebClient Flux 做不到:
无法优雅做前置同步逻辑(限流、查服务商)
无法统一封装 error / done 事件
无法完美绑定「客户端断开 ➜ 取消上游大模型请求」
无法自定义溢出策略、背压缓冲
五、Flux.create + FluxSink 原理
1. create 的 lambda 执行时机
只有订阅时才执行,不是创建对象时执行。天然冷流。
2. FluxSink(emitter)本质
emitter 是手动适配响应式规范的适配器:
emitter.next() ➜ 向下游触发 onNext
emitter.complete() ➜ 向下游触发 onComplete
emitter.error() ➜ 向下游触发 onError
3. Sink 队列与背压
FluxSink 才有队列,Flux 本身没有队列。
BUFFER:无限缓冲(适合 SSE 弱网)
队列作用:适配下游背压,防止线程阻塞、数据丢失
4. onDispose 核心价值
唯一能监听客户端主动断开的钩子(doOnComplete/doOnError 监听不到)。
实现:用户关闭页面 ➜ 自动 dispose 大模型请求 ➜ 释放信号量,杜绝资源泄漏。
六、三大副作用钩子(doOn)使用规范
全部为纯监听、不改变流数据、不改变流程的副作用操作符。
doOnNext:每片数据到达触发,用于缓存最后一条分片(取 usage、token 统计)
doOnComplete:正常结束触发,落库、上报、推送结束事件
doOnError:异常触发,错误封装、异常埋点
终极收尾方案:优先使用 doFinally,同时覆盖 正常结束 / 异常 / 取消断开 三种场景。
七、SSE 全链路线程时序(完整闭环)
Tomcat Worker 线程:接收请求、调用接口、发起外层 Flux 订阅
subscribeOn 切线程:订阅动作转入 boundedElastic,Tomcat 线程立刻释放
boundedElastic 线程:执行 create lambda、限流、初始化 trace、手动订阅内层 WebClient
Netty IO 线程:发起 HTTP 流请求、接收 TCP、解析 SSE 分片、向上游发出 onNext
publishOn 线程切换:数据入队列,IO 线程解放
boundedElastic 线程:消费分片、更新原子引用、封装业务事件、emitter.next 推向外层
Spring MVC 异步线程:将事件写出为 SSE 响应推给前端
结束/断连:触发 onDispose,取消上游、释放并发信号量
八、背压完整原理(从 Reactor 到 TCP)
背压不是代码逻辑,是全链路操作系统级联动:
前端网络慢 / 消费慢
Servlet 输出流阻塞,下游 request 配额耗尽
外层 Sink 队列堆积,停止消费上游数据
publishOn 队列堆积,向上游传递背压
WebClient 底层关闭 Netty 自动读
不读取 TCP 缓冲区,内核缓冲区堆满
TCP 滑动窗口缩小,大模型服务端自动降速
全程零业务代码干预,全自动自适应,这是响应式最大优势。
九、生产级代码致命坑点(必须修正)
1. 信号量重复释放(高危)
doOnComplete / doOnError / onDispose 三处释放,会导致重复 release,限流失效、并发爆炸。
正确方案:统一使用 doFinally 或 原子布尔幂等释放。
2. BUFFER 无上限内存风险
客户端弱网极端场景无限堆积,有 OOM 风险。线上需控制队列上限或搭配降级策略。
3. IO 线程禁止执行业务阻塞逻辑
无 publishOn 时,所有 doOnNext 逻辑跑在 Netty IO 线程,一旦阻塞,连接全部卡死。
4. 必须监听 onDispose
用户关闭页面属于 cancel 信号,不会触发 complete/error,不处理会导致请求残留、资源泄露、配额卡死。
十、最终极简口诀(随时复盘)
Flux 是规则不是容器,冷流订阅才执行
订阅信号向上走,数据信号向下走
subscribeOn 管源头阻塞,publishOn 管下游业务
Netty IO 只收包,业务线程只干活
两层 Flux 做封装,Sink 手动桥接数据流
doFinally 统一释放资源,杜绝泄漏
全链路背压自动调速,高并发不崩
(注:部分内容可能由 AI 生成)