etcd

etcd

December 20, 2021
Etcd
Raft

etcd #

raft #

介绍 #

由多个节点组成的集群维护着一个可复制状态机的协议。通过复制日志来保持状态机的同步。 可理解的共识算法

状态机以消息为输入。消息可以是一个本地定时器更新,或一条网络消息。输出一个3元结构:[]Messages, []LogEntries, NextState,分别是消息列表、日志条目列表、下个状态。同样状态的状态机,在相同输入时总是输出相同结果。

插曲 #

人、联系、共识

人生下来,触摸着这个世界的人和物,做着或有趣或无聊的事,建立起或浅或深的联系。

当两个人面对面时,就某个想法达成一致或不一致,非常容易。

如果两个人不是面对面呢?

如果不只两个人,同坐在祠堂里呢?

如果不止两个人,还分散在不同地点呢?

那么,为什么要达成共识呢?

因为有些事必须达成共识才能执行,比如,两个人双向奔赴。

如果彼此异心,一个向东,一个往南,事情就办不成了。

所以,共识是大伙成事的前提。

共识,除了就某件事所要达成的结果,也要考虑所使用的方法。

有可能是步步为营,走一步算一步,也就是每走一步再就下一步达成共识。

也有可能是,一次性就接下来的几步均达成共识,然后各自执行。

message type #

// For description of different message types, see:
// https://pkg.go.dev/go.etcd.io/etcd/raft/v3#hdr-MessageType
type MessageType int32

const (
    // 选举时使用;
    // 如果节点是一个follower或candidate,它在选举超时前没有收到任何心跳,它就回传递MsgHup消息给它自己的Step方法,然后成为(或保持)一个candidate从而开启一个新的选举
	MsgHup            MessageType = 0
    // 一个内部类型,它向leader发送一个类型为“MsgHeartbeat”的心跳信号
    // 如果节点是一个leader,raft里的tick函数将会是“tickHeartbeat”,触发leader周期性地发送“MsgHeartbeat”消息给它的followers
	MsgBeat           MessageType = 1
    // 提议往它的日志条目里追加数据;
    // 这是一个特别的类型,由follower反推提议给leader(正常是leader提议,follower执行);
    // 发给leader的话,leader调用“appendEntry”方法追加条目到它的日志里,然后调用“bcastAppend”方法发送这些条目给它的远端节点;
    // 发给candidate的话,它们直接丢弃该消息
    // 发给follower的话,follower会将消息存储到它们的信箱里。会把发送者的id一起存储,然后转发给leader。
	MsgProp           MessageType = 2
    // 包含了要复制的日志条目
    // leader调用“bcastAppend”(里面调用“sendAppend”),发送“一会要被复制的日志”消息;
    // 当candidate收到消息后,在它的Step方法里,它马上回退为follower,因为这条消息表明已经存在一个有效leader了。
    // candidate和follower均会返回一条“MsgAppResp”类型消息以作响应。
	MsgApp            MessageType = 3
    // 调用“handlerAppendEntries”方法
	MsgAppResp        MessageType = 4
    // 请求集群中的节点给自己投票;
    // 当节点是follower或candidate,并且它们的Step方法收到了“MsgHup”消息,节点调用“campaign”方法去提议自己成为一个leader。一旦“campaign”方法被调用,节点成为candidate,并发送“MsgVote”给集群中的远端节点请求投票。
    // 当leader或candidate的Step方法收到该消息,并且消息的Term比它们的Term小,“MsgVote”将被拒绝。
    // 当leader或candidate收到的消息的Term要更大时,它会回退为follower。
    // 当follower收到该消息,仅当发送者的最后的term比“MsgVote”的term要大,或发送者的最后term等于“MsgVote”的term(但发送者的最后提交index大于等于follower的),
	MsgVote           MessageType = 5
    // 投票响应;
    // 当candidate收到后,它会统计选票,如果大于majority(quorum),它成为leader并调用“bcastAppend”。如果candidate收到大量的否决票,它将回退到follower
	MsgVoteResp       MessageType = 6
    // 请求安装一个快照消息;
    // 当一个节点刚成为leader,或者leader收到了“MsgProp”消息,它调用“bcastAppend”方法(里面再调用“sendAppend”)方法到每个follower。在“sendAppend”方法里,如果一个leader获取term或条目失败了,leader通过"MsgSnap"消息请求快照。
	MsgSnap           MessageType = 7
    // leader发送心跳;
    // 当candidate收到“MsgHeartbeat”,并且消息的term比candidate的大,candidate回退到follower并且更新它的提交index为这次心跳里的值。然后candidate发送消息到它的信箱。
    // 当消息发送到follower的Step方法,并且消息的term比follower的大,follower更新它的leader id
	MsgHeartbeat      MessageType = 8
    // 心跳响应;
    // leader收到后就知道有哪些follower响应了。
    // 只有当leader的最后提交index比follower的Match index大时,leader执行“sendAppend”方法
	MsgHeartbeatResp  MessageType = 9
    // 表明请求没有被交付;
    // 当“MsgUnreachable”被传送到leader的Step方法,leader发现follower无法到达,很有可能“MsgApp”都丢失了。当follower的进度状态为复制时,leader设置它回probe(哨兵)
	MsgUnreachable    MessageType = 10
    // 表明快照安装消息的结果
    // 当一个follower拒绝了“MsgSnap”,这显示快照请求失败了--因为网络原因;**leader认为follower成为哨兵了**?(Then leader considers follower's progress as probe.);
    // 当“MsgSnap”没有被拒绝,它表明快照成功了,leader设置follower的进度为哨兵,并恢复它的日志复制
	MsgSnapStatus     MessageType = 11
	MsgCheckQuorum    MessageType = 12
	MsgTransferLeader MessageType = 13
	MsgTimeoutNow     MessageType = 14
	MsgReadIndex      MessageType = 15
	MsgReadIndexResp  MessageType = 16
    // "MsgPreVote"和“MsgPreVoteResp”用在可选的两阶段选举协议上;
    // 当Config.PreVote为true,将会进行一次预选举,除非预选举表明竞争节点会赢,否则没有节点会增加它们的term值。
    // 这最小化了**一个发生了分区的节点重新加入到集群时**会带来的中断/干扰
	MsgPreVote        MessageType = 17
	MsgPreVoteResp    MessageType = 18
)

raft, Node and RawNode #

type Node interface {
    // ...
}

func StartNode(...) Node {
    rn, err := NewRawNode(...)
	if err != nil {
		panic(err)
	}

    n := newNode(rn)
    go n.run()
    return &n
}

func NewRawNode(config *Config) (*RawNode, error) {
    r := newRaft(config)
    rn := &RawNode{
        raft: r,
    }

    ...

    return rn, nil
}

type node struct { // impl Node interface
    ...

    rn *RawNode
}

func newNode(rn *RawNode) node {
    return node{
        ...
    }
}

实现 #

使用 #

存储 #

bbolt

传输 #

grpc