Redis源码剖析与实战
蒋德钧
中科院计算所副研究员
新⼈⾸单¥59.9
2698 人已学习
课程目录
已更新 18 讲 / 共 33 讲
0/4登录后,你可以任选4讲全文学习。
课前导读 (2讲)
开篇词 | 阅读Redis源码能给你带来什么?
免费
01 | 带你快速攻略Redis源码的整体架构
数据结构模块 (6讲)
02 | 键值对中字符串的实现,用char*还是结构体?
03 | 如何实现一个性能优异的Hash表?
04 | 内存友好的数据结构该如何细化设计?
05 | 有序集合为何能同时支持点查询和范围查询?
06 | 从ziplist到quicklist,再到listpack的启发
07 | 为什么Stream使用了Radix Tree?
事件驱动框架和执行模型模块 (7讲)
08 | Redis server启动后会做哪些操作?
09 | Redis事件驱动框架(上):何时使用select、poll、epoll?
10 | Redis事件驱动框架(中):Redis实现了Reactor模型吗?
11 | Redis事件驱动框架(下):Redis有哪些事件?
12 | Redis真的是单线程吗?
13 | Redis 6.0多IO线程的效率提高了吗?
14 | 从代码实现看分布式锁的原子性保证
缓存模块 (3讲)
15 | 为什么LRU算法原理和代码实现不一样?
16 | LFU算法和其他算法相比有优势吗?
17 | Lazy Free会影响缓存替换吗?
Redis源码剖析与实战
15
15
1.0x
00:00/00:00
登录|注册

17 | Lazy Free会影响缓存替换吗?

你好,我是蒋德钧。
Redis 缓存淘汰算法的目的,其实是为了在 Redis server 内存使用量超过上限值的时候,筛选一些冷数据出来,把它们从 Redis server 中删除,以保证 server 的内存使用量不超出上限。我们在前两节课,已经分别学习了 Redis 源码对 LRU 算法和 LFU 算法的实现,这两种算法在最后淘汰数据的时候,都会删除被淘汰的数据。
不过,无论是 LRU 算法还是 LFU 算法,它们在删除淘汰数据时,实际上都会根据 Redis server 的 lazyfree-lazy-eviction 配置项,来决定是否使用 Lazy Free,也就是惰性删除。
惰性删除是 Redis 4.0 版本后提供的功能,它会使用后台线程来执行删除数据的任务,从而避免了删除操作对主线程的阻塞。但是,后台线程异步删除数据能及时释放内存吗?它会影响到 Redis 缓存的正常使用吗?
今天这节课,我就来给你介绍下惰性删除在缓存淘汰时的应用。了解这部分内容,你就可以掌握惰性删除启用后,会给 Redis 缓存淘汰和内存释放带来的可能影响。这样,当你在实际应用中,遇到 Redis 缓存内存容量的问题时,你就多了一条排查思路了。
好,那么接下来,我们就先来看下缓存淘汰时的数据删除的基本过程。不过在了解这个删除过程之前,我们需要先了解下 Redis server 启动惰性删除的配置。因为在 Redis 源码中,有不少地方都会根据 server 是否启动惰性删除,来执行不同的分支操作。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/1000字
划线
笔记
复制
该试读文章来自付费专栏《Redis源码剖析与实战》,如需阅读全部文章,
请订阅文章所属专栏新⼈⾸单¥59.9
立即订阅
登录 后留言

精选留言(2)

  • Kaito
    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,在释放内存时,依旧在「主线程」中操作。

    8、可见,即使打开了 lazy-free,String 类型的 bigkey,在删除时依旧有「阻塞」主线程的风险。所以,即便 Redis 提供了 lazy-free,还是不建议在 Redis 存储 bigkey

    9、Redis 在释放内存「评估」代价时,不是看 key 的内存大小,而是关注释放内存时的「工作量」有多大。从上面分析可以看出,如果 key 内存是连续的,释放内存的代价就比较低,则依旧放在「主线程」处理。如果 key 内存不连续(包含大量指针),这个代价就比较高,这才会放在「后台线程」中执行

    课后题:freeMemoryIfNeeded 函数在使用后台线程,删除被淘汰数据的过程中,主线程是否仍然可以处理外部请求?

    肯定是可以继续处理请求的。

    主线程决定淘汰这个 key 之后,会先把这个 key 从「全局哈希表」中剔除,然后评估释放内存的代价,如果符合条件,则丢到「后台线程」中执行「释放内存」操作。

    之后就可以继续处理客户端请求,尽管后台线程还未完成释放内存,但因为 key 已被全局哈希表剔除,所以主线程已查询不到这个 key 了,对客户端来说无影响。
    2021-09-02
    1
    4
  • 一步
    现在有个问题,如果配置的 后台线程删除,在删除前后计算 内存使用量的时候 (long long)zmalloc_used_memory(); 是不是就不准了?这时候 key 和 value 的内存还没有得到释放的,只是把全局和失效 哈希表里面的删除了
    2021-09-03
收起评论
2
返回
顶部