etcd-v3.5release-(3)-readIndexRead

笔记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})
相关推荐
python机器学习建模3 小时前
科研论文必须要了解的25个学术网站
数据库
J.P.August4 小时前
Oracle DataGuard启动与关闭顺序
数据库·oracle
尚雷55804 小时前
Oracle 与 达梦 数据库 对比
数据库·oracle·达梦数据库
小猿姐6 小时前
Ape-DTS:开源 DTS 工具,助力自建 MySQL、PostgreSQL 迁移上云
数据库·mysql·postgresql·开源
百香果果ccc6 小时前
MySQL中的单行函数和聚合函数
数据库·mysql
摸摸陌陌6 小时前
Redis快速入门
数据库·redis·缓存
Elastic 中国社区官方博客6 小时前
Elasticsearch Serverless 中的数据流自动分片
大数据·数据库·elasticsearch·搜索引擎·serverless·时序数据库
Minyy116 小时前
牛客网刷题SQL--高级查询
数据库·sql
秋意钟6 小时前
MySQL基本架构
数据库·mysql·架构
朱小勇本勇7 小时前
Qt实现控件拖曳
开发语言·数据库·qt