raft算法mysql主从复制_Etcd raft算法实现原理分析

1.1 主要概念

要实现集群数据的一致性,节点在进行通信的时候必定需要遵守特定规则进行数据校验,而这些规则具体都是通过某些具有特定含义的属性来实现的。为了让对Raft 算法比较陌生的读者对算法的关键概念有一个初步认识,作者整理了算法中涉及的概念如下所示:

da4f6fbf285ac8e6bf8ac1fbb4ab44ac.png

00992a6462a13995f2a4ee0b3fd75a9e.png

1.2 节点状态

Raft 算法比较简单,其中一个原因是节点的状态少,一共只有三种角色,大大降低了角色间转换的复杂性。这三种角色分别是Leader 、Follower 和Candidate (其实大部分时间只有Leader 和Follower 角色,因为Candidate 只是选举的一个过渡角色),每种角色都有自己的运行规则。角色之间可以在一定条件下进行角色转换,状态转换如下图所示:

4aec856b45ed025218ba6ba203251cfd.png

l Follower:集群启动时,每个Raft 节点初始以Follower的角色运行。他们各自维护了一个超时字段(ElectionTick属性),如果在超时时间段内Follower都没有收到来自Leader的消息,Follower就知道这个时候集群中没有Leader节点,这时它会把自己的角色转换为Candidate,并发起领导人选举。

l Candidate:处于Candidate状态的节点会发起选举的操作的操作,如果选举成功,那它将成为下一任的领导人,如果选举失败,就转化为Follower节点,负责处理Leader节点发起的请求,比如添加日志,心跳检测等操作。在选举过程中可能会有多个节点竞争领导人角色,并且多个选举人获得同样多的选票。这时候大家都不能当选Leader,经过一定时间,Follower会重新发起一轮选举,保证一次选举最多只有一个领导人。这里面有一些优化机制,比如每个候选人重新发起选举的间隔时间是不一样的,可以降低多次选票相同,重新选举的问题。

l Leader:Leader节点主要负责集群中数据的管理,包括给Follower发送添加日志、心跳检测(HeartbeatTick 属性)等命令。在Raft 节点运行过程中可能会有这种情况,即当前Leader节点宕机了,这时其他节点会重新选出一个Leader并且运行了一段时间,宕机的Leader节点恢复了,新的Leader会给老的Leader发送消息,这时老的Leader发现消息里面的任期(Term字段)比自己当前的Term更大,老的Leader就知道自己已经不是最新的领导人了,它会主动转换为Follewer角色。

1.3领导人选举

根据1.2 的介绍我们知道,Raft 节点在一启动的时候,就初始化为Follower 角色,并且每个Follower 角色都有一个ElectionTick 的随机超时属性。在超时的时间内如果Leader 对它们发起请求或者心跳检测,Follower 节点会使自己转变为Candidate 进行领导人选举。在领导人选举过程中,每个节点最多只能给一个节点投票,如果有两个节点发起投票,先到的那个节点会获得选票。这样能够保证每次选举期间最多只有一个节点当选。论文中节点发起选举参数和接收者实现规则如下图所示:

22aaa14c70b1b79fa672018e65d9cab3.png

1. Follower首先给自己发送一个消息,使自己变为Candidate角色,设置Term+1 然后并发的对集群的所有节点发送选举的消息。集群中每个节点保存了一个数组,这个数组保存集群中所有节点的信息

2. 收到消息的Follower节点会对选举的请求做出参数校验和选举响应,如果Candidate发送的消息里面的Term比自己当前记录的更大,并且Candidate拥有自己已经更新的最新日志信息,就同意为Candidate投票,否则拒绝投票。

3. 如果Candidate收到大多数节点的投票响应,那Candidate就会成为新一任的Leader。给其他发送消息,并更

4. 如果Candidate没有当选Leader,就使自己转变为Follower角色,听从Leader的指令。

