Redis脑裂为何会导致数据丢失?
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
1 案例
主从集群有1个主库、5个从库和3个哨兵实例突然发现客户端发送的一些数据丢了直接影响业务层数据可靠性。
最终排查发现是主从集群中的脑裂问题导致主从集群中同时有两个主节点都能接收写请求。
影响
客户端不知道应往哪个主节点写数据导致不同客户端往不同主节点写数据。严重的脑裂会进一步导致数据丢失。
2 脑裂原因
最初问题在主从集群中客户端发送的数据丢失了。
2.1 为什么数据会丢失
① 确认数据同步是否异常
在主从集群中发生数据丢失最常见原因主库数据还没同步到从库结果主库故障等从库升级为主库后未同步数据丢了。
新写入主库的数据a=1、b=3因为在主库故障前未同步到从库失了。
这种数据丢失case可直接对比主从库的复制进度差值
master_repl_offset - slave_repl_offset
若从库的slave_repl_offset < 原主库的master_repl_offset则可认定数据丢失是由数据同步未完成导致。
部署主从集群时也监测了
- 主库的master_repl_offset
- 从库上的slave_repl_offset
但发现数据丢失后检查了新主库升级前的slave_repl_offset以及原主库的master_repl_offset一致说明该升级为新主库的从库在升级时已和原主库的数据一致。
那为啥还会出现客户端发的数据丢失
所有数据操作都是从客户端发给Redis实例是否可从客户端操作日志发现问题
② 排查客户端的操作日志发现脑裂现象
发现主从切换后的一段时间有个客户端仍在和原主库通信并没有和升级的新主库交互。
相当于主从集群中同时有两个主库。据此想到主从集群故障的脑裂。但不同客户端给两个主库发送数据写操作应只会导致新数据会分布在不同主库而不会造成数据丢失。
思路又断了。“从原理出发是追本溯源的好方法”。脑裂是发生在主从切换过程猜测是漏掉了主从集群切换过程中的某环节所以聚焦主从切换的执行过程。
③ 发现是原主库假故障导致的脑裂
我们采用哨兵机制进行主从切换的主从切换发生时一定有超过预设数量quorum配置项的哨兵实例和主库的心跳都超时才会把主库判断为客观下线然后哨兵开始执行切换操作。
哨兵切换完成后客户端会和新主库通信发送请求操作。
但切换过程中既然客户端仍和原主库通信说明原主库并未真故障如主库进程挂掉。怀疑主库某些原因无法处理请求也没响应哨兵的心跳被哨兵错判客观下线。
被判下线后原主库又重新开始处理请求了而此时哨兵还没完成主从切换客户端仍可和原主库通信客户端发送的写操作就会在原主库写数据。
为验证原主库只是“假故障”查看原主库服务器的资源使用监控。原主库所在机器有段时间CPU利用率飙升因某程序把机器CPU用满导致Redis主库无法响应心跳这期间哨兵就把主库判为客观下线开始主从切换。这程序很快恢复正常CPU使用率也下来了。原主库又继续正常服务请求。
正因原主库未真故障在客户端操作日志中就看到和原主库通信记录。从库被升级为新主库后主从集群里就有两个主库这就是案例脑裂原因。
3 为何脑裂会导致数据丢失
主从切换后从库一旦升级为新主哨兵就会让原主库执行slave of命令和新主重新进行全量同步。
在全量同步执行最后阶段原主需清空本地数据加载新主发送的RDB文件原主在主从切换期间保存的新写数据就丢了。
主从切换过程中若原主只是“假故障”会触发哨兵启动主从切换一旦等它从假故障恢复又开始处理请求这就和新主共存导致脑裂。
等哨兵让原主和新主做全量同步后原主在切换期间保存的数据就丢了。
4 脑裂应急方案
主从集群中的数据丢失是因为发生脑裂必须有应对脑裂方案。
问题出在原主假故障后仍能接收请求因此可在主从集群机制的配置项中查找是否有限制主库接收请求的设置。Redis提供如下配置项限制主库的请求处理
- min-replicas-to-write
主库能进行数据同步的最少从库数量 - min-replicas-max-lag
主从库间进行数据复制时从库给主库发送ACK消息的最大延迟单位s
分别设置阈值N和T俩配置项组合后的要求是
- 主库连接的从库中至少有N个从库
- 和主库进行数据复制时的ACK消息延迟不能超过T秒
否则主库就不会再接收客户端请求。
即使原主假故障假故障期间也无法响应哨兵心跳也不能和从库进行同步自然就无法和从库进行ACK确认。这俩配置项组合要求就无法得到满足原主库就会被限制接收客户端请求客户端也就不能在原主库中写新数据。
等新主上线就只有新主能接收和处理客户端请求此时新写的数据会被直接写到新主。而原主会被哨兵降为从库即使它的数据被清空也不会有新数据的丢失。
假设
- min-replicas-to-write=1
- min-replicas-max-lag设为12s
- 哨兵的down-after-milliseconds设为10s
主库因某原因卡住15s导致哨兵判断主库客观下线开始进行主从切换。
同时因原主库卡住15s没有一个从库能和原主库在12s内进行数据复制原主库也无法接收客户端请求。
主从切换完成后也只有新主库能接收请求不会发生脑裂也就不会发生数据丢失。
5 总结
脑裂主从集群中同时有两个主能接收写请求。Redis主从切换过程中若发生脑裂客户端数据就会写入原主若原主被降为从库这些新写入数据就丢了。
脑裂主要是因为原主库发生了假故障假故障的原因
- 和主库部署在同一台服务器上的其他程序临时占用了大量资源例如CPU资源导致主库资源使用受限短时间内无法响应心跳。其它程序不再使用资源时主库又恢复正常
- 主库自身遇到阻塞如处理bigkey或是发生内存swap你可以复习下第19讲中总结的导致实例阻塞的原因短时间内无法响应心跳等主库阻塞解除后又恢复正常的请求处理了。
应对脑裂你可以在主从集群部署时通过合理地配置参数min-slaves-to-write和min-slaves-max-lag来预防脑裂。
在实际应用中可能会因为网络暂时拥塞导致从库暂时和主库的ACK消息超时。在这种情况下并不是主库假故障我们也不用禁止主库接收请求。
6 最佳实践
假设从库有K个可将
- min-slaves-to-write设置为K/2+1如果K等于1就设为1
- min-slaves-max-lag设置为十几秒例如10~20s
这个配置下如果有一半以上的从库和主库进行的ACK消息延迟超过十几s我们就禁止主库接收客户端写请求。
这样一来我们可以避免脑裂带来数据丢失的情况而且也不会因为只有少数几个从库因为网络阻塞连不上主库就禁止主库接收请求增加了系统的鲁棒性。
假设
- min-slaves-to-write 置 1
- min-slaves-max-lag 设置为 15s哨兵的
- down-after-milliseconds 设置为 10s
哨兵主从切换需要 5s主库因为某些原因卡住12s此时还会发生脑裂吗主从切换完成后数据会丢失吗
主库卡住 12s达到哨兵设定的切换阈值所以哨兵会触发主从切换。但哨兵切换时间5s即哨兵还未切换完成主库就会从阻塞状态中恢复回来且没有触发 min-slaves-max-lag 阈值所以主库在哨兵切换剩下的 3s 内依旧可以接收客户端的写操作如果这些写操作还未同步到从库哨兵就把从库提升为主库了那么此时也会出现脑裂的情况之后旧主库降级为从库重新同步新主库的数据新主库也会发生数据丢失。
即使 Redis 配置了 min-slaves-to-write 和 min-slaves-max-lag当脑裂发生时还是无法严格保证数据不丢失只是尽量减少数据的丢失。
这种情况下新主库之所以会发生数据丢失是因为旧主库从阻塞中恢复过来后收到的写请求还没同步到从库从库就被哨兵提升为主库了。如果哨兵在提升从库为新主库前主库及时把数据同步到从库了那么从库提升为主库后也不会发生数据丢失。但这种临界点的情况还是有发生的可能性因为 Redis 本身不保证主从同步的强一致。
还有一种脑裂情况就是网络分区主库和客户端、哨兵和从库被分割成了 2 个网络主库和客户端处在一个网络中从库和哨兵在另一个网络中此时哨兵也会发起主从切换出现 2 个主库的情况而且客户端依旧可以向旧主库写入数据。等网络恢复后主库降级为从库新主库丢失了这期间写操作的数据。
脑裂本质是Redis 主从集群内部没有通过共识算法来维护多个节点数据的强一致性。不像 Zookeeper每次写请求必须大多数节点写成功后才认为成功。当脑裂发生时Zookeeper 主节点被孤立此时无法写入大多数节点写请求会直接返回失败因此它可以保证集群数据的一致性。
对于min-slaves-to-write如果只有 1 个从库当把 min-slaves-to-write 设置为 1 时在运维时需要小心一些当日常对从库做维护时例如更换从库的实例需要先添加新的从库再移除旧的从库才可以或者使用 config set 修改 min-slaves-to-write 为 0 再做操作否则会导致主库拒绝写影响到业务。