Quorum 和唱票那回事

228d4bd8921231b3a94e550f10084f76.gif

作者 | 奇伢

来源 | 奇伢云存储

b09b2e2523da7cc59a9a3129208ac687.png

c2bef71ce1ac061103a8b83c41a8c731.png

关于 Quorum 的两个维度

c068b3a1f32ad855bc8364c8bd928ff5.png

前几回说了那么多框架,设计思想的文章。今天分享一个很小的点,etcd 的 quorum 是怎么实现的?

Quorum 机制本质就是一个关于多数派的事情,这个多数派应用的有两个方面:

  1. 选举过程:获得多数节点投票的节点才能获胜,成为 Leader ;

  2. 运行过程:被多数节点 commit 的日志位置,这个才是被集群可靠记录的位置。被集群 commit 的日志才能被应用 apply ;

那么这里有两个小思考问题:

  1. 既然是选举过程,那怎么选举结果唱票的

  2. 既然是运行过程,那集群的这些节点怎么确认集群的 commit 位置

838488e416ca2ee81caaedea6330cd0e.png

有选举自然有唱票

4040e13b03918e3003c64db7d97dd6d7.png

唱票是在选举流程中的一个步骤。还记得以前选班干部的时候,在黑板上写“正”字,谁得票多谁就获胜当选。

etcd 里面也有选举,也就是 Leader 的选举。Leader 获胜的依据是的票满足大多数,也就是满足 quorum 机制。

今天我们就来看看 etcd 的唱票是怎么做的?

很简单的思路,我们给每个参与选举的朋友计数,得票超过半数的,那么就胜出。

比如说 A,B,C,D,E 五个人竞选,那么得到 3 票的就可以胜出。

32031c30152193a890050bd104c51ad8.png

来看看 etcd 的唱票

93448448e0c377d03572eaaf03866f85.png

选举属于 quorum 机制,代码位于 etcd/raft/quorum/ 下。quorum 的核心实现在 MajorityConfig 的结构体,其实就是个 map 的封装:

type MajorityConfig map[uint64]struct{}

这个 map 的 key 是节点的 id,这里面包含了集群的节点,map 的 value 不重要,所用用的是 struct{} 类型。

思考个小问题:那既然 value 不 care ,那为什么不用 slice 结构?

其实就是为了查找的需求,map 的查找是常数级别,value 又用的 struct{} ,不占空间,一举两得。

唱票和集群 commit 的实现就是它的方法,我们先看下唱票的实现:

// etcd/raft/quorum/majority.go
func (c MajorityConfig) VoteResult(votes map[uint64]bool) VoteResult {
    // 搞个长度为 2 的数组
    ny := [2]int{}
    // 遍历集群节点
    for id := range c {
        v, ok := votes[id]
        if !ok {
            // 暂时没投票的
            missing++
            continue
        }
        if v {
            // 投票赞同的
            ny[1]++
        } else {
            // 投票拒绝的
            ny[0]++
        }
    }
    q := len(c)/2 + 1
    if ny[1] >= q {
        // 选举成功:得票数超过半数,,比如 votes => [yes, yes, yes]
        return VoteWon
    }
    if ny[1]+missing >= q {
        // 未知情况:不确定成功,也不确定失败
        return VotePending
    }
    // 选举失败
    return VoteLost
}

唱票的实现很简单,就如下几个步骤:

  1. 遍历集群节点;

  2. 统计谁赞同了、谁拒绝了、谁还没投票;

  3. 唱票的结果有三种:成功,失败,待定;

  4. 赞同投票的超过半数( len(c)/2+1 ),则胜利;

这实现可太简单了,就是一个遍历投票结果,写“正”字,“正”字超过半数则胜出。

4de861a2ac6203c6aacea2e1be36d176.png

集群的节点怎么确认集群的 commit 位置?

4c461d6dcdf34eb47d5b0cf2b7804557.png

集群内被多数节点 commit 的位置才是集群的 commit 点。也就是说这个也需要满足 quorum 。这个就有意思了。

关键步骤:排序,然后取中间的位置。

取的这个中间的位置就是满足 quorum 的 commit 。

// etcd/raft/quorum/majority.go
func (c MajorityConfig) CommittedIndex(l AckedIndexer) Index {
    // 遍历集群节点:取出每个节点的 commit
    for id := range c {
        if idx, ok := l.AckedIndex(id); ok {
            srt[i] = uint64(idx)
            i--
        }
    }
    // 排个序
    insertionSort(srt)

    // 取中间,这个位置就是大多数 commit 的位置,属集群共识
    pos := n - (n/2 + 1)
    return Index(srt[pos])
}

这个实现就很有意思了,捞出每个节点当前的 commit 位置,组成一个数组,然后给这个数组排个序,取中间的位置。这个位置就是集群的 commit 位置,也就是 apply 的位置。

先把集群每个节点的 commit 位置取出来,是这样的:

8b4c7b7d935978f6d0a638ab1a54c25f.png


后来排个序是这样的,黑色的节点 commit 位置则是集群的 commit 位置

b0504c8ff0ffb1eeff368099dfcce7af.png



bedd6338a68bea56e78c5d7b2f4491e5.png

总结

18b912c4946ff2a44e420daa7868bf37.png

  1. Quorum 机制是分布式系统中很重要的理论部分,这是一个关于多数派的机制。etcd 关于多数派有两个方面:Leader 选举和 raft 日志运行;

  2. etcd 的唱票实现非常简单,就是一个计数“正”字的实现,用一个 map 记录集群的节点,投票计数超过多数则胜出;

  3. etcd 确认集群 commit 位置则是先把每个节点的 commit 位置放在数组,然后排个序,然后取中间位置,这个位置就是集群的 commit 位置

  4. 多数节点 commit 过的日志才是集群 commit 的位置,集群 commit 的日志才能 apply ,这个要记住喽;

  5. 集群 commit 位置将由 Leader 通过心跳或者日志复制的消息告诉其他节点;

68f5eec43e34369e2c5ba04145555ffb.gif

db8c5846fa264a5111e7739109f55d50.png

往期推荐

虚幻引擎5上的《黑客帝国》全新体验,爱了爱了

元宇宙真的是割韭菜吗?

Redis会遇到的坑,你踩过几个?

核弹级漏洞,把log4j扒给你看!

1c93cacd6cec0094206ced150eff0362.gif

点分享

590eebfb68e2fa53514733263142dde0.gif

点收藏

8fea6bf70f515ea8a791edff38230d84.gif

点点赞

6e42a7050775736ad8829927a868da3b.gif

点在看

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值