dashboard 堆得再多,系统也不会因此变得可观测。你需要的是足够的证据,能在不部署新调试代码的前提下解释一个没见过的故障。从用户能感知到的症状出发,再用稳定的上下文把 logs、metrics 和 traces 串起来。
🎙️ 发布并录制于: ·
监控问的是:已知的坏情况有没有正在发生。可观测性帮你调查那些你没预料到的情况。检验标准很简单:一位客户说十点十四分下单卡住了,你能不能找到那次请求,看清它调了哪些依赖,并说出时间花在哪一步?
User symptom: checkout took 18 seconds
Questions:
Which requests were affected?
Did errors or latency change by region or release?
Which operation consumed the time?
Which dependency returned slowly or failed?
Was the user impact inside the reliability objective?
What changed just before the symptom?
采集 telemetry 是为了回答运维问题,不是因为厂商的架构图上画了三类信号。metric 暴露的是整体层面的尖峰。trace 在代表性请求里定位慢在哪儿。log 保留详细的事件和错误。它们共用的 service、environment、release、route 和 trace 标识,才把彼此分离的记录变成证据。
一条有用的 log 事件要有稳定的事件名、时间戳、严重级别、service、environment、release、请求或 trace 标识、结果、耗时,还有有界的业务上下文。写成结构化 JSON,机器才能按字段过滤,而不用去解析句子。给人看的那句话可以留,但别让它充当 schema。
{
"timestamp": "2026-07-25T10:14:32.481Z",
"level": "error",
"event": "payment_authorization_failed",
"service": "checkout",
"release": "2026.07.25.3",
"trace_id": "4f92c6...",
"order_id": "ord_8132",
"provider": "acquirer_a",
"duration_ms": 3002,
"error_type": "deadline_exceeded"
}
在那个能补上有用上下文的边界记一次就够。如果四层都 catch 再 rethrow 同一个异常,四条一模一样的堆栈只会推高成本,不会多出任何证据。包装错误时保留原始 cause。不要记密码、session cookie、authorization 请求头、access token、完整支付数据,或者没经过审查的请求体。
Something went wrong 里没有操作、没有对象、没有结果、没有标识、也没有原因。把它换成一个事件名,比如 invoice_export_failed,再带上安全的发票标识、依赖名、耗时、错误类型和 trace ID。原始异常放进一个受控字段,导出前先脱敏。counter 只增不减,适合请求数、失败数和任务数。gauge 会上下波动,适合队列深度或内存占用。histogram 把观测值计进分桶,让你能跨实例聚合延迟。counter 要看速率。一个原始的累计请求数,基本只告诉你这个进程活了多久。
http_requests_total{service="checkout",route="/orders/{id}",status_class="5xx"}
http_request_duration_seconds_bucket{service="checkout",route="/orders/{id}",le="0.5"}
queue_depth{service="fulfillment",queue="shipments"}
build_info{service="checkout",release="2026.07.25.3"} 1
# Useful views
error rate = 5xx request rate / all request rate
p95 latency from histogram buckets
queue age alongside queue depth
平均值会藏住尾部那些还在等的用户。给出延迟分布,在流量够大的时候看 p95 或 p99。分桶边界要贴着产品阈值来定,别照抄另一个服务传下来的整数。一个三十秒的任务和一个五十毫秒的 API,需要的分桶完全不同。
一条 trace 表示一次端到端的操作。span 表示其中一段计过时的工作,带父子关系、属性、事件和状态。入口的 server span 下面应该挂着数据库、cache、队列和对外 HTTP 调用的子 span。这个形状能看出时间堆在哪里,以及错误最早出现在哪一步。
POST /checkout 3.24 s
├─ validate basket 0.02 s
├─ SELECT inventory 0.08 s
├─ POST payment-provider /authorize 3.01 s ERROR
└─ publish order-failed 0.03 s
trace_id: shared by every span
span_id: unique operation
parent_span_id: causal relationship
baggage: propagated context; use sparingly
上下文传播是团队最容易低估的一环。把标准的 trace context 通过 HTTP 和消息库一路传下去。异步的活儿,要把消费者 span 链接到生产者的上下文。如果埋点库已经支持标准的那个头,就别自己再发明一个 trace ID 请求头。span 属性里永远不要放密钥,也不要放没有大小上限的负载。
从活儿进出服务的地方开始。给 HTTP 服务端和客户端、数据库查询、cache 调用、消息的生产和消费、定时任务、外部服务商都埋上点。这些边界能暴露延迟、错误、重试和扇出。给每个 helper 函数都套一个 span,只会带来噪声和开销,解释不了系统。
Minimum service telemetry:
request count, outcome, and duration
in-flight work and queue age
dependency count, outcome, and duration
retries and timeouts
resource saturation
release and configuration version
Useful span attributes:
service.name, deployment.environment, service.version
http.request.method, http.route, http.response.status_code
db.system, db.operation.name
messaging.system, messaging.destination.name
标准库优先用自动埋点,然后围绕业务操作补手写 span,比如计价或者支付授权。上线前审一遍自动生成的 route 名和数据库语句。参数化的 route 模板是安全的维度;原始 URL 和 SQL 里的值既可能泄露数据,也会把 cardinality 炸开。
SLI 衡量的是用户能看到的成功,比如两秒内正确完成的下单请求占比。SLO 给这个指标定一个窗口内的目标。error budget 就是允许的失败比例。它支撑一个明确的取舍:可以把可靠性花在变更上,但当失败把预算烧得太快时,就得慢下来。
SLI = good checkout requests / eligible checkout requests
SLO = 99.9% over a rolling 28-day window
Allowed bad fraction = 0.1%
At 10,000,000 eligible requests:
error budget = 10,000 bad requests
Burn rate = observed bad rate / allowed bad rate
burn rate 1 → budget consumed exactly across the window
burn rate 14 → budget disappearing fourteen times too fast
把「计入统计」和「算成功」定义精确。排除健康检查,也排除还没进入产品流程就被拒掉的请求。用户取消和服务商故障怎么算,要从用户视角决定,不是按团队归属决定。因为依赖挂了而失败的下单,仍然是一次失败的下单。
一次 page 的含义是:必须有人现在动手。对 error budget 的快速燃烧、用户能感知的持续延迟,或者快要错过截止时间的积压,值得 page。CPU 高、一个副本崩了、磁盘偏低,可能重要,但通常是原因类信号。只有当立刻动手能挡掉已知影响时,才为它们 page。
PAGE:
Checkout SLO burn rate is 18 over 5 minutes
and 7 over 1 hour, affecting 3 regions.
Runbook: /runbooks/checkout-slo
Dashboard: /d/checkout
Recent releases: 2026.07.25.3 at 10:08 UTC
TICKET:
Search cluster disk projected to reach 80% in 4 days.
Owner: search-platform
Capacity procedure: /runbooks/search-capacity
短窗口和长窗口要一起用。短窗口能快速抓到严重故障,长窗口能避免为了一分钟的抖动就把人叫起来。每条告警都需要归属人、影响描述、当前证据和第一个诊断动作。如果收到的人做不了任何事,就把它转给能做的人,或者删掉。
每一种唯一的 metric label 组合都会生成一条时间序列。带用户 ID、请求 ID、原始 URL、堆栈或时间戳的 label,能造出几百万条序列,把存储或查询系统压垮。metrics 需要有界的维度。高基数的标识放进采样后的 traces,或者建了索引的 logs,在那里你可以有意识地去查。
# Bad: one series per user and raw path
http_requests_total{user_id="813294",path="/orders/998123"}
# Better: bounded route and status class
http_requests_total{route="/orders/{id}",status_class="2xx"}
# Estimate series before shipping
services × routes × methods × status_classes × regions
40 × 80 × 5 × 5 × 4 = 320,000 possible series
request_id 从 label 里拿掉之前先去买存储,等于给这个 bug 出钱。用分层保留、采样、聚合和明确归属来控成本。详细的 telemetry 留在它还能回答活跃问题的地方。旧的 metrics 做降采样。安全和审计记录按它们自己的政策保留。「什么都永久存着」不是可观测性策略。
假设一次上线之后下单延迟跳高了。先从影响入手,别随机去翻主机。确认受影响的 route、region、release 和时间范围。把成功和失败的 trace 拿来对比。在慢的那些 trace 里,payment span 跑了三秒,最后以 deadline 错误收尾。接着去看依赖的结果和重试次数。
rpc error: code = DeadlineExceeded desc = context deadline exceeded
10:08 release 2026.07.25.3 begins
10:11 checkout p95: 0.8 s → 6.4 s
10:11 payment attempts/request: 1.0 → 2.8
10:11 payment deadline errors increase
Trace evidence:
attempt 1: 3.0 s deadline
attempt 2: 3.0 s deadline
request deadline: 6.5 s
2026.07.25.3,或者关掉它的重试开关。第二步:确认延迟、错误率和预算燃烧都恢复了。第三步:对比两个 release 的配置;新代码在同一个请求 deadline 之内重试了超时失败。第四步:给每次尝试单独设定有上限的时间预算,只重试安全的瞬时失败,并加上抖动。第五步:用一个故意变慢的服务商做压测,对总请求时间下断言。第六步:补上「每请求尝试次数」这项 telemetry,并给以后的 release 打上标注。那句报错原文说的是:本地这个操作超过了它的 context deadline。它并不能证明服务商挂了。trace 证明的是两次串行等待。release 标记和回滚证明这次退化跟着新的重试行为走。做得好的故障排查,会把观察、假设、验证和结论分清楚。
从用户旅程往里建。一条精心埋点的下单链路,价值高于五十个服务的主机 dashboard,因为没人能把那些主机跟一笔失败的订单对上。
BUILD
1. Name critical user journeys and owners
2. Define good events and an SLO for each journey
3. Measure rate, errors, duration, and saturation
4. Instrument inbound, outbound, database, cache, and queue boundaries
5. Propagate trace context across HTTP and messaging
6. Add structured events with safe identifiers and release version
7. Link metrics to traces and traces to logs
8. Set retention, sampling, redaction, and series budgets
9. Test telemetry loss; the product must keep working
10. Page on fast user-impact burn with a usable runbook
INCIDENT
1. State user impact and exact time window
2. Check SLI, traffic, regions, routes, and release markers
3. Compare healthy and unhealthy populations
4. Open representative traces; locate the slow or failed span
5. Query correlated logs by trace ID and event
6. Check dependency, retry, queue, and saturation metrics
7. Mitigate with rollback, flag, traffic shift, or capacity
8. Verify user metrics recover
9. Preserve timestamps, queries, traces, and decisions
10. Fix the missing test and missing signal
有用的标准不是完美的 telemetry,而是从一个用户症状走到一个站得住脚的解释,这条路要足够短。让标识彼此连通,让维度有界,让告警能动手,让 release 变更可见。这样生产环境才能回答问题,而不只是产出图表。