分布式一致性算法-Raft

分布式一致性算法Raft

  • 分布式一致性问题
  • Raft算法细节
    • 节点状态
    • 节点状态演变
      • 选举leader过程
      • 日志复制过程
    • 选举leader
      • 初始的选举
      • 领导者故障后的选举
      • 拆分投票
    • 日志复制
    • 网络分区
  • 再看分布式一致性问题
  • 写在最后

分布式一致性问题

假设有一个单节点的系统,这个系统是一个数据库服务器,存储单个值。一个客户端往这个服务器发送一个值。在这个单节点系统中,这个值是很容易维护的,不会产生二义性,客户端设置的值是多少,服务器就存多少。

假设现在有多个节点的服务器组成的系统,就会有分布式一致性问题。这些节点服务器是对等的,客户端可以连接其中任意一个节点服务器来获取服务。考虑这样的场景:一个客户端往其中一个节点服务器设置了一个值;之后,客户端无论访问哪一个节点服务器,都能获取到这个值。

按照常理,客户端往其中一个节点服务器设置了值以后,这个节点服务器应该把这个值拷贝到其他节点服务器,那么客户端再访问任意一个服务器节点就能获取到这个值了。
但是,有很多种情况会导致上述情况不会如你所愿。假设三个服务器节点,分别为服务器A、B、C:

  • 情况1,客户端往A写了一个值5,那么A会通过网络向B和C拷贝这个值,在这个拷贝刚刚开始或者还未完成之前,客户端就向B去请求这个值,得到的值就可能是错误的。
  • 情况2,服务器B故障了(离线),此时客户端往A写了一个值,A通过网络把这个值拷贝给C,无法拷贝给B。之后B修复后上线,在数据还未同步给B之前,客户端向B请求这个值,得到错误的值。还有,这个值应该由谁同步给B呢?假设是A负责同步给B,而与此同时,客户端请求C将这个值改变了,在这个值还未同步给A之前,A将该值拷贝给B,B得到的就不是最新的值,此时向B请求到的值就不对。

上述的情况,就是分布式一致性问题范畴,即一个共识问题,所有服务器节点要对某个状态,某个值达成共识,给客户返回一样的结果(一致的结果)。
带着这些问题,下面看Raft算法的实现细节,想想它是如何解决这些问题的。

Raft算法细节

Raft算法是一种分布式系统中的一致性算法,提供和Paxos(点击了解Paxos)相同级别的功能和性能,2014年首次发表。Raft因其易于理解和实现的特性,已经被广泛应用于多种分布式系统中,包括分布式数据库、分布式文件系统、服务发现系统等。一些知名的使用Raft算法的系统和项目包括:

  • Etcd:一个高可用的键值存储,用于配置共享和服务发现,广泛用于Kubernetes。
  • Consul:一种服务网格解决方案,提供服务发现、配置和分段功能。
  • CockroachDB:一个分布式SQL数据库,设计目标是提供全球数据强一致性。

下面详细介绍Raft算法实现细节

节点状态

用一个实心圆表示一个节点,如图:
          实心圆
每个节点有三种状态

  1. 追随者(Follower),无边框的实心圆表示,如图:
    在这里插入图片描述
  2. 候选者(candidate),领导者的候选者,虚线边框的实心圆表示,如图:
    在这里插入图片描述
  3. 领导者(leader),实现边框的实心圆表示,如图:
    在这里插入图片描述

节点状态演变

选举leader过程

领导者选举(Leader Election),本小节只是简单描述领导者(leader)的选举过程,大家在本小节中只需大致了解选举领导者(leader)的过程,重点在关注节点的状态变化。后续有专门的章节详细描述领导者(leader)选举的细节。

  1. 所有节点初始都为追随者(follower)状态(无边框的实心圆):
    在这里插入图片描述
  2. 如果追随者(follower)发现没有领导者(leader)存在,那么就会变为候选者(candidate):
    在这里插入图片描述
  3. 候选者(candidate)向其他节点发出投票请求:请投票给我,让我成为领导者(leader):
    在这里插入图片描述
  4. 其他节点回复投票,如果候选者(candidate)节点得到大多数的投票,它就成为了领导者(leader):
    在这里插入图片描述

