Raft 算法:分布式系统的不二之选

背景

在当今数字化时代,数据中心和应用程序的运行环境高度动态化。为了应对这种动态特性,它们采用通过增设服务器进行横向扩展的策略,并根据实际需求灵活调整规模,实现扩展与收缩。与此同时,服务器故障和网络故障时有发生,这是无法回避的现实问题。

基于此,系统在正常运行期间必须具备处理服务器上线与下线的能力。面对突发状况,系统要在短短几秒钟内自动做出反应并完成自适应调整,因为对于客户而言,任何明显的服务中断通常都是难以接受的。

幸运的是,分布式共识机制能够为应对这些挑战提供有效的解决方案。

拜占庭将军问题

在深入探讨共识算法之前,我们先引入一个简化版的拜占庭将军问题,以此来帮助理解共识算法的核心概念。

假设有多位拜占庭将军,其中不存在叛军,并且信使传递的信息真实可靠,但存在被暗杀的风险。在这种情况下,将军们如何就是否发动进攻达成一致的决策呢?

其解决方案大致可以这样理解:首先从所有将军中推选一位大将军,由这位大将军负责做出所有关键决策。

例如,假设有三位将军A、B和C。每位将军都配有一个倒计时器,倒计时时间随机。当某个将军的倒计时结束时,他便将自己视为大将军候选人,并派遣信使向将军B和C传递选举投票信息。如果将军B和C此时还未将自己当作候选人(即自己的倒计时尚未结束),且没有把选举票投给其他人,那么他们就会把票投给将军A。当信使返回将军A处时,将军A便能知晓自己是否收到了足够的票数,从而确定自己是否成为大将军。一旦确定了大将军,例如将军A成为大将军,那么是否发动进攻就由大将军A决定。之后,大将军A会派信使通知另外两位将军自己已成为大将军。如果在一段时间内没有收到将军B和C的回复(因为信使可能被暗杀),则会重新派遣信使,直至收到回复为止。

共识算法

共识算法

在可容错系统中,共识是一个基础且关键的问题,其核心在于:即便遭遇故障,服务器之间也能就共享状态达成一致。

共识算法让一组节点协同工作,仿佛是一个整体,即便部分节点出现故障,系统依旧能够持续运转。其正确性主要得益于复制状态机的特性:一组服务器的状态机计算出相同状态的副本,就算有部分服务器宕机,它们也能继续运行。

复制状态机一般通过使用复制日志来实现。每台服务器都存储着一份包含命令序列的日志文件,状态机会依照顺序执行这些命令。由于每个日志包含的命令相同,且顺序一致,所以每个状态机处理的命令序列也相同。又因为状态机具有确定性,在处理相同状态时,会得到相同的输出。

所以,共识算法的任务就是维持复制日志的一致性。服务器上的共识模块接收来自客户端的命令,并将其添加到日志中。该模块与其他服务器上的共识模块进行通信,以确保即便部分服务器发生故障,每个日志最终都能按相同顺序记录请求。一旦命令被正确复制,就称其已提交。每台服务器的状态机按日志顺序处理已提交的命令,并将输出返回给客户端。如此一来,这些服务器就构成了一个高度可靠的单一状态机。

适用于实际系统的共识算法,通常具备以下特性:

  • 安全性:确保在非拜占庭条件(即上文提到的简易版拜占庭情形)下的安全,涵盖网络延迟、分区、数据包丢失、复制以及重新排序等情况。
  • 高可用性:只要多数服务器可正常运行,且能相互通信以及与客户端通信,那么这些服务器就可视为完全功能可用。例如,一个典型的由五台服务器组成的集群,能够容忍任意两台服务器发生故障。假设服务器因停止运行而出现故障,后续它们也可能从稳定存储的状态中恢复并重新加入集群。
  • 一致性不依赖时序:即便时钟出现错误或消息延迟极其严重,在最坏情况下也仅会导致可用性问题,而不会引发一致性问题。
  • 高效响应:只要集群中多数服务器作出响应,命令就能完成,不会因少数运行缓慢的服务器而影响整体系统性能。

