木筏求生Raft支持玩家联机,游戏中目前还没有对于玩家的联机人数进行限制,8人联机游戏也可以支持,但是对电脑和服务器负担很大,可能会卡顿。
联机相关问题解答
1木筏求生支持玩家联机进行游戏,游戏中目前还没有对于玩家的联机人数进行限制,8人联机游戏也可以支持。
2理论情况下目前联机人数是没有上限的,但是联机人数越多可能游戏内的卡顿问题就会增加。
3玩家们如果想要同时和很多朋友进行联机的话可能需要根据人数来看看游戏内体验是否流畅。
上篇讲到了「拜占庭将军问题」:多个拜占庭将军要如何在可能有叛徒、信使可能被策反或者暗杀的情况下达成是否要进攻的一致性决定?还不了解的先看看上一篇 《拜占庭将军问题》 。这篇主要是介绍简化版拜占庭将军问题的解决方案:Raft 共识算法。
所以将拜占庭将军问题根据常见的工作上的问题进行简化: 假设将军中没有叛军,信使的信息可靠但有可能被暗杀的情况下,将军们如何达成一致性决定?
对于这个简化后的问题,有许多解决方案,第一个被证明的共识算法是 Paxos,由拜占庭将军问题的作者 Leslie Lamport 在1990年提出,最初以论文难懂而出名,后来这哥们在2001重新发了一篇简单版的论文 Paxos Made Simple ,然而还是挺难懂的。
因为 Paxos 难懂,难实现,所以斯坦福大学的教授在2014年发表了新的分布式协议 Raft。与 Paxos 相比,Raft 有着基本相同运行效率,但是更容易理解,也更容易被用在系统开发上。
我们还是用拜占庭将军的例子来帮助理解 Raft。
Raft 的解决方案大概可以理解成 先在所有将军中选出一个大将军,所有的决定由大将军来做。 选举环节 :比如说现在一共有3个将军 A, B, C,每个将军都有一个 随机时间 的倒计时器,倒计时一结束,这个将军就会把自己当成大将军候选人,然后派信使去问其他几个将军,能不能选我为总将军?假设现在将军A倒计时结束了,他派信使传递选举投票的信息给将军B和C,如果将军B和C还没把自己当成候选人(倒计时还没有结束),并且没有把选举票投给其他,他们把票投给将军A,信使在回到将军A时,将军A知道自己收到了足够的票数,成为了大将军。在这之后,是否要进攻就由大将军决定,然后派信使去通知另外两个将军,如果在一段时间后还没有收到回复(可能信使被暗杀),那就再重派一个信使,直到收到回复。
故事先讲到这里,希望不做技术方面的朋友可以大概能理解 Raft 的原理,下面从比较技术的角度讲讲 Raft 的原理。
从拜占庭将军的故事映射到分布式系统上,每个将军相当于一个分布式网络节点,每个节点有 三种状态:Follower,Candidate,Leader ,状态之间是互相转换的,可以参考下图,具体的后面说。
每个节点上都有一个倒计时器 (Election Timeout),时间随机在 150ms 到 300ms 之间。有几种情况会重设 Timeout:
在 Raft 运行过程中,最主要进行两个活动:
假设现在有如图5个节点,5个节点一开始的状态都是 Follower。
在一个节点倒计时结束 (Timeout) 后,这个节点的状态变成 Candidate 开始选举,它给其他几个节点发送选举请求 (RequestVote)
其他四个节点都返回成功,这个节点的状态由 Candidate 变成了 Leader,并在每个一小段时间后,就给所有的 Follower 发送一个 Heartbeat 以保持所有节点的状态,Follower 收到 Leader 的 Heartbeat 后重设 Timeout。
这是最简单的选主情况, 只要有超过一半的节点投支持票了,Candidate 才会被选举为 Leader ,5个节点的情况下,3个节点 (包括 Candidate 本身) 投了支持就行。
一开始已经有一个 Leader,所有节点正常运行。
Leader 出故障挂掉了,其他四个 Follower 将进行重新选主。
4个节点的选主过程和5个节点的类似,在选出一个新的 Leader 后,原来的 Leader 恢复了又重新加入了,这个时候怎么处理?在 Raft 里,第几轮选举是有记录的,重新加入的 Leader 是第一轮选举 (Term 1) 选出来的,而现在的 Leader 则是 Term 2,所有原来的 Leader 会自觉降级为 Follower
假设一开始有4个节点,都还是 Follower。
有两个 Follower 同时 Timeout,都变成了 Candidate 开始选举,分别给一个 Follower 发送了投票请求。
两个 Follower 分别返回了ok,这时两个 Candidate 都只有2票,要3票才能被选成 Leader。
两个 Candidate 会分别给另外一个还没有给自己投票的 Follower 发送投票请求。
但是因为 Follower 在这一轮选举中,都已经投完票了,所以都拒绝了他们的请求。所以在 Term 2 没有 Leader 被选出来。
这时,两个节点的状态是 Candidate,两个是 Follower,但是他们的倒计时器仍然在运行,最先 Timeout 的那个节点会进行发起新一轮 Term 3 的投票。
两个 Follower 在 Term 3 还没投过票,所以返回 OK,这时 Candidate 一共有三票,被选为了 Leader。
如果 Leader Heartbeat 的时间晚于另外一个 Candidate timeout 的时间,另外一个 Candidate 仍然会发送选举请求。
两个 Follower 已经投完票了,拒绝了这个 Candidate 的投票请求。
Leader 进行 Heartbeat, Candidate 收到后状态自动转为 Follower,完成选主。
以上是 Raft 最重要活动之一选主的介绍,以及在不同情况下如何进行选主。
总结:
1每个节点一直有一个timeout,timeout结束就开始选自己为主节点;
2主节点会轮询从节点,从节点更新timeout;
3选举等级机制;
Raft 在实际应用场景中的一致性更多的是体现在不同节点之间的数据一致性,客户端发送请求到任何一个节点都能收到一致的返回,当一个节点出故障后,其他节点仍然能以已有的数据正常进行。在选主之后的复制日志就是为了达到这个目的。
一开始,Leader 和 两个 Follower 都没有任何数据。
客户端发送请求给 Leader,储存数据 “sally”,Leader 先将数据写在本地日志,这时候数据还是 Uncommitted (还没最终确认,红色表示)
Leader 给两个 Follower 发送 AppendEntries 请求,数据在 Follower 上没有冲突,则将数据暂时写在本地日志,Follower 的数据也还是 Uncommitted。
Follower 将数据写到本地后,返回 OK。Leader 收到后成功返回, 只要收到的成功的返回数量超过半数 (包含Leader) ,Leader 将数据 “sally” 的状态改成 Committed。( 这个时候 Leader 就可以返回给客户端了)
Leader 再次给 Follower 发送 AppendEntries 请求,收到请求后,Follower 将本地日志里 Uncommitted 数据改成 Committed。这样就完成了一整个复制日志的过程,三个节点的数据是一致的,
在 Network Partition 的情况下,部分节点之间没办法互相通信,Raft 也能保证在这种情况下数据的一致性。
一开始有 5 个节点处于同一网络状态下。
Network Partition 将节点分成两边,一边有两个节点,一边三个节点。
两个节点这边已经有 Leader 了,来自客户端的数据 “bob” 通过 Leader 同步到 Follower。
因为只有两个节点,少于3个节点,所以 “bob” 的状态仍是 Uncommitted。所以在这里, 服务器会返回错误给客户端
另外一个 Partition 有三个节点,进行重新选主。客户端数据 “tom” 发到新的 Leader,通过和上节网络状态下相似的过程,同步到另外两个 Follower。
因为这个 Partition 有3个节点,超过半数,所以数据 “tom” 都 Commit 了。
网络状态恢复,5个节点再次处于同一个网络状态下。但是这里出现了数据冲突 “bob" 和 “tom"
三个节点的 Leader 广播 AppendEntries
两个节点 Partition 的 Leader 自动降级为 Follower,因为这个 Partition 的数据 “bob” 没有 Commit,返回给客户端的是错误,客户端知道请求没有成功,所以 Follower 在收到 AppendEntries 请求时,可以把 “bob“ 删除,然后同步 ”tom”,通过这么一个过程,就完成了在 Network Partition 情况下的复制日志,保证了数据的一致性。
Raft 是能够实现分布式系统强一致性的算法,每个系统节点有三种状态 Follower,Candidate,Leader。实现 Raft 算法两个最重要的事是:选主和复制日志
参考链接:
Raft 官网: >你好,你是要问raft加载不出来好友的船是怎么回事吗?raft加载不出来好友的船是因为:
1、两个人进同一个节点,没有输入房主设置的密码。
2、电脑网速的原因,需要重启电脑,开加速
3、你的好友没有和您开一个加速一个服务器一个节点。
所谓分布式共识(consensus),与 CAP理论 中的一致性(consistency)其实是异曲同工,就是在分布式系统中,所有节点对同一份数据的认知能够达成一致。保证集群共识的算法就叫共识算法,它与一致性协议这个词也经常互相通用。
当今最著名的共识算法就是Paxos算法。它由Leslie Lamport在1990年提出,很长时间以来都是一致性的事实标准。但是它有两个不小的缺点:难以理解和证明,难以在实际工程中实现。Google Chubby的工程师就曾有以下的评论:
于是2014年,来自斯坦福的两位大佬Diego Ongaro与John Ousterhout通过论文 《In Search of an Understandable Consensus Algorithm》 提出了一个新的共识算法Raft。从题目就可以看出,Raft的特点就是容易理解,在此基础上也容易实现,因此在real world中,它的应用也比Paxos要广泛,比较有名的如etcd、Kudu等。
Raft为了达到易懂易用的目标,主要做了两件事:一是分解问题(decomposition),即将复杂的分布式共识问题拆分为 领导选举 (leader election)、 日志复制 (log replication)和 安全性 (safety)三个子问题,并分别解决;二是压缩状态空间(state space reduction),相对于Paxos算法而言施加了更合理的限制,减少因为系统状态过多而产生的不确定性。
下面先简要介绍共识算法的基础——复制状态机,然后就来按顺序研究Raft是如何解决三个子问题的。
在共识算法中,所有服务器节点都会包含一个有限状态自动机,名为复制状态机(replicated state machine)。每个节点都维护着一个复制日志(replicated logs)的队列,复制状态机会按序输入并执行该队列中的请求,执行状态转换并输出结果。可见,如果能保证各个节点中日志的一致性,那么所有节点状态机的状态转换和输出也就都一致。共识算法就是为了保障这种一致性的,下图示出简单的复制状态机及其相关架构。
根据分布式系统的 Quorum机制 与NRW算法,集群中半数以上节点可用时,就能正确处理分布式事务,因此Raft集群几乎都使用奇数节点,可以防止脑裂并避免浪费资源。采用ZAB协议的ZooKeeper集群也是如此。
在Raft集群中,任意节点同一时刻只能处于领导者(leader)、跟随者(follower)、候选者(candidate)三种状态之一。下图示出节点状态的转移规则。
可见,集群建立时所有节点都是跟随节点。如果在一定时间过后发现没有领导节点,就会切换到候选状态,发起选举。得到多数票的候选者就会成为领导节点。如果候选节点或当前领导节点发现了更新的领导者,就会主动退回跟随状态。
领导节点全权负责管理复制日志,也就是从客户端接收请求,复制到跟随节点,并告诉跟随节点何时可以处理这些请求。如果领导节点故障或断开连接,就会重新进行选举。可见,领导节点的存在大大简化了共识算法的设计。
在上面的图中出现了任期(term)这个词。领导者并不是一直“在位”的,工作一段时间之后,就会选举出新的领导者来接替它。
由上图可见,蓝色表示选举时间段,绿色表示选举出的领导者在位的时间段,这两者合起来即称作一个任期,其计数值是自增的。任期的值就可以在逻辑上充当时间戳,每个节点都会保存一份自己所见的最新任期值,称为currentTerm。另外,如果因为票数相同,没能选出领导,就会立即再发起新的选举。
如果一个或多个跟随节点在选举超时(election timeout)内没有收到领导节点的心跳(一个名为AppendEntries的RPC消息,本意是做日志复制用途,但此时不携带日志数据),就会发起选举流程:
根据其他节点回复的消息,会出现如下三种结果:
获得多数票的节点只要当选,就会立即给其他所有节点发送AppendEntries,避免再次选举。另外,在同一任期内,每个节点只能投一票,并且先到先得(first-come-first-served),也就是会把票投给RequestVote消息第一个到达的那个节点。
至于上面的第三种情况,也就是所谓“split vote”现象,容易在很多跟随者变成候选者时出现,因为没有节点能得到多数票,选举有可能无限继续下去。所以,Raft设置的选举超时并不是完全一样的,而是有些许随机性,来尽量使得投票能够集中到那些较“快”的节点上。
领导节点选举出来后,集群就可以开始处理客户端请求了。前面已经说过,每个节点都维护着一个复制日志的队列,它们的格式如下图所示。
可见,日志由一个个按序排列的entry组成。每个entry内包含有请求的数据,还有该entry产生时的领导任期值。在论文中,每个节点上的日志队列用一个数组log[]表示。
当客户端发来请求时,领导节点首先将其加入自己的日志队列,再并行地发送AppendEntries RPC消息给所有跟随节点。领导节点收到来自多数跟随者的回复之后,就认为该请求可以提交了(见图中的commited entries)。然后,领导节点将请求应用(apply)到复制状态机,并通知跟随节点也这样做。这两步做完后,就不会再回滚。
这种从提交到应用的方式与最基础的一致性协议——两阶段提交(2PC)有些相似,但Raft只需要多数节点的确认,并不需要全部节点都可用。
注意在上图中,领导节点和4个跟随节点的日志并不完全相同,这可能是由于跟随节点反应慢、网络状况差等原因。领导节点会不断地重试发送AppendEntries,直到所有节点上的日志达到最终一致,而不实现强一致性。这就是CAP理论中在保证P的情况下,C与A无法兼得的体现。
日志复制的过程仍然遗留了一个问题:如果领导或者跟随节点发生异常情况而崩溃,如何保证日志的最终一致性?它属于下面的安全性问题中的一部分,稍后会解答它。
安全性是施加在领导选举、日志复制两个解决方案上的约束,用于保证在异常情况下Raft算法仍然有效,不能破坏一致性,也不能返回错误的结果。所有分布式算法都应保障安全性,在其基础上再保证活性(liveness)。
Raft协议的安全性保障有5种,分别是:选举安全性(election safety)、领导者只追加(leader append-only)、日志匹配(log matching)、领导者完全性(leader completeness)、状态机安全性(state machine safety) 。下面分别来看。
选举安全性是指每个任期内只允许选出最多一个领导。如果集群中有多于一个领导,就发生了脑裂(split brain)。根据“领导选举”一节中的描述,Raft能够保证选举安全,因为:
在讲解日志复制时,我们可以明显地看出,客户端发出的请求都是插入领导者日志队列的尾部,没有修改或删除的 *** 作。这样可以使领导者的行为尽量简单化,使之没有任何不确定的行为,同时也作为下一节要说的日志匹配的基础。
日志匹配的具体描述如下。
如果两个节点的日志队列中,两个entry具有相同的下标和任期值,那么:
第一点自然由上一节的“领导者只追加”特性来保证,而第二点则由AppendEntries RPC消息的一个简单机制来保证:每条AppendEntries都会包含最新entry之前那个entry的下标与任期值,如果跟随节点在对应下标找不到对应任期的日志,就会拒绝接受并告知领导节点。
有了日志匹配特性,就可以解决日志复制中那个遗留问题了。假设由于节点崩溃,跟随节点的日志出现了多种异常情况,如下图。
注意图中不是6个跟随节点,而是6种可能的情况。比如a和b是丢失了entry,c和d是有多余的未提交entry,e和f则是既有丢失又有冗余。这时领导节点就会找到两个日志队列中最近一条匹配的日志点,将该点之后跟随节点的所有日志都删除,然后将自己的这部分日志复制给它。例如对于上图中的情况e来说,最近一条匹配的日志下标为5,那么5之后的所有entry都会被删除,被替换成领导者的日志。
领导者完全性是指,如果有一条日志在某个任期被提交了,那么它一定会出现在所有任期更大的领导者日志里。这也是由两点来决定的:
根据这两个描述,每次选举出的领导节点一定包含有最新的日志,因此只存在跟随节点从领导节点更新日志的情况,而不会反过来,这也使得一致性逻辑更加简化,并且为下面的状态机安全性提供保证。
状态机安全性是说,如果一个节点已经向其复制状态机应用了一条日志中的请求,那么对于其他节点的同一下标的日志,不能应用不同的请求。这句话就很拗口了,因此我们来看一种意外的情况。
这里就有问题了,在时刻c的日志与新领导者的日志发生了冲突,此时状态机是不安全的。
为了解决该问题,Raft不允许领导者在当选后提交“前任”的日志,而是通过日志匹配原则,在处理“现任”日志时将之前的日志一同提交。具体方法是:在领导者任期开始时,立刻提交一条空的日志,所以上图中时刻c的情况不会发生,而是像时刻e一样先提交任期4的日志,连带提交任期2的日志。就算此时S1再崩溃,S5也不会重新被选举了。
如果想要更直观地理解Raft,建议参考 这里 ,是一个用动画来描述该算法的网页,形象生动。
世界三大服务器是lig> Raft算法是解决分布式系统共识的问题的算法,Raft是基于Multi-Paxos的基础上做了简化和限制。不同于Paxos的难以理解,Raft设计的首要目的就是可理解性,一个易于理解、实现简单的分布式一致性协议。
Raft 将一致性算法分解成了几个关键模块,例如选举、日志复制和安全性,本文将主要基于 raft论文 简单分析raft算法。
Raft是强领导(Strong leader)模型,一切以leader为主,比如日志只能由leader复制到其他服务器。所以leader的选举是非常重要的一部分。
首先介绍raft算法的三个服务状态:
任意时间集群中只能由一个leader存在。
Raft使用心跳机制实现leader选举。在服务启动的时候,处于follower角色,需要注意的是每个服务于leader的心跳超时的时间是随机的(150-300 毫秒)。
如上图,集群中有三个几点A、B、C,超时时间分别为150ms、200ms、300ms刚启动时任期编号都是0,都处于follower角色。节点A与leader的心跳超时时间最短,最先从follower状态转为candidate,并增加自己的任期编号,先给自己投上一张选票,并向集群中其他节点发送投票信息,当B、C节点接受到A的投票请求之后,在任期为1的这个阶段没有给其他节点投过票,便接受A的投票请求。此时节点A接受到了集群中超过一半的节点的投票,便成为任期为1的leader。
上诉是最简单的选举流程,里面有很多概念都需要解释,比如为什么超时时间不一样?任期编号是什么?投票比较的规则又是什么?
1 任期编号
每个leader在当选期间都有一个自己的任期编号,它是全局单调递增的数字。每个节点都存储这当前的leader的任期编号,当处于candidate阶段的时候,发起投票的时候会把当前任期编号加一。
而且当一个节点接受到比自己任期高的请求时,会将自己的任期编号更新为高的任期编号,如果当前角色是leader,会从leader转换为follower角色。当接受到任期编号比自己小的请求时,节点会直接拒绝这个请求。
2 投票比较规则
a 先到先服务:一个节点在一个任期只能投一票,如果A、B节点都请求C节点投票,C节点如果先投给A之后、就会拒绝B的投票请求。
b日志完整性:一个节点接受的投票信息如果它的日志比自身小,将会拒绝该投票请求。
c过半策略:当某节点接受到了集群中超过一半的节点投票之后,成为该次任期的leader,向其他节点发送leader心跳。
d 在等待投票期间,candidate 可能会收到另一个声称自己是 leader 的服务器节点发来的 AppendEntries RPC 。如果这个 leader 的任期号(包含在RPC中)不小于 candidate 当前的任期号,那么 candidate 会承认该 leader 的合法地位并回到 follower 状态。
3随机超时
前面提高过,每个几点与leader的心跳超时时间是不同的,这样的好处在于避免瓜分票数的情况存在,能快速的进行leader选举。如果各个节点的超时时间都是一样的,就容易出现瓜分票数的情况存在,每个节点都没有获得超过一半的投票,就会开启下一轮的选举,选举时间就会很长。使用随机超时机制,正常情况下,一个时间段里只有一个节点发起投票请求。
下图是整个集群中服务角色变化的流程图。
Leader选举出来之后为客户端提供服务,将接受到的指令作为一个新的日志项追加到日志中去,然后并行的发起 AppendEntries RPC 给其他的服务器,让它们复制该日志项。当该日志项被安全地复制(过半的节点已复制完成),leader 会应用该日志项到它的状态机中(状态机执行该指令)然后把执行的结果返回给客户端。如果 follower 崩溃或者运行缓慢,或者网络丢包,会不断地重试 AppendEntries RPC(即使已经回复了客户端)直到所有的 follower 最终都存储了所有的日志。
上图展示了日志的格式,一个日志项包含三部分
Leader通过 AppendEntries RPC 将日志复制到其他节点。
AppendEntries RPC:
接收者实现:
上诉是AppendEntries RPC的参数的接受流程。term与leaderId不用介绍很简单,而prevLogIndex、prevLogTerm的作用是日志的一致性检测,如果 follower 在它的日志中找不到包含相同索引位置和任期号的条目,那么他就会拒绝该新的日志条目。一致性检查就像一个归纳步骤:一开始空的日志状态肯定是满足 Log Matching Property(日志匹配特性) 的,然后一致性检查保证了日志扩展时的日志匹配特性。因此,每当 AppendEntries RPC 返回成功时,leader 就知道 follower 的日志一定和自己相同(从第一个日志条目到最新条目)。
正常 *** 作期间,leader 和 follower 的日志保持一致,所以 AppendEntries RPC 的一致性检查从来不会失败。然而,leader 崩溃的情况会使日志处于不一致的状态(老的 leader 可能还没有完全复制它日志里的所有条目)。如下情况:
在 Raft 算法中,leader 通过强制 follower 复制它的日志来解决不一致的问题。这意味着 follower 中跟 leader 冲突的日志条目会被 leader 的日志条目覆盖。
Leader 针对每一个 follower 都维护了一个 nextIndex ,表示 leader 要发送给 follower 的下一个日志条目的索引。当选出一个新 leader 时,该 leader 将所有 nextIndex 的值都初始化为自己最后一个日志条目的 index 加1。如果 follower 的日志和 leader 的不一致,那么下一次 AppendEntries RPC 中的一致性检查就会失败。在被 follower 拒绝之后,leaer 就会减小 nextIndex 值并重试 AppendEntries RPC 。最终 nextIndex 会在某个位置使得 leader 和 follower 的日志达成一致。此时,AppendEntries RPC 就会成功,将 follower 中跟 leader 冲突的日志条目全部删除然后追加 leader 中的日志条目(如果有需要追加的日志条目的话)。一旦 AppendEntries RPC 成功,follower 的日志就和 leader 一致,并且在该任期接下来的时间里保持一致。
本机简单介绍了raft 的leader选举和日志复制,当然raft还有其他的特性本文并没有介绍,推荐去看raft的论文,完整的了解raft。
我之前 ZAB协议 的文章分析了zookeeper的zab协议,这里对比一下两者的异同。
最后 >
在分布式系统中,一致性算法至关重要。在所有一致性算法中,Paxos 最负盛名,它由莱斯利·兰伯特(Leslie Lamport)于 1990 年提出,是一种基于消息传递的一致性算法,被认为是类似算法中最有效的。
Paxos 算法虽然很有效,但复杂的原理使它实现起来非常困难,截止目前,实现 Paxos 算法的开源软件很少,比较出名的有 Chubby、LibPaxos。此外,Zookeeper 采用的 ZAB(Zookeeper Atomic Broadcast)协议也是基于 Paxos 算法实现的,不过 ZAB 对 Paxos 进行了很多改进与优化,两者的设计目标也存在差异——ZAB 协议主要用于构建一个高可用的分布式数据主备系统,而 Paxos 算法则是用于构建一个分布式的一致性状态机系统。
由于 Paxos 算法过于复杂、实现困难,极大地制约了其应用,而分布式系统领域又亟需一种高效而易于实现的分布式一致性算法,在此背景下,Raft 算法应运而生。
Raft 算法在斯坦福 Diego Ongaro 和 John Ousterhout 于 2013 年发表的《In Search of an Understandable Consensus Algorithm》中提出。相较于 Paxos,Raft 通过逻辑分离使其更容易理解和实现,目前,已经有十多种语言的 Raft 算法实现框架,较为出名的有 etcd、Consul 。
根据官方文档解释,一个 Raft 集群包含若干节点,Raft 把这些节点分为三种状态:Leader、 Follower、Candidate,每种状态负责的任务也是不一样的。正常情况下,集群中的节点只存在 Leader 与 Follower 两种状态。
• Leader(领导者) :负责日志的同步管理,处理来自客户端的请求,与Follower保持heartBeat的联系;
• Follower(追随者) :响应 Leader 的日志同步请求,响应Candidate的邀票请求,以及把客户端请求到Follower的事务转发(重定向)给Leader;
• Candidate(候选者) :负责选举投票,集群刚启动或者Leader宕机时,状态为Follower的节点将转为Candidate并发起选举,选举胜出(获得超过半数节点的投票)后,从Candidate转为Leader状态。
通常,Raft 集群中只有一个 Leader,其它节点都是 Follower。Follower 都是被动的,不会发送任何请求,只是简单地响应来自 Leader 或者 Candidate 的请求。Leader 负责处理所有的客户端请求(如果一个客户端和 Follower 联系,那么 Follower 会把请求重定向给 Leader)。
为简化逻辑和实现,Raft 将一致性问题分解成了三个相对独立的子问题。
• 选举(Leader Election) :当 Leader 宕机或者集群初创时,一个新的 Leader 需要被选举出来;
• 日志复制(Log Replication) :Leader 接收来自客户端的请求并将其以日志条目的形式复制到集群中的其它节点,并且强制要求其它节点的日志和自己保持一致;
• 安全性(Safety) :如果有任何的服务器节点已经应用了一个确定的日志条目到它的状态机中,那么其它服务器节点不能在同一个日志索引位置应用一个不同的指令。
根据 Raft 协议,一个应用 Raft 协议的集群在刚启动时,所有节点的状态都是 Follower。由于没有 Leader,Followers 无法与 Leader 保持心跳(Heart Beat),因此,Followers 会认为 Leader 已经下线,进而转为 Candidate 状态。然后,Candidate 将向集群中其它节点请求投票,同意自己升级为 Leader。如果 Candidate 收到超过半数节点的投票(N/2 + 1),它将获胜成为 Leader。
第一阶段:所有节点都是 Follower。
上面提到,一个应用 Raft 协议的集群在刚启动(或 Leader 宕机)时,所有节点的状态都是 Follower,初始 Term(任期)为 0。同时启动选举定时器,每个节点的选举定时器超时时间都在 100~500 毫秒之间且并不一致(避免同时发起选举)。
第二阶段:Follower 转为 Candidate 并发起投票。
没有 Leader,Followers 无法与 Leader 保持心跳(Heart Beat),节点启动后在一个选举定时器周期内未收到心跳和投票请求,则状态转为候选者 Candidate 状态,且 Term 自增,并向集群中所有节点发送投票请求并且重置选举定时器。
注意,由于每个节点的选举定时器超时时间都在 100-500 毫秒之间,且彼此不一样,以避免所有 Follower 同时转为 Candidate 并同时发起投票请求。换言之,最先转为 Candidate 并发起投票请求的节点将具有成为 Leader 的“先发优势”。
第三阶段:投票策略。
节点收到投票请求后会根据以下情况决定是否接受投票请求(每个 follower 刚成为 Candidate 的时候会将票投给自己):
请求节点的 Term 大于自己的 Term,且自己尚未投票给其它节点,则接受请求,把票投给它;
请求节点的 Term 小于自己的 Term,且自己尚未投票,则拒绝请求,将票投给自己。
第四阶段:Candidate 转为 Leader。
一轮选举过后,正常情况下,会有一个 Candidate 收到超过半数节点(N/2 + 1)的投票,它将胜出并升级为 Leader。然后定时发送心跳给其它的节点,其它节点会转为 Follower 并与 Leader 保持同步,到此,本轮选举结束。
注意:有可能一轮选举中,没有 Candidate 收到超过半数节点投票,那么将进行下一轮选举。
在一个 Raft 集群中,只有 Leader 节点能够处理客户端的请求(如果客户端的请求发到了 Follower,Follower 将会把请求重定向到 Leader) ,客户端的每一个请求都包含一条被复制状态机执行的指令。Leader 把这条指令作为一条新的日志条目(Entry)附加到日志中去,然后并行得将附加条目发送给 Followers,让它们复制这条日志条目。
当这条日志条目被 Followers 安全复制,Leader 会将这条日志条目应用到它的状态机中,然后把执行的结果返回给客户端。如果 Follower 崩溃或者运行缓慢,再或者网络丢包,Leader 会不断得重复尝试附加日志条目(尽管已经回复了客户端)直到所有的 Follower 都最终存储了所有的日志条目,确保强一致性。
第一阶段:客户端请求提交到 Leader。
如下图所示,Leader 收到客户端的请求,比如存储数据 5。Leader 在收到请求后,会将它作为日志条目(Entry)写入本地日志中。需要注意的是,此时该 Entry 的状态是未提交(Uncommitted),Leader 并不会更新本地数据,因此它是不可读的。
第二阶段:Leader 将 Entry 发送到其它 Follower
Leader 与 Followers 之间保持着心跳联系,随心跳 Leader 将追加的 Entry(AppendEntries)并行地发送给其它的 Follower,并让它们复制这条日志条目,这一过程称为复制(Replicate)。
有几点需要注意:
1 为什么 Leader 向 Follower 发送的 Entry 是 AppendEntries 呢?
因为 Leader 与 Follower 的心跳是周期性的,而一个周期间 Leader 可能接收到多条客户端的请求,因此,随心跳向 Followers 发送的大概率是多个 Entry,即 AppendEntries。当然,在本例中,我们假设只有一条请求,自然也就是一个Entry了。
2 Leader 向 Followers 发送的不仅仅是追加的 Entry(AppendEntries)。
在发送追加日志条目的时候,Leader 会把新的日志条目紧接着之前条目的索引位置(prevLogIndex), Leader 任期号(Term)也包含在其中。如果 Follower 在它的日志中找不到包含相同索引位置和任期号的条目,那么它就会拒绝接收新的日志条目,因为出现这种情况说明 Follower 和 Leader 不一致。
3 如何解决 Leader 与 Follower 不一致的问题?
在正常情况下,Leader 和 Follower 的日志保持一致,所以追加日志的一致性检查从来不会失败。然而,Leader 和 Follower 一系列崩溃的情况会使它们的日志处于不一致状态。Follower可能会丢失一些在新的 Leader 中有的日志条目,它也可能拥有一些 Leader 没有的日志条目,或者两者都发生。丢失或者多出日志条目可能会持续多个任期。
要使 Follower 的日志与 Leader 恢复一致,Leader 必须找到最后两者达成一致的地方(说白了就是回溯,找到两者最近的一致点),然后删除从那个点之后的所有日志条目,发送自己的日志给 Follower。所有的这些 *** 作都在进行附加日志的一致性检查时完成。
Leader 为每一个 Follower 维护一个 nextIndex,它表示下一个需要发送给 Follower 的日志条目的索引地址。当一个 Leader 刚获得权力的时候,它初始化所有的 nextIndex 值,为自己的最后一条日志的 index 加 1。如果一个 Follower 的日志和 Leader 不一致,那么在下一次附加日志时一致性检查就会失败。在被 Follower 拒绝之后,Leader 就会减小该 Follower 对应的 nextIndex 值并进行重试。最终 nextIndex 会在某个位置使得 Leader 和 Follower 的日志达成一致。当这种情况发生,附加日志就会成功,这时就会把 Follower 冲突的日志条目全部删除并且加上 Leader 的日志。一旦附加日志成功,那么 Follower 的日志就会和 Leader 保持一致,并且在接下来的任期继续保持一致。
第三阶段:Leader 等待 Followers 回应。
Followers 接收到 Leader 发来的复制请求后,有两种可能的回应:
写入本地日志中,返回 Success;
一致性检查失败,拒绝写入,返回 False,原因和解决办法上面已做了详细说明。
需要注意的是,此时该 Entry 的状态也是未提交(Uncommitted)。完成上述步骤后,Followers 会向 Leader 发出 Success 的回应,当 Leader 收到大多数 Followers 的回应后,会将第一阶段写入的 Entry 标记为提交状态(Committed),并把这条日志条目应用到它的状态机中。
第四阶段:Leader 回应客户端。
完成前三个阶段后,Leader会向客户端回应 OK,表示写 *** 作成功。
第五阶段,Leader 通知 Followers Entry 已提交
Leader 回应客户端后,将随着下一个心跳通知 Followers,Followers 收到通知后也会将 Entry 标记为提交状态。至此,Raft 集群超过半数节点已经达到一致状态,可以确保强一致性。
需要注意的是,由于网络、性能、故障等各种原因导致“反应慢”、“不一致”等问题的节点,最终也会与 Leader 达成一致。
前面描述了 Raft 算法是如何选举 Leader 和复制日志的。然而,到目前为止描述的机制并不能充分地保证每一个状态机会按照相同的顺序执行相同的指令。例如,一个 Follower 可能处于不可用状态,同时 Leader 已经提交了若干的日志条目;然后这个 Follower 恢复(尚未与 Leader 达成一致)而 Leader 故障;如果该 Follower 被选举为 Leader 并且覆盖这些日志条目,就会出现问题,即不同的状态机执行不同的指令序列。
鉴于此,在 Leader 选举的时候需增加一些限制来完善 Raft 算法。这些限制可保证任何的 Leader 对于给定的任期号(Term),都拥有之前任期的所有被提交的日志条目(所谓 Leader 的完整特性)。关于这一选举时的限制,下文将详细说明。
在所有基于 Leader 机制的一致性算法中,Leader 都必须存储所有已经提交的日志条目。为了保障这一点,Raft 使用了一种简单而有效的方法,以保证所有之前的任期号中已经提交的日志条目在选举的时候都会出现在新的 Leader 中。换言之,日志条目的传送是单向的,只从 Leader 传给 Follower,并且 Leader 从不会覆盖自身本地日志中已经存在的条目。
Raft 使用投票的方式来阻止一个 Candidate 赢得选举,除非这个 Candidate 包含了所有已经提交的日志条目。Candidate 为了赢得选举必须联系集群中的大部分节点。这意味着每一个已经提交的日志条目肯定存在于至少一个服务器节点上。如果 Candidate 的日志至少和大多数的服务器节点一样新(这个新的定义会在下面讨论),那么它一定持有了所有已经提交的日志条目(多数派的思想)。投票请求的限制中请求中包含了 Candidate 的日志信息,然后投票人会拒绝那些日志没有自己新的投票请求。
Raft 通过比较两份日志中最后一条日志条目的索引值和任期号,确定谁的日志比较新。如果两份日志最后条目的任期号不同,那么任期号大的日志更加新。如果两份日志最后的条目任期号相同,那么日志比较长的那个就更加新。
如同 41 节介绍的那样,Leader 知道一条当前任期内的日志记录是可以被提交的,只要它被复制到了大多数的 Follower 上(多数派的思想)。如果一个 Leader 在提交日志条目之前崩溃了,继任的 Leader 会继续尝试复制这条日志记录。然而,一个 Leader 并不能断定被保存到大多数 Follower 上的一个之前任期里的日志条目 就一定已经提交了。这很明显,从日志复制的过程可以看出。
鉴于上述情况,Raft 算法不会通过计算副本数目的方式去提交一个之前任期内的日志条目。只有 Leader 当前任期里的日志条目通过计算副本数目可以被提交;一旦当前任期的日志条目以这种方式被提交,那么由于日志匹配特性,之前的日志条目也都会被间接的提交。在某些情况下,Leader 可以安全地知道一个老的日志条目是否已经被提交(只需判断该条目是否存储到所有节点上),但是 Raft 为了简化问题使用了一种更加保守的方法。
当 Leader 复制之前任期里的日志时,Raft 会为所有日志保留原始的任期号,这在提交规则上产生了额外的复杂性。但是,这种策略更加容易辨别出日志,即使随着时间和日志的变化,日志仍维护着同一个任期编号。此外,该策略使得新 Leader 只需要发送较少日志条目。
raft 的读写都在 leader 节点中进行,它保证了读的都是最新的值,它是符合强一致性的(线性一致性),raft 除了这个还在客户端交互那块也做了一些保证,详情可以参考论文。但是 zookeeper 不同,zookeeper 写在 leader,读可以在 follower 进行,可能会读到了旧值,它不符合强一致性(只考虑写一致性,不考虑读一致性),但是 zookeeper 去 follower 读可以有效提升读取的效率。
对比于 zab、raft,我们发现他们选举、setData 都是需要过半机制才行,所以他们针对网络分区的处理方法都是一样的。
一个集群的节点经过网络分区后,如一共有 A、B、C、D、E 5个节点,如果 A 是 leader,网络分区为 A、B、C 和 D、E,在A、B、C分区还是能正常提供服务的,而在 D、E 分区因为不能得到大多数成员确认(虽然分区了,但是因为配置的原因他们还是能知道所有的成员数量,比如 zk 集群启动前需要配置所有成员地址,raft 也一样),是不能进行选举的,所以保证只会有一个 leader。
如果分区为 A、B 和 C、D、E ,A、B 分区虽然 A 还是 leader,但是却不能提供事务服务(setData),C、D、E 分区能重新选出 leader,还是能正常向外提供服务。
1)我们所说的日志(log)与状态机(state machine)不是一回事,日志指还没有提交到状态机中的数据。
2)新 leader 永远不会通过计算副本数量提交旧日志,他只能复制旧日志都其他 follower 上,对于旧日志的提交,只能是新 leader 接收新的写请求写新日志,顺带着把旧日志提交了。
个人理解:
任期的作用:
当一个 candidate 获得集群中过半服务器节点针对同一个任期的投票,它就赢得了这次选举并成为 leader 。
对于同一个任期,每个服务器节点只会投给一个 candidate ,按照先来先服务(first-come-first-served)的原则(注意:54 节在投票上增加了额外的限制)。
要求获得过半投票的规则确保了最多只有一个 candidate 赢得此次选举(图 3 中的选举安全性)。
一旦 candidate 赢得选举,就立即成为 leader 。然后它会向其他的服务器节点发送心跳消息来确定自己的地位并阻止新的选举。
在等待投票期间,candidate 可能会收到另一个声称自己是 leader 的服务器节点发来的 AppendEntries RPC 。
如果这个 leader 的任期号(包含在RPC中)不小于 candidate 当前的任期号,那么 candidate 会承认该 leader 的合法地位并回到 follower 状态。
如果 RPC 中的任期号比自己的小,那么 candidate 就会拒绝这次的 RPC 并且继续保持 candidate 状态。
第三种可能的结果是 candidate 既没有赢得选举也没有输:如果有多个 follower 同时成为 candidate ,那么选票可能会被瓜分以至于没有 candidate 赢得过半的投票。
当这种情况发生时,每一个候选人都会超时,然后通过增加当前任期号来开始一轮新的选举。然而,如果没有其他机制的话,该情况可能会无限重复。
Raft 算法使用随机选举超时时间的方法来确保很少发生选票瓜分的情况,就算发生也能很快地解决。为了阻止选票一开始就被瓜分,选举超时时间是从一个固定的区间(例如 150-300 毫秒)随机选择。这样可以把服务器都分散开以至于在大多数情况下只有一个服务器会选举超时;然后该服务器赢得选举并在其他服务器超时之前发送心跳。同样的机制被用来解决选票被瓜分的情况。每个 candidate 在开始一次选举的时候会重置一个随机的选举超时时间,然后一直等待直到选举超时;这样减小了在新的选举中再次发生选票瓜分情况的可能性。93 节展示了该方案能够快速地选出一个 leader 。
个人理解:
选举其实看其实PK3个指标:term、LastLogIndex、lastLogTerm,至少要不比自己小才会vote给你,实际上这并不能保证最新的数据
因为vote RPC里面,只有lastLogIndex和Term,可能leader里面的lastLog是未提交的状态,但是其它follower的状态是提交的。所以新一任leader上任之前做的第一件事就是发一个心跳,update lastCommitIndex
follow在一定时间内未接收到leader发过来的heartbeat,超时后自己状态成为候选者,并且向其它的所有节点发起一个投票消息
一个完整的投票消息包括4个参数
receiver节点:先判断term,再判断日志是否是最新的。至少任期以及日志记录,不比自己旧,才会投票给你。所以过时的节点不会得到大多数的投票。
candidate之所以会发起选举,是因为没有收到leader的心跳,但是在选举期间又重新收到心跳会如何?
论文中描述,当重新受到leader的心跳时会判断term,至少不能比自己小,也就是说,即使是因为自己网络原因没有收到心跳而发起投票,也不会终止这次投票,因为老leader的term比现在的要小,自己是自增了一次的。
但是如果在投票等待期间,已经有新的leader产生,并且接收到leader的 appending的RPC时,candidate会放弃投票,因为term不小于当前candidate,说明这个leader不是老leader,要么和自己是同一个term的leader,要么比自己更新term的leader。
所以理论上存在某一个follower的节点因为网路延迟而发起leader申请,并且还有可能成功顶替leader的可能性,即使leader的功能正常,是这个follower自己的网络突然发生了延迟。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)