注册 登录
  • 欢迎访问"运维那点事",推荐使用Google浏览器访问,可以扫码关注本站的"微信公众号"。
  • 如果您觉得本站对你有帮助,那么可以扫码捐助以帮助本站更好地发展。

Redis缓存过期失效机制

Redis 彭东稳 10388次浏览 已收录 0个评论

我们都知道,Redis是key-value数据库,我们可以设置Redis中缓存的key的过期时间。那么Redis缓存失效(key过期)的故事要从EXPIRE这个命令说起,EXPIRE命令允许用户为某个key指定其过期时间,当key超过这个时间后,我们应该就访问不到这个值了。接下来我们继续深入探究这个问题,Redis缓存失效机制是如何实现的呢?也就是当key超过过期时间后Redis如何处理key呢?

过期策略通常有以下三种:

  • 定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
  • 惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
  • 定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。(expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。)

Redis中同时使用了惰性过期和定期过期两种过期策略。

这篇文章主要在分析Redis源码的基础上站在Redis设计者的角度去思考Redis缓存失效的相关问题。

一、Redis缓存过期运用机制

通常Redis key创建时没有设置相关过期时间。他们会一直存在,除非使用显示的命令移除,例如,使用DEL命令。EXPIRE一类命令能关联到一个有额外内存开销的key。当key执行过期操作时,Redis会确保按照规定时间删除他们。key的过期时间和永久有效性可以通过EXPIRE和PERSIST命令(或者其他相关命令)来进行更新或者删除过期时间。

为给定key设置生存时间,当key过期时(生存时间为0),它会被自动删除。在Redis中,带有生存时间的key被称为『易失的』(volatile)。生存时间可以通过使用DEL命令来删除整个key来移除,或者被SET和GET/SET命令覆写(overwrite),这意味着,如果一个命令只是修改(alter)一个带生存时间的key的值而不是用一个新的key值来代替(replace)它的话,那么生存时间不会被改变。比如说,对一个key执行INCR命令,对一个列表进行LPUSH命令,或者对一个哈希表执行HSET命令,这类操作都不会修改key本身的生存时间。

另一方面,如果使用RENAME对一个key进行改名,那么改名后的key的生存时间和改名前一样。RENAME命令的另一种可能是,尝试将一个带生存时间的key改名成另一个带生存时间的another_key,这时旧的another_key(以及它的生存时间)会被删除,然后旧的key会改名为another_key,因此,新的another_key的生存时间也和原本的key一样。

使用PERSIST命令可以在不删除key的情况下,移除key的生存时间,让key重新成为一个『持久的』(persistent) key 。


 

注意点:

在小于2.1.3的Redis版本里,只能对key设置一次expire。Redis 2.1.3和之后的版本里,可以多次对key使用expire命令,更新key的expire time。

Redis术语里面,把设置了expire time的key叫做:volatile keys,意思就是不稳定的key。

如果对key使用set或del命令,那么也会移除expire time。尤其是set命令,这个在编写程序的时候需要注意一下。

Redis 2.1.3之前的老版本里,如果对volatile keys做相关写入操作(LPUSH,LSET),和其他一些触发修改value的操作时,Redis会删除该key。

Redis对过期键采用了lazy expiration,在访问key的时候判定key是否过期,如果过期,则进行过期处理。其次,每秒对volatile keys进行抽样测试,如果有过期键,那么对所有过期key进行处理。

到此为止我们大概明白了什么是缓存失效机制以及缓存失效机制的一些应用场景,接下来我们继续深入探究这个问题,Redis缓存失效机制是如何实现的呢?

二、Redis淘汰过期key机制

1. 惰性过期机制

惰性过期机制即当客户端请求操作某个key的时候,Redis会对客户端请求操作的key进行有效期检查,如果key过期才进行相应的处理,惰性过期机制也叫消极失效机制。我们看看t_string组件下面对get请求处理的服务端端执行堆栈:

关键的地方是expireIfNeed,Redis对key的get操作之前会判断key关联的值是否失效,这里先插入一个小插曲,我们看看Redis中实际存储值的地方是什么样子的:

上面是Redis中定义的一个结构体,dict是一个Redis实现的一个字典,也就是每个DB会包括上面的五个字段,我们这里只关心两个字典,一个是dict,一个是expires:

  1. dict是用来存储正常数据的,比如我们执行了set key “hahaha”,这个数据就存储在dict中。
  2. expires使用来存储关联了过期时间的key,比如我们在上面的基础之上有执行的expire key 1,这个时候就会在expires中添加一条记录。

