Paxos 算法难理解?来看看大家都在用的 Raft 算法 - 今日头条

本文由 简悦 SimpRead 转码, 原文地址 www.toutiao.com

前面几篇文章介绍了 Paxos 算法,一直是分布式协议的标准,但 Paxos 算法难于理解,更难以实现。Raft 算法在 2013 年发表,就是建立在希望得

分布式共识系列文章:

前面几篇文章介绍了 Paxos 算法,一直是分布式协议的标准,但 Paxos 算法难于理解,更难以实现。即使 Google 的分布式锁系统 Chubby 以 Paxos 算法实现,也遇到了很多坑。为了简化分布一致性算法,斯坦福大学提出了一种 Raft 算法。

Raft 算法在 2013 年发表,就是建立在希望得到一个更易于理解的 Paxos 算法的替代品。论文题目《In Search of an Understandable Consensus Algorithm》就可以看出来,可理解性是 Raft 算法的主要目标之一。到现在已经有了十多种语言的 Raft 算法的实现框架,最为出名的就是 Etcd。

Etcd 是 CoreOS 团队于 2013 年 6 月发起的开源项目,它的目标是构建一个高可用的分布式键值 (key-value) 数据库。

在一个由 Raft 协议组织的集群中有三类角色:Leader(领袖)、Follower(群众)、Candidate(候选人)。

https://p3.toutiaoimg.com/origin/pgc-image/ca71f32fc5e640f8b73714a86de9ee80?from=pc

Raft 三类角色的状态转换

  • Leader:处理所有客户端交互,日志复制等。集群一般只能有一个领导,领导在位期间就是一个任期(Term) 。Leader 定期向 Follower 发送心跳,宣示自己的存在。
  • Follower:如果 Follower 在 election timeout 内没有收到来自 Leader 的心跳,那么会认为 Leader 挂掉了。Follower 会更新自己的当前任期(Current Term) 并成为 Candidate,并开始选举新的 Leader。
  • Candidate:候选人,有可能被选为一个新的领导人。如果收到了多数的投票就当选为 Leader。如果超过一定时间没有收到选票,就重新发起选举。

一个最小的 Raft 集群需要三个参与者,这样才可能投出多数票。如下图所示,初始状态下 ABC 三个节点开始选举 Leader。

https://p3.toutiaoimg.com/origin/pgc-image/598da7fd0b9a43258cfb1f80bb0db509?from=pc

Raft 协议 Leader 选举

Leader 选举过程中有三种可能的情形发生:

  • A 节点要投自己一票,并把提议(拉票请求)发给 B 和 C,B 和 C 接受了 A 的提议,也投票给 A。A 成为 Leader。
  • A 节点投了自己一票,B 节点也投了自己一票,C 节点支持 A 节点的提议,少数服从多数。A 成为 Leader。
  • A 节点、B 节点、C 节点都各自投了自己一票。没有节点获得多数票。

以上前二种情况都能选出 Leader,第三种情况本轮投票无效,出现了平票(Split Votes)。因为没有任何一方获得多数票,所以要发起新的一轮投票。

Raft 协议为了不让选举机制反复出现平票,定义了一个随机超时机制(randomized election timeouts) 。一旦发生平票,平票的节点会各自再来一次随机 timeout 倒数,然后重新拉票。先发起拉票的节点就可以优先获得了多数节点的投票。

Raft 协议强依赖 Leader 节点的可用性来确保集群数据的一致性。数据的流向只能从 Leader 节点向 Follower 节点转移。

  1. 当 Client 向集群 Leader 节点提交数据(v=3)后,Leader 节点接收到的数据处于未提交状态(Uncommitted)。
  2. 接着 Leader 节点会并发向所有的 Follower 节点复制数据并等待接收响应。
  3. Leader 节点确保至少集群中超过半数节点确认接收到数据。
  4. Leader 向 Client 发送 Ack 确认数据已接收,表明此时数据状态进入已提交(Committed)状态。Leader 节点向 Follower 节点发通知告知该数据状态(v=3)已提交。

https://p3.toutiaoimg.com/origin/pgc-image/6eead6e15ab94c0cbe0bd4994b0d6619?from=pc

数据从 Leader 向 Follower 转移过程

接下来我们看一下,如果在数据转移过程中,Leader 挂掉,对数据一致性会造成什么影响。

  1. 在数据到达 Leader 节点前,这个阶段 Leader 挂掉。显然不影响一致性。

