Raft在etcd中的实现(一)存储及重要的组件

raftexample简介

本文中raft的实现以etcd中raftexample实现为例。
raftexample的例子中,启动了一个kv数据库作为raft的state machine,另外启动了一个http数据库来响应客户端的请求。其中proposeC以及commitC,errorC主要用来交互,proposeC为由客户端发送过来的条目,通过httpserver流向共识部分raftNode;commitC为经共识的条目,由raftNode流向kv数据库。

/*
github.com/coreos/etcd/contrib/raftexample/main.go
main()
*/
    var kvs *kvstore
    getSnapshot := func() ([]byte, error) { return kvs.getSnapshot() }
        //启动raft节点
    commitC, errorC, snapshotterReady := newRaftNode(*id, strings.Split(*cluster, ","), *join, getSnapshot, proposeC, confChangeC)
       //启动kv数据库作为state machine
    kvs = newKVStore(<-snapshotterReady, proposeC, commitC, errorC)

    // the key-value http handler will propose updates to raft
    serveHttpKVAPI(kvs, *kvport, confChangeC, errorC)

raftexample中的存储

raftexample中的存储分为以下几部分

  • kv数据库, 存储state machine的状态。raftexample例子中是个简单的map。
  • 内存数据中的log,raft共识用到的日志条目,一般是一段较新的日志,可能包含一部分已共识的日志和一些尚未共识的日志条目。由于是内存维护,可以灵活的重写替换。该部分内容中位于commitIndex之前的部分是已共识的部分,内容不会被替换,commitIndex到lastIndex之间的内容是尚未共识的部分,index对应的条目内容可能被替换。该部分在raft.MemoryStorage实现。
  • wal部分,raft共识所用的日志文件。该文件只会追加,不会重写和覆盖。raft共识过程中收到的日志条目,都会记录在wal日志文件中。即可能在wal日志文件中看见同index不同term的日志条目。主要作用是在节点崩溃后可以通过wal部分重新启动。
  • snapshot文件部分,类似于存档点的概念,已经过共识的部分,可以在一定时间或者一点量消息后,生成snapshot,snapshot文件一般保存当前state machine的状态,及集群的相关配置等数据。snapshot可以帮助节点快速启动,以及新节点加入时的快速同步。

几个重要的组件

etcd中raft的实现了很好的模块化,其中raft共识模块主要是实现了算法的逻辑,而系统需要用到的存储、通讯等模块都从共识模块中很好的剥离了出来是单独模块的实现。这样做可以很方便对raft的共识模块进行移植,也可以很方便地支持多种底层的存储或者通讯方式。raftexample中的组件大概可以分为以下几部分。

  • 应用层部分:对应其raftNode组件,应用层可以根据自己的需要实现自己的raftNode。
  • 共识部分(github.com/coreos/etcd/raft):对应其raft组件。该部分实现了raft核心算法部分。其逻辑主要在raft结构体实现的方法中,其通过raftLog结构体以及progress结构体,实现raft算法中log部分的管理和节点对集群中其他节点信息的管理。并且通过node结构体提供给了应用层与共识部分沟通的渠道。
  • 通讯部分(github.com/coreos/etcd/rafthttp):对应其Transport组件。使用http协议完成节点间的相互通信。
  • 存储部分(github.com/coreos/etcd/raftsnap、github.com/coreos/etcd/wal):对应其snapshotter组件与wal组件。通过这两个组件分别对快照和日志条目进行持久化的存储。

raftNode组件:主要处理应用层服务

raftNode结构体,实际上是应用层的包装,真正的raft共识部分在其中的node(raft.Node)中。RaftNode结构体中可以放应用层需要的一些东西。大致有以下一些东西

  • 协程交互用的通道,包括proposeC,confChangeC,commitC,errorC。
  • raft节点的基本配置,包括id,peers,join,waldir,snapdir。
  • raft节点状态,包括lastIndex,confState,snapshotIndex,appliedIndex等。
  • raft共识部分的组件,包括node其是共识的关键部分,raftStorage是日志条目在内存中存储的实现,wal是日志条目存储在文件中的实现,snapshotter及其相关配置是snapshot处理的相关实现,transport是raft节点间通讯的组件。
