MIT6.5840 lab3A

先定义Raft结构体

type NodeState int

const (
	Follower NodeState = iota
	Candidate
	Leader
)

type Raft struct {
	mu        sync.Mutex          // 加锁以保护对该节点状态的共享访问
	peers     []*labrpc.ClientEnd // RPC端点
	persister *tester.Persister   // 用于保存此节点已持久化状态的对象
	me        int                 // 此节点在 peers 数组中的索引
	dead      int32               // 由 Kill() 函数设置

	
	// 持久性状态
	currentTerm int        // 记录当前的任期
	votedFor    int        // 投给了谁
	log         []logEntry // 日志条目数组

	// 易失性状态
	commitIndex int // 已被提交的日志索引
	lastApplied int // 已提交的日志索引

	// leader状态
	nextIndex  []int // 对于每一个server,需要发送给他下一个日志条目的索引
	matchIndex []int // 对于每一个server,已经复制给该server的最后日志条目下标

	// 快照状态

	// 自己的一些变量状态
	state     NodeState // follower/candidate/leader
	timer     Timer
	voteCount int // 票数
}

入口函数是MAKE函数
备注写在代码里

func Make(peers []*labrpc.ClientEnd, me int,
	persister *tester.Persister, applyCh chan raftapi.ApplyMsg) *Raft {
	// 输入是四个值
	// 1.peers[] 所有raft节点的列表信息,我们不用过多考虑
	// 2.me 自身节点的编号,对应在peers[]中的位置
	// 3. persister 一个指向 tester.Persister 类型的指针,用于持久化当前节点的状态信息,负责当前节点状态信息的持久化存储和读取
	// 4.作为节点与状态机之间的通信桥梁,用于传递已提交的日志条目相关信息,以便实现状态的更新和数据的应用,是 Raft 算法与外部组件进行交互的关键途径。

	rf := &Raft{}
	// 初始化一个raft节点

	rf.peers = peers
	rf.persister = persister
	rf.me = me
	// 把make函数的输入保存到raft节点中

	// 初始当前任期
	rf.currentTerm = 0
	// 没有给任何人投票
	rf.votedFor = -1
    // 初始化日志条目切片,初始长度为 0
    rf.log = make([]logEntry, 0)

	// 初始化已提交日志的最大索引为 -1
	rf.commitIndex = -1
	// 初始化最后应用到状态机的日志索引为 -1
	rf.lastApplied = -1

	// 初始化 nextIndex 切片,用于记录需要发送给每个节点的下一个日志条目的索引
	rf.nextIndex = make([]int, len(peers))
	// 初始化 matchIndex 切片,用于记录每个节点已经复制的最大日志条目的索引
	rf.matchIndex = make([]int, len(peers))

	// 初始状态设置为 Follower
	rf.state = Follower
	// 初始时获得的投票数为 0
	rf.voteCount = 0
	// 初始化选举超时定时器,超时时间在 150 到 350 毫秒之间随机
	rf.timer = Timer{timer: time.NewTicker(time.Duration(150+rand.Intn(200)) * time.Millisecond)}

	// 从持久化存储中读取之前崩溃前保存的状态信息
	rf.readPersist(persister.ReadRaftState())

	// 启动一个 goroutine 来处理选举定时器
	go rf.ticker()

	// 返回初始化好的 Raft 节点实例
	return rf
}

都是一些初始化操作,最重要的是 go rf.ticker(),这是我们lab3A部分的关键实现

下面来看具体的 rf.ticker()
首先介绍一点timer定时器


// 重置定时器,重新开始计时
	rf.timer.reset() 
 
func (t *Timer) reset() {
	randomTime := time.Duration(150+rand.Intn(200)) * time.Millisecond // 200~350ms
	t.timer.Reset(randomTime)                                          // 重置时间
}

通过reset方法,给timer传入一个200-350ms的时间值,那么,在时间到了的时候,会向timer.C这个chanel会发送信号

