Redis 过期策略及内存回收机制解析
Redis 作为高性能的内存数据库,在实际使用中需要合理管理内存资源,避免内存溢出导致的服务异常。其中过期键的处理和内存回收机制是 Redis 内存管理的核心内容,本文将详细解析这两部分的工作原理。
一、Redis 过期策略
Redis 支持为键设置过期时间,当键到达过期时间后,需要按照一定的策略进行处理,同时不影响 Redis 的正常读写性能。Redis 采用的是惰性删除 + 定期删除组合策略。
1.1 惰性删除
惰性删除是指当客户端访问某个键时,Redis 会先检查该键是否设置了过期时间,以及是否已经过期。如果键已过期,则直接删除该键,然后返回空结果;如果键未过期,则正常返回键值。
这种策略的优点是 CPU 友好,只会在访问键的时候才进行过期检查,不会额外消耗 CPU 资源;缺点是内存不友好,如果过期键长期没有被访问,会一直占用内存空间。
以下是惰性删除的核心逻辑伪代码:
def get_key(key): # 检查键是否设置过期时间,且已过期 if key in expire_dict and expire_dict[key] < current_time(): delete_key(key) return None return storage.get(key)
1.2 定期删除
定期删除是指 Redis 会周期性地(默认每秒 10 次)随机抽查一部分设置了过期时间的键,检查它们是否过期,如果过期则删除。定期删除是对惰性删除的补充,避免大量过期键长期占用内存。
定期删除的执行流程如下:
从设置了过期时间的键集合中,随机选择一批键(默认 20 个)
遍历这批键,删除其中已过期的键
如果这批键中过期键的比例超过 25%,则重复上述步骤,直到过期键比例低于 25% 或者达到执行时间上限
通过调整定期删除的频率和每次抽查的键数量,可以在 CPU 消耗和内存释放之间取得平衡。默认配置已经能满足大部分场景的需求,一般不需要手动修改。
二、Redis 内存回收机制
当 Redis 占用的内存达到预设的最大内存限制(通过 maxmemory 配置项设置)时,会触发内存回收机制,按照指定的淘汰策略删除部分键,释放内存空间。Redis 提供了 8 种淘汰策略,用户可以根据业务场景选择合适的策略。
2.1 淘汰策略分类
Redis 的淘汰策略可以分为以下几类:
| 策略名称 | 策略说明 | 适用场景 |
|---|---|---|
noeviction | 不淘汰任何键,当内存不足时,新写入操作会报错,读操作和删除操作正常执行 | 数据不能丢失的场景,比如缓存+持久化存储结合的场景 |
allkeys-lru | 从所有键中淘汰最近最少使用的键(LRU 算法) | 缓存场景,希望保留最常访问的数据 |
volatile-lru | 从设置了过期时间的键中淘汰最近最少使用的键 | 部分键设置过期时间的缓存场景 |
allkeys-random | 从所有键中随机淘汰键 | 所有键访问频率相近的缓存场景 |
volatile-random | 从设置了过期时间的键中随机淘汰键 | 部分键设置过期时间的随机淘汰场景 |
volatile-ttl | 从设置了过期时间的键中,淘汰剩余存活时间最短的键 | 希望优先淘汰即将过期的键的场景 |
allkeys-lfu | 从所有键中淘汰使用频率最低的键(LFU 算法,Redis 4.0 及以上支持) | 需要统计键访问频率的缓存场景 |
volatile-lfu | 从设置了过期时间的键中淘汰使用频率最低的键(LFU 算法,Redis 4.0 及以上支持) | 部分键设置过期时间的 LFU 淘汰场景 |
2.2 LRU 与 LFU 算法说明
LRU(Least Recently Used)即最近最少使用,核心思想是淘汰一段时间内最久没有被访问的键。Redis 中的 LRU 算法是近似 LRU,并非严格实现,因为严格 LRU 需要维护所有键的访问时间链表,会消耗大量内存和 CPU 资源。Redis 给每个键维护一个 24 位的时钟字段,记录最近一次访问的时间戳,淘汰时随机采样一批键,选择时间戳最小的(最久未访问的)进行淘汰。
LFU(Least Frequently Used)即最少使用频率,核心思想是淘汰一段时间内访问次数最少的键。和 LRU 相比,LFU 更关注键的访问频率,适合访问频率差异较大的场景。Redis 中的 LFU 会给每个键维护一个访问计数器和衰减时间,访问时计数器增加,同时会定期根据衰减时间降低计数器值,避免旧的热键一直占用内存。
2.3 内存回收的执行流程
当 Redis 执行写命令时,会先检查当前内存使用是否超过 maxmemory 限制,如果超过,则按照配置的淘汰策略执行淘汰操作,直到内存使用低于限制后再执行写命令。如果淘汰后仍然无法满足内存需求,写命令会返回错误(noeviction 策略下直接返回错误)。
以下是内存回收的简化流程伪代码:
def execute_write_command(cmd):
# 检查内存是否超过限制
while used_memory() > maxmemory:
# 执行淘汰策略,删除键释放内存
evict_keys()
# 如果淘汰策略返回 False,说明无法继续淘汰
if not evict_keys():
if maxmemory_policy == 'noeviction':
return error("OOM command not allowed when used memory > 'maxmemory'")
# 执行写命令
return do_write(cmd)三、实际配置建议
在实际使用 Redis 时,可以根据业务需求合理配置过期策略和内存回收机制:
如果作为纯缓存使用,建议设置
maxmemory为服务器可用内存的 70%-80%,避免 Redis 占用过多内存影响其他服务缓存场景下优先选择
allkeys-lru或allkeys-lfu策略,根据业务访问特征选择 LRU 或 LFU如果需要区分缓存键和持久化键,可以给缓存键设置过期时间,选择
volatile-lru等 volatile 开头的策略不需要手动调整定期删除的频率,默认配置已经做了性能平衡,特殊场景可以参考 Redis 官方文档调整
hz配置项
注意:Redis 的过期键删除和内存淘汰都不会触发持久化操作,删除的键会在后续的 AOF 重写或者 RDB 生成时从持久化文件中移除。