08 状态机与任务生命周期¶
难度:中级|前置:数据库、幂等|目标:把隐含状态变成可验证的规则
为什么需要状态机¶
如果代码中到处出现:
但没有统一定义哪些变化合法,系统很容易进入不可能状态。
状态机由三部分组成:
- 状态;
- 事件;
- 合法迁移。
一个任务状态机¶
pending → running → succeeded
│ ├──→ failed
│ ├──→ waiting_approval → running
│ └──→ cancelling → cancelled
└──────────────→ cancelled
每次迁移应该回答:
- 谁触发?
- 前置状态是什么?
- 是否允许重复?
- 需要写哪些数据?
- 是否产生外部副作用?
- 失败如何恢复?
状态与步骤分开¶
长任务不能只保存一个 status=running。还应记录:
{
"task_id": "t-123",
"status": "running",
"current_step": "analyze",
"step_version": 4,
"retry_count": 1
}
任务状态描述整体,步骤记录帮助恢复和排查。
乐观并发控制¶
两个 Worker 可能同时更新任务。可以使用版本号:
UPDATE tasks
SET status = 'running', version = version + 1
WHERE id = ?
AND status = 'pending'
AND version = ?;
受影响行数为 0,说明状态已被别人改变,当前操作不能继续。
状态迁移和副作用¶
数据库状态变更与外部设备命令、邮件、工单无法天然处于同一事务。常见做法:
- 幂等 operation_id;
- Outbox 事件;
- 执行结果保存;
- 可重试的补偿;
- 人工处理终态。
不要设计一个万能 running¶
需要区分:
- 等待调度;
- 正在执行;
- 等待重试;
- 等待用户;
- 等待外部回执;
- 正在取消。
不同等待原因决定恢复策略。
自测¶
- 状态机与几个 if/else 的本质区别是什么?
- 为什么长任务只存
running不够? - 两个 Worker 同时领取任务如何防止重复?
waiting_approval为什么应该是显式状态?
完成标准¶
- 能为 OTA 或 Agent 任务画状态机
- 能写出每个迁移的前置条件
- 能解释版本号的作用
- 能区分等待重试和等待人工
下一单元:可观测性