Mit6.5840 Lab 3: Raft 3B 3C
2025-5-27
| 2025-6-7
0  |  Read Time 0 min
type
status
date
slug
summary
tags
category
icon
password
说是要做Raft实验说了很久了,但是一直被拖延症拖延着,一直拖到了现在,争取能在近期能够把它写完吧
Raft是一个著名的分布式共识算法,这个算法的目的是保持多个节点在存储数据上具有一致性,对客户端来说具有透明性
从逻辑上来说好像不是很复杂,就是依靠过半节点投票出Leader,然后Leader与客户端对接,传输心跳信息同时保持信息同步。但是注意的是Leader和Follower在心跳信息传输之间也是存在偏差的。
这里有一个网站能够动态描述其领导人选举以及日志复制操作:

实验准备

分布式系统Debug非常困难,但是mit很贴心的给我们准备了相关的debug工具,在这里
通过这个脚本可以生成很简单易懂的输出log表示
notion image

Lab 3B 日志复制

在Leader选举之后,开始进入日志复制环节
首先需要大致确定一下日志复制的流程,这里我大致画了一个结构图,中间有些步骤可能没有包含
notion image
接下来按照一个个模块来进行总结吧

Start函数

这个函数的作用是进行log日志的添加,从抽象层面来看,上层的用户层kv数据库调用这个函数,添加log日志,如果不是Leader直接返回即可,没什么可说的

commitMsg

这个函数和计时器函数一样,设计理念是通过循环轮询来检查当前commitIndex是否存在变化,若是存在变化,则一边更改LastApplied值一边进行Msg提交,也就是流程图中的添加至ApplyCh通道
循环逻辑的轮询很简单,如果想要复杂一点的逻辑,可以使用cond,在Lab 3B指南中也推荐使用Cond进行检查

广播选举

对每一个维护的Follower节点进行requestVote的发送,要求节点们进行投票,统计投票,如果大于维护的节点数组的一半(包括自己),就成为Leader
💡
后面在进行Lab3C的时候发现这个逻辑不合理,once变量的在整个broadCast中只能被使用一次这个逻辑会导致奇怪的bug产生,后续删除了这段逻辑

广播心跳

在Leader接收到log信息,或者是计时器到达心跳间隔时,都会调用此函数向所有Follower进行心跳发送
需要注意:
  1. 在遍历到Leader自身节点的时候,也需要维护matchIndex和nextIndex数组
    1. 不然遍历match数组提交commitMsg的时候还需要单独讨论
  1. prevLogIndex的值针对每一个Follower可能都不一样
单个心跳的发送逻辑
 

RPC函数:选举限制

和Lab3A直接进行选举投票不一样,这里对Candidate的投票有两个重要的判断逻辑(二选一即可)
  1. Candidate的LastLogTerm大于Follower的LastLogTerm
    1. 通过
  1. 如果Candidate的LastLogTerm等于Follower的LastLogTerm,并且Candidate的log长度大于Follower的Log长度时
    1. 通过
这里将RPC函数贴下:

RPC函数:AppendEntries

Lab 3C persistence持久化

这个Lab主要是针对raft中数据的保存机制,原本应该是使用文件形式进行保存的,但为了方便在Lab中使用了结构体进行保存,在上层调用Make函数创建Raft对象的时候会将结构体作为参数传递进去。
在这个Lab中有两个目标:
  1. 进行持久化保存
  1. 优化back up nextIndex操作
    1. 也就是当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来说就有几个情况:
  1. 当Reply中不存在XTerm:
    1. 表明Follower的log长度小于prevLogIndex,则nextIndex变为XLen+ 1大小(也就是Follower的Log长度 + 1)
  1. 当Reply中存在XTerm:
    1. 表明存在Follower的log和Leader的存在冲突
    2. 查看Leader中是否存在此Term
      1. 若存在,则nextindex赋值为XTerm+1的最开始index
      2. 若不存在,证明Leader没有Follower中的XTerm,则从XIndex的位置重新开始
代码实现:
正如在Lab2说的,在进行broadcastElection的时候,sync.Once 变量会导致在整个broadcastElection 周期中只能调用一次Leader晋升逻辑,会出现一些奇奇怪怪的问题(由于时间久远了忘记bug原因是什么了),在lab3对其进行修改
 
  • 开发
  • Mit6.5840 Lab 3DHot 100(回溯、动态规划) (1)
    • Giscus
    • Cusdis
    Catalog