键过期删除策略
Redis的键可以设置过期时间,时间一到,就会自动删除。但是我们会不会这么一种情景发生:会不会因为有这么同一时刻太多的key过期,以至于忙不过来。同时因为 Redis 是单线程的,收割的时间也会占用线程的处理时间,如果收割的太过于繁忙,会不会导致线上读写指令出现卡顿?
其实关于这个键过期删除我们也是有策略的,所以并不会导致这个情况发生。
定时删除(主动删除策略)
通过使用定时器(时间事件,采用无序链表实现,),定时删除数据。定时删除策略可以保证过期的键会尽可能快的被删除了,并释放过期键锁占用的内存。
优点:对内存是友好的,通过使用定时器,定时删除策略可以保证过期键会尽可能快地被删除,并释放过期键锁占用地内存。
缺点:对CPU时间是最不友好地:在过期键比较多的情况下,删除过期键这一行为会占用相当一部分CPU时间,在内存不紧张但是CPU非常紧张的情况下,将CPU应用于删除和当前任务无关的过期键上,无疑会对服务器的响应时间和吞吐量造成影响。
惰性删除(被动删除策略)
程序在每次使用到键的时候去检查是否过期,如果过期则删除并返回空。
优点:对CPU时间是最友好的,程序只会在取出键的时候才对键进行过期检查,所以不会在删除其他无关的过期键上花费任何CPU时间。
缺点:对内存是不友好的,如果一个键已经过期,而又保留在数据库中,只要这个过期键没有被删除,那么它所占有的内存就永远不会释放。而我们如果永远没有访问到这些键的话,那么这些内存就永远不会被释放,我们可以把这看成是一种内存泄漏。
定期删除(主动删除策略)
可以看成是两种策略的整合和折中
我们可以发现不管是定时删除还是惰性删除,这两种策略都有明显的缺陷
- 定时删除占用太多的CPU时间,影响服务器的响应时间和吞吐量
- 惰性删除浪费太多内存,有内存泄漏的危险
所以通过使用定期删除策略我们可以比较好的解决这些问题
优点:
- 每隔一段时间执行一次删除过期键操作,并通过限制删除操作执行的时长和频率来减少操作对CPU时间的影响(这里我们用到是定时扫描策略)
- 定期删除过期键,有效地减少了因为过期键带来的内存浪费
缺点:
- 如果删除操作执行得太频繁,或者执行得时间太长,定期删除策略就会退化成定时策略,以至于将CPU时间过多地消耗在删除键上面。
- 如果删除操作执行的过少,或者执行的时间太短,定期删除策略又会和惰性删除策略一样,出现内存浪费的情况。
总结:如果采用定期删除策略的话,服务器必须根据情况,合理地设置删除操作的执行时长和执行频率。
定时扫描策略
上面我们定期删除优点第一点提到了这个策略。Redis 默认会每秒进行十次过期扫描,过期扫描不会遍历过期字典中所有的 key,而是采用了一种简单的贪心策略。
从过期字典中随机 20 个 key;
删除这 20 个 key 中已经过期的 key;
如果过期的 key 比率超过 1/4,那就重复步骤 1;
同时,为了保证过期扫描不会出现循环过度,导致线程卡死现象,算法还增加了扫描时间的上限,默认不会超过 25ms。
内存淘汰策略
当 Redis 内存超出物理内存限制时,内存的数据会开始和磁盘产生频繁的交换 (swap)。交换会让 Redis 的性能急剧下降,对于访问量比较频繁的 Redis 来说,这样龟速的存取效率基本上等于不可用。
在生产环境中我们是不允许 Redis 出现交换行为的,为了限制最大使用内存,Redis 提供了配置参数 maxmemory 来限制内存超出期望大小。
当实际内存超出 maxmemory 时,Redis 提供了几种可选策略 (maxmemory-policy) 来让用户自己决定该如何腾出新的空间以继续提供读写服务。
关于内存淘汰策略有以下六种:
(1)noeviction 不会继续服务写请求 (DEL 请求可以继续服务),读请求可以继续进行。也就是当内存不足以容纳新入数据时,新写入操作就会报错。这样可以保证不会丢失数据,但是会让线上的业务不能持续进行。这是默认的淘汰策略。
应用场景:目前默认使用的就是。
(2)volatile-lru 尝试淘汰设置了过期时间的 key,最少使用的 key 优先被淘汰。没有设置过期时间的 key 不会被淘汰,这样可以保证需要持久化的数据不会突然丢失。
应用场景:如果希望一些数据能长期被保存,而一些数据可以被淘汰掉时
(3)volatile-ttl 跟上面一样,除了淘汰的策略不是 LRU,而是 key 的剩余寿命 ttl 的值,ttl
越小越优先被淘汰。
应用场景:如果研发者需要通过设置不同的ttl来判断数据过期的先后顺序,此时可以选择volatile-ttl策略。
(4)volatile-random 跟上面一样,不过淘汰的 key 是过期 key 集合中随机的 key。
应用场景:如果希望一些数据能长期被保存,而一些数据可以被淘汰掉时。
(5)allkeys-lru 区别于 volatile-lru,这个策略要淘汰的 key 对象是全体的 key 集合,而不只是过期的 key 集合。这意味着没有设置过期时间的 key 也会被淘汰。
应用场景:
- 在Redis中,数据有一部分访问频率较高,其余部分访问频率较低,或者无法预测数据的使用频率时,设置allkeys-lru是比较合适的。
- 由于设置expire会消耗额外的内存,如果计划避免Redis内存在此项上的浪费,可以选用allkeys-lru 策略,这样就可以不再设置过期时间,高效利用内存了。
(6)allkeys-random 跟上面一样,不过淘汰的策略是随机的 key。
应用场景: 如果所有数据访问概率大致相等时,可以选择allkeys-random。
参考资料
《Redis设计与实现》
《Redis深度历险:核心原理与应用实践》