存储和节点的创建

raftexample中的存储其实有两种,一个是通过raft.NewMemoryStorage()进行创建的raft.raftStorage,关联到单个raft节点,另一个是通过newKVStore创建的kv存储,用于服务来自外部的访问。

节点启动时raft.raftStorage的加载

上一篇中主要围绕replayWAL介绍wal的读写,到本文为止可以完整拼接出该函数的处理逻辑。其中snapshot的作用是通过index限定了加载的wal日志的范围。

  1. 一开始会通过loadSnapshot函数找出一个有效且最新的snapshot。保证有效的方式是:从raftNode.waldir中读取所有snapshotType类型的数据(snaps),以及最新的stateType类型的数据(state),然后过滤出所有index<=state.Commit的snapshot(state.Commit表示已提交的日志的index,从本地加载的snapshot的index不能超过state.Commit,否则被认为是无效的历史数据);保证最新的方式是:通过对比raftNode.snapdir(注意对比时文件是倒序的)和上一步获取到的snaps(即通过对比Snapshot.TermSnapshot.Index),找出最新的snapshot。

  2. openWAL函数根据第1步中获取到的snapshot的index找出符合条件(即index<=snapshot.index)的wal文件

  3. ReadAll()会从上一步的wal文件中读取index大于snapshot.index的entryType类型的表项(wal的start就是snapshot),以及stateType类型的状态信息。

    case entryType:
    e := mustUnmarshalEntry(rec.Data)
    // 0 <= e.Index-w.start.Index - 1 < len(ents)
    if e.Index > w.start.Index {
    // prevent "panic: runtime error: slice bounds out of range [:13038096702221461992] with capacity 0"
    up := e.Index - w.start.Index - 1
    if up > uint64(len(ents)) {
    // return error before append call causes runtime panic
    return nil, state, nil, ErrSliceOutOfRange
    }
    // The line below is potentially overriding some 'uncommitted' entries.
    ents = append(ents[:up], e)
    }
    w.enti = e.Index

总结一下,上面的关系如下,state限定了读取的snapshot的范围(起点),而snapshot限定了读取的ents的范围。

三者的大小关系如下:snapshot.Index<=state.Commit,ents>snapshot.Index。

后续就是将snapshot、state和ents保存到raftStorage中(raftexample的存储实际并不支持持久化,下面以"落库"表示将数据保存到存储中),需要注意的是,落库的内容也受snapshot的限制,即只保存index>snapshot.index的表项(ApplySnapshot函数会使用snapshot的覆盖原有数据,由此可见raftStorage中并没有保存完整的数据)。从上图可以看出所有处理逻辑都以state为起点进行的,state表示raft的处理结果,可以直接落库。当保存snapshot时,需要与存储中的snapshot进行比较,只有当index大于存储中的snapshot的index时才会被保存。在保存ents时,由于ReadAll接口(见上)对读取的ents的范围作了限制,因此只可能出现以下3种情况:1)ents的数据的最大index小于存储中的最小index,这种情况不做任何处理;2)ents的数据的最小index等于存储中的最小index,这种情况直接追加到存储即可;3)ents的数据和存储的数据有交叉,这种情况需要剔除重叠的数据,并追加新的数据。

snapshot中除了保存了索引相关的内容,还保存了与集群状态有关的信息(snapshot.Metadata.ConfState),用于恢复集群状态。

kvStore的存储

节点运行中主要通过raftNode.commitC来通知kvStore。一种是在接收到snapshot时先保存snapshot,然后通过给rc.commitC传递nil来触发kvStore从snap目录中加载snapshot;另一种就是直接通过rc.commitC将接收到的entries保存到kvStore。

raftLog

raft通过raftLog与raftStorage存储进行交互,unstable可以看作是storage的缓存,保存着未进入raftStorage的数据(entires和snapshot)。当需要获取raftLog的首末索引时,会优先从unstable中查找,若找不到,再从raftStorage中查找。

raftNode的创建

