type
status
date
slug
summary
tags
category
icon
password
实验准备Lab 3B 日志复制Start函数commitMsg广播选举广播心跳RPC函数:选举限制RPC函数:AppendEntriesLab 3C persistence持久化Make方法进行持久化保存readPersist方法和persist方法优化操作
说是要做Raft实验说了很久了,但是一直被拖延症拖延着,一直拖到了现在,争取能在近期能够把它写完吧
Raft是一个著名的分布式共识算法,这个算法的目的是保持多个节点在存储数据上具有一致性,对客户端来说具有透明性
从逻辑上来说好像不是很复杂,就是依靠过半节点投票出Leader,然后Leader与客户端对接,传输心跳信息同时保持信息同步。但是注意的是Leader和Follower在心跳信息传输之间也是存在偏差的。
这里有一个网站能够动态描述其领导人选举以及日志复制操作:
实验准备
分布式系统Debug非常困难,但是mit很贴心的给我们准备了相关的debug工具,在这里
通过这个脚本可以生成很简单易懂的输出log表示

Lab 3B 日志复制
在Leader选举之后,开始进入日志复制环节
首先需要大致确定一下日志复制的流程,这里我大致画了一个结构图,中间有些步骤可能没有包含

接下来按照一个个模块来进行总结吧
Start函数
这个函数的作用是进行log日志的添加,从抽象层面来看,上层的用户层kv数据库调用这个函数,添加log日志,如果不是Leader直接返回即可,没什么可说的
commitMsg
这个函数和计时器函数一样,设计理念是通过循环轮询来检查当前commitIndex是否存在变化,若是存在变化,则一边更改LastApplied值一边进行Msg提交,也就是流程图中的添加至ApplyCh通道
循环逻辑的轮询很简单,如果想要复杂一点的逻辑,可以使用cond,在Lab 3B指南中也推荐使用Cond进行检查
‣
广播选举
对每一个维护的Follower节点进行requestVote的发送,要求节点们进行投票,统计投票,如果大于维护的节点数组的一半(包括自己),就成为Leader
后面在进行Lab3C的时候发现这个逻辑不合理,once变量的在整个broadCast中只能被使用一次这个逻辑会导致奇怪的bug产生,后续删除了这段逻辑
广播心跳
在Leader接收到log信息,或者是计时器到达心跳间隔时,都会调用此函数向所有Follower进行心跳发送
需要注意:
- 在遍历到Leader自身节点的时候,也需要维护matchIndex和nextIndex数组
- 不然遍历match数组提交commitMsg的时候还需要单独讨论
- prevLogIndex的值针对每一个Follower可能都不一样
单个心跳的发送逻辑
RPC函数:选举限制
和Lab3A直接进行选举投票不一样,这里对Candidate的投票有两个重要的判断逻辑(二选一即可)
- Candidate的LastLogTerm大于Follower的LastLogTerm
- 通过
- 如果Candidate的LastLogTerm等于Follower的LastLogTerm,并且Candidate的log长度大于Follower的Log长度时
- 通过
这里将RPC函数贴下:
RPC函数:AppendEntries
Lab 3C persistence持久化
这个Lab主要是针对raft中数据的保存机制,原本应该是使用文件形式进行保存的,但为了方便在Lab中使用了结构体进行保存,在上层调用Make函数创建Raft对象的时候会将结构体作为参数传递进去。
在这个Lab中有两个目标:
- 进行持久化保存
- 优化back up nextIndex操作
- 也就是当Leader发现prevLogIndex冲突的时候的回退操作
Make方法
其中有些参数,比如说rf.applyCh是其他Lab才会用到,但为了方便我就不改了,可以看到在进行方法调用的时候是调用了
readPersist
方法进行持久化保存
readPersist方法和persist方法
这两个方法都是Lab自己给了模板,直接照猫画虎填充即可,作用就是写入和读取raft中的参数,包括:
rf.votedFor
rf.currentTerm
rf.log
在lab3D中还会保存:
rf.lastIncludedIndex
rf.lastIncludedTerm
snapshot
快照
这里的代码是包括了全部的,然后就是注意:每当对其中的变量进行修改时,都要调用一下
persist()
作为变量保存优化操作
在这里需要优化nextIndex更新的操作,重点逻辑在于:
- 如果Leader发现对Follower的AppendEntries操作失败,若是由于Follower对Leader的PrevLogIndex参数校验失败,则进行nextindex的回退
- 这个回退逻辑需要优化,而不是简单减一
在lab文档中提醒了思路:
The paper is vague about the details; you will need to fill in the gaps. One possibility is to have a rejection message include:
Then the leader's logic can be something like:
翻译过来就是说,在
AppendEntries
这个RPC
函数的reply中,添加几个参数这样一来对Leader来说就有几个情况:
- 当Reply中不存在XTerm:
- 表明Follower的log长度小于
prevLogIndex
,则nextIndex
变为XLen+ 1
大小(也就是Follower的Log长度 + 1)
- 当Reply中存在XTerm:
- 表明存在Follower的log和Leader的存在冲突
- 查看Leader中是否存在此Term
- 若存在,则
nextindex
赋值为XTerm+1
的最开始index - 若不存在,证明Leader没有Follower中的
XTerm
,则从XIndex
的位置重新开始
代码实现:
正如在Lab2说的,在进行
broadcastElection
的时候,sync.Once
变量会导致在整个broadcastElection
周期中只能调用一次Leader晋升逻辑,会出现一些奇奇怪怪的问题(由于时间久远了忘记bug原因是什么了),在lab3对其进行修改