一个 Raft 集群包含若干个服务器节点;一般是 5 个,这容许整个系统容忍 2 个节点的失效,每一个节点处于如下三种状态之一:git
follower
:全部结点都以 follower 的状态开始。若是没收到leader
消息则会变成 candidate
状态。candidate
:会向其余结点“拉选票”,若是获得大部分的票则成为leader。这个过程就叫作Leader选举(Leader Election
)。leader
:全部对系统的修改都会先通过leader。Raft经过选出一个leader来简化日志副本的管理,例如,日志项(log entry
)只容许从leader流向follower。github
基于leader的方法,Raft算法能够分解成三个子问题:算法
Leader election
(领导选举):原来的leader挂掉后,必须选出一个新的leaderLog replication
(日志复制):leader从客户端接收日志,并复制到整个集群中Safety
(安全性):若是有任意的server将日志项回放到状态机中了,那么其余的server只会回放相同的日志项Leader election (领导选举)
Raft 使用一种心跳机制来触发领导人选举。当服务器程序启动时,他们都是 follower
(跟随者) 身份。若是一个跟随者在一段时间里没有接收到任何消息,也就是选举超时,而后他就会认为系统中没有可用的领导者而后开始进行选举以选出新的领导者。要开始一次选举过程,follower
会给当前term
加1而且转换成candidate
状态。json
而后他会并行的向集群中的其余服务器节点发送请求投票的 RPCs 来给本身投票。候选人的状态维持直到发生如下任何一个条件发生的时候.缓存
他本身赢得了此次的选举安全
first-come-first-served
的原则进行投票,而且一个term中只能投给一个节点, 这样就保证了一个term最多有一个节点赢得半数以上的vote
。其余的服务器成为领导者,若是在等待选举期间,candidate接收到其余server要成为leader的RPC,分两种状况处理:服务器
一段时间以后没有任何一个获胜的人数据结构
Log replication (日志复制)
当选出 leader 后,它会开始接受客户端请求,每一个请求会带有一个指令,能够被回放到状态机中。leader
把指令追加成一个log entry
,而后经过AppendEntries RPC
并行的发送给其余的server
,当该entry
被多数派server
复制后,leader 会把该entry回放到状态机中,而后把结果返回给客户端。动画
当 follower 宕机或者运行较慢时,leader 会无限地重发AppendEntries给这些follower,直到全部的follower都复制了该log entry。ui
Raft的log replication保证如下性质(Log Matching Property):
log entry
有相同的index
和term
,那么它们存储相同的指令log entry
在两份不一样的日志中,而且有相同的index
和term
,那么它们以前的log entry
是彻底相同的其中特性一经过如下保证:
leader在
一个特定的term
和index
下,只会建立一个log entry
log entry
不会改变它们在日志中的位置特性二经过如下保证:
leader会带上须要复制的log entry前一个log entry的(index, iterm)
,若是follower没有发现与它同样的log entry,那么它会拒绝接受新的log entry 这样就能保证特性二得以知足。http://thesecretlivesofdata.c...
https://github.com/goraft/raft
https://blog.csdn.net/xiongwe...
goraft主要抽象了server、peer和log三个结构,分别表明服务节点、Follower节点和日志。
Raft做为一种多节点状态一致性维护协议,运行过程当中必然涉及到多个物理节点,server就是用来抽象其中的每一个节点,维护节点的状态信息。其结构以下:
type server struct { *eventDispatcher name string path string state string // 每一个节点老是处于如下状态的一种:follower、candidate、leader transporter Transporter context interface{} currentTerm uint64 // Raft协议关键概念,每一个term内都会产生一个新的leader votedFor string log *Log leader string peers map[string]*Peer // raft中每一个节点须要了解其余节点信息,尤为是leader节点 mutex sync.RWMutex syncedPeer map[string]bool // 对于leader来讲,该成员记录了日志已经被sync到了哪些follower stopped chan bool c chan *ev // 当前节点的命令通道,全部的命令都经过该channel来传递 electionTimeout time.Duration heartbeatInterval time.Duration snapshot *Snapshot // PendingSnapshot is an unfinished snapshot. // After the pendingSnapshot is saved to disk, // it will be set to snapshot and also will be // set to nil. pendingSnapshot *Snapshot stateMachine StateMachine maxLogEntriesPerRequest uint64 connectionString string routineGroup sync.WaitGroup }
log是Raft协议的核心,Raft使用日志来存储客户发起的命令,并经过日志内容的同步来维护多节点上状态的一致性。
// A log is a collection of log entries that are persisted to durable storage. type Log struct { ApplyFunc func(*LogEntry, Command) (interface{}, error) // 日志被应用至状态机的方法,这个应该由使用raft的客户决定 file *os.File // 日志文件句柄 path string // 日志文件路径 entries []*LogEntry // 内存日志项缓存 commitIndex uint64 // 日志提交点,小于该提交点的日志均已经被应用至状态机 mutex sync.RWMutex startIndex uint64 // 日志中起始日志项的index startTerm uint64 // 日志中起始日志项的term initialized bool }
log entry是客户发起的command存储在日志文件中的内容
// 编码后的日志项包含Index、Term,原始Command的名称以及Command具体内容 type LogEntry struct { Index *uint64 `protobuf:"varint,1,req" json:"Index,omitempty"` Term *uint64 `protobuf:"varint,2,req" json:"Term,omitempty"` CommandName *string `protobuf:"bytes,3,req" json:"CommandName,omitempty"` Command []byte `protobuf:"bytes,4,opt" json:"Command,omitempty"` XXX_unrecognized []byte `json:"-"` } // A log entry stores a single item in the log. // LogEntry是日志项在内存中的描述结构,其最终存储在日志文件是通过protocol buffer编码之后的信息 type LogEntry struct { pb *protobuf.LogEntry Position int64 // position in the log file Position表明日志项存储在日志文件内的偏移 log *Log event *ev }