读书笔记服务发布了新版。Pod都在运行,CPU曲线也不高,却有人反馈打开笔记要等两秒。是图片太大、数据库变慢,还是新代码出了问题?
现在先不要重启服务。重启可能暂时缓解,也可能清掉连接池、打断任务,让原来的证据消失。我们需要先说明“谁在什么时间、经过哪条路径、慢在哪一段”,再选择动作。
这一章用一个假设故障推演排查过程。数字只为解释机制,关键是每一步怎样缩小问题范围。
先看用户经过的路径
打开一篇笔记可能包含两个独立请求:API从数据库读取正文,浏览器从对象存储取得图片。摘要则由另一个worker异步生成。API响应快,不代表图片也快;提交任务成功,也不代表摘要已经完成。
因此先确定症状:是所有页面还是某种笔记?从点击到正文出现慢,还是图片加载慢?摘要的等待时间从入队开始算,还是只测worker开始执行之后?测量边界不同,得到的结论也不同。
指标、日志与追踪分别提供不同粒度的证据:
- 指标(metrics)按时间聚合数值,适合发现错误比例、请求量、延迟分布何时改变
- 日志(logs)记录具体事件,适合解释一次失败的错误码、执行分支和上下文
- 追踪(traces)把相关操作组织成调用关系,适合定位一次请求的时间花在哪段;其中每段操作称为span
三者需要能互相找到。指标按服务、路由、版本缩小范围,追踪定位代表性请求,日志再用trace ID查具体事件。只有三套互不关联的看板,排查时仍然要靠猜。
先把监控数据接起来
要用上这些证据,可以先按采集、时序存储、告警、展示四个环节理解指标系统。
采集端从应用埋点或exporter取得数值;时序库保存数值、时间和标签;规则引擎定期计算条件;展示端把查询结果画出来。以Prometheus为例,它可以拉取指标、存储时间序列并计算规则,Alertmanager负责后续告警处理,Grafana等工具负责展示。10 告警与展示都使用数据,并不需要先告警才能看图。
这里要注意,日志和追踪通常还有各自的存储与查询路径,不能把三种数据全部当成时序指标。选择容量也不能只数机器:活跃序列数、采集间隔、保留时间和查询负载都要考虑。先让关键请求有完整证据,再逐步补充看板,会更容易维护。
指标先被采集和保存,告警与展示分别使用这些数据;一张看板不是整条观测链路
RED定位症状 USE检查资源
面对API,可以先看RED:请求速率(Rate)、失败情况(Errors)、耗时分布(Duration)。1 请求速率上升而错误率和耗时稳定,可能只是服务正常承受更多工作;请求速率没变、延迟却在发布后升高,就值得按新旧版本比较。
面对CPU、磁盘、连接池等资源,可以用USE:利用率(Utilization)、饱和程度(Saturation)、错误(Errors)。2 利用率回答资源有多忙,饱和程度回答有没有工作排队等待。CPU不忙时,请求仍可能排队等数据库连接。
这两种方法帮助提出问题,不直接给出根因。数据库连接池满了,可能因为池太小,也可能因为事务变长,让连接迟迟不归还。只把池调大,可能将等待从应用搬到数据库。
对摘要worker,还要看入队速率、完成速率、最老任务等待时间和失败重试。任务执行只需一秒,但先排队十分钟,用户感受到的仍是十分钟。前面讨论过的CPU扩容指标,无法单独说明这种等待。
p95必须带着口径读
p95是第95百分位:在指定请求集合和时间窗口里,大约95%的请求耗时不超过这个值。它不是平均耗时,也不是最慢请求。3
假设100次请求中,95次耗时100毫秒,5次耗时2秒。平均值是195毫秒;按排序后第95条取值,p95为100毫秒。两个数字都没写出那5次请求完整的体验。选择p95还是p99,要结合用户容忍度、样本量和业务重要性。
读延迟图时,先看窗口、路由、状态码和样本数。把图片下载与正文查询混在一起,业务流量比例改变也会推动分位数变化。只统计成功请求,则可能漏掉超时请求的糟糕体验。
这里还有个容易踩的坑:两个Pod的p95不能直接平均成整个服务的p95。每个Pod的请求数量和分布可能不同。应先合并可聚合的直方图分布,再计算分位数;预先计算好的summary分位数通常无法这样合并。经典直方图的估计精度又受到桶边界影响,要让桶覆盖关心的延迟范围。3
把一次请求的上下文带过去
OpenTelemetry提供生成、收集和导出遥测数据的标准接口与组件,具体存储、检索和展示仍需要相应后端。4
假设入口收到请求,API调用数据库驱动。合适的埋点可以记录API处理span及数据库调用span。若API又通过HTTP调用其他服务,发送端把上下文注入请求头,接收端提取,才能将两端串起来。常见载体是W3C Trace Context的 traceparent 头。5
“各处都安装了SDK”还不够。代理可能丢掉请求头,自定义客户端可能没有埋点,异步线程切换也可能丢失上下文。对摘要任务,需要在消息元数据中传递上下文,并由消费端恢复关联。长时间排队、批量消费时,可以按消息语义使用span link表达关系,不能强行把所有工作画成一个同步调用。9
日志记录trace ID和span ID能帮助跳转,但要检查日志框架是否实际完成关联。跨服务透传的字段也应尽量少,不能把密码、完整笔记或图片签名URL随手放进上下文和日志。
采样会改变你能看到的证据
保留每个span有存储和传输成本。头采样在请求早期决定是否记录,成本较容易控制,但当时还不知道请求最终会不会失败。尾采样在收集到全部或多数span后,按错误、总耗时等条件决定保留,更容易留下异常样本,也要付出缓存、汇聚和等待成本。6
两者组合有明确边界:如果头采样只留下5%的请求,尾采样无法恢复前面已经丢掉的95%。尾采样本身也会受等待窗口、数据丢失和处理容量影响。“保留所有错误”的策略,要说明是在什么输入集合和条件下成立。
所以排查时要注意:追踪里没有找到慢请求,不能据此认定慢请求不存在。应同时看采样设置与采集链路是否丢数据。用于SLO的请求计数也不能直接拿偏向保留异常的trace样本相除,宜在应用或入口按明确口径独立计数。
控制标签数量也是可靠性工作
指标通常通过标签区分路由、方法、状态等维度。每一种实际标签组合都会产生独立时间序列,并消耗内存、存储和查询资源。7
假设一个指标有20条路由、5种状态分组、10个实例,最多就有1000种组合。再加十万个用户ID,潜在组合会急剧增加;直方图还可能为每组产生多个桶序列。
因此把 /notes/12345 归一化为 /notes/{id},不要把用户ID、笔记ID、trace ID或完整URL直接用作常规指标标签。需要查某次请求时,使用有访问控制与保留期限的日志或追踪。允许的维度应围绕实际排查问题选择,而不是先把所有字段收进来。
用SLO决定什么时候需要叫人
服务等级指标SLI描述实际表现,服务等级目标SLO规定希望达到什么水平。例如:过去30天,符合预先定义范围的“保存笔记”请求中,至少99.9%成功。8
如果该窗口共有100万次符合口径的请求,允许的失败数为 100万 × 0.1% = 1000次。已经失败600次,就消耗了60%的错误预算,还剩400次。这是按请求数计算的预算,不能直接换算成固定的停机分钟数。
口径应在事故前定好:哪些路由纳入、怎样识别成功、入口超时怎样计数。不能临时剔除错误来美化结果。摘要任务还可以独立定义“在两分钟内完成”的目标,避免仅用提交API的200响应代表整个流程。
告警可以围绕预算消耗速度安排轻重。99.9%目标允许0.1%坏请求,某窗口实际达到2%,相对消耗速度就是20倍。短窗口帮助发现仍在恶化的事故,较长窗口帮助确认影响;低流量服务还应结合请求数与具体失败,避免一个偶发错误反复叫醒人。8
CPU高可以触发容量调查,但紧急告警应尽量指向需要及时行动的用户影响,并带上受影响路由、时间范围、负责人和排查入口。告警负责叫对人,排障负责找到原因。
回到那次两秒的等待
假设指标显示:新版读取正文的p95升高,图片请求正常,请求量没有明显变化。追踪中API共耗时2秒,其中等待连接池约1.6秒,SQL执行只有80毫秒。同一API进程的保存请求则在等摘要完成,连接池指标显示活跃连接长时间占满。
这些证据支持继续检查连接占用,但还不足以宣布根因。需要确认两条API路径是否共享连接池,以及等待摘要时是否仍持有连接。若证实新版保存流程在事务内轮询异步任务,就应缩短事务范围,把等待移到释放连接之后,并限制并发,再观察连接等待和用户延迟是否一起恢复。
整个过程形成一个可复查的链条:症状、假设、证据、动作、验证。恢复之后还应留下缺失的埋点与防复发措施,否则下一次仍要重新猜一遍。
先定位时间花在哪段,再检查连接由谁占用;SQL执行快不能排除连接池等待
动手想一想
题目 1
两个Pod的p95分别为100毫秒和900毫秒,整个服务的p95是500毫秒吗?
提示
还缺每个Pod的请求数量与完整分布。
答案
无法这样推算。分位数不是可直接平均的普通计数,应合并适当的直方图数据后重新计算,并保持统计窗口和请求范围一致。
题目 2
头采样保留5%,随后尾采样设为保留所有错误,能保证查到每一次失败吗?
提示
尾采样处理的数据是否包含最初的全部请求?
答案
不能。前面没有记录或已经丢弃的span无法恢复,尾采样还受数据到达和处理容量约束。失败总量应由独立、口径明确的计数验证。
题目 3
30天内有200万次有效请求,成功率目标99.9%,已失败1500次。还剩多少错误预算?
提示
先按请求数计算允许的坏事件,再扣除已发生部分。
答案
允许2000次失败,剩500次,已经消耗75%。这个数属于该统计窗口;滚动窗口继续变化时,请求总量和失败数都要重新计算。