Raft算法是什么?

Raft同样是一种一致性算法,与Paxos算法目标一致。不过,它还有一个别称——易于理解的一致性算法。Paxos和Raft都是为实现一致性而诞生的。Paxos算法是直接从分布式一致性问题出发推导得出;而Raft算法则是从多副本状态机的视角提出,用于管理多副本状态机的日志复制。

Raft实现了与Paxos相同的功能,它将一致性问题分解为多个子问题,包括Leader选举(Leader election)、日志同步(Log replication)、安全性(Safety)、日志压缩(Log compaction)、成员变更(Membership change)等。同时,Raft算法采用了更强的假设条件,以此减少需要考虑的状态,使得自身更易于理解和实现。

基础

理解Raft算法

节点类型

在Raft算法的架构中,一个Raft集群由若干台服务器组成,以常见的5台服务器集群为例进行说明。在任何时刻,每台服务器必然处于以下三种状态之一:

  • 领导者(Leader):承担着核心职责,负责定期发起心跳信号,以维持与集群中其他节点的连接状态;响应客户端的各类请求,确保用户操作得到及时处理;创建新的日志条目,记录系统中的关键操作;并将日志同步至集群内的其他服务器,保障数据的一致性和完整性。
  • 候选人(Candidate):这是在领导者选举过程中出现的临时角色,由跟随者(Follower)转变而来。当跟随者在一定时间内未收到领导者的心跳信号时,便会转变为候选人,主动发起投票请求,积极参与领导者的竞选。
  • 跟随者(Follower):主要工作是接收来自领导者的心跳信号,确认领导者的正常运行状态;接收并处理领导者同步过来的日志数据,保证自身数据与领导者一致;在选举期间,为符合条件的候选人投票,协助完成领导者的选举。

在系统正常运行时,集群中仅有一台服务器处于领导者状态,其余服务器均为跟随者。跟随者处于被动响应模式,自身不会主动发送请求,仅对来自领导者和候选人的请求做出相应的回应 ,以此维持集群的稳定运行。

任期

raft-任期

如图所示,Raft算法将时间切分为长度不固定的任期(term),任期以连续递增的数字进行标识,这个数字被视为当前的term号。每一段任期的起始都伴随着一次选举。在选举启动时,一个或多个Candidate(候选人)会竞相角逐,试图成为Leader(领导者)。一旦某个Candidate在选举中胜出,便会在该任期内担当起Leader的职责。倘若某次选举未能成功选出Leader,那么将会开启新的一个任期,并且马上紧锣密鼓地启动下一轮选举。Raft算法能够确保在每一个给定的任期内,至少会产生一位Leader。

集群中的每个节点都会保存当前的term号,在服务器之间进行通信交互时,会相互交换各自的当前term号。一旦有服务器察觉到自身的term号小于其他服务器,就会主动将其更新为较大的term值。倘若Candidate或者Leader发现自己所处的term已经过期,会即刻退回到Follower(跟随者)状态。当一台服务器接收到的请求所携带的term号过期时,它会果断拒绝此次请求,以此保障系统的一致性和稳定性 。

三类角色的变迁图如下:

raft-领导者-候选人-跟随者的变迁

日志

  • entry:系统运行过程中的每一个事件都会形成一个日志条目(entry),且只有领导者(Leader)具备创建日志条目的权限。日志条目包含三个关键信息:任期号(term)、索引值(index)以及操作指令(cmd),其中操作指令(cmd)是能够应用于状态机的具体操作。
  • lo):lo)是由 entry 组成的数组结构。在这个数组中,每一个日志条目都有一个唯一的索引值(index),用于表明它在日志数组中的位置。需要注意的是,只有领导者(Leader)有权修改其他节点的日志。日志条目总是先由领导者添加到自身的日志数组中,随后领导者发起共识请求,在获得其他节点的同意后,才会将该日志条目提交给状态机。而跟随者(Follower)仅能从领导者处获取新的日志以及当前的提交索引(commitIndex),然后将对应的日志条目应用到自身的状态机中 。