/*
github.com/coreos/etcd/contrib/raftexample/raft.go
*/
// A key-value stream backed by raft
type raftNode struct {
    proposeC    <-chan string            // proposed messages (k,v)
    confChangeC <-chan raftpb.ConfChange // proposed cluster config changes
    commitC     chan<- *string           // entries committed to log (k,v)
    errorC      chan<- error             // errors from raft session

    id          int      // client ID for raft session
    peers       []string // raft peer URLs
    join        bool     // node is joining an existing cluster
    waldir      string   // path to WAL directory
    snapdir     string   // path to snapshot directory
    getSnapshot func() ([]byte, error)
    lastIndex   uint64 // index of log at start

    confState     raftpb.ConfState
    snapshotIndex uint64                   //snapshotIndex
    appliedIndex  uint64                    //同论文中的appliedIndex,用于记录最新的已提交state machine执行的日志的索引

    // raft backing for the commit/error channel
    node        raft.Node                    //真正的共识部分的node
    raftStorage *raft.MemoryStorage          //raft中内存存储日志的部分
    wal         *wal.WAL                     //wal文件部分

    snapshotter      *raftsnap.Snapshotter
    snapshotterReady chan *raftsnap.Snapshotter // signals when snapshotter is ready

    snapCount uint64
    transport *rafthttp.Transport
    stopc     chan struct{} // signals proposal channel closed
    httpstopc chan struct{} // signals http server to shutdown
    httpdonec chan struct{} // signals http server shutdown complete
}

大致有以下几种函数

  • 启动raft函数:startRaft
  • snapshot相关的函数,主要处理snapshot的触发、将什么内容保存到snapshot、保存snapshot、加载snapshot等事项,包括:maybeTriggerSnapshot,publishSnapshot,saveSnap,loadSnapshot
  • 日志条目相关的函数,主要将条目提交给state machine去执行,包括:publishEntries,entriesToApply
  • 重新加载和重放WAL,包括:openWAL,replayWAL
  • 响应Raft和客户端请求的函数:serverRaft,serveChannels

node组件,应用层与共识模块的沟通者

node结构体的主要作用是应用层和共识模块的衔接。将应用层的消息传递给底层共识模块,并将底层共识模块共识后的结果反馈给应用层。结构体的主要成员都是用来作消息传递用处的通道。

type node struct {
    propc      chan pb.Message
    recvc      chan pb.Message
    confc      chan pb.ConfChange
    confstatec chan pb.ConfState
    readyc     chan Ready
    advancec   chan struct{}
    tickc      chan struct{}
    done       chan struct{}
    stop       chan struct{}
    status     chan chan Status

    logger Logger
}

大致函数有几种:

  1. 启动、新建、停止:StartNode,RestartNode,newNode,Stop。
  2. 发送命令给共识模块的函数:Tick,Campaign,Propose,Step,ProposeConfChange,ApplyConfChange,TransferLeadership。
  3. 反馈给应用层的函数Ready,Advance,,Status,ReportUnreachable,ReadIndex。

raft组件:共识组件

raft结构体,共识组件结构体。
首先放一下论文中需要维护的state来对比

currentTerm                   //当前任期   
votedFor                      //当前任期的候选者编号,无则为null
log[]                         //日志条目

//Volatile state on all servers,所有服务器上维护
commitIndex             //已知的最高的可被提交的日志条目的索引,初始为0
lastApplied             //当前已提交给state machine执行的条目的索引,初始为0

//Volatile state on leaders:(Reinitialized after election),只在leader节点上维护
nextIndex[]          //对于每一台服务器,下一条将要发给该服务器的条目的索引,初始为leader最后一条条目索引+1
matchIndex[]         //每一个服务器已知的最高的已复制的条目的索引,初始为0

etcd中raft实现中raft结构体内容,可以看出其主要包含一下几部分

  • state的基本项,包括:id,Term(论文中currentTerm),vote(论文中voteFor),raftLog(论文中log[],并且维护了comitIndex和lastApplied),prs及learnerPrs(论文中的matchIndex与nextIndex以及一些其他关于对应节点状态的信息)。
  • 一些其他状态项,包括state用于判别当前节点状态、isLearner判别当前节点是否是learner、lead表示当前leaderid、以及leadTransferee、pendingConfIndex、readStates、checkQuorum、preVote等。
  • 一些其他配置项,包括maxInflight,maxMsgSize,heartbeatTimeout,electionTimeout,randomizedElectionTimeout,disableProposalForwarding等。
  • 重要的函数,tick用来计时,step用来处理各种RPC。
type raft struct {
    //同论文中的state的一些基本项
    id         uint64               //节点id
    Term       uint64               //节点当前所处任期,即currentTerm
    Vote       uint64               // 即votedFor
    raftLog    *raftLog             //节点的日志,即log[]
    prs        map[uint64]*Progress //progress中存储了对应节点的matchIndex和nextIndex
    learnerPrs map[uint64]*Progress

    //一些其他状态
    readStates []ReadState
    state      StateType
    // isLearner is true if the local raft node is a learner.
    isLearner bool
    votes     map[uint64]bool
    msgs      []pb.Message
    readOnly  *readOnly
    lead      uint64 // the leader id
    // leadTransferee is id of the leader transfer target when its value is not zero.
    // Follow the procedure defined in raft thesis 3.10.
    leadTransferee   uint64
    pendingConfIndex uint64
    checkQuorum      bool
    preVote          bool

    //一些其他配置项
    maxInflight               int
    maxMsgSize                uint64
    electionElapsed           int
    heartbeatElapsed          int
    heartbeatTimeout          int
    electionTimeout           int
    randomizedElectionTimeout int
    disableProposalForwarding bool

    tick   func()
    step   stepFunc //按照leader,follower,candidate角色不同,有三个不同的函数
    logger Logger
}

raft结构体及其方法是算法中的主体部分。大致有以下几种函数:

  • 新建并启动:newRaft
  • 查询或者获取状态类:包括hasLeader,softState,hardState,quorum,nodes,learnerNodes,getProgress,maybeCommit,pastElectionTimeout,checkQuorumActive
  • 控制类函数:主要作用是根据消息类型调用响应的函数,包括:Step,stepLeader,stepCandidate,stepFollower
  • 发送消息类:包括send,sendAppend,sendHeartbeat,sendTimeoutNow,bcastAppend,bcastHearbeat,bcastHearbeatWithCtx。其中几个bcast打头函数表示向集群中的每个节点发送。
  • 处理消息类:包括handleAppendEntries,handleHeartbeat,handleSnapshot,
  • 处理集群成员变更类:addNode,addLearner,addNodeOrLearnerNode,removeNode,restoreNode,setProgress,delProgress
  • 实际操作类:包括appendEntry,
  • tick函数类:tickElection,tickHeartbeat
  • 角色转化类:becomeFollower,becomeCandidate,becomePreCandidate,becomeLeader
  • 辅助函数类:reset,restore,resetRandomizedElectionTimeout,loadState,campaign,poll

Progress:维护集群中的节点状态
Progress结构体,该结构体主要是在每个节点维护集群中其他节点的状态用。主要包括

  • 论文中用以共识用的Match(论文中的matchIndex),next(论文中的nextIndex)。
  • 包括一些节点的是否正常运行的状态RecentActive、Paused。
  • 节点的身份状态State及isLearner等。
type Progress struct {
    Match, Next     uint64
    State           ProgressStateType
    Paused          bool
    PendingSnapshot uint64
    RecentActive    bool
    ins             *inflights
    IsLearner       bool
}

大致有以下几种函数:

  • 控制类:pause、resume
  • 状态转变类函数: resetState, becomeProbe, becomeReplicate, becomeSnapshot
  • Match和Next维护类函数:
    maybeUpdate, optimisticUpdate, maybeDecrTo, -
  • 状态查询类函数:IsPaused
  • snapshot相关: snapshotFailure,needSnapshotAbort

raftLog:raft中的日志管理者
raftLog结构体,raft中的日志条目管理的结构体,前文中介绍raft中的日志存储包括文件存储和内存存储两部分。该结构体中storage即为内存存储的日志条目部分。unstable是在日志尚未存储到日志文件时的状态。commited和applied是论文中commitIndex和lastApplied的概念,及最新的已共识可提交的日志条目索引,和已提交给state machine执行的最新条目的索引。

type raftLog struct {
    // storage contains all stable entries since the last snapshot.
    storage Storage

    // unstable contains all unstable entries and snapshot.
    // they will be saved into storage.
    unstable unstable

    // committed is the highest log position that is known to be in
    // stable storage on a quorum of nodes.
    committed uint64
    // applied is the highest log position that the application has
    // been instructed to apply to its state machine.
    // Invariant: applied <= committed
    applied uint64

    logger Logger
}

大致有以下几种函数

  • 新建: newLog
  • 查询状态类:获取索引、快照或者条目信息等,包括:unstableEntries,
    nextEnts, hasNextEnts, snapshot, firstIndex, lastIndex, lastTerm , entries, allEntries。
  • 状态更新类:更新commited或者applied的值,包括:commitTo,
    appliedTo, stableTo, stableSnapTo 。
  • 条目更新类:append
  • 判断状态类:判断条目是否可以被追加到当前,是否是当前term,找出冲突等。包括:maybeAppend, findConflict, isUpToDate matchTerm, maybeCommit, mustCheckOutOfBounds
  • 实际操作类:
  • 辅助类:term, slice,zeroTermOnErrCompacted

Snapshotter组件:快照管理者

type Snapshotter struct {
    dir string
}

大致包含保存快照SaveSnap,读取快照Read,加载快照Load等。

WAL组件:日志文件管理者

WAL结构体,用来管理日志文件。

type WAL struct {
    dir string // the living directory of the underlay files
    // dirFile is a fd for the wal directory for syncing on Rename
    dirFile *os.File
    metadata []byte           // metadata recorded at the head of each WAL
    state    raftpb.HardState // hardstate recorded at the head of WAL
    start     walpb.Snapshot // snapshot to start reading
    decoder   *decoder       // decoder to decode records
    readClose func() error   // closer for decode reader

    mu      sync.Mutex
    enti    uint64   // index of the last entry saved to the wal
    encoder *encoder // encoder to encode records

    locks []*fileutil.LockedFile // the locked files the WAL holds (the name is increasing)
    fp    *filePipeline
}

大致有以下几种函数:

  • 文件读取类:Open,OpenAtIndex,OpenForRead,ReadAll,
  • 文件写入类:saveEntry,saveState,Save,SaveSnapshot,saveCrc
  • 其他文件操作类:Create,renameWal,cut,Close

Transport组件:消息传输者

Transport组件是负责消息通信的,目前的实现方式是http的实现。

type Transport struct {
    DialTimeout time.Duration // maximum duration before timing out dial of the request
    // DialRetryFrequency defines the frequency of streamReader dial retrial attempts;
    // a distinct rate limiter is created per every peer (default value: 10 events/sec)
    DialRetryFrequency rate.Limit

    TLSInfo transport.TLSInfo // TLS information used when creating connection

    ID          types.ID   // local member ID
    URLs        types.URLs // local peer URLs
    ClusterID   types.ID   // raft cluster ID for request validation
    Raft        Raft       // raft state machine, to which the Transport forwards received messages and reports status
    Snapshotter *raftsnap.Snapshotter
    ServerStats *stats.ServerStats // used to record general transportation statistics
    // used to record transportation statistics with followers when
    // performing as leader in raft protocol
    LeaderStats *stats.LeaderStats
    // ErrorC is used to report detected critical errors, e.g.,
    // the member has been permanently removed from the cluster
    // When an error is received from ErrorC, user should stop raft state
    // machine and thus stop the Transport.
    ErrorC chan error

    streamRt   http.RoundTripper // roundTripper used by streams
    pipelineRt http.RoundTripper // roundTripper used by pipelines

    mu      sync.RWMutex         // protect the remote and peer map
    remotes map[types.ID]*remote // remotes map that helps newly joined member to catch up
    peers   map[types.ID]Peer    // peers map

    prober probing.Prober
}

大致包含以下几种函数:

  • 启动和停止:Start,Stop
  • 消息发送和处理:Handler,Get,Send,SendSnapshot
  • 集群链接的维护:CutPeer,MendPeer,AddRemote,AddPeer,RemovePeer,RemoveAllPeers,UpdatePeer,ActiveSince,ActivePeers
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,527评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,314评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,535评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,006评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,961评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,220评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,664评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,351评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,481评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,397评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,443评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,123评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,713评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,801评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,010评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,494评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,075评论 2 341

推荐阅读更多精彩内容