6.824 lab2 Part A

一、实验说明

https://pdos.csail.mit.edu/6.824/labs/lab-raft.html

二、梳理

在本实验中,需要实现Raft。

Raft 是一种为了管理复制日志的一致性算法。复制服务通过将其状态(即数据)的完整副本存储在多个副本服务器上来实现容错功能。即使服务的某些服务器出现故障(崩溃,网络故障或不稳定),复制也可以使服务继续运行。挑战在于,故障可能导致副本持有不同的数据副本。

Raft管理服务的状态副本,特别是它可以帮助服务找出故障后正确的状态。Raft实现了一个复制状态机。它将客户请求组织成一个序列,称为日志,并确保所有副本都同意日志的内容。每个副本都按照它们在日志中出现的顺序执行日志中的客户端请求,并将这些请求应用于服务状态的副本本地副本。由于所有活动复本都看到相同的日志内容,因此它们都以相同的顺序执行相同的请求,因此继续具有相同的服务状态。如果服务器出现故障但后来又恢复了,Raft会确保其日志为最新状态。只要至少大多数服务器都处于活动状态并且可以相互通信,Raft将继续运行。

Raft特性:

  • 强领导者:和其他一致性算法相比,Raft 使用一种更强的领导能力形式。比如,日志条目只从领导者发送给其他的服务器。这种方式简化了对复制日志的管理并且使得 Raft 算法更加易于理解。
  • 领导选举:Raft 算法使用一个随机计时器来选举领导者。这种方式只是在任何一致性算法都必须实现的心跳机制上增加了一点机制。在解决冲突的时候会更加简单快捷。
  • 成员关系调整:Raft 使用一种共同一致的方法来处理集群成员变换的问题,在这种方法下,处于调整过程中的两种不同的配置集群中大多数机器会有重叠,这就使得集群在成员变换的时候依然可以继续工作。

Raft定义

Raft三种状态,分别为

Follower

  • 接收并处理心跳包
  • 接收并处理选举
  • 超时未收到心跳包,变为Candidate

Candidate

  • 接收并处理心跳包
  • 接收并处理选举
  • 处理超时未收到心跳包
  • 发起投票,成功变为Leader,变为Follower

Leader

  • 接收并处理心跳包
  • 接收并处理选举
  • 发送心跳包,失败变为Follower

