学习自以下文章:
- Raft 一致性协议完整解析 - 知乎 (zhihu.com)
- Consul源码分析------Raft实现 - 简书 (jianshu.com)
- 一文搞懂Raft算法 - xybaby - 博客园 (cnblogs.com)
什么是raft?
- raft是工程中较为常用的强一致性,去中心化,高可用的协议,是学者觉得Paxos太难以理解后,研究出来的,raft协议就是一种leader-based(基于领导者)的共识算法。
- 简单概括下:raft会选举出领导者,领导者负责replicated log(复制日志)的管理,领导者负责接受所有客户端更新请求,然后复制到从节点,并在"安全"的时候执行这些请求。如果领导者故障,从节点会重新选举出新的领导者。
选举:
选举发生的条件
raft协议中每个节点都有三种状态: * 领导者 * 跟随者 * 选举者 可以看出所有节点启动时都是跟随者状态;在一段时间内如果没有收到来自领导者的心跳,从跟随者切换到选举者,发起选举。 任期term:raft 将系统时间划分为一个个逻辑段, 每个逻辑段的时间长度是不一致的, 可以是任意长度, 每一个逻辑段称为一个任期(term), raft 对每一个任期都设置一个整型编号, 称为任期号, 每一个任期可以进一步划分为两个子段, 其中第一个子段是选举期, 第二个子段是任职期, 选举期将竞选产生集群的领导人, 若领导人选举成功, 则进入了任职期, 在任职期内只要领导人持续保持健康状态(即持续不间断地向其他跟随者发送心跳包), 则这个时期可以无限期地持续, 当然在 raft 中选举不一定都是成功的, 可能存在某个 term 中的选举期没有任何候选人胜出, 这样 raft 会进行下一个 term, 重新进行选举, 直到有新的领导人胜出, 从而进入任职期, 下面我们将看到 raft 采用了特别的机制来尽可能地避免一个 term 中没有任何候选人竞选成功的情形出现
选举的过程:
前面已经说过,如果跟随者在一个规定时间内没有收到来自领导者的心跳,(也许此时还没有选出领导者,大家都在等;也许领导者挂了;也许只是领导者与该跟随者之间网络故障),则会主动发起选举。步骤如下:
- 增加节点本地的term+1,切换到选举者状态
- 投自己一票
- 并行给其他节点发送RequestVote RPCs(请求投票给自己)
- 等待其他节点的回复
在这个过程中,根据来自其他节点的消息,可能出现三种结果:
- 收到超过绝对多数的投票(含自己的一票),则赢得选举,成为领导者
- 被告知别人已当选,那么自行切换到跟随者
- 没发生上面两种情况,则保持选举者状态,重新发出选举 第一种情况,赢得了选举之后,新的领导者会立刻给所有节点发消息,广而告之,避免其余节点触发新的选举。在这里,先回到投票者的视角,投票者如何决定是否给一个选举请求投票呢,有以下约束:
- 在任一任期内,单个节点最多只能投一票
- 候选人知道的信息不能比自己的少(term不能低于自己的)
- first-come-first-served 先来先得
第二种情况,比如有三个节点A B C。A B同时发起选举,而A的选举消息先到达C,C给A投了一票,当B的消息到达C时,已经不能满足上面提到的第一个约束,即C不会给B投票,而A和B显然都不会给对方投票。A胜出之后,会给B,C发心跳消息,节点B发现节点A的term不低于自己的term,知道有已经有Leader了,于是转换成follower。
如果一直选举不成功咋办?
现象:当领导人宕机以后, 可能有多个跟随者都同时或几乎同时发现了这一状况, 他们都立即转变了为候选人, 并发起了选举, 最后可能出现大家的选票不相上下, 使得没有任何一个候选人胜出, 如若不采取其他措施, 系统将陷入死锁而无法挣脱出来;
为了解决问题, raft 协议对每一轮选举都设置了超时时间, 对于每一个跟随者, 从它转变为候选人发起选举的时刻, 会将选举计时器归零, 如若计时器超过了设定的超时时间, 系统中仍然没有新的领导人被选举出来, 则此轮选举失败, 候选人会将 currentTerm 再加一, 并发起新一轮的选举, 同时会重新将选举计时器再次归零, 这样可以避免系统出现死锁;
但是仅仅加入选举计时器是不够的, 比如说一直超时或者一直平票,这样的话,就会导致一直在选举,系统一直不可用。(试想这样一种场景, 某一时刻, 系统中的领导人发生宕机, 跟随者在指定的时间间隔内没有收到来自领导人的心跳信息, 于是转变自己的身份为候选人并发起选举, 原先的跟随者在几乎相同的时刻发起了选举, 即便设置了超时计时器, 也可能出现候选人几乎同时到达选举超时的情况出现, 此时所有候选人又几乎同时发起了新一轮选举, 如此这样下去, 系统会耗费大量的时间在领导人选举上, 即如若不采取额外的措施, 系统会发生大量的「选举碰撞」)
为了避免这种情况的出现, raft 将选举超时时间改为了随机化, 每个结点都在一个固定的时间长度内随机选定一个选举超时时间, 这样即便大家同时发起选举, 由于每个结点的选举超时时间是不同的, 因为系统中一定存在一个结点会首先达到选举超时, 这时它又发起新一轮的选举, 由于每次选举都会将 currentTerm 加一, 所以最早达到选举超时的结点再发起新一轮选举后, 它的 currentTerm 比系统中的其它候选人结点都大, 从而赢得选举, 随机化不仅仅用在选举超时时长上, 也用在了两次选举之间的等待时长上, 对于候选者来说, 当一轮选举达到超时时间, 并且没有选出领导人后, 它不会马上开始下一轮选举, 而是等待一个随机的时长, 等待一段时间后重新发起新一轮选举, 这样以来, 不同候选者之间发生选举碰撞的概率大大减少, 这里的思想其实很类似于以太网的 CSMA/CD 协议(载波监听多点接入/碰撞检测协议), 都是用随机化的等待时长来尽可能地减少碰撞
日志复制
当有了leader,系统应该进入对外工作期了。客户端的一切请求来发送到leader,leader来调度这些并发请求的顺序,并且保证leader与followers状态的一致性。raft中的做法是,将这些请求以及执行顺序告知followers。leader和followers以相同的顺序来执行这些请求,保证状态一致。
相同的初识状态 + 相同的输入 = 相同的结束状态 在raft中,领导者将客户端请求(command)封装到一个个log entry,将这些log entries复制(replicate)到所有跟随者节点,然后大家按相同顺序应用(apply)log entry中的command,则状态肯定是一致的。
raft怎么保证一致性(也就是raft的原理)?
通过选举领导者,日志复制