Redis数据“丢失”问题
Redis大部分应用场景是纯缓存服务,请求后端有Primary Storage的组件,如MySQL,HBase;请求Redis的键未命中,会从primary Storage中获取数据返回,同时更新Redis缓存。
如果少量数据丢失,相当于请求”缓冲未命中“; 一般对业务的影响是无感知的。
但现在Redis用作存储的业务场景变多,数据丢失对业务是致命的影响。
本文简单讨论Redis常见数据”丢失“现象,以及怎么规避;会列举几个生产中有意思的情节。
记1次Redis”数据丢失“的故障排查
Redis数据被丢失问题,发生次数也很多; 如何快速定位问题和避免呢。
先分享一个小故事(大家都喜欢带情节的片,不对是技术文章)
情节:我的Redis掉了90000多个Keys, 是不是DBA有删除操作?
(时间:12-04日;故事人物:RD(研发工程师)和DBA; 故事:Redis一夜之间不见90000个key)
RD:我们Redis集群中,以“t_list”前缀的90000多key今早发现都掉了,其他key都在,是不是DBA有清理操作啊?
DBA:没有维护性操作(一脸懵B和无辜),先止损,把Key从Primary store中导入Redis;我先分析一下原因,有结果了通知你;定位问题前,你也关注一下,避免二次发生。
RD:“已从MySQL把key导入到Redis. 好的,等你消息。”
然后RD就下楼了,DBA扣上他的25元的boss耳机,开始自言自语Troubleshooting.
“这部分key未设置TTL, 查看监控的 expired_keys基本都是0”
“是否达到了maxmeory,key被强制驱逐淘汰了? 查看监控 used_memory_pct未到100%,查看 evicted_keys一直为0,最近24小时无key被淘汰”
“只是部分key丢失,而且都是同一个key前缀,说明这个凶手很了解业务;查看监控的实例总key数, keys指标,发现果断keys果断下降,但未变为0,排除Flushall/flushdb和Redis, 定位是程序或人为操作”
“如果程序主动删除key, 就只能是DEL操作,查看监控comdstat_del指标,表示每秒执行的Del次数,果然平时基本为0,昨晚22:01开始有每秒几十个的del”
“再查看slowlog, 22:01时,执行 ‘ KEYS t list*’ 的命令,获取这类key,进行批量清理”
这时问题定位了, 一首歌多的时间。 然后DBA通知RD排查的结论,让其他排查程序或人为操作;分析证据简述如下:
从03日的Redis key监控可见,22:00到22:40这个数据key个数下降30000(1个分片,此集群共3个分片)
03日22:00~22:40分之间,Redis的DEL操作大约12个,持续40min; 删除12 6040约29000个key.(3个分片,共删除约90000个key)
查看slowlog监控,2015-12-03 22:01:01 时间点,执行KEYS “t list*” 获取所有key的前缀, 目的应该是执行后面的DEL操作
说明:精细化的监控告警很重要。
数据丢失的影响
- Redis存储的应用场景,数据丢失是不能接受的;
因为Redis的持久化特性,数据还原很难保证一致性,因rdb全备和aof重写备份,RPO不能像MySQL这样保证恢复到故障操作的前一个事务。 - 缓存的应用场景,如果大量缓存数据丢失,往往导致后端存储组件”打死“,应用程序雪崩的情况
常见Redis数据丢失的情况
- 程序bug或人为误操作
- 因客户端缓冲区内存使用过大,导致大量键被LRU淘汰
- 主库故障后自动重启,可能导致数据丢失
- 网络分区的问题,可能导致短时间的写入数据丢失
- 主从复制数据不一致,发生故障切换后,出现数据丢失
- 大量过期键,同时被淘汰清理
程序bug或人为误操作
如前文情节1,程序bug误删除数据; DBA/RD误操作执行flushall/flushdb这类命令。
这类问题的预防和监控
1 重命名危险命令:keys(程度大批量误删除,很多通过keys获取键后再删除),flushall,flushdb
2 细化几个重要的监控项:
- 实例当前的键个数(dbsize/info), 当大量键丢失时,可通过此项历史监控图,定位发生的时间范围
- 各类删除命令的执行数监控:cmdtats_flushall, cmdstats_flushdb,cmdstat_del
对应时间范围,确认具体是什么操作
因客户端缓冲区内存使用过大,导致大量键被LRU淘汰
因客户端缓冲区的内存大小很难限制,它们消耗的内存数会计算在used_memory内;如果使用不当,
导致缓冲区内存使用过大,达到maxmemory限制;(缓存场景)会导致大量的键被淘汰,最坏会把所有键清理,缓冲无键可淘汰,写入失败。相当于整个缓冲失效,对业务影响较大。
关于Redis客户端缓冲区问题,详细分析见之前文章 Redis Clients Two Buffers
这类问题的预防和监控:
1 业务容量规划时把缓冲正常消耗计算在内,合理高大maxmemory的限制;
每个实例最好可预留几百M(大小根据客户端连接数和key的使用有关,根据大小集群合理调整)
2 对输出缓冲区设置合理limit;如normal设置10MB, SLAVE设置1GB等。 如果复制因slave线程输出缓冲区反复同步,需临时调大slave client-output-buffer,要同时调大maxmemory限制。
说明:关于Redis复制中断和无限同步,详细分析请见 Redis复制中断和无限同步问题
3 主要监控
- 监控内存使用大小 used_memory
- 监控两个buffer的使用量client_longest_output_list和client_biggest_input_buf
- 监控键的LRU驱逐数量:evicted_keys
主库故障后自动重启,可能导致数据全部丢失
这种故障发生,极有可能数据全部丢失。
问题发生的现象:时间点T1,主库故障关闭了,因设置有自动重启的守护程序,时间点T2主库被重新拉起,因(T2-T1)时间间隔过小,未达到Redis集群或哨兵的主从切换判断时长;这样从库发现主库runid变了或断开过,会全量同步主库rdb清理,并清理自己的数据。
而为保障性能,Redis主库往往不做数据持久化设置,那么时间点T2启动的主库,很有可能是个空实例(或很久前的rdb文件)。
这种问题发生时间间隔,一般小于1分钟,可能监控告警无法感知到。
这类总是的预防和监控:
1 强烈反对Redis粗暴地设置自动重启
2 这种监控键个数的变化,缓存命中率,同时ELK类型准实时监控redis日志变化并告警
建议:数据库这类重“状态性”服务,不建议程序暴力自动重启
网络分区的问题,可能导致短时间的写入数据丢失
这种问题出现丢失数据都很少,网络分区时,Redis集群或哨兵在判断故障切换的时间窗口,这段时间写入到原主库的数据,5秒~15秒的写入量。
详细分析参考:
Reply to Aphyr attack to Sentinel
redis-sentinel-at-flickr
图片(引至参考2): Redis哨兵结构的网络分区导致的“split-brain”场景
主从复制数据不一致,发生故障切换后,出现数据丢失
主从数据出现不一致,发生故障切换,从库提升为主后,导致数据丢失的情况。
关于Redis复制数据不一致,请参考 Redis复制主从数据不-致
大量过期键,同时被淘汰清理
这类情况不是真正的“数据丢失”,只是定期主动清理Redis堆积的过期键,会导致Redis的键个数(dbsize)出现陡降(最大能达20%)。业务方常误以为有数据丢失。
这时可通过监控过期键淘汰的数量:expireed_keys的增长量,与dbsize键总数减少数据量是否相等。
说明:关于过期键,大量堆积成为“死键”问题,详细分析参考 Redis的“死键”问题
欢迎大家留言补充,遇到的数据丢失场景。