Raft 一致性算法

Raft

Raft提供了一种在计算系统集群中分布状态机的通用方法,确保集群中的每个节点都同意一系列相同的状态转换。
一个Raft集群包含若干个服务器节点,通常为5个,这允许整个系统容忍2个节点的失效。每个节点处于以下三种状态之一:

  1. follower(跟随者):所有节点都以follower的状态开始。如果没有收到leader的消息则会变成candidate状态。
  2. candidate(候选人):会向其他节点“拉选票”,如果得到大部分的票则成为leader,这个过程叫做Leader选举(Leader Election)。未当选Leader的节点会将状态转换为follower。
  3. leader(领导者):所有对系统的修改都会先经过leader。

raft 是 etcd 和consoul的核心算法。

Raft 一致性算法

  • Raft通过选出一个leader来简化日志副本的管理,例如,日志项(log entry)只允许从leader流向follower。
  • 基于leader的方法,Raft算法可以分解成三个子问题。
    1. Leader election(领导选举):原来的leader挂掉后,必须选出一个新的leader。
    2. Log replication(日志复制):leader从客户端接收日志,并复制到整个集群中。
    3. Safety(安全性):如果有任意的server将日志项回放到状态机中了,那么其他的server只会回放相同的日志项。

动画演示

地址:http://thesecretlivesofdata.com/raft/
网络节点必须是奇数个。
动画主要包含三部分内容:

  • 第一部分介绍简单版的领导者选举和日志复制的过程
  • 第二部分介绍详细版的领导者选举和日志复制过程
  • 第三部分介绍如果遇到网络分区(脑裂),raft算法是如何回复网络一致的。

Leader election(领导选举)

  • Raft 使用一种心跳机制来触发领导人选举
  • 当服务器程序启动时,节点都是 follower(跟随者) 身份
  • 如果一个跟随者在一段时间里没有接收到任何消息,也就是选举超时,然后他就会认为系统中没有可用的领导者然后开始进行选举以选出新的领导者
  • 要开始一次选举过程,follower 会给当前term加1并且转换成candidate状态,然后它会并行的向集群中的其他服务器节点发送请求投票的 RPCs 来给自己投票。
  • 候选人的状态维持直到发生以下任何一个条件发生的时候
    • 他自己赢得了这次的选举
    • 其他的服务器成为领导者
    • 一段时间之后没有任何一个获胜的人

Log replication(日志复制)

  • 当选出 leader 后,它会开始接收客户端请求,每个请求会带有一个指令,可以被回放到状态机中
  • leader 把指令追加成一个log entry,然后通过AppendEntries RPC并行地发送给其他的server,当该entry被多数server复制后,leader 会把该entry回放到状态机中,然后把结果返回给客户端
  • 当 follower 宕机或者运行较慢时,leader 会无限地重发AppendEntries给这些follower,直到所有的follower都复制了该log entry
  • raft的log replication要保证如果两个log entry有相同的index和term,那么它们存储相同的指令
  • leader在一个特定的term和index下,只会创建一个log entry

代码

package main

import (
	"os"
	"strconv"
	"fmt"
	"sync"
	"net/rpc"
	"net/http"
	"math/rand"
	"time"

	"log"
)

//对每个节点id和端口的封装类型
type nodeInfo struct {
	id   string
	port string
}

//声明节点对象类型Raft
type Raft struct {
	node nodeInfo
	mu   sync.Mutex
	//当前节点编号
	me            int
	currentTerm   int
	votedFor      int
	state         int
	timeout       int
	currentLeader int
	//该节点最后一次处理数据的时间
	lastMessageTime int64

	message   chan bool
	eclectCh  chan bool
	heartbeat chan bool
	//子节点给主节点返回心跳信号
	heartbeatRe chan bool
}

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

//设置节点个数
const raftCount = 2

var leader = Leader{0, -1}
//存储缓存信息
var bufferMessage = make(map[string]string)
//处理数据库信息
var mysqlMessage = make(map[string]string)
//操作消息数组下标
var messageId = 1
//用nodeTable存储每个节点中的键值对
var nodeTable map[string]string

func main() {
	//终端接收来的是数组
	if len(os.Args) > 1 {
		//接收终端输入的信息
		userId := os.Args[1]
		//字符串转换整型
		id, _ := strconv.Atoi(userId)
		fmt.Println(id)
		//定义节点id和端口号
		nodeTable = map[string]string{
			"1": ":8000",
			"2": ":8001",
		}
		//封装nodeInfo对象
		node := nodeInfo{id: userId, port: nodeTable[userId]}
		//创建节点对象
		rf := Make(id)
		//确保每个新建立的节点都有端口对应
		//127.0.0.1:8000
		rf.node = node
		//注册rpc
		go func() {
			//注册rpc,为了实现远程链接
			rf.raftRegisterRPC(node.port)
		}()
		if userId == "1" {
			go func() {
				//回调方法
				http.HandleFunc("/req", rf.getRequest)
				fmt.Println("监听8080")
				if err := http.ListenAndServe(":8080", nil); err != nil {
					fmt.Println(err)
					return
				}
			}()
		}
	}
	for {;
	}
}

