Redis设计与实现---缓存淘汰策略

Redis中的设有TTL的key在过期时是怎么被处理的?

Redis中所有的数据结构都可以设置ttl,如果设置的ttl的key太多,且redis是单线程的,那么删除这些过期的key时会不会造成卡顿呢?redis是怎么处理的?

设置了ttl的key集合

Redis会将设置了ttl的key都维护在一个字典里,会定时扫描这个字典,将过期的key删除。

定时扫描

Redis会每秒进行10次扫描字典,但是并不会遍历所有的key,而是采用了一种简单的贪心策略。

如:从字典中取20个key,然后删除其中过期的key,如果过期的key超过了1/4,则重复步骤。

惰性删除

除了定时扫描外,还有惰性策略去处理过期的key,即在使用到这个key时,才会去从字典中查询这个key的ttl,如果过期了再删除掉。

从库的过期策略

如果Redis采用的主从架构,我们知道,从库一般是用来读的,那么从库怎么的过期的key怎么处理呢?

答案是AOF。主库会在AOF中追加一条del命令,然后同步到从库中,从库根据AOF去同步数据时,就会执行这条del命令。

如果在同步AOF时,有请求访问从库读取数据,就会造成主从数据不一致。

lazyFree

使用del删除体积较大的key时,又或者使用FLUSHDB和FLUSHALL删除包含大量key的数据库时,会造成redis阻塞。

另外redis在清理过期的key时,如果碰巧碰到了体积较大的key时也会造成redis阻塞。

为了解决这个问题,redis4.0引入了lazyfree,他可以将del,flushdb,flushall这些命令放入后台线程处理,避免阻塞。

缓存淘汰算法

maxmemory

redis提供了maxmemory配置参数去限制内存超出期望大小。

当实际内存超出mamemory时,就会根据设置的策略(maxmemory-policy)进行内存的管理。

 maxmemory-policy

volatile-xxx 策略只会针对带过期时间的key 进行淘汰,allkeys-xxx 策略会对所有的 key 进行淘汰

Noeviction(默认)

当redis内存达到maxmemory时,不会继续写服务,但会执行del请求。

volatile-lru

ttl集合中最少使用的key被淘汰(LRU)。

volatile-ttl

ttl集合中ttl越小的优先被淘汰

volatile-random

随机淘汰ttl集合中的key

allkeys-lru

所有key中最少使用的key被淘汰

allkeys-random

所有key中随机淘汰key

如何选择策略

如果只是用Redis做缓存,那么就采用 allkeys-xxx。

如果Redis采用了持久化功能,那么采用volatile-xxx,这样可以保证未设置ttl的key不会丢失,从而持久化。

LRU

LRU( Least Recently Used),最早最近使用。由一个Hash表和一个双向链表组成。越靠近链表头部代表是最近使用的元素。越靠近链表尾部代表不被重用的元素。

当一个元素被访问时,会移动到链表头部。当空间满了时,会删除链表尾部的元素。

LRU的其他使用:MySQL Buffer Pool缓存页替换//TODO

LRU的组成与ZSet的底层数据结构类似。//TODO

近似LRU

 redis采用的是近似LRU的算法。为什么不采用LRU?因为LRU会消耗大量内存,对现有数据结构造成较大改动。

redis为每个key设置了一个24bit的字段记录最后一次被访问的时间戳

近似LRU执行过程:当redis内存超过maxmemory时,会随机采样(maxmemory-samples)个key,然后淘汰掉最旧的key,如果内存还是超过maxmemory,重复以上操作。

如何采样就是看maxmemory-policy 的配置,如果是 allkeys 就是从所有的 key 字典中随机,如果是 volatile 就从带过期时间的 key 字典中随机。每次采样多少个 key 看的是 maxmemory_samples 的配置,默认为 5。

怎么找到最旧的key?

1.取出Redis中缓存的全局时钟。(Redis内部是维护了一个24bit的时钟的,按秒算最大为194天)

2.取出每个key的24bit的内部时钟

3.全局时钟与内部时钟进行比较。

  • 如果内部时钟<194,那么用减法(全局时钟-内部时钟)比较出最小的。
  • 如果内部时钟>194,那么用加法(全局时钟+内部时钟)比较出最大的。

LFU

LFU算法是Redis4.0里面新加的一种淘汰策略。voliatile-lfu,allkeys-lfu

它的全称是Least Frequently Used,它的核心思想是根据key的最近被访问的频率进行淘汰,很少被访问的优先被淘汰,被访问的多的则被留下来。

LRU的弊端:如果一个很久不用的元素只是突然被用了一下,那么他就不会被淘汰。而有些key可能会被用到却被淘汰了。

LFU的出现避免了这种弊端,它采用计数器对key的使用次数排序,key被访问的越多,它的计数就越大,代表越热点。

计数器的实现

在近似LRU中,每个key维护了一个24bit的内部时钟,LFU将24bit拆分成前16位ldt代表内部时钟,后8位logc代表计数器。

为什么 Redis 要缓存系统时间戳

我们平时使用系统时间戳时,常常是不假思索地使用System.currentTimeInMillis或者time.time()来获取系统的毫秒时间戳。Redis不能这样,因为每一次获取系统时间戳都是一次系统调用,系统调用相对来说是比较费时间的,作为单线程的Redis承受不起,所以它需要对时间进行缓存,由一个定时任务,每毫秒更新一次时间缓存,获取时间都是从缓存中直接拿。

  • 34
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值