可观测性是架构属性,不是事后补的
一个服务能不能被观测,在写下第一个接口签名时就已经定了一半:日志字段是谁规定的、traceId 在哪一层生成、指标标签带不带用户 ID、消息中间件透不透传上下文。这些决定一旦落进代码,后期补的成本不是「加几行埋点」,而是要改所有服务的调用约定。本章只回答一个问题:可观测性在架构设计阶段要定下什么。具体怎么排查、敲哪条命令,属于运维排障的范围,这里不重复。
可观测性欠债的典型症状
| 症状 | 表层表现 | 架构层的根因 |
|---|---|---|
| 只能证明「服务活着」 | 健康检查全绿,用户还在投诉 | 只采集了资源指标,没有面向用户行为的 SLI |
| 故障时无法定位到服务 | 每个服务日志都正常,合起来就是错的 | 没有跨服务上下文贯通,缺 traceId 或中途丢失 |
| 定位要登机器捞日志 | 值班必须 SSH 才能查问题 | 日志没结构化、没集中采集出口 |
| 指标平台账单失控 | 指标数量随用户量线性增长 | 标签用了高基数维度(用户 ID、订单号、URL 全路径) |
| 告警天天响没人管 | 群里刷屏,真故障被淹没 | 告警按原因(CPU、连接数)设,而不是按症状设 |
| 上线后才发现看不见 | 新链路完全没有监控 | 可观测性没有进设计评审与 Definition of Done |
结论:把可观测性当成验收项写进设计文档,而不是等到出故障时补。
设计阶段必须定下的六件事
- SLI 与 SLO:这个服务承诺什么,用什么指标衡量。
- 上下文传播约定:traceId 谁生成、走哪些协议头、异步链路怎么带。
- 日志字段规范:统一字段名、级别语义、脱敏规则、保留期。
- 指标命名与标签白名单:标签只能从白名单里选,禁止高基数维度。
- 采样策略:哪一段全采、哪一段低采、错误请求怎么保底。
- 告警契约:谁负责、几级、有没有 runbook、多久无人响应升级。
这六项就是架构决策记录(ADR)里可评审的内容,也是「事后补」与「设计时定」的分界线。
三条支柱的采集边界与成本
| 支柱 | 采集边界(谁负责) | 数据量级 | 主要成本项 | 建议保留 |
|---|---|---|---|---|
| 日志 Logging | 应用内结构化输出,边车或采集代理统一出口 | 与请求量成正比,单条 KB 级 | 存储 GB 与索引开销 | 热数据 7~30 天,冷归档按合规要求 |
| 指标 Metrics | 进程暴露端点(如 /metrics),平台定时拉取或推送 | 与「时间序列基数」成正比,不随 QPS 涨 | 活跃时间序列数、卡片基数 | 原始 15 天,降采样后 1~2 年 |
| 追踪 Tracing | 框架或边车自动埋点 + 关键业务手工 Span | 与请求量 × 采样率成正比 | Span 存储、查询扫描量 | 7~15 天,异常 Trace 单独留存 |
三条支柱的成本结构完全不同,架构决策也因此不同:指标的成本由维度设计决定(设计期可控),日志的成本由字段与采样决定(设计期可控),追踪的成本由采样率决定(运行期可调)。想省钱要先改设计,而不是先砍保留期。
traceId 与上下文如何贯通
约定比工具重要。一次请求的标识要满足三件事:唯一、可跨语言传递、能穿过异步边界。
入口(网关 / BFF):
- 请求头已有 traceparent → 沿用,不重新生成(否则链路断裂)
- 没有 → 生成 32 位十六进制 traceId,写入响应头便于用户报障时提供
- 校验采样标记 sampled,决定后续是否落 Span
同步调用(HTTP / gRPC):
- HTTP 用 W3C Trace Context:traceparent、tracestate
- gRPC 用 metadata,键名与 HTTP 保持一致,避免两套解析
异步(消息 / 事件 / 定时任务):
- 生产端把 traceId 写入消息头,不要只写进消息体
- 消费端从消息头恢复上下文,并为「消费」新建一个子 Span
- 定时任务没有上游:以「任务名 + 触发时间」作为链路根
跨线程(线程池 / 协程 / 回调):
- 上下文必须显式传递(装饰任务、透传上下文对象)
- 依赖线程本地变量自动继承是最常见的丢失点
设计文档里应写明一句话:任何一次跨进程调用,traceId 必须随协议头传递;任何一次跨线程切换,上下文必须显式携带。这句话写进编码规范,比事后逐个服务补埋点便宜得多。
SLI / SLO 与错误预算
SLI 是测量值,SLO 是目标,错误预算是「允许失败多少」的额度。
| SLI 类型 | 度量方式 | 适合谁 |
|---|---|---|
| 可用性 | 成功请求数 / 总请求数(按分钟聚合) | 所有对外服务 |
| 延迟 | 快于阈值的请求数 / 总请求数(如 P95 < 300ms) | 用户可感知的接口 |
| 正确性 | 业务成功数 / 有效请求数(排除用户自身错误) | 交易、支付类 |
| 吞吐 | 单位时间处理量 | 批处理、消费型服务 |
| 新鲜度 | 数据延迟时间(如同步滞后秒数) | 数据管道、Feed |
SLO 示例:下单接口月度可用性 99.9%
错误预算 = (1 - 0.999) × 30 天 = 43.2 分钟/月
预算消耗策略(比单点阈值告警更抗噪):
- 燃烧率 > 14.4 倍(1 小时烧掉 2% 月度预算)→ 立即呼叫
- 燃烧率 > 6 倍(6 小时烧掉 5%)→ 群里通知,值班跟进
- 燃烧率 > 1 倍(持续消耗)→ 只进工单,复盘时看
预算用尽后的约定:冻结非修复类变更,直到预算恢复
错误预算的价值不在数字,而在它把「要不要发版」从争论变成算术。没有 SLO 的团队,只能靠吵架决定能不能上线。
可观测性预算与采样决策
先给可观测性定一个成本上限(常见做法是基础设施成本的 5%~15%),再决定采样。
| 决策点 | 选项 | 建议 |
|---|---|---|
| 追踪采样位置 | 头部采样(入口决定) | 实现简单、成本可预测,但会随机丢掉长尾 |
| 尾部采样(收集端按结果决定) | 能保住错误与慢请求,需要缓冲与聚合能力 | |
| 采样率 | 按服务分级 | 核心交易链路 10%~100%,内部批处理 1% 或更低 |
| 错误保底 | 一律全采 | 只要出现错误或超过延迟阈值,无条件保留整条链路 |
| 指标维度 | 标签白名单 | 只允许:服务名、接口、方法、状态码、实例、版本 |
| 日志级别 | 语义固定 | ERROR 必须可行动,WARN 表示降级生效,INFO 不做高频循环输出 |
高基数标签是成本失控的第一杀手:把用户 ID、订单号、完整 URL 当标签,时间序列数会随业务量爆炸,而查询性能同时崩塌。需要按用户排查时,那是日志与 Trace 的职责,不是指标的。
告警设计原则
- 面向症状,不面向原因:「下单 5xx 比例超 1%」是症状,「数据库连接数超 80」是原因。原因类信号做大盘,症状类信号做告警。
- 可行动:每条告警必须对应一个明确动作。写不出 runbook 的告警,先别上。
- 信噪比优先:频繁误报的告警要下线或调参,而不是让人学会忽略。
- 分级与责任到人:P0 呼叫、P1 群通知、P2 工单;每条告警都有 owner,无主告警等于没有告警。
- 用预算驱动:优先告警「错误预算燃烧率」,比逐个接口设静态阈值更稳、更少误报。
- 新链路必须带告警:上线清单里写清楚——新服务的核心 SLI、告警规则、大盘链接缺一不可。
常见误区
| 误区 | 后果 | 正确做法 |
|---|---|---|
| 把可观测性等同于「装个 APM」 | 工具到位但上下文断裂,链路仍然拼不起来 | 先定字段与传播约定,工具只是执行者 |
| 让每个团队自由决定日志格式 | 跨服务检索失效,聚合查询要写 N 套解析 | 字段规范统一发布,变更走评审 |
| 指标里带上用户或订单维度 | 序列数爆炸、账单失控、查询变慢 | 高基数信息只进日志与 Trace |
| 只监控资源不监控业务 | 资源正常但业务已错 | 每个核心流程至少一个业务 SLI |
| 告警全部直连值班群,不设分级 | 告警疲劳,真故障被淹没 | 分级 + 抑制 + 聚合,按 SLO 燃烧率触发 |
| 采样率一刀切 1% | 长尾与错误请求被丢,故障时无证据 | 错误与慢请求全采,正常流量可低采 |
边界说明:故障发生后的具体定位步骤与命令见运维排障的可观测性章节,稳定性控制手段见弹性设计章节,决策留痕与评审见架构决策记录章节。
小结:可观测性是设计出来的——在架构阶段就把 SLI/SLO 与错误预算、traceId 与上下文的传播约定、日志字段规范、指标标签白名单、采样与保留策略、告警分级契约定下来;成本由维度与字段设计决定而非保留期决定,告警要面向症状并用预算燃烧率驱动;等到故障发生再补,补的不是埋点,而是所有服务的调用约定。