Fork me on GitHub

版权声明 本站原创文章 由 萌叔 发表
转载请注明 萌叔 | https://vearne.cc

1. 参考资料

The original project authors have created new raft implementations now used in etcd and InfluxDB.

goraft/raftd的作者参与了etcd项目的实现,所以goraft/raftd是有参考价值的 。另外goraft/raftd的实现不完整,没有实现Log擦除等功能,因此不能用于生产环境。

2. Node的简单介绍

2.1 node的三种状态(state)

图1 有限状态自动机

  • Leader
  • Candidate
  • Follower

2.2 各种存储


2.2.1 Write-Ahead Log(WAL)

存储:文件

      4f
    raft:join">{"name":"2832bfa","connectionString":"http://localhost:4001"}
       e
raft:nop      4f
    raft:join">{"name":"3320b68","connectionString":"http://localhost:4002"}
      4f
    raft:join">{"name":"7bd5bdc","connectionString":"http://localhost:4003"}
       e
raft:nop      29
write"{"key":"foo","value":"bar"}
      29
write"{"key":"aaa","value":"bbb"}
      29
write"{"key":"bbb","value":"ccc"}
      29
    write"{"key":"ddd","value":"eee"}
       e

raft:nop       e

       raft:nop      29

    write"{"key":"foo","value":"bar"}
      29
    write"{"key":"foo","value":"bar"}
2.2.2 状态信息(状态信息)

commitIndex、peer等

存储:内存&文件

type server struct {
    *eventDispatcher

    name        string
    path        string
    // Leader、Follower 或者 Candidate
    state       string
    transporter Transporter
    context     interface{}
    // 代表它所感知的全局的Term情况
    currentTerm uint64

    votedFor   string
    log        *Log
    // 代表它所感知的全局的leader情况
    leader     string
    peers      map[string]*Peer
}
type Log struct {
    ApplyFunc   func(*LogEntry, Command) (interface{}, error)
    file        *os.File
    path        string
    entries     []*LogEntry
    commitIndex uint64
    mutex       sync.RWMutex
    startIndex  uint64 // the index before the first entry in the Log entries
    startTerm   uint64
    initialized bool
}

goraft的实现是写完Log.entries, 接着就写WAL

2.2.3 内存数据库

存储:内存

// The key-value database.
type DB struct {
    data  map[string]string
    mutex sync.RWMutex
}

2.3 节点之间的通讯&重要的名词解释

Leader --> Follower

{
    "Term": 17,
    "PrevLogIndex": 26, 
    "PrevLogTerm": 17,
    "CommitIndex": 26,
    "LeaderName": "2832bfa",
    "Entries": [{
        "Index": 27,
        "Term": 17,
        "CommandName": "write",
        "Command": "eyJrZXkiOiJhYWEiLCJ2YWx1ZSI6ImJiYiJ9Cg=="
    }]
}

名词解释

  • Term
  • CommitIndex
  • LogEntry

3. Leader Election(选举过程)

注意:不需要集群中节点的数量是奇数
可以是4、8个,都没关系

3.1 什么时候开始选举?

3.2 投票相关--如何处理VoteRequest

看参考资料1.4 的演示动画

3.2.1 一个Term期间,最多只能投出1票

// VoteRequest中的Term,必须大于本地的currentTerm
    if req.Term < s.Term() {
        s.debugln("server.rv.deny.vote: cause stale term")
        return newRequestVoteResponse(s.currentTerm, false), false
    }

    // If the term of the request peer is larger than this node, update the term
    // If the term is equal and we've already voted for a different candidate then
    // don't vote for this candidate.
// VoteRequest中的Term,如果大于本地的currentTerm,则更新本地的currentTerm
    if req.Term > s.Term() {
        s.updateCurrentTerm(req.Term, "")
    } else if s.votedFor != "" && s.votedFor != req.CandidateName {
        s.debugln("server.deny.vote: cause duplicate vote: ", req.CandidateName,
            " already vote for ", s.votedFor)
        return newRequestVoteResponse(s.currentTerm, false), false
    }

3.2.2 获得投票需要满足的条件

raft协议中这样的要求

candidate’s log is at least as up-to-date as receiver’s log then vote

解释起来,就是必须同时满足以下2个条件,才会给candidate投票

  • candidate.LastLogTerm >= receiver.LastLogTerm
  • candidate.LastLogIndex >= receiver.LastLogIndex

注意: Log中有擦除情况出现,所以条件1是必须的

3.3 当选&当选后的一系列动作

  • 获得majority投票的候选人当选为Leader
  • 通过心跳压制其它Candidate, 迫使其它Candidate转变为Follower
  • 写入NOPCommand

聊聊RAFT的一个实现(4)–NOPCOMMAND

3.4 一种极端场景

  • 注意1: Node BNode D每个节点收到2张选票,
    所以这一轮投票没有选出LeaderNode BNode D的状态仍为candidate。一段时间后(goraft/raftd的实现为electionTimeout至2倍electionTimeout之间的随机值),他们会发起下一轮投票。详情见图1
  • 注意2 每轮投票Term均需要加1

4. Log Replication(数据写入)

聊聊RAFT的一个实现(3)–commit


Step0. Client发出WriteCommand

Step1. 先写Leader的Log

Step2. 在通过AppendRequest,写Follower的Log

Step3. 执行Leader的Commit(写内存数据库)

Step4. 执行Follower的Commit

Step5. 给Client返回结果

  • 注意1: 写入动作,只能由Leader来发起, 在goraft/raftd的实现里,Follower会直接拒绝执行WriteCommand
  • 注意2: Step5 不需要等到Step4完全完成(收到Follower的response) 就可以开始执行。

只要client等到leader完成Commit动作。即使后续leader发生变更或部分节点崩溃,raft协议可以保证,client所提交的改动依然有效。

5. 数据读取&watch

5.1 默认consul有3种一致性模型

  • default
  • consistent
  • stale

默认情况下,consul server(follower)不提供数据查询,仅转发请求给consul server(leader)
consistency

其中consistent模式是强一致性的,其它两种模式都不能保证强一致性。用stale模式可以提高吞吐能力,当然数据短时间内可能会有不一致问题

5.2 defaultconsistent模式的区别

5.3 如何使用stale模型

curl -v 'http://dev1:8500/v1/health/service/es?dc=dc1&passing=1&stale

5.4 watch是怎么回事?

玩转CONSUL(1)–WATCH机制探究


请我喝瓶饮料

微信支付码

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注