谈谈 Redis 的过期策略


谈谈 Redis 的过期策略

文章插图
 
在日常开发中,我们使用 redis 存储 key 时通常会设置一个过期时间,但是 Redis 是怎么删除过期的 key,而且 Redis 是单线程的,删除 key 会不会造成阻塞 。要搞清楚这些,就要了解 Redis 的过期策略和内存淘汰机制 。
Redis采用的是定期删除 + 懒惰删除策略 。
定期删除策略Redis 会将每个设置了过期时间的 key 放入到一个独立的字典中,默认每 100ms 进行一次过期扫描:
  1. 随机抽取 20 个 key
  2. 删除这 20 个key中过期的key
  3. 如果过期的 key 比例超过 1/4,就重复步骤 1,继续删除 。
为什不扫描所有的 key?
Redis 是单线程,全部扫描岂不是卡死了 。而且为了防止每次扫描过期的 key 比例都超过 1/4,导致不停循环卡死线程,Redis 为每次扫描添加了上限时间,默认是 25ms 。
如果客户端将超时时间设置的比较短,比如 10ms,那么就会出现大量的链接因为超时而关闭,业务端就会出现很多异常 。而且这时你还无法从 Redis 的 slowlog 中看到慢查询记录,因为慢查询指的是逻辑处理过程慢,不包含等待时间 。
如果在同一时间出现大面积 key 过期,Redis 循环多次扫描过期词典,直到过期的 key 比例小于 1/4 。这会导致卡顿,而且在高并发的情况下,可能会导致缓存雪崩 。
为什么 Redis 为每次扫描添的上限时间是 25ms,还会出现上面的情况?
因为 Redis 是单线程,每个请求处理都需要排队,而且由于 Redis 每次扫描都是 25ms,也就是每个请求最多 25ms,100 个请求就是 2500ms 。
如果有大批量的 key 过期,要给过期时间设置一个随机范围,而不宜全部在同一时间过期,分散过期处理的压力 。
从库的过期策略从库不会进行过期扫描,从库对过期的处理是被动的 。主库在 key 到期时,会在 AOF 文件里增加一条 del 指令,同步到所有的从库,从库通过执行这条 del 指令来删除过期的 key 。
因为指令同步是异步进行的,所以主库过期的 key 的 del 指令没有及时同步到从库的话,会出现主从数据的不一致,主库没有的数据在从库里还存在 。
懒惰删除策略Redis 为什么要懒惰删除(lazy free)?
删除指令 del 会直接释放对象的内存,大部分情况下,这个指令非常快,没有明显延迟 。不过如果删除的 key 是一个非常大的对象,比如一个包含了千万元素的 hash,又或者在使用 FLUSHDB 和 FLUSHALL 删除包含大量键的数据库时,那么删除操作就会导致单线程卡顿 。
redis 4.0 引入了 lazyfree 的机制,它可以将删除键或数据库的操作放在后台线程里执行, 从而尽可能地避免服务器阻塞 。
unlinkunlink 指令,它能对删除操作进行懒处理,丢给后台线程来异步回收内存 。
> unlink keyOKflushflushdb 和 flushall 指令,用来清空数据库,这也是极其缓慢的操作 。Redis 4.0 同样给这两个指令也带来了异步化,在指令后面增加 async 参数就可以将整棵大树连根拔起,扔给后台线程慢慢焚烧 。
> flushall asyncOK异步队列主线程将对象的引用从「大树」中摘除后,会将这个 key 的内存回收操作包装成一个任务,塞进异步任务队列,后台线程会从这个异步队列中取任务 。任务队列被主线程和异步线程同时操作,所以必须是一个线程安全的队列 。
不是所有的 unlink 操作都会延后处理,如果对应 key 所占用的内存很小,延后处理就没有必要了,这时候 Redis 会将对应的 key 内存立即回收,跟 del 指令一样 。
更多异步删除点Redis 回收内存除了 del 指令和 flush 之外,还会存在于在 key 的过期、LRU 淘汰、rename 指令以及从库全量同步时接受完 rdb 文件后会立即进行的 flush 操作 。
Redis4.0 为这些删除点也带来了异步删除机制,打开这些点需要额外的配置选项 。
  • slave-lazy-flush 从库接受完 rdb 文件后的 flush 操作
  • lazyfree-lazy-eviction 内存达到 maxmemory 时进行淘汰
  • lazyfree-lazy-expire key 过期删除
  • lazyfree-lazy-server-del rename 指令删除 destKey
内存淘汰机制Redis 的内存占用会越来越高 。Redis 为了限制最大使用内存,提供了 redis.conf 中的配置参数 maxmemory 。当内存超出 maxmemory,Redis 提供了几种内存淘汰机制让用户选择,配置 maxmemory-policy: