工作流、编排与补偿

共 20 题
📑 题目列表 20 题
#
★★★

1. 工作流版本演进中流程定义变更时进行中实例如何处理(迁移/终止/双版本并存)?

当流程定义变更时,进行中的实例应如何处理?请比较迁移、终止、双版本并存三种策略的取舍?

  • 流程定义版本化
  • 进行中实例的迁移/终止/双版本策略
  • 不同策略的取舍

流程定义变更时需处理进行中实例,常见策略:一是"双版本并存"(run-old-version):已有实例继续按旧版本执行,新实例用新版本,安全但回滚与维护复杂、长期并存成本高;二是"迁移"(migrate):把进行中实例迁移到新版本,需保证状态映射合法、新版本能接续旧状态,通常支持"等实例到达安全点再迁移"或"迁移到新版本的对应状态",能统一行为但需做兼容性验证;三是"终止"(terminate):对不兼容版本直接终止并人工处理,简单粗暴但可能中断业务。工程上常按变更影响面分级:小变更直接兼容旧版本、大变更双版本并存并预留迁移窗口、破坏性变更需终止+人工补偿。

正确的策略取决于变更的破坏性、进行中实例数量与业务容忍度。工作流引擎(如 Temporal、Cadence)提供版本化原语(如 patch/versioning)让代码在重放时按版本分支,从而实现平滑演进。核心是"不破坏进行中实例的一致性与可恢复性"。

#
★★★

2. 工作流引擎 vs 手写状态机中长流程、人工审批、超时/重试场景下各自的取舍?

比较使用工作流引擎与手写状态机在长流程、人工审批、超时/重试场景下的取舍?

  • 工作流引擎的持久化与重放能力
  • 手写状态机的灵活性与轻量
  • 长流程、审批、超时重试场景的取舍

工作流引擎(如 Temporal/Cadence)内置持久化、重放、超时、重试、人工审批等待与版本化机制,适合长流程、跨进程、需要崩溃恢复与审计的场景,但要引入基础设施与架构约束;手写状态机轻量、无外部依赖、可控性强,适合短流程、单进程、状态数量有限的场景,但要自己实现持久化、超时、重试与恢复。在人工审批场景,工作流引擎的 signal/wait 机制天然支持"等待外部事件不阻塞线程",而手写状态机需自行处理事件轮询与持久化。在超时/重试场景,工作流引擎提供声明式超时与重试策略,手写状态机需手写定时器与退避逻辑。

取舍的关键是"流程的复杂度、长度与可靠性要求"。长流程、需可靠恢复、多参与者时应选工作流引擎;短流程、逻辑简单、低延迟要求时手写状态机更合适。避免"杀鸡用牛刀"或"短流程也上引擎"的两种极端。

#
★★★

3. 工作流的超时层级中 schedule-to-start、start-to-close 与 activity 超时各自拦截哪类故障,重试策略的退避与终止条件

说明工作流中 schedule-to-start、start-to-close 与 activity 超时各自拦截哪类故障,以及重试策略的退避与终止条件?

  • schedule-to-start(调度到开始)超时
  • start-to-close(开始到结束)超时
  • activity 超时与重试退避、终止条件

schedule-to-start 超时拦截"任务调度后迟迟未开始执行"的故障(如 Workflow Task 被 worker 领取但未执行、队列积压),反映调度延迟;start-to-close 超时拦截"任务开始后执行过久"的故障(如 activity 卡死、死循环),反映执行时长;activity 超时是链路、心跳、重试等组合在一起的超时,其中 heartbeat 超时用于检测 activity 崩溃(worker 未发心跳)。重试策略通常采用指数退避 + 抖动(如 1s、2s、4s…封顶),并设最大重试次数或总时长作为终止条件;超过终止条件后 activity 进入失败,由父工作流决定是补偿、重试人工作业还是终止。

分层超时的意义在于精确定位故障阶段:调度问题 vs 执行问题。重试的退避让故障服务有恢复时间,抖动避免惊群,终止条件防止无限重试拖垮系统。理解这些层级能正确设计高可靠工作流。

#
★★★

