Paxos 实现一致性的基础是每一次 touch 集群时, 都要走所谓的 2 phases. 这个 2 phases 是一个实实在在的写操作, 并没有所谓的只读. 对于 Paxos, 它并不关心是读操作还是写操作, 它要做的, 就是将数据复制到多个节点上. 有一种特殊情况下的优化, 那就是 Paxos proposer 获得了集群的全部副本, 并且知道全部副本都是完全相同的, 就不需要执行 phase 2.
在某些情况下, 并不能这样优化, 因为等待全部节点返回结果, 可能消耗很长的时间. 而且, 某个节点故障时, 显然不应该等也等不到全部结果. 所以, 简单的做法是无论怎样, 都从多数派里获取最新的值, 然后要求所有节点都接受这个值(phase 2).
所以, 对于 Paxos 来说, 要么全部节点都查询一遍(并且期望全部节点是完全相同的), 要么产生一个新的共识, 否则不能返回结果给客户端.
这增加了系统的故障机率. 工程实践上极度厌恶"不可用", 追求的是"高可用", 所以, 很少人这样做, 也就是人们选择了 CAP 中的 AP.
例如, 系统可以只读取唯一个节点, 返回该节点的数据即可. 这样做有工程上的价值. 如果某个节点故障, 换另一个节点继续尝试(例如一致性 hash).
更进一步(更接近强一致性, 但仍然不是)的做法, 可以读取多数节点, 但不要求多数节点达成共识, 只返回多数节点之中最新版本的数据.
这些对于读操作的优化, 都只能达到数据本身的"最终一致性", 从用户的观察角度看, 系统的状态在某个时间段是飘忽不定的(不一致). 无论你是否愿意承认, 只要你违反了 3 项原则中的任何一项, 你的系统就不是强一致性的. 不管你借鉴了 Paxos 还是 Raft. Raft 比较特殊一点, 把 Leader 当作存储共识的单点, 只需要查询 Leader 就能知道是否达成共识, 写或者读.
强一致性既然那么难, 所以工程不太可能会去实现强一致性的系统. 正如前面所说, 大部分都对读操作进行了简化(优化). 那么, 写操作还是强一致的吗? 答案是否定的. 一旦读不是强一致性的, 写便不再是强一致性的. 一致性是对整个系统而言.
那么, 很多借鉴了 Paxos 或者 Raft 的写流程的系统, 它们在做什么? 其实, 大部分只是利用了多数派顺序写特性. 多数派(多副本)是为了达到高可靠性, 顺序写是为了最终一致. 例如 Paxos 的 prepare 阶段就像是在申请全局递增的序号同时拷贝多副本, 而 accept 阶段是拷贝多副本.
是的, 大部分系统最终实现的是高可靠, 高可用, 和最终一致性.