Redis 数据管理策略很值得借鉴。 所有服务器其实都有数据过期数据怎么清理的问题。
键值过期的策略
目前来说有三种删除策略:
- 定时删除:在设置键的过期时间时,创建一个定时器,当到达键过期时间时通过定时器去删除键。
- 惰性删除:惰性删除并不是当到达过期时间时去删除,而是每次获取键时,会判断是否过期,如果过期则删除,并返回空;没过期,就返回键值。
- 定期删除:每隔一段时间,就对数据库中的键进行检查,如果过期则删除。至于要删除多少什么时候删除,则是通过具体程序决定。
下面来详细介绍每一种删除策略。
定时删除
定时删除策略
优点是:对内存友好。因为通过定时器,当一个键到达过期时间时就会立马被删除,直接就释放了内存。
缺点是:对 CPU 不友好。因为如果过期键比较多,那么删除这些过期键会占用相当一部分CPU时间,如果CPU时间非常紧张的话,还将CPU时间用在删除和当前任务无关的过期键上,会对服务器的响应时间以及吞吐量造成影响。
因此,通过 定时删除 策略来时间过期键的删除不太现实。
惰性删除
惰性删除策略优点:对 CPU 时间友好。程序只会在取出键时才会判断是否删除,并且只作用到当前键上,其他过期键不会花费 CPU 时间去处理。
惰性删除策略缺点:对内存不友好。因为只有键被使用时才会去检查是否删除,如果有大量的键一直不被使用,那么这些键就算过期了也不会被删除,会一直占用着内存。这种可以理解为是一种内存泄漏——大量无用的数据一直占用着内存,并且不会被删除。
定期删除
相比较定时删除对CPU的不友好,惰性删除的对内存不友好。定期删除采用了一种折中的方式:
定期删除策略每隔一段时间执行一次删除过期键操作,并通过限制删除操作执行的时长和频率来减少删除操作对CPU时间的影响。
并且,通过定期删除过期键,有效的减少了过期键带来的内存浪费。
但删除的时长和频率比较难定义,因为:
如果频率太高或者时长太长,那么会占用大量的CPU时长。
如果过短又会出现内存浪费的情况。
因此。如果采用定期删除策略的话需要通过具体的业务场景来定义时长和频率。
Redis实际使用的是惰性删除+定期删除的策略。
通过这两种方式可以很好的利用CPU时间以及避免内存浪费的情况。接下来讲讲惰性删除以及定期删除的实现。
惰性删除策略的实现
惰性删除策略由 expireIfNeeded 函数实现,所有读写数据库的 Redis 命令在执行之前都会调用 exipreIfNeeded 函数对输入键进行检查。
如果键过期,会将键删除并返回空。
如果键没有过期,则不做操作。
定期删除策略的实现
定期删除策略由 activeExpireCucle 函数实现,被调用时,它在规定的时间内,分多次遍历服务器中的各个数据库,从数据库的 expires 字典中随机检查一部分键的过期时间,并删除其中的过期键。
函数每次运行时,都是从一定数量的数据库键中随机取一定数量的键进行检查,并删除其中的过期键。
有一个全局变量 current_db 会记录当前 activeExpireCycle 函数检查的进度,并且下一次 函数执行时,接着上一次的进度进行处理。如,当前 activeExpireCycle 函数执行到了 10, 讲 current_db = 10;然后下一次函数执行时,从 current_db 取到 10 继续执行。
当所有的数据库键都被检查完时, current_db = 0。
内存淘汰策略
当 Redis 的内存超过最大允许的内存之后,Redis 会触发内存淘汰策略,这和过期策略是完全不同的两个概念,经常有人把二者搞混,这两者一个是在正常情况下清除过期键,一个是在非正常情况下为了保证 Redis 顺利运行的保护策略。
Redis 内存淘汰策略有哪些?
在 4.0 版本之前 Redis 的内存淘汰策略有以下 6 种。
noeviction:不淘汰任何数据,当内存不足时,执行缓存新增操作会报错,它是 Redis 默认内存淘汰策略。
allkeys-lru:淘汰整个键值中最久未使用的键值。
allkeys-random:随机淘汰任意键值。
volatile-lru:淘汰所有设置了过期时间的键值中最久未使用的键值。
volatile-random:随机淘汰设置了过期时间的任意键值。
volatile-ttl:优先淘汰更早过期的键值。
而在 Redis 4.0 版本中又新增了 2 种淘汰策略:
volatile-lfu,淘汰所有设置了过期时间的键值中最少使用的键值;
allkeys-lfu,淘汰整个键值中最少使用的键值。
从以上内存淘汰策略中可以看出,allkeys-xxx 表示从所有的键值中淘汰数据,而 volatile-xxx 表示从设置了过期键的键值中淘汰数据。
这个内存淘汰策略我们可以通过配置文件来修改,redis.conf 对应的配置项是“maxmemory-policy noeviction”,只需要把它修改成我们需要设置的类型即可。
需要注意的是,如果使用修改 redis.conf 的方式,当设置完成之后需要重启 Redis 服务器才能生效。还有另一种简单的修改内存淘汰策略的方式,我们可以使用命令行工具输入“config set maxmemory-policy noeviction”来修改内存淘汰的策略,这种修改方式的好处是执行成功之后就会生效,无需重启 Redis 服务器。但它的坏处是不能持久化内存淘汰策略,每次重启 Redis 服务器之后设置的内存淘汰策略就会丢失。
算法
内存淘汰算法主要包含两种:LRU 淘汰算法和 LFU 淘汰算法。
(1)LRU( Least Recently Used,最近最少使用)淘汰算法:是一种常用的页面置换算法,也就是说最久没有使用的缓存将会被淘汰。
LRU 是基于链表结构实现的,链表中的元素按照操作顺序从前往后排列,最新操作的键会被移动到表头,当需要进行内存淘汰时,只需要删除链表尾部的元素即可。
Redis 使用的是一种近似 LRU 算法,目的是为了更好的节约内存,它的实现方式是给现有的数据结构添加一个额外的字段,用于记录此键值的最后一次访问时间。Redis 内存淘汰时,会使用随机采样的方式来淘汰数据,它是随机取 5 个值 (此值可配置) ,然后淘汰最久没有使用的数据。
(2)LFU(Least Frequently Used,最不常用的)淘汰算法:最不常用的算法是根据总访问次数来淘汰数据的,它的核心思想是“如果数据过去被访问多次,那么将来被访问的频率也更高”。
LFU 相对来说比 LRU 更“智能”,因为它解决了使用频率很低的缓存,只是最近被访问了一次就不会被删除的问题。如果是使用 LRU 类似这种情况数据是不会被删除的,而使用 LFU 的话,这个数据就会被删除。Redis 内存淘汰策略使用了 LFU 和近 LRU 的淘汰算法,具体使用哪种淘汰算法,要看服务器是如何设置内存淘汰策略的,也就是要看“maxmemory-policy”的值是如何设置的。