Skip to content

Spinnaker:用 Paxos 建数据存储 ​

标签
分布式/共识算法
字数
2711 字
阅读时间
11 分钟

这是一篇来自生产的工程总结,价值不在协议本身(用的是 Paxos),而在它回答了那个反复被问的问题:把 Paxos 真的用进数据存储,代价有多大? 答案有具体数字:相对最终一致的数据存储,读一样快甚至更快,写只慢 5% 到 10%,节点恢复耗时不到半秒。

Spinnaker 是 IBM 与 LinkedIn 合作的实验性 datastore,设计目标是单个数据中心内的大规模商品服务器集群。它的三个特性是:基于 key 的范围分区、3 路复制、带事务的 get-put API,读上可选强一致或 timeline 一致(后者允许返回可能过期的数据以换性能)。

先看它在 CAP 里选了哪一格 ​

定位说得很直白:在 CAP 术语里 Spinnaker 是一个 CA 系统。

这个选择的依据来自 Stonebraker 的论证:在单个数据中心内、网络分区罕见时,为强一致与可用性而设计是更好的取舍,也就是选 CA。 跨数据中心的容错交给另一套机制(推测是异步复制),于是单集群内的 Paxos 只需要面对崩溃故障。

对照一下隔壁那条路线:Dynamo 那类最终一致的存储走的是 AP —— 故障、分区、冲突写会让副本分叉,应用必须自己做冲突检测与解决,而且不支持 ACID 的隔离保证。态度是:少数有极端可用性要求的应用能忍受最终一致的这些细节,但多数应用会希望有更强的一致性保证和一定的事务支持。

为什么不用 master-slave:一段具体的失效时序 ​

一条四步失效序列,说明只挂一个节点就能让数据库对读写都不可用。

先看前提。传统 2 路同步复制里:一个节点是主,所有写都路由到它;主的日志发给从;主只有在从先把提交记录落盘之后,才强制把提交记录落盘。这个设计下:

  • 从挂了 → 主继续跑,不影响;
  • 主挂了 → 从有最新状态,可以接管。

看起来能容忍一个节点。但失效的先后顺序能把它破掉:

步骤状态
(a)两节点都从 LSN = 10 开始
(b)从挂了
(c)主继续接受写一直到 LSN = 20,然后主也挂了
(d)从先恢复,主仍挂着

问题出在 (d):此时从既不能接受读也不能接受写,因为它没有最新的数据库状态。而且如果主是永久失效,LSN 11 到 20 的已提交写就丢了 —— 这些是客户端已经收到成功响应的写。

避免这个问题的唯一办法是"主从对里任一节点挂掉就阻塞写",但这种可用性限制对某些应用不可接受。

顺带解释两件事:

  • 为什么"这种例子概率极低"不足以反驳:在大集群里,双盘故障也变得更容易发生;2 路复制且没有 RAID 或特殊硬件时,这会导致灾难性数据丢失。所以3 路复制在商品服务器上成了常见选择。
  • 3 路复制的额外好处:单个节点失效不再进入 "panic mode"(再挂一个就丢数据),在线升级也变容易 —— 可以摘掉一个副本升级,另两个保持在线。

但 3 个副本会带来比上面那条更复杂的失效序列。 结论是:朴素解法在简单情形下能工作,但没有被证明在一般情况下正确;Paxos 家族被广泛认为是"有 3 个或更多副本"时唯一有证明的解法,它在 2F+1 个副本上达成共识并容忍 F 个失效。

于是就有了那个"为什么以前没人用"的问题:

Paxos 一直没被用于数据库复制,因为它通常被认为太复杂、太慢。

Spinnaker 就是来拆这个假设的。

四步序列画成时间线(从下往上依次推进):

前提:2 路同步复制 —— 主必须先等从把提交记录落盘,才能强制自己落盘

(a) 两个节点都从 LSN = 10 开始
        主  ──── 接受写 ────▶
        从  ──── 同步落盘 ──▶

(b) 从挂了
        从  ──── 停止 ────
        主  ──── 继续接受写 ──────────────────────▶ 一直走到 LSN = 20

(c) 主也挂了(此时 LSN = 20)
        主  ──── 停止 ────        LSN 11–20 是客户端已经收到成功响应的写

(d) 从先恢复,主仍挂着
        从  活着,但缺 LSN 11–20 的状态
        ⇒ 它既不能接受读、也不能接受写:只挂一个节点,数据库对读写都不可用
        ⇒ 若主是永久失效,LSN 11–20 的已提交写随之丢失

为什么不用 2PC ​

2PC 也被提出来过做副本一致性,但有三条否决理由:

  1. 单个节点失效就会导致 abort —— 与"节点失效时保持系统可用"这个目标直接冲突;
  2. 性能差 —— 典型实现要 2 次磁盘强制落盘(disk force)+ 2 个消息延迟;
  3. 协调者失效时会阻塞。