var clientWriter http.ResponseWriter

func (rf *Raft) getRequest(writer http.ResponseWriter, request *http.Request) {
	request.ParseForm()
	if len(request.Form["age"]) > 0 {
		clientWriter = writer
		fmt.Println("主节点广播客户端请求age:", request.Form["age"][0])

		param := Param{Msg: request.Form["age"][0], MsgId: strconv.Itoa(messageId)}
		messageId++
		if leader.LeaderId == rf.me {
			rf.sendMessageToOtherNodes(param)
		} else {
			//将消息转发给leader
			leaderId := nodeTable[strconv.Itoa(leader.LeaderId)]
			//连接远程rpc服务
			rpc, err := rpc.DialHTTP("tcp", "127.0.0.1"+leaderId)
			if err != nil {
				log.Fatal("\nrpc转发连接server错误:", leader.LeaderId, err)
			}
			var bo = false
			//首先给leader传递
			err = rpc.Call("Raft.ForwardingMessage", param, &bo)
			if err != nil {
				log.Fatal("\nrpc转发调用server错误:", leader.LeaderId, err)
			}
		}
	}
}

func (rf *Raft) sendMessageToOtherNodes(param Param) {
	bufferMessage[param.MsgId] = param.Msg
	// 只有领导才能给其它服务器发送消息
	if rf.currentLeader == rf.me {
		var success_count = 0
		fmt.Printf("领导者发送数据中 。。。\n")
		go func() {
			rf.broadcast(param, "Raft.LogDataCopy", func(ok bool) {
				//需要其它服务端回应
				rf.message <- ok
			})
		}()

		for i := 0; i < raftCount-1; i++ {
			fmt.Println("等待其它服务端回应")
			select {
			case ok := <-rf.message:
				if ok {
					success_count++
					if success_count >= raftCount/2 {
						rf.mu.Lock()
						rf.lastMessageTime = milliseconds()
						mysqlMessage[param.MsgId] = bufferMessage[param.MsgId]
						delete(bufferMessage, param.MsgId)
						if clientWriter != nil {
							fmt.Fprintf(clientWriter, "OK")
						}
						fmt.Printf("\n领导者发送数据结束\n")
						rf.mu.Unlock()
					}
				}
			}
		}
	}
}

//注册Raft对象,注册后的目的为确保每个节点(raft) 可以远程接收
func (node *Raft) raftRegisterRPC(port string) {
	//注册一个服务器
	rpc.Register(node)
	//把服务绑定到http协议上
	rpc.HandleHTTP()
	err := http.ListenAndServe(port, nil)
	if err != nil {
		fmt.Println("注册rpc服务失败", err)
	}
}

//创建节点对象
func Make(me int) *Raft {
	rf := &Raft{}
	rf.me = me
	rf.votedFor = -1
	//0 follower ,1 candidate ,2 leader
	rf.state = 0
	rf.timeout = 0
	rf.currentLeader = -1
	rf.setTerm(0)

	//初始化通道
	rf.message = make(chan bool)
	rf.heartbeat = make(chan bool)
	rf.heartbeatRe = make(chan bool)
	rf.eclectCh = make(chan bool)

	//每个节点都有选举权
	go rf.election()
	//每个节点都有心跳功能
	go rf.sendLeaderHeartBeat()

	return rf
}

//选举成功后,应该广播所有的节点,本节点成为了leader
func (rf *Raft) sendLeaderHeartBeat() {
	for {
		select {
		case <-rf.heartbeat:
			rf.sendAppendEntriesImpl()
		}
	}
}

func (rf *Raft) sendAppendEntriesImpl() {
	if rf.currentLeader == rf.me {
		var success_count = 0
		go func() {
			param := Param{Msg: "leader heartbeat",
				Arg: Leader{rf.currentTerm, rf.me}}
			rf.broadcast(param, "Raft.Heartbeat", func(ok bool) {
				rf.heartbeatRe <- ok
			})
		}()

		for i := 0; i < raftCount-1; i++ {
			select {
			case ok := <-rf.heartbeatRe:
				if ok {
					success_count++
					if success_count >= raftCount/2 {
						rf.mu.Lock()
						rf.lastMessageTime = milliseconds()
						fmt.Println("接收到了子节点们的返回信息")
						rf.mu.Unlock()
					}
				}
			}
		}
	}
}

func randomRange(min, max int64) int64 {
	//设置随机时间
	rand.Seed(time.Now().UnixNano())
	return rand.Int63n(max-min) + min
}

//获得当前时间(毫秒)
func milliseconds() int64 {
	return time.Now().UnixNano() / int64(time.Millisecond)
}

func (rf *Raft) election() {
	var result bool
	//每隔一段时间发一次心跳
	for {
		//延时时间
		timeout := randomRange(1500, 3000)
		//设置该节点最有一次处理消息的时间
		rf.lastMessageTime = milliseconds()

		select {
		//间隔时间为1500-3000ms的随机值
		case <-time.After(time.Duration(timeout) * time.Millisecond):
		}

		result = false
		for !result {
			//选择leader
			result = rf.election_one_round(&leader)
		}
	}
}