日志复制过程

在本小节中,只需大致了解日志复制(Log Replication)的过程。

所有对这个分布式系统的更改都经过领导者(leader)节点。下面以更改值5为例:

  • 每个更改会被作为一个日志项添加到领导者(leader)节点。并且这个新加的日志项处于uncommitted(未提交)状态,因此它不会更新领导者节点的值。下图中,红色的SET 5表示状态是未提交的更改:
    在这里插入图片描述
  • 为了提交这个日志项,领导者(leader)节点首先将它复制到所有追随者(follower)节点。日志项复制到追随者(follower)节点后也是保持未提交的状态(红色表示):
    在这里插入图片描述
  • 领导者(leader)进入等待状态,直到大部分节点回复已经记录该日志项为止。此时领导者(leader)节点上提交这个日志项(状态变为committed,SET 5颜色从红色变为黑色来表示),现在领导者节点的值为5了。
    在这里插入图片描述
  • 领导者(leader)通知所有追随者(follower)该日志项已被提交,收到这个通知的追随者(follower)立即提交自己的该日志项。
    在这里插入图片描述
    此时,这个集群关于这个系统状态就达成共识了(或者说这个值/状态在集群节点之间就一致了)。这个过程就是日志复制(Log Replication)

选举leader

本节详细描述领导者选举(Leader Election)的细节。Raft的选举过程设计得非常巧妙,下面开始慢慢体会。

初始的选举

在Raft中,有2个重要的控制选举的超时设置。

第一个是选举超时(Election Timeout)。选举超时时间,是追随者(follower)成为候选者(candidate)之前等待的时间。更详细一点,是追随者(follower)本身一直会有这个选举超时时间的倒计时,如果一直到倒计时结束系统中没有领导者(leader),那么这个追随者(follower)就会成为候选者(candidate)。

每个追随者的选举超时都是150~300ms之间的随机值,即大概率上,每个节点的这个选举超时时间是不相同的(后面“拆分投票”小节会描述当这个超时时间出现相同时的情况)。

以初始状态为例,所有节点都是追随者(follower),集群中没有领导者(leader),每个节点都有各自的随机的选举超时时间。如下图所示:
在这里插入图片描述
图中描述了在初始的选举周期(Election Term)(初始选举周期Term=0)中的情况,图中圆缺陷的部分表示当前选举超时时间的流逝,可以看到节点B流逝的要快一些,说明B节点的选举超时这个随机值最短,那么它会首先成为候选者(candidate),如下图:
在这里插入图片描述
B在选举超时时间之后,成为候选者(candidate),并且开启了一个新的选举周期(Term=1),向其他节点发起投自己成为领导者的投票请求,与此同时自己给自己投了1票,如下图:
在这里插入图片描述
收到投票请求的节点会检查自己在这个选举周期中是否已经投过票,如果已经投过了,那么啥事儿不做;如果还没投过,就会给发起投票的候选者(candidate)节点投一票。并且重置自己的“选举超时”时间(重新从0开始,经历一个150~300ms之间的随机时间)。如下图所示:
在这里插入图片描述
一旦一个候选者(candidate)拥有大多数(超过一半)节点的投票,它就会成为领导者(leader)节点。注意上图中A和C此时都更新了自己的选举周期Term值。

这个新的领导者节点(leader)开始给它的追随者(follower)们发送“追加条目(Append Entries)”消息。这个消息每隔一段时间就会发送,这个间隔由心跳超时(Heartbeat Timeout)指定。心跳超时也是第2个重要的控制选举的超时设置。
追随者(follower)们响应每个追加条目消息,并重置自己的选举超时时间

