Redis 中的过期键删除策略:惰性删除与定期删除

好的,各位程序猿、攻城狮、以及未来要征服代码星辰大海的弄潮儿们! 🚀 大家好!

今天咱们要聊的,是 Redis 这位内存数据库界的大佬,它内部一个非常重要的“保洁阿姨”机制:过期键删除策略。 啥?你以为数据库就只会存东西吗? 错! 存进去的东西,总有要清理的时候,不然内存早晚被撑爆,整个系统就得嗝屁!

那么,Redis 这位“阿姨”是怎么工作的呢? 她可不是简单粗暴的“一刀切”,而是采用了两种策略,堪称“软硬兼施”,既保证了性能,又避免了内存泄漏。 这两种策略就是: 惰性删除 (Lazy Expiration)定期删除 (Active Expiration)

别怕,听起来高大上,其实理解起来非常简单。 咱们这就开始,保证让你听得懂,记得住,还能给面试官讲得明明白白!

一、 背景故事: 为什么需要过期键删除?

想象一下,你开了一家“记忆存储银行”,专门帮别人记住各种信息。 比如,某人存了 100 块钱,约定一个月后到期,到期之后这笔钱就自动转到你的腰包(嘿嘿,是不是有点黑)。

如果一个月后,客户没来取钱,你是不是应该主动把这笔记录从你的账本上划掉? 不然,你的账本会越来越厚,查询起来越来越慢,最终变成一堆废纸! 🗑️

Redis 也是一样。 它存储的数据都有一个“过期时间 (TTL, Time To Live)”。 一旦数据过期,就应该被删除,释放内存空间。 如果不删除,Redis 的内存就会被无用的数据塞满,性能会急剧下降,甚至崩溃。

所以,过期键删除策略,就是 Redis 用来清理这些过期数据的“保洁阿姨”。

二、 策略一: 惰性删除 (Lazy Expiration) – “不到万不得已,绝不出手!”

惰性删除,顾名思义,就是“懒”。 它就像一个非常佛系的“阿姨”,平时没事就躺着,只有在“万不得已”的情况下才会出手。

  • 工作原理: 当客户端尝试访问一个键时,Redis 首先会检查这个键是否已经过期。 如果过期了,就立即删除这个键,然后返回 "key not found" 的错误。 如果没过期,就正常返回数据。

  • 优点:

    • 节省 CPU 资源: 只有在访问过期键的时候才进行删除操作,避免了主动扫描所有键的开销。 这就像“能躺着绝不坐着,能坐着绝不站着”的原则,最大程度地节省体力。
    • 简单高效: 逻辑非常简单,实现起来也很容易。
  • 缺点:

    • 内存泄漏风险: 如果一个键过期了,但是一直没有被访问,那么它就会一直占用内存空间,直到被访问到才会删除。 如果有大量的过期键一直没有被访问,就会导致内存泄漏。 这就像你家的垃圾,一直堆在那里,直到你有一天突然想起来要扔掉它。
    • 极端情况下的性能问题: 如果大量的键在同一时间过期,并且在短时间内被大量访问,那么 Redis 可能会花费大量的时间在删除过期键上,导致性能下降。 这就像你家的水管突然爆了,你不得不花大量的时间来修理它。
  • 适用场景:

    • 适合于读写操作频繁,并且过期键被访问的概率比较高的场景。

用一段伪代码来表示惰性删除:

def get_key(key):
    if key not in db:
        return "key not found"

    if db[key].expired():  # 检查是否过期
        del db[key]        # 删除过期键
        return "key not found"
    else:
        return db[key].value

举个栗子:

假设你设置了一个键 user:session:123 的过期时间为 10 分钟。

  1. 10 分钟后,这个键过期了。
  2. 如果在这之后的任何时候,客户端尝试 GET user:session:123,Redis 就会发现这个键已经过期,立即删除它,并返回 "key not found"。
  3. 但是,如果在过期后的很长时间内,客户端都没有尝试访问这个键,那么这个键就会一直占用内存空间。

三、 策略二: 定期删除 (Active Expiration) – “定期大扫除,保持环境卫生!”

