Redis脑裂导致的数据丢失问题
一般的数据丢失
在主从集群中发生数据丢失,最常见的原因就是主库的数据还没有同步到从库,结果主库发生了故障,等从库升级为主库后,未同步的数据就丢失了。
如果是这种情况,我们可以通过比对主从库上的复制进度差值来进行判断,也就是计算 master_repl_offset 和 slave_repl_offset 的差值。
脑裂导致的数据丢失
所谓的脑裂,就是指在主从集群中,同时有两个主节点,它们都能接收写请求。
主从切换后,从库一旦升级为新主库,哨兵就会让原主库执行 slave of 命令,和新主库重新进行全量同步。而在全量同步执行的最后阶段,原主库需要清空本地的数据,加载新主库发送的 RDB 文件,这样一来,原主库在主从切换期间保存的新写数据就丢失了。
上述情况的发生,主要是由于误判造成的原主库假死,然后在执行主从切换的过程中,原主库还可以进行数据的处理,但是进行全量同步时这些数据并不会一起进行同步,而是会保存在缓存中,等到同步完发送,但是主从切换在执行的最后阶段会清空主库原来的数据,导致全量同步过程中新写入的数据丢失。
而误判的原因可能是原主库在执行cpu密集型的操作,导致无法相应哨兵的心跳检测,导致哨兵误判原主库故障。
如何应对上述情况?
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 秒,否则,主库就不会再接收客户端的请求了。
这样设置后,假如主库假死,如果N设置为1,T设置为5,如果从库和主库进行数据复制的ACK时间超过5s,那么主库就不能够处理客户端的请求。
上述设置是否可以避免脑裂?
考虑如下问题:
假设我们将 min-slaves-to-write 设置为 1,min-slaves-max-lag 设置为 15s,哨兵的 down-after-milliseconds 设置为 10s,哨兵主从切换需要 5s。主库因为某些原因卡住了 12s,此时,还会发生脑裂吗?主从切换完成后,数据会丢失吗?
主库卡住 12s,达到了哨兵设定的切换阈值,所以哨兵会触发主从切换。但哨兵切换的时间是 5s,也就是说哨兵还未切换完成,主库就会从阻塞状态中恢复回来,而且也没有触发 min-slaves-max-lag 阈值,所以主库在哨兵切换剩下的 3s 内,依旧可以接收客户端的写操作,如果这些写操作还未同步到从库,哨兵就把从库提升为主库了,那么此时也会出现脑裂的情况,之后旧主库降级为从库,重新同步新主库的数据,新主库也会发生数据丢失。
脑裂产生问题的本质原因是,Redis 主从集群内部没有通过共识算法,来维护多个节点数据的强一致性。它不像 Zookeeper 那样,每次写请求必须大多数节点写成功后才认为成功。当脑裂发生时,Zookeeper 主节点被孤立,此时无法写入大多数节点,写请求会直接返回失败,因此它可以保证集群数据的一致性。