小结一下:

  • 追随者(follower)一直通过领导者(leader)发来的心跳(即追加条目消息)来检测领导者(leader)的存在。
  • 每次收到领导者(leader)的心跳检测时就将自己的选举超时时间重置,重新从0开始经历一个150~300ms之间的随机超时时间。
  • 当追随者(follower)在选举超时时间之内没有收到领导者(leader)的心跳检测,那么它将成为候选者(candidate),开启一个新的选举周期,发起新的选举自己为领导者(leader)的投票。
  • 上述过程是不断进行中的,从未停歇。

领导者故障后的选举

当领导者(leader)故障以后,会发生重新选举。流程如下:

以上面的示例继续,假设现在B故障了。

  • 此时A和C都有自己的一个随机选举超时时间,假设A的选举超时时间值比C的小,那么A会提前完成自己的选举超时,这期间由于B故障了,不会有来自领导者(leader)的心跳检测(追加条目消息)。
  • A成为候选者,并开启一个新的选举周期(Term=2,上一个Term+1得到),并为自己先投一票。然后向B和C发出投票请求;
  • B仍处于故障状态,无法作出响应。
  • C发现自己在新的Term=2选举周期中未给A投过票,于是给A回复“给你投一票”,并更新自己的Term=2。
  • 此时A的得票数为2。于是在这个新的投票周期中,A得到了“大多数”的投票(3个中的2个,自己的+C的投票),成为了领导者(leader)。
  • 之后A开始持续向B和C发送追加条目消息,做心跳检测。C不断收到A的心跳检测,不断重置自己的选举超时时间。

如图所示:
在这里插入图片描述
“需要大多数投票”和“每个选举周期只投一次票”的设定,保证了每个选举周期中只会选举出一个领导者(leader)。

拆分投票

因为每个节点的选举超时时间是一个150~300之间的随机数,理论上会出现多个节点之间的这个值相同的情况。如果领导者(leader)是存在的,出现节点之间的选举超时相同也不会有问题,因为会被领导者的心跳消息给重置。如果领导者(leader)不存在,那么这种情况就需要考虑了,会出现同时多个节点成为候选者(candidate)。如果多个节点成为候选者(candidate),那么会发生拆分投票(Split Vote)。如下图所示,是4个节点在选举周期为3时,都为追随者(follower)的场景:
在这里插入图片描述
假设A和D的选举超时时间恰好相等,那么A和D在会同时成为候选者,如下图所示:
在这里插入图片描述
如上图所示,恰巧节点A和D的选举超时时间相同,它们同时成为候选者(candidate),并都开启了选举周期Term=4的投票,其中绿色小圆点表示它们发出的为自己投票的请求,分别发向另外的3个节点。

假设B先收到A的投票请求,B为A投一票,当B再收到D的请求后,发现自己在该选举周期(Term=4)已经给A投过票了,将不再给D投票。同理假设C先收到D的投票请求,C为D投票,而不会给A投票。与此同时,B和C在收到投票请求的时候,都重置了自己的选举超时时间,不会成为候选者(candidate)。A和D两个节点已经给自己投了票,也不会出现彼此相互投票的情况。如下图所示:
在这里插入图片描述
当投票请求返回到A和D的时候,A和D在当前选举周期中各自有2个投票数。如下图:
在这里插入图片描述
由于A和D的得票相同,都没有“大多数”的得票,所以都不能成为领导者(leader)。

与此同时A、B、C和D还有自己的选举超时在倒计时(虽然B和C选举超时在收到投票请求时被重置了,但它俩新的选举超时也可能小于A和D的选举超时,也有可能成为候选者)。我们假设D先完成选举超时(当然可以是A、B、C、D中的任意一个先完成这个选举超时,这里以D为例),成为候选者(candidate),那么D将开始一个新的选举周期,向另外三个节点发起投票,最终D收到了4个投票成为新的领导者(leader)。

