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

好的实现,看看别人怎么写的,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将所有请求串行化,然后另一个特点是根据不同的状态,设置不同的处理函数,整个实现非常的清晰,因为每个状态针对每个请求的处理都是非常明确的。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏BY的专栏

快速完成JSON\字典转模型 For YYModelJSON转模型 For YYModel

4428
来自专栏乐沙弥的世界

Oracle RAC OCR 的管理与维护

   OCR相当于Windows的注册表。对于Windows而言,所有的软件信息,用户,配置,安全等等统统都放到注册表里边。而集群呢,同样如此,所有和集群相关的...

984
来自专栏tkokof 的技术,小趣及杂念

Sweet Snippet系列 之 TCP数据接收

  虽说仍然是Sweet Snippet,不过本篇并没有代码,纯粹是自己觉得有点趣味,就索性一记了~

651
来自专栏人工智能头条

TensorFlow架构与设计:会话生命周期

1574
来自专栏一个会写诗的程序员的博客

安装适用于 Java 的 TensorFlow安装适用于 Java 的 TensorFlow

TensorFlow 可提供在 Java 程序中使用的 API。这些 API 特别适合用于加载以 Python 语言创建的模型并在 Java 应用中运行这些模型...

571
来自专栏DeveWork

Gravatar开发者手册

Gravatar上所有URL都是基于电子邮箱地址的哈希值。图像和个人档都是通过电子邮件的哈希值访问获取的,这是系统识别用户身份的主要方式。为确保哈希值的一致性和...

23510
来自专栏cloudskyme

设计模式(5)-己所不欲,施之于人(代理模式)

什么是代理?在我们的日常生活中的例子非常多。 比如上网有的时候使用代理服务器,通过代理上网,这就是代理的一个非常常见的例子。 从这里边可以看到3个对象:真实网路...

3374
来自专栏Seebug漏洞平台

CVE-2015-1641 Word 利用样本分析

00 引 子 本文我们将通过一个恶意文档的分析来理解漏洞 CVE-2015-1641(MS15-033)的具体利用过程,以此还原它在现实攻击中的应用。就目前来...

3318
来自专栏hotqin888的专栏

HydroCMS-用ueditor无法实现word中图片转存的问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hotqin888/article/det...

1911
来自专栏DeveWork

Gravatar开发者手册

Gravatar上所有URL都是基于电子邮箱地址的哈希值。图像和个人档都是通过电子邮件的哈希值访问获取的,这是系统识别用户身份的主要方式。为确保哈希值的一致性和...

2835

扫码关注云+社区