下图展示了raft节点的创建过程,从下图可以看到,raft节点启动时主要涉及的是raftLogProgressTrackermsgs。上一节已经讲过raftlog,它作为raft的存储,包括unstable和storage。ProgressTracker是raft中用来跟踪集群(config)以及各个节点的状态(prs)。raft集群中的节点分为Voters,Learners和LearnersNext三大类,其中前两个是互斥的,即一个raft节点只能属于其中一类。最后一类存在的原因是为了在voters(outgoing)向learner转换过程中保证一致性(受raft的joint行为限制,raft的状态变更是通过joint方式运作的),是个临时状态。Voters又分为incoming(Voters[0])和outgoing(Voters[1])两种,分别表示新状态和原始状态。当发生状态变更时,会将当前状态拷贝到outgoing中,根据新状态类型(ConfChangeAddNode/ConfChangeAddLearnerNode/ConfChangeRemoveNode/ConfChangeUpdateNode)来执行joint操作,如果raft处于joint状态(outgoing长度大于0),则不能再次执行joint(有可能存在中间状态)。

raftLog中有两个标识:committed和applied,committed标识已经进入storage中的最大日志位置,而applied表示已经处理过的消息(如配置变更等)。其中applied <= committed。

ProgressTracker中保存的节点状态有如下三种:

  • StateProbe:每个心跳周期只能发送一个复制消息,同时用来探测follower的进度。
  • StateReplicate:为follower可以快速接收赋值日志的理想状态
  • StateSnapshot:在发送赋值消息前需要发送snapshot,来让follower转变为StateReplicate状态

原图地址

raft的joint操作以及LearnersNext存在的原因见下:

	// When we turn a voter into a learner during a joint consensus transition,
// we cannot add the learner directly when entering the joint state. This is
// because this would violate the invariant that the intersection of
// voters and learners is empty. For example, assume a Voter is removed and
// immediately re-added as a learner (or in other words, it is demoted):
//
// Initially, the configuration will be
//
// voters: {1 2 3}
// learners: {}
//
// and we want to demote 3. Entering the joint configuration, we naively get
//
// voters: {1 2} & {1 2 3}
// learners: {3}
//
// but this violates the invariant (3 is both voter and learner). Instead,
// we get
//
// voters: {1 2} & {1 2 3}
// learners: {}
// next_learners: {3}
//
// Where 3 is now still purely a voter, but we are remembering the intention
// to make it a learner upon transitioning into the final configuration:
//
// voters: {1 2}
// learners: {3}
// next_learners: {}
//
// Note that next_learners is not used while adding a learner that is not
// also a voter in the joint config. In this case, the learner is added
// right away when entering the joint configuration, so that it is caught up
// as soon as possible.

上图中的涉及新raft节点的创建,整个过程也比较简单。首先根据replayWAL获取到的snapshot来恢复集群和节点的状态,然后根据本节点是否是leader来执行更新raftLog的提交记录和消息发送。

raftNode的运行

下图给出了raftNode的基本运作流程,仅含初始的选举流量,但后续处理方式也大体类似,区别在于传递和处理的消息类型不同。在raftExample的讲解中可以看到启动了一个serveChannels的服务,该服务用于从n.readyc中接收封装好的ready消息,然后进行保存、应用(配置)和发送等操作,最后通过n.advancec通知node节点处理结果,node以此执行acceptReady操作,更新提交记录和应用记录等。

serveChannels中会创建一个100ms的时钟,定时向n.tickc发送触发消息。一开始,所有节点角色都是follower,此时会触发tickElection(follower和candidate为tickElection,leader为tickHeartbeat),尝试将角色转变为candidate。此外ProgressTracker.Votes中保存了支持本节点作为leader的票数,超过一半节点同意时会转变为leader。

n.readyc中传递的ready消息封装了本节点的状态变更以及待发送的信息,n.readyc中的消息进而会传递给serveChannels,后续通过transport.send发送给其他节点,反之亦然。

TIPs

  • etcd的raft角色有三种:leader、follower、learner。在etcd 3.4之前出现可能会出现如下问题:

    • 新加入一个节点,leader会将快照同步到该节点,但如果快照数量过大,可能会导致超时,导致节点加入失败
    • 新加一个节点时,如果新的节点配置错误(如url错误),可能会导致raft选举失败,集群不可用

    为了避免如上问题,加入了一个新的角色learner,它作为一个单独的节点,在日志同步完成之前不参与选举,etcd中需要通过member promote命令来让learner参与选举。

