你为何在Redis里读到了本应过时的数据

一个事故的故事

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

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

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

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

那么形成这一问题的缘由是什么呢?dom

简单的说,redis的从库是没法主动的删除已通过期的key的,因此若是你作了读写分离,那么就极可能会在从库读到脏数据。函数

复杂的说,这个问题的答案相对复杂或者你根本不想知道这么详细,因此若是你只是想简单的了解如何避免这个问题,那么请继续看,很简单,咱们有两种作法:性能

1.经过一个程序循环遍历全部key,例如scan

2.升级到redis3.2

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

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

Redis key的三种过时策略code

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

  • 按期删除:因为惰性删除策略没法保证冷数据被及时删掉,因此redis会按期主动淘汰一批已过时的key。(在第二节中会具体说明)

  • 主动删除:当前已用内存超过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到新的版本吧。

相关文章
相关标签/搜索