版权声明:可以转载,但请备注原文链接:https://shuwoom.com/?p=826

分布式一致性问题

如果说,服务器只有一个节点,那么,要保证一致性,没有任何问题,因为所有读写都在一个节点上发生。那如果server端有2个、3个甚至更多节点,要怎么达成一致性呢?下面就来介绍其中一种分布式共识算法—raft算法

Raft是什么

1.历史背景

在讲Raft前,有必要提一下Paxos算法,Paxos算法是Leslie Lamport于1990年提出的基于消息传递的一致性算法。然而,由于算法难以理解,刚开始并没有得到很多人的重视。其后,作者在八年后,也就是1998年在ACM上正式发表,然而由于算法难以理解还是没有得到重视。而作者之后用更容易接受的方法重新发表了一篇论文《Paxos Made Simple》。

可见,Paxos算法是有多难理解,即便现在放到很多高校,依然很多学生、教授都反馈Paxos算法难以理解。同时,Paxos算法在实际应用实现的时候也是比较困难的。这也是为什么会有后来Raft算法的提出。

2.概念

Raft是实现分布式共识的一种算法,主要用来管理日志复制的一致性。它和Paxos的功能是一样,但是相比于Paxos,Raft算法更容易理解、也更容易应用到实际的系统当中。而Raft算法也是联盟链采用比较多的共识算法。

备注:区块链分为:公有链、联盟链和私有链

 

Raft的三种状态(角色)

 

  • Follower(群众)

被动接收Leader发送的请求。所有的节点刚开始的时候是处于Follower状态。

  • Candidate(候选人)

由Follower向Leader转换的中间状态

  • Leader(领导)

负责和客户端交互以及日志复制(日志复制是单向的,即Leader发送给Follower),同一时刻最多只有1个Leader存在。

三种状态的转换关系如下,下一节的Raft工作机制会具体讲到。

几个关键概念

1、复制状态机

我们知道,在一个分布式系统数据库中,如果每个节点的状态一致,每个节点都执行相同的命令序列,那么最终他们会得到一个一致的状态。也就是和说,为了保证整个分布式系统的一致性,我们需要保证每个节点执行相同的命令序列,也就是说每个节点的日志要保持一样。所以说,保证日志复制一致就是Raft等一致性算法的工作了。

复制状态机架构

这里就涉及Replicated State Machine(复制状态机),如上图所示。在一个节点上,一致性模块(Consensus Module,也就是分布式共识算法)接收到了来自客户端的命令。然后把接收到的命令写入到日志中,该节点和其他节点通过一致性模块进行通信确保每个日志最终包含相同的命令序列。一旦这些日志的命令被正确复制,每个节点的状态机(State Machine)都会按照相同的序列去执行他们,从而最终得到一致的状态。然后将达成共识的结果返回给客户端,如下图所示。

2、任期(Term)概念

在分布式系统中,“时间同步”是一个很大的难题,因为每个机器可能由于所处的地理位置、机器环境等因素会不同程度造成时钟不一致,但是为了识别“过期信息”,时间信息必不可少。

Raft算法中就采用任期(Term)的概念,将时间切分为一个个的Term(同时每个节点自身也会本地维护currentTerm),可以认为是逻辑上的时间,如下图。

每一任期的开始都是一次领导人选举,一个或多个候选人(Candidate)会尝试成为领导(Leader)。如果一个人赢得选举,就会在该任期(Term)内剩余的时间担任领导人。在某些情况下,选票可能会被评分,有可能没有选出领导人(如t3),那么,将会开始另一任期,并且理科开始下一次选举。Raft 算法保证在给定的一个任期最少要有一个领导人。

3、心跳(heartbeats)和超时机制(timeout)

在Raft算法中,有两个timeout机制来控制领导人选举:

一个是选举定时器(eletion timeout):即Follower等待成为Candidate状态的等待时间,这个时间被随机设定为150ms~300ms之间

另一个是headrbeat timeout:在某个节点成为Leader以后,它会发送Append Entries消息给其他节点,这些消息就是通过heartbeat timeout来传送,Follower接收到Leader的心跳包的同时也重置选举定时器。

Raft的工作机制

Raft算法可以分为如下3个部分:

1、领导人选举(Leader Election)

(1)一开始,所有节点都是以Follower角色启动,同时启动选举定时器(时间随机,降低冲突概率)

(2)如果一个节点发现在超过选举定时器的时间以后一直没有收到Leader发送的心跳请求,则该节点就会成为候选人,并且一直处于该状态,直到下列三种情况之一发生:

  • 该节点(Candidate)赢得选举
  • 其他节点赢得选举
  • 一段时间后没有任何一台服务器赢得选举(进入下一轮Term的选举,并随机设置选举定时器时间)

(3)然后这个候选人就会向其他节点发送投票请求(Request Vote),如果得到半数以上节点的同意,就成为Leader(Leader)。如果选举超时,还没有Leader选出,则进入下一任期,重新选举。