Raft算法子问题

Raft算法达成了与Paxos算法相同的功能,不过它采用了更为细致的处理方式,将一致性问题拆解为多个子问题:

  • 领导者选举(Leader election):决定由集群中的哪个节点担当领导者角色,负责统筹关键事务。
  • 日志同步(Log replication):保障集群内各节点的日志信息保持一致,维护数据的连贯性。
  • 安全性(Safety):确保系统在各种情况下都能稳定运行,避免出现数据错误或不一致的状况。
  • 日志压缩(Log compaction):对日志进行精简处理,减少存储空间占用,提升系统性能。
  • 成员变更(Membership change):处理集群中节点的加入与退出,确保变更过程中集群的正常运转。

领导人选举

Raft采用心跳机制触发Leader选举。服务器启动时,默认初始化为Follower状态。在正常运行期间,Leader会周期性地向所有Followers发送heartbeat信号,以此维持自身的领导地位,并确保集群内节点状态的同步。若Follower在选举超时时间内未收到Leader的heartbeat,便会等待一段随机时长后,发起一次Leader选举。

在发起选举时,Follower会将自身当前的term值加1,并转换为Candidate状态。它首先会给自己投上一票,随后向集群内其他服务器发送RequestVote RPC请求选票。选举结果存在以下三种情况:

  • 成功当选:Candidate赢得超过半数(超过1/2)的选票,成功当选为Leader,开始履行领导职责。
  • 他人当选:Candidate收到了来自其他节点的Leader消息,这表明已有其他服务器抢先当选为Leader,本次选举结束。
  • 选举失败:没有任何Candidate赢得超过半数的选票,导致本次Leader选举失败。此时,Candidate将等待选举时间超时(Election Timeout),之后再次发起下一轮选举。

在Candidate等待选票期间,可能会收到其他节点发送的声明自己是Leader的心跳信息。面对这种情况,Candidate会根据不同情形做出相应处理:

  • 接受领导:若该Leader的term号大于等于自己的term号,说明对方已成功当选为Leader,自身应回退为Follower,接受新Leader的管理。
  • 拒绝请求:若该Leader的term号小于自己的term号,Candidate会拒绝该请求,并要求对方更新term值,以确保集群内的状态一致性。

如果一台服务器能够接收到来自Leader或者Candidate的有效信息,它会始终保持Follower状态,并刷新自己的electionElapsed计时,重新开始计算选举超时时间。

Leader会持续向所有Follower周期性发送心跳,以稳固自己的领导地位。一旦某个Follower在一个周期内未收到心跳信息,即发生选举超时,它便会判定当前没有可用的Leader,进而启动新一轮选举,试图选出新的Leader。

值得注意的是,由于可能在同一时刻出现多个Candidate参与竞选,若没有合理的选票分配机制,就可能导致没有Candidate能够获得大多数选票,从而使选举陷入无限循环。

日志复制

一旦成功选出Leader,它便开始承担起接收客户端请求的职责。每一个客户端请求都包含一条指令,这条指令需要被复制状态机(Replicated State Machine)执行。

当Leader收到客户端请求后,会生成一个日志条目(entry),该条目包含索引(index)、任期号(term)以及操作指令(cmd),即 。随后,Leader将这个日志条目添加到自身日志的末尾,紧接着向集群中的所有节点广播该日志条目,要求其他服务器进行复制。

如果Follower接收该日志条目,会将其追加到自己的日志之后,同时向Leader返回同意的响应。

当Leader收到了多数节点的成功响应,便会将这个日志条目应用到自己的状态机中,此时可以判定这个日志条目已提交(committed),并且向客户端返回执行结果。

