当前位置:首页 > 问答 > 正文

Redis里头多线程怎么搞过期管理,实践经验和一些思路分享

关于Redis内部如何处理过期键的问题,其实是一个挺有意思的设计点,很多人知道Redis是单线程的,但处理过期这件事上,它并不是完全“傻等”或者全靠单线程来扫,而是用了几种策略组合起来干活的,这里面有一些实践经验和思路可以分享。

核心思路:惰性删除 + 定期删除

Redis官方文档和相关的技术讨论里都明确提到了,它主要依靠两种方法来管理过期键:惰性删除和定期删除,这两种方法都不是在键 exactly 到过期时间点的那一刻就立刻删除,这是一种权衡。

惰性删除:用到的时候才检查

Redis里头多线程怎么搞过期管理,实践经验和一些思路分享

这个最好理解,就是当客户端发起一个命令,尝试访问某个键的时候(比如GET key),Redis会先检查一下这个键是否设置了过期时间,以及是否已经过期了,如果过期了,那么Redis会立刻把这个键删除,然后返回一个空值(nil))给客户端,就好像这个键从来不存在一样。

  • 实践经验:
    • 优点: 这种方式非常省资源,因为只有在键被实际访问时才会触发删除操作,如果一堆键过期了但一直没人访问,它们就会暂时赖在内存里,但不会消耗任何CPU时间去做删除检查,这对于CPU资源是种保护。
    • 缺点: 如果有很多键过期后长时间没有被访问,那么它们就成了“内存垃圾”,无法被及时释放,这纯粹是一种空间换时间的策略,在实际应用中,如果你的应用有大量“一次性”的键,之后再也不用了,那么依赖惰性删除会导致内存使用率居高不下。

定期删除:主动找活儿干

光靠惰性删除不行,内存可能会被垃圾占满,所以Redis又启动了一个“后台任务”(注意,这个任务也是在主线程中周期性执行的,并不是真正的后台线程),定期地随机抽查一部分设置了过期时间的键,并把其中已经过期的键删除掉。

Redis里头多线程怎么搞过期管理,实践经验和一些思路分享

  • 具体过程(根据Redis源码和设计文档): 这个定期任务并不是扫描整个数据库,那样会卡住主线程太久,它是这样工作的:

    1. 每次任务,从一定数量的数据库中(默认16个),随机抽取一定数量(默认20个)的设置了过期时间的键进行检查。
    2. 删除其中所有已经过期的键。
    3. 如果发现本轮检查中,过期的键比例超过了25%,那就立即重复步骤1,再随机抽20个键进行检查删除,这样做的原因是,如果过期键很多,说明“垃圾”很多,那就得抓紧多清理几轮。
    4. 如果过期键比例不高,或者已经达到了一定的时间限制(避免长时间阻塞),本次定期任务就结束,等待下一次触发。
  • 实践经验:

    • 优点: 这是对惰性删除的有效补充,能够在一定程度上主动释放那些不再被访问的过期键所占用的内存。
    • 缺点: 它仍然是一个近似算法,无法保证所有过期键都被立刻删除,删除的及时性取决于定期任务的执行频率和每次检查的力度,这个任务毕竟是在主线程跑的,如果某次需要清理的过期键非常多,还是可能会引起短暂的延迟波动,在Redis的配置文件中,你可以通过hz参数来调整这个定期任务每秒执行的次数,默认是10,调高它可以让过期键更及时地被清理,但也会消耗更多的CPU。

一个重要的误解澄清:过期键的删除并非由额外线程完成

Redis里头多线程怎么搞过期管理,实践经验和一些思路分享

这里要特别强调一点,无论是惰性删除还是上面说的定期删除,在Redis 6.0版本之前,所有的删除动作都是由主线程(单线程)同步完成的,并没有一个独立的“过期清理线程”在后台运行,如果某个键关联的值非常大,删除它本身就是一个耗时的操作,那么无论是客户端访问触发(惰性删除)还是定期任务触发,都会导致主线程被阻塞一下,从而影响其他命令的执行。

Redis 6.0引入的多线程与过期管理

Redis 6.0开始支持了I/O多线程,但这和过期键删除是两回事,I/O多线程指的是使用额外的线程来处理网络数据的读写和协议解析,而真正的命令执行(包括键的查找、过期检查、删除操作)依然是在主线程上串行进行的。过期键的管理核心逻辑仍然没有变,还是主线程的惰性删除+定期删除

有一些思路和实践是值得分享的:

  • 思路借鉴: 虽然Redis核心没有变,但这种把耗时操作(比如大键删除)从关键路径上剥离出去的思想是共通的,在一些自研系统或者对Redis的定制中,有人会尝试将删除特别大的过期键这种任务,扔到一个额外的线程或任务队列中去异步处理,从而避免阻塞主线程,但这需要非常小心地处理数据一致性和并发安全问题。
  • 实践经验:
    • 避免大键: 最根本的实践建议是,尽量不要在Redis中存储非常大的键(比如一个Hash里有百万个字段,或者一个String值有几十MB),大键的删除,无论用什么策略,都是潜在的风险点。
    • 监控内存和过期键数量: 使用info memoryinfo stats命令关注内存碎片率、已过期但未删除的键数量等指标,如果发现有过期键堆积,可以适当调高hz参数,但要监控CPU变化。
    • 考虑使用Redis模块或代理: 对于一些极端场景,可以考虑使用Redis的模块功能编写自定义逻辑,或者通过代理层来实现更复杂的过期和淘汰策略。

Redis的过期管理是一个经典的工程权衡案例:它没有追求极致的实时性,而是通过惰性删除和定期删除这两种低开销的策略,在CPU和内存之间取得了很好的平衡,理解这个原理,对于我们在实践中合理使用Redis、规避潜在风险非常有帮助。