文章目录

  • Redis核心技术与实战
  • 实践篇
  • 33 | 脑裂:一次奇怪的数据丢失
  • 问题
  • 为什么会发生脑裂?
  • 为什么脑裂会导致数据丢失?
  • 如何应对脑裂问题?



Redis核心技术与实战

实践篇

33 | 脑裂:一次奇怪的数据丢失

问题

主从集群有 1 个主库、5 个从库和 3 个哨兵实例,在使用的过程中,发现客户端发送的一些数据丢失,直接影响到了业务层的数据可靠性。

所谓的脑裂,就是指在主从集群中,同时有两个主节点,它们都能接收写请求。

脑裂最直接的影响,就是客户端不知道应该往哪个主节点写入数据,结果就是不同的客户端会往不同的主节点上写入数据。而且,严重的话,脑裂会进一步导致数据丢失。

为什么会发生脑裂?

最初发现的问题是:在主从集群中,客户端发送的数据丢失。

疑问:为什么数据会丢失?

第一步:确认是不是数据同步出现了问题

在主从集群中发生数据丢失,最常见的原因就是主库的数据还没有同步到从库,结果主库发生了故障,等从库升级为主库后,未同步的数据就丢失了。

mysql 集群脑裂监控 数据库脑裂_脑裂

如果是这种情况的数据丢失,可以通过比对主从库上的复制进度差值来进行判断,也就是计算 master_repl_offset 和 slave_repl_offset 的差值。如果从库上的 slave_repl_offset 小于原主库的 master_repl_offset,那么,就可以认定数据丢失是由数据同步未完成导致的。

在部署主从集群时,监测主库上的 master_repl_offset,以及从库上的 slave_repl_offset。但是,当发现数据丢失后,检查新主库升级前的 slave_repl_offset 以及原主库的 master_repl_offset,发现二者一致,也就是说,升级为新主库的从库,在升级时已经和原主库的数据保持一致。那么,为什么还会出现客户端发送的数据丢失呢?

第二步:排查客户端的操作日志,发现脑裂现象

排查客户端的操作日志,发现在主从切换后的一段时间内,有一个客户端仍然在和原主库通信,并没有和升级的新主库进行交互,这就相当于主从集群中同时有了两个主库。

不同客户端给两个主库发送数据写操作,按道理来说,只会导致新数据会分布在不同的主库上,并不会造成数据丢失。那么,为什么数据仍然会丢失?

第三步:发现是原主库假故障导致的脑裂

当主从切换发生时,一定是有超过预设数量(quorum 配置项)的哨兵实例和主库的心跳超时,才会把主库判断为客观下线,然后,哨兵开始执行切换操作。哨兵切换完成后,客户端会和新主库进行通信,发送请求操作。

在切换过程中,既然客户端仍然和原主库通信,这就表明,原主库并没有真的发生故障(例如主库进程挂掉、CPU 利用率突增)。主库是由于某些原因无法处理请求,也没有响应哨兵的心跳,才被哨兵错误地判断为客观下线。结果,在被判断下线之后,原主库又重新开始处理请求,而此时,哨兵还没有完成主从切换,客户端仍然可以和原主库通信,客户端发送的写操作就会在原主库上写入数据。

mysql 集群脑裂监控 数据库脑裂_redis_02

为什么脑裂会导致数据丢失?

主从切换后,从库一旦升级为新主库,哨兵就会让原主库执行 slaveof (5.0 后是 replicaof)命令,和新主库重新进行全量同步。而在全量同步执行的最后阶段,原主库需要清空本地的数据,加载新主库发送的 RDB 文件,最终导致原主库在主从切换期间保存的新写数据丢失

mysql 集群脑裂监控 数据库脑裂_redis_03

问题发生过程和原因:在主从切换的过程中,如果原主库只是“假故障”,它会触发哨兵启动主从切换,一旦等它从假故障中恢复后,又开始处理请求,这样就会和新主库同时存在的情况,形成脑裂。等到哨兵让原主库和新主库做全量同步后,原主库在切换期间保存的数据就丢失。

如何应对脑裂问题?

Redis 提供了两个配置项来限制主库的请求处理,分别是 min-slaves-to-write 和 min-slaves-max-lag。

  • min-slaves-to-write:这个配置项设置了主库能够接收写请求的最少从库数量;
  • min-slaves-max-lag:这个配置项设置了主从库间进行数据复制时,从库给主库发送 ACK 消息的最大延迟(以秒为单位)。

可以把 min-slaves-to-write 和 min-slaves-max-lag 这两个配置项搭配起来使用,分别给它们设置一定的阈值,假设为 N 和 T。这两个配置项组合后的要求是,主库连接的从库中至少有 N 个从库,和主库进行数据复制时的 ACK 消息延迟不能超过 T 秒,否则,主库就不会再接收客户端的请求。

即使原主库是假故障,它在假故障期间也无法响应哨兵心跳,也不能和从库进行同步,自然也就无法和从库进行 ACK 确认。min-slaves-to-write 和 min-slaves-max-lag 的组合要求就无法得到满足,原主库就会被限制接收客户端请求,客户端也就不能在原主库中写入新数据。

等到新主库上线时,就只有新主库能接收和处理客户端请求,此时,新写的数据会被直接写到新主库中。而原主库会被哨兵降为从库,即使它的数据被清空,也不会有新数据丢失。