创建虚拟机、网络、磁盘或安全组,本质上不是一次数据库 CRUD,而是一条跨系统、长耗时、部分可失败的工作流。把它包装成一个同步 HTTP 请求,最终会遇到超时、重复提交、状态不一致和无法恢复。
一、为什么订单接口不能一直等底层资源完成
底层云平台调用可能需要几十秒甚至数分钟,还可能经历排队、配额校验、网络重试和异步任务。上层请求若同步阻塞,会占用线程与连接;网关超时后,客户端不知道请求到底失败还是仍在执行,重试又可能创建重复资源。
client → create order → persist intent → return orderId
↓
async worker
↓
validate → allocate → configure → verify
因此接口只承诺“已受理”,资源是否最终成功由订单状态机表达。订单不是一行状态字段,而是业务意图、执行进度、失败证据和恢复入口的集合。
二、状态机设计:状态要能回答下一步该做什么
一个实用状态集可以是:PENDING、VALIDATING、PROVISIONING、VERIFYING、SUCCEEDED、RETRYING、COMPENSATING、FAILED、CANCELLED。状态数量不是越多越好,关键是每个状态都有明确进入条件、允许事件和退出动作。
| 当前状态 | 事件 | 下一状态 | 副作用 |
|---|---|---|---|
| PENDING | START | VALIDATING | 校验租户、配额与参数 |
| VALIDATING | PASS | PROVISIONING | 调用底层资源 API |
| PROVISIONING | ACCEPTED | VERIFYING | 记录外部任务 ID |
| VERIFYING | READY | SUCCEEDED | 绑定资源与订单 |
| 任意执行态 | RETRYABLE_ERROR | RETRYING | 计算退避时间 |
| 任意执行态 | FATAL_ERROR | COMPENSATING / FAILED | 释放已创建资源 |
三、幂等不是“加一个唯一索引”就结束
幂等要覆盖三个层次:
- 入口幂等:客户端携带业务请求号,同一租户内唯一,重复提交返回原订单。
- 步骤幂等:每个执行步骤有 step key 和版本,已成功步骤不会重复执行。
- 外部调用幂等:把订单号或资源意图 ID 传给底层;若底层不支持,则调用前后都要查询已有资源。
UPDATE order_task
SET state = 'PROVISIONING', version = version + 1
WHERE id = ? AND state = 'VALIDATING' AND version = ?;
受影响行数为 0,说明状态已被其他工作线程推进或版本已变化,当前执行者应停止,而不是继续调用外部系统。这是乐观锁保护状态转换。
四、重试与补偿:先判断错误语义,再决定动作
超时不等于失败。外部请求超时时,资源可能已经创建成功。正确动作通常是先按幂等键或外部任务 ID 查询,再决定重试。错误可以分为:瞬时错误、限流、业务拒绝、未知结果和不可恢复错误。
- 瞬时错误:指数退避 + 抖动,设置最大次数。
- 限流:尊重 Retry-After,避免重试风暴。
- 业务拒绝:直接失败,保留可读错误原因。
- 未知结果:先查询外部状态,禁止盲目重放。
- 部分成功:进入补偿流程,按逆序释放资源。
补偿不是数据库事务回滚。它是新的业务动作,本身也可能失败,因此同样需要状态、幂等、重试和人工接管入口。
五、可观测与验收:让失败可以被解释
每次状态转换应记录订单 ID、旧状态、新状态、事件、执行器、外部任务 ID、耗时和错误分类。链路追踪把入口请求、异步消息和底层 API 串起来;指标至少覆盖成功率、各状态停留时间、重试次数、补偿成功率和超时积压。
可靠的订单系统不是“永不失败”,而是失败后状态不丢、动作不重、原因可查、流程可继续。