Raft算法保证了以下两个关键性质:

  • 命令一致性:在两个不同的日志中,若存在两个拥有相同索引(index)和任期号(term)的日志条目,那么它们必定存储着相同的操作指令(cmd)。
  • 日志连贯性:在两个不同的日志中,若存在两个拥有相同索引(index)和任期号(term)的日志条目,那么它们之前的所有日志条目也必定相同。

第一条特性的成因是,在同一个任期内,Leader在给定的日志索引位置最多只会创建一条日志条目,并且该条目的位置不会发生改变。

第二条特性则源于AppendEntries过程中的一个简单一致性检查。当Leader发送AppendEntries RPC时,会把新日志条目紧接着之前条目的日志索引(log index)和任期号(term)都包含在其中。如果Follower在自身日志中未能找到日志索引和任期号都相同的条目,就会拒绝接收新的日志条目。

在通常情况下,Leader和Followers的日志能够保持一致,因此AppendEntries一致性检查一般不会失败。然而,当Leader发生崩溃时,可能会导致日志不一致的情况出现,因为旧的Leader可能没有将日志中的所有条目完全复制。此时,Leader会通过强制Follower复制自己的日志来解决日志不一致问题,这也就意味着Follower上冲突的日志会被Leader的日志覆盖。

为了使Follower的日志与自身保持一致,Leader需要先找出Follower与自己日志一致的位置,然后删除Follower在该位置之后的日志,接着将之后的日志发送给Follower。

Leader为每一个Follower维护了一个nextIndex,它代表Leader将要发送给该Follower的下一条日志条目的索引。当一个Leader开始掌权时,会将nextIndex初始化为其最新日志条目索引数加1。如果Follower的日志与Leader不一致,AppendEntries一致性检查会在下一次AppendEntries RPC时返回失败。在失败后,Leader会将nextIndex递减,然后重新尝试发送AppendEntries RPC。最终,nextIndex会达到一个使Leader和Follower日志一致的位置。此时,AppendEntries会返回成功,Follower中冲突的日志条目都被移除,并且添加了所缺失的Leader的日志条目。一旦AppendEntries返回成功,Follower和Leader的日志就达成一致,这种一致状态会一直保持到该任期结束。

安全性

选举限制

Leader必须确保自身存储了所有已提交的日志条目,如此才能保证日志条目仅存在从Leader流向Follower这一个方向,Leader永远不会覆盖已有的日志条目,从而维护集群日志的一致性与完整性。

每个Candidate在发送RequestVote RPC(请求投票远程过程调用)时,都会附带自身最后一个日志条目(entry)的信息。当集群中的所有节点收到投票信息时,会对该条目的新旧程度进行比较。若节点发现自己的日志条目比Candidate的更新,便会拒绝为其投票。

判断日志新旧程度的具体方式如下:若两个日志的任期号(term)不同,任期号大的日志更新;若任期号相同,则索引值(index)更大,即日志更长的更新。

节点崩溃

倘若Leader崩溃,集群中的节点在electionTimeout(选举超时)时间内未收到Leader的心跳信息,便会触发新一轮的领导者选举。在选举期间,整个集群对外处于不可用状态,直到新的Leader被选出。

相较之下,如果是Follower或Candidate崩溃,处理方式则简单许多。之后发送给它们的RequestVote RPC和AppendEntries RPC会失败。不过,由于Raft算法中所有请求都具有幂等性,即多次执行相同请求产生的效果与一次执行相同,所以请求失败后会进行无限重试。当崩溃的节点恢复后,便能够接收新的请求,进而根据情况选择追加或拒绝日志条目(entry) 。

时间与可用性

Raft算法的关键要求之一,是其安全性不依赖于时间因素,即系统不会仅仅因为某些事件的发生速度快于或慢于预期就出现错误。为达成这一要求,理想状态下需要满足以下时间条件:

