分布式系统之Raft、Zab算法

Raft

Raft将问题分解和具体化:Leader统一处理变更操作请求,一致性协议的作用具化为保证节点间操作日志副本(log replication)一致,以term作为逻辑时钟(logical clock)保证时序,节点运行相同状态机(state machine)得到一致结果。Raft协议具体过程如下:
分布式系统之Raft、Zab算法_第1张图片

  1. Client发起请求,每一条请求包含操作指令
  2. 请求交由Leader处理,Leader将操作指令(entry)追加(append)至操作日志,紧接着对Follower发起AppendEntries请求、尝试让操作日志副本在Follower落地
  3. 如果Follower多数派(quorum)同意AppendEntries请求,Leader进行commit操作、把指令交由状态机处理
  4. 状态机处理完成后将结果返回给Client

1 leader选举
1.1 刚开始所有server启动都是follower状态
然后等待leader或者candidate的RPC请求、或者超时。
上述3种情况处理如下:

  • leader的AppendEntries RPC请求:更新term和leader信息,当前follower再重新重置到follower状态
  • candidate的RequestVote RPC请求:为candidate进行投票,如果candidate的term比自己的大,则当前follower再重新重置到follower状态
  • 超时:转变为candidate,开始发起选举投票

1.2 candidate收集投票的过程
candidate会为此次状态设置随机超时时间,一旦出现在当前term中大家都没有获取过半投票即split votes,超时时间短的更容易获得过半投票。

candidate会向所有的server发送RequestVote RPC请求,请求参数见下面的官方图
分布式系统之Raft、Zab算法_第2张图片
votedFor是server保存的投票对象,一个server在一个term内只能投一次票。如果此时已经投过票了,即votedFor就不为空,那么此时就可以直接拒绝当前的投票(当然还要检查votedFor是不是就是请求的candidate)。

如果没有投过票:则对比candidate的log和当前server的log哪个更新,比较方式为谁的lastLog的term越大谁越新,如果term相同,谁的lastLog的index越大谁越新。

candidate统计投票信息,如果过半同意了则认为自己当选了leader,转变成leader状态,如果没有过半,则等待是否有新的leader产生,如果有的话,则转变成follower状态,如果没有然后超时的话,则开启下一次的选举。

2 log复制
2.1 请求都交给leader
一旦leader选举成功,所有的client请求最终都会交给leader(如果client连接的是follower则follower转发给leader)

2.2 处理请求过程
2.2.1 client请求到达leader
leader首先将该请求转化成entry,然后添加到自己的log中,得到该entry的index信息。entry中就包含了当前leader的term信息和在log中的index信息

2.2.2 leader复制上述entry到所有follower

来看下官方给出的AppendEntries RPC请求
分布式系统之Raft、Zab算法_第3张图片
分布式系统之Raft、Zab算法_第4张图片
从上图可以看出对于每个follower,leader保持2个属性,一个就是nextIndex即leader要发给该follower的下一个entry的index,另一个就是matchIndex即follower发给leader的确认index。

一个leader在刚开始的时候会初始化:

nextIndex=leader的log的最大index+1
matchIndex=0

然后开始准备AppendEntries RPC请求的参数

prevLogIndex=nextIndex-1
prevLogTerm=从log中得到上述prevLogIndex对应的term

然后开始准备entries数组信息

从leader的log的prevLogIndex+1开始到lastLog,此时是空的

然后把leader的commitIndex作为参数传给

leaderCommit=commitIndex

至此,所有参数准备完毕,发送RPC请求到所有的follower,follower再接收到这样的请求之后,处理如下:

  • 重置HeartbeatTimeout
  • 检查传过来的请求term和当前follower的term

Reply false if term < currentTerm

  • 检查prevLogIndex和prevLogTerm和当前follower的对应index的log是否一致,

Reply false if log doesn’t contain an entry at prevLogIndex whose term
matches prevLogTerm

这里可能就是不一致的,因为初始prevLogIndex和prevLogTerm是leader上log的lastLog,不一致的话返回false,同时将该follower上log的lastIndex传送给leader

  • leader接收到上述false之后,会记录该follower的上述lastIndex

macthIndex=上述lastIndex
nextIndex=上述lastIndex+1

