含flatMap与多observeOn的Flowable链路背压未生效问题排查
背压失效的核心根因
你核对过所有操作符的背压支持标识、确认所有组件都具备合规背压能力,但仍然出现内存溢出的问题,和observeOn本身的实现逻辑无关,根源完全出在你链路里用了默认参数的flatMap操作符上——flatMap虽然本身是完整支持背压的操作符,但它的背压折算逻辑和你预期的“1:1透传下游请求”并不一样,具体的失效路径可以拆成三步说清楚:
flatMap的默认并发上限是RxJava内置的bufferSize()常量(固定值128),它向上游拉取数据的逻辑不会严格跟着下游的实时请求数走:哪怕下游整条链路当前只发了1个元素的处理请求,flatMap也会立刻向上游(也就是最前面的Flowable.fromIterable)一次性请求拉满128个源级别的资源项。- 你用的上游源
Flowable.fromIterable是纯同步冷源,一旦收到订阅请求就会立刻完成迭代,完全不会做背压等待:被flatMap一次性拉取的128个资源项会立刻触发你定义的flowableUsing(it)子流订阅,每个子流平均产出1-2个元素,这部分合计128-256个中间结果会全部驻留在flatMap的内部缓存队列里,根本不会走到你设置了小缓冲的两个observeOn节点。 - 你配置的两个
observeOn(bufferSize=1)的背压逻辑本身是完全合规的:它们只会在自身队列空出位置时才向上游发请求,但因为flatMap提前在自己内部缓存了远超下游处理能力的元素,这部分缓存完全不受observeOn的队列大小约束,最终就会随着缓存量持续上涨触发OOM。
关于observeOn构建的异步边界对背压的影响,其实它本身不会破坏背压逻辑:它的本质是把上下游的执行线程拆开,在边界处严格按照自身的队列容量向上游传递请求量——但这个请求量到达
flatMap之后,并不会被精准透传到最上游的迭代源,反而会被flatMap的默认并发拉取逻辑放大成远超实际需求的拉取量。
针对性修复方案
- 显式调低
flatMap的并发上限,和下游处理能力匹配:把原来的flatMap { flowableUsing(it) }替换为flatMap({ flowableUsing(it) }, 1),强制flatMap同一时间只从上游拉取1个资源项,等处理完成发往下游、收到下游的下一个请求后,再拉取下一个资源,从根源上消除flatMap内部的过度缓存。 - 如果场景需要保留一定的并发处理能力,不要直接用默认的128并发值,根据下游写磁盘的实际吞吐把并发数设置为匹配的量级(比如4-8),避免一次性拉取过多源数据驻留内存。
- 验证修复效果:调整参数后重新观察内存画像,会发现原来在
flatMap内部缓存的批量元素会消失,剩余待处理的元素只会存在于两个observeOn的固定大小队列里(每个队列最多1个元素),内存占用会长期维持稳定。
内容的提问来源于stack exchange,提问作者Ben Butterworth
相关产品推荐
相关产品推荐

