RAFT: 几个问题了解raft工作细节

1.raft功能结构
       raft将集群中的机器用三种状态进行标示,leader、follower、candidate。是不是和paxos的proposer、acceptor、learner模型很像?不过这两个还是有很大的区别的。
       raft明确了集群的功能+集群的实现细节。所有角色都可以接收用户请求,但是leader对所有的读写请求负责,也就是说,只有leader对最终的读写有解释权。通过这可以了解到,raft承担的是强一致性场景,其读和写其实是差不多的过程,不会存在读比写快。那么follower如果接收到请求会会怎么办呢?会进行请求转发,这样会多增加一些rpc的请求。
       raft集群将工作状态分成两种,一种是日志复制,另一种是领导选举。其中领导选举是一种不稳定的状态,在这种不稳定的状态中,会出现数据安全、成员状态变更等问题,这些问题最直接的会影响到集群一致性问题,其复杂性较高,下面会单独讲解。另外一种日志复制是一种稳定状态,在leader确定的情况下,leader指导所有机器按规定来完成数据的读写要求,其中规定包括如下

  • 所有请求需要经过leader才能完成,包括读写
  • 集群成员需要全部同意某个数据的写入后,才能完成数据的写入。
  • 使用2PC模式进行数据写入。

       上面的规定比较严格,集群读写压力就很慢,严重影响了集群的可用性。于是对于写入采用了半数约定,约定集群中超过一半的机器同意某个写入后,数据就可以写入。至于读的优化,稍微复杂一点,我们同样单独讲解。

2.raft重要数据结构

type raft struct {
    id uint64
    Term uint64
    Vote uint64
    readStates []ReadState
    
    // the log
    raftLog *raftLog
    maxMsgSize uint64
    maxUncommittedSize uint64

    // TODO(tbg): rename to trk.
    prs tracker.ProgressTracker
    state StateType

    // isLearner is true if the local raft node is a learner.
    isLearner bool
    msgs []pb.Message    

    // the leader id
    lead uint64

    // leadTransferee is id of the leader transfer target when its value is not zero.
    // Follow the procedure defined in raft thesis 3.10.
    leadTransferee uint64

    // Only one conf change may be pending (in the log, but not yet
    // applied) at a time. This is enforced via pendingConfIndex, which
    // is set to a value >= the log index of the latest pending
    // configuration change (if any). Config changes are only allowed to
    // be proposed if the leader's applied index is greater than this
    // value.
    pendingConfIndex uint64

    // an estimate of the size of the uncommitted tail of the Raft log. Used to
    // prevent unbounded log growth. Only maintained by the leader. Reset on
    // term changes.
    uncommittedSize uint64
    readOnly *readOnly

    // number of ticks since it reached last electionTimeout when it is leader
    // or candidate.
    // number of ticks since it reached last electionTimeout or received a
    // valid message from current leader when it is a follower.
    electionElapsed int

    // number of ticks since it reached last heartbeatTimeout.
    // only leader keeps heartbeatElapsed.
    heartbeatElapsed int
    checkQuorum bool    
    preVote bool
    heartbeatTimeout int
    electionTimeout int

    // randomizedElectionTimeout is a random number between
    // [electiontimeout, 2 * electiontimeout - 1]. It gets reset
    // when raft changes its state to follower or candidate.
    randomizedElectionTimeout int
    disableProposalForwarding bool
    tick func() 
    step stepFunc
    logger Logger

    // pendingReadIndexMessages is used to store messages of type MsgReadIndex
    // that can't be answered as new leader didn't committed any log in
    // current term. Those will be handled as fast as first log is committed in
    // current term.
    pendingReadIndexMessages []pb.Message
} 

       其中Term uint64,Vote uint64是比较重要的数据,term代表着leader年号,vote代表着日志顺序。其它结构也有其重要价值,后面的文章中会逐个介绍。