基于以上,需要实现两个周期性的方法:

  • 作为Candidate时,发起投票的 startElection(定义electionTimer,收到心跳包从新计时,到期发起投票
  • 作为Leader时,发送心跳包broadcastHeartbeat(定义heartbeatTimer,,到期发送心跳包

实现两个方法:

  • 对应投票的 RequestVote
  • 对应心跳包的 AppendEntries

Part 2A

状态和timer

const (
	Follower  int32 = 0
	Candidate int32 = 1
	Leader    int32 = 2

	HeartbeatInterval  = time.Duration(120) * time.Millisecond
	ElectionTimeoutMin = time.Duration(300) * time.Millisecond
	ElectionTimeoutMax = time.Duration(400) * time.Millisecond
)

func randTimeDuration(lower, upper time.Duration) time.Duration {
	num := rand.Int63n(upper.Nanoseconds()-lower.Nanoseconds()) + lower.Nanoseconds()
	return time.Duration(num) * time.Nanosecond
}

状态转换方法

func (rf *Raft) convertTo(state int32) {
	if state == rf.state {
		return
	}
	rf.state = state
	switch state {
	case Follower:
		rf.heartbeatTimer.Stop()
		rf.electionTimer.Reset(randTimeDuration(ElectionTimeoutMin, ElectionTimeoutMax))
		rf.votedFor = -1

	case Candidate:
		rf.startElection()

	case Leader:
		rf.electionTimer.Stop()
		rf.broadcastHeartbeat()
		rf.heartbeatTimer.Reset(HeartbeatInterval)
	}
}

Raft

currentTerm    当前版本
votedFor       竞选对象
electionTimer  竞选timer
heartbeatTimer 心跳包timer
state          状态
type Raft struct {
	mu        sync.Mutex          // Lock to protect shared access to this peer's state
	peers     []*labrpc.ClientEnd // RPC end points of all peers
	persister *Persister          // Object to hold this peer's persisted state
	me        int                 // this peer's index into peers[]

	currentTerm    int32
	votedFor       int
	electionTimer  *time.Timer
	heartbeatTimer *time.Timer
	state          int32

}
添加goroutine处理electionTimer和heartbeatTimer
rf.votedFor = -1
	rf.currentTerm = 0
	rf.heartbeatTimer = time.NewTimer(HeartbeatInterval)
	rf.electionTimer = time.NewTimer(randTimeDuration(ElectionTimeoutMin, ElectionTimeoutMax))
	rf.state = Follower

go func(node *Raft) {
		for {
			select {
			case <-rf.electionTimer.C:
				rf.mu.Lock()
				if rf.state == Follower {
					rf.convertTo(Candidate)
				} else {
					rf.startElection()
				}
				rf.mu.Unlock()

			case <-rf.heartbeatTimer.C:
				rf.mu.Lock()
				if rf.state == Leader {
					rf.broadcastHeartbeat()
					rf.heartbeatTimer.Reset(HeartbeatInterval)
				}
				rf.mu.Unlock()
			}
		}
	}(rf)

两个周期性的方法

注意点

  • 处理返回结果时,一定要加锁后处理数据
  • 需要考虑是否是当前节点发起的请求
  • goroutine并发问题

发起选举startElection

发起投票,如果返回成功,计数加一,如果同意方多于总数的一半,则状态改为Leader

如果发现其他节点当前版本大于发起选举的节点,则状态改为Follower

func (rf *Raft) startElection() {
	rf.currentTerm += 1
	rf.electionTimer.Reset(randTimeDuration(ElectionTimeoutMin, ElectionTimeoutMax))

	args := RequestVoteArgs{
		Term:         rf.currentTerm,
		CandidateId:  rf.me,
	}

	var voteCount int32

	for i := range rf.peers {
		if i == rf.me {
			rf.votedFor = rf.me
			atomic.AddInt32(&voteCount, 1)

			continue
		}

		go func(server int) {
			var reply RequestVoteReply

			if rf.sendRequestVote(server, &args, &reply) {
				rf.mu.Lock()
				if reply.VoteGranted && rf.state == Candidate {
					atomic.AddInt32(&voteCount, 1)

					if atomic.LoadInt32(&voteCount) > int32(len(rf.peers)/2) {
						rf.convertTo(Leader)
					}
				} else {
					if reply.Term > rf.currentTerm {
						rf.currentTerm = reply.Term
						rf.convertTo(Follower)
					}
				}

				rf.mu.Unlock()
			}
		}(i)
	}
}

Leader发送心跳包

如果发现其他节点当前版本大于发送心跳包的节点(可能存在网络分割等情况,导致有多个leader),则状态改为Follower

func (rf *Raft) broadcastHeartbeat() {
	for i := range rf.peers {
		if i == rf.me {
			continue
		}
		go func(server int) {
			if rf.state != Leader {
				rf.mu.Unlock()
				return
			}


			args := AppendEntriesArgs{
				Term:         rf.currentTerm,
				LeaderId:     rf.me,
			}

			var reply AppendEntriesReply
			if rf.sendAppendEntries(server, &args, &reply) {
				rf.mu.Lock()
                
                if reply.Term > rf.currentTerm {
				    rf.currentTerm = reply.Term
				    rf.convertTo(Follower)
                }

				rf.mu.Unlock()
			}
		}(i)
	}
}

实现两个方法

对应投票的 RequestVote

如果当前节点的版本大于传入的版本,或者当前节点版本等于当前版本但是当前节点已经选举其他节点(同时有其他节点在发起选举),则返回失败

若成功需要Reset electionTimer(重新开始计时)

func (rf *Raft) RequestVote(args *RequestVoteArgs, reply *RequestVoteReply) {
	rf.mu.Lock()
	defer rf.mu.Unlock()

	if args.Term < rf.currentTerm || (args.Term == rf.currentTerm && rf.votedFor != -1  && rf.votedFor != args.CandidateId) {
		reply.Term = rf.currentTerm
		reply.VoteGranted = false
		return
	}

    if args.Term > rf.currentTerm {
		rf.currentTerm = args.Term
		rf.convertTo(Follower)
	}

	rf.electionTimer.Reset(randTimeDuration(ElectionTimeoutMin, ElectionTimeoutMax))

	rf.votedFor = args.CandidateId
	reply.VoteGranted = true
}

对应心跳包的 AppendEntries

如果当前节点的版本大于传入的版本,则返回失败

若成功需要Reset electionTimer(重新开始计时)

func (rf *Raft) AppendEntries(args *AppendEntriesArgs, reply *AppendEntriesReply) {
	rf.mu.Lock()
	defer rf.mu.Unlock()

	if args.Term < rf.currentTerm {
		reply.IsSuccess = false
		reply.Term = rf.currentTerm
		return
	}

    if args.Term > rf.currentTerm {
		rf.currentTerm = args.Term
		rf.convertTo(Follower)
	}

    rf.electionTimer.Reset(randTimeDuration(ElectionTimeoutMin, ElectionTimeoutMax))
	reply.Success = true
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值