11.09 过载与级联故障:重试如何压垮 IM 历史补拉
返回第十一卷 · 有界并发:05.08 · 可靠重试:07.07 · SLO:11.08 · 容量:11.07
11.09 过载与级联故障:重试如何压垮 IM 历史补拉
DeepTutor 八节初稿经技术与教学审阅后的静态课程。故障、时钟、速率、容量、队列和恢复均为虚构纸上模型;未运行 Go、IM、数据库、故障注入、监控、部署或站点。当前 S2
/v1仍是正文非空且最多 6 UTF-8 B、原始 HTTP 请求正文最多 4096 B、同 ID 即使同正文重复也 409、非成员隐藏 404、成功200 accepted_in_memory仅代表本进程内存受理;未来 S3/v2的stored_in_teaching_db是提议且仍为 6 B,R9 6→9 B 待审。09.02 当前合同
一、一个慢依赖怎样变成整条 IM 链路变慢
前几章分别学了确认点、观测、容量与告警。本章以未来 S5 的纸上场景串起来:B 的设备重连,按 c-a 的会话序号补拉有权历史,权威库里有 m-9/seq9;历史查询依赖变慢,B 等不到完整结果,客户端与网关先后重试。原本每秒约 100 个用户补拉意图,可能很快变成远大于 100 的数据库请求尝试。CPU 图有时仍不高,因为大量 goroutine 在等池、队列或 I/O;增加 goroutine 或重启节点未必解决问题。11.06 低 CPU 等待
这是一条正反馈链:
依赖变慢 → 请求占用连接/worker 更久 → 队列与超时增加
↑ ↓
└──── 客户端/网关重试增加到达量 ←───────┘链条上的“业务结果”仍要分开:A 当前 S2 的内存受理 200 不证明 B 已补到历史;未来 S3 权威 m-9/seq9 存在也不证明某台设备已处理。级联故障是某处失效通过额外负载或资源占用向其他部位扩散,不能只看第一个慢组件。Google SRE 的级联故障章节特别指出重试、排队和无效工作会互相放大。Google SRE:Addressing Cascading Failures
二、先算用户意图,再算请求尝试和队列
用户意图是 B 希望恢复一次会话历史;请求尝试是为实现它实际发出的 HTTP/RPC 调用。设每秒有 100 个独立补拉意图,客户端每个意图最多 2 次重试(共 3 次尝试),网关对每次客户端尝试访问数据库也最多 2 次重试(共 3 次下游尝试)。只有在每层都用满尝试、无共享去重/缓存/提前终止的最坏条件下,一个意图可形成 3×3=9 次数据库尝试,即 100×9=900 次/秒。100 次用户需求没有变成 900 次真实新需求;放大的是系统内部工作。Google SRE:多层重试放大
再设数据库在该纸上工作负载下只能完成 120 次尝试/秒。若 900/s 全部进入同一等待队列,连续 5 秒没有拒绝、取消、过期清理、旁路或扩容,则净积压 (900−120)×5=3,900 次尝试。真实系统通常有有界队列与拒绝,不能说“实际一定积压 3,900”;若队列容量只有 200,多余的会被上游阻塞或拒绝,必须把这些分支计入守恒。下游数据库的“完成”也可能是错误结果,不等于 B 恢复了 120 条历史。11.04 到达/完成守恒
| 纸上量 | 本题单位 | 不应读成 |
|---|---|---|
| 100/s | 唯一用户补拉意图 | 数据库实际调用数 |
| 最多 900/s | 数据库请求尝试 | 900 位新增用户或 900 条新消息 |
| 120/s | 假设的数据库尝试完成能力 | 设备应用确认数 |
| 净增长 3,900/5s | 无其他流向时的等待尝试 | 有界队列的必然实际长度 |
压测和告警要分别记录用户意图、每层尝试数、重试比例、入队/出队、拒绝/超时/仍在途、最终有权历史恢复比例。若只看到“请求 QPS 升高”,不知道是否是真实用户增长还是重试风暴,就可能错误扩容前端并把更多流量送进已慢的权威库。11.07 计划与实际负载
三、超时与取消减无效工作,但不撤销已发生事实
为一次用户补拉设端到端期限,再把剩余时间沿客户端→网关→数据库传递。Go context.Context 的取消/截止可告诉下游停止等待和释放资源;若客户端已经放弃,网关不应无期限继续跑一个没人会看的历史查询。每个阶段在开始高成本操作前都可检查剩余时间,而不是给每层各加完整 2 秒,使一个用户动作实际等待 6 秒。Go:Context 与取消 · Google SRE:Deadlines
但取消信号不是撤销已提交业务事实。换回当前 S2 的 POST:服务端可能先完成内存受理,随后回应在网络中丢失;A 超时并取消请求,不会自动清除已受理的 m-a。客户端用相同消息 ID重试,按当前合同即使正文相同也得到 409,旧正文和顺序不覆盖;若想知道事实,须在相应版本的有权查询/状态确认边界核对。改用新 ID 猛发可能制造第二条业务消息。未来 S3 的 DB 提交若已发生,更不能靠上游超时“回滚”它。09.04 响应丢失 · 07.07 稳定身份与重试
错误类别决定能否重试。坏 JSON/超 6 B 的 400、非成员隐藏 404、同 ID 重复 409都不应无脑立即再发同一请求;409 首先应核对已有结果。未来若设计明确的“暂时过载/稍后再试”回应,应单独评审其状态码、重试提示、幂等性与客户端兼容,不能冒充当前 S2 已定义的 503/429 合同。重试必须有最终截止和次数预算,失败时清楚暴露“未确认”,不能无限占用资源。09.02 合同
四、退避与抖动防止所有设备同秒重连
一个网关重启后,假设 1,000 台虚构设备在同一秒重连并补历史。即使每台只发一次,数据库也会看到突发。若纸上把 1,000 次独立请求理想地均匀分散到 10 秒,平均约 100/s;这是算术平均,不保证每秒峰值刚好 100。真实重连时间、群热点、设备时钟和网络恢复会形成波峰,需测实际分布。11.07 开环与突发
指数退避让后续尝试的等待逐渐增长,抖动 jitter让不同客户端不要在同一固定时刻一起重试;再设每意图总尝试数、进程级重试预算和绝对期限。要选择一层负责主要重试,避免客户端、网关、消费者三层各自把一次故障乘成更多调用。即使某层有能力重试,也应判断下游是局部故障、整体过载还是永久错误,重试预算用尽后把明确结果交还上游。Google SRE:重试预算与抖动 · Google SRE:客户端过载处理
| 做法 | 能控制什么 | 没有解决什么 |
|---|---|---|
| 绝对期限与取消 | 无人等待时减少继续排队和占用 | 已经完成的内存受理或 DB 提交 |
| 次数/全局重试预算 | 抑制乘法放大 | 原依赖为什么慢 |
| 退避 + 抖动 | 降低重试同刻碰撞 | 目标总量大于长期处理能力 |
| 断线后历史补拉 | 从权威事实恢复遗漏 | 未授权用户不可因此看到私有历史 |
五、有界队列、隔离和熔断保护哪条事实
背压让生产者感知下游跟不上;有界队列/限流阻止内存无限积压,并在明确边界早拒绝;隔离让历史补拉的慢请求不占满权威发送或健康会话所需的全部连接/worker;熔断在依赖持续异常时暂时快速失败,待条件改善用有限探测恢复。它们都是控制资源与故障传播的机制,不能被写成“自动保证所有消息都成功”。Google SRE:限载与优雅降级 · 05.08 有界组合
| 机制 | 初学者应先说清的状态/边界 | IM 业务风险 |
|---|---|---|
| 有界历史队列 | 容量、满时拒绝/等待、取消如何移除 | 长等待变成拒绝后,用户结果仍可能坏 |
| 独立资源池 | 历史读与消息权威写的份额、共享 DB 上限 | 分池不等于共享数据库有余量 |
| 熔断器 | closed 正常通过;open 快速失败;half-open 少量探测 | 错把授权 404/重复 409 当故障会误开路 |
| 通知降级 | 哪类实时提醒可延后/省略,怎样补历史 | 不能丢权威 m-9/seq9 或放宽成员权限 |
例如当未来实时通知队列过载,只要权威历史确实保存、客户端能按权限和序号检测缺口并补拉,某些可恢复提醒可按明确政策延后或丢弃;这不等于允许丢失 m-9 权威记录、outbox 责任或设备处理状态。若 B 离线 25 小时,超过教学 broker 24 小时保留,恢复必须从有权 DB 历史按 seq9 补,不依赖 broker 仍有 E9。07.12 权威与离线恢复
熔断也要有退出条件:依赖恢复、有限 half-open 探测真正成功、用户坏事件下降,才逐步放开。若只是错误响应变快,平均/P95 可能改善,但更多 B 根本补不到历史;必须连同拒绝、完成、积压和 11.08 的用户结果一起评估。11.08 SLI 与预算
六、纸上 T0–T4:从原因候选到止损动作
下面不是任何实际系统的事故时间线。每一站把已给定的纸上事件与仍待取证的原因分开:
| 时刻 | 纸上现象 | 下一份证据 | 首要动作的边界 |
|---|---|---|---|
| T0 | 未来历史 DB 的所选读路径变慢 | 池等待、SQL 执行与设备 I/O,确认是否同窗 | 保留权威 m-9/seq9 与会话权限 |
| T1 | 补拉占用更久,等待队列增长 | 意图/尝试/队列/取消计数守恒 | 限制新入队,避免无界占用 |
| T2 | B 客户端超时,客户端与网关同时重试 | 两层尝试次数、稳定请求 ID、deadline | 暂停放大源,设置预算和退避 |
| T3 | 池/worker 接近上限,其他功能变慢 | 发送与历史读的资源隔离和用户 SLI | 保护权威写与有权历史,不盲目扩池 |
| T4 | 设备重连又形成一波补拉 | 计划/实际重连、历史缺口和设备 ACK | 慢放流量,按权威历史追平 |
T0 的“DB 慢”是症状,不一定是第一根因:也可能先有连接泄漏、热点查询或存储失衡;T2 的重试使它恶化但未必是初始触发。实时处置先阻断明显正反馈、守住权威事实和权限,再在稳定窗口检查完整因果。盲重启可能把连接重连、冷缓存和补拉流量一起放大。11.06 池/盘诊断 · Google SRE:级联故障处置
本章没有给当前 S2 新增过载状态码或熔断实现;上述是未来系统的待评审机制。任何限流/降级策略要列出用户可见结果、权威数据是否保留、失败是否可查询和回退方式,不能只说“系统活下来了”。
七、恢复不止于 CPU 下降或队列清空
可审的恢复顺序是:确认权威存储可服务且池/磁盘有余量 → 把多层重试压到预算内、慢放重连 → 按有权历史核对 m-9/seq9 与 outbox/E9 的责任 → 查 B 的缺口和应用确认 → 在积压持续下降、超时/拒绝回落、用户 SLI 恢复后逐步撤销临时限额。若队列突然为 0 是因为把所有请求都拒绝,不能称“恢复”;若 B 收到实时通知但历史缺 seq9,同样未闭环。11.08 用户结果告警
要同时留存停止门:若恢复放量再次使到达率超过完成率、retry/原始意图比值升高、DB 池等待或设备积压上升,就停止继续放量并回到上一档。真实值班还需要明确负责人与通信、运行手册和安全权限;课程只给静态分析方法,不代替实际操作。10.11 运行手册与复盘
固定 openimsdk/open-im-server 提交 f6411a8a1a31d3df36f4c2b3ad28481a94141e1f 的 send.go 选定分支调用 MsgToMQ 后返回;另一 Mongo 消费路径调用 BatchInsertChat2DB。这两处只提示发送返回与 Mongo 消费是异步阶段,不证明 OpenIM 有本章教学 SQL/outbox、具体重试次数、熔断器、数据库瓶颈、设备 ACK 或真实恢复策略。OpenIM 阅读地图
八、22 道分层练习:给级联故障画停止门
先自己推导“意图→尝试→积压”,再检查哪些行为改变了用户结果。
基础 1–8:理解反馈链
1. B 历史补拉变慢,当前 S2 的 200 能证明 B 已恢复吗?
不能。S2 只承诺 A 侧本进程内存受理;B 恢复是未来另一链路。
2. 100 个用户补拉意图等于 100 次 DB 尝试吗?
不一定。客户端、网关和下游重试可能让尝试数更高。
3. 什么是本题的正反馈?
DB 慢使等待/超时增多,重试提高 DB 到达量,进一步加剧 DB 慢。
4. Go `context` 取消可自动撤销已内存受理的消息吗?
不能。它通知后续工作停止,已发生业务事实需另按合同核对。
5. 当前 S2 同 ID、同正文重复 POST 返回什么?
409,既有消息/顺序不覆盖;客户端应有权查询结果而非改 ID 猛发。
6. 坏 JSON 的 400 值得立即原样重试吗?
不值得。输入错误是永久性候选,先修正请求。
7. 为什么重连设备加随机抖动?
避免所有设备按同一固定间隔同时补拉,形成同步尖峰。
8. 队列长度下降一定表示 B 已恢复历史吗?
不一定。也可能请求被拒绝/取消;仍要看有权历史结果与设备 ACK。
推导 9–16:尝试和积压的条件算式
9. 客户端与网关各最多 2 次重试,单用户意图最多几次 DB 尝试?
各层共 3 次尝试,最坏 3×3=9 次。
10. 每秒 100 个意图,最坏下游尝试率多少?
100×9=900 次/秒,不是 900 个新用户需求。
11. DB 完成 120/s,900/s 全入同队列且无其他流向,5 秒净增多少?
(900−120)×5=3,900 次尝试。
12. 队列容量只有 200 时还能说实际排着 3,900 吗?
不能。其余必须被阻塞、拒绝或走其他流向,须重新对账。
13. 1,000 台设备理想均匀分散 10 秒,平均多少次重连/秒?
约 100/s;真实峰值仍未知。
14. 每层各给完整 2 秒 deadline,会保证端到端只等 2 秒吗?
不能。要传播同一个绝对期限或合理剩余时间,避免层层叠加。
15. 限流让完成者 P95 降低,是否等于用户结果改善?
不一定。拒绝/超时和未补拉者可能增加,须看完整分母。
16. B 离线 25 小时、教学 broker 只保留 24 小时,靠什么补?
从有权权威 DB 历史按会话序号缺口补,不能只靠 broker E9。
决策 17–22:限载、隔离与恢复
17. 历史读过载时可以丢权威 `m-9/seq9` 吗?
不能。可恢复通知可按明确政策降级,权威消息与成员权限必须守住。
18. 熔断器 open/half-open/closed 分别做什么?
open 快速失败,half-open 少量试探恢复,closed 正常通过;状态转换须依真实依赖结果。
19. 扩大 DB 连接池能无条件解除过载吗?
不能。可能把排队从池转到数据库,需先查 DB 容量和连接占用。
20. 清零重试、队列和 CPU 后还需验证什么?
权威 m-9/seq9、outbox/E9 责任、B 有权历史缺口/应用 ACK,以及用户坏事件回落。
21. 两处 OpenIM 固定源码能证明真实重试风暴或熔断策略吗?
不能。只看到选定发送与 Mongo 消费的异步边界。
22. 一份可审故障矩阵至少列哪些项?
用户确认点、意图/尝试与到达完成、各层 deadline/重试预算、队列/拒绝、权威保护、止损和慢放恢复门。
本章完成标准与后续路径
能不看答案从 100 个意图手算最坏 900 次 DB 尝试与条件积压,说明何时取消、何时不能重试、哪些通知可降级但权威不可丢,并为 B 重连补拉写出限载、反证与恢复门,才算完成第一轮。数字与故障全为纸上推演;真实故障处置需要有权限的运行证据。下一章11.10 事件响应将把这种诊断与止损接进指挥、沟通和恢复验收。