缓存过期:为什么Redis不立刻删除已经过期的数据?

39 阅读6分钟

缓存过期:为什么Redis不立刻删除已经过期的数据?

缓存命中率

缓存命中率(缓存命中次数除以查询总次数) 是衡量缓存效果的关键指标。

在实践中我们要尽量把缓存命中率提高到90%以上。

实现过期机制的一般思路

过期之类的机制一般从定时删除、延迟列队、懒惰删除、定期删除四个方面考虑

  • 定时删除:对每一个需要被删除的对象启动一个定时器,过期直接删除

    • 优点:时间精确,过期马上就被删除
    • 缺点:计时器开销大、对象过期时间被重置需要取消原本的计时器,开启一个新的
  • 延迟列队:把对象放到一个延迟列队中,当对象被取出时,代表这个对象过期了,可以删除

    • 优点:时间精确,过期马上就被删除
    • 队列有开销、对象过期时间被重置需要重新调整对象在队列的位置(如果延时列表不精确,可能需要删除原对象,重新加入)
  • 懒惰删除:每次使用对象时,检查一下是否过期。过期直接删除

    • 优点:实现简单、重置过期时间没影响
    • 缺点:对象过期被删除取决于再次被调用时间、浪费内存(过期对象不被调用,永远不被删除)
  • 定期删除:每隔一段时间遍历对象,找到过期的对象,然后删除

    • 优点:实现简单、重置过期时间没影响
    • 缺点:对象过期后,等到下个删除循环才会被删除、性能损耗不可空(很多对象过期了,定期删除需要执行很久)

Redis采用懒惰删除和定期删除结合的策略,定时查询和延迟列队对缓存场景性能太差

为什么不立刻删除过期的key(做不到)

想要做到立刻删除的key需要的代价太大了,太影响性能。

想做到立刻删除过期key需要用到定时删除或延迟列队。但是,两者本身的实现就需要很大的开销,更新过期时间时也需要额外的操作,面对缓存的场景不使用。对比之下,定期删除和懒惰删除只是浪费一点空间,不浪费时间,更加贴合缓存的场景。

Redis 是怎么控制定期删除开销的?

既然没办法立刻删除,只能定期删除,那么我们怎么控制这个开销呢?

Redis是通过控制执行定期删除循环时间来控制开销的

假如现在redis有100万key,redis在定期删除过期key时,不可能遍历完100万个key。所以,redis会在每一个循环中遍历DB。如果本次定期循环没有遍历完全部DB,下次循环就会从最后遍历的DB的下一个继续遍历下去。

每个DB都会经历5个步骤:

  • DB的key都没有设置过期时间,就遍历下一个DB
  • 从设置了过期时间的key中抽一批,默认25个
  • 检查这些key,如果过期,则执行删除操作
  • 每遍历16个key,就检测执行时间,如果时间超过阈值,则中断这次定期删除循环
  • 如果这一批过期key比例超过一个阈值,那就抽取下一批key检查(阈值也是通过参数控制的)

这时候就会有一个问题——为什么要随机抽样,同一个 DB 内按照顺序遍历下去不就可以吗?

是为了确保每个 key 都能遍历到

随机是为了保证每个key都有概率被抽查到。如果我们每次都从头遍历,那么遍历到中间可能时间就没了,DB后面的key永远都遍历不到。

其实大部分缓存控制开销的办法就是:控制时间或者控制个数

如何控制定期删除的频率?(hz和dynamic-hz)

Redis中,定期删除频率通过hz参数控制。hz控制的是所有后台任务,并不是单独控制定期删除循环

假如hz的值是N,说明每1/N秒会执行一次后台任务。(hz=10,就是100ms执行一次)

一般来说hz不要超过100,因为hz越大后台执行频率越高,cpu使用率就越高。

还有dynamic-hz选项。开启后,hz的值被认为是一个基数,实际值是Redis自己动态计算的

从库处理过期 key(从库处理过期 key后删除)

又有一个新问题,Redis在主从集群时,查询从库会查到过期的数据吗?

当从从库查询到的key过期时,redis从库会返回一个NULL(3.2版本之前会拿到数据,之后修复了这个bug)

主库和从库的区别是,主库发现key过期后会立即执行删除操作。但从库不会,而是等待主库的删除命令。

持久化处理过期 key

Redis 有持久化机制,那么Redis 在持久化时怎么处理过期的 key?

Redis有两种持久化文件:RDB和AOF

RDB(主库不读不写,从库原封不动):在生成RDB的时候主库会忽略过期的key。主库加载RDB时也会忽略RDB中过期的key。从库是整个RDB文件都加载进来,但是从库加载完RDB后,很快就能从主库收到删除指令,从而删除过期key

AOF(忽略过期key):redis会考虑重写AOF,会把内存中的数据都写下来,然后删除之前的AOF文件。重写过程中会忽略过期的key

如何确定过期时间?(根据缓存命中率来确定)

一般都是考虑缓存容量和缓存命中率。正常来说,缓存命中率越高,就要更多的缓存容量和越长的过期时间。我们可以通过测试尽可能的满足缓存命中率来延长过期时间。比如,一个数据被查出来后,用户可能会在30分钟内再次使用这个数据,那么设置过期时间为30分钟就可以。

那怎么确定缓存命中率呢? (根据用户体验来确定)

比如,公司要求平均响应时间是300ms,命中响应时间是100ms,没命中缓存的响应时间是1000ms,假如命中率为p,那么p要满足100*p+1000*(1-p)=300

总结

  • Redis是怎么处理过期key的?

    • 懒惰删除和定期删除
  • Redis为什么不立刻删除过期key?

    • 代价太大了,影响性能
  • Redis是怎么控制定期删除的开销的?

    • 控制执行时间
  • 怎么控制定期删除频率?

    • 通过hz和dynamic-hz参数
  • 从库是怎么处理过期key的?

    • 查询返回NULL,等待主库命令
  • Redis持久化怎么处理过期key?

    • RDB:主库不读不写,从库原封不动。
    • AOF:正常追加DEL命令,重写AOF则是忽略