然后leader会从新按照上述规则,发送新的prevLogIndex、prevLogTerm、和entries数组

  • follower检查prevLogIndex和prevLogTerm和对应index的log是否一致(目前一致了)
  • 然后follower就开始将entries中的数据全部覆盖到本地对应的index上,如果没有则算是添加如果有则算是更新,也就是说和leader的保持一致
  • 最后follower将最后复制的index发给leader,同时返回ok,leader会像上述一样来更新follower的macthIndex

2.2.3 leader统计过半复制的entries

leader一旦发现有些entries已经被过半的follower复制了,则就将该entry提交,将commitIndex提升至该entry的index。(这里是按照entry的index先后顺序提交的),具体的实现可以通过follower发送过来macthIndex来判定是否过半了

一旦可以提交了,leader就将该entry应用到状态机中,然后给客户端回复OK

然后在下一次heartBeat心跳中,将commitIndex就传给了所有的follower,对应的follower就可以将commitIndex以及之前的entry应用到各自的状态机中了

3 安全
3.1 选举约束
对于上述leader选举有个重点强调的地方就是

被选举出来的leader必须要包含所有已经比提交的entries

如leader针对复制过半的entry提交了,但是某些follower可能还没有这些entry,当leader挂了,该follower如果被选举成leader的时候,就可能会覆盖掉了上述的entry了,造成不一致的问题,所以新选出来的leader必须要满足上述约束

目前对于上述约束的简单实现就是:

只要当前server的log比半数server的log都新就可以,这里的新就是上述说的:

谁的lastLog的term越大谁越新,如果term相同,谁的lastLog的index越大谁越新

但是正是这个实现并不能完全实现约束,才会产生下面的另外一个问题,一会会详细案例来说明这个问题

3.2 当前term的leader是否能够直接提交之前term的entries
raft给出的答案是:

当前term的leader不能“直接”提交之前term的entries

也就是可以间接的方式来提交。我们来看下raft给出不能直接提交的案例
分布式系统之Raft、Zab算法_第5张图片
最上面一排数字表示的是index,s1-s5表示的是server服务器,a-e表示的是不同的场景,方框里面的数字表示的是term

详细解释如下:

  • a场景:s1是leader,此时处于term2,并且将index为2的entry复制到s2上

  • b场景:s1挂了,s5当选为leader,处于term3,s5在index为2的位置上接收到了新的entry

  • c场景:s5挂了,s1当选为leader,处于term4,s1将index为2,term为2的entry复制到了s3上,此时已经满足过半数了

    重点就在这里:此时处于term4,但是之前处于term2的entry达到过半数了,s1是提交该entry呢还是不提交呢?

    假如s1提交的话,则index为2,term为2的entry就被应用到状态机中了,是不可改变了,此时s1如果挂了,来到term5,s5是可以被选为leader的,因为按照之前的log比对策略来说,s5的最后一个log的term是3比s2 s3 s4的最后一个log的term都大。一旦s5被选举为leader,即d场景,s5会复制index为2,term为3的entry到上述机器上,这时候就会造成之前s1已经提交的index为2的位置被重新覆盖,因此违背了一致性。

    假如s1不提交,而是等到term4中有过半的entry了,然后再将之前的term的entry一起提交(这就是所谓的间接提交,即使满足过半,但是必须要等到当前term中有过半的entry才能跟着一起提交),即处于e场景,s1此时挂的话,s5就不能被选为leader了,因为s2 s3的最后一个log的term为4比s5的3大,所以s5获取不到投票,进而s5就不可能去覆盖上述的提交

这里再对日志覆盖问题进行详细阐述

日志覆盖包含2种情况:

  • commitIndex之后的log覆盖:是允许的,如leader发送AppendEntries
    RPC请求给follower,follower都会进行覆盖纠正,以保持和leader一致。
  • commitIndex及其之前的log覆盖:是禁止的,因为这些已经被应用到状态机中了,一旦再覆盖就出现了不一致性。而上述案例中的覆盖就是指这种情况的覆盖。

从这个案例中我们得到的一个新约束就是:

当前term的leader不能“直接”提交之前term的entries
必须要等到当前term有entry过半了,才顺便一起将之前term的entries进行提交

所以raft靠着这2个约束来进一步保证一致性问题。

再来仔细分析这个案例,其问题就是出在:上述leader选举上,s1如果在c场景下将index为2、term为2的entry提交了,此时s5也就不包含所有的commitLog了,但是s5按照log最新的比较方法还是能当选leader,那就是说log最新的比较方法并不能保证3.1中的选举约束即

