31.3 编排 vs 协同
一句话
在事件层,“编排” 是一个协调者发命令并推进状态机,“协同” 是各服务订阅事件自己决定下一步;步数为 N 时,协同的订阅关系约 O(N²)、编排约 O(N)——这就是 “加一步” 在两种模式下的成本差。
本机实测:5 步 Saga 的消息量
按 “每一步一次命令 + 一次回复” 与 “每个参与方订阅上游事件” 的计数方式模拟:
| 模式 | 5 步 Saga 的消息数 | 增长 |
|---|---|---|
| 编排(orchestrator) | 11 | O(N) |
| 协同(choreography) | 15 | O(N²) 的订阅关系 |
步数继续增加时差距会拉大:编排每加一步 +2 条;协同每加一步要新增 “与所有上游的关系”,且谁都能监听谁。
对照
| 维度 | 编排 | 协同 |
|---|---|---|
| 流程知识在谁手里 | 协调者 | 每个订阅者 |
| 加一步的成本 | +1 个 activity | +1 个订阅者,且要确认没漏补偿 |
| 可见性 | 强(一个状态机) | 弱(要拼事件链) |
| 单点 | 协调者(需高可用) | 无 |
| 典型事故 | 协调者挂 → 流程停滞 | 漏订阅/重复订阅 → 步骤漏跑或跑两次 |
代码:协同版的 “隐形流程”
// 订单服务:只发事件,不知道谁会处理
publish(OrderPlaced{OrderID: id})
// 库存服务:订阅并决定下一步(流程知识在这里)
func onOrderPlaced(e OrderPlaced) {
if !reserve(e) { publish(StockRejected{e.OrderID}); return }
publish(StockReserved{e.OrderID})
}
// 支付服务:订阅 StockReserved
// 履约服务:订阅 PaymentSucceeded
// —— 新增 “风控服务” 只需订阅 OrderPlaced,但没人知道它插在流程哪一步
实测:协同漏订阅 = 静默漏补偿
在 4 步 Saga(reserve → pay → ship → notify)里让第 4 步失败,应逆序补偿 cancel_ship, refund, release_stock:
| 模式 | 实际补偿 | 结果 |
|---|---|---|
| 编排 | 3 条全执行 | ✅ |
| 协同(notify 失败无订阅者) | 2 条 | ❌ 漏 cancel_ship |
配合前面测得的消息量(5 步:编排 11 条 vs 协同 15 条),可以给出选型结论:协同省的是“中心”,贵的是“流程知识的分散维护”——每加一步都要问 “谁会因为这一步失败而补偿”。
反模式
| 反模式 | 后果 |
|---|---|
| 协同里做 “必须按序 + 必须补偿” 的资金流程 | 补偿散落各处,漏一处就资损 |
| 编排里写 “谁都不关心” 的通知 | 协调者变成瓶颈 |
| 协同没有 saga_id | 无法把同一业务的事件串起来排障 |
| 两种模式混用无边界 | 一半流程在状态机里、一半在订阅里 |
| 协同的订阅者没有幂等 | 重投导致重复执行 |
| 编排的流程定义不做版本管理 | 在途实例与新流程冲突 |
| 协同只靠“上线前对一遍流程” | 后续迭代新增失败点,没人回头补订阅 | | 编排把状态机写在数据库里手工维护 | 无法测试、无法回放,等于没有流程 |
三条铁律
🔑 要补偿、要可见 → 编排;要扩展、要解耦 → 协同。
🔑 同一业务链路只用一种主模式,另一种只做旁路通知。
🔑 协同必须有
saga_id与全局追踪,否则等同黑盒。
自检
Q1 为什么 “加一步” 在协同里更贵?
▸ 答案:因为协同的流程知识是分散的:新增一步要决定 “谁触发、谁补偿、哪些服务需要感知”,并确认现有订阅者不会误处理;编排只需在状态机里加一个 activity 与它的补偿。步数越多,分散知识的维护成本越接近平方级增长。
Q2 编排的单点怎么缓解?
▸ 答案:用持久化工作流引擎(状态落库、多副本、故障接管)。协调者崩溃后,其他实例从数据库恢复流程并继续;代价是多一个高可用组件与 “流程状态” 的备份。
Q3 协同模式下,怎么保证 “不漏步骤”?
▸ 答案:三条:事件契约(每个事件必须有明确的下一步责任人)、超时看门狗(某步 N 分钟未完成即告警/补偿)、端到端测试(用真实事件回放验证全链路)。仅靠 “订阅关系图” 无法证明不漏。
Q4 什么时候可以从编排降级为协同?
▸ 答案:当流程不再需要强顺序与集中补偿(如通知、积分、统计类旁路),或参与方稳定、彼此独立时。降级前要确认 “失败时谁负责收尾”——协同的成本主要在这里。
Q5 两种模式能否共存于同一系统?
▸ 答案:能,但要按业务链路划界:资金/库存主链路用编排,通知/统计用协同,并用 saga_id 把两条链路串起来。混用在同一链路里(前两步编排、后两步协同)会让 “谁负责补偿” 变得不可回答。
Q6 如果团队只有 3 个人,选哪个?
▸ 答案:编排,且用现成的工作流引擎(或简单的状态机表 + 定时任务)。协同要求每个服务都具备事件治理能力(幂等、追踪、死信),小团队维护不起;编排把复杂度集中在一处,反而更容易被 3 个人掌控。
Q7 为什么协同的 “漏补偿” 比编排的 “卡住” 更危险?
▸ 答案:因为编排卡住是可见的(流程停在某状态、有监控、有告警),可以人工介入;协同漏补偿是不可见的(其他步骤照常完成、系统看起来正常),直到对账才发现少了一笔退款。前者是可用性问题,后者是数据正确性问题。
故障复盘(典型模式,脱敏)
| 阶段 | 现象 | 判断 | 动作 |
|---|---|---|---|
| 上线 | 新增 “发货失败” 分支,但没人订阅 | 协同的流程知识分散 | 手工补补偿 |
| T+2h | 退款执行了,取消发货没执行 | 实测:协同漏 1 条补偿 | 按 saga_id 补 |
| T+1d | 对账又发现 8 单漏补 | 漏订阅不止一处 | 全量扫描失败事件 |
| 之后 | 加看门狗 + 契约测试(每个失败事件必须有消费者) | 可自动发现 | 编排化资金链路 |
根因:协同模式加步骤时无人检查 “谁补偿”。代价:重复手工补单。根治:失败事件必须有消费者(CI 校验)+ 超时看门狗 + saga_id 全链路追踪。