本篇文章将会以redis集群为例,分享在主从集群中会导致数据丢失的一个问题:BrainSplit-集群脑裂
1.什么是集群脑裂
所谓的脑裂,就是指在主从集群中,同时有两个主节点,它们都能接收写请求。而脑裂最直接的影响,就是客户端不知道应该往哪个主节点写入数据,结果就是不同的客户端会往不同的主节点上写入数据。而且,严重的话,脑裂会进一步导致数据丢失。
2.数据丢失的排查
对于排查主从集群中数据丢失的问题,通常来说我们会考虑以下两个原因:
(1) 数据同步未完成
最常见的原因就是主库的数据还没有同步到从库,结果主库发生了故障,等从库升级为主库后,未同步的数据就丢失了。
我们可以根据以下方法来排查:
比对主从库上的复制进度差值来进行判断,也就是计算 master_repl_offset和 slave_repl_offset 的差值。
如果从节点的偏移量(slave_repl_offset)小于主节点的偏移量(master_repl_offset),那么我们可以认定数据丢失是由于数据同步未完成导致的;如果从节点的偏移量(slave_repl_offset)等于主节点的偏移量(master_repl_offset),那么我们有理由怀疑很可能是由于原主库假故障导致的集群脑裂造成的。
为了直观表示这一关系,我用以下的伪代码形式来描述:
if(slave_repl_offset < master_repl_offset){
//认定数据丢失是由数据同步未完成导致的。
}else if(slave_repl_offset == master_repl_offset){
//集群脑裂
}
(2) 集群脑裂
在哨兵机制将主库判断为客观下线后,哨兵开始执行切换操作,客户端与新主库通信,但是切换过程中,客户端仍然和原主库通信,这就表明原主库并没有真的发生故障。正因为原主库并没有真的发生故障,我们在客户端操作日志中就看到了和原主库的通信记录。等到从库被升级为新主库后,主从集群里就有两个主库了。
主库为什么会出现假故障?
1.和主库部署在同一台服务器上的其他程序临时占用了大量资源(例如 CPU 资源),导致主库资源使用受限,短时间内无法响应心跳。其它程序不再使用资源时,主库又恢复正常。
2.主库自身遇到了阻塞的情况,例如,处理 bigkey 或是发生内存 swap,短时间内无法响应心跳,等主库阻塞解除后,又恢复正常的请求处理了。
接下来我们来讨论一下__bigkey删除导致的阻塞:
删除操作的本质是要释放键值对占用的内存空间。释放内存只是第一步,为了更高效地管理内存空间,在应用程序释放内存时,操作系统需要把释放掉的内存块插入一个空闲内存块的链表,以便后续进行管理和再分配。这个过程本身需要一定时间,而且会阻塞当前释放内存的应用程序,所以,如果一下子释放了大量内存,空闲内存块链表操作时间就会增加,相应地就会造成 Redis 主线程的阻塞。
3.脑裂是如何导致数据丢失的
当主库出现假故障时,哨兵将主库判断为客观下线,从库升级为新的主库,但是当主库恢复后,客户端仍然可以给恢复后的主库中写入新数据,此时从库已经升级为主库了
4.脑裂问题如何解决
对于redis的主从集群来说,修改配置项就可以了:
min-slaves-to-write:这个配置项设置了主库能进行数据同步的最少从库数量;
min-slaves-max-lag:这个配置项设置了主从库间进行数据复制时,从库给主库发送ACK 消息的最大延迟(以秒为单位)。
这两个配置项组合后的要求是:主库连接的从库中至少有 N 个从库,和主库进行数据复制时的 ACK 消息延迟不能超过 T 秒,否则,主库就不会再接收客户端的请求,即使原主库是假故障,它在假故障期间也无法响应哨兵心跳,也不能和从库进行同步,自然也就无法和从库进行 ACK 确认了。
这样一来,_min-slaves-to-write _和 _min-slaves-max-lag _的组合要求就无法得到满足,原主库就会被限制接收客户端请求,客户端也就不能在原主库中写入新数据了。