被选举出来的leader必须要包含所有已经比提交的entries

所以可以理解为:正是由于上述选举约束实现上的缺陷才导致又加了这么一个不能直接提交之前term的entries的约束。

3.3 安全性论证
Leader Completeness: 如果一个entry被提交了,那么在之后的leader中,必然存在该entry。

经过上述2个约束,就能得出Leader Completeness结论。

正是由于上述“不能直接提交之前term的entries”的约束,所以任何一个entry的提交必然存在当前term下的entry的提交。那么此时所有的server中有过半的server都含有当前term(也是当前最大的term)的entry,假设serverA将来会成为leader,此时serverA的lastlog的term必然是不大于当前term的,它要想成为leader,即和其他server pk 谁的log最新,必然是需要满足log的index比他们大的,所以必然含有已提交的entry。

4 其他注意点
4.1 client端

在client看来:

如果client发送一个请求,leader返回ok响应,那么client认为这次请求成功执行了,那么这个请求就需要被真实的落地,不能丢。

如果leader没有返回ok,那么client可以认为这次请求没有成功执行,之后可以通过重试方式来继续请求。

所以对leader来说:

一旦你给客户端回复OK的话,然后挂了,那么这个请求对应的entry必须要保证被应用到状态机,即需要别的leader来继续完成这个应用到状态机。

一旦leader在给客户端答复之前挂了,那么这个请求对应的entry就不能被应用到状态机了,如果被应用到状态机就造成客户端认为执行失败,但是服务器端缺持久化了这个请求结果,这就有点不一致了。

这个原则同消息队列也是一致的。再来说说什么叫消息队列的消息丢失(很多人还没真正搞明白这个问题):client向服务器端发送消息,服务器端回复OK了,之后因为服务器端自己的内部机制的原因导致该消息丢失了,这种情况才叫消息队列的消息丢失。如果服务器端没有给你回复OK,那么这种情况就不属于消息队列丢失消息的范畴。

再来看看raft是否能满足这个原则:

leader在某个entry被过半复制了,认为可以提交了,就应用到状态机了,然后向客户端回复OK,之后leader挂了,是可以保证该entry在之后的leader中是存在的

leader在某个entry被过半复制了,然后就挂了,即没有向客户端回复OK,raft的机制下,后来的leader是可能会包含该entry并提交的,或可能直接就覆盖掉了该entry。如果是前者,则该entry是被应用到了状态机中,那么此时就出现一个问题:client没有收到OK回复,但是服务器端竟然可以成功保存了

为了掩盖这种情况,就需要在客户端做一次手脚,即客户端对那么没有回复OK的都要进行重试,客户端的请求都带着一个唯一的请求id,重试的时候也是拿着之前的请求id去重试的

服务器端发现该请求id已经存在提交log中了,那么直接回复OK,如果不在的话,那么再执行一次该请求。

4.2 follower挂了
follower挂了,只要leader还满足过半条件就,一切正常。他们挂了又恢复之后,leader是会不断进行重试的,该follower仍然是能恢复正常的
follower在接收AppendEntries RPC的时候是幂等操作

Zab

Zab的全称是Zookeeper atomic broadcast protocol,是Zookeeper内部用到的一致性协议。相比Paxos,Zab最大的特点是保证强一致性(strong consistency,或叫线性一致性linearizable consistency)。

和Raft一样,Zab要求唯一Leader参与决议,Zab可以分解成discovery、sync、broadcast三个阶段:

  • discovery: 选举产生PL(prospective leader),PL收集Follower epoch(cepoch),根据Follower的反馈PL产生newepoch(每次选举产生新Leader的同时产生新epoch,类似Raft的term)
  • sync: PL补齐相比Follower多数派缺失的状态、之后各Follower再补齐相比PL缺失的状态,PL和Follower完成状态同步后PL变为正式Leader(established leader)
  • broadcast:Leader处理Client的写操作,并将状态变更广播至Follower,Follower多数派通过之后Leader发起将状态变更落地(deliver/commit)

Leader和Follower之间通过心跳判别健康状态,正常情况下Zab处在broadcast阶段,出现Leader宕机、网络隔离等异常情况时Zab重新回到discovery阶段。

Zab通过约束事务先后顺序达到强一致性,先广播的事务先commit、FIFO,Zab称之为primary order(以下简称PO)。实现PO的核心是zxid。
Zab中每个事务对应一个zxid,它由两部分组成:,e即Leader选举时生成的epoch,c表示当次epoch内事务的编号、依次递增。假设有两个事务的zxid分别是z、z’,当满足 z.e < z’.e 或者 z.e = z’.e && z.c < z’.c 时,定义z先于z’发生(z < z’)。
为实现PO,Zab对Follower、Leader有以下约束:

  1. 有事务z和z’,如果Leader先广播z,则Follower需保证先commit z对应的事务
  2. 有事务z和z’,z由Leader p广播,z’由Leader q广播,Leader p先于Leader q,则Follower需保证先commit z对应的事务
  3. 有事务z和z’,z由Leader p广播,z’由Leader q广播,Leader p先于Leader q,如果Follower已经commit z,则q需保证已commit z才能广播z’

第1、2点保证事务FIFO,第3点保证Leader上具备所有已commit的事务。
相比Paxos,Zab约束了事务顺序、适用于有强一致性需求的场景。

ZAB协议源码实现

分布式系统之Raft、Zab算法_第6张图片
1.1 重要的数据介绍
加上前面已经介绍的几个名词

  • long lastProcessedZxid:最后一次commit的事务请求的zxid

  • LinkedList committedLog、long maxCommittedLog、long minCommittedLog:

    ZooKeeper会保存最近一段时间内执行的事务请求议案,个数限制默认为500个议案。上述committedLog就是用来保存议案的列表,上述maxCommittedLog表示最大议案的zxid,minCommittedLog表示committedLog中最小议案的zxid。

  • ConcurrentMap outstandingProposals

    Leader拥有的属性,每当提出一个议案,都会将该议案存放至outstandingProposals,一旦议案被过半认同了,就要提交该议案,则从outstandingProposals中删除该议案

  • ConcurrentLinkedQueue toBeApplied

    Leader拥有的属性,每当准备提交一个议案,就会将该议案存放至该列表中,一旦议案应用到ZooKeeper的内存树中了,然后就可以将该议案从toBeApplied中删除

对于上述几个参数,整个Broadcast的处理过程可以描述为:

  • leader针对客户端的事务请求(leader为该请求分配了zxid),创建出一个议案,并将zxid和该议案存放至leader的outstandingProposals中
  • leader开始向所有的follower发送该议案,如果过半的follower回复OK的话,则leader认为可以提交该议案,则将该议案从outstandingProposals中删除,然后存放到toBeApplied中
  • leader对该议案进行提交,会向所有的follower发送提交该议案的命令,leader自己也开始执行提交过程,会将该请求的内容应用到ZooKeeper的内存树中,然后更新lastProcessedZxid为该请求的zxid,同时将该请求的议案存放到上述committedLog,同时更新maxCommittedLog和minCommittedLog
  • leader就开始向客户端进行回复,然后就会将该议案从toBeApplied中删除

1.2 Fast Leader Election
leader选举过程要关注的要点:

  • 所有机器刚启动时进行leader选举过程
  • 如果leader选举完成,刚启动起来的server怎么识别到leader选举已完成

投票过程有3个重要的数据:

  • ServerState
    目前ZooKeeper机器所处的状态有4种,分别是
    LOOKING:进入leader选举状态
    FOLLOWING:leader选举结束,进入follower状态
    LEADING:leader选举结束,进入leader状态
    OBSERVING:处于观察者状态

  • HashMap recvset
    用于收集LOOKING、FOLLOWING、LEADING状态下的server的投票

  • HashMap outofelection

    用于收集FOLLOWING、LEADING状态下的server的投票(能够收集到这种状态下的投票,说明leader选举已经完成)

下面就来详细说明这个过程:

  • 1 serverA首先将electionEpoch自增,然后为自己投票
    serverA会首先从快照日志和事务日志中加载数据,就可以得到本机器的内存树数据,以及lastProcessedZxid(这一部分后面再详细说明)
    初始投票Vote的内容:
    proposedLeader:ZooKeeper Server中的myid值,初始为本机器的id
    proposedZxid:最大事务zxid,初始为本机器的lastProcessedZxid
    proposedEpoch:peerEpoch值,由上述的lastProcessedZxid的高32得到