broadcastTime << electionTimeout << MTBF

  • broadcastTime:指的是向其他节点并发发送消息时的平均响应时间;
  • electionTimeout:即选举超时时间;
  • MTBF(mean time between failures):表示单台机器的平均无故障运行时间。

broadcastTime应比electionTimeout小一个数量级,这是为了确保Leader能够持续向Follower发送心跳信息(heartbeat),以此阻止Follower启动选举流程。

electionTimeout则需要比MTBF小几个数量级,目的是保障系统稳定运行。当Leader发生崩溃时,系统大约会在整个electionTimeout的时长内不可用,我们期望这种不可用时间在系统总运行时间中所占比例极小。

鉴于broadcastTime和MTBF是由系统自身属性决定的,所以在实际应用中,主要需要确定electionTimeout的时长。

通常情况下,broadcastTime一般处于0.5~20ms之间,electionTimeout可设置在10~500ms范围,而MTBF通常为一两个月。

日志压缩

在实际运行的系统里,日志不能无限制地增长。要是日志持续增长,系统重启时就需要耗费大量时间来回放日志,这会严重影响系统的可用性。Raft算法通过对整个系统进行快照(snapshot)来解决这一问题,在完成snapshot之后,之前的日志便可以丢弃。

每个副本都会独立地对自身的系统状态进行snapshot,并且只能针对已经提交的日志记录进行操作。

Snapshot主要包含以下两部分内容:

  • 日志元数据:记录最后一条已提交的日志条目(log entry)的索引(log index)和任期号(term)。这两个值在snapshot之后的第一条日志条目进行AppendEntries RPC完整性检查时会发挥作用。
  • 系统当前状态:呈现系统此刻的完整状态信息。

当Leader要发送给某个日志严重落后的Follower的日志条目(log entry)已被丢弃时,Leader就会将snapshot发送给该Follower。另外,当有新机器加入集群时,也会向其发送snapshot。发送snapshot借助InstalledSnapshot RPC来实现。

进行snapshot操作时,频率把控十分关键。如果过于频繁,会大量消耗磁盘带宽资源;若过于稀疏,一旦节点重启,就需要回放海量日志,同样会对系统可用性造成影响。比较推荐的做法是,当日志达到某个固定大小的时候执行一次snapshot。

有时,执行一次snapshot可能耗时较长,这会干扰正常的日志同步工作。不过,可以运用写时复制(copy-on-write)技术,有效避免snapshot过程对正常日志同步产生影响。

成员变更

在集群的运行过程中,成员变更指的是副本数量或组成结构发生变化的情况,比如增加或减少副本数量、进行节点替换等操作。

成员变更本质上属于一个分布式一致性问题,其核心在于确保所有服务器能够就新成员的加入或旧成员的退出达成一致。然而,成员变更又具有独特的复杂性,这是因为在达成一致性的过程中,参与投票决策的进程会随着成员的变动而发生改变。

若将成员变更视为普通的一致性问题来处理,通常的做法是直接向Leader发送成员变更请求。Leader接收到请求后,会复制成员变更日志,并在获得多数服务器的认可后提交该日志。当各个服务器都提交了成员变更日志后,便会从旧的成员配置(Cold)切换到新的成员配置(Cnew)。

但是,由于每台服务器提交成员变更日志的时间点可能存在差异,这就导致了各服务器从旧成员配置(Cold)切换到新成员配置(Cnew)的时刻也不尽相同。

在成员变更过程中,必须保证服务的可用性不受影响。然而,在成员变更的某个特定时刻,有可能出现这样的情况:在旧成员配置(Cold)和新成员配置(Cnew)中,同时存在两个相互独立且不相交的多数派。这种情况下,很可能会选举出两个不同的Leader,进而做出不同的决议,最终破坏系统的安全性。

raft-成员变更

由于成员变更具有特殊复杂性,不能简单地当作一般的一致性问题来处理。