步骤1 描述的操作可能会出现一个问题;假如集群出现网络错误,使得集群中的节点(A 、B 、C 、D 、E 五个节点)被分成两部分,节点A 、B 、C 和节点D 、E 。这时D 、E 没收到Leader 节点的消息会重新发起选取,但是由于不能获得多数节点的选票,因此Term 会不断增大。如图3 所示,当网络回复的时候,D 、E 节点的Term 已经比正常集群的Term 更大,因此节点D 或节点E 将当选Leader ,导致节点A 、B 、C 在节点D 、E 宕机时期添加的正常数据被清除,从而破坏集群的历史数据。

809afe901322a29cca8060871a122d2f.png

这个问题在数据一致性的应用场景是不允许出现的,Raft 算法采用PreVote 算法解决了这个问题。PreVote 算法在Candidate 发起选举之前,会首先向其他节点发送一个预投票信息,这时Term 没有加1 ,如果超过半数节点同意了选举请求,Candidate 才设置Term+1 ,真正发起领导人选举投票。在上述环境中D 、E 节点是没办法获得超过半数节点的选票,因此节点只能反复Prevote ,Term 却没有机会增加。当网络恢复的时候当收到Node A 的消息,立刻使自己变成Follower 继续为集群服务。

1.4日志复制

Raft 集群中Leader 节点负责与客户端的交互,当非Follower 节点接收到客户端请求的时候,该节点会把该请求重定向到leader 节点,这种集中式的处理方式大大降低了集群和客户端的交互复杂度。用户的每次请求操作在Raft 集群中最终都是一个日志复制的过程,我们来看看一次简单日志复制过程:

1. 客户端给Leader节点发送一个 SET X = 5 的请求。

2. Leader节点收到客户端请求,首先把SET X = 5的指令写到日志中,然后把请求并发发送到所有Follower节点。

3. Follower节点收到Leader请求,也把SET X = 5 写到日志中,然后响应Leader 节点。

4. 当Leader节点收到大部分Follower节点写入成功后,把 SET X = 5 这个指令 commit 这个时候,Leader 节点上的X 才真正等于5,并发送commit 成功给所有Follower节点。

5. Follower节点收到Leader节点commit成功的信息,也commit,这个时候就实现了Raft集群数据的一致性。

在日志复制过程中,节点会对相应参数进行检测,避免无效信息被加入到日志中造成集群中数据不一致。论文中日志发送参数和节点校验规则如图5 所示:

0ef12f9022b399d83099d935f734de54.png

添加日志消息的参数及其含义解释如下:

1. Term: 发送者的任期号。

2. LeaderId: 领导人的id。

3. PrevLogIndex: 之前Leader发送给Follower的最后一条日志索引,用于 Follower 确认与 Leader 的日志是否完全一致。新添加的日志会在这个索引之后开始。

4. PrevLogTerm: prevLogIndex索引对应的任期,用于检测Term的合法性。

5. Entries[]: 发送的日志数组,一次可以发送多条日志以提高效率。

6. LeaderCommit: Leader节点已经提交的最大日志索引,当节点收到这个记录的时候就知道,自提交日志到这个索引值是安全的。

消息接收者的数据校验规则:

1. 节点接收到消息时,如果发现消息来源的Term 小于自己当前记录的currentTerm,直接忽略这个消息。

2. 节点会根据prevLogIndex找到自己对应log的任期号term,如果和term和prevLogTerm不匹配,则直接返回false,不处理这个消息。

3. 检测entries数组是否和自己已经保存的log有冲突,如果有冲突,则删除这个冲突索引之后的所有日志。

4. 把所有新的日志entries加到自己的日志中。

5. 如果leaderCommit > commitIndex(自己已提交的最大索引),则设置 commitIndex = min( leaderCommit, entries)数组的最后一个元素的索引。

◆◆

2. Etcd raft 源码分析

◆◆

