第 13 章 —— ChangeStream 的背压与溢出策略
本章目标: 本章完成后,开发者能够为 ChangeStream 显式选择容量、事件来源和溢出策略,并把丢弃、阻塞与回调超时变成可观察结果。
学习目标
- 区分集合收敛与 ChangeStream 事件投递。
- 解释有界缓冲为何是稳定性边界。
- 比较
DROP_OLDEST、DROP_NEWEST、BLOCK和ERROR。 - 根据消费者用途选择本地/远端事件来源与失败策略。
前置条件
- 已完成第 5 章的 Register 变更流实验。
- 理解第 11—12 章的 delta 与 repair。
- 不把 ChangeStream 当可靠业务事件日志。
案例进度
Metrics Adapter 订阅
route-hints,把变化投给一个变慢的诊断面板。集合本身仍在正常更新,但消费者速度低于生产速度。团队需要选择:丢旧事件、丢新事件、短暂阻塞,还是明确报错。
缓冲区满时的策略选择
ChangeStreamOptions 默认包含本地和远端事件,缓冲容量为
1024,溢出策略为 DROP_OLDEST,阻塞上限 1 秒,订阅回调上限 5
秒。默认值使流保持有界,但默认值并不替代业务选择。
| 策略 | 满时行为 | 更适合 | 主要代价 |
|---|---|---|---|
DROP_OLDEST |
丢最旧事件,接纳最新事件 | UI 刷新、当前状态提示 | 中间变化不完整 |
DROP_NEWEST |
保留已有队列,丢新事件 | 先到先处理的短队列 | 当前状态可能更陈旧 |
BLOCK |
等待消费者腾位直到 timeout | 可承受短抖动的受控消费者 | 生产线程等待;超时仍会丢 |
ERROR |
抛结构化溢出异常 | 不允许静默损失的验证/控制路径 | 调用方需要处理失败 |
这些策略改变的是观察流,不是已经提交的集合值。ChangeStream
丢一条事件后,register.get(key)
仍可能返回最新值;消费者若需要当前状态,应重新读取集合,而不是假设事件序列完整。
来源过滤也是契约
事件 origin 区分本地操作与远端 delta。只观察
includeLocal=true 适合确认本节点动作;同时观察 local/remote
适合诊断收敛;两者都关闭没有意义,因此构造器会拒绝。
对贯穿案例:
- 诊断面板同时看 local 与 remote,标注 origin。
- 本地发布审计辅助可以只看 local,但它仍不是交易审计源。
- repair 验证需要结合最终集合读取,不能只数 ChangeEvent。
慢回调不能无限占住推送线程
push subscriber 的回调也有超时。超过
subscriberCallbackTimeout 时,流通过 onError
暴露失败。把用户代码放在无限制回调中,会把「一个面板慢」扩散成整个本地更新路径的资源问题。
稳定做法是:回调只做快速转交;耗时 IO 进入独立有界执行器;同时记录 overflow 和 callback timeout 的低基数指标。
反例与故障注入
把容量设为 1,连续发布两个变化但不消费:
DROP_OLDEST最终留下第二条;DROP_NEWEST最终留下第一条;BLOCK等消费者腾位,超时后保留已有事件并记录 overflow;ERROR抛出结构化异常并记录指标。
这个实验刻意不讨论哪一个「总是正确」。正确答案由消费者是否需要最新视图、顺序前缀还是显式失败决定。
实验
cd submodule/dsm
mvn -q -pl dsm-runtime -am \
-Dtest=BufferedChangeStreamTest,InMemoryDsmRegisterTest \
-Dsurefire.failIfNoSpecifiedTests=false test然后完成 overflow-cases.json:为四种策略写出 retained
event、producer outcome 和 required metric。
实验验收卡
| 字段 | 内容 |
|---|---|
| 运行命令 | 上述 Maven
聚焦测试;node --test tests/chapter-assets.test.mjs |
| 输入或故障 | 容量 1、两条事件、慢消费者和超时回调 |
| 可观察结果 | 四种策略保留/拒绝行为不同;overflow 与 callback timeout 可见 |
| 证据等级 | E2:集合和缓冲区行为测试 |
| 本实验未证明 | ChangeStream 可靠历史投递、跨进程 exactly-once、生产容量足够 |
回顾
- 集合状态正确不代表每个订阅者都收到每条事件。
- 有界容量需要配套显式 overflow policy。
- 阻塞是有上限的策略,不是无限可靠性。
- 观察流漏事件后,应按用途重读当前状态或转向可靠事件系统。
下一步
第 14 章把所有失败窗口叠在一起:网络分区时两个节点都可能返回局部成功,而 repair 不能撤销已经发生的业务承诺。