非阻塞的三阶段提交也被提出来过,但因为性能差在实践中很少使用。

关键区别在于定位:2PC 把每个参与者当作独立的资源管理器(各有自己的数据库),而副本场景里它们只是同一份数据的拷贝 —— 判断是对复制来说 2PC 是杀鸡用牛刀(overkill),而且还要额外承受上面三条代价。

Spinnaker 的 Paxos 用法 ​

对 Paxos 的使用与"教科书用法"相比有三个不同点,这正是它声称"Paxos 可以更简单"的依据:

  • 与提交日志集成:Paxos 复制协议与 Spinnaker 的 commit log 和恢复流程整合在一起,而不是叠在数据库外面。这是它能把开销压下来的主要原因 —— 复制不额外多写一遍日志。
  • 使用分布式协调服务:"使用一个分布式协调服务"被列为让 Paxos 实现变简单的设计选择之一。Paxos 方案本身假定"提案号不重复"由实现保证、leader 选举由外部提供,这些在工程上都是要落地的活;交给一个现成的协调服务(例如 Chubby / ZooKeeper 那一类,见 04-Zab 与 ZooKeeper)就省掉了自研选举与成员管理。
  • 可用性的判据:只要持有某个数据分区副本的节点多数存活,该分区就可读写。

最后这条是 Paxos 相对 master-slave 的真正收益,一句话点明:

与传统主从复制不同,这一点与故障发生的顺序无关。

这正好回应了上面那条四步失效序列 —— 主从复制的可用性依赖于"先挂谁、后挂谁",Paxos 的可用性只依赖"多数副本是否活着"。

可用性的判据对照:

Spinnaker:可用性只看「多数副本是否存活」,与失效顺序无关

   分区 P 的 3 个副本: [A]  [B]  [C]
        挂掉 [A]        ⇒ 还剩 B、C 两个 ⇒ 多数存活 ⇒ 该分区仍可读写
        再挂 [B]        ⇒ 只剩 C ⇒ 不是多数 ⇒ 该分区不可读写(此时本就无从恢复)

   谁先挂、谁后挂都不改变这条判据。

master-slave:可用性取决于失效顺序 —— 上面那条四步序列里,
              只要「从先恢复、主后恢复」这一个顺序出现,整库就卡死。

恢复成本的一个具体来源:日志恢复靠一个周期性的异步 commit 消息,
提交周期取 1 秒左右时,节点恢复时间落在半秒以内。

判据速查 ​

问题答案
为什么不能只用 2 路主从复制存在只挂一个节点就让数据库对读写都不可用的失效顺序(见那四步时序);主永久失效还会丢掉已提交的写
为什么用 3 路复制大集群里双盘故障更可能发生,2 路复制的数据丢失是灾难性的;3 路还让在线升级可以用"摘一个副本"的方式做
Paxos 相对 master-slave 的核心收益可用性只依赖"多数副本存活",与故障发生的顺序无关
2PC 为什么不适合做复制单节点失效就 abort;每事务 2 次 disk force + 2 个消息延迟;协调者失效阻塞
Spinnaker 在 CAP 里是哪一格CA —— 单数据中心、分区罕见,按 Stonebraker 的论证选强一致 + 可用
跨数据中心怎么办假定用另一套(异步)复制,不在本篇范围内
Paxos 复制的实测代价相对最终一致的存储:读持平甚至更快,写慢 5%–10%;节点恢复 < 0.5 秒
让 Paxos 实现变简单的两个设计选择与提交日志集成(不额外写日志)+ 用分布式协调服务接管选举与成员管理
读的一致性可以选择吗可以 —— 强一致或 timeline 一致(允许返回过期数据换性能)

相关 ​

  • Paxos —— 本篇用的是它的 Multi-Paxos 形态(一串实例对应提交日志)
  • 01-分布式事务 —— 本篇「为什么不用 2PC」一节与那篇的 2PC/Paxos Commit 是同一个话题的两个角度:那里用 Paxos 替换 2PC 的 TM,这里直接用 Paxos 做复制而绕开 2PC
  • 04-Zab 与 ZooKeeper —— 前面提到的"分布式协调服务"在这一类系统里通常由谁提供
  • 01-CAP 定理 —— 「单数据中心选 CA」这条判断的出处与边界

参考 ​

  • Jun Rao, Eugene J. Shekita, Sandeep Tata. Using Paxos to Build a Scalable, Consistent, and Highly Available Datastore. Proceedings of the VLDB Endowment 4(4), 2011, pp. 243–254.

贡献者 ​

文件历史 ​