腾讯面试原题:深入解析Redis内存淘汰策略:如何高效管理过期数据的删除与控制

Redis 作为当前分布式缓存领域的主流工具,其在面试中的出现频率极高。特别是在涉及后端项目中,Redis 几乎是分布式缓存的标准选择。

设置缓存数据过期时间的意义

在大多数情况下,我们在保存缓存数据时都会为其设置一个过期时间。这是因为内存空间是有限的,若缓存数据无限制地保留,会迅速导致内存溢出。

Redis 为我们提供了设置缓存数据过期时间的功能,示例如下:

127.0.0.1:6379> expire key 60  # 数据将在 60 秒后过期 
(integer) 1  
127.0.0.1:6379> setex key 60 value  # 数据将在 60 秒后过期 (setex: [set] + [ex]pire)
OK  
127.0.0.1:6379> ttl key  # 查看数据还有多久过期
(integer) 56  

需要注意的是,除了字符串类型的 setex 命令可以直接为数据设置过期时间外,其他数据类型则需要使用 expire 命令。通过 persist 命令,我们也可以移除某个键的过期时间。

除了减轻内存消耗外,过期时间还有其他用途。例如,在某些业务场景中,特定数据只需在短时间内有效,例如短信验证码通常在 1 分钟内有效,而用户登录的 Token 可能有效期为 1 天。若通过传统数据库处理这些数据,通常需要手动判断过期时间,这样不仅繁琐,且性能较差。

Redis 如何判断数据的过期状态?

Redis 利用一个称为“过期字典”的结构来存储数据的过期时间。该字典将 Redis 数据库中的某个键指向一个值,这个值是一个长整型整数,表示该键的过期时间(精确到毫秒的 UNIX 时间戳)。

图片

过期字典存储在 redisDb 数据结构中:

typedef struct redisDb {  
    ...  
    dict *dict;     // 数据库键空间,保存所有键值对  
    dict *expires;  // 过期字典,保存键的过期时间  
    ...  
} redisDb;  

你了解过期数据的删除策略吗?

假设你设置了一批键只能存活 1 分钟,那么在这 1 分钟后,Redis 将如何处理这些键?常用的过期数据删除策略主要有两种(重要!在设计缓存时需要特别注意):

  1. 惰性删除:仅在访问键时检查其过期状态。这种方式对 CPU 友好,但可能导致过期键未被及时删除。
  2. 定期删除:系统会定期检查并删除已过期的键。这种方式相对友好于内存,并且 Redis 在底层通过限制删除操作的时间和频率来降低对 CPU 的影响。

一般来说,Redis 结合了 定期删除惰性删除 的策略。不过,仅仅依靠设置过期时间并不能完全解决问题,因为定期和惰性删除都可能未能及时清理过期键,导致过期键在内存中堆积,最终引发内存溢出。

为解决这个问题,Redis 引入了 内存淘汰机制

Redis 内存淘汰机制简介

相关问题:在 MySQL 中存有 2000 万数据,而 Redis 只存 20 万的数据,如何确保 Redis 中的数据为热点数据?

Redis 提供了六种内存淘汰策略:

  1. volatile-lru(最少使用策略):从已设置过期时间的键中选择最近最少使用的键进行淘汰。
  2. volatile-ttl:从已设置过期时间的键中选择即将过期的键进行淘汰。
  3. volatile-random:随机选择已设置过期时间的键进行淘汰。
  4. allkeys-lru(最少使用策略):当内存不足以容纳新写入数据时,在所有键中移除最近最少使用的键(此策略使用频繁)。
  5. allkeys-random:从所有数据集中随机选择键进行淘汰。
  6. no-eviction:禁止淘汰数据,当内存不足以容纳新写入数据时会报错。

在 4.0 版本之后,增加了两种策略:

  1. volatile-lfu(最不常用策略):从已设置过期时间的键中选择最不常用的键进行淘汰。
  2. allkeys-lfu(最不常用策略):当内存不足以容纳新写入数据时,在所有键中移除最不常用的键。