基于重试协调器的事务重试架构 (Transaction Retry Architecture With Retry Coordinator)
本文档是 StackSaga 框架*事务重试架构 (Transaction Retry Architecture)* 的权威参考指南。 专为集成、部署或运维基于 StackSaga 的微服务的*架构师*、*DevOps 工程师*和*开发人员*编写。
简介 (Introduction)
在分布式微服务架构中,事务通常跨越多个服务和数据库,这使得它们极易受到网络瞬态故障、超时和服务暂时不可用的影响。 StackSaga 框架通过高可靠的*事务重试子系统 (Transaction Retry Subsystem)* 解决了这一严峻挑战,在确保可靠性和最终一致性的同时,杜绝了重复处理或并发冲突的风险。
该子系统能够精准检测处于暂停状态的事务,并结合使用令牌环分区 (Token Ring Partitioning)、RSocket 响应式通信以及时间窗口发布机制,在微服务实例集群之间安全地重新触发这些事务。 其结果是:即使面对瞬态故障也能实现高可用性与系统弹性,同时严格保证事务*恰好执行一次 (Exactly-Once)*。
本文档范围 (Scope of This Document)
本文档专门聚焦于*事务重试子系统 (Transaction Retry Subsystem)*。
涵盖的主题包括:
-
识别可重试错误与不可重试错误 (Retryable vs Non-Retryable Errors)
-
为什么仅依靠实例级重试是不够的
-
编排器部署模式:标准节点 (Standard-Node) 与重试节点 (Retry-Node)
-
三组件重试生态系统:Orchestrator、Retry-Coordinator-Slave 与 Retry-Coordinator-Master
-
基于 Murmur3 的令牌环分区机制 (Token Ring Partitioning)
-
组件间的 RSocket 通信模式
-
基于时间窗口的令牌发布与冲突规避 (Conflict Avoidance)
-
故障模式、重连行为与部署考量
-
多区域部署架构 (Multi-Region Deployments)
-
适用于超大规模系统的虚拟集群分区 (Virtual Cluster Partitioning)
核心基础概念 (Foundational Concepts)
在深入研究架构之前,必须先明确两个核心概念:什么类型的错误会触发重试,以及为什么不能简单地由发起事务的原始节点来负责重试。
可重试错误与不可重试错误 (Retryable vs Non-Retryable Errors)
StackSaga 将错误划分为两大类别:
不可重试错误 (Non-retryable error) —— 业务逻辑校验失败或永久性错误状态。 此类错误不是瞬态故障,无法通过重试来解决。 典型示例包括参数验证失败、权限认证失败,或任何表明事务本身存在根本性业务冲突的条件。 这类错误在主流程(向前执行)和补偿流程(回滚)中都可能发生。 在主流程中,不可重试错误会触发补偿回滚;如果发生在补偿流程中,该事务将被标记为永久失败 (Permanently Failed)。 在这两种情况下,事务都*不会被重试*。
可重试错误 (Retryable error) —— 瞬态或临时性故障,例如下游微服务暂时不可用、数据库连接超时或瞬时网络分区。 事务将被置于暂停状态 (Paused),并由系统自动重播。 可重试错误在主流程和补偿流程中都有可能发生。 这正是重试子系统发挥核心作用的场景,确保事务在没有重复执行或并发冲突的情况下安全地完成重试。
为什么实例级重试是不充分的 (Why Instance-Level Retrying Is Insufficient)
乍看之下,让最初处理该事务的同一节点负责重试似乎轻而易举。 然而,这种方法存在致命的设计缺陷。
标准微服务节点是*瞬态的 (Ephemeral)* —— 它们的生命周期较短,旨在根据实时流量进行弹性伸缩、动态销毁或按需替换。 这意味着,当重试时机到来时,最初处理该事务的节点可能早已下线或被销毁。 这样一来,该事务将无限期处于暂停状态,没有任何节点能认领并继续执行它。
举个具体的例子:假设有 3 个正在运行的微服务实例,每个实例都因下游网络波动而暂存了一些待重试的事务。 如果依赖每个实例自身的定时调度器来重播各自的事务,而其中一个实例因缩容策略被 Kubernetes 终止下线,那么它原本持有的事务将永远无法被其余实例接管重试。
StackSaga 通过将*事务重试完全与原始发起节点解耦*解决了此问题,其核心正是下文所述的三组件架构。
编排器部署模式 (Orchestrator Deployment Modes)
由于标准的编排器节点具有瞬态性,StackSaga 为编排器服务定义了两种不同的部署模式:
- 标准节点 (Standard-Node)
-
常规的编排器微服务(启用了 StackSaga),负责常规业务流量的事务处理与逻辑编排。 标准节点可以根据系统流量自由进行水平扩容或缩容。
- 重试节点 (Retry-Node)
-
在处理常规事务的基础之上,额外承担事务重试管理职责的专属节点。 重试节点被设计为比标准节点更加稳定和长寿命 (Long-Lived),以确保在集群其余节点动态伸缩时,重试管理始终平稳可靠。
在典型的生产部署中,您可能会配置 5 到 1000 个标准节点来应对高并发流量,同时仅保持 2 个长期稳定的重试节点来专职管理重试任务。 技术上并没有限制重试节点扩缩容的硬性规则——这种区分主要是运维层面的:重试节点就是您选择保持高稳定性的服务节点。
|
重试是事务处理过程中的*最坏降级路径 (Worst-Case Scenario)*,而非正常主流程。 有关重试发生的频率及其相对于常规流程的处理开销比重,请参阅 Saga 长事务比例分析。 |
接下来的关键挑战在于:如何在重试节点集群中安全地分配重试职责,避免任意两个节点针对同一事务产生并发竞争冲突? 这正是*令牌环分区 (Token Ring Partitioning)* 所解决的问题。
令牌环分区 (Token Ring Partitioning)
概述 (Overview)
StackSaga 采用 Murmur3 一致性哈希算法 (Murmur3 Consistent Hashing Algorithm) 将每个事务确定性地映射到 64 位令牌环上的唯一位置。 当事务*首次初始化*时(由任意 Orchestrator 发起),系统会计算该令牌值,并与事务记录一同持久化到数据库中,同时记录发起 Orchestrator 的*集群名称 (Cluster Name)* 与*区域 (Region)*。 重试子系统利用持久化存储的令牌、集群和区域信息,在任意时间点确定*具体由哪一个 Orchestrator 实例全权负责*该事务的重试。
完整的令牌空间范围从 \(-2^{63}\) 到 \(2^{63}-1\):
-9,223,372,036,854,775,808 → 9,223,372,036,854,775,807
下图展示了重试节点如何分配互不重叠的令牌环分段。 在此示例中有 4 个重试节点,每个节点各自负责令牌空间的四分之一:
-
哈希空间范围:
-9223372036854775808→9223372036854775807 -
总哈希范围大小:
18446744073709551616 -
每节点分区大小 (4 节点):
4611686018427387904
| 节点 (Node) | 槽位 (Slot) | 起始令牌 (Token Range Start) | 结束令牌 (Token Range End) |
|---|---|---|---|
|
A |
|
|
|
B |
|
|
|
C |
|
|
|
D |
|
|
两级分区机制 (Two-Level Partitioning)
令牌环通过*两级层级*进行切分:
-
Master → Retry-Coordinator-Slave: Retry-Coordinator-Master 将完整的令牌环均分给所有已注册的 Retry-Coordinator-Slave 节点。
-
Retry-Coordinator-Slave → Orchestrator: 每个 Retry-Coordinator-Slave 进一步将自身分配到的令牌切片均分给连接到它的各个 Orchestrator 实例。
Master → Retry-Coordinator-Slave 示例 (3 个 Retry-Coordinator-Slave)
| 从重试协调器节点 (Slave Node) | 起始令牌 (Inclusive) | 结束令牌 (Inclusive) |
|---|---|---|
Retry-Coordinator-Slave Node 1 |
|
|
Retry-Coordinator-Slave Node 2 |
|
|
Retry-Coordinator-Slave Node 3 |
|
|
| 每当已注册的 Retry-Coordinator-Slave 数量发生变动时,分区边界将在下一个 30 秒发布周期由系统自动重新计算。 |
Retry-Coordinator-Slave → Orchestrator 示例 (Slave Node 1 下挂 2 个 Orchestrator)
| 编排器实例 (Orchestrator Instance) | 起始令牌 (Inclusive) | 结束令牌 (Inclusive) |
|---|---|---|
Order Service Instance 1 |
|
|
Order Service Instance 2 |
|
|
所有其他的 Retry-Coordinator-Slave 节点对其各自注册的 Orchestrator 实例遵循完全相同的划分逻辑。
|
并不强制要求多台物理机才能实现令牌环分区。
|
三组件重试架构 (The Three-Component Retry Architecture)
令牌环分区是由三个组件通力协作管理的。 在详细深入各组件之前,先进行整体职责速览:
| 组件 (Component) | 核心职责 (Responsibility) |
|---|---|
领域的权威决策核心。 每 30 秒将完整的令牌环均分给所有活跃的 Retry-Coordinator-Slave。 通过轮询算法 (Round Robin) 指引新上线的 Orchestrator 应该连接哪一个 Retry-Coordinator-Slave。 |
|
Master 与 Orchestrator 之间的中继枢纽。 接收 Master 下发的令牌切片,并进一步将其细分给连接到它的各个 Orchestrator。 每分钟将专属的子令牌范围下发给对应 Orchestrator。 |
|
持有当前分钟内的子令牌范围。 轮询数据库中令牌落入该子范围内的暂停事务并重新执行——绝不会与其他任何实例产生冲突。 |
每个组件职责边界极其清晰,没有越权侵入。 最终构建出一个支持水平扩展、容忍部分节点故障,并严格保证每个暂停事务*在任意时刻恰好被单一节点执行重试*的高可用系统。
Retry-Coordinator-Master (主重试协调器)
架构定位
Retry-Coordinator-Master 是微服务领域内重试协调的*唯一权威实体 (Single Authority)。 每个业务领域内*有且仅有一个 Master(或者每个虚拟集群一个 Master——参见 通过虚拟集群进行水平扩展)。
Master 完全不感知您的业务逻辑,并且绝不直接访问事务数据库。 它的全部职责就是纯粹的协调调度:切分令牌环、跟踪各个 Retry-Coordinator-Slave 的存活状态,并在新的 Orchestrator 启动时为其指引正确的 Retry-Coordinator-Slave。
它基于 RSocket 构建在*全非阻塞的 Netty 反应式堆栈*之上,这意味着单个 Master 实例即可轻松维持*数千个并发的 Retry-Coordinator-Slave 长连接*——远超绝大多数生产系统的需求。
三大核心职责
职责 1 — 维护 Retry-Coordinator-Slave 注册表。
每个启动的 Retry-Coordinator-Slave 通过持久的 RSocket request-stream 连接到 Master 并注册自身。
Master 维护所有在线连接的 Slave 实时注册表。
当某个 Slave 断开连接(崩溃、重启)时,Master 能够立即察觉并更新注册表。
职责 2 — 每 30 秒发布一次令牌环分区。 在每分钟的第 30 秒,Master 触发定时器。 它根据当前注册的 Slave 列表,将完整的 64 位令牌环均分,并将分配到的范围推送给每个 Slave,打上在*下一个整分钟*生效的时间戳标签。 这 30 秒的提前量为整个集群提供了充足的时间,以便在正式生效前接收并就绪新分配的分区。
职责 3 — 指引新上线 Orchestrator 连接对应 Slave。
当新的 Orchestrator 实例启动时,它向 Master 发送单次 Request-Response 探测请求,询问:“我应该连接哪一个 Retry-Coordinator-Slave?”
Master 采用*轮询算法 (Round Robin)* 从可用列表中挑选一个 Slave——将 Orchestrator 均匀打散到各个 Slave 上——并返回该 Slave 的主机名和端口。
在此次交互之后,Orchestrator 不再与 Master 联系;后续的所有通信都直接通过 Slave 进行。
知识边界 (Knows and Does Not Know)
- 感知的信息 (Knows)
-
-
已注册的 Retry-Coordinator-Slave 节点及其连接存活状态。
-
如何使用 Murmur3 分区算法均分 64 位令牌环。
-
通过轮询算法为新上线的 Orchestrator 分配对应的 Slave。
-
- *不*感知的信息 (Does not know)
-
-
初始寻址完成后的各个单独 Orchestrator 实例细节。
-
事务数据库及其内容。
-
具体的业务逻辑或事务结构。
-
其他微服务领域的 Master——每个 Master 在其所属领域内高度自治隔离。
-
Master 节点故障处理 (Master Node Failure)
当 Master 节点发生故障宕机时:
-
所有已注册的 Retry-Coordinator-Slave 失去与 Master 的长连接。
-
Retry-Coordinator-Slave 停止接收新的令牌范围更新。 其*最后已知的缓存范围将保留*,但会在下一个分钟边界到达时过期。
-
Orchestrator 继续使用最后接收到的子范围执行重试,直到该范围自然过期。
-
新上线的 Orchestrator 实例由于无法从 Master 获取 Slave 分配,初始化重试连接将失败。
|
Master 故障*仅影响该领域的重试子系统*。 其他微服务领域的运行完全不受影响。 最重要的是,新事务的常规主执行流程(业务流量)完全不受 Master 存活状态的影响。 对于即使是这种局部故障窗口也无法容忍的超高可用系统,请参阅虚拟集群 (Virtual Clusters)。 |
Retry-Coordinator-Slave (从重试协调器)
架构定位
Retry-Coordinator-Slave 是一个*专用的基础设施轻量级服务*,通常与您的微服务伴生部署。 它完全不感知任何业务逻辑。 它不触碰事务数据库。 它不执行任何业务事务。
它的核心目的在于充当 Retry-Coordinator-Master 与 Orchestrator 实例之间的*分发网桥 (Distribution Bridge)*:从 Master 接收较大的令牌切片,并将其进一步细分成更小且互不重叠的子范围,分别下发给连接到它的各个 Orchestrator。
四步日常流程 (Four-Step Routine)
步骤 1 — 向 Master 注册。
启动时,Retry-Coordinator-Slave 向 Master 打开一条持久的 RSocket request-stream 连接,宣告自身上线,并静默等待令牌范围更新。
步骤 2 — 接收令牌范围切片。 每 30 秒 Master 发布更新的范围。 Slave 接收其分配到的切片(完整 64 位环的一段连续区间),并获取该切片在哪个自然分钟内生效的元数据。
步骤 3 — 接受 Orchestrator 注册。 当 Orchestrator 启动并连接时,Slave 在其本地注册表中记录该实例。
步骤 4 — 细分并下发。 凭借手中的令牌切片和连接的 Orchestrator 列表,Slave 将切片等额细分——每个 Orchestrator 独占一段互不重叠的子范围——并通过持久流推送到各自对应的 Orchestrator。
此循环每分钟重复一次,使每个连接的 Orchestrator 实时掌握其当前所拥有的重试权限窗口。
为什么需要 Slave 这一中间层?
微服务 Pod(即 Orchestrator)的变动极其频繁——频繁发布部署、高并发自动扩容、夜间自动缩容。 如果由 Master 直接跟踪每个业务 Pod,频繁的注册和注销事件将使 Master 不堪重负,陷入持续全量重新计算令牌环的泥潭。
Retry-Coordinator-Slave 在此起到了关键的*缓冲区 (Buffer)* 作用。 Master 只需要感知极少数且极其稳定的 Slave 节点。 底层应用 Pod 的一切伸缩波动全部由 Slave 在本地静默吸收与消化,由其在 Orchestrator 变动时自行微调子范围分配——完全不会打扰 Master 的稳定性。
知识边界 (Knows and Does Not Know)
- 感知的信息 (Knows)
-
-
自身所注册的 Master 地址(
stacksaga.agent.slave.target-master.host/.port)。 -
当前连接到自身的 Orchestrator 实例。
-
分配给自身的令牌切片以及如何细分该切片。
-
- *不*感知的信息 (Does not know)
-
-
业务逻辑或事务执行图。
-
事务数据库。
-
其他 Slave 节点——各个 Slave 之间完全独立无感知。
-
Slave 节点故障处理 (Slave Node Failure)
当某个 Retry-Coordinator-Slave 崩溃或网络不可达时:
-
连接到该 Slave 的所有 Orchestrator 丢失数据流。 它们的子范围失效,这些实例的重试轮询将暂时挂起。
-
Master 察觉连接断开并执行*惰性再平衡策略 (Lazy Rebalance Strategy)*:
-
如果丢失的 Slave 不是注册表中的最后一个索引,Master 假定其很快会自愈恢复(在 Kubernetes 环境中尤为常见),*不会立即*重新全量打散分区。
-
Master 继续向其余 Slave 下发缓存的原有分区。
-
崩溃 Slave 对应的令牌范围暂时*冻结 (Frozen)*——在此故障窗口期内没有 Orchestrator 会认领该范围。
-
-
当该 Slave 重启后,重新连入 Master 并作为*新注册节点*处理。
-
在下一个 30 秒发布周期,Master 重新计算并将范围下发给所有当前在线的 Slave(包含重启完成的 Slave)。
|
在 Slave 故障期间,哈希令牌落入该冻结范围内的事务*暂时不会被重试*,直到该范围重新被节点认领覆盖。
这些事务安全保存在数据库中,状态维持为 |
具备重试连接器的编排器 (Orchestrator With Ring-Coordinator connector, Retry-Node)
重试节点 (Retry-Node) 是指启用了 StackSaga 重试环连接器 的 Orchestrator 实例,专门参与分布式事务重试生态系统。 它是在执行常规事务流程之外,额外承担事务重试调度职责的专属节点。 重试节点被设计为比普通标准节点更加稳定和*长寿命 (Long-Lived)*,以确保在集群其他节点弹性伸缩时,重试调度平稳连续。
-
要将标准节点升级为重试节点,只需在现有 Orchestrator 服务中引入
stacksaga-ring-coordinator-connector依赖并配置连接参数。 -
技术上对重试节点的伸缩没有任何限制——区分纯粹是运维维度的:它们就是您团队选择保持高稳定性的微服务实例。
-
一旦配置为重试节点,Orchestrator 会自动加入重试体系:初始连接 Master 寻址 Slave,随后从被分配的 Slave 接收分配给本节点的令牌子分区,并开始主动拉取并重试落入该分区的所有暂停事务。
它在内部始终并行运行两项核心作业。
两项并发作业 (Two Concurrent Jobs)
作业 1 — 执行常规事务 (标准编排器职责)。 当新的业务请求到达时(例如用户下单),Orchestrator 创建事务,将其分解为各个*跨度 (Spans)* 并依次执行——调用下游服务、更新数据库、发布事件。 如果某个跨度发生不可逆的永久性错误,它将触发*补偿 (Compensation)* 回滚流程,逆向撤销已执行的操作。
作业 2 — 重试暂停的事务 (重试调度职责)。 某些跨度失败并非由于业务冲突,而是由于底层资源瞬时不可用。 StackSaga 不会抛弃这些事务——而是将其标记为暂停状态并持久化到数据库中。 Orchestrator 周期性地检查数据库中的暂停事务并重新执行。 关键在于:根据当前时间窗口持有的令牌子范围,它仅检查并认领自身*全权负责*的那部分事务。 这正是 StackSaga 确保即使有海量运行节点,也绝不会出现两个节点同时重复重试同一事务的核心保障。
注册与令牌分发时序流程 (Registration & Token Distribution Flow)
下方的时序图将这三大组件有机串联在一起,完整展示了从启动注册到活跃重试执行的全生命周期。
逐步详解 (Step-by-Step Description)
阶段 1 — Retry-Coordinator-Slave 注册
-
启动时,每个 Retry-Coordinator-Slave 向 Master 打开一条持久的 RSocket
request-stream长连接。 -
Master 将该 Slave 记录到其实时注册表中。
-
Slave 保持连接畅通,被动等待令牌范围更新。
阶段 2 — Orchestrator 注册
-
启动时,每个 Orchestrator 向 Master 发送一条单次的 RSocket
Request-Response探测消息,申请分配一个可用的 Slave。 -
Master 采用*轮询算法 (Round Robin)* 选择一个健康的 Slave,并返回该 Slave 的主机地址和端口号。
如果同一个 Orchestrator 实例重启,随着轮询指针的前移,它可能会被分配到*不同的* Slave。这确保了在长时间运行中各个 Slave 上的负载保持均衡。 -
Orchestrator 向其分配到的 Slave 打开持久的 RSocket
request-stream连接,开始监听子范围更新。
RSocket 通信模式 (RSocket Communication Patterns)
StackSaga 选用 RSocket 作为所有组件间通信的骨干协议,主要是因为其在非阻塞 Netty 传输层之上提供了出色的持久性、反应式双向数据流支持。
| 连接链路 (Connection) | 交互模型 (Interaction Model) | 描述说明 (Description) |
|---|---|---|
Retry-Coordinator-Slave → Master |
|
持久流订阅。Slave 在 Master 上完成注册并保持长连接通道,用于持续接收令牌范围更新。 |
Orchestrator → Master |
|
一次性单次寻址。Orchestrator 在启动时向 Master 请求分配一个可用的 Slave 节点。 |
Orchestrator → Retry-Coordinator-Slave |
|
持久流订阅。Orchestrator 订阅分配给它的 Slave 并保持长连接通道,用于接收其专属的子令牌范围更新。 |
令牌时间窗口与冲突规避 (Token Time Window & Conflict Avoidance)
发布周期 (The Publish Cycle)
Retry-Coordinator-Master 运行一个在*每分钟第 30 秒*触发的循环定时器。 在每次触发时,Master:
-
根据当前活跃注册的 Slave 重新计算令牌环分区。
-
将最新计算出的令牌范围下发给每个 Slave。
-
每个 Slave 重新计算其子范围并将更新推送到其注册的各个 Orchestrator。
所发布的令牌范围被明确标记为在*下一个完整自然分钟*(T+1)生效,而非当前分钟。
为什么采用 30 秒的时间偏移量?
在第 30 秒发布并标记在*下一分钟*生效,构建了一个整整 30 秒的缓冲就绪窗口 (30-second Preparation Window)。 该时间窗口充分考虑了各种极端网络延迟:Slave 层层分发开销、Orchestrator 处理开销以及超大型集群中的跨节点网络抖动。
当第 T+1 分钟开始的瞬间,所有 Orchestrator 均已绝对可靠地在本地就绪了最新的子范围,可以立即无缝启动新一轮事务轮询。
时间线示例 (Timeline Example): 第 T 分钟第 30 秒 → Master 发布令牌分区 (标记 T+1 分钟生效) 第 T 分钟第 31–59 秒 → Slave 与 Orchestrator 陆续接收并本地缓存新分区 第 T+1 分钟第 0 秒 → 各 Orchestrator 基于新分区准时启动重试轮询 第 T+1 分钟第 30 秒 → Master 发布适用于 T+2 分钟的新分区 ...
多区域部署架构 (Multi-Region Deployment)
对于跨多个地理区域部署的系统,StackSaga 通过 region 属性界定重试归属权。
每个组件——Master、Retry-Coordinator-Slave 以及 Orchestrator——都打上了其所属区域的烙印。
区域隔离的工作原理 (How Region Scoping Works)
当一笔事务创建时,它自动继承发起该事务的 Orchestrator 的区域属性。 该区域值被持久化到数据库的事务记录中。
在重试轮询期间,每个 Orchestrator 在令牌范围过滤之外,将 region 作为强制约束条件:
SELECT *
FROM transactions
WHERE token BETWEEN :subRangeStart AND :subRangeEnd
AND region = :region
AND cluster = :cluster
AND status = 'FAILED_WITH_RETRYABLE_ERROR';
这确保了位于 us-central 区域的 Orchestrator 绝不会尝试拉取并重试由 asia-south 区域发起的事务——即使两个区域共享同一个底层分布式数据库(例如全球多活的 Cassandra 集群)。
虚拟集群 (Virtual Clusters)
为什么需要虚拟集群?
在超大规模部署中,随着系统体量的激增,单领域单 Master 架构会面临两大挑战:
-
吞吐扩展 (Scale) — 单个 Master 必须维持该领域的所有 Slave 连接。 尽管 Master 基于 RSocket 的反应式非阻塞 Netty 堆栈能够承载数千个长连接,但在极端规模场景下,平台架构师可能希望进一步分流协调负载。
-
故障隔离 (Fault Isolation) — 如果该领域的唯一 Master 发生故障,该领域的重试子系统将暂停,直到其自愈恢复。 某些对可用性极其严苛的核心系统要求:部分基础设施故障所波及的重试能力绝不能超过设定的安全比例。
虚拟集群 (Virtual Clusters) 通过将单一物理部署划分为*多个完全独立的 Master-Slave 组*,完美解决了上述问题。每个组在同一物理区域内作为完全独立的重试协调单元运行。
什么是虚拟集群?
虚拟集群是由特定名称标识的 Retry-Coordinator-Master、Retry-Coordinator-Slave 和 Orchestrator 实例集合,与其他组完全物理隔离运行。
组标识通过 stacksaga.instance.cluster 属性进行声明。
共享相同集群名称的三类组件共同构成一个逻辑重试集群:
-
Master 仅接受具有匹配集群名称的 Slave 注册。
-
Slave 仅连接属于自身集群的 Master(
stacksaga.agent.slave.target-master.host/.port)。 -
Orchestrator 在初始寻址时仅连接自身集群的 Master,并随后连接该 Master 分配的 Slave。
-
重试 SQL 查询在令牌范围和区域之外,将集群名称作为强制过滤条件。
运行在同一物理 Kubernetes 集群内的两个虚拟集群是*完全隔离的*——它们不共享任何状态、不共享任何网络连接,重试职责也完全独立。
部署拓扑 (Deployment Topologies)
- 多区域单集群 (Multi-Region Single-Cluster)
-
最基础的多区域方案——每个物理区域配置一个虚拟集群。 这是地理分布式部署的基准标准。
- 多区域多虚拟集群 (Multi-Region Multi-Virtual-Cluster)
-
为了实现极致的扩展性与故障隔离,每个物理区域内部署*多个虚拟集群*。 如果任何单个 Master 发生故障,仅仅波及属于该特定虚拟集群的事务比例——同一区域内的其余虚拟集群继续完全正常运作。
跨虚拟集群的事务归属权 (Transaction Ownership Across Virtual Clusters)
事务永久绑定到创建它的 Orchestrator 所在的区域和虚拟集群。 其他任何虚拟集群——即使位于相同的物理区域内——都绝不会尝试接管重试。
重试查询语句始终强制包含 region 和 cluster:
SELECT *
FROM transactions
WHERE token BETWEEN :subRangeStart AND :subRangeEnd
AND region = 'us-central' -- 物理区域
AND cluster = 'us-central-c1' -- 虚拟集群
AND status = 'FAILED_WITH_RETRYABLE_ERROR';
这种设计尤其契合 Apache Cassandra 等分布式数据库,在 Cassandra 中可以将 region 和 cluster 作为复合分区键 (Partition Key) 的一部分,从而使数据库能够将查询精确路由到特定的数据节点,完全规避全表扫描。
专业术语表 (Glossary)
| 术语 (Term) | 术语定义 (Definition) |
|---|---|
跨度 (Span) |
分布式事务中单一且可独立追踪的工作执行单元。一个完整的事务由一个或多个按序执行的跨度组成。 |
令牌 (Token) |
从事务 ID 经 Murmur3 哈希算法计算得到的 64 位整数。用于将事务确定性地映射给特定的重试责任人。 |
令牌环 (Token Ring) |
切分为互不重叠连续区间的完整 64 位整数哈希空间。StackSaga 采用 Murmur3Partitioner 模型,原理与 Apache Cassandra 的令牌环完全一致。 |
令牌范围 (Token Range) |
令牌环上的连续子集区间,在特定的时间窗口内由 Master 授予特定的 Slave 或 Orchestrator 独占负责。 |
时间窗口 (Time Window) |
一个自然分钟的时间周期,在此期间 Orchestrator 对其分配的子令牌范围拥有排他性的重试所有权。 |
发布周期 (Publish Cycle) |
每分钟第 30 秒触发的周期性事件,Master 在此期间重新计算并分发令牌范围。 |
补偿回滚 (Compensation) |
当事务主执行流程遇到不可重试错误时触发的反向回滚撤销序列。 |
Murmur3 分区器 (Murmur3Partitioner) |
一种采用 MurmurHash3 算法的一致性哈希方案,用于在令牌环上均匀分布哈希键。 |
物理区域 (Region) |
物理机房或云部署边界(例如 |
虚拟集群 (Virtual Cluster) |
命名逻辑组,包含完全物理隔离的 Master、Slave 和 Orchestrator 实例。通过 |
RSocket |
一种支持多种交互模式( |
惰性再平衡 (Lazy Rebalance) |
Master 的弹性策略:在 Slave 意外断开时推迟令牌环的重新切分计算直到下一个预定发布周期,避免对临时网络波动做出过激的瞬时再平衡反应。 |
标准节点 (Standard-Node) |
负责常规业务事务处理的瞬态 Orchestrator 实例,可根据业务流量自由水平伸缩。 |
重试节点 (Retry-Node) |
长期稳定运行的 Orchestrator 实例,同时承担常规事务处理与事务重试管理两项职责。 |
事务重播调用的模块交互架构 (StackSaga Module Interaction for Transaction Re-Invocation)
下图从高层架构视角展示了 StackSaga 各模块之间如何协作完成事务的重新调用。 该图经过了适当简化,仅突出与事务重播直接相关的核心模块,略去了部分底层实现细节。
-
stacksaga-ring-coordinator(Slave) 通过持久的 RSocket 流向 Orchestrator 上的stacksaga-ring-coordinator-connector模块发布分配给它的令牌子范围。 -
对应
stacksaga-{database}-support数据库实现中的ReInvokeTaskManager任务管理器基于分配到的令牌子范围长轮询事件存储,查找需要被重新调用的事务。 -
符合重试条件的事务被移交给对应
stacksaga-{impl}-support实现模块中的TransactionReInvokeManager。 -
TransactionReInvokeManager通过stacksaga-{database}-support提供的event-store service请求历史事件数据,将事务快照 (Snapshot) 重建到发生故障的断点状态。 -
事务快照重建完毕后,
TransactionReInvokeManager将快照发送给同模块内的ExecutionManager(执行管理器)。 -
最终,引擎像处理常规流程一样重新执行该事务。
虽然架构图中未直接画出 stacksaga-env-{impl}-support,但该模块专门负责提供具体部署环境的元数据解析。
|
重试环协调器部署拓扑 (Ring-Coordinator Deployment Topologies)
根据系统的规模和性能 SLA 要求,重试环协调器支持多种灵活的部署拓扑结构:
-
内嵌式部署拓扑 (Embedded Ring-Coordinator Deployment)
-
集群化部署拓扑 (Clustered Ring-Coordinator Deployment)
内嵌式部署拓扑 (Embedded Ring-Coordinator Deployment)
在此拓扑中,Ring-Coordinator 的 Master 和 Slave 均内嵌在同一个物理服务实例内部运行。 非常适合中小型规模的部署。即便如此,由于底层基于 RSocket 非阻塞 Netty 反应式架构,单实例依然能轻松应对海量的 Orchestrator 实例。
内嵌共享模式 (Shared Mode Of Embedded Ring-Coordinator Deployment)
在此模式下,单个服务实例运行一组内嵌共享的 Ring-Coordinator Master 和 Slave,为同一物理区域内的多个微服务领域提供统一服务。
这种模式资源利用率最高,但各个领域之间存在一定的耦合度。
例如在 us-central 区域内有三个微服务领域(订单 Order、支付 Payment、库存 Inventory),系统部署单个共享服务实例 (Shared-Service),内嵌的 Master 和 Slave 同时为这三个业务领域提供服务。
架构图如下:
集群化部署拓扑 (Clustered Ring-Coordinator Deployment)
在此拓扑中,Master 和 Slave 实例作为独立的微服务分别独立部署,以实现更高的伸缩性与故障隔离。 该拓扑同样进一步细分为三种模式:
集群化共享模式 (Shared Mode Of Clustered Ring-Coordinator Deployment)
这是一种性价比更高的部署拓扑:在同一物理区域内,由一个统一的 Ring-Coordinator 集群(一个 Master 和多个 Slave)为多个微服务领域提供共享服务。
例如在 us-central 区域内,单个共享的协调器集群 (Shared-RC-Cluster) 同时服务于订单、支付和库存三个领域。
架构图如下:
集群化独立专用模式 (Dedicated Mode Of Clustered Ring-Coordinator Deployment)
这是解耦最为彻底的顶级生产拓扑:每个微服务领域在同一物理区域内都拥有完全独立的专属 Ring-Coordinator 集群。
这提供了最高等级的业务隔离与容灾能力。
例如在 us-central 区域内,订单、支付和库存三个领域各自拥有完全独立的集群(Order-RC-Cluster、Payment-RC-Cluster、Inventory-RC-Cluster),各自配备独立的 Master 和 Slave 节点。
架构图如下:
集群化混合模式 (Hybrid Mode Of Clustered Ring-Coordinator Deployment)
这是一种兼顾隔离性与资源成本的灵活拓扑:某些核心领域拥有专属集群,其余领域共享公共集群。 例如,高频核心的订单领域独享 Order-RC-Cluster,而支付与库存领域则共享 Shared-RC-Cluster。
架构图如下:
Kubernetes StatefulSet 集群化部署
在云原生容器化的 Kubernetes 环境中,官方强烈推荐将 Clustered Ring-Coordinator 作为 StatefulSet 运行。
StatefulSet 为每个副本提供了稳定的网络标识和确定性的递增序号索引(coordinator-0、coordinator-1、coordinator-2 等)。
StackSaga 原生利用这一特性实现了基于索引的自动化角色识别(stacksaga.coordinator.deploy.index-based=true):
-
索引为
0的副本(coordinator-0)自动作为 Master 节点引导启动。 -
所有索引
> 0的副本(coordinator-1、coordinator-2等)自动作为 Slave 节点引导启动。
这一精妙设计使运维团队只需维护单一的 StatefulSet 清单和 ConfigMap 即可搞定整个协调器集群,免除了为 Master 和 Slave 分别编写并维护多套部署配置的繁琐开销。
完整的配置指南与属性详解,请参阅 Ring-Coordinator 指南中的基于 Kubernetes StatefulSet 的自动化角色识别。
禁用事务 (Disabled Transactions) 与终止事务 (Terminated Transactions)
禁用事务 (Disabled Transactions)
如果某些事务在事务生命周期 (Transaction Lifetime) 内经过多次重试后仍然超时过期,这些事务将被标记为*禁用事务 (Disabled Transactions)*。
导致事务未能完成并最终过期的原因有哪些?
-
在事务执行期间某些执行器 (Executors) 发生了变更。
例如,假设某个事务在服务版本 1.0.0 中发起,由于目标服务不可用而在重试队列中保持了一段时间;在此期间如果部署了新版本,而新版本中删除了该执行器,那么该事务将无法被执行。 即使该事务被传递去重试,由于执行器不存在,事务也无法执行。 即使此时未找到执行器,系统仍会一次又一次地重新调度该事务进行重试,直到达到事务生命周期的终点。 生命周期结束后,该事务将被标记为禁用事务。即使未找到执行器仍重新调度事务的原因是,开发和运维团队能够识别问题并在生命周期结束前进行修复。 发生此类错误时,StackSaga 框架会在 StackSaga Trace Window (链路追踪窗口) 中发出问题通知。
-
未找到目标服务主机 (Target Service Host)。
如果目标主机持续未找到,直到超出生命周期,该事务在生命周期结束后将被标记为禁用事务。此时系统不会通过 StackSaga Trace Window 发出警告,因为底层资源暂时不可用属于框架允许并处理的正常瞬态故障。
终止事务 (Terminated Transactions)
如果事务在回滚补偿 (Revert) 过程中失败,该事务将被标记为*终止事务 (Terminated Transactions)*。因为在 StackSaga 中,回滚补偿流程除抛出 RetryableException (可重试异常) 之外不允许失败。
终止事务与禁用事务的区别在于:终止事务会在未耗尽生命周期时就立即停止重试。
如何恢复禁用事务与终止事务?
禁用事务和终止事务的根本问题应由团队人工排查和定位。 修复缺陷并准备好重新执行这些事务后,您可以恢复事务以进行重试。
系统提供了 2 个端点用于处理禁用事务和终止事务:
-
获取禁用事务与终止事务 (Fetching the Disabled Transactions and Terminated Transactions)。
您可以通过此端点拉取禁用事务和终止事务,用于分析错误和定位问题。 -
恢复禁用事务与终止事务 (Restoring the Disabled Transactions and Terminated Transactions)。
您可以通过此端点延长事务的生命周期,从而恢复并重新触发这些事务的重试。
| 不同实现方式 (Implementation) 下的端点可能有所不同,请根据您所使用的具体实现查找对应的端点。 |