文章目录
- 一、前言
- 二、生存时间和三种过期策略
- 2.1 生存时间的设置与读取
- 2.2 生存时间的底层保存(过期字典)
- 2.3 过期字典如何处理过期key(即过期key的删除):定时删除 惰性删除 定期删除
- 三、淘汰策略
- 3.1 最大内存设置
- 3.2 淘汰策略
- 四、尾声
一、前言
问题:redis为什么需要内存回收机制?
回答:内存回收就是数据过期,redis的数据都是存放在内存里面,当然可以通过RDB和AOF持久化到磁盘,但是运行的时候还是要加载到内存里面的,如果key-value键值对越来越多,撑爆了内存引起机器重启等不好境况,所以redis自带内存回收机制,清理掉一些访问频率不高的key-value键值对。
本文内容:
内存使用未达到最大内存极限,有三种删除方式:定时删除、惰性删除、定期删除;
内存使用已达到最大内存极限,有八种淘汰方式:volatile-lru、allkeys-lru、allkeys-lru、allkeys-lfu、volatile-random、allkeys-random、volatile-ttl、noeviction。
二、生存时间和三种过期策略
过期策略包括三种:立即过期 惰性过期 定期过期
2.1 生存时间的设置与读取
介绍四个命令(和TTL time to live 生存时间有关的),用表格清晰些,如下:
命令 | 含义 |
EXPIRE | 设置剩余生存时间,以秒为单位,将键key的生存时间设置为ttl秒 |
PEXPIRE | 设置剩余生存时间,以毫秒为单位,将键key的生存时间设置为ttl毫秒 |
EXPIREAT | 设置剩余生存时间,以秒为单位,将键key的生存时间设置为timestamp所指定的秒数时间戳 |
PEXPIREAT | 设置剩余生存时间,以毫秒为单位,将键key的生存时间设置为timestamp所指定的秒数时间戳 |
TTL | 返回指定key的剩余生存时间,以秒为单位 |
PTTL | 返回指定key的剩余生存时间,以毫秒为单位 |
这个表格给出了指定key的过期时间的存储,这里需要注意一个点,设置指定key生存时间一共有四个命令EXPIRE PEXPIRE EXPIREAT PEXPIREAT,这里展示四个命令底层关系,如图:
我们可以看到,四个命令底层关系:四个设置生存时间的命令,底层最终都是使用PEXPIREAT命令去实现的。
2.2 生存时间的底层保存(过期字典)
redis是基于key-value存储的一个非关系型数据库,对于每一个记录的key,都有一个生存时间TTL,上面介绍了指定key的生存时间的读写,那么,redis中每一个key的生存时间是底层是如何存储的呢?答案是使用“过期字典”存储。
过期字典引入:redisDB结构的expires字典保存了数据库中所有键的过期时间,这个expires字典就是过期字典。
过期字典的键:是一个指针,这个指针指向键空间中的某个键对象(也即是某个数据库键)。
过期字典的值:是一个long long类型的整数 ,这个整数保存了键所指向的数据库键的过期时间,即—个毫秒精度的UNIX时间戳。
展示了一个带有过期字典的数据库例子,在这个例子中,键空间保存了数据库中的所有键值对,而过期时间则保存了数据库中所有按键值对的过期时间。
生存时间(过期时间)的添加和删除略过。
2.3 过期字典如何处理过期key(即过期key的删除):定时删除 惰性删除 定期删除
被动删除/惰性删除:当客户端尝试访问某个key时,发现当前key已经过期了,就直接删除这个key。当然,有可能会存在一些key,一直没有客户端访问,就会导致这部分key一直占用内存,因此加了一个 主动删除/定时删除 方式。
主动删除/定时删除:Redis定期扫描期间中的key进行删除,它的删除策略是:
(1) 从过期键中随机获取20个key,删除这20个key中已经过期的key。
(2) 如果在这20个key中有超过25%的key过期,则重新执行当前步骤。实际上这是利用了一种概率算法。
一表总结,如下:
删除方式(过期key删除) | 解释名称 | 含义 | 删除类型 | 优点 | 缺点 | 备注 |
定时删除 | 内部含有定时器,故称为定时删除 | 在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即执行对键的删除操作。 | 主动删除 | 对内存友好,过期key尽快被删除,被释放过期key的内存 | 对CPU时间不友好,较快删除key,占用CPU时间 | 该方式redis服务器中需要创建大量定时器,不现实,舍去。 |
惰性删除 | 一定要等到使用该键的时候才删除过期key,比较懒惰,故称为惰性删除 | 放任键不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键;如果没有过期,就返回该键。 | 被动删除 | 对CPU时间友好,过期key尽可能慢地删除,取出时才检查是否过期,尽可能少的占用CPU时间 | 对内存不友好,过期地key删除不及时,一定要等到再次取出时才检查删除,这段时间内占用内存 | 由于存在着很多过期的key没有及时被删除,容易造成内存泄露 |
定期删除 | 均衡定时删除和惰性删除,得到一个合适的时间段删除,故称为定期删除 | 每隔一段时间,程序就会对数据库进行一个检查,删除里面的过期键。至于要删除多少个过期键,以及要检查多少个数据库,则由算法决定。 | 主动删除 | 定时删除和惰性删除的综合,合理使用CPU和内存 | 难以确定删除操作执行的时长和频率 | 若删除频繁,则接近定时删除,消耗太多CPU时间;若删除太少,则接近惰性删除,消耗内存。 |
总结:对于设置了过期时间的key,到了过期时间会使用定时删除掉;对于没有设置过期时间的key,Redis中同时使用了惰性删除和定期删除两种过期策略。
三、淘汰策略
Redis的内存淘汰策略,是指当内存使用达到最大内存极限的时候,需要使用淘汰算法来决定清除掉哪些数据,以保证新数据的写入。所以,这里涉及到两个问题:
第一,最大内存极限数值是多少?回答:可以自己设置
第二,常用的淘汰策略有哪些?
回答:淘汰策略分为两种
LRU,Least Recently Used:最近最少使用
LFU,Least Frequently Used
内存淘汰策略执行的一个前提是 内存使用达到最大内存极限,一般来说,只要三种删除 定时删除 惰性删除 定期删除 搞好,不会出现这种情况。
3.1 最大内存设置
最大内存极限数值是多少?如下:
./redis-cli --raw
config get maxmemory
config set
3.2 淘汰策略
查看当前的redis使用的哪种淘汰策略,需要查看redis.conf文件,如下:
先看一下都有哪些淘汰策略?一共八种。
从前缀针对的对象来分:volatile针对设置了ttl的key,allkeys是针对所有key
从后缀使用的方法来分:
LRU,Least Recently Used:最近最少使用。判断最近被使用的时间,目前最远的数据优先被淘汰。
LFU,Least Frequently Used:最小频率使用。Redis 4.0版本新增。
random:随机删除。
策略 | 含义 |
volatile-lru | 针对带有过期时间的键,选择最远使用,直到腾出内存为止。如果没有可以删除的键对象,回退到 noeviction |
allkeys-lru | 针对所有键(不管有没有设置超时时间),选择最远使用,直到腾出内存为止。 |
volatile-lfu | 针对带有过期时间的键,选择最少频率使用的,直到腾出内存为止。如果没有可以删除的键对象,回退到 noeviction |
allkeys-lfu | 针对所有键(不管有没有设置超时时间),选择最少频率使用的,直到腾出内存为止。 |
volatile-random | 针对带有过期时间的键,随机选择删除,直到腾出内存为止。如果没有可以删除的键对象,回退到 noeviction |
allkeys-random | 针对所有键(不管有没有设置超时时间),随机选择删除,直到腾出内存为止。 |
volatile-ttl | 针对带有过期时间的键,删除最近ttl将要过期的数据。如果没有可以删除的键对象,回退到 noeviction |
noeviction | 默认策略,不会删除操作,会拒绝所有写入操作并返回客户端错误信息OOM,此时Redis只响应读操作 |
如果没有设置ttl,那么 volatile-lru volatile-lfu volatile-random volatile-ttl 相当于 noeviction ,不做内存回收。
建议使用 volatile-lru,在保证正常服务的情况下,优先删除最近最少使用的key.
./redis-cli --raw
config get maxmemory-policy
config set
四、尾声
Redis内存回收包括两个部分,如下:
内存使用未达到最大内存极限,有三种删除方式:定时删除、惰性删除、定期删除;
内存使用已达到最大内存极限,有八种淘汰方式:allkeys-lru、volatile-lru、allkeys-lfu、volatile-lfu、volatile-random、allkeys-random、volatile-ttl、noeviction。
问题1:为什么Redis需要淘汰策略,而不是扫描全部设置了过期时间的key呢?
回答1:因为Redis里面所有的key都有过期时间,如果全部扫描压力太大,全扫描跟你去查数据库不带where条件不走索引全表扫描一样,不显示。
问题2:如果一直没随机到很多key,里面不就存在大量的无效key了,如何处理?
回答2:定时删除+惰性删除
定时删除:每隔一段实践删除一些,对CPU不友好,对内存友好;
惰性删除:不主动删,我等你来查询了我看看你过期没,过期就删去redis中这个key,没过期就取出,对CPU友好,对内存不友好。
问题3:如果某些key,定时删除没删,我也没查询(则惰性删除也没有删除),如何?
内存淘汰机制!官网上给到的内存淘汰机制是六个:
(1) noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
(2) allkeys-lru: 尝试回收距离现在最远使用的键(LRU),使得新添加的数据有空间存放。
(3) volatile-lru: 尝试回收距离现在最远使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
(4) allkeys-lfu:尝试回收最少频率使用的键(LFU),使得新添加的数据有空间存放。
(5) volatile-lfu:尝试回收最少频率使用的键(LFU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
(6) allkeys-random: 回收随机的键使得新添加的数据有空间存放。
(7) volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
(8) volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。
最后,如果没有键满足回收的前提条件的话,策略volatile-lru, volatile-random以及volatile-ttl就和noeviction 差不多了。
Redis内存回收机制,完成了。