4. signal 与人工审批中工作流如何在等待外部事件时不阻塞线程、重放时如何恢复等待状态

说明工作流如何用 signal 在等待外部事件(如人工审批)时不阻塞线程,以及重放时如何恢复等待状态?

  • signal 机制与等待外部事件
  • 不阻塞线程的原理
  • 重放时恢复等待状态

工作流用 signal 实现"等待外部事件":工作流代码调用 await(signal) 时并不真正阻塞线程,而是把"等待某 signal"作为工作流状态持久化,框架在线程空闲后返回;当外部系统(如审批系统)发送该 signal 时,框架唤醒对应工作流继续执行。由于工作流是"事件驱动 + 持久化状态",等待期间不占用线程,支持大量并发等待。重放时,工作流从持久化的事件历史重建执行路径,遇到 signal 时事件历史中已记录"signal 已到达",于是直接继续而不再等待;尚未到达的 signal 则保持等待。因此重放能精确恢复"等待哪个 signal"的状态。

这是工作流引擎"确定性重放"的关键:等待被建模为事件历史的一部分,而非线程阻塞。理解 signal + 重放,能合理设计人工审批、超时、外部回调等异步场景,避免"阻塞线程"或"重放丢失等待"。

#
★★

5. Saga 编排式(Orchestration)与协同式(Choreography)的对比中集中控制器 vs 事件驱动的故障处理差异?

对比 Saga 的编排式(Orchestration)与协同式(Choreography)两种模式,说明集中控制器与事件驱动在故障处理上的差异?

  • 编排式 Saga 的集中控制器
  • 协同式 Saga 的事件驱动
  • 故障处理差异

编排式 Saga 由一个集中编排器(Orchestrator)按顺序调用各参与服务,并跟踪各步骤状态、决定补偿顺序,故障处理集中、易于理解、可控性强,但编排器是单点、逻辑集中可能成为瓶颈。协同式 Saga 没有集中控制器,各服务通过事件驱动链式触发下一步(A 完成发事件,B 订阅后执行),故障处理分散、解耦、无单点,但流程分散、难以追踪全局状态、补偿顺序难以集中控制。故障处理差异:编排式能集中决策"何时补偿、补偿哪些、顺序如何",协同式需靠事件链与消息一致性保证,排查与补偿更复杂。

选择依据是流程复杂度与耦合要求。简单流程用编排便于控制;服务多、要解耦时用协同式,但需配套事件溯源与消息可靠性。两种也可混合,编排器内部用事件驱动分工。

#
★★

6. Temporal/Cadence 工作流确定性的要求中工作流代码不能直接调用随机数、时钟或外部 IO,activity 与 workflow 如何划分?

说明 Temporal/Cadence 工作流确定性的要求,为什么工作流代码不能直接调用随机数、时钟或外部 IO,以及 activity 与 workflow 如何划分?

  • 确定性重放的要求
  • 随机数/时钟/外部 IO 对确定性的破坏
  • activity 与 workflow 的划分

Temporal/Cadence 通过确定性重放恢复工作流:工作流代码在 worker 崩溃后从事件历史重新执行,因此必须保证"同样的输入产生同样的执行轨迹"。直接调用随机数、时钟(System.currentTimeMillis)、外部 IO 会破坏确定性——重放时得到不同结果,导致状态不一致。正确做法是用框架提供的确定性 API(如 workflow 的时钟、sleep、随机数封装),而把随机数、网络、文件等外部副作用封装进 activity。activity 与 workflow 的划分:workflow 只做确定性的流程编排与状态推进(可重放),activity 执行具体副作用(调用外部服务、数据库、IO),activity 有独立的心跳、重试与超时,其执行结果作为事件记录,重放时直接复用记录结果而不重跑。

确定性是"可靠重放"的前提。划分准则:能重放、无副作用、需确定性 → workflow;有副作用、不可重放、需隔离 → activity。这一区分是理解工作流引擎可靠性的核心。

#
★★

7. 工作流的失败恢复中活动失败后的重试、补偿与人工介入如何编排,如何保证工作流实例在进程崩溃后可恢复且幂等?