研究了Raft 的算法设计思想,接下来我们看Etcd 是怎么实现Raft 算法的。研究的Etcd 源码为V3.1.20 版本,下载地址https://github.com/etcd-io/etcd 。为了使读者对raft 算法代码结构有一个大致认识,我根据raft 启动过程画出以下代码调用图。建议要看源码的读者可以对着以下流程自己进入代码走一遍,对etcd 的raft 源码结构有一个大致印象,不然后面的代码可能会看的不知所云。

9cd12bf6948df7b00f986dc80e16a162.png

Etcd 的启动方法在etcdmain/main.go 文件中,应用启动后,raft 相关会启动两个协程,一个运行内层函数(raftnode.go 文件的node.run 方法) ,另一个运行外层函数(etcdserver/raft.go 文件中的start 方法)。内层函数主要处理节点数据内部的变更,外层函数主要负责获取内层函数的变更信息与其他节点的通信。内层函数和外层函数都有node 这个结构体的引用,因此两个协程之间可以通过node 里面的Channel 进行通信。

在Etcd 的raft 实现中,所有消息都被封装到一个结构体中,参数根据消息类型进行组装,因此很多参数都是可选的。一开始觉得所有消息的属性都集成在一个结构体不太好,后来看代码过程中发现这个结构体中属性并不多,而且大部分属性在大部分消息中都是要用到的。只是冗余少数字段但是大大减少了消息结构体的数量,使得消息参数整体看起来更简单。消息类型和属性如下图所示:

db46ddb0f8c382b8113cf01f63bf7174.png

f583b5de9daad22db2add0d3b7c921cb.png

介绍了消息类型,接下来我们来看看raft 算法中领导人选举的具体源码实现。领导人选举的大致步骤为

1. Candidate: 内层函数raft.run把消息添加到raft.msgs的slice结构中。

2. Candidate: 内层函数监听到raft.msgs有新消息写入,把raft.msgs的消息封装成一个Ready结构体,然后把这个结构体放入 node.readyc这个channel。

3. Candidate: 外层函数raftNode.start 监听到node.readyc这个channel中有数据到达,拿到这个消息,根据消息的目标节点把消息发送出去。

4. Follower: Follower收到Candidate的领导人选举请求,对请求做出响应。

5. Candidate:统计选举的回复,如果超过半数节点同意则当选Leader,否则转为Follower。

源码部分我将从go.etcd.io/etcd/etcdserver/raft.go 文件的StartNode(c *Config, peers []Peer) 函数开始分析,从这里开始主要是Raft 算法的核心实现。在StartNode 方法结尾启动了一个协程用于运行内层函数。

c1958f7d256ef2162f93d047d5df0bbc.png

在StartNode 方法中,节点启动后初始化为Follower 角色,如果在选举超时内没有收到Leader 的消息,会发出一个选举超时事件,这个事件在内层函数(raftnode.go 文件的node.run 方法) 中被捕获,调用r.tick 函数,tick 实际上是一个函数变量,在becomeFollower 函数中被赋值为tickElection ,因此r.tick 方法实际上调用的是tickElection 方法进行领导人选举。

d4607173eaf97b1380a7b27da9eaf244.png

tickElection 方法中会检测相应参数,如果节点当前可选举,并且已超时则会节点的状态机函数(raftraft.go 文件Step 函数)传入一个类型为pb.MsgHup 的选举消息,这个消息不用于节点间通信,只是通知自己可以重新发起一轮领导人选举。

71e37d198054e66e2733c920cf03ad27.png

进入Step 状态机函数,pb.MsgHup 消息被捕获到,还是先检测相应参数,如果检测到自己已经是Leader ,就忽略这个消息。否则根据是否是Prevote 阶段进入相应投票阶段,前面我们说到,Prevote 解决了raft 集群网络分区造成的问题。

a07d1ed579feb74b21e0e8dc5e8f6041.png

campaign 方法(raftraft.go 文件campaign 函数)中对消息做进一步处理,这里节点会排除自己对其他每个节点“发送“预选举/ 选举的消息,注意这里并没有真正发送消息,而是把消息加到raft 结构体的msgs 切片中。Raft.msgs 结构体主要用于临时存放待发送的消息。

