Raft——分布式一致性算法 go简单实现

Raft 是什么

⚫ Raft 提供了一种在计算系统集群中分布状态机的通用方法,确保集群中的每个节点 都同意一系列相同的状态转换

⚫ 它有许多开源参考实现,具有 Go,C ++,Java 和 Scala 中的完整规范实现

⚫ 一个 Raft 集群包含若干个服务器节点,通常是 5 个,这允许整个系统容忍 2 个节 点的失效,每个节点处于以下三种状态之一

◼follower(跟随者) :所有节点都以 follower 的状态开始。如果没收到 leader 消息则会变成 candidate 状态

◼candidate(候选人):会向其他节点“拉选票”,如果得到大部分的票则成为 leader,这个过程就叫做 Leader 选举(Leader Election)

◼leader(领导者):所有对系统的修改都会先经过 leader

⚫ Raft 通过选出一个 leader 来简化日志副本的管理,例如,日志项(log entry)只允许

从 leader 流向 follower

⚫ 基于 leader 的方法,Raft 算法可以分解成三个子问题

◼Leader election (领导选举):原来的 leader 挂掉后,必须选出一个新的 leader

◼Log replication (日志复制):leader 从客户端接收日志,并复制到整个集群中

◼Safety (安全性):如果有任意的 server 将日志项回放到状态机中了,那么其他 的 server 只会回放相同的日志项


//模拟三节点的分布式选举

//定义常量3
const raftCount = 3

//声明leader对象
type Leader struct {
	//任期
	Term int
	//领导编号
	LeaderId int
}

//创建存储leader的对象
//最初任期为0,-1代表没编号
var leader = Leader{0, -1}

//声明raft节点类型
type Raft struct {
	//锁
	mu sync.Mutex
	//节点编号
	me int
	//当前任期
	currentTerm int
	//为哪个节点投票
	votedFor int
	//当前节点状态
	//0 follower  1 candidate  2 leader
	state int
	//发送最后一条消息的时间
	lastMessageTime int64
	//当前节点的领导
	currentLeader int

	//消息通道
	message chan bool
	//选举通道
	electCh chan bool
	//心跳信号
	heartBeat chan bool
	//返回心跳信号
	hearbeatRe chan bool
	//超时时间
	timeout int
}

func main() {
	//过程:创建三个节点,最初是follower状态
	//如果出现candidate状态的节点,则开始投票
	//产生leader

	//创建三个节点
	for i := 0; i < raftCount; i++ {
		//定义Make() 创建节点
		Make(i)
	}

	//对raft结构体实现rpc注册
	rpc.Register(new(Raft))
	rpc.HandleHTTP()
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		log.Fatal(err)
	}

	//防止选举没完成,main结束了
	for {;
	}
}

//创建节点
func Make(me int) *Raft {
	rf := &Raft{}
	//编号
	rf.me = me
	//给0  1  2三个节点投票,给谁都不投
	rf.votedFor = -1
	//0 follower
	rf.state = 0
	rf.timeout = 0
	//最初没有领导
	rf.currentLeader = -1
	//设置任期
	rf.setTerm(0)
	//通道
	rf.electCh = make(chan bool)
	rf.message = make(chan bool)
	rf.heartBeat = make(chan bool)
	rf.hearbeatRe = make(chan bool)
	//随机种子
	rand.Seed(time.Now().UnixNano())

	//选举的逻辑实现
	go rf.election()
	//心跳检查
	go rf.sendLeaderHeartBeat()

	return rf
}

func (rf *Raft) setTerm(term int) {
	rf.currentTerm = term
}

//设置节点选举
func (rf *Raft) election() {
	//设置标签
	var result bool
	//循环投票
	for {
		timeout := randRange(150, 300)
		//设置每个节点最后一条消息的时间
		rf.lastMessageTime = millisecond()
		select {
		case <-time.After(time.Duration(timeout) * time.Millisecond):
			fmt.Println("当前节点状态为:", rf.state)
		}
		result = false
		//选leader,如果选出leader,停止循环,result设置为true
		for !result {
			//选择谁为leader
			result = rf.election_one_rand(&leader)
		}
	}
}

//产生随机值
func randRange(min, max int64) int64 {
	//用于心跳信号的时间等
	return rand.Int63n(max-min) + min
}

//获取当前时间的毫秒数
func millisecond() int64 {
	return time.Now().UnixNano() / int64(time.Millisecond)
}

