笔记1:读操作包括两种,readIndex和serilizable,readIndex指一致性读,一旦a读到了数据x,那么a及a以后的数据都能读到x,readIndex读会先确认本leader是不是有效地leader,如果有效则记录此刻的commiteIndex作为confirmIndex,等到applyIndex>confirmIndex时就可以进行serilizable读了,而serilizableRead就是副本读,直接读leader的数据。
笔记2:a、b、c三个readIndex读请求先后到达etcd,他们对应的confirmIndex分别为xa,xb,xc,且xa<=xb<=xc,applyIndex>xc时,会通过close(ch)来一次性唤醒三个a、b、c这三个请求,但是使用close(chan)的方式唤醒时,这三个请求被唤醒时的顺序是随机的,close(ch)只是一个唤醒操作,唤醒以后读请求开始执行,最终会来到range,range函数开始执行的时候会先把请求包装到一个读事务中,这个读事务会保存读事务创建时etcdserver的currentRev即etcdserver此刻的最大版本号,也就是说readIndex是读最新的数据,虽然请求对应的confirmIndex有大小有先后,但是这只是一个要求,一旦满足要求就读最新的数据是最新的数据,读事务采用的是concurrent模式,也就是加读锁-复制-解读锁-执行读取,也就是采用读写间隔模式,假设c先唤醒,然后c加锁,然后读到的版本号为k,然后解读锁,然后正好此时另一个goroute加写锁提交解写锁,因为版本号是递增的,所以此时etcd的最新版本号肯定大于k的,假设为k+z,然后a请求开始执行,加读锁-读取版本号为k+z,然后解读锁,假设c读取key=k1这个key,然后a也读取key=k1这个key,版本号k+z必定大于k,可以肯定的是如果c读到了版本号为k的数据,那么在他后面的a也必定能读到版本k的数据如果有更新的数据,那么就是读更新的数据,即一个数据已经读到,那么他后面的所有请求都能读到这个数据后者这个数据的更新的值
!!!serilizableRead最终也是通过事务来实现的,也就是说etcd不管是读还是写最终都是通过事务来执行
etcd raft range:etcd不管是读还是写最终都是通过事务来执行,如果没有事务,就会包装一个事务,都分为两步,第一步是raft流程,第二步是执行事务,raft流程中如果是写操作则需要写同步日志,而如果是readIndex读则不是同步日志,而是同步状态,第二步执行事务时,如果是读就封装读事务,如果是写就是写事务
v3_server.EtcdServer.Range #就两步:1:先阻塞,直到其他线程通知他可以读;2:去数据库读取最新数据
if !serilizable{ #serilizable表示直接读leader,!serilizable表示ReadIndex即线性一致性读
#ReadIndex读就是先等待,然后直到满足readIndex读条件之后再进行serilizable读
#具体步骤就是:1:就是leader首先确认自己此刻是不是leader,
#因为有可能网络分区等原因导致leader实际不是leader
#2:如果是,那么当前leader此刻的commitedIndex就是此读请求对应的ReadIndex
#当本节点的 applyIndex>=readIndex时这个readIndex读请求就可以执行serilizable读了
v3_server.EtcdServer.linearizableReadNotify #执行等待,直到appliedIndex>=ReadIndex后才去数据库读数据
#这个函数的逻辑很简单:
#写readWaitc这个chan来通知另一个goroute有人要readIndex读
#通知完后本线程就等待readNotifier这个chan即等待其他goroute通知
#当另一个goroute准备好后就会写readNotifier这个chan来唤醒当前请求
#goroute是很轻量的,所以直接阻塞就行,不停的来请求,不停的创新的goroute就行
nc := s.readNotifier #获取通知chan
#当readIndex准备好的时候其他goroute就会写这个notifyChan来通知本goroute
select:
case s.readwaitc <- struct{}{} #发消息到readwatic chan 来通知linearizableReadLoop函数有人需要ReadIndex
#readWaitC的容量大小为1
deafult: #如果readWaitc满了,导致写失败,那么就直接跳过此步,继续往下走
#也就是说写readWaitC是一个非阻塞操作,不管有没有写成功,都会继续往下走
select
case <-nc.c: #通知完readWaitc后当前goroute就阻塞在这个readNotifier直到被通知
---------------------------------------->
another thread 1: #当过半节点都承认leader节点有效地时候,
#thread1 会通过写chan来通知上层程序leader当前是有效的,即leader的commitedIndex是有效地
#后续上层程序只需要等待appliedIndex>=confirmIndex即可
#confirmIndex即请求到来时leader的commitedIndex
etcdserver.EtcdServer.linearizableReadLoop #一个死循环,获取此刻最新的commitedIndex,通过chan接受上层发来的ReadIndex请求,
#然后也通过chan把处理结果返回给发请求的线程
for: #就是一个死循环,监听某个chan,如果收到上层发来请求就唤醒,
#然后创建一个chan,发给其他线程,然后等待其他线程发回结果
idutil.Generator.Next #为本轮即将到来的请求先提前生成一个唯一reqid,后续用来检索
select:
case <-leaderChangedNotifier: #如果等待期间leader变了
continue #则放弃本轮循环,直接开始新的一轮循环
case <-s.readwaitc: #从readWaitc收到其他线程发来的ReadIndex的请求。一次for循环处理一个读请求
case <-s.stopping: #如果etcdserver停止了,那么退出循环结束
return
nextnr := newNotifier() #创建一个新的notify
s.readMu.Lock()
nr := s.readNotifier #获取旧的notify
s.readNotifier = nextnr #用新的notify替换掉旧的notify,
#本轮我们会处理旧notify,并且新请求会挂在新notify下面
s.readMu.Unlock() #!!!这里的逻辑是这样的:
#!!!我们用a代表linearizableReadNotify,b代表linearizableReadLoop
#!!!用rc代表readWaitC,用nc代表notifier
#!!!所以流程就简化为a收到客户端发来的请求req,然后写rc通知b,然后在nc上阻塞
#!!!然后b收到通知后就处理请求,处理完后唤醒nc上等待的所有a(一个req对应一个a)
#!!!因为b是串行处理即从rc取一个请求然后处理,处理完后才会从rc读下一个请求
#!!!而且a可能同时收到很多请求,假设有req1,req2,...,reqK,...,reqN
#!!!再加上rc的容量是1,所以会导致这样一种情况:
#!!!a收到reqK,然后发给b,b从rc取出reqK,然后a写reqK+1到rc会成功,
#!!!b然后处理reqK,此时a仍然不停的收到新的请求
#!!!这样就会导致reqK+1之后的请求都会写rc失败,但是a即使写rc失败
#!!!也会阻塞在nc上,当a收到请求reqN时,此时b刚好处理完,开始下一次循环
#!!!b会从rc读取reqK+1,然后rc空了,a就会写reqN成功
#!!!当b处理完reqK+1的时候下一个处理的请求就是reqN了
#!!!也就是说当b处理完reqN的时候不能仅仅通知reqN对应的请求
#!!!还必须唤醒所有在reqK+2到reqN之间的所有阻塞在nc上的请求
#!!!所以解决思路就是:用n1、n2两个nc,并且交替处理。
#!!!就是说处理n1的时候,所有后续到来的请求都阻塞在n2上
#!!!然后n1处理完后处理n2,然后在处理n2的时候,就让后续到来的请求都阻塞在n1上
#!!!这样我们处理完n1的时候直接n1.notifyAll就能唤醒所有之前阻塞在n1上的请求
#!!!同理:处理完n2的时候直接n2.notifyAll就能唤醒所有之前阻塞在n2上的请求
#!!!这个解决思路底层依赖于这样一个原理:
#!!!一旦readIndex读请求reqN处理完了即达到可进行readIndex读的时候
#!!!reqN之前的所有readIndex读请求一定都满足readIndex读的条件
#!!!具体点说就是后到来的请求y的readIndex必定大于在他之前的请求x的readIndex
#!!!即当applyIndex>=readIndex_y的时候必定有applyIndex>=readIndex_x
v3_server.EtcdServer.requestCurrentIndex #获取此刻commitedIndex的值并保存到一个叫confirmIndex的变量中
#注意,一个notifer下面会挂一大串请求,但是他这里只需要请求一次就行
#因为请求等待的readIndex不会大于此刻的commitedIndex
#所以当applied>commitedIndex时表示所有readIndex<commitedIndex的
#读请求的一致性要求都可以满足
#从而他会一次性唤醒所有readIndex<此刻commmitedIndex的读请求
etcdserver.EtcdServer.sendReadIndex #获取最新commitedIndex,会同步等待,直到ok或者出错
raft.node.ReadIndex #通过向raft状态机发送一个MsgReadIndex消息来获取
#丢完消息后就返回,让他异步去处理
raft.node.step(pb.MsgReadIndex,reqid) #构造一个MsgReadIndex消息
#前面生成的reqid作为数据部分放在消息的data字段中,然后处理
raft.node.stepWithWaitOption
case n.recvc <- m #把前面构造的的MsgReadIndex消息发到recvc然后此处就返回了
#让其他goroute异步走一遍stepLeader或者stepFollower(根据节点角色决定)
#这个recvc就是专门收发其他节点发来的消息,当然也可以自己发给自己
#读取实践中有三种方式:1:Log(每次读也写一条日志)
#2:readIndex:就录一个commitedIndex,
#直到appliedIndex>=记录的commitedIndex
#3:直接从本地读,不经过leader
#log方式太慢了;readIndex还是需要一轮广播;直接本地读,不安全
for: #这是一个死循环,其他线程处理完MsgReadIndex消息后
#会通过填充readStateC chan来解除死循环
#这个for循环是上面那个requestCurrentIndex函数里的
#requestCurrentIndex函数会阻塞,
#直到node.run中把readState中的chan发给他来唤醒它
select
case rs := <-s.r.readStateC #阻塞在readStateC上,其他线程处理完MsgReadIndex消息后,
#其他线程会把处理结果写到这个readStateC中来跳出循环
#linearizableReadNotify这个死循环有三种结束等待的方式:
#1:超时或者error结束等待;2:readStateC;3:notifier
#一个notifier对象可能对应1批ReadIndex请求,
#只要这一批有一个请求完成了,
#那么他完成时会通知本批次所有请求都结束等待
return rs.ReadIndex #到达这里说明该请求已经被批准了,此处返回结果
case <-firstCommitInTermNotifier #收到了当前任期第一次提交发来的通知。
#即当客户端发来ReadIndex的时候本leader才刚获得leader资格
#在他的这个任期内集群还没有发生过commited事件,
#所以必须等待,假设旧leader提交到了x+3然后崩溃
#然后新leader当选,因为此时集群变了比如旧leader崩溃了,
#导致没有过半节点到达x+3,
#那么新leader就不能从x+3开始提交
#需要重新确定commited,这是一个不断尝试的过程,
#也就是说这是一个不断变化的过程,
#所以在新leader确定commited之前不能读取,
#所以新leader第一次提交之前到来的请求
#都需要在新leader第一次提交之后重新尝试
#即重新丢一个MsgReadIndex到raft重做一遍
etcdserver.EtcdServer.sendReadIndex
time.Timer.Reset #重置定时器
case <-retryTimer.C:
etcdserver.EtcdServer.sendReadIndex
time.Timer.Reset
case <-leaderChangedNotifier #如果leader变了,则放弃所有读请求,并返回错误
return
case <-errorTimer.C #超时,返回错误
return
....后面的流程此处略,后面再补充....
---------------------------------------->
another thread 2: #node.recvc收到etcdserver发来的ReadIndex请求即发来的MsgReadIdnex消息
raft.node.run
for:
case m := <-n.recvc #etcdserver发来的MsgReadIndex消息
raft.raft.Step
//if role==leader: #如果当前节点角色是leader则走stepLeader
raft.stepLeader
case pb.MsgReadIndex #处理思想就是:走一遍heartbeat流程。
#如果heartbeat流程中有过半节点拥护当前节点,那么当前节点就是有效地leader
#那么此leader当前的commitedIndex就是此请求对应的ReadIndex
#即后面说的变量confirmIndex
#对MsgReadIndex消息的处理流程如下:
#1:用一个map acks保存所有节点对该ReadIndex的投票情况,map的key是节点id
#2:发送MsgHeartbeat消息给所有节点
#3:收到一个MsgHeartbeatResp时不但要标记该节点x是活跃的,
#还要同时令acks[x]=true即认为该节点是赞同当前leader和ReadIndex的
if !raft.raft.committedEntryInCurrentTerm #如果当前leader在任期内还没有提交过日志,
#那么就直接挂起这个ReadIndex,然后直接返回
#因为在处理完一个ReadIndex时会同时唤醒所有index
#在他之前的所有ReadIndex请求,
#所以这里可以安心挂起,因为后续的ReadIndex会唤醒它
#本文后面会解释
append(r.pendingReadIndexMessages) #挂起即把请求放到一个pending数组,然后直接返回,不管这个请求了
return
raft.sendMsgReadIndexResponse #发送heartbeat消息给所有peer节点
#两步:1:leader自己给自己投一票;2:发消息给follower
case ReadOnlySafe: #ReadOnlySafe表示ReadIndex读
#safe的含义是走一遍quorum投票,只有过半节点赞成才会执行
raft.readOnly.addRequest(r.raftLog.committed, m)
#保存此刻的commitedIndex以及本次请求
#!!!此刻的commitedIndex就是本次请求对应的readIndex
#MsgReadIndex消息的数据字段包含了本次ReadIndex的reqid
#当ReadIndex处理完毕后那么保存的这个commitedIndex值
#就是confirmIndex
ro.pendingReadIndex[s] = &readIndexStatus{index: index, req: m, acks: make(map[uint64]bool)}
#这里就是把req以及对应的投票信息保存到一个map中
#key=reqid,value=投票信息
ro.readIndexQueue = append(ro.readIndexQueue, s) #readIndexQueue保存了所有readindex读请求
#并且请求是按顺序保存的,所以当x满足readIndex条件时
#队列中所有在x之前的请求必定满足readIndex条件
raft.readOnly.recvAck(r.id, m.Entries[0].Data)
#消息的Data字段实际就是ReadIndex对应的reqid,
#r就代表本节点,这里就是当前节点默认是投自己一票
#!!!只有leader才会走到这里,follower会直接丢给leader
ro:=pendingReadIndex[reqid] #获取reqid对应的投票信息
ro.acks[id]=true #对于reqid对应的这个ReadIndex,leader肯定是表示支持的
#只有活跃节点才会放到这个map acks中,
#如果后续检测到这个map中有过半节点数
#那么就认为reqid对应的ReadIndex被批准了,
#就可以通过chan来通知上层可以去数据库读数据了
r.bcastHeartbeatWithCtx #广播heartbeat消息给所有peer节点
#follower节点对heartbeat消息的处理很简单,
#就简单返回本身的commitedIndex给leader
case ReadOnlyLeaseBased #ReadOnlyLeaseBased表示LeaseRead即副本读,即采用了租约
#当一个领导者被选举出来时,它会获得一个租约,
#这个租约保证了在一定的时间窗口内,
#集群可以基于这个领导者提供一致性视图来处理只读请求
#但是leader可能并不是真的leader,所以租约读是不安全的
//if role==follower: #如果当前节点是follower,则直接把请求丢给leader
stepFollower
switch:
case pb.MsgReadIndex:
m.To = r.lead
r.send(m)
---------------------------------------->
another thread 3: #thread 3处理MsgHeartbetaResp消息,即follower发回来的响应
#如果有过半节点承认reqid对应的ReadIndex就通知上层当前leader是有效的,
#reqid对应的ReadIndex请求可以结束等待了
raft.node.run
for:
case m := <-n.recvc #peer节点发来的MsgHeartBeatResp
raft.raft.Step
raft.stepLeader
case pb.MsgHeartbeatRes #对MsgHeartBeatResp的处理主要包括三步:
#1:标记该节点x是近期活跃的
#2:标记reqid对应的acks[x]=true
#3:如果过半,则写chan来通知上层可以结束对该reqid对应的ReadIndex的等待了
progress.RecentActive=true #1:标记该节点是近期活跃
if pr.Match < r.raftLog.lastIndex #follower节点会把自己的commitedIndex告知leader节点,
#此处发现follower节点落后了,所以发送MsgApp通知他追赶
raft.raft.sendAppend
raft.readOnly.recvAck(perrId,reqid) #2:标记reqid.acks[perrId]=true即该peer节点支持leader
quorum.JointConfig.VoteResult #3:计算投票结果,即看reqid对应的acks map中是否有过半数节点
#前面说过,acks map保存的是所有活跃的且承认当前节点是leader的节点
#如果有则通知上层linearizableReadLoop reqid对应的读请求可以结束等待了
#就是一个count,看是否过半
rss=raft.readOnly.advance(m.Index)#从等待队列移除所有index在m.Index之前的所有pendingRequest,
#会把满足要求的pendingRequest放到一个叫做rssd的数组中
#我们前面把reqid和一个commitedIndex(假设值为x)绑定在一起
#当x可以结束等待时,那些commitedIndex小于x的ReadIndex请求肯定可以结束等待了
raft.raft.responseToReadIndexReq #根据rss中的请求构造MsgReadIndexResp消息,
#这个MsgReadIndexResp消息中包含了reqid对应的ReadIndex值
#即当时的commitedIndex值
#如果消息来源是follower,则把MsgReadIndexResp消息发给follower,
#follower再把该req放到readState中(readState用来保存当前已经批准的的读请求)
#然后会把readState中的元素发到指定的r.readStateC,
#linearizableReadLoop 每次循环就是在等待这个readStateC
#我们通过前面的步骤已经确定了该req对应的读请求所等待的commitedIndex值,
#因为客户端如果请求的是follower节点,follower节点会把请求转发给follower,
#leader会把批准的ReadIndex值放到这个MsgReadIndexResp中(假设用变量confirmIndex表示),
#这样后续当follower节点发现本机appliedIndex>=confirmIndex时
#就可以遍历readState中的所有读请求,
#凡是req.confirmIndex<=appliedIndex的读请求都可以解除阻塞
#如果消息来源是leader自己,一样的把他加到leader节点自己的readState数组
#在node.run在下一次循环中会检测到readState不为空,然后就触发case rd<-r.Ready()
if req.From == None || req.From == r.id { #如果是自己发给自己的
r.readStates = append(r.readStates, ReadState{ #直接把ReadState丢到leader自己的readStates中
#后续会把readStates里的数据丢到readStatesC
Index: readIndex,
RequestCtx: req.Entries[0].Data,
})
return pb.Message{}
}
return pb.Message{ #如果是follower发来的ReadIndex读请求
Type: pb.MsgReadIndexResp, #那么就返回MsgReadIndexResp消息给follower
To: req.From,
Index: readIndex, #readIndex是req创建时的leader的commitedIndex
Entries: req.Entries,
}
if req.to!=None:
raft.raft.send(pb.MsgReadIndexResp) #把MsgReadIndexResp响应消息返回给follower
---------------------------------------->
another thread 4: #上面已经把批准的ReadIndex请求放到readState了,
#然后readState不为空会被node Ready()检测到
#然后raftnode.run会把readState最后一个元素发到指定的r.readStateC以激活下一步
#在他之前的必定满足要求,所以发送最后一个就行了
raft.raftNode.run
for:
select
case rd := <-r.Ready()
if len(rd.ReadStates) != 0 {
case r.readStateC <- rd.ReadStates[len(rd.ReadStates)-1]
#发送最新的readState到指定chan,激活相关线程,
#因为他是不断循环的,只要readState不为空,那么就会继续ready,
#继续处理,直到为空
---------------------------------------->
//这里又回到another thread 1 中的etcdserver.EtcdServer.linearizableReadLoop函数
etcdserver.EtcdServer.linearizableReadLoop
etcdserver.EtcdServer.linearizableReadLoop
...... #当requestCurrentIndex返回后,就可以获取此刻得appliedIndex
etcdserver.EtcdServer.getAppliedIndex #获取当前的appliedIndex
if appliedIndex<confirmIndex #如果还没有apply到confirmIndex
#即读请求到来时的有效commitedIndex值就继续等待
case <- wait.timelist.Wait(confirmIndex) #继续阻塞,直到etcdserver.EtcdServer.applyAll线程
#在完成一此apply操作后主动唤醒所有appliedIndex之前的读请求
#这个Wait会创建一个chan,
#applyAll唤醒它时调用close(ch)来填充这个chan,来结束阻塞
etcdserver.notifier.notify #当属于同一个notifier的一批请求中的某个被批准的时候
#会唤醒所有在等待这个oldnotifier的读请求
#即会唤醒linearizableReadNotify goroute,
#一个请求对应一个linearizableReadNotify goroute
#所以这里一次就会唤醒很多歌goroute
close(oldnotifier chan) #close(chan)会唤醒所有等待这个chan的线程
---------------------------------------->
//这里就回到linearizableReadNotify
v3_server.EtcdServer.Range
etcdserver.EtcdServer.doSerialize #serializeRead指直接从从bbolt数据库读取数据
#而ReadIndex读则相当于在serializeRead之前增加了一个wait操作,
#直到appliedIndex>=commitedIndex
#线性读要求读最新数据,这里就直接去数据库读了,
#doSerialize就是LeaseRead,
#当ReadIndex读请求被放行以后就执行LeaseRead
#这个serialize就是调用txn.Range来读取
#即serialize是一个读事务
apply.applierV3backend.Range
if txn == nil: #如果事务为空
txn = a.s.kv.Read(mvcc.ConcurrentReadTxMode) #则创建对应的conncurentReadTx事务对象
......
backend.readTx.buf.unsafeCopy #conncurrentReadTx会复制一份readTx的readBuf
#因为applierV3backend.Range会在txn中调用,即op= "Range"
#也可能直接调用,所以txn可能为空也可能不为空
...
rev=s.currentRev #本次读事务看到的版本号就是此刻etcd最新的版本号
defer txn.End()
metrics_txn.metricsTxnWrite.Range #doSerialize就是LeaseRead,当ReadIndex读请求被放行以后就执行LeaseRead
#这个Lease Read就是调用txn.Range来读取
kvstore_txn.storeTxnRead.Range
kvstore_txn.storeTxnRead.rangeKeys(tr.Rev())#读取revision版本不超过tr.Rev的key即只能读到事务开始前就完成的key
#笔记:apply之后只是把writeTxn请求丢给底层的batchTxBufferd就返回了
#然后readbuf此时是没有这些新数据的,也就是此时读还是只能看到旧版本的数据
#当batchTxBuffered提交以后就会更新readBuf并更新s.currentRev
#此后的事务就能看到最新的版本号以及从readBuf读到最新的数据了
revPairs=index.treeIndex.Revisions(key,end,atRev) #从treeIndex获取对应的revision
#key表示要查找的key范围的起点,end表示key范围的终点,
#atRev表示版本号,即不会读取atRev之后的版本的数据
#举个例子:
#key为a的数据有修改了三次,对应三个版本号:rev1=3 rev2=6 rev3=10
#假设事务开始时的rev=8即atRev=8
#那么就只会读取该key的rev小于8的最新的数据即rev=6
if end==nil: #end=nil表示本次只读取一个key
index.treeIndex.Get(key,atRev)
keyi := &keyIndex{key: key}
index.treeIndex.keyIndex(keyi) #从treeIndex中获取key对应的keyIndex结构,treeIndex是一棵b树
#etcd所有key的keyIndex都会放在内存,这也限制了etcd支持的数据集大小
key_index.keyIndex.Get #从key对应的keyIndex中获取数据对应的rev
key_index.keyIndex.findGeneration#先从keyIndex中获取generation
key_index.keyIndex.walk #一个generation中可能多次修改key,所以generation可能含有多个版本号
#这里就是选一个不超过且最靠近atRev的版本号,包括rev包括(main,sub)
#获取了(main,sub)后就唯一确定了bbolt中的一个数据
for revPairs: #遍历获取的所有rev对即(main,sub)对
revision.revToBytes(revpair, revBytes) #把(main,sub)转换成key,即bbolt也是kv,只不过key=main_sub
binary.BigEndian.PutUint64(bytes, uint64(rev.main))
bytes[8] = '_'
binary.BigEndian.PutUint64(bytes[9:], uint64(rev.sub))
readTx.baseReadTx.UnsafeRange #读取操作很简单:1:先尝试从readBUf读
tx_buffer.txReadBuffer.Range #这里就是baseReadTx.buf.Range即从readBuf读取
if int64(len(keys)) == limit: #如果从readBuf读到了指定数量的数据,那就直接返回,否则就要去读bbolt
return keys, vals
batch_tx.unsafeRange #readBuf中没有再去bbolt读取
#笔记:读过的数据不会丢到readBuf中,readBuf中保存的是最新commit的数据
stepFollower对leader发来的heartbeat消息的处理:
stepFollower:
case pb.MsgHeartbeat: #leader发来msgHeartbeat消息m
r.lead = m.From #m.From表示leader
raft.raft.handleHeartbeat
log.raftLog.commitTo(m.Commit) #follower尝试把自己的commitIndex提升到m.Commit
#m.Commit表示leader此刻的commitIndex
if l.committed < tocommit { #如果follower的commitIndex a <leader的commitIndex b
if l.lastIndex() < tocommit { #并且如果follower的最大日志索引还没有到达b
l.logger.Panicf( #则说明follower出了问题,可能是落后了
"tocommit(%d) is out of range [lastIndex(%d)]. #这里打印日志,然后直接杀掉follower
Was the raft log corrupted, truncated, or lost?", #估计会有recover重启follower吧
tocommit, l.lastIndex())
}
l.committed = tocommit #否则把follower.commitIndex设置为leader的commitIndex b
}
raft.raft.send(pb.Message{ #发回对leaderHeartbeat的响应
To: m.From,
Type: pb.MsgHeartbeatResp,
Context: m.Context})
case pb.MsgReadIndex: #如果当前节点是follower并且收到ReadIndex读请求
r.send(m) #那么就直接把请求转发给leader
case pb.MsgReadIndexResp: #如果收到leader发来的对readIndex读请求的响应
#注意:leader会等待readIndex读请求条件满足时
#才会发送响应MsgReadIndexResp给follower
r.readStates = append(r.readStates, #follower收到后就把对应的数据丢到readStates中
ReadState{ #然后就会激活follower上阻塞的readIndex读请求
Index: m.Index, #后面就是serilizable读了
RequestCtx: m.Entries[0].Data})