3.5 Key 的过期策略与内存淘汰策略 Redis 3.x 高级特性:Key 的过期策略与内存淘汰策略详解 Redis 作为高性能的键值对数据库,其高效的内存管理机制是其卓越性能的关键组成部分。其中,Key 的过期策略与内存淘汰策略是 Redis 内存管理中至关重要的两个方面。合理地配置和理解这两种策略,能够帮助我们更好地利用 Redis 资源,构建稳定可靠的应用系统。 Key 的过期策略 (Expiration Policies) 在 Redis 中,我们可以为 Key 设置过期时间,当 Key 过期后,Redis 会自动将其删除,释放内存空间。这对于缓存数据、会话管理等场景非常有用。
Redis 作为高性能的键值对数据库,其高效的内存管理机制是其卓越性能的关键组成部分。其中,Key 的过期策略与内存淘汰策略是 Redis 内存管理中至关重要的两个方面。合理地配置和理解这两种策略,能够帮助我们更好地利用 Redis 资源,构建稳定可靠的应用系统。
在 Redis 中,我们可以为 Key 设置过期时间,当 Key 过期后,Redis 会自动将其删除,释放内存空间。这对于缓存数据、会话管理等场景非常有用。Redis 提供了两种主要的过期策略:惰性删除 (Lazy Expiration) 和 定期删除 (Periodic Expiration)。
原理: 惰性删除策略并非在 Key 过期后立即删除,而是在客户端 尝试访问 过期 Key 的时候,Redis 才会去检查 Key 是否过期。如果 Key 已经过期,Redis 会先删除该 Key,然后再返回客户端请求的结果(如果是非读取操作,则直接删除后返回)。
代码实践:
我们可以使用 EXPIRE 命令来设置 Key 的过期时间(秒为单位),或者使用 PEXPIRE 命令设置过期时间(毫秒为单位)。
SET mykey "Hello" EXPIRE mykey 60 # 设置 mykey 60 秒后过期 GET mykey # 第一次访问,如果 60 秒内,正常返回 "Hello" # 如果超过 60 秒后访问,返回 nil (Key 不存在)
详细解释:
当客户端执行 GET mykey 命令时,Redis 内部会进行如下检查:
检查 Key mykey 是否存在。
如果 Key 存在,检查 Key 是否设置了过期时间。
如果设置了过期时间,检查当前时间是否超过了过期时间。
如果已过期,则删除 mykey,并返回 nil 给客户端。
如果未过期,则返回 Key 的值 "Hello"。
优点:
节省 CPU 资源: 惰性删除只在访问时检查,避免了主动扫描过期 Key 带来的 CPU 消耗。
实现简单: 逻辑简单,实现成本较低。
缺点:
原理: 定期删除策略是 Redis 会 周期性地 随机抽取一部分设置了过期时间的 Key 进行过期检查,并删除其中过期的 Key。这个周期性的任务由 Redis 的后台进程 serverCron 函数负责执行。
代码实践:
定期删除策略是 Redis 自动执行的,无需用户显式操作。我们只需要设置 Key 的过期时间,Redis 就会在后台定期进行检查和删除。
详细解释:
Redis 的 serverCron 函数默认每秒执行 10 次(可以通过 hz 配置项调整),其中会包含定期删除过期 Key 的逻辑。定期删除的具体步骤如下:
从设置了过期时间的 Key 集合中随机抽取一部分 Key (默认最多 20 个)。 这个数量可以通过 redis.conf 中的 hz 配置项和相关参数调整。
检查抽取的 Key 是否过期。
如果 Key 已过期,则删除该 Key。
如果在一轮检查中删除的过期 Key 比例超过 25%,则会重复执行步骤 1-3,直到删除比例低于 25% 或者达到最大检查次数。 这个比例和最大检查次数也是为了避免一次性删除大量过期 Key 导致 Redis 性能抖动。
优点:
减少内存浪费: 相比惰性删除,定期删除能够更及时地释放过期 Key 占用的内存。
平衡 CPU 和内存: 通过周期性地检查,兼顾了 CPU 消耗和内存释放的效率。
缺点:
可能存在延迟: 定期删除是周期性执行的,可能无法立即删除所有过期 Key,仍然可能存在一定的内存延迟释放。
CPU 消耗: 定期删除会消耗一定的 CPU 资源,虽然相比主动扫描所有 Key 的方式要高效得多,但仍然需要注意 hz 配置项的设置,避免过于频繁的检查导致 CPU 负载过高。
| 策略 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 惰性删除 | 访问时检查过期,过期则删除 | 节省 CPU 资源,实现简单 | 内存浪费,过期 Key 长时间占用内存 | 读多写少,对内存敏感度较低的应用 |
| 定期删除 | 周期性随机抽取 Key 检查过期,过期则删除 | 减少内存浪费,平衡 CPU 和内存消耗 | 可能存在延迟,CPU 消耗(可配置) | 读写均衡,对内存有一定要求,但 CPU 资源相对充足的应用 |
代码实践:查看 Key 的剩余过期时间
可以使用 TTL 命令查看 Key 的剩余过期时间(秒为单位),或者使用 PTTL 命令查看剩余过期时间(毫秒为单位)。
SET mykey "Hello" EX 60 TTL mykey # 返回剩余过期时间,例如 55 (秒) PTTL mykey # 返回剩余过期时间,例如 55000 (毫秒) TTL non_exist_key # 返回 -2,表示 Key 不存在 TTL key_no_expire # 返回 -1,表示 Key 没有设置过期时间
代码实践:移除 Key 的过期时间
可以使用 PERSIST 命令移除 Key 的过期时间,使其永久有效。
SET mykey "Hello" EX 60 TTL mykey # 返回剩余过期时间 PERSIST mykey # 移除 mykey 的过期时间 TTL mykey # 返回 -1,表示 Key 没有设置过期时间
当 Redis 使用的内存超过配置的 maxmemory 限制时,Redis 会触发内存淘汰策略,根据配置的策略删除一部分 Key,回收内存空间,以保证 Redis 的正常运行。
配置 maxmemory:
可以通过 redis.conf 文件或者 CONFIG SET 命令来配置 Redis 的最大内存使用量 maxmemory。
# redis.conf maxmemory 2gb # 设置最大内存为 2GB CONFIG SET maxmemory 1gb # 通过命令动态设置最大内存为 1GB
配置内存淘汰策略 maxmemory-policy:
可以通过 redis.conf 文件或者 CONFIG SET 命令来配置内存淘汰策略 maxmemory-policy。Redis 3.x 版本提供了以下几种主要的内存淘汰策略:
noeviction: 当内存达到限制时,不进行任何淘汰,客户端尝试执行 写操作 (例如 SET, LPUSH 等) 会返回错误 (error) OOM command not allowed when used memory > 'maxmemory'。读操作不受影响。这是 Redis 默认的策略。
volatile-lru: 从 设置了过期时间 的 Key 中,淘汰 最近最少使用 (Least Recently Used, LRU) 的 Key。
allkeys-lru: 从 所有 Key 中,淘汰 最近最少使用 (LRU) 的 Key。
volatile-random: 从 设置了过期时间 的 Key 中,随机淘汰 一部分 Key。
allkeys-random: 从 所有 Key 中,随机淘汰 一部分 Key。
volatile-ttl: 从 设置了过期时间 的 Key 中,淘汰 即将过期 (TTL 最小) 的 Key。
代码实践:配置内存淘汰策略
# redis.conf maxmemory-policy volatile-lru # 设置内存淘汰策略为 volatile-lru CONFIG SET maxmemory-policy allkeys-lru # 通过命令动态设置内存淘汰策略为 allkeys-lru
2.1.1 noeviction 策略
行为: 当内存达到 maxmemory 限制时,Redis 不会淘汰任何 Key。当客户端尝试执行写操作时,Redis 会返回 OOM 错误。
适用场景: 适用于对数据 完整性 要求极高,宁愿拒绝写操作也不允许数据丢失 的场景。例如,作为权威数据源的场景。
风险: 如果应用不处理 OOM 错误,或者持续写入数据,可能导致 Redis 崩溃。
2.1.2 volatile-lru 策略
行为: 当内存达到 maxmemory 限制时,Redis 会从 设置了过期时间 的 Key 中,淘汰 最近最少使用 (LRU) 的 Key。LRU 算法会维护一个 Key 的访问时间戳,淘汰最近最少被访问的 Key。
适用场景: 适用于 缓存场景,只希望淘汰 缓存数据 (设置了过期时间的 Key),优先保留 重要数据 (没有设置过期时间的 Key)。LRU 算法能够淘汰 热点数据 之外的冷数据,保证缓存命中率。
代码实践 (模拟 LRU 淘汰): 以下代码模拟了 volatile-lru 策略的效果。
CONFIG SET maxmemory 100m CONFIG SET maxmemory-policy volatile-lru SET key1 "value1" EX 60 # 设置过期时间 SET key2 "value2" EX 60 SET key3 "value3" EX 60 # 模拟访问 key2 和 key3,使 key1 成为最少使用的 Key GET key2 GET key3 # 写入新的数据,触发内存淘汰 SET key4 "value4" EX 60 EXISTS key1 # 可能会返回 0,key1 被淘汰 EXISTS key2 # 返回 1,key2 仍然存在 EXISTS key3 # 返回 1,key3 仍然存在 EXISTS key4 # 返回 1,key4 新写入的数据存在
2.1.3 allkeys-lru 策略
行为: 当内存达到 maxmemory 限制时,Redis 会从 所有 Key 中,淘汰 最近最少使用 (LRU) 的 Key,包括设置了过期时间和没有设置过期时间的 Key。
适用场景: 适用于 纯缓存场景,或者 所有数据都可以被淘汰 的场景。相比 volatile-lru,allkeys-lru 策略更加激进,可以更有效地回收内存。
代码实践 (模拟 allkeys-lru 淘汰):
CONFIG SET maxmemory 100m CONFIG SET maxmemory-policy allkeys-lru SET key1 "value1" # 没有设置过期时间 SET key2 "value2" EX 60 # 设置过期时间 SET key3 "value3" EX 60 # 模拟访问 key2 和 key3,使 key1 成为最少使用的 Key GET key2 GET key3 # 写入新的数据,触发内存淘汰 SET key4 "value4" EXISTS key1 # 可能会返回 0,key1 被淘汰 (即使没有过期时间) EXISTS key2 # 返回 1,key2 仍然存在 EXISTS key3 # 返回 1,key3 仍然存在 EXISTS key4 # 返回 1,key4 新写入的数据存在
2.1.4 volatile-random 策略
行为: 当内存达到 maxmemory 限制时,Redis 会从 设置了过期时间 的 Key 中,随机淘汰 一部分 Key。
适用场景: 适用于对数据 重要性区分不明显,或者 对淘汰策略性能要求较高,可以接受一定随机性 的场景。相比 LRU 算法,随机淘汰算法实现简单,性能更高,但淘汰效果可能不如 LRU 精确。
2.1.5 allkeys-random 策略
行为: 当内存达到 maxmemory 限制时,Redis 会从 所有 Key 中,随机淘汰 一部分 Key。
适用场景: 类似于 volatile-random,但淘汰范围扩大到所有 Key。适用于 所有数据重要性区分不明显,且 对淘汰策略性能要求极高 的场景。
2.1.6 volatile-ttl 策略
行为: 当内存达到 maxmemory 限制时,Redis 会从 设置了过期时间 的 Key 中,淘汰 即将过期 (TTL 最小) 的 Key。
适用场景: 适用于 缓存场景,希望 优先淘汰即将过期的缓存数据,尽可能保留更长时间的缓存数据。这种策略能够最大化缓存的有效时间。
选择合适的内存淘汰策略需要根据具体的应用场景和数据特点进行权衡。以下是一些选择建议:
优先考虑 volatile-lru 或 allkeys-lru: LRU 算法在大多数缓存场景下都能取得较好的淘汰效果,能够淘汰冷数据,保留热点数据,提高缓存命中率。
如果 Redis 主要用于缓存,且 重要数据和缓存数据区分明显 (重要数据不设置过期时间,缓存数据设置过期时间),推荐使用 volatile-lru。
如果 Redis 用于 纯缓存,或者 所有数据都可以被淘汰,推荐使用 allkeys-lru。
volatile-ttl 策略适用于希望最大化缓存有效时间的场景: 例如,在对缓存命中率要求极高,且希望尽可能延长缓存有效期的场景下,volatile-ttl 策略可能更合适。
*-random 策略适用于对性能要求极高,可以接受一定随机性的场景: 例如,在写入量非常大,对淘汰策略性能要求很高的场景下,*-random 策略可能是一个折衷方案。
noeviction 策略适用于对数据完整性要求极高的场景: 但需要 谨慎使用,并确保应用能够正确处理 OOM 错误,或者有其他机制防止内存溢出。
重要提示:
为 Key 设置合理的过期时间: 无论是哪种内存淘汰策略,都建议为 Key 设置合理的过期时间,以便 Redis 能够更好地管理内存。
监控 Redis 内存使用情况: 需要密切监控 Redis 的内存使用情况,例如使用 INFO memory 命令查看 used_memory 和 maxmemory 等指标,及时调整 maxmemory 和 maxmemory-policy 配置,避免 Redis 内存溢出。
合理评估业务数据特点: 在选择内存淘汰策略时,需要充分评估业务数据的访问模式、重要性、生命周期等特点,选择最适合的策略,以达到最佳的性能和资源利用率。
Key 的过期策略和内存淘汰策略是 Redis 内存管理的重要组成部分。理解和合理配置这两种策略,能够帮助我们更好地利用 Redis 资源,构建高性能、高可靠的应用系统。
过期策略: Redis 采用 惰性删除 和 定期删除 相结合的方式处理过期 Key,兼顾了 CPU 效率和内存回收效率。
内存淘汰策略: Redis 提供了多种内存淘汰策略,包括基于 LRU、TTL 和随机淘汰等算法,以及针对设置过期时间和所有 Key 的不同策略,用户可以根据具体场景选择合适的策略。
在实际应用中,建议根据业务需求和数据特点,综合考虑过期策略和内存淘汰策略的选择,并持续监控 Redis 的内存使用情况,进行调优,以确保 Redis 服务的稳定性和高效性。