可观测
长链路任务系统最容易出现一种错觉:
功能好像写完了,能跑了,就算完成了。
实际上,一旦任务进入异步、跨服务、跨队列、跨 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_idtrace_idtask_typestepstatusattemptworker_idduration_mserror_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 真正处理任务的时间 |
| 死信数量 | 最终无法恢复的任务数 |
这些指标基本就能帮你判断:
- 是流量突然大了
- 是队列消化不动了
- 是某个步骤变慢了
- 还是系统进入了异常失败状态
链路追踪为什么尤其重要
长链路任务最容易出的问题,是跨组件之后“断线”。
比如一次任务会经过:
- 前端提交
- API 创建任务
- 队列投递消息
- Worker 消费
- 调 OCR 服务
- 更新数据库
如果没有 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 分钟了还没完成?
如果有基本可观测能力,你的排障顺序会很清楚:
- 先按
task_id查状态表,确认停在什么状态 - 再查日志,看卡在哪个步骤
- 再看指标,是单个任务异常还是整体堆积
- 如果涉及外部服务,再按
trace_id查整条调用链
这时候定位问题就会很快。
没有可观测时,大家就只能猜:
- 是不是队列坏了?
- 是不是 Worker 掉了?
- 是不是 OCR 卡住了?
这就是“盲飞”。
告警应该怎么设
最实用的几类告警:
- 队列堆积持续升高
- 成功率连续下降
- 死信数量超过阈值
- 某个关键步骤 P95 / P99 延迟异常升高
- 某个外部依赖错误率激增
告警不要追求一开始就无比完整,先把“最影响用户体验和任务成功率”的几项盯住。
初学者最容易踩的坑
1. 日志没有 task_id
出了问题根本串不起来。
2. 只看接口响应时间,不看排队时间
长链路系统里,很多慢不是慢在 API,而是慢在队列和 Worker。
3. 只有系统指标,没有业务指标
CPU 很健康,不代表任务成功率就健康。
4. trace_id 没有跨消息队列传递
到 Worker 那边链路就断了。
整个系列收束一下
到这里,长链路任务系统的一条主线就完整了:
- Web 请求不适合直接跑长任务
- 任务要先抽象成有生命周期的对象
- 队列负责解耦与削峰
- 执行架构要分层
- 多步骤任务要考虑 DAG 编排
- 状态存储与进度展示决定可用性
- 幂等、重试、补偿决定可靠性
- 可观测决定你能不能把系统真正跑稳
如果把这 8 篇串起来看,你就已经不是在“会调用一个异步接口”,而是在理解一套完整的长链路任务系统了。