11.04 诊断方法:先画像,再证伪 IM 消息慢
返回第十一卷 · 测量前置:11.01 · 分布前置:11.02 · 观测前置:11.03 · CPU 调度前置:03.04
11.04 诊断方法:先画像,再证伪 IM 消息慢
DeepTutor 八节初稿经技术与教学审阅后的静态课程。请求、队列、时延和资源数字均是虚构纸上数据,没有运行 Go、IM、监控、剖析或压力测试。当前 S2
/v1仍是正文非空且最多 6 UTF-8 B、原始 HTTP 请求正文最多 4096 B、同 ID 重复 409、非成员目标隐藏 404、200 accepted_in_memory仅代表本进程内存受理;未来 S3/v2stored_in_teaching_db是提议且仍为 6 B,R9 6→9 B 待审。09.02 合同
一、先说明“谁觉得哪一步慢”
用户说“消息发出去了,但很慢”。这句话至少可能指 A 等待发送回应、未来持久提交、B 设备看到通知、断线后补拉到消息或阅读状态同步。11.01 已要求为每一个事件写起点、终点、分母和时间窗;诊断也从这一步开始。当前静态课程只有 S2 的接口设计,没有实际运行的服务,所以本章选一个以后学习者可在隔离实现中复现的问题:A 点击发送 m-a,直到 A 收到 /v1 的 200 accepted_in_memory,这一段变慢。 它不描述 B 收到或已读。11.01 业务确认点
把症状写成可复核的记录,而不是先说“数据库慢”:
| 字段 | 本章纸上病例 | 若未记录会误判什么 |
|---|---|---|
| 谁、哪条路由 | 虚构 A,POST /v1/conversations/{id}/messages | 把历史 GET 或 WebSocket 通知混进来 |
| 起止与单位 | A 点击 → A 收到受理回应,毫秒 | 把服务端写回应时刻当作 A 已收到 |
| 群体与窗口 | 同版本、同类授权发送尝试,在相邻 5 秒窗口比较 | 只展示慢样本,忽略分母与未完成请求 |
| 同时报告 | 收到 200 的 P95、超时、拒绝、仍在途数量 | 只报完成者 P95,漏掉最慢的等待者 |
| 变化与范围 | 哪些会话/设备/实例受影响,何时开始 | 将单个客户端网络问题推成全系统故障 |
先记录业务结果,再找可能的等待位置。监控的服务视角通常看延迟、流量、错误和饱和,但每一项仍需用本题的确认点定义。Google SRE 称这四项为用户可见服务的四个黄金信号;它们提供问题轮廓,不自动给根因。Google SRE:监控分布式系统
二、画像工作负载:系统当时在处理什么
工作负载是进入系统的工作种类与强度。IM 不能只数“在线用户”:10 万条空闲连接可能几乎不发消息,一个大群在短时间内发 100 条消息却会触发大量扇出。诊断前至少列出同一窗口的连接数、活跃连接比例、HTTP 请求尝试/秒、唯一消息/秒、单聊与群聊占比、群规模、设备数、正文/事件字节数、历史读写和重试。不同单位不能直接相加。Brendan Gregg:性能分析方法
假设纸上观察到一个仅持续 5 秒的突发:每秒有 60 个请求进入某个处理队列,平均每秒 40 个请求离开;起始队列长度是 0,且这 5 秒没有丢弃、超时移除或旁路。则 入队 60×5=300,出队 40×5=200,队列净增加 100。若存在拒绝、取消、超时清理或别的消费者,必须把这些流量也计入守恒式,不能机械写成 100。这里的“请求”包含可能的重试,不等于 300 条唯一消息,也不表示 200 条已内存受理;离队可能是 400、409、404 等多种结果。
到达速率是“进入所选边界的项数 ÷ 时间”,完成速率是“离开同一边界的项数 ÷ 时间”。两者同为项/秒才可相减。若只看完成吞吐 40/s,就可能错以为负载稳定;若看到 60/s 到达与增长队列,才知道工作正积压。突发时间与平均速率要一起保留:一分钟均值 42/s 可能掩盖其中五秒的 60/s。
有些人会立刻用 Little 定律 L=λW,把速率乘平均等待时长算在系统内的项数。这个关系需要稳定状态、相同的工作集合和一致的时间平均;本题到达 60/s、完成 40/s、队列仍在增长,不能拿某一刻的 P95 乘 60/s 冒充稳态平均并宣称“队列一定有 24 条”。这里先用守恒式识别增长,再在足够长且稳定的窗口讨论关系。11.02 分布与样本
三、USE 看资源;黄金信号看服务
Brendan Gregg 的 USE 方法要求对每个资源分别检查 Utilization 利用率、Saturation 饱和/等待、Errors 错误。资源可以是 CPU、内存、磁盘、网络接口,也可以是有明确容量和等待语义的连接池或工作队列。利用率要写分母:一核 CPU 忙 0.8 核秒/秒是 80%;同一个 0.8 核秒/秒放在八核宿主机图上可能只显成 10%。排队/节流通常比“CPU 百分比高”更直接表明容量冲突。Brendan Gregg:USE 方法
| 资源 | 利用率怎样定义 | 饱和/等待要看什么 | 错误/拒绝要看什么 |
|---|---|---|---|
| 容器 CPU | 使用核秒 ÷ 配额核秒;注明窗口 | 可运行任务等待、配额节流与延迟 | 进程失败或工作超时,不能把所有 HTTP 4xx 当 CPU 错误 |
| 内存 | 工作集/限额、Go 堆与 RSS 分开 | 回收/分配压力、接近限额及等待 | OOM 或分配失败;高 RSS 本身不是证据 |
| 磁盘/网络 | 吞吐相对于可用能力,注明设备/链路 | I/O 排队、重传、连接等待 | I/O 错误、超时与丢弃 |
| 有界队列/池 | 占用槽位/容量 | 等待者数、等待时长、队列增长 | 入队拒绝、获取超时、降级 |
服务的四个黄金信号则问:用户看到多慢(延迟)、来了多少工作(流量)、哪些业务请求失败(错误)、服务离容量边界多近(饱和)。同样的队列等待可同时出现在资源 USE 和服务饱和里,但问题不同:前者定位资源,后者描述对用户服务的影响。一个节点 CPU 低不能排除容器配额被用满、单核热点或锁等待;一个资源占用高也不证明它造成这次发送慢。要检查同一实例、容器和时间窗口,再寻找能推翻假设的证据。03.04 CPU 调度
四、纸上病例:从异常读数走到可反驳的假设
在同版本、同授权路由、相邻两个 5 秒窗口里,我们虚构以下记录。两条 P95 都只针对窗口内收到 200 的同一组请求,拒绝与未完成请求另列;它们不是本仓库或 OpenIM 的真实监控结果。即使是同一组,请求排序可能不同,两个 P95 仍不可相减。
| 同口径观察 | 基线窗口 | 异常窗口 | 可以先说什么 |
|---|---|---|---|
| A 点击到收到 200 的 P95 | 40 ms | 400 ms | 完成者的用户等待变长,未完成者仍未知 |
| 服务入口到内存提交 P95 | 30 ms | 340 ms | 同一组成功请求的服务内阶段也变慢;不能把两个 P95 相减当网络耗时 |
| 进入处理队列 | 40/s | 60/s | 到达强度增加 |
| 离开处理队列 | 40/s | 40/s | 若无其他流向,五秒净积压 100 项 |
| 超时/拒绝/仍在途 | 基线另记 | 异常另记,数值待查 | 完成者 P95 不覆盖这些用户 |
这批纸上值支持“某个入口之后积压,服务内等待可能增大”,不支持直接宣称 CPU、锁、磁盘或数据库是根因。当前 S2 根本没有 SQL 持久确认;若猜“数据库慢”,第一条反证就是这条教学路径并无数据库提交。然后把候选拆成可测试的预测:
| 候选 | 若为真,预计还能看到 | 什么证据会削弱它 |
|---|---|---|
| H1:CPU/配额限制 | 同作用域的 CPU 忙、配额节流或可运行队列变长,工作处理时间增加 | 配额余量足、无节流,时间主要消耗在等待队列/锁;仍需核查单核热点 |
| H2:工作队列/锁等待 | 入队到出队等待增长,block/mutex 等待与对应代码阶段相合 | 队列等待稳定,慢时间落在执行/写回应等其他阶段 |
| H3:客户端网络/回应丢失 | 服务端提交阶段正常,A 仍超时;客户端网络和响应写阶段有异常 | 同一请求服务内阶段显著增长且客户端链路正常;网络仍可能叠加 |
| H4:重试放大 | 请求尝试/唯一消息比值升高,409 与重发时间相关 | 稳定身份去重后尝试率没变;需确认不是其他流量混入 |
一份假设表须附采样范围、反证条件和下一步取证。例如看到宿主机 CPU 30% 就“排除 CPU”不合格;先查容器配额、可运行队列和单核分布。看到 409 增加也不等于服务端吞吐下降;它可能是 A 收不到首次 200 后用同 ID 重试的结果。09.04 响应丢失
五、沿单个请求拆阶段,但守住时钟与采样边界
11.03 已区分日志、指标、Trace。现在为一条虚构的完成请求画阶段:A 端测得点击到回应 400 ms;服务端在自身单调时钟上测得入口到内存提交 320 ms,其中入应用队列到获准处理等待 250 ms。这里“320 包含 250”,不能相加成 570 ms,也不能把客户端的 400 与服务端 320 的 P95 相减;本例两者是同一请求分别测得的持续时间。跨机器的墙上时钟可能有偏差,不能用 A 的绝对时间戳减服务端绝对时间戳算网络段。11.03 Span 与阶段
A 点击发送 m-a ── A 自己计时 400 ms ── A 收到 200
│
└─ 服务端入口 ── 本机计时 320 ms ── 内存提交
├─ 队列等待约 250 ms
└─ 其余服务内阶段约 70 ms本例说明等待位置在服务内队列附近,仍不知道排队因何而起。要在同一时间窗看队列入/出、服务时间、CPU 配额、锁/网络以及错误流向,再缩小原因。Trace 若只采样 1% 请求,没看到某个慢请求不等于它不存在;响应丢失可能让 A 端只有“超时”而服务端有提交记录。敏感正文、令牌和原始会话标识不要为了关联而写入普通指标标签。11.03 高基数与脱敏
六、Go 诊断工具用在假设之后
如果未来隔离实现确实显示 CPU 执行时间高,再用 CPU profile 找热点;如果显示分配和 GC 压力,再看 heap/alloc profile 与 GC 统计;如果大量 goroutine 等锁/同步,再考虑 block 或 mutex profile;如果调度和阶段交错难解释,再考虑 Go execution trace。这些工具要与请求负载、采样时长、开销和版本一起记录,结果也只覆盖被观察的运行。Go 官方诊断文档描述 profile/trace 的用途,工具不会自动判断“用户消息慢”的业务确认点。Go Diagnostics
| 假设与所需证据 | 可考虑的工具/记录 | 工具不能替代什么 |
|---|---|---|
| CPU 热点 | 容器配额与节流、CPU profile、相同负载对照 | 只看宿主总 CPU;profile 平坦也不证明网络正常 |
| 分配/GC | Go 堆、alloc/heap profile、GC 停顿与 RSS 对照 | 把 RSS 全算成可回收 Go 对象 |
| 锁/调度等待 | 队列时长、block/mutex profile、trace、受控交错 | 从 goroutine 数上涨直接推定死锁 |
| 网络/写回应 | 客户端耗时、服务端写阶段、重传/超时和请求身份 | 用服务端 200 日志证明 A 完整收到 |
初学者要养成“先预测,再取最小能证伪的证据”的习惯。使用剖析工具也可能改变开销或暴露私有数据,个人练习应在隔离环境使用虚构输入,并注明采样盲区。本章只给选择理由,未执行任何诊断命令。
七、修复前确认业务影响与回退门
诊断记录的最小闭环是:症状与分母 → 工作负载变化 → 候选与反证 → 缩小等待阶段 → 单一改动 → 同口径复测 → 回退条件。例如 H2 最终若被验证为“入队等待上升”,可以考虑限制突发、调整并发或拆分热点;但限流可能把等待改成拒绝,A 的 P95 下降不代表更多消息成功。必须把完成时延、拒绝/超时、仍在途和唯一消息结果并排比较,且先写可接受的业务权衡。11.01 分母 · 10.10 回退门
固定 openimsdk/open-im-server 提交 f6411a8a1a31d3df36f4c2b3ad28481a94141e1f 的 send.go 选定分支调用 MsgToMQ 后返回;另一 Mongo 消费路径调用 BatchInsertChat2DB。这两段只提示发送返回与消费落库可分属异步阶段,不能从中推断生产者 ACK 级别、真实延迟、CPU/锁瓶颈、设备是否收到或本教学 S2 的行为。公开项目的性能诊断需要固定版本、实际工作负载、同口径观测与进一步源码证据。OpenIM 阅读地图
八、22 道分层练习:交一份可反驳的诊断记录
先独立回答,再展开反馈。第一轮守业务口径,第二轮用纸上数字定位,第三轮选择验证与修复门。
基础 1–8:定义症状与负载
1. “消息慢”可直接等于 B 已读慢吗?
不能。先选 A 受理、持久、设备交付或已读的具体起止事件;本章只选 A 等 accepted_in_memory。
2. 当前 S2 的 200 能证明 B 设备收到吗?
不能,只表示本进程内存受理;设备确认是另一阶段。
3. 一张发送时延卡至少要写哪些口径?
起点、终点、单位、请求/用户群、时间窗、分母、版本、完成/超时与未完成分类。
4. 60 次 POST 与 60 条唯一消息必相同吗?
不必。响应丢失后的同 ID 重试会增加请求尝试,不增加相应唯一消息。
5. 在线 10 万连接能直接推出消息吞吐吗?
不能,还需要活跃比、消息速率、群规模、设备数、正文大小与突发形状。
6. 黄金信号有哪四项?
延迟、流量、错误、饱和;每项仍需绑定业务对象与窗口。
7. USE 的三个字母分别问什么?
每个资源的利用率、饱和/等待和错误。
8. 宿主机 CPU 30% 能排除 CPU 限制吗?
不能。还需看容器配额/节流、单核热点与可运行队列,作用域要相同。
推导 9–16:用反证缩小等待位置
9. 五秒内到达 60/s、离开 40/s,起始队列 0 且无其他流向,净增加多少?
(60−40)×5=100 项;若有拒绝、取消或旁路须另计。
10. 上题 300 次入队可直接写成 300 条已内存受理吗?
不能。单位是请求尝试,离队也可能为 400/409/404,不是全部成功。
11. 队列增长时用 `L=λW` 和 P95 算精确队列长度合适吗?
不合适。本题不稳态且 P95 不是匹配口径的时间平均,先做入出守恒。
12. 客户端 P95 400 ms、服务端 P95 340 ms,能相减得网络 P95 60 ms 吗?
不能。两个 P95 可能来自不同请求,分位数差不是网络分位数。
13. 同一请求服务内 320 ms 含队列等待 250 ms,可以相加成 570 ms 吗?
不能。250 是 320 的一部分,其余服务内阶段约 70 ms。
14. 队列等待上升就证明锁争用是根因吗?
不能。还需查 CPU 配额、工作服务时间、锁等待与其他阻塞证据。
15. S2 纸上路径的“SQL 写入慢”是合理首要根因吗?
不是。当前 S2 成功点仅内存受理,SQL 持久提交属于未来 S3 提议;先核实际路径。
16. 服务端记录准备写 200,A 超时,两者矛盾吗?
不矛盾。响应可能丢失;服务端准备或写入成功不等于 A 完整收到。
决策 17–22:决定下一份证据和修复门
17. CPU 忙时第一份范围信息是什么?
明确实例/容器/核/配额和时间窗,查节流与可运行等待,再选 CPU profile。
18. goroutine 数上升,能直接诊断死锁吗?
不能。须查等待位置、队列和任务生命周期;采样可考虑 block/mutex profile 或 trace。
19. 采样 Trace 没看到一条慢请求,说明它没发生吗?
不能。检查采样率、采集丢失、时间窗与业务身份,再用聚合指标和客户端记录核对。
20. 限流后完成者 P95 降低,是否足以宣布修复?
不足。拒绝/超时和仍在途可能增加,要同口径看唯一消息结果和用户影响。
21. 固定 OpenIM 两处源码能证明真实性能瓶颈吗?
不能。只看到了选定发送与 Mongo 消费的异步边界,需要真实负载、测量及更多路径证据。
22. 一份最小可复核诊断记录交什么?
症状的起止/分母/窗口、负载画像、USE/服务信号、候选与反证、同请求阶段、下一份证据、单一改动及复测/回退门。
本章完成标准与后续路径
能把“消息慢”改写成具体确认点与同口径窗口,手算五秒队列守恒,区分服务信号和资源 USE,提出至少两个可反驳假设,并说明为何当前证据只定位等待而未证实根因,才算完成第一轮。第二轮在自己的隔离实现中按权限采集真实样本,保留负载、工具开销和反例;本章没有替学习者完成实测。下一章11.05 CPU 与内存性能将在这套方法上专门分析 CPU、分配、GC 与调度。