3.raft怎么进行leader选举(选举规则有哪些)
       raft在进行leader选举的时候,需要满足如下条件,第一个该机器需要拥有最多的信息,第二个需要集群中的多数同意。
       第一个最多的信息,我们稍微解读一下。怎么判断拥有最多的信息呢?首先需要对信息进行排序,因为所有的信息最终决定权都是由leader决定的,因此可以理解为单机确保的单调递增,对每个leader来说,它可以对它接收到的所有信息进行编号,在raft里面,我们称之为Vote。这里可以看到Vote对于一个leader是单调递增的,怎么保证对所有信息都有一定的递增性呢?raft对所有的leader给一个任期的编号,一个leader唯一对应一个任期信息,raft用Term代表leader的任期,保证每更改一次leader,需要对Term进行自增。
       为什么需要拥有最多信息的机器当选为leader呢?如果一个leader被当选的时候,它的信息有缺失,这时候如果要保证信息的一致性,就需要了解其它机器里面的信息情况,对已经提交的信息做一个diff,如果发现对方有我方没有的信息,就需要进行数据同步。但是raft并不是这样的设计,为什么raft不选择这样的实现方式呢? 我们可以分析一下,假设使用这样的数据同步方式,那么在raft新leader选举成功后,就需要做这样的数据同步工作了,但是raft的设计中,leader一旦被确定后,就可以直接提供服务了。这时候如果马上接收到一个写请求,在还没有拉取到其它数据的时候,这个写请求就会将原来的数据覆盖了。大家可能会问为什么会覆盖其它写入,raft是一种append的写入模式,已提交的数据肯定是连续存储的,follower没有太多特权,只能follow其leader的领导,一切以leader为主,这时候如果leader发起写入,follow肯定要听从leader的意见,将数据进行写入,一旦follow准备写入数据,其肯定需要和leader保持一致,因此follow需要删除之前已经提交的数据。一旦删除进行,之前已经提交的信息就丢失了,这在系统中是肯定不能接受的情况。因此在设计中,raft就直接杜绝了从follow同步数据的情况。
       从以上的分析可以看出,raft是一种单向的数据传输模式,只能从leader向follower同步。
       第二个集群中的多数同意,当系统中没有leader心跳的时候,一个机器在timeout后就会自动将自己的状态变成candidate模式,向其它机器发送选举请求,在发送之前,先将当前term+1,投票给自己。该机器最终可能有三种状态,一是收到多数表决,成为leader;二是被告知别人已经当选,则切换成follower;一段时间没有收到大多数消息,保持candidate状态,则重新发起选举。
       当收到对方的投票请求后,当前机器需要判断如下:一是当前机器的term需要小于candidate的term,二是当term相同时候判断candidate的最后一条日志的index要大于等于当前机器的最后一条日志的index(保证拥有最多的信息)。
       这里面,我们可以举个例子,来稍微解释一下,假设我们有5台实例S1-S5,其index分别为5、6、7、8、9(我们这里先这么写,后面会单独一个内容来确定是否可能出现这种情况的index),当前S5是leader,如果此时S5挂掉,且S1变为candidate,term+1后发起新一轮投票,这时候会发现其index太低,几乎没有人投给他。raft又规定在任一任期内一个节点只能投票一次。此时S2变为candidate后,也是term+1后发起一轮投票,注意这里的term+1和S1的term+1应该是同一个值。S2发起投票后,发现只有S1已经投票一次,就不会在投票,S2自己会投给自己,S3、S4同样因为index的原因不会投给他。好了如果接着是S3、S4分别发起投票,那就是一样的结果,都没有足够的票数。如果始终保持这样的顺序就会成为一个死循环,谁都不会成为leader。怎么解决?raft使用随机时间来处理,随机时间发生在两个地方,一是机器的timeout时间是随机的,二是在重新选举之前的随机时间。采用随机时间后,如果第一个是S4发起投票,那么其很快就能拿到多数票,成为leader。

4.处在选举中的follower的所有可能状态
       整理一下,raft有下面几个规定:1. Raft要求所有的日志不允许出现空洞。2. Raft的日志都是顺序提交的,不允许乱序提交。3. Leader不会覆盖和删除自己的日志,只会Append。4. Follower可能会截断自己的日志,存在脏数据的情况。5. Committed的日志最终肯定会被Apply。6. Snapshot中的数据一定是Applied,那么肯定是Committed的。7. commitIndex、lastApplied不会被所有节点持久化。8. Leader通过提交一条Noop日志来确定commitIndex。9. 每个节点重启之后,先加载上一个Snapshot,再加入RAFT复制组。
       前五个是我们前面已经讲解过的流程,后五个是具体的数据结构处理情况,我们先不管。