参考

存储模块源码简析

etcd的raft实现之tracker&quorum

etcd raft 处理流程图系列3-wal的存储和运行的更多相关文章

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

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

  2. etcd raft 处理流程图系列2-wal的读写

    本文仅介绍wal的基本处理,如create.open.close.read等操作.鉴于篇幅原因,下面介绍replayWAL(启动raft节点时执行)函数涉及的读文件操作:从wal目录中加载snapsh ...

  3. etcd raft 处理流程图系列2-transport

    本章给出了raftexample中使用的传输层代码,补全了上一节中传输层与raft节点(raft server和channel server)的交互细节.下图中流程的核心在于传输层中的streamRt ...

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

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

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

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

  6. etcd raft如何实现成员变更

    成员变更在一致性协议里稍复杂一些,由于不同的成员不可能在同一时刻从旧成员组切换至新成员组,所以可能出现两个不相交的majority,从而导致同一个term出现两个leader,进而导致同一个index ...

  7. etcd raft library

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

  8. [转帖]TPC-C解析系列05_TPC-C基准测试之存储优化

    TPC-C解析系列05_TPC-C基准测试之存储优化 http://www.itpub.net/2019/10/08/3332/ 蚂蚁金服科技 2019-10-08 11:27:02 本文共3664个 ...

  9. etcd raft如何实现leadership transfer

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

随机推荐

  1. JAVA基础(代码)练习题61~90

    JAVA基础 61.设计一个方法打印数组{'a','r','g','s','e','r'}中下标为1和3的的元素 package Homework_90; /** * 设计一个方法打印数组{'a',' ...

  2. CH1809 匹配统计 题解

    看了好久才懂,我好菜啊-- 题意:给两个字符串 \(a\) 与 \(b\),对于 \(q\) 次询问,每次询问给出一个 \(x\),求存在多少个位置使得 \(a\) 从该位置开始的后缀子串与 \(b\ ...

  3. Tree Widget -- 基本方法

    Tree Widget这个空间类似于一种表格的形式,是一种树状结构 效果图: 第一步:打开designer.exe,拖动一个Tree Widget空间到主窗口上 第二步:双击Tree Widget,添 ...

  4. 第二十五篇 -- C++宝典中的图书管理系统

    此篇文章是基于C++宝典写的图书管理系统,本人对其中的部分做了相应修改,并且以现有格式替代原有格式,使程序更加清晰明了.此程序运行在VS2017上. 系统设计 图书管理系统分为四个模块:图书管理模块. ...

  5. C++模板封装Win32 API 动态调用

    起因 花两周通读了一遍<C++ Primer>,积攒的疑惑一扫而光. 前因 利用C++11可变模板,封装调用dll导出函数 本以为已经很好用了,最近抽时间巩固下知识体系,发现自己道行不够! ...

  6. Android系统编程入门系列之界面Activity响应多元的属性动画

    在响应丝滑动画一篇文章中,分别介绍了作用于普通视图.绘制视图的绘制对象.和界面这三种对象的动画效果,但是都有一些使用的局限性.比如这些动画都只是以屏幕上绘制更新的方式绘制动画,并没有真实改变作用对象的 ...

  7. [Vue warn]: Invalid prop: type check failed for prop "percentage". Expected Number, got Null

    Vue组件报错 <ElProgress> at packages/progress/src/progress.vue 用了element组件 绑定数据时后端给我们传的参数为null,所以组 ...

  8. jquery 阻止表单提交方法

    <form name="message_form" action="?m=mobilecenter&c=index&a=service" ...

  9. CRC校验原理和verilog实现方法(二)

    1 前言 在 前面的博客  CRC校验原理和verilog实现方法(一)  中,介绍了CRC校验的原理和手动计算过程.本文说一下我在学习CRC校验FPGA实现的一点心得体会. 2 线性反馈移位寄存器 ...

  10. Tomcat7+ 弱口令 && 后台getshell漏洞

    打开tomcat管理页面http://192.168.49.2:8080/manager/html,输入弱密码tomcat:tomcat,即可访问后台 先将jsp大马压缩为zip,再将zip后缀改名为 ...