Redis 作为高性能内存数据库,广泛用于缓存、会话管理和实时数据处理。由于内存资源有限,Redis 提供了数据过期删除策略和内存淘汰策略来管理内存。本文将深入剖析 Redis 的数据过期删除机制(包括惰性删除和定时删除)以及 8 种内存淘汰策略,结合生活化例子、Go 代码示例和教学风格,带你全面理解 Redis 的内存管理之道。无论你是初学者还是资深开发者,这篇文章都将为你提供清晰、实用的指导。
一、Redis 数据过期删除策略
Redis 支持为键设置过期时间(TTL,Time To Live),通过 EXPIRE
、SETEX
等命令实现。过期键不会立即删除,而是通过特定的策略清理,以平衡性能和内存使用。Redis 的过期删除策略主要包括 惰性删除 和 定时删除,两者结合使用。
1. 惰性删除(Lazy Deletion)
原理
惰性删除是指 Redis 在访问键时检查其是否过期,如果过期则立即删除并返回空(nil
)。这是一种被动清理机制,核心实现在 expire.c
的 lookupKeyRead
和 expireIfNeeded
函数中。
为什么用惰性删除?
- 性能优先:不主动扫描所有键,减少 CPU 开销。
- 延迟清理:仅在必要时(如读写操作)删除过期键,避免无用操作。
生活化例子
想象你的冰箱(Redis 内存)里有些食物(键)标了保质期(TTL)。你不每天检查每件食物是否过期(主动清理),而是在想吃时(访问键)才看一眼保质期。如果过期(TTL 到期),直接扔掉(删除)。这就是惰性删除,省时省力。
实现细节
- 过期时间存储:Redis 在键的元数据中存储过期时间戳(
expire
字段,毫秒级),通过dictEntry
关联。 - 检查逻辑:每次访问键时,调用
expireIfNeeded
检查当前时间是否超过过期时间戳。 - 删除操作:如果键过期,调用
dbDelete
删除键值对,并传播删除事件(AOF 和复制)。
Go 代码示例(模拟惰性删除):
|
|
优缺点
- 优点:简单高效,只在访问时处理过期键,CPU 开销低。
- 缺点:如果键长期未访问,过期键可能占用内存,直到被访问或定时删除。
2. 定时删除(Periodic Deletion)
原理
定时删除是指 Redis 定期扫描部分键,主动删除已过期的键。核心实现在 expire.c
的 activeExpireCycle
函数中,通过 serverCron
定时调用。
为什么用定时删除?
- 内存回收:主动清理未被访问的过期键,释放内存。
- 渐进式清理:每次扫描少量键,避免阻塞主线程。
生活化例子
你每周清理一次冰箱(定时删除),检查每件食物的保质期,扔掉过期的。你不一次检查所有食物(全量扫描),而是每次看几件(采样),慢慢清理。Redis 的定时删除就像这种定期整理,保持冰箱整洁。
实现细节
- 采样扫描:每次调用
activeExpireCycle
,从数据库中随机抽样一批键(默认 20 个,ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP
)。 - 过期检查:检查采样键的过期时间戳,删除已过期键。
- 时间限制:每次扫描受时间预算限制(默认 25% 的
server.hz
周期),避免影响主线程。 - 数据库轮询:依次扫描每个数据库(
db0
到dbN
),确保公平性。 - 快慢模式:
- 慢模式:正常扫描,受时间预算限制。
- 快模式:内存压力大时(如接近
maxmemory
),加速扫描。
Go 代码示例(模拟定时删除):
|
|
优缺点
- 优点:主动回收内存,适合长期未访问的过期键。
- 缺点:扫描需要 CPU 资源,频繁扫描可能影响性能。
3. 惰性删除与定时删除的协同工作
- 互补机制:惰性删除处理活跃键,定时删除清理冷门键。
- 性能平衡:惰性删除零开销,定时删除渐进式执行,避免阻塞。
- 配置调整:通过
hz
参数调整定时删除频率(默认 10,范围 1-500)。
生活化例子:
冰箱清理(内存管理)结合了你用时检查(惰性删除)和每周整理(定时删除)。用时扔掉坏食物很快(低开销),每周慢慢整理角落的过期品(渐进清理)。Redis 用这两种方式,保持冰箱(内存)整洁高效。
二、Redis 内存淘汰策略
当 Redis 内存使用量接近 maxmemory
配置的上限时,触发内存淘汰策略,删除部分键以释放空间。Redis 提供 8 种淘汰策略,实现在 evict.c
的 evict.c
文件中,通过 performEvictions
函数执行。
1. noeviction(不淘汰)
- 原理:不删除任何键,当内存不足时,返回错误(如
OOM
)。 - 适用场景:内存充足或严格要求数据不丢失(如主数据库)。
- 生活化例子:冰箱满了,你拒绝放新食物(返回错误),宁愿去买个新冰箱。
2. volatile-lru(对过期键使用 LRU)
- 原理:从设置了过期时间的键中,选择最近最少使用(LRU)的键删除。
- 适用场景:希望保留非过期键,优先删除不活跃的过期键。
- 生活化例子:冰箱里只扔掉标了保质期的食物(过期键),挑最久没吃的(LRU)。
3. volatile-random(随机删除过期键)
- 原理:从设置了过期时间的键中,随机选择键删除。
- 适用场景:过期键较多,LRU 开销不可接受时。
- 生活化例子:冰箱里随机扔掉一件标了保质期的食物,省去挑拣时间。
4. volatile-ttl(优先删除剩余TTL最短的键)
- 原理:从设置了过期时间的键中,选择剩余 TTL 最短的键删除。
- 适用场景:希望尽快删除即将过期的键,释放内存。
- 生活化例子:冰箱里扔掉保质期最快到的食物(TTL 最小)。
5. allkeys-lru(对所有键使用 LRU)
- 原理:从所有键中,选择最近最少使用的键删除。
- 适用场景:缓存场景,数据可重建,优先保留活跃键。
- 生活化例子:冰箱里扔掉最久没吃的食物,不管有没有保质期。
6. allkeys-random(随机删除所有键)
- 原理:从所有键中,随机选择键删除。
- 适用场景:数据均匀访问,LRU 开销高时。
- 生活化例子:冰箱里随机扔掉一件食物,简单快速。
7. allkeys-lfu(对所有键使用 LFU)
- 原理:从所有键中,选择最不经常使用(LFU,Least Frequently Used)的键删除。
- 适用场景:希望保留高频访问的键,适合热点数据场景。
- 生活化例子:冰箱里扔掉最少吃的食物(访问频率低)。
8. volatile-lfu(对过期键使用 LFU)
- 原理:从设置了过期时间的键中,选择最不经常使用的键删除。
- 适用场景:只对过期键应用 LFU,保护非过期键。
- 生活化例子:冰箱里只扔掉标了保质期且最少吃的食物。
Go 代码示例(模拟 LRU 淘汰策略):
|
|
实现细节
- LRU 近似算法:Redis 使用随机采样(默认 5 个键,
maxmemory-samples
)近似 LRU,降低开销。 - LFU 实现:通过
objectLFULog
字段记录访问频率,使用对数计数器(Logarithmic Counter)优化内存。 - 淘汰触发:当
used_memory
超过maxmemory
,调用performEvictions
执行淘汰。 - 配置参数:
maxmemory
:设置内存上限。maxmemory-policy
:指定淘汰策略。maxmemory-samples
:采样大小。
选择淘汰策略的建议
- 缓存场景:推荐
allkeys-lru
或allkeys-lfu
,优先保留活跃数据。 - 混合场景:使用
volatile-lru
或volatile-ttl
,保护非过期键。 - 性能敏感:选择
volatile-random
或allkeys-random
,减少计算开销。 - 严格数据保留:使用
noeviction
,搭配监控告警。
生活化例子:
冰箱空间有限(maxmemory
),你根据策略扔食物:
- noeviction:不扔,宁愿买新冰箱。
- volatile-lru:扔最久没吃的过期食物。
- allkeys-lfu:扔最少吃的食物,保留常吃的。
三、过期删除与内存淘汰的协同工作
- 分工明确:
- 过期删除:处理设置了 TTL 的键,释放内存。
- 内存淘汰:处理内存不足时的键删除,适用所有键或部分键。
- 触发条件:
- 过期删除:键访问(惰性)或定时任务(定时)。
- 内存淘汰:内存使用量超过
maxmemory
。
- 性能优化:
- 过期删除渐进式执行,降低主线程阻塞。
- 内存淘汰使用采样算法,减少 CPU 开销。
生活化例子:
冰箱管理(内存)有两个机制:你用时扔过期食物(惰性删除),每周整理过期品(定时删除)。如果冰箱满了(maxmemory
),按策略扔食物(淘汰),如最久没吃的(LRU)或最少吃的(LFU)。
四、实际应用与最佳实践
应用场景
- 缓存系统:设置短 TTL(如 1 小时),结合
volatile-lru
,清理不活跃缓存。 - 会话管理:为会话键设置 TTL(如 30 分钟),用
volatile-ttl
优先删除快过期会话。 - 实时分析:使用
allkeys-lfu
,保留高频访问的热点数据。 - 消息队列:设置 TTL 避免消息堆积,搭配
noeviction
确保消息不丢失。
最佳实践
- 合理设置 TTL:为临时数据设置适当过期时间,减少内存占用。
- 监控内存:通过
INFO MEMORY
检查used_memory
和maxmemory
,设置告警。 - 调整采样:增大
maxmemory-samples
(如 10)提高 LRU/LFU 准确性,注意 CPU 开销。 - 测试策略:在测试环境模拟高内存压力,选择合适的淘汰策略。
- 结合集群:在 Redis 集群中,确保各节点
maxmemory
配置一致。
案例:
一个电商网站使用 Redis 缓存商品详情,键格式为 product:{ID}
,TTL 为 1 小时,内存上限 10GB,使用 volatile-lru
策略。访问高峰期,Redis 自动删除不活跃的过期键,保持内存稳定,平均延迟 0.5ms。
五、总结
Redis 的内存管理通过 数据过期删除策略 和 内存淘汰策略 高效平衡性能和内存使用:
- 过期删除策略:
- 惰性删除:访问时清理过期键,零开销。
- 定时删除:定期采样删除,渐进式回收内存。
- 内存淘汰策略:
- 8 种策略(
noeviction
、volatile-lru
等),适配不同场景。 - 使用 LRU/LFU 近似算法和随机采样,兼顾性能和准确性。
- 8 种策略(
评论 0