如果选举超时相同的节点还是超过1个,那么又会轮回到下一个选举超时中,直至选举超时都不相等为止。这里有3个说明:

  • 如果是偶数个节点,理论最坏情况下就会一直选举循环下去,无法选举出领导者(leader)。这也是推荐奇数个节点原因之一。
  • 即使奇数个节点,在故障的时候也会出现理论最坏情况。
  • 由于每个节点的超时时间是一个随机数,所以
    • 出现相同的概率很小。
    • 而且相同必须是选举超时时间最短的2个及以上个节点的时间值相同,这个概率就更小了。
    • 再进一步出现一直相同的概率就更更小了。
    • 最坏情况只会出现在初始偶数节点,或者故障部分节点的系统中。综合这些因素,概率就非常非常小了。

日志复制

日志复制(Log Replication)是指对分布式系统的所有修改都会作为日志项进行记录,首先在领导者(leader)节点记录修改日志,然后领导者(leader)节点将修改日志拷贝到所有追随者(follower)节点上,使得每次修改都在集群中达成一致性。

一旦有了选举出的领导者(leader)节点,对系统的所有修改都需要复制到所有节点上,这是通过领导者(leader)节点发出的追加条目消息(Append Entries)来完成的,该消息还用于心跳检测。

考虑下面这个三节点的分布式系统,C是领导者节点:
在这里插入图片描述
现在有一个客户端发起设置值5的请求,这个请求会发送到领导者(leader)节点,追加到领导者(leader)的日志中,作为一个日志项,该日志项处于未提交状态(uncommitted,红色字体表示)。如图所示:
在这里插入图片描述
该修改会随下次心跳(也就是追加条目消息)发送到所有追随者(follower)节点
在这里插入图片描述
追随者(follower)节点记录该修改日志之后,会回复领导者(leader)节点告知它“我已记录该修改”。领导者(leader)节点收到“大多数”追随者(follower)的回复后,就会提交这个修改日志项(状态变为已提交,committed,黑色字体表示)。如图:
在这里插入图片描述
然后,领导者(leader)节点回复客户端:已修改成功。如图所示:
在这里插入图片描述
然后,领导者(leader)节点向追随者(follower)们发送本次修改已提交的消息(通过追加条目消息),追随者(follower)们收到该消息后也会提交自己的这条修改的日志项,如下图:
在这里插入图片描述
(注:上图中节点B和A的SET 5应该会变为黑色,表示已提交的状态)

让我们再考虑:再发送将该值5增加2的修改命令,流程会是怎样的?

  1. 客户端向领导者(leader)节点C发送ADD 2的修改请求
  2. 节点C记录一个ADD 2的日志项,其状态为未提交。
  3. 领导者(leader)节点C用下一次心跳向所有追随者(follower)发送这次修改。
  4. 追随者(follower)收到心跳后,把修改记录到日志中,状态为未提交。然后回复领导者(leader)已记录修改。
  5. 当领导者(leader)节点C收到大多数追随者(follower)回复的已记录修改消息之后,将这个修改日志项提交,其状态变为已提交(committed)。
  6. 领导者(leader)回复客户端:已经完成这次修改.
  7. 领导者(leader)向所有追随者(follower)发送已经提交日志项的消息(也是通过心跳完成)
  8. 追随者(follower)收到消息后,将自己的这次修改日志项提交。


    这样,系统中所有节点都达成一致,值都更新成7了。如图所示:
    在这里插入图片描述

网络分区

Raft可以在网络分区(Network Partition)中保持一致性。
在这里插入图片描述
如上图所示,原本的领导者(leader)为B节点。此时由于网络故障导致了网络分区,将节点A、B和C、D、E隔离,即网络分区下半部分中A和B网络是互通的,网络分区上半部分中C、D和E之间网络是互通的,但是网络上下两区域之间不连通。

