28.4 服务编排与编制

一句话

编排(orchestration)是 “中心指挥”:一个协调者按流程依次调用服务并处理补偿;编制(choreography)是 “各自响应”:每个服务订阅事件、自行决定下一步;前者可观测、可测,后者松耦合、难追因。

对照表

维度 编排(orchestration) 编制(choreography)
流程定义在哪 协调者/工作流引擎里 分散在各服务的订阅逻辑里
可观测性 强(一个地方看全流程) 弱(要跨服务拼链路)
耦合 协调者知道所有参与者 服务之间只认事件
变更成本 改流程 = 改协调者(集中评审) 加一步 = 新增订阅(分散、易漏)
单点 协调者(需高可用) 无
适合 步骤多、分支多、要补偿的业务(订单/支付/履约) 事件多、参与方多、彼此不关心的场景(通知/积分/统计)

代码:编排用一个 “可重放的工作流”

// Temporal 风格(示意):流程即代码,天然可重放、可补偿
func OrderWorkflow(ctx workflow.Context, in OrderInput) error {
    // 1) 预留库存;失败即返回
    if err := workflow.ExecuteActivity(ctx, ReserveStock, in).Get(ctx, nil); err != nil {
        return err
    }
    // 2) 扣款;失败则补偿(释放库存)
    if err := workflow.ExecuteActivity(ctx, Charge, in).Get(ctx, nil); err != nil {
        _ = workflow.ExecuteActivity(ctx, ReleaseStock, in).Get(ctx, nil)
        return err
    }
    // 3) 履约(可异步、可重试)
    return workflow.ExecuteActivity(ctx, Fulfill, in).Get(ctx, nil)
}

编制版本则没有 “流程代码”,只有各自的事件处理器:

OrderCreated → 库存服务(预占) → StockReserved → 支付服务(扣款) → PaymentSucceeded → 履约服务

实测:协同漏一个订阅,就少一次补偿

本机模拟 “4 步 Saga,第 4 步 notify 失败,应补偿 ship/pay/reserve”:

模式 实际执行的补偿 是否完整
编排(协调者知道全部补偿) cancel_ship, refund, release_stock ✅
协同(notify 失败无人订阅) refund, release_stock ❌ 漏 cancel_ship

漏掉的 cancel_ship 就是 “包裹已经出库但没人取消”——协同的流程知识分散,漏订阅不会被编译或测试发现,只会变成线上资损。

编排:失败点 → 查自己的补偿表 → 逆序执行(可测试)
协同:失败点 → 指望某个服务订阅了失败事件 → 没有就静默漏掉

反模式

反模式 后果
用编制实现 “强顺序 + 补偿” 的业务 补偿逻辑散落各处,漏一个就资损
用编排实现 “谁都不关心” 的通知 协调者变成瓶颈与单点
编排里写业务规则 流程引擎变成第二套业务系统
编制没有全局追踪 出问题无法回答 “卡在哪一步”
两种模式混用且无边界 一半在流程里、一半在事件里,排障成本翻倍
补偿动作不幂等 重试导致重复退款/重复释放

| 协同用“文档约定谁订阅”代替契约测试 | 新人加步骤时漏订阅,线上才发现 | | 编排的补偿表与流程分两处维护 | 加步骤忘加补偿,回滚不完整 |

三条铁律

🔑 要补偿、要可见 → 编排;要解耦、要扩展 → 编制。

🔑 同一业务链路只用一种主模式,另一种只做边缘通知。

🔑 补偿必须幂等,否则重试会把 “纠正” 变成 “二次事故”。

自检

Q1 为什么 “订单 → 支付 → 履约” 更适合编排?

▸ 答案:因为它有强顺序、多个失败点、必须补偿:库存要释放、款项要退回、履约要取消。编排把这些 “下一步与补偿” 集中在一个可测试的流程里;编制则要求每个服务自己知道 “失败后该通知谁”,漏掉一条就是资损。

Q2 编制(choreography)的真正优势是什么?

▸ 答案:参与方解耦:新增一个 “积分服务” 只需订阅 OrderCompleted,不用改流程定义。它适合 “一对多广播、参与方互不关心” 的场景。代价是 “全流程可见性” 变差,需要额外的追踪与补偿设计。

Q3 编排的 “单点” 怎么解决?

▸ 答案:用持久化工作流引擎 + 多副本:流程状态落库(可恢复),引擎本身做高可用;协调者崩溃后由其他实例接管并继续推进。注意 “流程状态” 也是数据,需要备份与版本兼容。

Q4 编排与 “Saga” 是什么关系?

▸ 答案:Saga 是补偿式事务的模式,编排与编制都可以实现它:编排版把 Saga 步骤写在流程里;编制版靠事件驱动、每个服务自己触发下一步与补偿。区别在 “谁掌握流程知识”,不在 “有没有补偿”(见 24.5 Saga (编排 / 协同 / 补偿))。

Q5 怎么给编制补上 “可见性”?

▸ 答案:三件事:统一事件规范(同一 trace_id/saga_id 贯穿)、集中式追踪(链路 + 事件表)、超时看门狗(某步长时间未完成即告警/补偿)。没有这三样,编制在生产里就是 “黑盒异步”。

Q6 什么时候该从编制迁到编排?

▸ 答案:当出现 “必须按序、必须补偿、必须知道进度” 的需求时(如涉及资金/库存/合规审批)。判据是 “出问题时,能否在一个地方回答 ‘这单卡在哪、下一步谁负责’”;答不上来,就该迁。

Q7 协同模式怎么发现 “漏订阅”?

▸ 答案:三条硬手段:契约测试(每个失败事件必须至少有一个消费者,且在 CI 校验)、端到端故障注入(在测试环境让某步失败,断言补偿全部发生)、生产对账(按 saga_id 检查 “失败单是否都有补偿记录”)。缺任何一条,漏订阅都只能靠事故暴露。

故障复盘(典型模式,脱敏)

阶段 现象 判断 动作
T+0 履约失败后,部分订单未退款 协同漏订阅失败事件 手工补退款
T+1h 补退款 300 单,其中 12 单重复退 补偿不幂等 按幂等键去重
T+1d 对账发现仍有 5 单未退 无全局 saga_id 按业务单号回溯
之后 加编排 + 补偿幂等 + 对账 可发现、可修复 周级对账常态化

根因:用协同实现 “必须补偿” 的资金流程,且补偿不幂等。代价:300 单人工 + 12 单重复。根治:资金链路用编排;补偿必须幂等 + 可对账。

关联