严格可串行化需要一个全局顺序。FoundationDB 把这个顺序钉在 版本号 上:事务开始时拿到 read version,提交时拿到更大的 commit version;commit version 既是序列化顺序,也是日志序号(LSN)语义的来源(Zhou et al., SIGMOD 2021 §2.4.2)。发放与推进这些版本的权威,就是 Sequencer(7.x 文档常称 Master)。
常见误解是:「有单点 Sequencer = 没有高可用」。更准确的说法是:每一世代只有一个活跃 Sequencer,失败则整代事务系统恢复并由新 Sequencer 接管——可用性靠短 MTTR,而不是靠多活同时发号。
本文是系列第 6 篇(第一部分收官):版本分配规则、与 Proxy 的协作、逻辑单点含义、恢复时版本与客户端如何衔接。写流水线细节见第 7 篇;严格可串行化证明见第 9 篇;完整恢复状态机见第 13 篇。
本文是「FoundationDB 内核」系列第 6 篇(共 18 篇)。→ 系列目录
篇目 核心内容 第 5 篇 · 角色拓扑 读写总图 第 6 篇 · Sequencer 版本分配、单点、恢复 第 7 篇 · 写事务流水线 Client → TLog 端到端
版本锚定:Zhou et al., SIGMOD 2021 §2.3.2、§2.3.5、§2.4.1–2.4.4;FoundationDB 7.x Architecture(Master、Determine Read Version、Transaction Commit、Transaction System Recovery)。下文统一称 Sequencer,括注文档名 Master。
一、两个版本:读快照与提交序号
论文 §2.4.1–2.4.2:
| 版本 | 谁申请 | 不变量(论文表述) | 用途 |
|---|---|---|---|
| read version | 客户端经 Proxy(现 Grv Proxy)向 Sequencer | 不低于启动时已知的已提交版本 | MVCC 读快照;只读事务的序列点 |
| commit version | Commit Proxy 向 Sequencer | 大于任何现有 read / commit version | 序列化顺序 / LSN;Resolver 与 Log 按此排序 |
Sequencer 选择 commit version 时以约 每秒一百万 的速率推进版本空间(论文 §2.4.1)。这是版本计数设计,便于在物理时钟抖动下仍有足够序号粒度;不是「集群每秒恰好一百万笔事务」的吞吐断言。
为避免 LSN 空洞,Sequencer 在返回 commit version 时附带 previous commit version;Proxy 把二者传给 Resolver 与 Log Server,使之按序处理(论文 §2.4.2)。
flowchart LR
rv["read version R"]
cv["commit version C"]
rv -->|"R <= committed at start"| snap["MVCC snapshot"]
cv -->|"C > all prior R and C"| order["Serialization order / LSN"]
snap --> txn["Transaction body"]
txn --> order
1.1 严格可串行化的直觉(预告)
因为事务在 \(R\) 上读到「当时已提交」的结果,又在更大的 \(C\) 上提交,且冲突检测按 \(C\) 排序拒绝读写冲突,论文称由此得到 strict serializability(§2.4.2)。完整论证、与实时顺序、5 秒窗口的关系见第 9 篇;本篇只固定版本权威在 Sequencer。
二、与 Proxy 的协作:Sequencer 不面向海量客户端
2.1 读版本路径
7.x 文档:Grv Proxy 向 Master 取最大已提交版本,并检查日志存活,再返回给客户端(第 4 篇)。Master 作为「各 Commit Proxy 已提交版本的汇聚点」,避免客户端直连单点造成连接爆炸。
2.2 提交版本路径
Commit Proxy 为每批事务向 Sequencer 申请 commit version;成功 durable 后把 committed version 汇报回去,推动后续 read version 前进。空提交用于客户端安静时仍推进版本(第 4 篇)。
sequenceDiagram
participant Grv as Grv Proxy
participant CP as Commit Proxy
participant Seq as Sequencer
participant Client
Client->>Grv: get read version
Grv->>Seq: latest committed
Seq-->>Grv: R
Grv-->>Client: R
Client->>CP: commit
CP->>Seq: next commit version
Seq-->>CP: C, prev
Note over CP: Resolver + TLog ...
CP->>Seq: report committed C
2.3 历史职责:招募写子系统
论文 §2.3.2:因历史原因,Sequencer 还招募 Proxies、Resolvers、LogServers。官方文档中 Master 协调写子系统换代。对排障的含义是:Sequencer 进程退出往往不是「只丢一个发号器」,而是一代写管线作废(第 3、13 篇)。
三、逻辑单点 ≠ 无高可用
| 说法 | 更准确的机制 |
|---|---|
| 「Sequencer 单点故障集群永久不可用」 | 失败后 Cluster Controller 招募新 Sequencer,走 reconfiguration;论文目标是短 MTTR |
| 「多 Sequencer 同时发号做 HA」 | 同一世代只允许一个权威;双活发号会破坏版本不变量 |
| 「Sequencer 存着全部事务状态」 | 版本与世代协调为主;Proxy/Resolver 无用户持久状态,耐久在 Log |
论文设计原则 Make failure a common case(§2.1):与其在写路径上处理每一种局部故障,不如关掉当前世代、走统一恢复。官方 Architecture:Master 监控 Grv/Commit Proxy、Resolver、Transaction Log;任一失败则 Master 退出,Controller 招募新 Master,开启新 epoch。
stateDiagram-v2
[*] --> EpochN: Sequencer N active
EpochN --> Detect: failure in write subsystem
Detect --> Suicide: Sequencer exits
Suicide --> Recruit: Cluster Controller recruits
Recruit --> Recover: new Sequencer recovery
Recover --> EpochN1: Sequencer N+1 accepts commits
3.1 恢复与版本、客户端
论文 §2.4.4 与官方恢复节要点(细节第 13 篇):
- 新 Sequencer 从 Coordinators 读出并锁定上一代配置,防止并发恢复。
- 停止旧 Log 接受新事务,招募新 Proxy / Resolver / Log。
- 确定 Recovery Version(RV),丢弃 RV 之后的半提交状态;Storage 后台继续拉日志,恢复不依赖 ARIES 式整库 redo。
- 官方补充:恢复可 快进约 90
秒,令进行中事务
transaction_too_old,重试时附着新世代。 - 提交中遇到恢复的客户端可能看到 commit 结果未知,必须幂等重试(第 2 篇)。
版本租约 / 窗口与 5 秒 MVCC 的关系:Storage 与 Resolver 只保留短窗口历史;恢复快进进一步把旧读版本作废。应用侧表现仍是「短事务 + 可重试」,而不是跨分钟的快照会话。
四、具体故事:两个提交如何排成全序
事务 \(T_1\)、\(T_2\) 并发:
- 两者经 Grv Proxy 取得 read version \(R_1\)、\(R_2\)(可能相同或相近)。
- \(T_1\) 先进入 Commit Proxy,获得 commit version \(C_1\),Resolver 通过,TLog durable,Proxy 向 Sequencer 汇报。
- \(T_2\) 稍后获 \(C_2 > C_1\)。若 \(T_2\) 的读集与 \(T_1\) 的写集在 \((R_2, C_2)\) 上冲突,Resolver 拒绝 \(T_2\)。
- 若无冲突,\(T_2\) 按 \(C_2\) 排在序列化历史中 \(T_1\) 之后。
Sequencer 不检查 key 冲突——它只保证版本单调与全序编号;冲突是 Resolver 的职责(第 8 篇)。把 Sequencer 理解成「全局锁管理器」会误导优化方向:降低冲突要靠数据模型与减少读冲突范围,而不是「加更多 Sequencer」。
flowchart TB
subgraph seqDuty ["Sequencer duty"]
mono["Monotonic versions"]
prev["Prev LSN chaining"]
epoch["Epoch recruitment"]
end
subgraph notDuty ["Not Sequencer duty"]
keys["Per-key locking"]
sql["SQL planning"]
store["Serving user reads"]
end
常见误解
- 「逻辑单点就是单机玩具」。生产系统用快速检测 + 重配换取 \(f+1\) 复制等资源优势(论文 Abstract);是否适合你的故障域,是部署问题。
- 「read version 由每台 Storage 本地分配」。版本权威在 Sequencer;Storage 只提供给定版本上的数据。
- 「commit version 等于墙上时钟」。版本按策略推进,与实时顺序的关系在严格可串行化定义下讨论(第 9 篇),不要当成 NTP 时间戳。
工程间隙
论文评价恢复在生产上通常较短(§5.3)。真实尾部取决于失败检测灵敏度、日志封印、RV 计算与客户端重试风暴。多租户高冲突时,恢复后的重试可能叠加成负载尖峰——Ratekeeper 与应用退避需要一起设计(第 16–17 篇)。形式化验证与确定性模拟如何覆盖「发号 + 恢复」交错,见第 14 篇与 distributed/54。
五、版本、租约与 5 秒窗口如何咬合
「版本租约」在运维口语里有时被说成 Sequencer 对时间的控制;机制上更精确的拆法是三条互相约束的规则:
- 序号规则:commit version 单调,read version 不低于已知提交(Sequencer + Proxy 汇报)。
- 窗口规则:Storage / Resolver 只保留约 5 秒的多版本与冲突历史(官方 Architecture;Known Limitations)。
- 世代规则:恢复快进使旧 read version 迅速过期,客户端必须重挂新世代。
因此应用看到的「事务不能太长」,不是任意超时参数,而是 MVCC 状态有界 的直接推论——有界状态使恢复与内存占用可预期,却禁止长事务(论文与 Anti-Features 立场一致)。第 9 篇会把这三条收进严格可串行化论述。
flowchart TB
seq["Sequencer versions"]
win["5s MVCC / conflict window"]
epoch["Recovery epoch fast-forward"]
seq --> client["Client transactions"]
win --> client
epoch --> client
client --> retry["Short, idempotent retries"]
5.1 与 PD TSO 的一句对照
TiKV 生态里 PD TSO 也是全局时间权威(见 tikv-htap 第 9 篇规划)。相似点:都避免「每个存储节点自己发时间戳」。差异点:FDB 的 Sequencer 与写子系统世代、日志封印绑定;TSO 与 Multi-Raft 提交路径的耦合方式不同。不要假设调 PD 的经验能直接翻译成调 Sequencer。
5.2 第一部分收束:01–06 各自钉住什么
| 篇 | 读者应带走的一句 |
|---|---|
| 01 | Unbundled 把 TS/LS/SS 拆开,事务穿多跳角色 |
| 02 | 客户端契约是冲突范围 + 重试 + 5 秒窗 |
| 03 | Coordinators 共识的是配置与世代,不是每个 mutation |
| 04 | Grv 发安全 read version;Commit 编排提交三步 |
| 05 | 读扩 Storage,写扩 Proxy/Resolver/Log;单例靠恢复 |
| 06 | Sequencer 发全序版本;逻辑单点通过换代保可用 |
下一篇开始第二部分,把 Commit Proxy 之后直到 TLog 的路径写成可逐步对照源码/文档的流水线。
flowchart LR
p1["01-06 roles"] --> p2["07-09 txn SSI"]
p2 --> p3["10-12 log storage"]
p3 --> p4["13-18 ops layer"]
5.3 开放问题(版本权威)
全局单点发号简化了严格可串行化论证,却把「发号器世代切换」变成集群可用性的敏感点。是否存在可水平扩展、又保持同一严格可串行化契约的版本服务,仍是分布式事务系统里反复出现的设计张力——FDB 的回答是 Proxy 批处理 + 快速恢复,而不是多活 Sequencer。第 9、18 篇会在一致性与选型语境下再碰这个问题。
对应用开发者,开放问题落地成一条纪律就够:不要把跨秒级的业务工作流塞进一个
FDB
事务;把长流程拆成多个短事务,并用业务幂等键消化
commit_unknown_result。核心用有界窗口换可恢复性,Layer
与应用补齐长流程。
六、小结
三句话小结
- Sequencer 为事务分配 read version 与严格更大的 commit version(附 previous LSN),定义序列化顺序;冲突检测不在 Sequencer 上做。
- 每一世代一个活跃 Sequencer 是逻辑单点;高可用靠失败后整代恢复与短 MTTR,而不是多活同时发号。
- 客户端经 Proxy 间接使用 Sequencer;恢复会导致旧版本作废或提交结果未知,应用必须短事务且幂等。
第一部分(01–06)到此收束。下一篇把提交路径一次性串完:写事务流水线。
参考资料
核心论文
- Zhou et al., SIGMOD 2021, §2.1、§2.3.2、§2.3.5、§2.4.1–2.4.4(原则、Sequencer、版本、恢复)。
规范 / 文档
- FoundationDB Architecture — Master、Determine Read Version、Transaction Commit、Transaction System Recovery(7.x)。
站内
- 第 4 篇 Proxy、第 5 篇角色拓扑。
- distributed/54 确定性模拟(第 14 篇前置)。
- 本系列 index。
上一篇:角色拓扑与数据流
下一篇:写事务流水线
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【FoundationDB 内核】严格可串行化与 5 秒限制:实时顺序与恢复有界性
说明 FoundationDB 如何用 Sequencer 版本把 OCC+MVCC 提升到严格可串行化,区分 serialization order 与实时顺序;论证 5 秒事务上限如何约束冲突窗口与恢复日志量,并点明高冲突下的开放问题。
【FoundationDB 内核】故障恢复:角色招募与恢复 epoch
按 SIGMOD 2021 钉住 FoundationDB 把失败收敛到同一条恢复路径:Sequencer 锁定 Coordinator、停止旧 LogServer、招募新 Proxy/Resolver/TLog、确定 RV/PEV,并说明写暂停、读边界与旧角色隔离。
【FoundationDB 内核】Unbundled · OCC · TLog · Redwood · Simulation
补齐 FoundationDB 选型叙事与生产内核之间的一层:拆解 Proxy、Sequencer、Resolver、TLog、Storage Server 的 Unbundled 写路径,严格可串行化、Redwood 与确定性模拟,并以 Record Layer 和 TiKV/etcd 对照收束。
【FoundationDB 内核】Unbundled 全景:事务、日志与存储为何拆开
以 SIGMOD 2021 为锚点,厘清 FoundationDB 控制面与数据面角色分工,说明一次事务穿过 Proxy、Sequencer、Resolver、TLog、Storage 的路径,并与站内 distributed/39、tikv-htap 划清边界。