// ticker 方法是一个循环函数,用于处理 Raft 节点的选举和心跳逻辑。
// 它会在节点未被杀死的情况下持续运行,根据定时器的触发来执行不同的操作。
func (rf *Raft) ticker() {
    // 只要节点未被标记为已杀死,就持续循环
    for rf.killed() == false {
        // 使用 select 语句监听定时器的通道
        select {
        // 当定时器到期时,执行以下操作
        case <-rf.timer.timer.C:
			// rf.timer.timer.C传来信号,就说明计时器到时间了,那么根据此时节点的状态做出相应的操作
            // 加锁以保证线程安全,避免多个 goroutine 同时修改节点状态
            rf.mu.Lock()

            // 根据节点的当前状态执行不同的逻辑
            switch rf.state {
            // 如果当前是 Follower 状态
            case Follower: //如果此时节点的状态是Follwer,及在follower状态下超时了,那么根据raft的设计
							//此时应该转变为 Candidate 状态,开始参与选举
                rf.state = Candidate
                // 打印调试信息,注释掉以避免干扰正常输出
                // fmt.Println(rf.me, "进入candidate状态")
                // fallthrough 关键字让程序继续执行下一个 case 分支
                fallthrough
            // 如果当前是 Candidate 状态
            case Candidate: 
                // 增加当前任期号,表示开始新的选举周期
                rf.currentTerm++ 
                // 给自己投一票,初始得票数为 1
                rf.voteCount = 1 
                // 重置定时器,重新开始计时
                rf.timer.reset() 
                // 记录自己把票投给了自己
                rf.votedFor = rf.me 

                // 遍历所有节点,向其他节点发送投票请求
                for i := 0; i < len(rf.peers); i++ {
                    // 排除自己,不向自己发送投票请求
                    if rf.me == i { 
                        continue
                    }
                    // 构造投票请求的参数
                    args := RequestVoteArgs{
                        Term:         rf.currentTerm,
                        CandidateId:  rf.me,
                        LastLogIndex: len(rf.log) - 1,
                    }
                    // 如果日志不为空,设置最后一条日志的任期号
                    if len(rf.log) > 0 {
                        args.LastLogTerm = rf.log[len(rf.log)-1].Term
                    }
                    // 初始化投票响应结构体
                    reply := RequestVoteReply{}
                    // 启动一个新的 goroutine 异步发送投票请求
                    go rf.sendRequestVote(i, &args, &reply)
                }
            // 如果当前是 Leader 状态
            case Leader:
                // 重置心跳定时器,开始新的心跳周期
                rf.timer.resetHeartBeat() 
                // 遍历所有节点,向其他节点发送心跳消息(附加日志条目请求)
                for i := 0; i < len(rf.peers); i++ {
                    // 排除自己,不向自己发送心跳消息
                    if i == rf.me {
                        continue
                    }
                    // 构造附加日志条目请求的参数
                    args := AppendEntriesArgs{
                        Term:     rf.currentTerm,
                        LeaderId: rf.me,
                    }
                    // 初始化附加日志条目响应结构体
                    reply := AppendEntriesReply{}
                    // 启动一个新的 goroutine 异步发送附加日志条目请求
                    go rf.sendAppendEntries(i, &args, &reply)
                }
            }
            // 解锁,允许其他 goroutine 访问节点状态
            rf.mu.Unlock()
        }
    }
}

然后我们来看,如果当前是 Candidate 状态出发了定时器,说明要启动选举了
在初始化一些参数后,for循环启动
对于每一个peers成员(其他节点)
都启动一个 go rf.sendRequestVote(i, &args, &reply)
来请求投票
我们来看

// sendRequestVote 函数用于向指定的服务器发送投票请求,并处理投票响应。
// 它会持续尝试发送 RPC 请求,直到成功。根据响应结果更新节点的状态和投票计数。
func (rf *Raft) sendRequestVote(server int, args *RequestVoteArgs, reply *RequestVoteReply) bool {
    // 尝试向指定服务器发送投票请求的 RPC 调用
    ok := rf.peers[server].Call("Raft.RequestVote", args, reply)
    // 如果第一次调用失败,则持续尝试,直到成功
    for !ok {
        ok = rf.peers[server].Call("Raft.RequestVote", args, reply)
    }

    // 加锁以保证线程安全,避免多个 goroutine 同时修改节点状态
    rf.mu.Lock()
    // 函数结束时自动解锁
    defer rf.mu.Unlock()

    // 收到过期的 RPC 回复,即请求的任期号小于当前节点的任期号,不处理该回复
    if args.Term < rf.currentTerm {
        return false
    }

    // 如果对方同意投票
    if reply.VoteGranted {
        // 增加当前节点获得的投票数
        rf.voteCount++
        // 如果获得的投票数超过节点总数的一半
        if rf.voteCount > len(rf.peers)/2 {
            // 打印调试信息,可注释掉以避免干扰正常输出
            // fmt.Println("新王登基,他的 ID 是:", rf.me)
            // 当前节点成为领导者
            rf.state = Leader
            // 重置定时器为心跳定时器,开始发送心跳消息
            rf.timer.resetHeartBeat() 
        }
    } else {
        // 对方拒绝投票,此处可添加更多处理逻辑,当前仅作占位
    }

    return true
}

然后我们可以看到

const HeartBeatTimeout = 125 * time.Millisecond

func (t *Timer) resetHeartBeat() {
	t.timer.Reset(HeartBeatTimeout)
}

也就是说,leader节点超时的时间是固定125ms,而其他的节点则是随机200-350ms
leader可以在其他节点超时之前,通过go rf.sendAppendEntries(i, &args, &reply)

刷新定时器的时间