说明工作流中活动失败后的重试、补偿与人工介入如何编排,以及如何保证工作流实例在进程崩溃后可恢复且幂等?

  • 活动失败后的重试、补偿、人工介入
  • 崩溃恢复与幂等
  • 重试策略:指数退避与抖动、补偿按 LIFO 顺序执行且补偿本身幂等

活动失败后,工作流先按重试策略自动重试(指数退避+抖动);重试耗尽仍失败则触发补偿(执行已成功步骤的补偿动作)或转入人工介入(把流程挂起、通知人工处理,待人工确认后继续)。编排上,补偿按"后成功先补偿"(LIFO)的顺序执行,并保证补偿本身幂等。要保证崩溃后可靠恢复且幂等:工作流状态与事件历史持久化,重启后从历史重放重建状态;每个 activity 用幂等键/请求 ID 去重,重放时复用已记录的结果而非重跑,避免副作用重复执行;外部副作用(如扣款、发消息)也需幂等设计。

失败恢复的核心是"重试 + 补偿 + 人工介入"三层兜底,配合"持久化重放 + 幂等"保证一致性与无重复副作用。工程上需明确每一层失败后的去向,避免无限重试或部分成功。

#
★★

8. 编排 vs 状态机中复杂长流程用 BPMN/DSL 表达 vs 代码状态机的取舍?

比较复杂长流程用 BPMN/DSL 表达与用代码状态机表达的取舍?

  • BPMN/DSL 的可视化与业务协作
  • 代码状态机的灵活与可测试
  • 取舍

BPMN/DSL 把流程建模为标准的图形/声明式模型,业务人员可读、可评审、可改配置,适合业务流程经常调整、需要业务与开发协作的场景,但表达能力受限、调试困难、性能与灵活性受引擎限制。代码状态机用编程语言表达流程逻辑,灵活、可测试、可控性强、可复用代码库能力,但业务人员难以直接阅读,流程变更需改代码发版。取舍:流程复杂、需频繁由业务调整、注重可审计时选 BPMN/DSL;逻辑复杂、需要深度定制与单元测试时选代码状态机;实践中常混合——用 DSL 定义主干,用代码承载复杂校验与集成。

两种方案本质是"模型驱动 vs 代码驱动"。关键看流程的变更频率与参与者身份:业务频繁改选 DSL,开发复杂逻辑选代码。考虑"谁在改、怎么改、如何验证"。

#
★★

9. 编排式 Saga 的控制器设计中如何跟踪各步骤状态、决定补偿顺序与重试策略?

说明编排式 Saga 的控制器如何跟踪各步骤状态、决定补偿顺序与重试策略?

  • 控制器跟踪各步骤状态
  • 补偿顺序(LIFO)
  • 重试策略

编排式 Saga 的控制器维护一个步骤清单,记录每个步骤的状态(待执行/已成功/失败/已补偿),并保存每步的补偿信息。执行时按顺序调用各步骤;某步失败后,控制器按"已成功步骤的逆序"(LIFO)执行补偿,确保先补偿后发生的操作,避免顺序颠倒造成不一致。重试策略:对可重试的瞬时失败(网络抖动、超时)按指数退避重试,对确定性失败(业务校验失败)不重试直接补偿;重试需设最大次数/总时长作为终止条件。控制器可通过持久化状态跟踪(如把步骤状态存库)支撑崩溃恢复后继续补偿。

控制器是 Saga 的"大脑",其正确性关键在于"状态跟踪完备 + 补偿 LIFO + 重试分级"。设计时需区分瞬时失败与确定性失败,并保证控制器本身可持久化、可恢复,避免补偿遗漏。

#
★★

10. 工作流重放的幂等中每个 activity 都需要幂等键,重放时如何避免副作用重复执行?

说明为什么每个 activity 都需要幂等键,以及重放时如何避免副作用重复执行?

  • activity 幂等键
  • 重放时副作用去重
  • 幂等设计