af6b038e171d611e35e43b4ce09cb212.png

当数据被写到raft.msgs 切片时,内层函数(raftnode.go 文件的node.run 方法)监听到raft.msgs 中有新数据待发送,会把数据封装成一个Ready 结构体放入node.readyc channel 中,清空raft.msgs 消息,并赋值advancec 为node.advancec channel 。一开始没看懂为什么这样写,后来在外层函数看到有一个往node.advancec 写入空结构体的操作才明白。当源节点往readyc 写入ready 数据后,就是一个异步操作,根本不知道目标节点什么时候能够处理完这个消息。源节点和目标节点通过node.advancec channel 就可以传递消息已经处理完的信号,源节点收到这个信号就可以进行后续的操作。

77222252b8ca1b01b75e102a03d5807f.png

由于外层函数(etcdserver/raft.go 文件中的start 方法)拥有node.readyc 的引用,并且监听着这个管道,把readyc 到来的消息拿出来,根据消息结构体中的目标节点把消息发送出去,这个时候就是端到端节点消息的发送了。

Candidate 节点选举的消息发出去后,目的节点会在状态机函数(raftraft.go 文件Step 函数)中捕获到这个消息,捕获的消息类型为pb.MsgVote 和pb.MsgPreVote 。在这里对Term 和消息的index 进行了检测。对Term 的检测主要包括:1. 之前是不是已经给这个节点投过票,如果没投过票可以继续投,这表示是投票的PreVote 阶段。2. 投票节点的Term 是否大于自己的Term, 只有大于才是合理的Term 值。3. 已经投过票,这次的投票请求者是否和自己投过票的节点一样,这表示正式的投票阶段,第二次投票的必须和第一次投票的一样;对于Index 的检测主要是发起投票节点日志的Term 必须比自己的大,或者Term 相同,但是日志索引更多,也就是说发起投票的节点至少要拥有和自己一样或者更新的日志记录。这部分检测都通过后就回复同意投票的消息,否则回复否定投票的消息。

c25b5d615def9ab27205c2b66ef5ca1e.png

当所有节点都回复完Candidate 选举的请求时。这时候再回到Candidate 节点,消息仍然会在状态机函数(raftraft.go 文件Step 函数)被捕获。因为消息类型既不是pb.MsgHup ,又不是pb.MsgVote ,没有对应消息,所以代码会走到switch case 的default 部分,调用r.step(r, m) 函数,step 是一个函数变量,因为当前节点是Candidate 角色,所以r.step 对应stepCandidate 函数。在这个函数里,Candidate 对选票进行统计,如果获得超过半数的选票,就当选Leader ,否则变为Follower 。

46d57794eff1dc23ec6d2885de489fd1.png

到此,领导人选举的源码分析就大致完成了,日志添加过程,心跳检测机制等其他操作其实是一样的消息流转机制,只不过消息类型不同,只要找到相应的消息类型,一步步分析下来还是比较简单的。另外Etcd存储部分和Raft算法紧密相关,并且Etcd V3版本相比与V2版本的存储机制进行了较大的更新和优化,由于时间和篇幅的关系,这部分内容下次再分析。Etcd raft算法实现原理初步分析就到这里,个人理解有限,如果有不对的地方欢迎指出!

— END—

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

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

相关文章

【hihocoder - offer编程练习赛60 C】路径包含问题(LCA,树上倍增)

题干&#xff1a; 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 给定一棵N的节点的树&#xff0c;节点编号1~N&#xff0c;并且1号节点是根节点。 小Hi会反复询问小Ho一个问题&#xff1a;给定两个节点a和b&#xff0c;有多少对节点c和d满足c < d且c到d的…

【面试题 - 最大值减去最小值小于或等于 num 的子数组数量】滑动窗口

题干&#xff1a; 解题报告&#xff1a; 我们用两个指针&#xff08;i&#xff0c;j&#xff09;分别代表窗口的左边界和右边界&#xff0c;窗口也就是子数组&#xff1b; 用两个双端队列分别维护这个窗口的最大值和最小值&#xff1b; 当窗口扩大时&#xff0c;即j向右扩展时…

