1482 字 | 4 分钟
manual failover实现
使用方法
主动failover是通过redis命令实现的, 命令格式为CLUSTER FAILOVER [FORCE|TAKEOVER]
其条件要求越来越低,cluster failover要求追加offset
, 然后才能启动选举, force
命令直接开启选举,而takeover
命令直接选举成功开始替换角色。
if(takeover) {
/* 不选举 直接改epoch 并修改元数据 */
clusterBumpConfigEpochWithoutConsensus();
clusterFailoverReplaceYourMaster();
} else if(force) {
/* 不与 master 沟通,主节点也不会阻塞其客户端,需要经过选举 */
setMfStart();
} else {
/* 与 master 沟通,需要经过选举 */
clusterSendMFStart(_myself->getMaster());
}
执行方法: 在slave节点执行命令cluster failover
, 则slave会替代它的master
社区版实现流程如下
cluster failover 流程如下图所示, [FORCE|TAKEOVER]
模式省去了大部分的流程。
流程说明如下:
- 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
,这里slave
和master
的_mfend
差一个gossip信息发送和接收的时间, 同时设置_mfSlave 为sender 节点(锁定搞事情的 slave) master 的cron定期处理流程中,看到自己的_mfend不为0,就会给
_mfSlave
发送PING
信息, 这个时候会带上CLUSTERMSG_FLAG0_PAUSED
标记。if (cstate->getMyselfNode()->nodeIsMaster() &&
cstate->getMfEnd()) {
_mflags |= CLUSTERMSG_FLAG0_PAUSED;
}
- 在master node手动
slave 追加并选举
搞事情的
slave
收到自己master
的回包后,发现是带有CLUSTERMSG_FLAG0_PAUSED
标记的gossip
消息,就会将_mfMasterOffset
设置成gossip中的_replOffset
, 也就是树立一个追赶的目标/* If we are a slave performing a manual failover and our master
* sent its offset while already paused, populate the MF state. */
if (msg.getMflags() & CLUSTERMSG_FLAG0_PAUSED &&
setMfMasterOffsetIfNecessary(sender)) {
_mfMasterOffset = sender->_replOffset;
}
接下来 由于主从同步数据仍然在继续,slave
的offset
一定会越来越接近master
, slave
需要不断去check
自己的offset
在clusterCron
函数里有clusterHandleManualFailover
的逻辑,做不断check
,逻辑如下:
_mfEnd
为 0,说明此时没有mf
发生,直接return_mfCanStart
非 0 值,表示现在可以此slave
可以发起选举了, 直接return_mfMasterOffset
为 0,说明现在还没有获得master
的复制偏移量, 直接return- 当
_mfMasterOffset
值等于replicationGetSlaveOffset
函数的返回值时, 把_mfCanStart
置为1, 然后return
另外使用带有force
选项的CLUSTER FAILOVER
命令,直接就会把_mfCanStart
置为 1
failover 选举
在把_mfCanStart
置为 1 后, 在clusterCron
函数里有clusterHandleSlaveFailover
的逻辑会周期判断这个slave
是不是有参与选举的条件(manual_failover值>0
):
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
的过程,这里master
和slave
都需要计算offset
这里为了保证slave
数据的准确性,计算offset的过程Tendis存储版
会遍历所有kvstore
,并将所有kvstore
的max binlogid
累加在一起. 主从建立连接时一定是保证kvstore
个数相等,且max binlogid
代表着阻塞期间master最终会存储数据的数据量(如果取低水位,可能slave提前追上的,但实际上master还有数据没同步过去)
- master阻塞逻辑实现
阻塞的目的是为了让master停止offset
的增长,不接受新的请求.这里是通过给master
负责的slot
加Chunk
的S
锁实现的。
加chunk锁有两点好处:
- 如果直接加对
db
加X
锁,虽然也可以阻塞请求,但是master
发送binlog
同步同样要获取db
锁,会发生锁冲突,导致slave
永远接受不到新的binlog
,导致追offset
失败 - chunk锁的id 是从
0..16383
总共16384个, 这里只需要加锁的是master负责的slot id
, 比如master负责1..1000slot , 那么加锁只需要加1..1000个chunk , 更加高效
加锁算法如下:
fun lock(time){
slots = getMyslots();
locklist = lockChunks(slots);
thread[this](locklist){
std::mutex lk
_cv.wait_for(lk, time)
}
}
加锁过程有几个注意要点:
- 需要先阻塞之后, 再设置
_mfend = now + cluster_MF_timeout
,也就是说加锁的过程要同步,否则可能mfend
设置后,slave
已经开始追offset
,而这个时候没加锁,master
的offset
可能依然在增加,那么slave
追加的offset
就可能是错误的 slave
追加成功后,进行选举,选举成功后切换master
并广播自己的信息,master
这个时候可能没有收到这个信息,那么等10s
后这个master
解锁了依然会响应请求,这里为了安全起见可以增加master
阻塞时间