工作流重放时,已完成的 activity 结果会从事件历史中读出并复用,理论上不会重跑;但在"结果已执行但结果事件尚未持久化"的崩溃窗口,或者 activity 与外部服务之间出现"执行了但客户端未收到响应"的情况,activity 可能被再次调度。因此每个 activity 需要幂等键(唯一请求 ID),让外部服务在收到重复请求时能识别并返回已处理结果(或拒绝重复),从而避免副作用重复执行(如重复扣款、重复发消息)。幂等实现:外部服务记录"幂等键 → 结果",重复请求直接返回缓存结果;工作流侧保证相同幂等键对应同一 activity 语义。

幂等是分布式工作流"至少一次执行"语义下保证"恰好一次副作用"的关键。设计时每个会产生副作用的 activity 都要带幂等键,并让被调用方实现幂等去重。

#
★★

11. 补偿事务的幂等与对冲中补偿操作失败时的最终一致性兜底(重试/人工/对账)?

说明补偿事务的幂等与对冲,以及补偿操作失败时如何通过重试、人工、对账实现最终一致性兜底?

  • 补偿操作的幂等与对冲
  • 补偿失败时的兜底手段
  • 最终一致性

补偿操作本身必须幂等(可重入、重复执行结果一致),因为补偿可能要重试多次;对冲(hedging)指在补偿/操作不确定是否成功时,主动发起一次对冲请求来确认结果或触发兜底,避免一直等待。当补偿操作失败时,兜底手段包括:持续重试(带退避与终止条件)、升级为人工介入(通知运维/业务手动处理)、以及对账(定时任务把本地状态与外部系统对账,发现不一致后自动或人工修正)。最终一致性的达成依赖"重试 + 人工 + 对账"的组合,保证补偿最终到位,除非确认为不可修复的确定性失败。

补偿的可靠落地是分布式事务最终一致的关键。设计要点:补偿幂等、支持重试、提供人工与对账入口、记录补偿状态以便审计。对账是兜底的最后一道防线。

#
★★

12. 工作流引擎选型中 Temporal/Cadence、Airflow 与状态机库在长流程、调度与人工介入上的适用边界

比较 Temporal/Cadence、Airflow 与状态机库在长流程、调度与人工介入上的适用边界?

  • Temporal/Cadence 的流程能力
  • Airflow 的调度能力
  • 状态机库的轻量边界

Temporal/Cadence 面向"长流程、高可靠、需重放与人工等待"的应用流程(如订单、审批、分布式事务),支持 durable execution、signal、多语言 SDK,适合业务编排;Airflow 面向"定时/事件驱动的批处理与数据管道"(DAG 调度),擅长 cron 调度、依赖管理、任务重试,但交互式人工审批与长流程事件等待能力弱;状态机库(如 Spring StateMachine、XState)面向嵌入应用内的轻量流程控制,灵活、无基础设施依赖,但缺乏持久化重放、超时重试与人工介入的完整支撑。选型边界:长流程+人工介入+高可靠选 Temporal/Cadence;批处理调度选 Airflow;轻量流程内嵌选状态机库。

三种方案定位不同:"处理型长流程" vs "调度型任务" vs "代码内状态控制"。选型看流程类型:业务编排、调度批处理、还是应用内状态。避免用 Airflow 做人工审批或让状态机库承担跨进程高可靠。

#
★★

13. 定时触发的错过处理中 cron 工作流错过执行窗口时如何补跑,触发如何保证幂等

说明 cron 工作流错过执行窗口时如何补跑,以及触发如何保证幂等?

  • 错过窗口的补跑策略
  • 触发幂等
  • 防重入

cron 工作流因停机、负载等原因错过执行窗口时,需补跑策略:一是 catch-up/backfill(按错过的批次顺延补跑,如 missed task 逐个执行),二是跳过(若时延敏感则忽略错过窗口,等待下一次调度),三是合并(把多次错过合并为一次执行)。补跑时要保证幂等,避免重复执行副作用:用唯一执行实例 ID(run id)去重,同一触发时间只允许一个实例执行;用分布式锁/排他标记防止同窗口并发触发;对更新类任务要求操作幂等(同参数重复执行结果相同)。触发本身也应记录"上次成功执行时间",据此判断是否错过以及是否需要补跑。