https://p3.toutiaoimg.com/origin/pgc-image/fd680a3a24414dc78d126adf9a9e3429?from=pc

数据到达 Leader 前,Leader 挂掉

  1. 当数据到达 Leader 节点,但未复制给 Follower 节点。这个阶段 Leader 挂掉,数据属于未提交状态。Client 没有收到 Ack 确认会认为超时失败,于是发起重试。

Follower 节点重新选主,新的 Leader 上没有该数据。Client 重新提交数据到新 Leader 可以成功。原来的 Leader 节点恢复后作为 Follower 加入集群,从当前任期的新 Leader 同步数据,保持和 Leader 数据一致。

https://p3.toutiaoimg.com/origin/pgc-image/9a393c1b49144c7c8f1841e2eac6cf41?from=pc

数据到达 Leader,处于未提交状态

3 当数据到达 Leader 节点,并成功复制给所有 Follower 节点,但 Follower 节点尚未向 Leader 响应接收。此时 Leader 挂掉。

虽然数据在 Follower 节点处于未提交状态(Uncommitted),但已经保持一致。重新选出 Leader 后可完成数据提交。此时 Client 由于不知到底提交成功没有,会重试提交。针对这种情况 Raft 要求 RPC 请求实现幂等性,也就是要实现内部去重机制。

https://p3.toutiaoimg.com/origin/pgc-image/59e2d63b4b8e4bb79a939e3efd04d73e?from=pc

当数据到达 Leader 节点,并成功复制给所有 Follower 节点,但尚未提交

  1. 数据到达 Leader 节点,并成功复制给 Follower 部分节点,但还未向 Leader 响应接收。在这个阶段 Leader 挂掉,数据在 Follower 节点处于未提交状态(Uncommitted)且数据不一致。

Raft 协议要求投票只能投给拥有最新数据的节点。这样拥有最新数据的节点会被选为 Leader,再同步数据到 Follower,数据不会丢失并实现最终一致。

5 数据到达 Leader 节点,并成功复制给 Follower 所有或多数节点,数据在 Leader 处于已提交状态,但在 Follower 处于未提交状态。这个阶段 Leader 挂掉,重新选出新 Leader 后的处理流程和上面第 3 种情况一样。

https://p3.toutiaoimg.com/origin/pgc-image/f010b8a1536e450fb6dd519d39e700b5?from=pc

数据在 Leader 节点处于已提交,在 Follower 处于未提交状态

6 数据到达 Leader 节点,成功复制到 Follower 所有或多数节点,数据在所有节点都处于已提交状态,但由于某种原因,Client 未收到 Ack 响应。这个阶段 Leader 挂掉,集群内部数据其实已经是一致的,Client 重新提交数据,基于幂等策略对一致性无影响。

https://p3.toutiaoimg.com/origin/pgc-image/15bfd612c0e842f08f2db41802eb74fd?from=pc

集群内数据已达成一致

从上面的讨论可知,在 Leader 向 Follower 进行数据同步的不同阶段,Leader 挂掉也不会影响数据的最终一致性。那么如果由于网络分区,导致双 Leader 出现,即所谓脑裂的情况,那么对数据一致性会有影响吗?

所谓脑裂,就是集群中出现了双 Leader,这种情况通常是由于网络分区导致。一山不容二虎,一个集群也不能有二主。我们来看一下这种情况下,Raft 协议如何将集群恢复到正常。

  1. 网络分区将原先的 Leader 节点和 Follower 节点分隔开,由于 Follower 收不到 Leader 的心跳将发起选举产生新的 Leader。
  2. 这时就产生了双 Leader,原先的 Leader 独自在一个区,向它提交数据不可能复制到多数节点,所以永远提交不成功。
  3. 向新的 Leader 提交数据可以提交成功,网络恢复后旧的 Leader 发现集群中有了新 Leader,于是自己主动降级为 Follower,并从新 Leader 那里同步数据,最终达成集群数据一致。

https://p3.toutiaoimg.com/origin/pgc-image/c2b6b49512184e42bd2aca57d650c422?from=pc

脑裂最终达成一致

设计 Raft 算法的目的是实现一种可理解性较好的 Multi Paxos 算法。看了上面的讲解,大家是否也觉得 Raft 算法理解起来非常容易呢?

我会持续更新关于物联网、云原生以及数字科技方面的文章,用简单的语言描述复杂的技术,也会偶尔发表一下对 IT 产业的看法,请大家多多关注,欢迎留言和转发,希望与大家互动交流,谢谢。