【go共识算法】-Raft

介绍

Raft 状态

一个 Raft 集群包含若干个服务器节点;一般是 5 个,这容许整个系统容忍 2 个节点的失效,每一个节点处于如下三种状态之一:git

  • follower :全部结点都以 follower 的状态开始。若是没收到leader消息则会变成 candidate状态。
  • candidate:会向其余结点“拉选票”,若是获得大部分的票则成为leader。这个过程就叫作Leader选举(Leader Election)。
  • leader:全部对系统的修改都会先通过leader。

Raft 一致性算法

Raft经过选出一个leader来简化日志副本的管理,例如,日志项(log entry)只容许从leader流向follower。github

基于leader的方法,Raft算法能够分解成三个子问题:算法

  • Leader election (领导选举):原来的leader挂掉后,必须选出一个新的leader
  • Log replication (日志复制):leader从客户端接收日志,并复制到整个集群中
  • Safety (安全性):若是有任意的server将日志项回放到状态机中了,那么其余的server只会回放相同的日志项
Leader election (领导选举)

Raft 使用一种心跳机制来触发领导人选举。当服务器程序启动时,他们都是 follower(跟随者) 身份。若是一个跟随者在一段时间里没有接收到任何消息,也就是选举超时,而后他就会认为系统中没有可用的领导者而后开始进行选举以选出新的领导者。要开始一次选举过程,follower 会给当前term加1而且转换成candidate状态。json

而后他会并行的向集群中的其余服务器节点发送请求投票的 RPCs 来给本身投票。候选人的状态维持直到发生如下任何一个条件发生的时候.缓存

  • 他本身赢得了此次的选举安全

    • 若是这个节点赢得了半数以上的vote就会成为leader,每一个节点会按照first-come-first-served的原则进行投票,而且一个term中只能投给一个节点, 这样就保证了一个term最多有一个节点赢得半数以上的vote
    • 当一个节点赢得选举, 他会成为leader, 而且给全部节点发送这个信息, 这样全部节点都会回退成follower。
  • 其余的服务器成为领导者,若是在等待选举期间,candidate接收到其余server要成为leader的RPC,分两种状况处理:服务器

    • 若是leader的term大于或等于自身的term,那么改candidate 会转成follower 状态
    • 若是leader的term小于自身的term,那么会拒绝该 leader,并继续保持candidate 状态
  • 一段时间以后没有任何一个获胜的人数据结构

    • 有可能,不少follower同时变成candidate,致使没有candidate能得到大多数的选举,从而致使没法选出leader。当这个状况发生时,每一个candidate会超时,而后从新发增长term,发起新一轮选举RPC。须要注意的是,若是没有特别处理,可能出致使无限地重复选主的状况。
    • Raft采用随机定时器的方法来避免上述状况,每一个candidate选择一个时间间隔内的随机值,例如150-300ms,采用这种机制,通常只有一个server会进入candidate状态,而后得到大多数server的选举,最后成为主。每一个candidate在收到leader的心跳信息后会重启定时器,从而避免在leader正常工做时,会发生选举的状况。
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有相同的indexterm,那么它们存储相同的指令
  • 若是两个log entry在两份不一样的日志中,而且有相同的indexterm,那么它们以前的log entry是彻底相同的

其中特性一经过如下保证:

  • leader在一个特定的termindex下,只会建立一个log entry
  • log entry不会改变它们在日志中的位置

特性二经过如下保证:

  • AppendEntries会作log entry的一致性检查,当发送一个AppendEntriesRPC时,leader会带上须要复制的log entry前一个log entry的(index, iterm),若是follower没有发现与它同样的log entry,那么它会拒绝接受新的log entry 这样就能保证特性二得以知足。

动画演示Raft流程

http://thesecretlivesofdata.c...

go实现Raft

项目地址

https://github.com/goraft/raft

数据结构

https://blog.csdn.net/xiongwe...

goraft主要抽象了server、peer和log三个结构,分别表明服务节点、Follower节点和日志。

server

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

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

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
}
相关文章
相关标签/搜索