错过处理与幂等是 cron 调度可靠性的关键。设计时明确"错过就补、跳过还是合并",并用 run id + 锁 + 幂等操作防止重复执行,兼顾及时性与一致性。

#

14. Saga 的补偿事务设计中如何保证补偿操作本身可重入、幂等,失败补偿的最终一致如何达成?

说明 Saga 补偿事务如何保证补偿操作本身可重入、幂等,以及失败补偿的最终一致如何达成?

  • 补偿操作可重入与幂等
  • 失败补偿的最终一致
  • 补偿状态记录

保证补偿可重入、幂等:补偿操作以"业务键 + 补偿状态"为唯一依据,重复执行返回相同结果(如"已退款"则再次退款请求被识别为幂等);补偿逻辑设计为可重复执行而副作用不叠加(如退款用幂等键、状态机记录补偿进行中/已补偿)。失败补偿的最终一致通过"重试 + 人工 + 对账"达成:补偿失败自动重试(退避),重试耗尽升级人工处理,并配套对账任务定期核对本地与外部状态,发现不一致自动修正。整个补偿过程记录在 Saga 状态表中,支持崩溃恢复后继续补偿。

补偿是 Saga 可靠性的关键,其自身必须幂等、可重入、可恢复。设计时把补偿当"一等公民"实现幂等与状态记录,并预留重试、人工、对账三层兜底,才能达成最终一致。

#

15. 工作流引擎(Temporal/Cadence)的持久化与重放中活动(Activity)与工作流(Workflow)的幂等边界?

说明工作流引擎(Temporal/Cadence)的持久化与重放机制,以及活动与工作流的幂等边界?

  • 持久化与重放机制
  • 活动与工作流的幂等边界
  • 幂等设计

Temporal/Cadence 把工作流的执行历史(事件序列)持久化,worker 崩溃后从历史重放重建执行状态,保证工作流可恢复。幂等边界:工作流层(确定性编排)只做可重放的状态推进,其"执行"本身通过事件历史保证不会重复推进;活动层(副作用执行)是副作用发生的点,需在活动层实现幂等——每个活动带唯一请求 ID,外部服务去重,重放时复用已记录的活动结果而非重跑。因此"工作流重放是确定性的、活动副作用需幂等"形成分工:工作流保证流程不重复,活动保证副作用不重复。

理解幂等边界有助于正确设计:把幂等关注点放在活动上,工作流专注确定性编排。这样崩溃恢复时既不会重复推进流程,也不会重复执行副作用。

#

16. 分布式事务的替代中 Saga 与 TCC、本地消息表的对比,什么场景下 Saga 优于 2PC?

对比 Saga、TCC、本地消息表三种分布式事务方案,说明什么场景下 Saga 优于 2PC?

  • Saga 与 TCC、本地消息表对比
  • 2PC 的局限性
  • Saga 优于 2PC 的场景

Saga 把长事务拆成多个本地事务 + 补偿,最终一致,适合长流程、跨服务、可接受短暂不一致的场景;TCC(Try-Confirm-Cancel)把每个操作拆成预留、确认、取消三阶段,需业务方实现预留资源,适合资源强约束、可预留的场景;本地消息表用本地事务写业务 + 消息表,再异步投递,适合解耦、可靠消息的场景。Saga 优于 2PC 的场景:2PC 有同步阻塞与协调者单点、长事务锁资源、对参与者要求高且不可用(不能保证最终一致),Saga 通过补偿提供最终一致,不长时间锁资源,适合长流程、跨服务、高可用要求高、可接受短暂不一致的业务(如订单、支付)。

2PC 提供强一致但阻塞、单点、可用性差;Saga 提供最终一致但短期状态不一致。选择看业务对"一致性与可用性"的权衡:需强一致且参与方少选 2PC,需高可用与长流程选 Saga。

#

17. 工作流执行的可观测性中如何记录每个步骤的输入输出、重试次数与耗时,支撑故障定位与回放?

说明如何记录工作流每个步骤的输入输出、重试次数与耗时,以支撑故障定位与回放?

  • 步骤 input/output 记录
  • 重试次数与耗时记录
  • 故障定位与回放

