Redis-过期删除策略和内存淘汰策略
简介、我们先来看如下几个问题:
①、如何设置Redis键的过期时间 ?
②、设置完一个键的过期时间后,到了这个时间,这个键还能获取到么?假如获取不到那这个键还占据着内存吗 ?
③、如何设置Redis的内存大小?当内存满了之后,Redis有哪些内存淘汰策略?我们又该如何选择 ?
一、设置Redis键过期时间
1、Redis提供了四个命令来设置过期时间(生存时间)
①、expire :表示将键 key 的生存时间设置为 ttl 秒。
②、pexpire :表示将键 key 的生存时间设置为 ttl 毫秒。
③、expireat :表示将键 key 的生存时间设置为 timestamp 所指定的秒数时间戳。
④、pexpireat :表示将键 key 的生存时间设置为 timestamp 所指定的毫秒数时间戳。
注释:在Redis内部实现中,前面三个设置过期时间的命令最后都会转换成最后一个PEXPIREAT 命令来完成。
返回键的剩余生存时间
-
TTL :以秒的单位返回键 key 的剩余生存时间。
-
PTTL :以毫秒的单位返回键 key 的剩余生存时间。
2、Redis过期时间的判定
在Redis内部,每当我们设置一个键的过期时间时,Redis就会将该键带上过期时间存放到一个过期字典中。当我们查询一个键时,Redis便首先检查该键是否存在过期字典中,
如果存在,那就获取其过期时间。然后将过期时间和当前系统时间进行比对,比系统时间大,那就没有过期;反之判定该键过期。
二、过期删除策略
删除策略就是针对已过期数据的处理策略,已过期的数据不一定被立即删除,在不同的场景下使用不同的删除方式会有不同效果,在内存占用与 CPU 占用之间寻找一种平衡,
顾此失彼都会造成整体 Redis 性能的下降,甚至引发服务器宕机或内存泄露
针对过期数据有三种删除策略:
-
定时删除
-
惰性删除(被动删除)
-
定期删除
1、定时删除
在设置键的过期时间的同时,创建一个 定时器(timer)
,让定时器在键的过期时间到达时,立即执行对键的删除操作
-
优点:节约内存,到时就删除,快速释放掉不必要的内存占用
-
缺点:对 CPU 不友好,无论 CPU 此时负载多高均占用 CPU,会影响 Redis 服务器响应时间和指令吞吐量
-
总结:用处理器性能换取存储空间(拿时间换空间)
创建一个定时器需要用到 Redis 服务器中的时间事件,而时间事件的实现方式是无序链表,查找一个事件的时间复杂度为 O(N),并不能高效地处理大量时间事件,所以采用这种方式并不现实
2、惰性删除
数据到达过期时间不做处理,等下次访问到该数据时执行 expireIfNeeded() 判断:
-
如果输入键已经过期,那么 expireIfNeeded 函数将输入键从数据库中删除,接着访问就会返回空
-
如果输入键未过期,那么 expireIfNeeded 函数不做动作
所有的 Redis 读写命令在执行前都会调用 expireIfNeeded 函数进行检查,该函数就像一个过滤器,在命令真正执行之前过滤掉过期键
惰性删除的特点:
-
优点:节约 CPU 性能,删除的目标仅限于当前处理的键,不会在删除其他无关的过期键上花费任何 CPU 时间
-
缺点:内存压力很大,出现长期占用内存的数据,如果过期键永远不被访问,这种情况相当于内存泄漏
-
总结:用存储空间换取处理器性能(拿空间换时间)
3、定期删除
定期删除策略是每隔一段时间执行一次删除过期键操作,并通过限制删除操作执行的时长和频率来减少删除操作对 CPU 时间的影响
-
如果删除操作执行得太频繁,或者执行时间太长,就会退化成定时删除策略,将 CPU 时间过多地消耗在删除过期键上
-
如果删除操作执行得太少,或者执行时间太短,定期删除策略又会和惰性删除策略一样,出现浪费内存的情况
定期删除是周期性轮询 Redis 库中的时效性数据,从过期字典中随机抽取一部分键检查,利用过期数据占比的方式控制删除频度
-
Redis 启动服务器初始化时,读取配置 server.hz 的值,默认为 10,执行指令 info server 可以查看,每秒钟执行 server.hz 次 serverCron() → activeExpireCycle()
-
activeExpireCycle() 对某个数据库中的每个 expires 进行检测,工作模式:
-
轮询每个数据库,从数据库中取出一定数量的随机键进行检查,并删除其中的过期键,如果过期 key 的比例超过了 25%,则继续重复此过程,直到过期 key 的比例下降到 25% 以下,或者这次任务的执行耗时超过了 25 毫秒
-
全局变量 current_db 用于记录 activeExpireCycle() 的检查进度(哪一个数据库),下一次调用时接着该进度处理
-
随着函数的不断执行,服务器中的所有数据库都会被检查一遍,这时将 current_db 重置为 0,然后再次开始新一轮的检查
-
定期删除特点:
-
CPU 性能占用设置有峰值,检测频度可自定义设置
-
内存压力不是很大,长期占用内存的冷数据会被持续清理
-
周期性抽查存储空间(随机抽查,重点抽查)
三、内存淘汰策略
我们看到,通过过期删除策略,对于某些永远使用不到的键,并且多次定期删除也没选定到并删除,那么这些键同样会一直驻留在内存中,
又或者在Redis中存入了大量的键,这些操作可能会导致Redis内存不够用,这时候就需要Redis的内存淘汰策略了。
1、逐出算法
数据淘汰策略:当新数据进入 Redis 时,在执行每一个命令前,会调用 freeMemoryIfNeeded() 检测内存是否充足。如果内存不满足新加入数据的最低存储要求,
Redis 要临时删除一些数据为当前指令清理存储空间,清理数据的策略称为逐出算法
逐出数据的过程不是 100% 能够清理出足够的可使用的内存空间,如果不成功则反复执行,当对所有数据尝试完毕,如不能达到内存清理的要求,出现 Redis 内存打满异常:
(error) OOM command not allowed when used memory >'maxmemory'
2、策略配置
Redis 如果不设置最大内存大小或者设置最大内存大小为 0,在 64 位操作系统下不限制内存大小,在 32 位操作系统默认为 3GB 内存,一般推荐设置 Redis 内存为最大物理内存的四分之三
内存配置方式:
-
通过修改文件配置(永久生效):修改配置文件 maxmemory 字段,单位为字节
-
通过命令修改(重启失效):
-
config set maxmemory 104857600:设置 Redis 最大占用内存为 100MB
-
config get maxmemory:获取 Redis 最大占用内存
-
info :可以查看 Redis 内存使用情况,used_memory_human 字段表示实际已经占用的内存,maxmemory 表示最大占用内存
-
影响数据淘汰的相关配置如下,配置 conf 文件:
-
每次选取待删除数据的个数,采用随机获取数据的方式作为待检测删除数据,防止全库扫描,导致严重的性能消耗,降低读写性能
maxmemory-samples count
-
达到最大内存后的,对被挑选出来的数据进行删除的策略
maxmemory-policy policy
数据删除的策略 policy:3 类 8 种
第一类:检测设置过期时间的数据(可能会过期的数据集 server.db[i].expires):
volatile-lru # 对设置了过期时间的 key 选择最近最少使用的数据淘汰 最近最少使用的键
volatile-lfu # 对设置了过期时间的 key 选择最不常用数据淘汰
volatile-ttl # 对设置了过期时间的 key 选择将要过期的数据淘汰
volatile-random # 对设置了过期时间的 key 选择任意数据淘汰
第二类:检测全库数据(所有数据集 server.db[i].dict ):
allkeys-lru # 对所有 key 选择最近最少使用的数据淘汰
allkeLyRs-lfu # 对所有 key 选择最不常用的数据淘汰
allkeys-random # 对所有 key 选择任意数据淘汰,相当于随机
第三类:放弃数据驱逐
no-enviction # 禁止驱逐数据(redis4.0中默认策略),会引发OOM(Out Of Memory)
在redis.conf 配置文件中,可以设置淘汰方式: