07-可观测

可观测

长链路任务系统最容易出现一种错觉:

功能好像写完了,能跑了,就算完成了。

实际上,一旦任务进入异步、跨服务、跨队列、跨 Worker 的世界,如果没有可观测性,线上出问题时你几乎等于“盲飞”。

你会遇到这些真实场景:

  • 用户说“任务一直转圈,到底卡哪了?”
  • 运营说“今天成功率怎么突然掉了?”
  • 研发说“到底是排队慢,还是 OCR 慢?”
  • 运维说“队列堆积了,但不知道是哪个 worker 出问题”

所以可观测不是锦上添花,而是任务系统能不能长期稳定运行的基础能力。

可观测通常看三件事

1. 日志 Logs

回答:发生了什么

2. 指标 Metrics

回答:整体表现怎么样

3. 链路追踪 Traces

回答:一次任务具体经过了哪些组件、卡在了哪里

把它们放在一起理解最直观:

flowchart LR
		C[Client] --> API[API Service]
		API --> MQ[Message Queue]
		MQ --> W[Worker]
		W --> DB[(Task DB)]
		W --> OSS[(Result Store)]

		API --> LOG[Logs]
		MQ --> METRIC[Metrics]
		W --> TRACE[Traces]
		DB --> METRIC
		OSS --> LOG

这张图表达的是:

  • 日志、指标、追踪不是单独系统,它们要贯穿主链路
  • 每个核心组件都应该往观测系统上报信息

日志该怎么打才有用

任务系统日志最怕两种情况:

  • 什么都没记
  • 记了一堆废话,但没有统一字段

真正有用的任务日志,至少要带这些字段:

  • task_id
  • trace_id
  • task_type
  • step
  • status
  • attempt
  • worker_id
  • duration_ms
  • error_code

例如:

{
	"task_id": "task_20260414_001",
	"trace_id": "trace_abc_001",
	"task_type": "contract_parse",
	"step": "ocr",
	"status": "FAILED",
	"attempt": 2,
	"duration_ms": 18234,
	"error_code": "OCR_TIMEOUT"
}

有了这些统一字段,你后面才能:

  • 按任务 ID 检索整条日志
  • 看哪个步骤最慢
  • 看失败是否集中在某个 worker 或某个外部依赖

指标最少要盯哪些

如果只能先做一版最小监控,优先盯这些指标:

指标 说明
提交量 每分钟进来多少任务
成功率 最终成功的比例
失败率 最终失败的比例
重试率 有多少任务进入重试
队列堆积长度 队列中积压了多少任务
排队时长 任务从创建到开始执行的时间
执行时长 Worker 真正处理任务的时间
死信数量 最终无法恢复的任务数

这些指标基本就能帮你判断:

  • 是流量突然大了
  • 是队列消化不动了
  • 是某个步骤变慢了
  • 还是系统进入了异常失败状态

链路追踪为什么尤其重要

长链路任务最容易出的问题,是跨组件之后“断线”。

比如一次任务会经过:

  1. 前端提交
  2. API 创建任务
  3. 队列投递消息
  4. Worker 消费
  5. 调 OCR 服务
  6. 更新数据库

如果没有 trace_id 贯穿,你很难把这几段串起来。

可以用一个简化时序理解:

sequenceDiagram
		autonumber
		participant C as Client
		participant API as API
		participant MQ as MQ
		participant W as Worker
		participant O as OCR Service

		C->>API: POST /tasks + trace_id
		API->>MQ: publish(task_id, trace_id)
		MQ->>W: deliver(task_id, trace_id)
		W->>O: call OCR(trace_id)
		O-->>W: response(trace_id)

核心原则是:

task_id 用来识别业务对象,trace_id 用来串联一次链路上的技术事件。

二者最好同时存在。

一个最常见的排障场景

假设用户反馈:

为什么我这个任务已经 10 分钟了还没完成?

如果有基本可观测能力,你的排障顺序会很清楚:

  1. 先按 task_id 查状态表,确认停在什么状态
  2. 再查日志,看卡在哪个步骤
  3. 再看指标,是单个任务异常还是整体堆积
  4. 如果涉及外部服务,再按 trace_id 查整条调用链

这时候定位问题就会很快。

没有可观测时,大家就只能猜:

  • 是不是队列坏了?
  • 是不是 Worker 掉了?
  • 是不是 OCR 卡住了?

这就是“盲飞”。

告警应该怎么设

最实用的几类告警:

  • 队列堆积持续升高
  • 成功率连续下降
  • 死信数量超过阈值
  • 某个关键步骤 P95 / P99 延迟异常升高
  • 某个外部依赖错误率激增

告警不要追求一开始就无比完整,先把“最影响用户体验和任务成功率”的几项盯住。

初学者最容易踩的坑

1. 日志没有 task_id

出了问题根本串不起来。

2. 只看接口响应时间,不看排队时间

长链路系统里,很多慢不是慢在 API,而是慢在队列和 Worker。

3. 只有系统指标,没有业务指标

CPU 很健康,不代表任务成功率就健康。

4. trace_id 没有跨消息队列传递

到 Worker 那边链路就断了。

整个系列收束一下

到这里,长链路任务系统的一条主线就完整了:

  1. Web 请求不适合直接跑长任务
  2. 任务要先抽象成有生命周期的对象
  3. 队列负责解耦与削峰
  4. 执行架构要分层
  5. 多步骤任务要考虑 DAG 编排
  6. 状态存储与进度展示决定可用性
  7. 幂等、重试、补偿决定可靠性
  8. 可观测决定你能不能把系统真正跑稳

如果把这 8 篇串起来看,你就已经不是在“会调用一个异步接口”,而是在理解一套完整的长链路任务系统了。

github