func (rf *Raft) election_one_round(args *Leader) bool {
	//已经有了leader,并且不是自己,那么return
	if args.LeaderId > -1 && args.LeaderId != rf.me {
		fmt.Printf("%d已是leader,终止%d选举\n", args.LeaderId, rf.me)
		return true
	}

	var timeout int64
	var vote int
	var triggerHeartbeat bool
	timeout = 2000
	last := milliseconds()
	success := false
	rf.mu.Lock()
	rf.becomeCandidate()
	rf.mu.Unlock()
	fmt.Printf("candidate=%d start electing leader\n", rf.me)
	for {
		fmt.Printf("candidate=%d send request vote to server\n", rf.me)
		go func() {
			rf.broadcast(Param{Msg: "send request vote"}, "Raft.ElectingLeader", func(ok bool) {
				//无论成功失败都需要发送到通道 避免堵塞
				rf.eclectCh <- ok
			})
		}()

		vote = 0
		triggerHeartbeat = false
		for i := 0; i < raftCount-1; i++ {
			fmt.Printf("candidate=%d waiting for select for i=%d\n", rf.me, i)
			select {
			case ok := <-rf.eclectCh:
				if ok {
					vote++
					success = vote >= raftCount/2 || rf.currentLeader > -1
					if success && !triggerHeartbeat {
						fmt.Println("okok", args)
						triggerHeartbeat = true
						rf.mu.Lock()
						rf.becomeLeader()
						args.Term = rf.currentTerm + 1
						args.LeaderId = rf.me
						rf.mu.Unlock()
						fmt.Printf("candidate=%d becomes leader\n", rf.currentLeader)
						rf.heartbeat <- true
					}
				}
			}
			fmt.Printf("candidate=%d complete for select for i=%d\n", rf.me, i)
		}
		if (timeout+last < milliseconds()) || (vote >= raftCount/2 || rf.currentLeader > -1) {
			break
		} else {
			select {
			case <-time.After(time.Duration(5000) * time.Millisecond):
			}
		}
	}
	fmt.Printf("candidate=%d receive votes status=%t\n", rf.me, success)
	return success
}

func (rf *Raft) becomeLeader() {
	rf.state = 2
	fmt.Println(rf.me, "成为了leader")
	rf.currentLeader = rf.me
}

//设置发送参数的数据类型
type Param struct {
	Msg   string
	MsgId string
	Arg   Leader
}

func (rf *Raft) broadcast(msg Param, path string, fun func(ok bool)) {

	//设置不要自己给自己广播
	for nodeID, port := range nodeTable {
		if nodeID == rf.node.id {
			continue;
		}

		//链接远程rpc
		rp, err := rpc.DialHTTP("tcp", "127.0.0.1"+port)
		if err != nil {
			fun(false)
			continue
		}

		var bo = false
		err = rp.Call(path, msg, &bo)
		if err != nil {
			fun(false)
			continue
		}
		fun(bo)
	}

}

func (rf *Raft) becomeCandidate() {
	if rf.state == 0 || rf.currentLeader == -1 {
		//候选人状态
		rf.state = 1
		rf.votedFor = rf.me
		rf.setTerm(rf.currentTerm + 1)
		rf.currentLeader = -1

	}
}

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

//Rpc处理
func (rf *Raft) ElectingLeader(param Param, a *bool) error {
	//给leader投票
	*a = true
	rf.lastMessageTime = milliseconds()
	return nil
}

func (rf *Raft) Heartbeat(param Param, a *bool) error {
	fmt.Println("\nrpc:heartbeat:", rf.me, param.Msg)
	if param.Arg.Term < rf.currentTerm {
		*a = false
	} else {
		leader = param.Arg
		fmt.Printf("%d收到leader%d心跳\n", rf.currentLeader, leader.LeaderId)
		*a = true
		rf.mu.Lock()
		rf.currentLeader = leader.LeaderId
		rf.votedFor = leader.LeaderId
		rf.state = 0
		rf.lastMessageTime = milliseconds()
		fmt.Printf("server = %d learned that leader = %d\n", rf.me, rf.currentLeader)
		rf.mu.Unlock()
	}
	return nil
}

//连接到leader节点
func (rf *Raft) ForwardingMessage(param Param, a *bool) error {
	fmt.Println("\nrpc:forwardingMessage:", rf.me, param.Msg)

	rf.sendMessageToOtherNodes(param)

	*a = true
	rf.lastMessageTime = milliseconds()

	return nil
}

//接收leader传过来的日志
func (r *Raft) LogDataCopy(param Param, a *bool) error {
	fmt.Println("\nrpc:LogDataCopy:", r.me, param.Msg)
	bufferMessage[param.MsgId] = param.Msg
	*a = true
	return nil
}

Reference
老男孩 Go 5期

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

metabit

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值