redis 开启lazy-free机制,内部是否加锁了?

redis 开启lazy-free机制后,有些操作会在另一个线程执行,那么对于有些共享且可修改的数据,是不是就要使用同步机制(比如加锁)。

如果使用了同步机制,那单线程还有意义吗,直接支持多线程得了。

回复
阅读 1.6k
2 个回答

没有锁。

4.0 引入 Lazy Free 时为了避免锁机制带来的性能下降,直接就干掉了共享对象,换成了数据拷贝,这样就不需要锁了。也就是说,表面上看起来这只是个影响删除操作的新特性,实际上整个底层的存储和数据结构都发生了变化。

P.S. 6.0 真的就引入多线程 I/O 了哟。

  1. lazy-free 是 4.0 新增的功能,默认是关闭的,需要手动开启
  2. 开启 lazy-free 时,有多个「子选项」可以控制,分别对应不同场景下,是否开启异步释放内存:
    a) lazyfree-lazy-expire:key 在过期删除时尝试异步释放内存
    b) lazyfree-lazy-eviction:内存达到 maxmemory 并设置了淘汰策略时尝试异步释放内存
    c) lazyfree-lazy-server-del:执行 RENAME/MOVE 等命令或需要覆盖一个 key 时,Redis 内部删除旧 key 尝试异步释放内存
    d) replica-lazy-flush:主从全量同步,从库清空数据库时异步释放内存
  3. 即使开启了 lazy-free,但如果执行的是 DEL 命令,则还是会同步释放 key 内存,只有使用 UNLINK 命令才「可能」异步释放内存
  4. Redis 6.0 版本新增了一个新的选项 lazyfree-lazy-user-del,打开后执行 DEL 就与 UNLINK 效果一样了
  5. 最关键的一点,开启 lazy-free 后,除 replica-lazy-flush 之外,其它选项都只是「可能」异步释放 key 的内存,并不是说每次释放 key 内存都是丢到后台线程的
  6. 开启 lazy-free 后,Redis 在释放一个 key 内存时,首先会评估「代价」,如果代价很小,那么就直接在「主线程」操作了,「没必要」放到后台线程中执行(不同线程传递数据也会有性能消耗)
  7. 什么情况才会真正异步释放内存?这和 key 的类型、编码方式、元素数量都有关系(详见 lazyfreeGetFreeEffort 函数):

    a) 当 Hash/Set 底层采用哈希表存储(非 ziplist/int 编码存储)时,并且元素数量超过 64 个
    b) 当 ZSet 底层采用跳表存储(非 ziplist 编码存储)时,并且元素数量超过 64 个
    c) 当 List 链表节点数量超过 64 个(注意,不是元素数量,而是链表节点的数量,List 底层实现是一个链表,链表每个节点是一个 ziplist,一个 ziplist 可能有多个元素数据)

只有满足以上条件,在释放 key 内存时,才会真正放到「后台线程」中执行,其它情况一律还是在主线程操作。

也就是说 String(不管内存占用多大)、List(少量元素)、Set(int 编码存储)、Hash/ZSet(ziplist 编码存储)这些情况下的 key,在释放内存时,依旧在「主线程」中操作。

  1. 可见,即使打开了 lazy-free,String 类型的 bigkey,在删除时依旧有「阻塞」主线程的风险。所以,即便 Redis 提供了 lazy-free,还是不建议在 Redis 存储 bigkey
  2. Redis 在释放内存「评估」代价时,不是看 key 的内存大小,而是关注释放内存时的「工作量」有多大。从上面分析可以看出,如果 key 内存是连续的,释放内存的代价就比较低,则依旧放在「主线程」处理。如果 key 内存不连续(包含大量指针),这个代价就比较高,这才会放在「后台线程」中执行
引用自极客时间《Redis 源码剖析与实战》- Lazy Free会影响缓存替换吗?评论区大神:Kaito
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