在网络分区上半本部分中,C的选举超时最快,很快就成为了候选者(candidate),并发起了新的Term=2的选举周期,并会得到D、E以及自己的合计3个选票,由于是5个中的3个,是大多数,所以节点C成为新的领导者(leader),如下图所示:
在这里插入图片描述
这样,每个网络分区中各自有一个领导者(leader),并且它们的选举周期是不同的。

假设一个客户端尝试设置B节点值为3,由于B节点无法收到大多数追随者(follower)的日志记录回复,所以B节点无法完成值修改(一致处于uncommitted的未提交状态)。如图所示:
在这里插入图片描述
假设另一个客户端尝试设置节点C的值为8,由于C能收到大多数追随者(follower)(包括自己在内共3个)的回复,所以完成值8的设置,并且D和E节点也能完成该修改(提交状态)。如图:
在这里插入图片描述
现在,修改好了这个网络分区故障。
B节点会看到更高的选举周期(自己的Term=1,而C、D和E的Term=2),那么B会回滚它没有提交的日志项,并且通过接收新领导者(leader)C节点的心跳,将自己的选举周期设置为2,且完成设置值8的日志提交操作。同理,节点A也跟B一样。如图:
在这里插入图片描述
现在,整个集群数据都一致了。

再看分布式一致性问题

现在再回过头来看第一节里描述的问题

  • 情况1,客户端往A写了一个值5,那么A会通过网络向B和C拷贝这个值,在这个拷贝刚刚开始或者还未完成之前,客户端就向B去请求这个值,得到的值就可能是错误的。

在Raft算法中,A写了一个值5,这个操作真正完成后,任意节点都能获取到最新的值5。因为只有在A向B和C拷贝写值状态成功以后,A上的值5才会提交,然后才会反馈给客户端写入成功。此时虽然B和C上的值5还未提交,但是都能读取到这个最新的值5。

  • 情况2,服务器B故障了(离线),此时客户端往A写了一个值,A通过网络把这个值拷贝给C,无法拷贝给B。之后B修复后上线,在数据还未同步给B之前,客户端向B请求这个值,得到错误的值。还有,这个值应该由谁同步给B呢?假设是A负责同步给B,而与此同时,客户端请求C将这个值改变了,在这个值还未同步给A之前,A将该值拷贝给B,B得到的就不是最新的值,此时向B请求到的值就不对。

情况2在Raft算法中是这样保障的:

  1. 故障节点B上线后,首先是要进行故障恢复流程的,因为B会检查到自己处于数据“过时”状态,需要从当前的leader那里同步最新的日志条目,然后才能向客户端提供服务。
  2. 故障恢复后的节点,最新的数据都是由leader节点负责同步给这些节点的。
  3. 修改数据请求都是由唯一的领导者来负责领衔处理的,即所有的写操作都需要经过领导者节点。

写在最后

  • 文章大概完成于2018年,一直躺在电脑的某个角落。最近想回顾过去的十年,重新翻了出来。
  • 最重要的一点:本文是对benbjohnson的杰出工作的翻译和增加了部分我对Raft的理解。benbjohnson对Raft的动画互动讲解是我看到过的Raft算法讲解中最直观易懂的,没有之一。大家伙可忽略文章内容,直接去观看动画演示点击观看Raft原理动画演示。有问题再来看本文,或者来这里跟我一起探讨Raft。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/496467.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【日常聊聊】程序员是如何看待“祖传代码”的?

🍎个人博客:个人主页 🏆个人专栏:日常聊聊 ⛳️ 功不唐捐,玉汝于成 目录 前言 正文 方向一:祖传代码的历史与文化价值 方向二:祖传代码的技术挑战与机遇 方向三:祖传代码与现…

【零基础入门TypeScript】对象

