Redis - 懒惰删除

Redis - 懒惰删除

​ 一直以来我们认为Redis是单线程的, 单线程为Redis带来了代码的简洁性和丰富多彩的数据结构。不过Redis内部实际上并不是只有一个主线程, 它还有几个异步线程专门用来处理一些耗时操作。

Redis为什么要懒惰删除?

​ 删除指令del会直接释放对象的内存, 大部分情况下, 这个指令非常快, 没有明显延迟。不过如果删除的key是一个非常大的对象, 比如包含了千万元素的hash, 那么删除操作就会导致单线程卡顿。

​ Redis为了解决这个卡顿问题, 在4.0版本引入了unlink指令, 它能对删除操作进行懒处理,丢给后退线程来异步回收内存。

127.0.0.1:6379> unlink key
(integer) 1

​ 那么这里是否有线程安全问题, 会不会出现多个线程同时并发修改数据结构的情况存在呢。

​ 关于这点, 可以打个比方, 可以将整个Redis内存里面所有有效数据想象成一棵大树。 当unlink指令发出时, 它只是把大树中的一个树枝折断, 然后扔到旁边的火堆中焚烧(异步线程池)。 树枝离开大树的一瞬间, 它旧再也无法被主线程中的其它指令访问到了, 因为主线程只会沿着这个大树来访问。

flush

​ Redis提供了flushdb和flushall指令, 用来情空数据库, 这也是极其缓慢的操作。Redis4.0同样给这两个指令带来异步化, 在指令后面增加async参数就可以将整个大树连根拔起, 扔给后台线程慢慢焚烧。

127.0.0.1:6379> flushall async
OK

异步队列

​ 主线程将对象的引用从[大树]中摘除后, 会将这个key的内存回收操作包装成一个任务, 塞进异步任务队列, 后台线程会从这个异步队列中取任务。 任务队列被主线程和异步队列同时操作, 所以必须时一个线程安全的队列。

​ 并不是所有的unlink操作都会延后处理, 如果对应的key所占用的内存很小, 延后处理就没有必要了, 这时候Redis会将对应的key内存立即回收, 跟del指令一样。

AOF Sync也很慢

​ Redis需要每秒一次(可配置)同步AOF日志到磁盘, 确保消息尽量不丢失, 需要调用sync函数, 这个操作会比较耗时, 会导致主线程的效率下降, 所以Redis也将这个操作一道异步线程来完成。 执行AOF Sync操作的线程是一个独立的异步线程, 和前面的懒惰删除线程不是一个线程, 同样它也有一个属于自己的任务队列, 队列里只用来存放AOF Sync任务。

更多异步删除点

​ Redis回收内存除了del指令和flush之外, 还会存在于在key的过期、LRU淘汰、rename指令及从库全量同步时接收完rdb文件后立即进行flush操作。

​ Redis4.0为这些删除点也带来了异步删除机制, 打开这些点需要额外的配置选项。

1、slave-lazy-flush 从库接收完rdb文件后的flush操作
2、lazyfree-lazy-eviction 内存达到maxmemory时进行淘汰
3、lazefree-lazy-expire key 过期删除
4、lazyfree-lazy-server-del rename 指令删除destKey
posted @ 2020-09-24 10:43  phper-liunian  阅读(717)  评论(0编辑  收藏  举报