工作流可观测性要求在每一步记录:输入(本步骤收到的参数)、输出(返回结果)、重试次数、耗时(调度到开始、开始到结束)、错误信息与上下文(trace/request id)。这些数据可写入结构化日志或事件历史,配合 traceId 串联整个流程。故障定位时,依据 traceId 检索每个步骤的输入输出与状态,定位失败步骤与原因;回放时,可用记录的输入重放该步骤或在沙箱中重放整个工作流历史,复现问题。高可观测性还依赖工作流引擎自带的事件历史(每个调度/完成/失败都是事件)与 metrics(重试率、耗时直方图)。

"可观测性"是长流程可靠性的保障。记录输入输出与重试耗时,让故障可定位、可回放、可度量。设计时统一 traceId、结构化日志、采集 metrics,把"发生了什么"完整沉淀。

#

18. 工作流与 Saga 的关系中编排器(Orchestrator)如何作为 Saga 的协调者管理补偿顺序?

说明工作流与 Saga 的关系,编排器(Orchestrator)如何作为 Saga 的协调者管理补偿顺序?

  • 工作流与 Saga 的关系
  • 编排器管理补偿顺序
  • 补偿执行

Saga 是一种分布式事务模式,工作流(尤其是编排式 Saga)是其实现载体:编排器作为 Saga 的协调者,按顺序调用参与服务,并记录每个步骤的状态与补偿动作。当某一步失败时,编排器按"已成功步骤的逆序"(LIFO)执行补偿,确保后执行的操作先被补偿,避免脏数据;编排器还负责重试策略、人工介入与超时处理。因此工作流引擎天然适合实现 Saga 的编排器——它提供持久化状态、重试、超时与确定性重放,让补偿顺序管理可靠且可恢复。

工作流(编排)与 Saga 的结合,把"补偿顺序、重试、状态跟踪"交给编排器,降低分布式事务的复杂度。编排器是 Saga 的"状态机",保证补偿顺序正确且可恢复。

#

19. 工作流引擎(Temporal/Cadence)的持久化与重放中如何保证工作流确定性?

说明工作流引擎(Temporal/Cadence)的持久化与重放如何保证工作流确定性?

  • 持久化事件历史
  • 确定性重放
  • 确定性保证手段

Temporal/Cadence 把工作流的每个执行步骤(事件:调度 activity、收到 signal、等待完成、超时等)追加到持久化的事件历史中。worker 崩溃后,从历史重放工作流代码,重建完全相同的执行轨迹。为保证确定性:工作流代码不能调用随机数、时钟、随机哈希等非确定性 API,必须用框架提供的确定性 API(workflow 时钟、sleep、随机数);外部 IO 封装进 activity,activity 结果作为事件记录,重放时直接复用结果而不重跑;所有分支基于事件历史中的确定性数据,保证重放结果与首次执行一致。这样重放得到的状态与原始状态一致,流程可恢复。

确定性是"可靠重放"的前提。核心手段是"事件历史驱动 + 限制非确定性 API + activity 隔离副作用"。理解它就能解释为何工作流代码有一系列约束。

#

20. 子工作流与取消传播中 child workflow 失败如何按策略影响父流程,取消信号如何向下传播

说明子工作流(child workflow)失败如何按策略影响父流程,以及取消信号如何向下传播?

  • 子工作流失败策略
  • 取消信号向下传播
  • 父子工作流关系

子工作流失败时,父工作流可按策略处理:默认子工作流失败会向上抛给父工作流(父可捕获并补偿/重试),也可以配置"子工作流失败不影响父工作流"(忽略或继续);父工作流可设置子工作流的超时、重试与终止策略。取消信号向下传播:父工作流被取消时,默认会传播取消给子工作流,子工作流收到取消请求后执行其取消逻辑(释放资源、清理),从而形成级联取消;也可配置取消不传播(子工作流独立完成)。这种层级关系让复杂的父子流程能统一管理失败与取消。

子工作流是流程复用的方式,失败与取消的传播策略决定父子流程的耦合方式。设计时明确"子失败后父是重试、补偿还是终止",以及"取消是否级联",避免流程悬挂或误取消。