为有效解决这一难题,Raft设计了两阶段的成员变更方法。在这个方法中,集群首先从旧成员配置Cold过渡到一种名为共同一致(joint consensus)的中间成员配置,共同一致是旧成员配置Cold与新成员配置Cnew的组合,即Cold U Cnew。一旦共同一致Cold U Cnew被成功提交,系统便会进一步切换到新成员配置Cnew。

Raft两阶段成员变更的具体流程如下:

  1. 变更请求接收:Leader收到成员变更请求,要求从Cold切换为Cnew。
  2. 生成并复制过渡配置日志:Leader在本地生成一个新的日志条目(log entry),内容为Cold∪Cnew,代表当前新旧成员配置共存的状态。此条目写入本地日志后,Leader将其复制到Cold∪Cnew中的所有副本。在此之后,新的日志同步需同时得到Cold和Cnew两个多数派的确认。
  3. Follower更新本地日志与配置:Follower收到包含Cold∪Cnew的log entry后,更新本地日志,并立即将该配置作为自己当前的成员配置。
  4. 过渡配置日志提交:当Cold和Cnew中的两个多数派都确认了Cold U Cnew这条日志时,Leader提交该log entry。
  5. 生成并复制新成员配置日志:接着,Leader生成一条新的log entry,内容为新成员配置Cnew,同样将其写入本地日志,并复制到各个Follower。
  6. Follower更新新成员配置:Follower收到新成员配置Cnew后,写入日志,并从这一刻起,将其作为自己的成员配置。若Follower发现自己不在Cnew成员配置中,会自动退出。
  7. 成员变更完成确认:Leader收到Cnew的多数派确认后,表明成员变更成功。此后,日志同步只需得到Cnew多数派确认即可。最后,Leader向客户端回复成员变更执行成功。

异常情况分析:

  • 过渡配置未推送完成:如果Leader还未将Cold U Cnew推送到Follower就发生故障,后续选出的新Leader不包含这条日志,新Leader会继续使用Cold作为成员配置。
  • 部分推送过渡配置:若Leader将Cold U Cnew推送到大部分Follower后故障,新选出的Leader可能来自Cold配置中的Follower,也可能来自Cnew配置中的Follower。
  • 推送新成员配置故障:当Leader在推送Cnew配置过程中发生故障,新选出的Leader同样可能来自Cold或Cnew中的某一个。这种情况下,客户端再次执行一次改变配置的命令即可。
  • 多数Follower确认新成员配置:若大多数Follower确认了Cnew消息,后续即便Leader故障,新选出的Leader必然在Cnew配置中。

两阶段成员变更方法虽然通用性强且易于理解,但实现过程较为复杂,同时在一定程度上会影响变更期间的服务可用性。因此,有必要增强成员变更的限制条件,以简化操作流程。

两阶段成员变更之所以分为两个阶段,是因为在默认情况下,Cold与Cnew的关系未作任何限定,为防止Cold和Cnew各自形成不相交的多数派,进而选出两个Leader,才引入了两阶段方案。

若增强成员变更的限制,假设Cold与Cnew的任意多数派交集不为空,那么这两个成员配置就无法各自形成独立的多数派,成员变更方案便有可能简化为一阶段。

要实现Cold与Cnew的任意多数派交集不为空,限制方法是每次成员变更仅允许增加或删除一个成员。通过数学方法可以严格证明,只要每次仅允许增加或删除一个成员,Cold与Cnew就不可能形成两个不相交的多数派。

一阶段成员变更规则如下:

  • 成员变更限制:每次成员变更仅允许增加或删除一个成员。如需变更多个成员,需连续多次执行变更操作。
  • 变更发起与确认:成员变更由Leader发起,当Cnew得到多数派确认后,向客户端返回成员变更成功。
  • 顺序执行限制:一次成员变更成功前,不允许开始下一次成员变更。新任Leader在开始提供服务前,需对自己本地保存的最新成员配置重新投票,获取多数派确认。
  • 日志同步配置更新:Leader一旦开始同步新成员配置,即可使用新的成员配置进行日志同步 。