mysql根据用户名查询数据_MySQL 查询数据

MySQL 查询数据 MySQL 数据库使用SQL SELECT语句来查询数据。 你可以通过 mysql> 命令提示窗口中在数据库中查询数据,或者通过PHP脚本来查询数据。 语法 以下为在MySQL数据库中查询数据通用的 SELECT 语法: SELECT column_name,column_name FROM table_name [WHERE Clause…

【牛客 - 370 I 】Rinne Loves Xor(按位前缀和,异或)

题干&#xff1a; Rinne 最近学习了位运算相关的知识&#xff0c;她想运用自己学习的知识发明一个加密算法。 首先她有一个源数组 A&#xff0c;还有一个密钥数组 B&#xff0c;现在她想生成加密后的数组 C。 她发明的方法是&#xff1a;当计算CiCi的时候&#xff0c;首先将…

quartz mysql索引_分布式系统中的定时任务全解(二)

在实际项目中&#xff0c;通常需要用到定时任务(定时作业)&#xff0c;spring框架提供了很好的实现。 1、 下载spring-quartz插件包 这里默认当前系统中是集成了spring框架的基本功能的。去网上下载spring定时器的jar包&#xff0c;这里用的是quartz-all-1.8.4.jar&#xff0c…

【牛客 - 331B】炫酷五子棋(STLset 或Hash,tricks,二维map标记)

题干&#xff1a; 五子棋是一个简单的双人游戏。 小希最近在思索一种更好玩的五子棋。她希望胜利不再是谁先五子连珠谁赢&#xff0c;而变成谁落子后&#xff0c;该子与之前的子五子连珠的次数更多才能胜利。 但是如果是在普通的棋盘上&#xff0c;这个游戏又显得不是很有趣…

【牛客 - 318J】王者荣耀(dp,01背包)

题干&#xff1a; "无论何时何地&#xff0c;都会遵守约定"。"奋力逃吧"。"关于取下敌人性命这件事&#xff0c;也从不失约"。 小懒虫zmx平时最喜欢玩的游戏就是《王者荣耀》&#xff0c;在这款游戏中它也最喜欢百里守约这个英雄。最近&#x…

xodo上的笔记不见了_goodnotes 的笔记无缘无故丢失了一本,还能救回来吗?

真的什么都没有做&#xff0c;很普通的记完笔记放在那里去吃饭&#xff0c;回来的时候&#xff0c;别的笔记都没变化&#xff0c;只有我一直在用、刚记完的那一本消失了……我尝试过的方案&#xff1a;1.icloud刚开始不确定那里有没有&#xff0c;我平时是开着 icloud 对笔记的…

【牛客 - 318G】LLLYYY的数字思维 与【牛客 - 289J】这是一个沙雕题II(贪心构造)

题干&#xff1a; LLLYYY很喜欢写暴力模拟贪心思维。某一天在机房&#xff0c;他突然抛给了队友ppq一 个问题。问题如下&#xff1a; 有一个函数f ()&#xff1a; int f(int x){ int tmp 0; while(x ! 0){ tmp x % 10; x / 10; } return tmp; } 接着…

react div组件设置可点击不可点击_React面试全解

更新:收藏前点个赞亲&#xff0c;为啥我每次写的东西收藏都是赞的n倍&#xff01;&#xff01;花了一个月时间总结的React面试题 希望能帮助到你全文近万字建议保存仔细过一遍目录面试中常提的重要概念React生命周期ReduxRouter重要的方法面试中常提的重要概念1 什么是模块化是…

【牛客 - 185B】路径数量(离散数学,长度为k的路径数量,图)

题干&#xff1a; 给出一个 n * n 的邻接矩阵A. A是一个01矩阵 . A[i][j]1表示i号点和j号点之间有长度为1的边直接相连. 求出从 1 号点 到 n 号点长度为k的路径的数目. 输入描述: 第1行两个数n,k (20 ≤n ≤ 30,1 ≤ k ≤ 10) 第2行至第n1行&#xff0c;为一个邻接矩阵 …

php mysql 菜鸟_PHP 和 MySQL 基础教程(四)

PHP 和 MySQL 基础教程(四)发布时间&#xff1a;2016-06-17 来源&#xff1a; 点击:次MySQL 中的 SQL对于 MySQL &#xff0c;第一件你必须牢记的是它的每一行命令都是用分号 (;) 作为结束的&#xff0c;但……没有完全绝对的事&#xff0c;在这儿也是一样。前面我曾经讲到&…

【POJ - 1724 】ROADS (带限制的最短路 或 dfs 或 A*算法,双权值)

题干&#xff1a; N cities named with numbers 1 ... N are connected with one-way roads. Each road has two parameters associated with it : the road length and the toll that needs to be paid for the road (expressed in the number of coins). Bob and Alice use…

mysql 5.7.20 win64_Win10下MySQL5.7.20 Mysql(64位)解压版安装及bug修复

2、解压到某一文件夹&#xff0c;如“C:\Program Files\MySQL\mysql-5.7.20-winx64”3、添加环境变量(系统变量)&#xff1a;变量名&#xff1a;MYSQL_HOME变量值&#xff1a;C:\Program Files\MySQL\mysql-5.7.20-winx64&#xff1b;在系统变量path原有值后添加路径&#xff1…

【CodeForces - 474D】Flowers (线性dp)

题干&#xff1a; We saw the little game Marmot made for Moles lunch. Now its Marmots dinner time and, as we all know, Marmot eats flowers. At every dinner he eats some red and white flowers. Therefore a dinner can be represented as a sequence of several f…

mysql事务顺序重排_MySQL事务处理及字符集和校对顺序

一、事务处理 事务处理&#xff1a;是一种机制&#xff0c;管理必须成批执行的MySQL操作&#xff0c;以保证数据库不包含不完整的操作结果。用来维护数据库的完整性。利用事务处理&#xff0c;可以保证一组操作不会中途停止&#xff0c;或作为整体执行或完全不执行(除非明确指示…

mysql权重怎么配置_mysql如何按权重查询数据啊?

楼上的回答全都会错意了&#xff0c;题主意思是根据权重设定随机几率&#xff0c;例如 A 的权重为10&#xff0c;B 的权重为 5&#xff0c;这个时候随机出现 A 的几率要比出现 B 的几率高。你可以试试这个备选方案。就是先取出权重列表再去根据权重随机出来的那个权重值&#x…

【计蒜客 - 蓝桥训练】修建公路(贪心,或运算,dp)

题干&#xff1a; 蒜头国有 nn 座城市&#xff0c;编号分别为 0,1,2,3,\ldots,n-10,1,2,3,…,n−1。编号为 xx 和 yy 的两座城市之间如果要修高速公路&#xff0c;必须花费 x|yx∣y 个金币&#xff0c;其中|表示二进制按位或。 吝啬的国王想要花最少的价格修建高速公路&#…

【计蒜客 - 蓝桥训练】阶乘位数(数学,对数运算,求阶乘位数)

题干&#xff1a; 蒜头君对阶乘产生了兴趣&#xff0c;他列出了前 1010 个正整数的阶乘以及对应位数的表&#xff1a; nnn!n!位数111221361424251203672037504048403205936288061036288007 对于蒜头君来说&#xff0c;再往后就很难计算了。他试图寻找阶乘位数的规律&#xff…

mysql win10 优化设置_windows10如何优化?系统优化设置方法

windows10如何优化&#xff1f;&#xff0c;高系统效率&#xff0c;尽可能提高运行速度&#xff0c;是我们关心的问题。以下是电脑系统优化设置的4种方法&#xff0c;能够有效的提高系统的使用效率和优化系统管理&#xff0c;我们一起来看看吧&#xff01;第一招&#xff1a;删…