记一次 redis 主从切换导致的数据丢失与陷入只读状态故障
背景
最近一组业务 redis 数据不断增长需要扩容内存,而扩容内存则需要重启云主机,在按计划扩容升级执行主从切换时意外发生了数据丢失与 master 进入只读状态的故障,这里记录分享一下。
业务 redis 高可用架构
该组业务 redis 使用的是一主一从,通过 sentinel 集群实现故障时的自动主从切换,这套架构已经平稳运行数年,经历住了多次实战的考验。高可用架构大体如下图所示:
简单说一下 sentinel 实现高可用的原理:集群的多个(2n+1,N>1)哨兵会定期轮询 redis 的所有 master/slave 节点,如果 sentinel 集群中超过一半的哨兵判定 redis 某个节点已主观下线,就会将其判定为客观下线进行相应处理:
如果下线节点是 master,选定一个正常 work 的 slave 将其选定为新的 master 节点。
如果下线节点是 slave,将其从 slave 节点中移除。
如果已经被客观下线的节点恢复了正常,sentinel 中超过一半哨兵确认后则将其加回可用的 slave 节点。所有需要读写 redis 的 server 并不需要直接写死 redis 主从配置,而是通过访问 sentinel 获取当前 redis 的主从可用状态,具体实现方式可以定时查询 sentinel 询问更新,也可以通过订阅机制让 sentinel 在主从变动时主动通知订阅方更新。sentinel 实现高可用的详细原理这里不做过多赘述,有兴趣的小伙伴可以移步参考文献中的相关资料。
具体内存扩容流程
sentinel 可以在检测到故障时自动切换 redis 主从,也可以主动执行 sentinel failover mastername 命令实现手动切换主从,所以这次的内存扩容重启流程设计如下(A 代表初始 master 所在云主机,B 代表初始 slave 所在云主机):
升级主机 B 内存配置,重启主机 B
检查 B 重启后其上的 redis slave 是否重新同步 master 数据完成,包括:2.1 查看 slave redis log 是否异常,无异常 pass2.2 使用 info keyspace 命令 check master、slave 各 db key 数量是否一致,无异常 pass2.3 在 master 写入一个测试 key,在 slave 上 check 是否同步成功 2.4 观察依赖 server log 是否有异常
使用 sentinel failover mastername 命令手动主从切换,主机 A 变成新 slave,主机 B 变成新 master,根据以前手动切换的经验走到这一步基本上就稳了--因为这里本质上和一次普通主从切换已经没有区别了。
升级主机 A 内存配置,重启主机 A,执行以下 check:4.1 查看新 slave redis log 是否异常 4.2 使用 info keyspace 命令 check 新 master、新 slave 各 db key 数量是否一致,无异常 pass4.3 在新 master 写入测试 key,在新 slave 上 check 是否同步成功 4.4 观察依赖 server log 是否有异常
至此,若以上步骤都正常通过,一个完美的 redis 内存升级工作就完成了。
主从切换后数据丢失
结果正是没有想过可能会出问题的步骤 3 反而出现了问题,直接导致了主从切换后丢掉了部分数据,并且新 master 进入只读状态将近十分钟。
当时的情况是这样的:
在执行完步骤 3 后,check 新 slave redis log 无异常,正在考虑观察一会儿后执行主机 A 的升级重启操作,api 的分钟级别异常监控触发了一小波 redis 相关报警。第一反应在新 master 与新 slave 上执行了 info keyspace 查看 key 数量是否已经不一致,结果发现 master/slave 的 key 数量是一致的--但是再仔细一看:和切换前的 key 总数百万级相比切换后 key 总数降到了十万级--大部分 key 数据被丢失了。查看新 master、新 slave log 都没有发现明显 log 可以解释为什么主从切换后会丢失一大半数据这一现象,这时小伙伴第一次提到了是不是内存不够了,当时自己略一思考马上回复到:新 master 刚升级了内存,不可能内容扩大后反而内存不足的,所以应该不是这个问题。
n 分钟后...
小伙伴再一次提出了是不是 maxmemory 问题,这一下子点中了关键点,马上想到主机 B 升级了内存是不会有系统层面内存不足的问题,但是 redis 的内存使用实际上还会受到 maxmemory 参数限制,马上在新 master 上执行 config get maxmemory, 只有 3GB,而升级前数据实际使用内存超过了 6GB!立刻调大了新 master 的 maxmemory 参数,redis 很快恢复了可读写正常状态,一大波 redis 只读引发的告警通知开始快速下降。
原因定位
紧张又刺激的故障处理就这么过去了,在优先处理完丢失 key 数据恢复工作之后,开始回顾整理故障的详细原因,总共有如下几个疑问:
明确记得上个月给主机 A、B 上的 redis 都通过 config set maxmemory 设置为了 7GB,为什么出现问题时查询 B 上 redis 的 maxmemory 配置却变成了 3GB?
如果主机 B 的 maxmemory 是 3GB,其作为 slave 时为什么从 master 同步超过 6GB 的数据时不会有问题?--在主从切换前无论是查看 info keyspace 还是在 master 上写入测试 key 同步 check 都是 OK 的。
为什么主从切换后主机 B 上的 key 数据会丢失?这个是因为 maxmemory 设置过小,是故障的直接原因。
为什么新 master 由于 maxmemory 参数超限进入只读状态且删除部分数据后,新 master 中实际数据占用的大小依然超过>3GB?
如上四个疑问除了问题 3 已经明确了,剩下三个问题都让人疑惑--事出诡异必有妖,经过一番探寻得出其答案:
上个月修改 redis maxmemory 时,只通过 config set 命令修改了其运行时配置,而没有修改对应配置 redis.conf 上 maxmemory 的值,主机 B 上 redis 在重启后就会从 redis.conf 上载入该 maxmemory,该配置正是 3GB,同时 maxmemory 参数是 redis 节点独立的配置,slave 并不会从 master 同步该值。
在 redis5.0 版本之后,redis 引入了一个新的参数 replica-ignore-maxmemory,其官方文档定义如下:
大意是 redis 作为 slave 时默认会无视 maxmemory 参数,这样可以保证主从的数据始终保持一致。当 master/slave 实际数据大小均小于其 maxmemory 设置时,这个参数没有任何影响,而这次丢失数据的原因正是因为主机 B 重启后作为 slave 时 maxmemory(3GB)小于实际数据大小(6GB+),此时 replica-ignore-maxmemory 默认开启保证作为 slave 时直接无视 maxmemory 的限制,而当执行 sentinel failover mastername 将主机 B 切换为新 master 后,新 master 不会受 replica-ignore-maxmemory 影响,发现自身 maxmemory<实际数据大小后直接开始主动淘汰 key,从而导致了数据丢失。3. 至于主机 B 作为 master 执行淘汰 key 策略并最终进入只读状态后,其实际数据大小依然>3GB 的原因,则是由于线上 redis 配置的策略是 volatile-lru 策略,该策略只会淘汰有过期时间的 key,对于不过期的 key 是不淘汰的。
总结
总的来看这次故障的根本原因还是个人对于 redis 的配置、操作经验不足,如果在调整运行时 maxmemory 时能做到以下二者之一,这次故障就不会出现了:
调整运行时 maxmemory 时同时调整配置文件 maxmemory 保持一致。
将配置文件 maxmemory 设置为 0--表示不限制内存使用。
正是因为对 redis 的认识和经验不足,没有想过到运行时配置与静态配置不一致可能导致的问题,这次不可避免的踩坑了。但是,作为一个本职 RD,半路接手基本靠自学的兼职运维,要考虑到 maxmemory 的运行配置与静态配置一致性问题好像也确实不是那么的理所当然🤔。处理完这次故障后,特意在网上搜索了一番 redis 主从切换的注意事项、踩坑文章,想看看有没有人提到类似的坑,但是并无所获,难道这个坑真的没其他人踩(分享)过?陷入思考...
最后
如果有经验丰富的小伙伴看到这里,也欢迎不吝赐教指导一下 redis 主从的切换的各类常识与常见大坑!
原文:http://www.cnblogs.com/AcAc-t/p/redis_master_switch_failure.html
如果感觉本文对你有帮助,点赞关注支持一下,想要了解更多 Java 后端,大数据,算法领域最新资讯可以关注我公众号【架构师老毕】私信 666 还可获取更多 Java 后端,大数据,算法 PDF+大厂最新面试题整理+视频精讲
评论