转载

你为什么在 Redis 里读到了本应过期的数据:Redis 过期策略解读

请叫我女王

为了防止世界被破坏,为了守护世界和平, 贯彻爱与真实的邪恶,可爱又迷人的反派角色,额,跑题了,其实我只是想说,每天都看一些高大上的技术贴,然并卵,即昨天污版TCP科普文之后,今天进入正题,我们只想关注一线运维工程师的台前幕后(一线、一线,挣钱少最苦逼的一线),欢迎点击上方的公众号关注我们! 喵~

一个事故的故事

晚上睡的正香突然被电话吵醒,对面是开发焦急的声音:我们的程序在访问redis的时候读到了本应过期的key导致整个业务逻辑出了问题,需要马上解决。

看到这里你可能会想:这是不是又是所谓的“redis的坑”啊?

不不不,我们从来不会随便把一些问题归类到“xxx的坑”里,那么这个问题真的存在吗?

是的,这个问题真实存在并且你很可能已经碰到了只是并未发觉。

那么造成这一问题的原因是什么呢?

简单的说,redis的从库是无法主动的删除已经过期的key的,所以如果你做了读写分离,那么就很可能会在从库读到脏数据。

复杂的说,这个问题的答案相对复杂或者你根本不想知道这么详细,所以如果你只是想简单的了解如何避免这个问题,那么请继续看,很简单,我们有两种做法:

1.通过一个程序循环遍历所有key,例如scan

2.升级到redis3.2

END

OK,你已经遇到/可能遇到/即将遇到的问题我们已经帮你解决了,那么如果你仍然对本文有兴趣那么可以继续往下看,因为在下文中对这个问题的分析以及更细化的解决方案一定会为你增加面试、讨论、对喷时的资本!

“通俗易懂” Redis 过期策略解读

Redis key的三种过期策略

  1. 惰性删除:当读/写一个已经过期的key时,会触发惰性删除策略,直接删除掉这个过期key,很明显,这是被动的!

  2. 定期删除:由于惰性删除策略无法保证冷数据被及时删掉,所以 redis 会定期主动淘汰一批已过期的key。(在第二节中会具体说明)

  3. 主动删除:当前已用内存超过maxmemory限定时,触发主动清理策略,该策略由启动参数的配置决定,可配置参数及说明如下:

volatile-lru:从已设置过期时间的数据集中根据LRU算法删除数据(redis3.0之前的默认策略)

volatile-ttl:从已设置过期时间的数据集中挑选过期时间最小的数据删除

volatile-random:从已设置过期时间的数据集中随机选择数据删除

allkeys-lru:从所有数据集中根据LRU算法删除数据

allkeys-random:从所有数据集中任意选择删除数据

noenviction:禁止从内存中删除数据(从redis3.0 开始默认策略)

maxmemory-samples删除数据的抽样样本数,redis3.0之前默认样本数为3,redis3.0开始默认样本数为5,该参数设置过小会导致主动删除策略不准确,过大会消耗多余的cpu

Redis过期key删除策略之定期删除

因为redis本身的定位为轻量、快速的内存数据库,所以如果为所有key都加上定时器,过期即删除的定时策略显然会消耗大量的性能,这与redis作者的价值观有着巨大差异;由于redis中key的过期删除只会在主库上进行,对于目前redis使用的组合策略来说,单位时间过期的数据量越多,越可能会带来key的过期延迟,对于做了读写分离的业务,很容易导致从库读取到过期的脏数据。

redis源码activeExpireCycle函数的解读结果请看下文(如果你懒得看,可以直接跳过本节看第三节):

相关参数默认值:

hz 10 :每秒执行10次activeExpireCycle 函数

activeExpireCycle 函数解析:

每次循环随机拿出的key的数量

正常过期模式最大cpu耗时率

过期模式:

“正常过期”模式 :执行时间限制:25ms;计算公式为

“快速过期”模式 :执行时间限制为1ms,触发条件为上次的执行时间超过了timelimit,之后函数会使timelimit_exit=1 为真,并从上次发生超时的db的下一个db开始继续处理。

过期策略:redis会遍历所有db,每次从db中随机拿出20个带有过期时间属性的key做过期判断。

循环检测:对随机拿出的20个key进行检测,如果在本次检测中发现有超过25%的key被判定为过期则持续执行过期检测循环,直到这批key中需要过期的key的比例低于25%或某次循环超过timelimit执行时间限制。

上文已经提到,过期删除行为只会在主库中进行。这是因为key的过期删除依赖于expireIfNeeded函数,这个函数在任何访问数据的操作中都会被调用并用来检测客户端访问的数据是否过期。

如果当前数据库实例角色是master,则不进行key过期的删除操作。反之,它会先调用另一个函数propagateExpire发送del key命令到aof和当前redis实例的所有slave,最后将该key从数据库中删除。此时,从库中的该key才真正意义上的过期/消失/你访问不到了!

所以一旦一个redis集群的内存没有触及maxmemory,而它每时每刻都有大量的key需要过期导致定期删除忙不过来,并且这些过期了的key不会再被访问到,那么你就很可能会在从库莫名其妙的读到了本应过期的key了。

如何避免从库读取到脏数据

  1. 通过scan命令扫库:
    当redis中的key被scan的时候,相当于访问了该key,同样也会做过期检测,充分发挥redis惰性删除的策略。这个方法能大大降低了脏数据读取的概率,但缺点也比较明显,会造成一定的数据库压力,谨慎合理使用,否则有可能影响线上业务的效率。

  2. 升级redis到新的版本:
    在redis 3.2-rc1版本中,redis加入了一个新特性来解决主从不一致导致读取到过期数据的问题(好吧,虽然这个新特性我们一直觉得是个bug fix),在源码db.c文件中,作者对lookupKeyRead做了相应的修改,增加了key是否过期以及对主从库的判断(代码如下),如果key已过期,当前访问的是master则返回null;当前访问的是从库,且执行的是只读命令也返回null(老版本从库真实的返回该操作的结果,如果该key过期后主库没有删除),源码片段如下:

你为什么在 Redis 里读到了本应过期的数据:Redis 过期策略解读

那么,不想通过自己写程序解决问题的同学,快快升级redis到新的版本吧。

END

这回真的结束了, 当然,你可以长按下图的二维码关注我们,喵~

你为什么在 Redis 里读到了本应过期的数据:Redis 过期策略解读

原文  http://mp.weixin.qq.com/s?__biz=MzIyNzUwMjM2MA==&mid=2247483696&idx=1&sn=c69e364b189fe261a466e4f8d290258b&scene=2&srcid=0812sWPJjYmbGOEzR8oMS0Oe
正文到此结束
Loading...