1482 字 | 4 分钟

manual failover实现

使用方法

主动failover是通过redis命令实现的, 命令格式为CLUSTER FAILOVER [FORCE|TAKEOVER]

其条件要求越来越低,cluster failover要求追加offset, 然后才能启动选举, force命令直接开启选举,而takeover命令直接选举成功开始替换角色。

  1. if(takeover) {
  2. /* 不选举 直接改epoch 并修改元数据 */
  3. clusterBumpConfigEpochWithoutConsensus();
  4. clusterFailoverReplaceYourMaster();
  5. } else if(force) {
  6. /* 不与 master 沟通,主节点也不会阻塞其客户端,需要经过选举 */
  7. setMfStart();
  8. } else {
  9. /* 与 master 沟通,需要经过选举 */
  10. clusterSendMFStart(_myself->getMaster());
  11. }

执行方法: 在slave节点执行命令cluster failover, 则slave会替代它的master

社区版实现流程如下

cluster failover 流程如下图所示, [FORCE|TAKEOVER]模式省去了大部分的流程。

manual_failover

流程说明如下:

  • slave处理命令

slave收到命令后 处理逻辑为clusterSendMFStart函数。该函数主要逻辑就是发送向要做failover的slave的master发送CLUSTERMSG_TYPE_MFSTART类型的gossip消息。 这里要设置slave的_mfend = now + cluster_MF_timeout,当cluster_MF_timeout时间后,slave会放弃failover

  • master处理命令

    • 在master node手动MFSTART类型的gossip消息的消息后,会阻塞客户端2*cluster_MF_timeout时间, 然后设置自己的_mfend = now + cluster_MF_timeout,这里slavemaster_mfend差一个gossip信息发送和接收的时间, 同时设置_mfSlave 为sender 节点(锁定搞事情的 slave)
    • master 的cron定期处理流程中,看到自己的_mfend不为0,就会给_mfSlave发送PING信息, 这个时候会带上CLUSTERMSG_FLAG0_PAUSED标记。

      1. if (cstate->getMyselfNode()->nodeIsMaster() &&
      2. cstate->getMfEnd()) {
      3. _mflags |= CLUSTERMSG_FLAG0_PAUSED;
      4. }
  • slave 追加并选举

    搞事情的slave收到自己master的回包后,发现是带有CLUSTERMSG_FLAG0_PAUSED标记的gossip消息,就会将_mfMasterOffset设置成gossip中的_replOffset, 也就是树立一个追赶的目标

    1. /* If we are a slave performing a manual failover and our master
    2. * sent its offset while already paused, populate the MF state. */
    3. if (msg.getMflags() & CLUSTERMSG_FLAG0_PAUSED &&
    4. setMfMasterOffsetIfNecessary(sender)) {
    5. _mfMasterOffset = sender->_replOffset;
    6. }

接下来 由于主从同步数据仍然在继续,slaveoffset一定会越来越接近master, slave需要不断去check自己的offsetclusterCron函数里有clusterHandleManualFailover的逻辑,做不断check,逻辑如下:

  1. _mfEnd 为 0,说明此时没有mf发生,直接return
  2. _mfCanStart非 0 值,表示现在可以此slave可以发起选举了, 直接return
  3. _mfMasterOffset为 0,说明现在还没有获得master的复制偏移量, 直接return
  4. _mfMasterOffset值等于replicationGetSlaveOffset函数的返回值时, 把_mfCanStart置为1, 然后return

另外使用带有force选项的CLUSTER FAILOVER命令,直接就会把_mfCanStart置为 1

failover 选举

在把_mfCanStart置为 1 后, 在clusterCron函数里有clusterHandleSlaveFailover的逻辑会周期判断这个slave是不是有参与选举的条件(manual_failover值>0):

  1. int manual_failover = _mfEnd != 0 && _mfCanStart;

这里 两个值都不为0 就可以进去选举的逻辑,最终一定会选举成功,替换master

算法的核心逻辑如下:

(1) 通过_mfEnd标志 来 控制 failover 的周期,

初始化 _mfEnd 为0 , 当slave收到命令时 设置 _mfEnd = now + cluster_MF_timeout(默认5秒)

在 node 的cron定时处理逻辑中, 会check 当前时间有没有到_mfEnd, 到了就重置_mfEnd (2) 通过master 合理阻塞 来保证 master 和slave 在failover 的时候数据准确性

正常流程的cluster failover 需要经过一个追加offset的过程, 而不是直接进行master slave角色切换, 这是由于master 的数据 还没有完全同步给slave , 这个时候slave 切换的话上面可能有丢失的数据。

在manual failover 实现中master 比 slave 晚一点设置_mfEnd, 并需要阻塞客户端请求 , 这时候 slave 会比较自己和master的复制偏移量offset, 当offset 追上时 则数据准确, 可以开始切换成master

Tendis存储版实现

  • 计算offset实现

要达到_mfMasterOffset时,slave才会发起选举,即默认选项有一个追平repl offset的过程,这里masterslave都需要计算offset

这里为了保证slave数据的准确性,计算offset的过程Tendis存储版会遍历所有kvstore,并将所有kvstoremax binlogid累加在一起. 主从建立连接时一定是保证kvstore个数相等,且max binlogid代表着阻塞期间master最终会存储数据的数据量(如果取低水位,可能slave提前追上的,但实际上master还有数据没同步过去)

  • master阻塞逻辑实现

阻塞的目的是为了让master停止offset的增长,不接受新的请求.这里是通过给master负责的slotChunkS锁实现的。

加chunk锁有两点好处:

  1. 如果直接加对dbX锁,虽然也可以阻塞请求,但是master发送binlog同步同样要获取db锁,会发生锁冲突,导致slave永远接受不到新的binlog,导致追offset失败
  2. chunk锁的id 是从0..16383总共16384个, 这里只需要加锁的是master负责的slot id, 比如master负责1..1000slot , 那么加锁只需要加1..1000个chunk , 更加高效

加锁算法如下:

  1. fun lock(time){
  2. slots = getMyslots();
  3. locklist = lockChunks(slots);
  4. thread[this](locklist){
  5. std::mutex lk
  6. _cv.wait_for(lk, time)
  7. }
  8. }

加锁过程有几个注意要点:

  • 需要先阻塞之后, 再设置_mfend = now + cluster_MF_timeout,也就是说加锁的过程要同步,否则可能mfend设置后,slave已经开始追offset,而这个时候没加锁,masteroffset可能依然在增加,那么slave追加的offset就可能是错误的
  • slave追加成功后,进行选举,选举成功后切换master并广播自己的信息,master这个时候可能没有收到这个信息,那么等10s后这个master解锁了依然会响应请求,这里为了安全起见可以增加master阻塞时间