Multi Paxos、Raft、ZAB的关系与区别

对比维度 Multi Paxos Raft ZAB
关系 基础算法,Raft和ZAB是其等价派生实现 基于Multi Paxos派生,将共识问题分解为领导者选举、实体复制和安全性三个子问题解决 基于Multi Paxos派生,思路与Raft类似
核心原理 - 选主 - 通过随机超时实现无活锁的选主过程 通过随机超时实现无活锁的选主过程
核心原理 - 写操作 - 通过主节点发起写操作 通过主节点发起写操作
核心原理 - 存活检测 - 通过心跳检测存活性 通过心跳检测存活性
核心原理 - 一致性 依靠多数派表决保证一致性 通过Quorum机制保证一致性 通过Quorum机制保证一致性
选主参与范围 所有节点都能参与提议和决策 全部节点都能参与选主 分为Leader、Follower和Observer,只有Follower能选Leader
Quorum机制 节点在决策中地位平等 节点在Quorum机制中地位平等 -
值的表示方式 根据具体应用和实现自定义 根据具体应用和实现自定义 根据具体应用和实现自定义
决策特点 类似民主制,人人平等可提议,决策过程复杂,需反复协商 类似独裁制,有明确负责人,命令执行简单直接,容易理解和执行 -
典型应用 MySQL MGR组复制,多主模式下多个节点都可写入(需冲突检测) MongoDB副本集,只有Primary节点可接收写入请求 ZooKeeper用于分布式协调服务

Raft算法的独特之处在于,它将共识问题拆解为“Leader Election(领导者选举)”“Entity Replication(实体复制)”和“Safety(安全性)”这三个关键问题,并逐一进行思考与解决,这种别具一格的解题思路构成了Raft算法的核心框架。

ZooKeeper的ZAB算法与Raft算法的设计思路极为相似,它们都被视作Multi Paxos的等价派生实现。这意味着它们在底层的核心原理上高度一致:

  • 选主机制:借助随机超时策略来实现无活锁的选主过程,避免选举过程陷入死循环。
  • 写操作发起:依赖主节点来发起写操作,确保数据写入的有序性和一致性。
  • 存活检测:利用心跳机制来实时检测节点的存活性,及时发现并处理故障节点。
  • 一致性保障:通过Quorum机制,即基于多数派的投票原则来保证数据的一致性,只有获得多数节点的认可,操作才能生效。

尽管核心原理相同,但在具体细节方面,它们仍存在一些差异:

  • 选主参与范围:在Raft算法中,全部节点都有资格参与选主;而ZAB算法将节点分为Leader、Follower和Observer,其中只有Follower能够参与选举Leader。
  • Quorum机制差异:不同算法在Quorum机制中,对于节点的权重设定有所不同。有的算法中节点众生平等,每个节点的投票权重相同;而有的算法则会为不同节点赋予不同的权重。
  • 值的表示方式:在数据值的表示和处理方式上,各个算法也存在差别,这影响着数据在系统中的存储、传输和处理逻辑。

从形象的角度来看,Paxos算法类似于民主制度,在Paxos中人人平等,每个节点都可以提出提议,但也正因如此,决策过程需要反复协商,较为复杂。MySQL MGR组复制就是其典型代表,在多主模式下,多个节点都可以进行写入操作,但需要进行冲突检测,以确保数据一致性。

而Raft算法更像是独裁制度,存在明确的负责人(如同班长),所有命令由其统一协调和分发,命令执行过程简单直接,易于理解和执行。MongoDB副本集便是Raft算法的典型应用,只有Primary节点可以接收写入请求,然后将数据同步到其他节点 。

推荐阅读

文章来源: https://study.disign.me/article/202509/1.raft-distro-choice.md

发布时间: 2025-02-24

作者: 技术书栈编辑