目录 句法 示例:对象文字表示法 TypeScript 类型模板 示例:Typescript 类型模板 示例:对象作为函数参数 示例:匿名对象 鸭子打字 例子 对象是包含一组键值对的实例。这些值可以是标量值或函数,甚至是其他对象的…

react 路由的基本原理及实现

1. react 路由原理 不同路径渲染不同的组件 有两种实现方式 ● HasRouter 利用hash实现路由切换 ● BrowserRouter 实现h5 API实现路由切换 1. 1 HasRouter 利用hash 实现路由切换 1.2 BrowserRouter 利用h5 Api实现路由的切换 1.2.1 history HTML5规范给我们提供了一个…

GB28181 —— Ubuntu20.04下使用ZLMediaKit+WVP搭建GB28181流媒体监控平台(连接带云台摄像机)

最终效果 简介 GB28181协议是视频监控领域的国家标准。该标准规定了公共安全视频监控联网系统的互联结构, 传输、交换、控制的基本要求和安全性要求, 以及控制、传输流程和协议接口等技术要求,是视频监控领域的国家标准。GB28181协议信令层面使用的是SIP(Session Initiatio…

FPFH特征匹配以及ransac粗配准

一、代码 Python import open3d as o3d import numpy as npdef extract_points(point_cloud, salient_radius5, non_max_radius5, gamma_210.95, gamma_320.95, min_neighbors6):keypoints o3d.geometry.keypoint.compute_iss_keypoints(point_cloud,salient_radiussalient_…

【MATLAB源码-第151期】基于matlab的开普勒化算法(KOA)无人机三维路径规划,输出做短路径图和适应度曲线。

操作环境: MATLAB 2022a 1、算法描述 开普勒优化算法(Kepler Optimization Algorithm, KOA)是一个虚构的、灵感来自天文学的优化算法,它借鉴了开普勒行星运动定律的概念来设计。在这个构想中,算法模仿行星围绕太阳的…

Windows下查看端口占用以及关闭该端口程序

打开命令窗口 windowsR 输入 cmd 查看所有运行端口 netstat -ano 该命令列出所有端口的使用情况。 在列表中我们观察被占用的端口,比如是 80,首先找到它。 查看被占用端口对应的 PID netstat -aon|findstr "80" 回车执行该命令&#xff…

StarRocks——Stream Load 事务接口实现原理

目录 前言 一、StarRocks 数据导入 二、StarRocks 事务写入原理 三、InLong 实时写入StarRocks原理 3.1 InLong概述 3.2 基本原理 3.3 详细流程 3.3.1 任务写入数据 3.3.2 任务保存检查点 3.3.3 任务如何确认保存点成功 3.3.4 任务如何初始化 3.4 Exactly Once 保证…

windows安装部署node.js并搭建Vue项目

一、官网下载安装包 官网地址:https://nodejs.org/zh-cn/download/ 二、安装程序 1、安装过程 如果有C/C编程的需求,勾选一下下图所示的部分,没有的话除了选择一下node.js安装路径,直接一路next 2、测试安装是否成功 【winR】…

Python 实现Excel自动化办公(中)

在上一篇文章的基础上进行一些特殊的处理,这里的特殊处理主要是涉及到了日期格式数据的处理(上一篇文章大家估计也看到了日期数据的处理是不对的)以及常用的聚合数据统计处理,可以有效的实现你的常用统计要求。代码如下&#xff1…

kotlin与java的相互转换

Kotlin转java 将kotlin代码反编译成java Tools -> Kotlin -> Show Kotlin Bytecode 然后点击 【Decompile】 生成java代码 java转kotlin Code -> Convert Java File To Kotlin File

在Node.js中如何实现用户身份验证和授权

当涉及到构建安全的应用程序时,用户身份验证和授权是至关重要的一环。在Node.js中,我们可以利用一些流行的库和技术来实现这些功能,确保我们的应用程序具有所需的安全性。本篇博客将介绍如何在Node.js中实现用户身份验证和授权。 用户身份验…