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承受不起,所以它需要对时间进行缓存,由一个定时任务,每毫秒更新一次时间缓存,获取时间都是从缓存中直接拿。