前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >raft 系列解读(4) 之 etcd-raft学习

raft 系列解读(4) 之 etcd-raft学习

作者头像
zhuanxu
发布2018-08-23 12:49:24
5430
发布2018-08-23 12:49:24
举报

好的实现,看看别人怎么写的,github

大多数Raft的实现都是整体设计,包括存储处理,消息序列化和网络传输,但是本raft库在实现的时候只实现了最核心的算法,换来了灵活性和性能,网络和disk IO部分都由使用者实现,使用者需要实现自己的消息传送层,同时,需要自己实现持久化部分来存储Raft log和state。 为了实现Raft库的可测性,库在实现的时候将Raft建模为一个状态机,输入是消息,可能是本地时间的更新或者网络消息,状态机的输出是一个3元组:{[]Messages, []LogEntries, NextState}。

第一步是使用,怎么使用raft来搭建自己的key-value系统

etcd-raft代码走读

node-run

上面是raft中一个node做的事,Node代表raft集群中的一个节点,刚开始node是follower,然后随着tickc的进行,开始进入选举,raft在变为follower的时候做了下面几件事:

becomeFollower

初始化了tick函数tickElection,用来开始选举,做判断后,调用Step

选举函数

判断消息类型为MsgHup,于是进入campaign

进行选举

选举函数中做的事情

选举

转换成candidate时,开始一个选举:

  1. 递增currentTerm;投票给自己;
  2. 重置election timer;
  3. 向所有的服务器发送 RequestVote RPC请求

成为candidate

接着看下send函数

raft.send

send函数中将消息存储了msgs中,在哪儿消费呢?通过读取newReady来返回Ready

图片

此时又返回到node.run中,此时因为会进入case readyc <- rd分支

图片

在里面做的事情

图片

msgs因为已经读取过了,设置为空,并且会赋值advancec,进行到这readyc中已经有一个数据, 而此channel会在函数Ready中返回给外面,下面接着看谁会去读Ready

func (n *node) Ready() <-chan Ready { return n.readyc }

读取Ready的是应用程序,看下Ready()函数的说明

//=> 读取到当前状态,当从Ready()取出状态后,需要调用 Advance
//=> 注意:只有当所有提交的entries都应用后,才会调用下一个 Ready 的状态

我们回到之前的选举上,读取到的Ready里面包含了Vote消息,我们会调用网络层发送消息出去,并且调用Advance(),而此时其他Node接收到网络层消息后,会调用Step()函数,在成为candidate的时候,我们设置了step函数为stepCandidate

stepCandidate

自后调用了node的send函数,此时是拒绝的,因为已经是candidate状态了,而如果是follower,其处理函数是stepFollower,

stepFollower

其规则就是之前说到的:

如果本地的voteFor为空或者为candidateId,并且候选者的日志至少与接受者的日志一样新,则投给其选票

进行到这,我们看到了follower在收到vote rpc后的处理,下面是candidate的处理了。

回到之前调用Ready(),接着应该调用Advance,

Advance

里面会设置advancec,好了,运行到这,我们又要回到node.run中了

此时的状态是:candidate,advancec中有数据,接着来看candidate在发送出vote rpc,接收到响应的处理,网络层的Send函数是:

Transport.Send

Send会调用Peer.send,函数注释说:此函数是非阻塞的,不保证请求一定能被peer收到

Peer.send

一般常理我们发送后,等待响应后再处理,但是找了很久也没找到常理函数,这个时候,我们再去看下follower对于投票的处理

send.MsgVoteResp

发现在响应上也是通过发送一个消息来响应的,因此我们此时可以看到peer之间的交互不是传统意义上的request-response模型了。

我们去看对于MsgVoteResp的处理,其入口都是通过调用node.Step函数,此时如果得到大多数票选,则成为leader

MsgVoteResp处理

看becomeLeader函数

becomeLeader

在leader函数中,最重要的就是发送命令了,我们看看这个过程

这是通过node.Propose函数实现的

node.Propose

到最后又是通过step函数

stepLeader

里面挨个调用send函数

func (r *raft) bcastAppend() {
    for id := range r.prs {
        if id == r.id {
            continue
        }
        r.sendAppend(id)
    }
}

sendAppend

看完发送端,接着看follower的接收端处理

handleAppendEntries

细看handleAppendEntries函数,就是去做raft协议中规定的操作了

图片

maybeAppend中,会去尝试更新committed index,然后接着看AppendResp的处理

AppendResp

去检查各个peer的matchIndex,然后尝试更新commitIndex

下一个问题,接着去看commitIndex > lastAppied后,在哪儿去应用log到状态机的 这是通过node.runreadycadvancec来实现的

node.run

上面就是etcd中raft的大致流程,有一个机遇raft实现的简单key-value系统,github地址:https://github.com/zhuanxuhit/distributed-system/tree/master/etcd-raft

读完代码后,最大的一个感受是整个node在实现的时候都是无锁的,其技巧是通过go的channel将所有请求串行化,然后另一个特点是根据不同的状态,设置不同的处理函数,整个实现非常的清晰,因为每个状态针对每个请求的处理都是非常明确的。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016.10.25 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档