各大中间件底层技术-分布式一致性协议 Raft 详解

前言

正式介绍 Raft 协议之前,我们先来举个职场产研团队的一个例子🌰。

方式一:

在一个技术团队内假设角色都是 均等的,会导致什么情况呢?产品提出一个需求,就可以随便去找团队中的任意一个人去发起需求。如果这个人因为请假走了,但是他没有把需求及时同步给团队其他人,因此会导致该需求存在很大的延迟。

方式二:

在技术团队中选举一个 ** Leader角色**,产品提出的需求必须优先提给 Leader,找 Leader 先沟通。Leader 自己消化完后,在将需求传达给团队其他成员。如果 Leader 请假了,会指定某一个人充当 Leader 角色负责接收产品需求,并将需求同步给其他成员。

上述很简单的案例,可以对应理解分布式系统中的数据一致性算法。

分布式系统数据一致性算法一般都应用在中间件项目中,平时你写的业务代码,一般根本不会接触到的。

SpringCloud 家族中集成了大名鼎鼎的注册中心 Eureka 就使用的上面的 方式一 的思路来完成节点之间数据同步的,称为 **Peer To Peer ** 集群架构。

Eureka 当中的每个节点的地位都是均等的,每个节点都可以接收写入请求,每个节点接收请求之后,进行请求打包处理,异步化延迟一点时间,将数据同步给 Eureka 集群当中的其他节点。任何一台节点宕机之后,理论上应该是不影响集群运行的,都可以从其他节点获取注册表信息。

另外的一些开源项目,比如 Etcd、Consul,Zookeeper,还有未来会流行的国产技术阿里开源的 Nacos 项目,其中的 CP 模式也是基于 Raft 协议 来实现分布式一致性算法的。当然 Zookeeper 在 Raft 协议基础上做了一些改良,使用的 ZAB 分布式一致性协议来实现的。

在 Raft 协议出现之前,广泛使用的一致性算法是 Paxos。Paxos 算法解决的是如何保障单一客户端操作的一致性,完成每个操作都需要至少两轮的消息交换。和 Paxos 算法不同,Raft 里有 Leader 的概念,Raft 在处理任何客户端操作之前必须要选举出来一个 leader 角色,选举一个 Leader 经过至少一轮的消息交换,但是选举了 Leader 之后,处理每个客户端的操作只需要一轮消息交换。

详解 Raft 协议

接下来,围绕如下几点展开:

1)Leader选举流程

2)节点心跳检测

3)选主超时操作

4)日志复制流程

5)日志条目结构

6)Leader崩溃恢复操作

1)Leader选举流程

如果系统中只有唯一一个节点,读写操作都由这一个节点来负责,不会存在数据不一致的问题。

但是,对于分布式系统会存在至少两个节点,需要有一个角色来充唯一的节点,我们把这个唯一节点叫做 Leader 节点,其他节点叫做 Follower 节点, leader 选举过程中才会有的状态叫做 Candidate 节点

三种状态

为了便于理解和说明,假设集群中有三个节点:节点A、节点B、节点C。

集群启动后,初始节点状态都是 Follower 状态。最开始各个节点启动之后,此时集群内还没有 Leader。

选主流程

如果每次各节点都投票给自己,Leader 会始终无法选出来,这样僵持下去肯定是不行的。所以出现了 electionTimeout 的概念,称为 选举超时时间 每个节点都会有 electionTimeout。

一旦发现一轮投票没有结果,集群中各节点自身设置一个 electionTimeout,时间范围在 150ms ~ 300ms 之间的一个随机值。

选主流程

当节点 A 的 electionTimeout 时间到了,会将节点 A 状态变为 Candidate 状态,节点 A 苏醒过来,很Happy,终于可以参与竞选了,立马给自己投了一票 。然后向集群中其他两个节点发起选举投票(Http协议或者RPC协议请求都可以)。

节点B、C可以认为处于 Follower 状态,如何进行选举投票呢?

这里就出现了 term 的概念,每个节点都会有个 term,term 表示任期,跟美国总统竞选里的任期类似。term 是一个全局且连续递增的整数,每完成一次选举,term会递增 +1。

Follower 状态的节点此时收到 Candidate 选举请求,发现集群中就只有一个 Candidate 状态的节点 A,所以直接投票给 Candidate 节点。

这里就出现了 voteFor 的概念,每个节点都会有voteFor,voteFor 表示投票的数据。

节点 A 收到投票反馈之后,引出了另外的判断条件:必须是集群中过半节点都投票给自己,也包括自己。

算式:n / 2 + 1

举个🌰:

集群中有三个节点,3 / 2 + 1 = 2 必须有 2 个节点投票成功。

集群中有五个节点,5 / 2 + 1 = 3 必须有 3 个节点投票成功。

最终 Candidate 状态的节点 A 很荣幸晋升为 Leader。

2)节点心跳检测
心跳检查

我们看到上述的状态流转图,当重新选举 Leader 时,各节点自身的状态会在 Candidate、Follower、Leader 之间不断的变换。

所以,被选举的节点 A 成为了 Leader ,为了保持它的『统治』地位,要不断的向其他节点发送心跳,告诉他们「我还活着,我还活着... 」。

其他节点 B、C 收到心跳请求后,会返回响应,发现原来 Leader 还在,不需要发起选举,同时要重置选举超时时间 electionTimeout。

如果作为 Leader 的节点 A 因为意外,比如网络问题无法正常发送心跳给其他节点。

此时,节点 B 的 electionTimeout 超时时间到了,变成了 Candidate 状态,term + 1,voteFor 投票给自己,并发送投票请求给节点 A、C。

之前还是 Leader 的节点 A 收到了节点 B 发来的投票请求,发现 term 任期不是最大的了,更新自身的 term 值为节点 B 的 term,从 Leader 状态切换为 Follower 状态,并重置 electionTimeout。

