比较Paxos和Raft
发布日期:2021-06-29 11:22:45 浏览次数:2 分类:技术文章

本文共 3325 字,大约阅读时间需要 11 分钟。

比较Paxos和Raft

前言

我一直在讲学习paxos和raft最好的方式就是看作者的论文和视频,只有看原滋原味的文章,才能真正体会到作者的设计思路,才能在现代繁杂的工程实践中抓住精髓。

这篇文章作为系列文章的最后一篇,我想谈谈paxos与raft的异同。算法在变,但是其中的核心思想是不变的。

paxos共识协议是后面所有一致性协议的基础,据说Google Chubby的作者Mike Burrows说过:这个世界上只有一种一致性算法,那就是Paxos,其它的算法都是残次品。raft协议在paxos协议的基础上,抽象出了一个共识协议需要做的以下几个事情:

  • Leader选举(Leader Election)
  • 日志同步(Log Replication)
  • Commit Index推进(Advance Commit Index)
  • 崩溃恢复(Crash Recovery)
  • 成员变更(Membership Change)

当然每个共识协议不需要都实现这里面每个方面,但其设计思路上一定是考虑过这些问题,然后根据实际需求做出取舍的。下面我们一个个来说,顺便比较paxos/raft的异同。我们这里讲的paxos主要是multi-paxos with leader这种工程上比较常见的版本。basic paxos这种只对单个决议作出共识的算法和raft可比性不大。

leader选举

既然Multi-Paxos支持多个proposer,为什么很多分布式协议仍需要leader?我觉得这是出于对简化一致性协议。降低log replication的复杂度的角度考虑的。有了leader之后,日志的提交只可以由leader发起。简化了acceptor的处理流程。另外一个好处是,单leader对事务比较友好。各种隔离级别实现起来也比较方便。

当然,引入leader必然要在一致性协议中引入leader选举过程,一个集群怎样保证只有一个leader?切主的时候集群的可用性如何?一致性如何保证?都是设计者需要考虑的问题。

  • multi-paxos理论上任何成员都可以成为leader,但是leader当选时要补齐自己本地缺失的日志,还要将自己已经chosen的日志在集群内进行广播,这个过程可以叫做日志的“重确认”
  • raft由于增加了对日志的顺序性保证,并且leader只允许commit当前term的日志,因此raft集群的leader选举遵循最大提交原则: During elections, choose candidate with log most likely to contain all committed entries。只有包含最全日志的节点才可能被选为leader。

日志同步

log replication是一致性协议的核心。paxos和raft最大的区别也在这里

  • multi-paxos允许日志乱序提交,也就是说允许日志中存在空洞。
  • raft协议增加了日志顺序性的保证,每个节点只能顺序的commit日志。顺序性日志简化了一致性协议复杂程度,当然在性能上也有了更多的限制,为此,工程上又有了很多对应的优化,如:batch、pipline、leader stickness等等。有关pipline的优化可以看
  • 另外再多说一句,乱序不乱序的问题其实还是上层业务决定、体现到RSM中的。如果两条日志有关联性,就算用paxos协议,业务还是要想办法保证这种顺序。如果使用raft希望乱序提交,大可使用多个raft组。

还有一个隐含的区别:raft/paxos如何对待读请求?处理读请求的关键是,leader怎样才能知道某条log已经在整个集群内达成共识了(paxos叫chosen,raft叫commit)

  • multi-paxos的leader维护了每一条LogEntry是否被chosen的信息。但是对未chosen的LogEntry,leader必须重新走paxos流程,才能知道它是否已经被chosen了(“薛定谔的提案”),当然,leader可以在当选时对这些LogEntry执行propose,迅速掌握这些日志的状态。
  • raft协议有顺序性保证,leader通过在后续提交的过程中对其他节点间接提交,可以将集群的状态迅速补齐。但是leader刚当选的时候,它是不清楚自己的日志是否真的commit了。这时我们可以通过引入no-op,让新当选的leader迅速获取系统的CommitIndex,方便提供读服务。

当日志过多之后,需要对已经确认提交的日志做快照(snapshot),将日志压缩、持久化。后续新上线的节点也可以通过读取快照的方式迅速追齐日志。

Commit Index推进

一条写请求到达paxos/raft集群之后,到底存放在哪里,这是CommitIndex想要解决的问题

  • raft得益于其顺序日志的设计,CommitIndex的推进比较简洁。leader不会持久化CommitIndex,而是在当选后与follower交互的过程中获取。leader只能推进commit index来提交当前term的已经复制到大多数服务器上的日志,旧term日志的提交要等到提交当前term的日志来间接提交
  • Multi-Paxos由于可以乱序提交,index的推进稍显麻烦。leader在收到client的写日志请求后,首先要找到空闲的可以提交日志的entry。最简单的方法是可以LogIndex从低到高尝试应用paxos去propose,找到一个没有任何value的LogEntry,去放置新的LogEntry。

崩溃恢复

  • raft比较巧妙的一点是,通过对日志的顺序性保证,将crash recovery做到了log replication里面。也就是之前提到的:leader只能推进commit index来提交当前term的已经复制到大多数服务器上的日志,旧term日志的提交要等到提交当前term的日志来间接提交。leader在这个过程中会覆盖于follower不一致的LogEntry。
  • Multi-Paxos的崩溃恢复要复杂的多:
    1. 新当选的leader要完整的对所有日志走一遍prepare流程。从而:
      1. Block old proposals:这里我们需要将ProposalId的影响范围扩展到整个log上,而不只是某个LogEntry。通过这样做,只要当前leader发送了proposal,自然就可排除掉之前的proposal
      2. Find out about (possibly) chosen values:不同的acceptor上可能会有不同的acceptedProposal,所以,acceptor需要回复[highestProposalIdForCurrentEntry, noMoreAccepted],通过这种方式,辨别每一个acceptor到底有哪些LogEntry,以及其状态是什么。通过这种方式,leader可以将已经chosen的LogEntry在整个集群中对齐
    2. 对于之前term的日志,leader对自己已经chosen但是其他节点未chosen的LogEntry,对该节点发送Success RPC。leader通过这种方式告诉acceptor,某个LogEntry已经被Chosen了。
    3. 注意,通过success这种方式,只能将chosen的LogEntry在整个集群中对齐,推动整个集群的Log index。但是对于未被chosen的之前的LogEntry,处理的不好会导致所谓的幽灵复现的问题,最简单地解决方式:Leader直接把之前未提交LogEntry全部提交,这也是raft使用的方法。

成员变更

成员变更的论证在这里不再详述,raft和paxos以及所有的工程实现都明白:成员变更需要特殊对待,以防在变更过程中集群同时出现两个多数派

很多的工程实现将成员变更作为一条日志,apply进集群节点的RSM。并且为了简单,很多工程每次只允许一个节点变更,上一次变更成功之后才允许下一个变更。

转载地址:https://blog.csdn.net/zxpoiu/article/details/115524567 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:分布式键值系统Amazon Dynamo简介
下一篇:zookeeper客户端库curator分析

发表评论

最新留言

路过,博主的博客真漂亮。。
[***.116.15.85]2024年04月24日 00时32分16秒