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 全链路追踪。

关联