成员变更在一致性协议里稍复杂一些,由于不同的成员不可能在同一时刻从旧成员组切换至新成员组,所以可能出现两个不相交的majority,从而导致同一个term出现两个leader,进而导致同一个index的日志不一致,违反一致性协议。下图是个例子:

raft作者提出了一种比较简单的方法,一次只增加或减少一个成员,这样能够保证任何时刻,都不可能出现两个不相交的majority,所以,可以从旧成员组直接切到新成员组。如下图:

切换的时机是把成员变更日志写盘的时候,不管是否commit。这个切换时机带来的问题是如果这条成员变更日志最终没有commit,在发生leader切换的时候,成员组就需要回滚到旧的成员组。

etcd raft为了实现简单,将切换成员组的实机选在apply成员变更日志的时候。

下面看看etcd raft library如何实现的:

应用调用

func (n *node) ProposeConfChange(ctx context.Context, cc pb.ConfChange) error {
data, err := cc.Marshal()
if err != nil {
return err
}
return n.Step(ctx, pb.Message{Type: pb.MsgProp, Entries: []pb.Entry{{Type: pb.EntryConfChange, Data: data}}})
}

可以看出,ConfChange是和普通的log entry一样封装在MsgProp消息中,进入propc,

跑raft算法的goroutine从propc中拿到消息后,会做如下判断:

for i, e := range m.Entries {
if e.Type == pb.EntryConfChange {
if r.pendingConf {
r.logger.Infof("propose conf %s ignored since pending unapplied configuration", e.String())
m.Entries[i] = pb.Entry{Type: pb.EntryNormal}
}
r.pendingConf = true
}
}

检查已经有成员变更正在做,就忽略新的成员变更。然后将pendingConf置为true,意味着目前有成员变更正在做了,从这里可以看出,多个成员变更不能同时进行。follower接收端的处理和普通log entry一样。

如果成员变更日志达成了一致,则会被封装在Ready中,应用拿到后,做如下处理:

if entry.Type == raftpb.EntryConfChange {
var cc raftpb.ConfChange
cc.Unmarshal(entry.Data)
s.Node.ApplyConfChange(cc)
}

ApplyConfChange:

func (n *node) ApplyConfChange(cc pb.ConfChange) *pb.ConfState {
var cs pb.ConfState
select {
case n.confc <- cc:
case <-n.done:
}
select {
case cs = <-n.confstatec:
case <-n.done:
}
return &cs
}

讲ConfChange放入confc,然后阻塞在confstatec上,跑raft协议的goroutine从confc中拿出ConfChange,做相应的增加/删除节点操作,然后将成员组放入confstatec。

switch cc.Type {
case pb.ConfChangeAddNode:
r.addNode(cc.NodeID)
case pb.ConfChangeRemoveNode:
// block incoming proposal when local node is
// removed
if cc.NodeID == r.id {
propc = nil
}
r.removeNode(cc.NodeID)
case pb.ConfChangeUpdateNode:
r.resetPendingConf()
default:
panic("unexpected conf type")
}
select {
case n.confstatec <- pb.ConfState{Nodes: r.nodes()}:
case <-n.done:
}

增加/删除节点操作都只是更新prs,map的每个元素保存一个peer的状态,其中最重要的状态莫过于

Match, Next uint64

看过raft小论文的人一看变量名就很明确意义,Match代表最大的已经落盘的log index,Next代表下一条需要发给这个peer的log index。然后将pendingConf置为false,代表成员变更结束。

重启如何恢复成员组:

hs, cs, err := c.Storage.InitialState()

Storage接口中:

// InitialState returns the saved HardState and ConfState information.
InitialState() (pb.HardState, pb.ConfState, error)

Storage是个接口,其中InitialState()用于恢复成员组,需要应用自己实现,通常将ConfState记在最后一次Snapshot的Metadata中:

message SnapshotMetadata {
optional ConfState conf_state = 1 [(gogoproto.nullable) = false];
optional uint64 index = 2 [(gogoproto.nullable) = false];
optional uint64 term = 3 [(gogoproto.nullable) = false];
}

ConfState:

message ConfState {
repeated uint64 nodes = 1;
}

拿到ConfState后就可以初始化上面提到的prs,snapshot后续的已经commit的log entry一样,通过Ready封装,应用进行apply,如果其中有ConfChange,则调用

s.Node.ApplyConfChange(cc)

