2020-03-06

Paxos 与分布式强一致性

Views: 1486 | Add Comments

Paxos 有着"难以理解"的恶劣名声, 事实确实如此. 它用大段内容来做证明, 却对现实问题缺少涉及. 例如最简单(常见)的问题: 怎么实现一致性读功能?

因为 Paxos 太难以理解, 所以无数人用各自不同的角度去理解 Paxos, 而且实现上千差万别和漏洞百出. 我觉得这种现象和一句名言类似: 不幸的家庭各有各的不幸. 但是, 学习 Raft 的人都很开心, 而且大家在实现(implement) Raft 协议时, 代码基本是一样的. 正如名言所说: 幸福的家庭都是相似的.

我也用我自己的角度去理解 Paxos, 我认为它最大的特点是不区别读和写. 简单说, Paxos 的读过程就是写操作, 就是在做数据同步.

首先, 无论使用何种一致性协议, 都无法解决在任何时刻副本数据不一致的问题. 也就是说, 一定存在某个时间点, 多个副本不一致. 这是无法解决的. 要解决的是读一致性, 即从观察者(用户)的角度看, 数据是一致的. 写操作永远都是"最终一致"的.

其次, Paxos "暗示"在读操作的时候进行数据同步, 以修复数据达到"最终一致性". 而工程上的常识是, 多个副本必须自动地最终一致, 而不依赖外界触发. 如果某个系统依赖 Paxos 的这种"暗示", 那么这个系统将是不可靠的, 也不是一致性的.

补充一点: Paxos 充满了各种工程上的错误暗示, 如果按它的暗示来, 你会犯各种错误. 例如, Paxos 暗示同步的是一个(唯一的一个) key 和对应的全量值, 工程上你不可能每一次都同步整个全量数据库, 对吧? 不要提 multi-paxos, 因为你会被它错误地暗示而独立地处理每一个 key, 然后你实现不了数据 Catch Up, 无法实现副本的最终一致性.

对于读操作, Paxos 要求收到读请求的节点(Proposer)向其它节点发起数据同步操作, 也即所谓的 prepare-accept 两阶段. 在 prepare 的过程中, Proposer 的数据可能得到更新. Prepare 阶段除了同步数据, 还有一个作用是更新版本号(num), 之后就是 Proposer 将其所知的最新的数据同步到其它节点. 注意, 两阶段中间如果有失败, 不做回滚, 而是继续重试, 等最终成功, 或者放弃并告知用户.

再次注意, Paxos 并不是按数据版本号来返回最新的数据, 这种想法是错误的. 也不是所谓的多数派读(Quorum Read). 仅仅 Quorum Read 并不能保证强一致性, 因为某个节点可能随时上下线, 导致一会是多数派一会是少数派, 这种结果不是一致性的. 提一句, 这是个经典问题, Raft 通过不 commit 前任的 log 来解决这个问题.

总结:

1. Paxos 读即是写, 读即是数据同步.
2. Paxos 既不依赖版本号返回最新的数据, 也不是所谓的多数派读.

补充: Paxos 与薛定谔的猫

因为副本的不一致性是无法避免的, 所以 Paxos 在执行 accept 操作时, 可能只写了一个副本, 那么对于用户来说, 它期望的数据到底有没有写入成功呢? 答案是薛定谔的猫. Paxos 无法告诉你有没有写入成功, 可能成功了也可能没成功, 通过观察是不能下结论的, 你必须去读它, 读了之后才能知道有没有成功. 因为 Paxos 读即是写的特性, 处理读请求时, 如果同步到了之前写入的数据就是成功了, 如果同步的不是之前写入的数据就是(用户的前一个操作)失败, 它会确保不会再次同步用户前一个操作写入的那个唯一的副本.

Related posts:

  1. 为什么 Leader Based 的分布式协议 Raft 是更好的
  2. “一致性”是镜花水月
  3. Raft 选主优化之 PreVote
  4. 关于分布式存储的上帝视角和观察者视角
  5. 在Linux进行IO的正确姿势
Posted by ideawu at 2020-03-06 13:57:02 Tags: ,

Leave a Comment