一个报错,横跨四五个服务
做后端这些年,线上问题排查花掉的时间不比写新功能少。
在某 SaaS 公司时,宠物医疗 SaaS 系统从 fasthttp C/S 迁到 go-zero B/S,微服务一多,一个"医生开不了处方"的报错可能横跨网关、鉴权、处方、HIS 对接四五个服务。后来做 AI 数据平台,知识库问答服务和数据集管理服务又多了 LLM 调用、向量化、Temporal Worker 这些新组件,问题形态更杂。
坑踩得够多之后,我形成了一套固定的排障顺序:先看监控定边界,再查链路定位置,最后翻日志看细节。顺序反了,就是在大海里捞针。
先看监控:把边界画出来
告警来了先别急着登机器,先在 Grafana 上回答三个问题:影响面多大(单用户还是全量)、从什么时候开始(发版后还是突发)、哪个指标异常(错误率、延迟、CPU、内存、队列堆积)。
我们在 KubeSphere 上给每个服务配了 RED 指标(Rate、Errors、Duration),业务侧再加关键看板:统一支付平台看支付成功率和回调延迟,统一认证中心看登录失败率和各 Provider(腾讯云 SMS/SES)的错误码分布,知识库问答服务看首 token 建立延迟和各 LLM 供应商的超时率。
一张 dashboard,能先把"是不是我的问题、是我的问题大概在哪"筛掉八成。
再查链路:定位到那一步
确定是某个服务的问题后,用 trace_id 把整条请求链拉出来。我们在 go-zero 和 Hertz 里都接了 gRPC 拦截器和 HTTP middleware,把 trace_id 从入口一路透传到下游、MQ 消费者、Temporal Workflow。
| |
拿到 trace_id 去 Jaeger 看瀑布图,一眼能看出哪个 span 慢、哪个 span 报错。AI 场景里我特意把对 LLM 的调用、向量检索、S3 上传都包成独立 span,不然一个"回答超时",你根本分不清是模型慢还是检索慢。
最后翻日志:看为什么
trace 定位到具体服务和时段后,去 ELK 用 trace_id: "xxx" 精确捞日志。
我们的日志规范是:顶层打印请求入参和最终错误,中间层只追加上下文、不重复打错误,错误用 zap.Error(err) 带堆栈。一条合格的错误日志,应该能直接回答"什么操作、什么入参、为什么失败"。
四条规矩
第一,日志别瞎打。早期有人在循环里打全量文档内容,一次解析任务日志几百 MB,ELK 直接被打爆。后来定了规矩:DEBUG 级别打细节,生产默认 INFO;大对象只打 ID 和长度;敏感字段(手机号、密钥)一律脱敏。统一认证中心里这是红线。
第二,告警要可收敛。每个错误都告警等于没有告警。按服务 + 错误类型聚合,5 分钟内同类错误只发一条,再配升级策略:错误率超阈值且持续 10 分钟才打电话。夜间告警的质量,直接决定你能不能睡个整觉。
第三,异步任务的 trace 要手动续上。MQ 消费者和 Temporal Workflow 是新的执行上下文,trace_id 不会自动传过去,必须生产端塞进消息体、消费端取出来注入 context。这块漏了,异步链路就是断的,排障时最痛苦。
第四,回滚优先于根因。KubeSphere 容器化部署后,回滚 5 分钟内搞定。发现是发版引起的问题,第一反应是回滚,不是在线上 debug。业务恢复之后再慢慢查根因,顺序不能反。
后来
排障能力拼的不是谁见过的错误多,而是有没有一套不依赖运气的检索路径。监控告诉你"哪里不对",链路告诉你"在哪一步不对",日志告诉你"具体为什么不对"。三件事平时建设好,告警响的时候才不会慌。