// sendAppendEntries 函数用于向指定的服务器发送附加日志条目请求(AppendEntries RPC),
// 该请求可用于领导者发送心跳消息或复制日志条目。函数会持续尝试发送 RPC 请求,直到成功,
// 并根据响应结果更新当前节点的状态和任期信息。
func (rf *Raft) sendAppendEntries(server int, args *AppendEntriesArgs, reply *AppendEntriesReply) bool {
    // 尝试向指定服务器发送附加日志条目请求的 RPC 调用
    ok := rf.peers[server].Call("Raft.AppendEntries", args, reply)
    // 如果第一次调用失败,则持续尝试,直到成功
    for !ok {
        ok = rf.peers[server].Call("Raft.AppendEntries", args, reply)
    }

    // 加锁以保证线程安全,避免多个 goroutine 同时修改节点状态
    rf.mu.Lock()
    // 函数结束时自动解锁
    defer rf.mu.Unlock()

    // 1. 收到过期的 RPC 回复
    // 如果请求中的任期号小于当前节点的任期号,说明该请求是过期的,直接返回 false 不处理
    if args.Term < rf.currentTerm {
        return false
    }

    // 2. 心跳或日志追加未成功
    // 如果响应中的 Success 字段为 false,表示附加日志条目操作未成功,
    // 通常意味着该节点的任期号已经过时,需要进行状态转换
    if !reply.Success {
        // 当前节点转变为追随者状态
        rf.state = Follower
        // 更新当前节点的任期号为响应中携带的任期号
        rf.currentTerm = reply.Term
        // 重置已投票节点的信息,表示还未投票给任何节点
        rf.votedFor = -1
        // 重置投票计数
        rf.voteCount = 0
        // 重置定时器,开始新的选举计时周期
        rf.timer.reset()
    }

    // 若执行到这里,说明请求和响应处理基本正常,返回 true
    return true
}

到此为止,3A完成

### MIT 6.5840 Course Environment Setup Guide #### Prerequisites Installation To set up the development environment for the MIT 6.5840 (also known as 6.824) course, it is essential to install several prerequisites on one&#39;s local machine or virtual environment. The primary tools required include Go programming language and Git version control system because these labs heavily rely on writing code in Go[^1]. For installing Go, users should download an installer from the official website suitable for their operating systems—Windows, macOS, Linux—and follow installation instructions provided there. Afterward, verify that Go has been installed correctly by checking its version using `go version` command. Similarly, ensure Git is available since cloning repositories will be necessary throughout this coursework. Installing Git can also be done via package managers like Homebrew (`brew install git`) on macOS or apt-get (`sudo apt-get install git`) under Debian-based distributions of Linux[^3]. ```bash # Verify installations go version git --version ``` #### Setting Up SSH Keys with GitHub Since many assignments involve interacting directly with remote repositories hosted at GitHub, setting up Secure Shell (SSH) keys ensures secure communication between a developer’s computer and repository server without needing passwords repeatedly during push/pull operations. Generate new SSH key pairs locally if not already present: ```bash ssh-keygen -t ed25519 -C "your_email@example.com" eval "$(ssh-agent -s)" ssh-add ~/.ssh/id_ed25519 ``` Then add generated public key content into personal account settings page within GitHub interface through web browser access[^2]. #### Cloning Repository & Initial Configuration With all dependencies met, proceed towards fetching source codes related specifically designed for educational purposes associated with MIT 6.5840 lab exercises. Use HTTPS URL when performing initial clone operation unless private SSH URLs are preferred instead due to firewall restrictions possibly encountered behind corporate networks. ```bash mkdir ~/workspace && cd $_ git clone https://github.com/username/mit-6.5840-labs.git cd mit-6.5840-labs make clean mr-test ``` After successfully obtaining project files along with any submodules they might depend upon, executing tests immediately after setup helps confirm everything works properly before starting modifications intended for completing given tasks outlined per each assignment specification document distributed alongside other learning materials provided officially by instructors teaching such advanced topics covered inside curriculum units offered periodically across semesters depending on academic calendar schedules followed closely according to institutional guidelines established previously over time based off accumulated experiences gathered collectively among faculty members involved actively contributing valuable insights gained while preparing comprehensive study guides aimed primarily toward helping students achieve mastery level proficiency skills sought after widely within industry sectors looking forward eagerly anticipating promising candidates capable enough demonstrating strong foundational knowledge coupled together effectively utilizing practical applications learned thoroughly hereafter becoming proficient practitioners ready tackling real-world challenges faced daily out there beyond classroom walls where theoretical concepts meet actual implementations requiring innovative thinking processes leading ultimately successful outcomes achieved collaboratively working side-by-side peers sharing common goals striving excellence always pushing boundaries further expanding horizons evermore reaching heights once thought unimaginable now made possible thanks largely contributions made continuously overtime advancing state-of-the-art technologies shaping future directions taken confidently moving ahead steadfastly pursuing progress relentlessly driven passion fueling relentless pursuit innovation constantly evolving adapting changing landscapes emerging rapidly today&#39;s fast-paced world demanding adaptability resilience perseverance determination above all else qualities embodied true leaders tomorrow prepared well today. --related questions-- 1. What specific versions of software packages need to match exactly for compatibility reasons? 2. How does one troubleshoot issues arising post-installation but prior to running make commands? 3. Are alternative methods besides SSH recommended for accessing protected resources securely? 4. Can this setup procedure apply similarly to different editions of the same course material released later?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值