//选leader
func (rf *Raft) election_one_rand(leader *Leader) bool {
	//超时时间
	var timeout int64
	timeout = 100
	//投票数量
	var vote int
	//用于是否开始心跳信号的方法
	var triggerHeartbeat bool
	//当前时间戳对应的毫秒
	last := millisecond()
	//定义返回值
	success := false

	//首先,要成为candidate状态
	rf.mu.Lock()
	rf.becomeCandidate()
	rf.mu.Unlock()

	//开始选
	fmt.Println("start electing leader")
	for {
		//遍历所有节点进行投票
		for i := 0; i < raftCount; i++ {
			//遍历到不是自己,则进行拉票
			if i != rf.me {
				//其他节点,拉票
				go func() {
					//其他节点没有领导
					if leader.LeaderId < 0 {
						//操作选举通道
						rf.electCh <- true
					}
				}()
			}
		}
		//设置投票数量
		vote = 0
		triggerHeartbeat = false
		//遍历所有节点进行选举
		for i := 0; i < raftCount; i++ {
			//计算投票数量
			select {
			case ok := <-rf.electCh:
				if ok {
					//投票数量+1
					vote ++
					//返回值success
					//大于总票数的一半
					success = vote > raftCount/2
					//成领导的状态
					//如果票数大于一半,且未出发心跳信号
					if success && !triggerHeartbeat {
						//选举成功
						//发心跳信号
						triggerHeartbeat = true

						rf.mu.Lock()
						//真正的成为leader
						rf.becomeLeader()
						rf.mu.Unlock()

						//由leader向其他节点发送心跳信号
						//心跳信号的通道
						rf.heartBeat <- true
						fmt.Println(rf.me, "号节点成为了leader")
						fmt.Println("leader发送心跳信号")
					}
				}
			}
		}
		//间隔时间小于100毫秒左右
		//若不超时,且票数大于一半,且当前有领导
		if (timeout+last < millisecond() || (vote >= raftCount/2 || rf.currentLeader > -1)) {
			//结束循环
			break
		} else {
			//没有选出leader
			select {
			case <-time.After(time.Duration(10) * time.Millisecond):
			}
		}
	}
	return success
}

//修改节点为candidate状态
func (rf *Raft) becomeCandidate() {
	//将节点状态变为1
	rf.state = 1
	//节点任期加1
	rf.setTerm(rf.currentTerm + 1)
	//设置为哪个节点投票
	rf.votedFor = rf.me
	//当前没有领导
	rf.currentLeader = -1
}

func (rf *Raft) becomeLeader() {
	//节点状态变为2,代表leader
	rf.state = 2
	rf.currentLeader = rf.me
}

//设置发送心跳信号的方法
//只考虑leader没有挂的情况
func (rf *Raft) sendLeaderHeartBeat() {
	for {
		select {
		case <-rf.heartBeat:
			//给leader返回确认信号
			rf.sendAppendEntriesImpl()
		}
	}
}

//返回给leader的确认信号
func (rf *Raft) sendAppendEntriesImpl() {
	//判断当前是否是leader节点
	if rf.currentLeader == rf.me {
		//声明返回确认信号的节点个数
		var success_count = 0

		//设置返回确认信号的子节点
		for i := 0; i < raftCount; i++ {
			//若当前不是本节点
			if i != rf.me {
				go func() {
					//子节点有返回
					//rf.hearbeatRe <- true

					//rpc
					rp, err := rpc.DialHTTP("tcp", "127.0.0.1:8080")
					if err != nil {
						log.Fatal(err)
					}
					//接收服务端发来的消息
					var ok = false
					er := rp.Call("Raft.Communication", Param{"hello"}, &ok)
					if er != nil {
						log.Fatal(err)
					}
					if ok {
						//rpc通信的情况下,子节点有返回
						rf.hearbeatRe <- true
					}
				}()
			}
		}
		//计算返回确认信号的子节点,若子节点个数>raftCount/2,则校验成功
		for i := 0; i < raftCount; i++ {
			select {
			case ok := <-rf.hearbeatRe:
				if ok {
					//记录返回确认信号的子节点的个数
					success_count++
					if success_count > raftCount/2 {
						fmt.Println("投票选举成功,校验心跳信号成功")
						log.Fatal("程序结束")
					}
				}
			}
		}
	}
}

//通过RPC实现分布式调用
//RPC:你电脑访问我电脑里的函数,也就是访问内存
//早期实现:基于HTTP协议,用XML实现
//基于TCP协议的RPC

//分布式通信
type Param struct {
	Msg string
}

//等待客户端消息
func (r *Raft) Communication(p Param, a *bool) error {
	fmt.Println(p.Msg)
	*a = true
	return nil
}

猜你喜欢

转载自blog.csdn.net/s15738841819/article/details/84286276