缓存过期:为什么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则是忽略