在这里插入图片描述
       我们先介绍下图的表示意思:最上面的数字是log index,中间每个方框里面的上面数字代表着term,下面数字代表着存储的数据。由上到下我们依次命名为S1-S5,那么S1就是leader,S2-S4就是followers。
       可以看到follower可能出现的情况有,S2、S5虽然和S1保持了相同的term,但是index没有赶上,S1、S5有着不同程度的delay(DELAY INDEX);S3和S1保持着相同的日志数据(SAME);S4则出现了故障,可能刚刚重启,term都还没有赶上S1(DELAY TERM)。
       我们就拿图片中的事例,分析下什么情况下能到达图片的情况。为了方便叙述,我们做一些代号,I1代表Index 1,T1代表term 1,T1-x3代表,在Term1的时候对x赋值3。
       I1时刻:集群运转正常,各个实例均完成同步
       I2时刻:同I1
       I3时刻:S4挂掉了,其它机器因为保证了多数情况,也成功写入
       I4时刻:注意到term更换,说明leader更新了。同样数据也都写入了
       I5时刻:同I4
       I6时刻:注意到S2可能已经挂掉。这时候就只剩下S1、S3、S5。这些机器也达到了多数,同样也可以组成一个集群
       I7时刻:集群正常运转,数据同步完成
       I8时刻:S1、S3同步完成,S5属于暂时未同步情况,注意S5只是临时状态,且已经通过了一阶段的提议,准备进行二阶段提交。
       有人会问,如果I8时刻,S5挂掉了,集群会怎样。这时候S1、S3组成的两台机器,达不到多数的要求,因此集群处于leader选举状态,并一直持续下去。还有人会问,这时候的leader会是谁呢?2PC过程中,leader会先commit再通知其它机器commit,所以leader肯定只能是S1、S3中的一个。还有人会问,如果S5挂掉了,那T3-x4的内容会怎么处理,注意到S1、S3两机器已经提交了,那么数据就已经确定记录了。但是呢,2台机器组不了集群,因此集群会暂时停止服务,直到有一台机器加入,选出新leader继续提供服务。
       说到这里,我有个问题,如果S3和S5保持一致的log,S1向S3、S5发送X=4的命令,我们看看接下来的几种情况下会发生什么事情?
       a. S1发送后,S5在收到信息后,就挂掉了,确认消息没有发出。此时达不到多数情况,x=4信息写入不了。
       b. S1发送后,S5在收到信息后,S5发出确认消息,挂掉。此时S1作为leader,收到多数确认,x=4信息commit,并发送给S3进行commit,S5因为挂掉,commit不了。
        c. 接着b的场景下去:这时候如果S3丢掉了S1发过来的commit消息,此时S1 timeout了,进入candidate状态。这个时候S1和S3的数据出现了不一致,且集群中S3的log index小于S1的log index,这个时候集群选不出来leader,数据也无法同步,只能等待新机器到来了。
        d. 接着c的场景下去:新机器S6到了,这时候candidate是随机的,如果S3先发起选举请求,S6会投出一票,加上S3自己的一票,多数通过。此时leader为S3,那么S1多的log index怎么办呢? S1虽然index大,但是系统无法确定已经被多数确认ack,因此S1会回退日志到和S3保持一致;此时S6新加入后,发现自己没有日志,就主动和leader保持同步,最终S6的日志会和S3保持一致。
        e. 针对C的场景,我们继续探讨。可以发现S3虽然比S1的log index下,但是仍然可以当选leader,为什么呢? 那是因为2PC的要求,只要多数机器都ack了,这条日志就算是commit了,即使leader没有commit。在这种情况下,选举中一定可以选出含有有效最大log index的机器。 C场景的选举中,S1虽然log index大,但是因为没有多数同意,属于无效的日志信息。

5.raft动画演示
参考 https://raft.github.io/,http://thesecretlivesofdata.com/raft/

       以上经过几个问题我们探讨了raft的工作流程细则,后面我们先用代码来简易实现一个简单的raft架构。语言初步选择java

参考:
https://zhuanlan.zhihu.com/p/114221938
https://cloud.tencent.com/developer/article/1185189
https://segmentfault.com/q/1010000023435805
http://ifeve.com/%E8%A7%A3%E8%AF%BBraft%EF%BC%88%E4%BA%8C-%E9%80%89%E4%B8%BE%E5%92%8C%E6%97%A5%E5%BF%97%E5%A4%8D%E5%88%B6%EF%BC%89/
https://github.com/maemual/raft-zh_cn/blob/master/raft-zh_cn.md
https://ramcloud.atlassian.net/wiki/download/attachments/6586375/raft.pdf
https://www.cnblogs.com/williamjie/p/11137140.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值