可观测性:向生产环境问出更好的问题

dashboard 堆得再多,系统也不会因此变得可观测。你需要的是足够的证据,能在不部署新调试代码的前提下解释一个没见过的故障。从用户能感知到的症状出发,再用稳定的上下文把 logs、metrics 和 traces 串起来。

🎙️ 发布并录制于: ·

01可观测性从一个问题开始

监控问的是:已知的坏情况有没有正在发生。可观测性帮你调查那些你没预料到的情况。检验标准很简单:一位客户说十点十四分下单卡住了,你能不能找到那次请求,看清它调了哪些依赖,并说出时间花在哪一步?

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 标识,才把彼此分离的记录变成证据。

我的原则如果一个 dashboard 九十天里没被人用来做过任何决定,就删掉它,或者重做。一墙的绿色图表如果分不出「下单坏了」和「首页正常」,那它只是装饰。

02Logs 该记录事件,不是记录散文

一条有用的 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。原始异常放进一个受控字段,导出前先脱敏。

03Metrics 是行为随时间的汇总

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,需要的分桶完全不同。

把单位写进名字名字里带上 seconds、bytes 或 total。记状态类别或者有界的结果,别记完整的错误信息。同时打一个 release 标记,这样图表才能回答排查时的第一个问题:刚才改了什么?

04一条 trace 展示一次请求如何跨越边界

一条 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 属性里永远不要放密钥,也不要放没有大小上限的负载。

采样决定了你能证明什么百分之一的 head sampling,可能在结果还没出来之前,就把那次罕见的失败请求丢掉了。尽量把错误全部留下,架构支持的话用 tail sampling,同时保留足够的正常流量做对比。trace 是带采样策略的证据,不是一份完整的交易账本。

05先给边界埋点,再管内部

从活儿进出服务的地方开始。给 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 炸开。

验证整条 pipeline在测试环境造一次带已知标记的请求。确认它的 metric 增量、结构化 log、server span、依赖子 span、release 属性,以及从 trace 跳到 log 的链接都在。然后把 telemetry collector 停掉,确认应用还能正常服务。可观测性 pipeline 绝不能变成一个新的关键依赖。

06SLO 把可靠性变成一份预算

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

把「计入统计」和「算成功」定义精确。排除健康检查,也排除还没进入产品流程就被拒掉的请求。用户取消和服务商故障怎么算,要从用户视角决定,不是按团队归属决定。因为依赖挂了而失败的下单,仍然是一次失败的下单。

别为了面子选五个九目标应该跟着用户需要和架构能做到的水平走。如果故障是连续发生的,百分之九十九点九在二十八天窗口里大约允许四十分钟的坏时间;不过对流量不均匀的服务,按请求数算的目标更合适。没有工程投入却把目标定得更严,换来的不是可靠性,是不诚实的报表。

07用户受影响才 page,原因类信号开 ticket

一次 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

短窗口和长窗口要一起用。短窗口能快速抓到严重故障,长窗口能避免为了一分钟的抖动就把人叫起来。每条告警都需要归属人、影响描述、当前证据和第一个诊断动作。如果收到的人做不了任何事,就把它转给能做的人,或者删掉。

告警疲劳是一个正确性缺陷如果一条告警每周都 page,工程师点掉确认却什么都不做,那它是在教大家忽略这个系统。要么修服务,要么拿证据去改那个错阈值,要么降级,要么删掉。别为没人相信的告警去庆祝平均确认时间很短。

08Cardinality 是藏在 label 里的 telemetry 账单

每一种唯一的 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
控住一次 cardinality 事故第一步:找出正在生成新序列的那个 metric 和 label。第二步:如果后端已经不稳,先在 collector 上把这个维度停掉或者改写。第三步:上线一个有界的 route 或错误类型。第四步:按后端的流程删掉有毒序列,或者让它过期。第五步:加上序列预算检查,并对新增速率告警。在把 request_id 从 label 里拿掉之前先去买存储,等于给这个 bug 出钱。

用分层保留、采样、聚合和明确归属来控成本。详细的 telemetry 留在它还能回答活跃问题的地方。旧的 metrics 做降采样。安全和审计记录按它们自己的政策保留。「什么都永久存着」不是可观测性策略。

09把一次故障从症状追到原因

假设一次上线之后下单延迟跳高了。先从影响入手,别随机去翻主机。确认受影响的 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
按顺序修第一步:先止损,回滚 release 2026.07.25.3,或者关掉它的重试开关。第二步:确认延迟、错误率和预算燃烧都恢复了。第三步:对比两个 release 的配置;新代码在同一个请求 deadline 之内重试了超时失败。第四步:给每次尝试单独设定有上限的时间预算,只重试安全的瞬时失败,并加上抖动。第五步:用一个故意变慢的服务商做压测,对总请求时间下断言。第六步:补上「每请求尝试次数」这项 telemetry,并给以后的 release 打上标注。

那句报错原文说的是:本地这个操作超过了它的 context deadline。它并不能证明服务商挂了。trace 证明的是两次串行等待。release 标记和回滚证明这次退化跟着新的重试行为走。做得好的故障排查,会把观察、假设、验证和结论分清楚。

10可观测性建设与故障速查表

从用户旅程往里建。一条精心埋点的下单链路,价值高于五十个服务的主机 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 变更可见。这样生产环境才能回答问题,而不只是产出图表。

Tell me what missed

A correction is more useful than a compliment. This goes straight to the person who writes SwiftGrasp.

Was this page useful?
0/1000

Please do not include passwords, private keys, or personal information.