对问题的回答。
该架构中心围绕 Durable Execution 模式,通过事件源控制平面分离短暂计算和持久状态。在其核心,工作流定义作为确定性状态机运行,每个状态转换都在确认之前作为不可变事件持久化到 Apache Kafka(写前日志),使其在故障期间能够决定性重放。计算层利用 AWS Lambda 或 Azure Functions 组织到特定租户的 VPC 和 IAM 边界,确保隔离,同时利用预Provisioned并发热池来减轻冷启动。为了在区域之间实现精确一次语义,系统使用 CockroachDB,其具有可序列化的默认隔离来存储工作流状态,利用 Raft 一致性算法在跨区域间保持一致性,而无需额外的协调服务。事件关联通过分层方法实现子秒延迟: Redis 集群与 RedisJSON 索引在内存中处理热点事件匹配,而 Elasticsearch 作为历史关联查询的冷存储,Cloudflare Workers 提供边缘事件缓冲以吸收流量峰值。
生活中的情况
在 2023 年黑色星期五,SwiftCart (一个全球电子商务平台)在处理 5000 万个并发交付工作流(每个持续 3-7 天)时,面临其传统 Step Functions 实现的灾难性故障。当 us-east-1 遇到区域故障时,故障转移到 us-west-2 导致 12,000 次重复发货,因为工作流状态恢复依赖于 DynamoDB 的最终一致性,TTL 窗口为 5 分钟。同时,承运人 webhook 事件遭遇 30 秒的关联延迟,打破了对客户的实时追踪承诺,并造成 200 万美元的 SLA 罚款。
解决方案 A:基于 Kubernetes 的调度器,使用 EKS 上的 Airflow
这种方法通过在 Amazon EKS 上运行的 Apache Airflow 和 PostgreSQL 作为元数据存储,承诺全面控制和成熟工具。优点包括广泛的插件生态系统和简单的本地开发环境。然而,缺点证明是致命的: Pod 调度延迟平均为 45 秒,违反了零规模要求,闲置工作流的计算成本应接近零。此外,维护 PostgreSQL 跨区域的同步复制使得每个任务状态转换增加 200 毫秒,而且缺乏内置的精确一次语义要求复杂的应用级锁定,导致在区域故障转移期间频繁死锁。
解决方案 B:纯事件驱动编排,使用 Kafka 和 Lambda
此无服务器本土化方案利用 Amazon MSK (Kafka)作为真相来源,Lambda 函数对事件做出反应,而没有中央调度器。优点包括真正的按需经济和通过基于日志的持久性自然实现的弹性。然而,实现精确一次语义需要跨 DynamoDB (用于幂等性)和 Kafka 的分布式事务,每个操作引入 500 毫秒以上的延迟。此外,对于长期运行的流程(7 天工作流的第 5 天),重建工作流状态需要从 S3 归档中重放数百万个事件,导致恢复时间超过 10 分钟,并使调试"分布式意大利面"变得不可能。
解决方案 C:具有分片状态管理的持久执行平台
所选架构实现了一个自定义的受 Temporal 启发的控制平面,将持久状态( CockroachDB 具有地理分区表)与短暂的 Lambda 工作者分开。 一致性哈希 将工作流分片分配到区域数据库节点,而 Redis Streams 提供子毫秒事件关联缓冲。优点包括通过 CockroachDB 的可序列化事务实现原生精确一次,针对调试的确定性重放,以及真正的零规模,其中处于非活动状态的工作流仅存在于便宜的 S3 快照中。缺点涉及到维护 etcd 集群以实现服务发现的重大操作复杂性,以及在大规模唤醒场景中防止潮涌效应的复杂缓存需求。
结果
通过实现解决方案 C,使用每个租户的 SQS 队列和 1 秒的可见时间限制,SwiftCart 在随后的 Prime Day 事件中实现了零工作流重复,即使在 us-west-2 出现 45 分钟的故障时。事件关联的 p95 延迟通过 Redis 边缘缓存下降至 400 毫秒。基础设施成本与始终在线的 EKS 方法相比降低了 70%,85% 的工作流在闲置等待期间仅以压缩状态快照的形式存在于 S3 中,节省了 140 万美元的年费用。
候选人常常忽略的内容
如何防止在网络分区期间同时处理事件的两个区域工作流状态发散?
大多数候选人错误地建议使用 DynamoDB 或 Cassandra 的 最后写入胜出 语义,这在工作流编排中失败,因为业务操作是非交换的(例如,“取消订单”和“发货订单”无法仅通过时间戳进行调和)。正确的实现利用工作流状态元数据中嵌入的 Vector Clocks 或 Dotted Version Vectors 。当网络分区愈合时,系统通过版本向量比较检测并发分支,并应用特定领域的合并函数。对于不可调和的冲突(例如同时取消和发货),架构实现了 saga compensation 模式,其中后续操作触发之前操作的回滚,并附有全面的审计日志。或者,利用 CockroachDB 的默认可序列化隔离在分区期间完全防止发散,通过拒绝冲突写入,迫使显式重试循环,具备指数回退,而不是允许静默数据损坏。
当 7 天长的工作流在 v1.0 上启动后必须在您部署了具有更改活动语义的 v2.0 后完成时,您如何处理工作流代码版本控制?
候选人经常忽略了对持久执行至关重要的 Deterministic Replay 需求。简单地更新 Lambda 函数代码会打断进行中的工作流,因为重放逻辑(用于在崩溃后重建状态)与原始执行路径偏离,导致非确定性的异常。解决方案通过事件源标记显式实现 工作流版本控制 。在部署 v2.0 时,工作者必须在 WebAssembly 沙箱或单独的 Docker sidecar 中同时支持 v1.0 和 v2.0 的活动实现。工作流状态记录每个历史活动的执行代码版本;在重放期间,工作者加载特定历史版本的沙箱,以确保对过去步骤进行确定性的重新执行,而新工作流则使用 v2.0。最大工作流持续时间结束后(7 天加 24 小时的安全缓冲),可以停用 v1.0 代码。这需要无限期维护向后兼容的活动签名,或利用 Pact Contract Testing 来验证版本之间的行为一致性。
您如何防止用户代码中的无限循环或内存泄漏等 "毒药药丸" 工作流,同时不打破健康工作流的精确一次保证?
简单的 死信队列 (DLQ)实际上违反了精确一次语义,因为将消息移动到 DLQ 需要确认原始消息,如果 DLQ 写入失败或消费者在操作过程中崩溃,则面临消息丢失的风险。稳健的解决方案采用 进度跟踪 和幂等检查点。工作者每 30 秒心跳,将进度令牌写入 etcd 或 CockroachDB,使用比较和交换操作。如果工作者在同一工作流任务上连续崩溃三次(通过存储在数据库中的执行尝试计数器检测),该任务被标记为“污染”,但依然保留在队列中,具有指数递增的可见延迟(1 分钟,5 分钟,30 分钟)。然后,拥有增强可观察性、内存限制和详细 OpenTelemetry 跟踪的单独“外科”工作者池尝试执行。仅在 24 小时内持续失败后,工作流才会转移到 "暂停" 状态,需手动操作员介入,同时实施所有状态转换在 CockroachDB 中利用 MVCC 时间戳进行原子比较和交换操作,确保在整个过程中保持精确一次不变。