(4)完成Leader选举后,Leader就会定时给其他节点发送心跳包(Heartbeat),告诉其他节点Leader还在运行,同时重置这些节点的选举定时器。

 

2、日志复制(Log Replication)

 

(1)Client向Leader提交指令(如:SET 5),Leader收到命令后,将命令追加到本地日志中。此时,这个命令处于“uncomitted”状态,复制状态机不会执行该命令。

(2)然后,Leader将命令(SET 5)并发复制给其他节点,并等待其他其他节点将命令写入到日志中,如果此时有些节点失败或者比较慢,Leader节点会一直重试,知道所有节点都保存了命令到日志中。之后Leader节点就提交命令(即被状态机执行命令,这里是:SET 5),并将结果返回给Client节点。

(3)Leader节点在提交命令后,下一次的心跳包中就带有通知其他节点提交命令的消息,其他节点收到Leader的消息后,就将命令应用到状态机中(State Machine),最终每个节点的日志都保持了一致性。

Leader节点会记录已经交的最大日志index,之后后续的heartbeat和日志复制请求(Append Entries)都会带上这个值,这样其他节点就知道哪些命令已经提交了,就可以让状态机(State  Machine)执行日志中的命令,使得所有节点的状态机数据都保持一致。

下面我们来看下日志内容不一致的情况下,Raft算法如何处理?

如下图,如果在一个分布式网络中,各个节点的日志状态如下。当Leader节点发送日志复制请求的时,它会带上上一次的日志记录的index和term。

此时Leader节点发送日志复制请求<nextIndex:8,命令:x←4,,已提交的日志index为7,term为3>。此时,A节点收到Leader的请求后,对比Leader节点记录的上一个日志记录的index和term,发现:

  • index(leader)> index(A)
  • term(leader)> currentTerm(A)

发现自己的日志中不存在这个命令,于是拒绝这个请求。此时,Leader节点知道发生了不一致,于是递减nextIndex,并重新给A节点发送日志复制请求,直到找到日志一致的地方为止。然后把Follower节点的日志覆盖为Leader节点的日志内容。

也就是说,Raft算法对于日志内容不一致的请求,会采取Leader节点的日志内容覆盖Follower节点的日志内容的做法,先找到两者日志记录第一次不一致的地方,然后一直覆盖到最新提交的命令位置。

3、安全性

之前的内容讨论了 Raft 算法是如何进行领导选取和复制日志的。然而,到目前为止这个机制还不能保证每一个状态机能按照相同的顺序执行同样的指令。例如,当领导人提交了若干日志条目的同时一个追随者可能宕机了,之后它又被选为了领导人然后用新的日志条目覆盖掉了旧的那些,最后,不同的状态机可能执行不同的命令序列。

而Raft算法通过在领导人选举阶段增加一个限制来完善了Raft算法。这个限制能保证,对于固定任期,任何领导人都拥有之前任期提交的全部日志命令。Raft算法通过投票的方式来阻止那些没有包含全部日志命令的节点赢得选举。

一个Candidate节点要成为赢得选举,就需要跟网络中大部分节点进行通信,这就意味着每一条已经提交的日志条目最少在其中一台服务器上出现。如果候选人的日志至少和大多数服务器上的日志一样新,那么它一定包含有全部的已经提交的日志条目。RequestVote RPC 实现了这个限制:这个 RPC包括候选人的日志信息,如果它自己的日志比候选人的日志要新,那么它会拒绝候选人的投票请求。

 

那么,怎么判断两个节点日志内容比较新呢?其标准如下,Raft算法通过比较日志中最后一个命令的索引(index)和任期号(term)来判定哪一个日志内容比较新。

  • 如果两个日志的任期号不同,任期号大的日志内容更新
  • 如果任期号相同,日志长的日志内容更新

 

实例分析

下面我们来考虑一个比较极端的情况,出现网络分区的时候,Raft如何保持一致性。

如下图,我们将分布式网络分割为两个子网,分别是子网络AB和子网络CDE,此时节点B是Leader节点。

然而由于网络分区导致子网1不存在Leader节点,此时,C、D和E节点由于没有收到Leader节点的心跳,导致选举定时器超时从而进入Candidate状态,开始进行领导人选举。

这时,我们假设C节点赢得选举,成为子网1的Leader节点。

此时,如果两个子网有Client节点分别向各个子网的Leader节点提交数据(如:X←3),由于子网2中Leader节点B不可能复制到大部分节点,所以其X←3命令会一直处于“uncomitted”状态。而子网1由于成功复制给大部分节点,所以X←3最终在子网1达成共识,如下图所示。

我们假设,子网1经过多次选举和数据交互,最终子网1的日志状态如下图所示:

而此时,分区隔离状态消失。Leader C和Leader B分别会发送心跳请求,最终Leader B发现Leader C选票比自己更多,从而转换为Follower状态。而通过日志复制(Log Replication),最终所有节点日志达成一直,如下图。

 

关注我的微信公众号(shuwoom的博客),每周定期推送文章:

打赏

发表评论

电子邮件地址不会被公开。