Redis过期删除策略和内存淘汰策略
过期删除策略
设置过期时间
- 只对key设置
1 | expire key n // key在n秒后过期 |
- 创建key的时候设置
1 | set key value ex n // 创建key,并且key在n秒后过期 |
- 查看某个key的存活时间
1 | ttl key |
判定key是否过期
1 | typedef struct redisDb { |
过期字典数据结构:
- key 是一个指针,指向某个键对象
- value 是一个 long long 类型的整数,这个整数保存了 key 的过期时间
判断策略:
- 字典实际上是哈希表,哈希表的最大好处就是让我们可以用 O(1) 的时间复杂度来快速查找
- 当我们查询一个 key 时,Redis 首先检查该 key 是否存在于过期字典中:
- 如果不在,则正常读取键值;
- 如果存在,则会获取该 key 的过期时间,然后与当前系统时间进行比对,如果比系统时间大,那就没有过期,否则判定该 key 已过期。
过期键删除策略
- 定时删除:给key创建过期时间的同时创建一个定时器,在过期时间来临的时候进行主动删除。好处是删除及时使得内存空间释放,坏处是定时器占用CPU时间
- 惰性删除:不主动删除过期键,每次从数据库访问 key 时,都检测 key 是否过期,如果过期则删除该key。好处是占用CPU时间少,坏处是容易造成内存泄漏
- 定期删除:每隔一段时间随机从数据库中取出一定数量的 key 进行检查,并删除其中的过期key。平衡了CPU和内存占用
- Redis采用的是最后两种,惰性删除和定期删除组合使用
Redis定期删除的流程
- 从过期字典中随机抽取 20 个 key
- 检查这 20 个 key 是否过期,并删除已过期的 key
- 如果本轮检查的已过期 key 的数量,超过 5 个(20/4),也就是「已过期 key 的数量」占比「随机抽取 key 的数量」大于 25%,则继续重复步骤 1;如果已过期的 key 比例小于 25%,则停止继续删除过期 key,然后等待下一轮再检查
内存淘汰策略
有哪些内存淘汰策略
- noevction:不淘汰任何数据,如果运行内存超过了最大设置内存,会不允许写入
- volatile:针对过期键
- lru:淘汰最久未访问到的数据
- lfu:淘汰使用频率最少的数据
- random:随机淘汰
- ttl:淘汰最久的过期键
- allkeys:针对所有键
- lru:淘汰最久未访问到的数据
- lfu:淘汰使用频率最少的数据
- random:随机淘汰
Redis的LRU算法
LRU,最近最久未使用算法:记录了每个key的最近访问时间,每次淘汰最久这个时间的key,但是redis的lru不是标准的,做了优化
优化:
- 标准lru需要维护双链表,开销很大,所以redis采用的是近似LRU算法
- 具体的是每次随机采样n个key,默认值是5,然后按照时间戳淘汰最久的那个。如果淘汰后内存还是不足继续随机采样淘汰。在3.0之后,redis LRU还维护了淘汰池,池中的数据按照访问时间进行排序。第一次随机选取的key都会放入池中,每次淘汰池中最久访问的key。
- 随后每次选取的key只有空闲时间(指的是没有访问到的时候)大于池中空间时间最小的key,才能放入其中。当池子装满了,需要新的key放入的时候,就将池子中最大的key淘汰
Redis的LFU算法
LRU 算法有一个问题,无法解决缓存污染问题,比如应用一次读取了大量的数据,而这些数据只会被读取这一次,那么这些数据会留存在 Redis 缓存中很长一段时间,造成缓存污染
所以, LFU 算法会记录每个数据的访问次数。当一个数据被再次访问时,就会增加该数据的访问次数。这样就解决了偶尔被访问一次之后,数据留存在缓存中很长一段时间的问题,相比于 LRU 算法也更合理一些
LFU 算法相比于 LRU 算法的实现,多记录了「数据的访问频次」的信息。Redis 对象的结构如下:
1 | typedef struct redisObject { |
Redis对象头中的lru字段,在LRU算法下和LFU算法下使用方式并不相同:
- 在 LRU 算法中,Redis对象头的24bits的lru字段是用来记录key的访问时间戳,因此在 LRU 模式下,Redis可以根据对象头中的lru字段记录的值,来比较最后一次key的访问时间长,从而淘汰最久未被使用的key
- 在 LFU 算法中,Redis对象头的24bits的lru字段被分成两段来存储,高16bit存储ldt(Last Decrement Time),低8bit存储logc(Logistic Counter)
lfu字段:
- ldt:用来记录key的访问时间戳;
- logc:用来记录key的访问频次,它的值越小表示使用频率越低,越容易淘汰,每个新加入的key的logc初始值为5
- logc 并不是单纯的访问次数,而是访问频次(访问频率),因为 logc 会随时间推移而衰减的。
在每次 key 被访问时,会先对 logc 做一个衰减操作,衰减的值跟前后访问时间的差距有关系,如果上一次访问的时间与这一次访问的时间差距很大 (ldt的作用),那么衰减的值就越大,这样实现的 LFU 算法是根据访问频率来淘汰数据的,而不只是访问次数。访问频率需要考虑 key 的访问是多长时间段内发生的。key 的先前访问距离当前时间越长,那么这个 key 的访问频率相应地也就会降低,这样被淘汰的概率也会更大。
对 logc 做完衰减操作后,就开始对 logc 进行增加操作,增加操作并不是单纯的 + 1,而是根据概率增加,如果 logc 越大的 key,它的 logc 就越难再增加
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 cloud_fly blog!