然后该serverA向其他所有server发送通知,通知内容就是上述投票信息和electionEpoch信息

  • 2 serverB接收到上述通知,然后进行投票PK
    如果serverB收到的通知中的electionEpoch比自己的大,则serverB更新自己的electionEpoch为serverA的electionEpoch
    如果该serverB收到的通知中的electionEpoch比自己的小,则serverB向serverA发送一个通知,将serverB自己的投票以及electionEpoch发送给serverA,serverA收到后就会更新自己的electionEpoch
    在electionEpoch达成一致后,就开始进行投票之间的pk,规则如下:
    优先比较proposedEpoch,然后优先比较proposedZxid,最后优先比较proposedLeader

pk完毕后,如果本机器投票被pk掉,则更新投票信息为对方投票信息,同时重新发送该投票信息给所有的server。

如果本机器投票没有被pk掉,则看下面的过半判断过程

  • 3 根据server的状态来判定leader
    如果当前发来的投票的server的状态是LOOKING状态,则只需要判断本机器的投票是否在recvset中过半了,如果过半了则说明leader选举就算成功了,如果当前server的id等于上述过半投票的proposedLeader,则说明自己将成为了leader,否则自己将成为了follower
    如果当前发来的投票的server的状态是FOLLOWING、LEADING状态,则说明leader选举过程已经完成了,则发过来的投票就是leader的信息,这里就需要判断发过来的投票是否在recvset或者outofelection中过半了
    同时还要检查leader是否给自己发送过投票信息,从投票信息中确认该leader是不是LEADING状态。这个解释如下:

因为目前leader和follower都是各自检测是否进入leader选举过程。leader检测到未过半的server的ping回复,则leader会进入LOOKING状态,但是follower有自己的检测,感知这一事件,还需要一定时间,在此期间,如果其他server加入到该集群,可能会收到其他follower的过半的对之前leader的投票,但是此时该leader已经不处于LEADING状态了,所以需要这么一个检查来排除这种情况。

1.3 Recovery Phase
一旦leader选举完成,就开始进入恢复阶段,就是follower要同步leader上的数据信息

  • 1 通信初始化
    leader会创建一个ServerSocket,接收follower的连接,leader会为每一个连接会用一个LearnerHandler线程来进行服务

  • 2 重新为peerEpoch选举出一个新的peerEpoch
    follower会向leader发送一个Leader.FOLLOWERINFO信息,包含自己的peerEpoch信息
    leader的LearnerHandler会获取到上述peerEpoch信息,leader从中选出一个最大的peerEpoch,然后加1作为新的peerEpoch。
    然后leader的所有LearnerHandler会向各自的follower发送一个Leader.LEADERINFO信息,包含上述新的peerEpoch
    follower会使用上述peerEpoch来更新自己的peerEpoch,同时将自己的lastProcessedZxid发给leader
    leader的所有LearnerHandler会记录上述各自follower的lastProcessedZxid,然后根据这个lastProcessedZxid和leader的lastProcessedZxid之间的差异进行同步

  • 3 已经处理的事务议案的同步
    判断LearnerHandler中的lastProcessedZxid是否在minCommittedLog和maxCommittedLog之间

LearnerHandler中的lastProcessedZxid和leader的lastProcessedZxid一致,则说明已经保持同步了

如果lastProcessedZxid在minCommittedLog和maxCommittedLog之间
从lastProcessedZxid开始到maxCommittedLog结束的这部分议案,重新发送给该LearnerHandler对应的follower,同时发送对应议案的commit命令

上述可能存在一个问题:即lastProcessedZxid虽然在他们之间,但是并没有找到lastProcessedZxid对应的议案,即这个zxid是leader所没有的,此时的策略就是完全按照leader来同步,删除该follower这一部分的事务日志,然后重新发送这一部分的议案,并提交这些议案

如果lastProcessedZxid大于maxCommittedLog
则删除该follower大于部分的事务日志

如果lastProcessedZxid小于minCommittedLog
则直接采用快照的方式来恢复

  • 4 未处理的事务议案的同步
    LearnerHandler还会从leader的toBeApplied数据中将大于该LearnerHandler中的lastProcessedZxid的议案进行发送和提交(toBeApplied是已经被确认为提交的)
    LearnerHandler还会从leader的outstandingProposals中大于该LearnerHandler中的lastProcessedZxid的议案进行发送,但是不提交(outstandingProposals是还没被被确认为提交的)

  • 5 将LearnerHandler加入到正式follower列表中
    意味着该LearnerHandler正式接受请求。即此时leader可能正在处理客户端请求,leader针对该请求发出一个议案,然后对该正式follower列表才会进行执行发送工作。这里有一个地方就是:
    上述我们在比较lastProcessedZxid和minCommittedLog和maxCommittedLog差异的时候,必须要获取leader内存数据的读锁,即在此期间不能执行修改操作,当欠缺的数据包已经补上之后(先放置在一个队列中,异步发送),才能加入到正式的follower列表,否则就会出现顺序错乱的问题
    同时也说明了,一旦一个follower在和leader进行同步的过程(这个同步过程仅仅是确认要发送的议案,先放置到队列中即可等待异步发送,并不是说必须要发送过去),该leader是暂时阻塞一切写操作的。
    对于快照方式的同步,则是直接同步写入的,写入期间对数据的改动会放在上述队列中的,然后当同步写入完成之后,再启动对该队列的异步写入。
    上述的要理解的关键点就是:既要不能漏掉,又要保证顺序

  • 6 LearnerHandler发送Leader.NEWLEADER以及Leader.UPTODATE命令
    该命令是在同步结束之后发的,follower收到该命令之后会执行一次版本快照等初始化操作,如果收到该命令的ACK则说明follower都已经完成同步了并完成了初始化
    leader开始进入心跳检测过程,不断向follower发送心跳命令,不断检是否有过半机器进行了心跳回复,如果没有过半,则执行关闭操作,开始进入leader选举状态
    LearnerHandler向对应的follower发送Leader.UPTODATE,follower接收到之后,开始和leader进入Broadcast处理过程

2 特殊情况的注意点

  • 2.1 事务日志和快照日志的持久化和恢复
    先来看看持久化过程:
    Broadcast过程的持久化
    leader针对每次事务请求都会生成一个议案,然后向所有的follower发送该议案
    follower接收到该议案后,所做的操作就是将该议案记录到事务日志中,每当记满100000个(默认),则事务日志执行flush操作,同时开启一个新的文件来记录事务日志
    同时会执行内存树的快照,snapshot.[lastProcessedZxid]作为文件名创建一个新文件,快照内容保存到该文件中

  • leader shutdown过程的持久化
    一旦leader过半的心跳检测失败,则执行shutdown方法,在该shutdown中会对事务日志进行flush操作

再来说说恢复:

  • 事务快照的恢复
    第一:会在事务快照文件目录下找到最近的100个快照文件,并排序,最新的在前
    第二:对上述快照文件依次进行恢复和验证,一旦验证成功则退出,否则利用下一个快照文件进行恢复。恢复完成更新最新的lastProcessedZxid

  • 事务日志的恢复
    第一:从事务日志文件目录下找到zxid大于等于上述lastProcessedZxid的事务日志
    第二:然后对上述事务日志进行遍历,应用到ZooKeeper的内存树中,同时更新lastProcessedZxid
    第三:同时将上述事务日志存储到committedLog中,并更新maxCommittedLog、minCommittedLog

由此我们可以看到,在初始化恢复的时候,是会将所有最新的事务日志作为已经commit的事务来处理的

也就是说这里面可能会有部分事务日志还没真实提交,而这里全部当做已提交来处理。这个处理简单粗暴了一些,而raft对老数据的恢复则控制的更加严谨一些。

2.2 follower挂了之后又重启的恢复过程
一旦leader挂了,上述leader的2个集合

  • ConcurrentMap outstandingProposals
    ConcurrentLinkedQueue toBeApplied

就无效了。他们并不在leader恢复的时候起作用,而是在系统正常执行,而某个follower挂了又恢复的时候起作用。

我们可以看到在上述2.3的恢复过程中,会首先进行快照日志和事务日志的恢复,然后再补充leader的上述2个数据中的内容。

2.3 同步follower失败的情况
目前leader和follower之间的同步是通过BIO方式来进行的,一旦该链路出现异常则会关闭该链路,重新与leader建立连接,重新同步最新的数据

2.4 对client端是否一致
客户端收到OK回复,会不会丢失数据?
客户端没有收到OK回复,会不会多存储数据?
客户端如果收到OK回复,说明已经过半复制了,则在leader选举中肯定会包含该请求对应的事务日志,则不会丢失该数据

客户端连接的leader或者follower挂了,客户端没有收到OK回复,目前是可能丢失也可能没丢失,因为服务器端的处理也很简单粗暴,对于未来leader上的事务日志都会当做提交来处理的,即都会被应用到内存树中。

你可能感兴趣的:(分布式)