RxJava 中的背压(Backpressure)问题是什么?如何解决?

解读

在国内一线 Android 面试中,背压几乎成了“高并发+响应式”必考题。面试官想确认三件事:

  1. 你是否真的在复杂业务里用过 RxJava(而不是只会 Retrofit 适配器);
  2. 能否把“生产速度 > 消费速度”这一抽象场景具象到 UI、埋点、日志等真实模块;
  3. 能否给出可落地的国内方案,比如针对国产 ROM 后台限制、低端机 CPU 核数少、ROM 定制线程池策略差异等做权衡。

背压不是“背压策略”几个字就能过关,必须展示对线程模型、调度器、丢弃/缓存/批量采样、以及 Kotlin 协程迁移成本的体系化思考。

知识点

  1. 背压本质:Observable 流是 push 模型,下游消费速率有限,上游生产速率无限,导致 MissingBackpressureException。
  2. 冷热流差异:Observable 不支持背压,Flowable 支持;国内很多老模块仍用 Observable.create() 手写发射,极易踩坑。
  3. 背压策略:BUFFER(无限缓存,OOM 风险)、DROP(丢弃最新/最旧)、LATEST(保留最新)、ERROR(直接抛异常)、MISSING(不管,自己处理)。
  4. 操作符:onBackpressureBuffer/onBackpressureDrop/onBackpressureLatest、throttleFirst/throttleLast/sample、debounce、observeOn 的 bufferSize 参数。
  5. 线程与调度:国内厂商定制线程池(如华为 power-intensive 限制)可能导致 Schedulers.io() 并发度骤降,需要自定义 Scheduler 并配合背压策略。
  6. 监控与灰度:美团、阿里内部会在 onNext 前后插桩,统计队列长度,一旦超过阈值自动降级为 DROP + 埋点报警。
  7. 协程迁移:Kotlin Flow 的 buffer(Channel.BUFFERED) 与 onBufferOverflow 等价策略,可作为 Rx→Flow 迁移时的平滑过渡方案。

答案

背压问题指上游生产数据速度持续大于下游处理能力,导致内存暴涨或 MissingBackpressureException。解决思路分四层:

  1. 选对流类型
    新业务直接用 Flowable,老 Observable 通过 .toFlowable(BackpressureStrategy.BUFFER) 迁移;若无法改代码,则在 create 内部使用 setDisposable 配合 onBackpressureDrop 手工丢弃。

  2. 选择合适策略
    UI 事件流(滚动坐标、传感器)采用 onBackpressureLatest + sample(100ms),既保流畅又防 OOM;埋点日志流允许丢数据,用 onBackpressureDrop + 计数器降级,丢失超过 1% 时上报监控。

  3. 调度器与缓冲区
    observeOn(AndroidSchedulers.mainThread(), false, 128) 显式指定缓冲区大小,避免默认 128 在低端机仍不够;若国产 ROM 限制后台线程,使用自定义 Scheduler(单线程 + 有界队列)兜底。

  4. 业务层兜底
    在 doOnNext 中记录队列长度,超过阈值 80% 时主动切换为 DROP 模式,并写入 MMKV 做灰度开关;对账阶段通过服务器时间窗口补齐丢弃数据,保证财务链路最终一致。

一句话总结:先“冷热流”选型,再“策略+操作符”控速,再“线程模型”调参,最后“业务灰度”兜底,四步闭环,背压不再只是面试题,而是线上可度量的稳定性指标。

拓展思考

  1. 折叠屏 120Hz 高刷场景下,触摸事件采样率翻倍,传统 16 ms 采样已不够;可尝试 throttleLatest(8ms) + onBackpressureLatest,配合 SurfaceFlinger 帧率动态调整。
  2. 国内厂商后台省电策略会冻结 Schedulers.io() 线程,导致背压策略失效;可接入厂商提供的 PowerKeeper SDK,在后台切换为单线程串行消费,前台再恢复并行。
  3. 如果团队已启动 Rx→Flow 迁移,可用 Flow.buffer(onBufferOverflow = BufferOverflow.DROP_OLDEST) 与现有 Rx 背压策略对齐,保持埋点口径一致,避免 AB 实验指标震荡。