定期删除,就像一个勤劳的“阿姨”,她会定期地对 Redis 的数据库进行扫描,删除那些过期的键。

  • 工作原理: Redis 会定期执行以下操作:

    1. 随机抽取 一些数据库 (默认是 16 个数据库中随机选择几个)。
    2. 扫描 被选中的数据库中的一部分键。
    3. 检查 这些键是否已经过期。
    4. 删除 所有过期的键。

    这个过程会重复执行,直到达到一定的 CPU 使用率上限,或者扫描了足够多的键。

  • 优点:

    • 降低内存泄漏风险: 通过定期扫描和删除过期键,可以有效地减少内存泄漏的风险。 这就像定期打扫卫生,可以避免垃圾堆积。
    • 避免极端情况下的性能问题: 定期删除可以分散删除过期键的压力,避免在短时间内删除大量的过期键,从而避免性能下降。 这就像分批处理任务,可以避免一次性处理大量任务导致系统崩溃。
  • 缺点:

    • 占用 CPU 资源: 定期扫描和删除过期键会占用一定的 CPU 资源。 如果扫描的频率太高,或者扫描的键太多,可能会影响 Redis 的性能。 这就像频繁打扫卫生,可能会影响你的工作效率。
    • 可能存在延迟: 由于是定期扫描,所以过期键可能不会立即被删除。 在过期后到被扫描到并删除的这段时间内,过期键仍然会占用内存空间。 这就像你家的垃圾,即使你每天都打扫,也可能会有一些垃圾残留。
  • 适用场景:

    • 适合于读写操作不频繁,或者过期键被访问的概率比较低的场景。
    • 也适合于需要保证内存使用率的场景。

用一段伪代码来表示定期删除:

def active_expire():
    for i in range(databases_to_scan): # 随机选择几个数据库
        db = random.choice(databases)
        keys = db.get_random_keys(keys_to_scan) # 随机选择一部分键

        for key in keys:
            if key in db and db[key].expired():
                del db[key]

举个栗子:

Redis 默认每秒执行 10 次定期删除操作。 每次操作会随机选择几个数据库,并扫描其中的一部分键,删除所有过期的键。

四、 两种策略的比较:

为了更清晰地理解这两种策略,我们用一个表格来比较一下:

特性 惰性删除 (Lazy Expiration) 定期删除 (Active Expiration)
触发时机 访问过期键时 定期扫描数据库
CPU 占用 中等
内存占用 中等
实时性
适用场景 读写频繁,过期键访问概率高 读写不频繁,过期键访问概率低
优点 节省 CPU 资源,简单高效 降低内存泄漏风险,避免极端情况下的性能问题
缺点 内存泄漏风险,极端情况下的性能问题 占用 CPU 资源,可能存在延迟

五、 Redis 如何平衡两种策略?

Redis 并没有完全依赖于其中的一种策略,而是将两者结合起来使用,以达到最佳的效果。

  • 惰性删除: 作为第一道防线,保证在访问过期键时能够立即删除它。
  • 定期删除: 作为第二道防线,定期扫描数据库,删除那些没有被访问到的过期键。

通过这两种策略的结合,Redis 可以在保证性能的同时,有效地减少内存泄漏的风险。

六、 影响过期键删除策略的配置项:

Redis 提供了一些配置项,可以用来调整过期键删除策略的行为。

  • hz: 这个配置项控制 Redis 执行定期删除操作的频率。 默认值是 10,表示每秒执行 10 次定期删除操作。 增加 hz 的值可以提高定期删除的频率,从而减少内存泄漏的风险,但也会增加 CPU 的占用。
  • maxmemory: 这个配置项设置 Redis 可以使用的最大内存量。 当 Redis 的内存使用量达到 maxmemory 时,会触发内存淘汰策略 (Memory Eviction Policies),删除一些键,释放内存空间。 虽然内存淘汰策略不是专门用来删除过期键的,但是它也可以删除一些过期键,从而减少内存泄漏的风险。

七、 思考题:

  1. 如果你的 Redis 实例主要用于缓存,并且数据更新频繁,你会更关注哪种过期键删除策略? 为什么?
  2. 如果你的 Redis 实例主要用于存储会话信息,并且会话过期时间较长,你会如何配置 hz 参数?
  3. 除了惰性删除和定期删除之外,你还知道其他的过期键删除策略吗? (提示:可以考虑内存淘汰策略)

八、 总结:

Redis 的过期键删除策略是保证其性能和稳定性的重要机制。 通过惰性删除和定期删除的结合,Redis 可以在保证性能的同时,有效地减少内存泄漏的风险。 理解这两种策略的原理和优缺点,可以帮助你更好地配置和使用 Redis。

好啦,今天的“Redis 保洁阿姨”的故事就讲到这里。 希望大家以后在使用 Redis 的时候,能够更加了解它的内部机制,从而更好地利用它来构建高性能的应用程序。

记住,好的程序猿,不仅要会写代码,还要了解代码背后的原理! 💪

下次有机会,我们再聊聊 Redis 的其他有趣特性! 拜拜! 👋

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注