# Redis 的过期键删除策略(惰性 + 定期)
# 标准答案
Redis 采用 惰性删除(Lazy Deletion) 和 定期删除(Periodic Deletion) 结合的方式管理过期键:
- 惰性删除:仅在访问键时检查是否过期,过期则删除,避免额外 CPU 资源消耗,但可能导致大量过期键占用内存。
- 定期删除:Redis 每秒运行 10 次(默认),随机抽取部分过期键检查并删除,降低内存占用,但可能存在漏删情况。
如果过期键未被及时删除,且内存达到maxmemory
限制,则触发 内存淘汰策略(如 LRU、LFU)。
# 答案解析
# 🔹 1. 过期键存储方式
Redis 不会主动删除已过期键,而是依赖两种策略:
- 每个 key 可能有过期时间(EXPIRE),过期时间存储在
dict->expires
哈希表。 - key 过期后仍存于主数据结构中(dict->main),但
expires
记录的过期时间会影响后续访问与删除策略。
示例:
SET key "value"
EXPIRE key 10 # 设置10秒后过期
1
2
2
Redis 并不会立即删除 key
,而是等到:
- 用户访问
key
时,触发惰性删除。 - 定期任务抽样检测,发现过期则删除。
- 内存达到上限时,触发淘汰策略。
# 🔹 2. 过期键删除策略
# 2.1 惰性删除(Lazy Deletion)
- 原理:
只有当客户端访问某个键时,Redis 才会检查其是否过期,如果已过期,则删除该键并返回nil
。 - 优点:
- 不消耗额外 CPU 资源,仅在访问时检查,避免对 CPU 造成额外负担。
- 缺点:
- 可能导致大量过期数据堆积,如果某些键从未被访问,就不会被删除,占用大量内存。
示例:
SET key "hello"
EXPIRE key 10 # 10秒后过期
# 等待 15 秒后执行:
GET key # 访问时发现过期,立即删除并返回 nil
1
2
3
4
2
3
4
源码分析(惰性删除实现):
robj *lookupKeyRead(redisDb *db, robj *key) {
expireIfNeeded(db, key); // 先检查 key 是否过期,若过期则删除
return lookupKey(db, key); // 再执行正常查询
}
1
2
3
4
2
3
4
结论:
- 只有 访问该 key 时才会触发删除,不会主动扫描内存删除无访问的过期键。
# 2.2 定期删除(Periodic Deletion)
原理:
- Redis 每秒执行 10 次(默认
hz = 10
),从每个数据库(db)中随机抽取部分键,检查是否过期并删除。 - 每次最多检查
ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP = 20
个键(默认值)。 - 如果删除率低(如 <25%),Redis 增加检查频率,确保过期键被删除。
- Redis 每秒执行 10 次(默认
优点:
- 定期清理内存,减少过期数据堆积。
- 降低单次删除的 CPU 影响,避免一次性删除过多键影响 Redis 响应速度。
缺点:
- 抽样方式可能漏删部分键,如果某些过期键未被抽中,就可能继续占用内存。
源码分析(定期删除实现):
void databasesCron(void) {
for (int i = 0; i < server.dbnum; i++) {
expireCycleTryExpire(server.db+i, ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP);
}
}
1
2
3
4
5
2
3
4
5
databasesCron()
是 Redis 定期任务(10ms 执行一次)。expireCycleTryExpire()
在每个数据库中 随机选 20 个键 检查过期。- 如果 删除率 <25%,则增加检查频率,确保删除效率。
结论:
- 定期删除策略 可以 主动减少过期数据,但因随机抽样,无法保证所有过期键都能及时删除。
# 🔹 3. 过期键未及时删除的后果
如果某些过期键 既不被访问(惰性删除无效),又未被随机抽样到(定期删除无效),它们仍然会占用 Redis 内存。
# Redis 解决方案:内存淘汰策略
当 Redis 内存达到 maxmemory
限制,Redis 触发 内存淘汰机制:
- 淘汰策略(默认
noeviction
)volatile-lru
:最近最少使用(LRU),在设置了过期时间的键中,淘汰最久未使用的键。allkeys-lru
:在所有键中,淘汰最久未使用的键。volatile-ttl
:优先淘汰快要过期的键。noeviction
:如果内存满了,直接返回错误(不会删除键)。
示例:
CONFIG SET maxmemory 100mb
CONFIG SET maxmemory-policy allkeys-lru
1
2
2
这样,当 Redis 内存达到 100MB 时,会自动删除最久未使用的数据。
# 🔹 4. 最佳实践与优化方案
合理配置
hz
参数(定期删除频率)hz
控制定期删除执行频率,默认hz=10
(每秒 10 次)。- 大数据量场景下,可以适当提高(如
hz=50
),提升定期删除效率:CONFIG SET hz 50
1
避免过多 "僵尸" 过期键
- 尽量访问可能过期的 key,触发惰性删除。
- 例如 定期访问所有缓存键,主动触发删除:
SCAN 0 MATCH * COUNT 1000
1
适当调整
maxmemory-policy
- 如果 Redis 用于缓存,建议采用 LRU 淘汰策略:
CONFIG SET maxmemory-policy allkeys-lru
1 - 这样,当内存不足时,Redis 优先淘汰最久未使用的键,保证热点数据可用。
- 如果 Redis 用于缓存,建议采用 LRU 淘汰策略:
# 🔍 深入追问
- 为什么 Redis 采用 "惰性删除 + 定期删除",而不是 "定时删除"?
- 定期删除的频率是否可调?如何调整?
- Redis 如何保证定期删除不会影响主线程性能?
- 如何优化 Redis 在大规模数据场景下的过期键删除?
- 过期键的删除会触发 AOF 持久化吗?如何优化?
# 相关面试题
- Redis 如何处理过期键?
- 过期键与内存淘汰策略的区别?
- Redis 为什么不用定时器主动删除过期键?
- 如何优化 Redis 的过期键删除?
- Redis 的
LRU
淘汰策略是如何实现的?
总结:
- 惰性删除:低 CPU 消耗,但可能造成内存占用过高。
- 定期删除:控制过期键增长,但抽样可能漏删。
- 内存淘汰:作为最终保障,确保 Redis 不会 OOM。