etcd raft如何实现成员变更的更多相关文章

  1. 解读Raft(四 成员变更)

    将成员变更纳入到算法中是Raft易于应用到实践中的关键,相对于Paxos,它给出了明确的变更过程(实践的基础,任何现实的系统中都会遇到因为硬件故障等原因引起的节点变更的操作). 显然,我们可以通过sh ...

  2. 【翻译】Raft 共识算法:集群成员变更

    转载请注明出处:https://www.cnblogs.com/morningli/p/16770129.html 之前都在集群配置是固定的(参与共识算法的server集合)假设下讨论raft.在实践 ...

  3. etcd raft library设计原理和使用

    早在2013年11月份,在raft论文还只能在网上下载到草稿版时,我曾经写过一篇blog对其进行简要分析.4年过去了,各种raft协议的讲解铺天盖地,raft也确实得到了广泛的应用.其中最知名的应用莫 ...

  4. etcd raft library

    https://github.com/coreos/etcd/tree/master/raft import "github.com/coreos/etcd/raft" ----- ...

  5. 彻底搞懂etcd raft选举、数据同步

    etcd raft选举机制 etcd 是一个分布式的k/V存储系统.核心使用了RAFT分布式一致性协议.一致性这个概念,它是指多个服务器在状态达成一致,但是在一个分布式系统中,因为各种意外可能,有的服 ...

  6. etcd raft 处理流程图系列1-raftexample

    最近在看raft相关的代码和实现,发现etcd的raft模块在实现上还是比较灵活的,但缺点就是需要用户实现比较多的功能,如存储和网络等,同时带来的优点就是不会对用户的存储和传输作限制.网上对该模块的描 ...

  7. etcd raft如何实现leadership transfer

    leadership transfer可以把raft group中的leader身份转给其中一个follower.这个功能可以用来做负载均衡,比如可以把leader放在性能更好的机器或者离客户端更近的 ...

  8. etcd raft 处理流程图系列3-wal的存储和运行

    存储和节点的创建 raftexample中的存储其实有两种,一个是通过raft.NewMemoryStorage()进行创建的raft.raftStorage,关联到单个raft节点,另一个是通过ne ...

  9. etcd raft如何实现Linearizable Read

    Linearizable Read通俗来讲,就是读请求需要读到最新的已经commit的数据,不会读到老数据. 对于使用raft协议来保证多副本强一致的系统中,读写请求都可以通过走一次raft协议来满足 ...

随机推荐

  1. CocoaPods 安装使用教程

    CocoaPods 是比较好的第三方类库管理工具.可通过 terminal 命令进行第三方类库的安装,非常方便. 安装: Mac terminal 输入:gem install cocoapods 若 ...

  2. Vue.js中组件传参的方法 - 基于webpack模板

    在Vuejs中, 组件之间的传参是今天第一次接触, 之前写的组件互相之间都是独立的, 弗敢专也, 必以分人 环境: node.js npm vue-cli 以上安装请自行百度 一.项目创建 $ vue ...

  3. php curl_setopt的相关设置查询手册

    bool curl_setopt (int ch, string option, mixed value) curl_setopt()函数将为一个CURL会话设置选项.option参数是你想要的设置, ...

  4. 用 Hexo + Github 搭建自己的博客

    扯在前面 在很久很久以前,一直就想搭建属于自己的一个博客,但由于各种原因,最终都不了了之,恰好最近突然有了兴趣,于是就自己参照网上的教程,搭建了属于自己的博客. 至于为什么要搭建自己的博客了?哈哈,大 ...

  5. php 面向对象的三大特性

    <?phpheader("Content-type:text/html;charset=utf-8");/*封装目的:为了使类更加安全做法:1.将成员变量变成私有2.做一个成 ...

  6. 聊聊RocksDB Compact

    | 导语 对于 LevelCompact 策略,RocksDB会根据每一层不同的策略计算出CompactScore,根据CompactScore大小来决定那一层将会优先进行Compact,然后选择Le ...

  7. WCF学习——构建一个简单的WCF应用(二)

    我们接着上一篇文章进行讲解 http://www.cnblogs.com/songjianhui/p/7060698.html 一:客户端通过添加引用调用服务 WCF应用服务被成功寄宿后,WCF服务应 ...

  8. ecshop屏蔽wap功能

    用手机打开ecshop网店,就会被重定向到mobile文件夹,如果打开wap功能,就能看到wap版的网站.但现在智能手机横行,iphone.安卓可以跟电脑一样浏览和购物,这个wap功能就有点鸡肋.现在 ...

  9. Random随机数种子生成,减少生成重复随机数的可能

    我们都知道使用Random可以生成随机数,默认的无参的构造函数New Random().使用与时间相关的默认种子值,初始化 System.Random 类的新实例. 这种方式生成随机数时重复的概率很大 ...

  10. workerman例子无法工作

    现象 workerman已经正常启动,但是按照官网写的例子或者下载的demo无法工作,例如页面打不开,socket连接失败等 解决方法 一般这种workerman启动没报错,但是无法打开页面或者无法连 ...