3)选主超时操作

如果恰巧有两个节点同时都发起了投票,但都没有获得多数节点的选票,此时就得打加时赛了,直到获得多数选票的节点成为 Leader。

如下图所示:

选举超时

假设集群中有4个节点,A、B、C、D。出现了两个节点处于 Candidate 状态。

第一轮选举状态如下:

节点A[Candidate] <----- 节点 C 选举节点A

节点B[Candidate] <----- 节点 D 选举节点B

第一轮选举之后,节点 A 和节点 B 还是处于 Candidate 状态,都不满足集群过半数投票成功的条件,选举 Leader 失败。

下一轮就要看 Candidate 状态的节点谁的 electionTimeout 先到,谁就先发起新一轮选举操作。只要发起选举,term 任期就要递增的。

如果节点 B 的 electionTimeout 先到,先发起选举操作,那么集群中按照条件必须是有 3 个节点投票成功之后最终成为 Leader。

为了避免出现上述这种平局的情况,所以一般集群中节点部署个数都是奇数: ** 2n + 1**

4)日志复制流程

当 Leader 选出来之后,Client 客户端发起的写请求都会由 Leader 节点来处理。

即使其他的 Follower 节点收到了 Client 客户端的请求,也会将请求转交给 Leader 来处理。

日志复制流程

Leader 接收到 Client 的请求之后,会优先将数据写入到自身节点的 log 日志文件中。

被写入到日志文件里的日志条目,被称为 **append entries **。

Leader 将发送 append entries 消息给其他节点,这里并不是每次都将相同的日志条目,都发送给其他节点,而是根据节点的不同对应的消息也是不同的。因为集群中节点之间数据可能会有不一致的情况。

其他 Follower 节点收到 Leader 的消息后,将数据添加到本地,然后返回给 Leader 响应,确认消息已收到。

Leader 节点收到其他节点确认消息且过半数,先 Commit 提交记录,返回 Client 客户端响应。

然后,Leader 节点再次向其他 Follower 节点发送 Commit 提交数据通知。

其他 Follower 节点收到通知后,Commit 提交自身的日志条目数据,返回 Leader 更新结束的通知。

日志条目提交保证两点:

容错:

在数量少于 Raft 服务器节点总数一半的 Follower 失败的情况下 ,Raft 集群仍然可以正常处理来自客户端的请求。

确保重叠:

一旦 Leader 响应了一个客户端的请求,即使出现了 Raft 集群中少数服务器的失败,也会有一个服务器包含所有以前提交的日志条目。

5)日志条目结构

每个节点都会有两个索引,日志条目最终提交完成,提交的索引值称为 ** commitIndex,另外一个是目前最后一行索引值,称为 lastApplied**

Leader 节点除了存储上面提到的 commitIndex 和 lastApplied之外,还需要存储其他 Follower 节点的数据情况。

一个是 nextIndex 记录的是 Leader 里有,Follower 节点没有的数据索引,即需要发送 append entries 的数据索引。

另外一个是 **matchIndex **,记录的是 Leader 节点已知,已经复制给他 Follower 节点日志的最高索引值。

当数据变更时, Leader 向其他节点发送不同的 append entries 消息数据。

如果重新选举了 Leader,新的 Leader 并不知道原来 Leader 的 nextIndex 和 matchIndex 两个数据,会将自身节点的 nextIndex 重置为 commitIndex,matchIndex 则全部重置为 0。

6)选主后日志同步

当集群中的 Leader 接收到 Client 请求,发现部分节点因为网络问题无法正常通讯,所以日志不能复制给多数节点,日志无法正常提交。

如下图所示:

集群中断流程

其他 Follower 节点无法正常收到 Leader 心跳检测请求,会发生重新选主操作。

当新的 Leader1 产生,原来的 Leader2 集群恢复之后,发现自己不是选票最多的节点,即 term 不是最大的,节点状态切换到 Follower,回滚未提交的日志。

新的 Leader1 重新将同步日志重新同步给其他 Follower 节点。

如下图所示 :

集群恢复流程

总结

经过对 Raft 协议的详细分析,我们能够总结到,其实 Raft 协议就是 2PC (两阶段提交) + 集群过半节点写机制 结合实现的。

1、每个节点都有 electionTimeout 选举超时时间,用于当 Follower 节点无法收到心跳时,到期发起选举 Leader 的操作。

2、Leader 节点选举时,最终选出来的 Leader 的 term 任期一定是最大的。

3、Leader 节点给其他 Follower 节点发送 append enties 日志条目时,针对不同的节点发送不同的日志消息。

4、Leader 接收客户端的写入请求,此时处于 uncommitted 状态,Leader 将 uncommitted 消息发送给其他 Follower 节点,作为第一阶段

5、其他 Follower 节点收到 uncommitted 请求之后,返回确认 ack 响应给 Leader,如果 Leader 收到了过半数 Follower 节点的 ack 响应,则将请求标记为 committed,同时发送 committed 请求发送给其他 Follower 节点,让其他节点对消息进行 committed。作为第二阶段 + 过半数机制

另外刚刚文中提到阿里开源的 Nacos 注册中心,其内部自己实现了一套简化版的 Raft 协议。看了 Nacos 官方的 Roadmap 能够了解到,规划在 nacos 1.7.0 GA 中会替换掉现在的 Raft 协议实现,有可能会采用蚂蚁金服开源的SOFA-JRaft,SOFA-JRaft 基于 Raft 一致性算法的生产级高性能 Java 实现,支持 MULTI-RAFT-GROUP,适用于高负载低延迟的场景。

参考:

http://ifeve.com/raft/
http://thesecretlivesofdata.com/raft/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容