1、任期Term:实际上就是一个逻辑时间,Raft算法将分布式系统中的时间划分成一个个不同的任期来解决时序问题。每个任期都有一个数字来表示任期号,任期号在算法启动时的初始值为0,单调递增且永远不会重复。任期对于Raft算法来说非常重要,Raft能够帮助Raft识别过期的信息,其原则是:Raft只使用最新任期的信息。
2、Raft选举过程中需要保证共识算法的两个特性:安全性和活性。安全性是只一个任期内只会有一个领导者被选举出来。活性意味着系统最终只能选出一个领导者。安全性的两个前提:1)每个节点在同一任期只能投一次票,投给第一个满足条件的RequestVote请求;2)只有获得超过集群中半数节点的选票才能成为领导者;
3、活锁问题以及解决办法:原则上节点可以无限重复分割选票,假如选举同一时间开始,然后瓜分选票,没有达成任何多数派,又同一时间超时,同一时间在此选举,如此循环,出现了类似于Paxos的活锁问题,同样可以用解决活锁的办法来解决,即节点随机选择超时时间,选举超时时间通常设置为150ms~300ms之间,由于随机性节点不大可能再同时开始竞选,所以先竞选的节点有足够的时间来索要其他节点的选票。死锁是多个线程互斥等待,活锁是指多个节点各自运行正常但是这些节点构成的整体系统无法正常运行。
4、法定人数机制(基于Quorum的数据冗余机制):法定人数机制是分布式系统中用来保证数据冗余和最终一致性的一种算法,该机制不需要主节点,但是需要满足以下条件:基于Quorum的数据冗余机制要求在一个由N个节点组成的系统中,至少需要W个节点写入成功,并且在读取时需要同时从R割接点中读取数据,只要满足W+R>N且W>N/2,则读取的R个返回值至少包含一个最新的值。W>N/2这个规则主要用于保证数据的串行化修改,两个不同的写请求不能同时成功修改一份数据。基于Quorum机智的最小读写副本数可以作为系统在读写性能方面的可调节参数,将W和R作为可配置参数后,系统管理员可以根据系统的工作负载来配置具体的参数值。W值越大R值越小,系统的读性能就越好,反之写操作的性能越好。
5、Raft的日志格式:每个节点存储自己的日志副本,日志中每个日志条目LogEntry包含如下内容:1)索引Index,用于表示该日志条目在整个日志中的位置;2)任期号,日志条目首次被领导者创建时的任期;3)命令,应用于状态机的命令;
6、Raft算法通过索引和任期号唯一标识一条日志记录。不用关心日志中的具体命令,一条日志的重点是其索引和任期号。日志必须被持久化存储。一个节点必须先将日志条目安全的写到磁盘中,才能向系统中其他节点发送请求或者回复请求。如果一条日志条目被存储在超过半数的节点上,则认为该记录已经提交。这是raft算法非常重要的特性!如果一条记录已经提交,则意味着状态机可以安全地执行该记录,这条记录就不能再被改变了。
7、Raft算法为了保证安全行,还维持了一下两个特性:1)如果两个节点的日志在相同的索引位置上任期号相同,则认为它们具有一样的命令,并且从日志开头到这个索引位置之间的日志也完全相同;2)如果给定的记录已经提交,那么前面的所有记录也已经被提交。第二条约束要求raft算法的日志必须连续地提交,不允许出现日志空洞。为了维护这两个特性,Raft算法尝试在集群中保持日志较高的一致性。Raft算法通过AppendEntries消息来检测之前的一个日志条目:每个AppendEntries消息请求包含新日志条目之前一个条目的索引prevLogIdex和lsscsi-w任期prevLogTerm;跟随者收到请求后,会检查自己最后一条日志的索引和任期号是否与请求消息中的prevLogInddex和pervLogTerm相匹配,如果匹配则接受该记录否则拒绝。一致性检查的原理可以使用数学归纳法证明:初始日志状态都是空的,之后每追加一条日志都要通过一致性检查来确保前一条日志是相同的,最后可得这条日志之前的所有日志都是相同的,能够满足所要求的安全性。
8、Raft算法在领导者上任时,对于冲突的日志无法采取任何特殊处理(即领导者不会执行任何清理操作),相反,它会在正常运行期间执行清理操作,而不是专门为此做任何额外的工作。这样处理的原因在于:新的领导者刚上任时,往往意味着有机器发生故障了或者发生了网络分区,此时并没有办法立即清理他们的日志,因此此时有可能仍然无法连通这些机器,在机器恢复正常运行前,我们必须要保证系统正常运行。这样做的前提是:raft算法假定了领导者的日志始终是正确的,因此领导者的工作是:随着时间推移,让所有跟随者的日志都最终与其匹配。一旦状态机应用了一条日志中的命令,就必须确保其他状态机在同样索引的位置不会执行不同的命令,否则就违反了状态复制的一致性。Raft算法的安全性就是为了保证状态机复制执行相同的命令,安全性要求,如果某个日志条目在某个任期号已提交,那么这条记录必然出现在更大任期号的未来领导者的日志中。也即,无论未来领导者如何变更,已提交的日志都必须保留在这些领导者的日志中。这保证了状态机命令的安全性,意味着:一方面领导者不会覆盖日志中已提交的记录,另一方面,只有领导者的日志条目才能被提交,并且在日志被应用到状态机之前,日志必须先被提交。
9、Raft算法中的延迟提交:已经提交的日志被定义为该日志条目已经在多数派节点中被存储。但是在某些时候,领导者在日志刚被复制到多数派中就宕机了,则后续领导者必须延迟提交日志记录,直到我们确认这条日志记录是安全的。所谓安全,就是后续领导者中也会有这条日志。这一优化被称为延迟提交。Raft算法通过比较日志,在选举期间,选择最有可能包含所有已提交日志的节点作为领导者,所谓最有可能,就是找出日志最新且最完整的节点来作为领导者。选取规则为:选取出来的领导者的日志索引和任期这对唯一标识是最大的,当然,这对标识中任期的优先级又要高一些,所以先判断任是否更新。raft算法保证选举出来的领导者任期最新,日志长度也最完整,这样能够避免领导者去追赶其他节点的日志而造成系统阻塞。这也能看出raft算法中领导者非常权威。
10、日志仅仅只是复制到多数派,Raft算法也并不能立即认为日志可以提交,并应用到状态机,因为之后某个节点可能覆盖这些日志重新执行某些命令,这样就会违反状态机安全性,这也是Raft算法要延迟提交的原因。Raft算法要求领导者想要提交一条日志必须满足:1)日志存储在超过半数的节点上;2)领导者必须看到超过半数的节点上还存储着至少一条自己任期内的日志。Raft算法还引入了一种no-op空日志,no-op空日志即只有索引和任期信息,命令信息为空。这类日志不会改变状态机的状态和输出,只是用来保持领导者的权威,驱动算法运行。具体流程是:领导者刚选举成功的时候,不管是否有客户端的请求,立即向自己本地追加一条no-op空日志,并立即将no-op空日志复制到其他其他节点。no-op空日志属于领导者任期的日志,多数派达成后立即提交,致使no-op空日志前面的那些未提交的日志全部间接提交了。(简单地来说,Raft算法中领导者只能直接提交自己任期内的日志从而间接提交之前任期未提交的日志,不能直接提交之前任期未提交的日志)。本质上,no-op空日志可以使领导者快速提交之前任期未提交的日志。
11、跟随者覆盖与领导者不一致的日志时,它会删除所有后续的日志记录(Raft算法认为任何无关紧要的记录之后的记录也是无关紧要的),之后再由领导者发送日志来补齐。
12、任期是用来发现过期领导者或者候选者的,其处理逻辑为:1)每个RPC请求都包含发送方的任期;2)如果接收方发现发送方的任期陈旧,那么无论哪个过程,该RPC请求都会被拒绝,接收方如果将已知的最新任期回传给发送方,发送方知道自己的任期已经过期后,转变为跟随者状态并跟新其任期。3)如果接收方发现自己的任期陈旧,那么接收方将自己转为跟随者,更新自己的任期然后正常的处理RPC请求。
13、事务的一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。事务必须保证数据库可以从一个一致的状态转移到另一个一致的状态,这种一致性要求不仅指常见的数据库完整性约束,有时候还需要由用户(应用程序)来保证。
14、原子提交在分布式领域更普遍地被称为提交问题。事务的一大好处就是保证了原子性,所有的操作要么都执行要么都不执行。原子性可以说是事务最重要的特性。常见的机械磁盘一般可以保证512字节的原子写。所谓原子写就是说,即便遭遇突然断电等意外情况,一般的机械磁盘也可以保证当前512字节的成功写入,但对于大于512字节的数据,原子性无法得到保障。因此,为了在更通用的情况下实现原子性,常见方法是使用日志或WAL这类技术。简单地来说,先将操作的元数据写入一个单独的日志文件,同时还有表示操作是否完成的标记。若在写入过程中系统发生故障,那么基于这些数据,系统恢复后能够识别哪些操作在故障发生前已经完成,然后通过撤销所有的操作来回滚失误或者通过完成剩余未执行的操作来继续提交事务。基于硬盘原子写和日志文件来实现事务原子性的方法,在文件系统和数据库中广泛应用。
15、原子提交协议分为两阶段提交协议和三阶段提交协议。两阶段提交协议是最经典的原子提交协议,其基本思想是:既然仅发送一个请求不足以知道其他节点是否成功提交事务,那么最直接的想法就是再增加一轮请求,先检查每个节点上的状态是否能够满足事务正确性,再进行事务操作。两阶段提交协议包含两个角色:协调者和参与者,协调者负责协调算法的各个阶段,而参与者则参与到事务中执行事务操作。值得一提的是,也可以选择一个参与者来同时扮演协调者。
参考链接:
1、《深入理解分布式系统》唐伟志著
2、Note/ARMS 算法、架构、数学、规范/A.0.0.0 Raft.md at master · hi-iwi/Note · GitHub