回过头来看看expireIfNeeded的流程,大致如下:

  1. 从expires中查找key的过期时间,如果不存在说明对应key没有设置过期时间,直接返回。
  2. 如果是slave机器,则直接返回,因为Redis为了保证数据一致性且实现简单,将缓存失效的主动权交给Master机器,slave机器没有权限将key失效。
  3. 如果当前是Master机器,且key过期,则Master会做两件重要的事情:1)将删除命令写入AOF文件。2)通知Slave当前key失效,可以删除了。
  4. Master从本地的字典中将key对应的值删除。

2. 定期过期机制

定期过期机制也叫主动失效机制,即服务端定期的去检查失效的缓存,如果失效则进行相应的操作。

我们都知道Redis是单线程的,基于事件驱动的,Redis中有个EventLoop,EventLoop负责对两类事件进行处理:

  1. 一类是IO事件,这类事件是从底层的多路复用器分离出来的。
  2. 一类是定时事件,这类事件主要用来对某个任务的定时执行。

看起来Redis的EventLoop和Netty以及JavaScript的EventLoop功能设计的大概类似,一方面对网络I/O事件处理,一方面还可以做一些小任务。

为什么讲到Redis的单线程模型,因为Redis的主动失效机制逻辑是被当做一个定时任务来由主线程执行的,相关代码如下:

serverCron就是这个定时任务的函数指针,adCreateTimeEvent将serverCron任务注册到EventLoop上面,并设置初始的执行时间是1毫秒之后。接下来,我们想知道的东西都在serverCron里面了。serverCron做的事情有点多,我们只关心和本篇内容相关的部分,也就是缓存失效是怎么实现的,我认为看代码做什么事情,调用堆栈还是比较直观的:

EventLoop通过对定时任务的处理,触发对serverCron逻辑的执行,最终之执行key过期处理的逻辑。值得一提的是,activeExpireCycle逻辑只能由master来做。

另外,serverCron周期性操作函数每100ms执行一次,还包括检查服务器状态,比如检查“save N M”是否满足条件,如果满足就执行BGSAVE;当然也包括AOF重写检查等。

三、过期时间计算方式

Keys的过期时间使用Unix时间戳存储(从Redis 2.6开始以毫秒为单位)。这意味着即使Redis实例不可用,时间也是一直在流逝的。

要想过期的工作处理好,计算机必须采用稳定的时间。如果你将RDB文件在两台时钟不同步的电脑间同步,有趣的事会发生(所有的keys装载时就会过期)。

即使正在运行的实例也会检查计算机的时钟,例如如果你设置了一个key的有效期是1000秒,然后设置你的计算机时间为未来2000秒,这时key会立即失效,而不是等1000秒之后。

key不存在或者不能为key设置生存时间时(比如在低于2.1.3版本的Redis中你尝试更新key的生存时间),返回0

四、在复制AOF文件时如何处理过期

为了获得正确的行为而不牺牲一致性,当一个key过期,del将会随着AOF文字一起合成到所有附加的slaves。在master实例中,这种方法是集中的,并且不存在一致性错误的机会。

然而,当slaves连接到master时,不会独立过期keys(会等到master执行DEL命令),他们任然会在数据集里面存在,所以当slave当选为master时淘汰keys会独立执行,然后成为master。

五、遗留问题

Redis对缓存失效的处理机制大概分为两种,一种是客户端访问key的时候消极的处理,一种是主线程定期的积极地去执行缓存失效清理逻辑,上面文章对于一些细节还没有展开介绍,但是对于Redis缓存失效实现机制这个话题,本文留下几个问题:

  1. Redis缓存失效逻辑为什么只有master才能操作?
  2. 上面提到如果客户端访问的是slave,slave并不会清理失效缓存,那么这次客户端岂不是获取了失效的缓存?
  3. 上面介绍的两种缓存失效机制各有什么优缺点?Redis设计者为什么这么设计?
  4. 服务端对客户端的请求处理是单线程的,单线程又要去处理失效的缓存,是不是会影响Redis本身的服务能力?

Redis的内存淘汰策略的选取并不会影响过期的key的处理。内存淘汰策略用于处理内存不足时的需要申请额外空间的数据;过期策略用于处理过期的缓存数据。

<参考>

http://blog.jobbole.com/102183/


如果您觉得本站对你有帮助,那么可以支付宝扫码捐助以帮助本站更好地发展,在此谢过。
喜欢 (7)or分享 (0)
关于作者:

您必须 登录 才能发表评论!