Redis 是一个内存型 key-value 存储系统,对性能要求非常高,但内存是有限资源,特别当有大量键设置了过期时间时,如何及时删除过期键或在内存满了之后如何淘汰键,是设计稳定系统、保证性能与内存利用率的关键。下面我们先介绍 Redis 处理过期键的机制,再讲内存淘汰(eviction)策略,最后结合不同场景给出应用建议。
Redis 过期键删除机制
Redis 为“已设置过期时间(TTL/Expire)”的键提供两种主要方式来删除过期键:被动删除(lazy delete)和主动/定期删除(periodic/active delete)。这两者配合使用,以平衡内存释放与 CPU 占用。
1. 被动删除(Lazy 或称惰性删除)
被动删除发生在客户端尝试访问某个键(读、写等操作)的时候,Redis 首先检查该键是否已经过期。如果确认已过期,Redis 会立即删除该键并返回“键不存在”的结果。这种方式不会主动扫描所有过期键,仅在访问时触发检查。
优点是 CPU 开销小、开销分散;缺点是对于那些“很少被访问”的过期键,如果无人访问就不会被删除,会长期占用内存。
2. 主动/定期删除(Active / Periodic Expiration)
为了处理那些不会被访问的但过期的键,Redis 定期执行一个后台任务来随机检查若干带过期时间的键,从中删除已过期的。具体行为为:
- Redis 在每个数据库中保存一个 “expires” 字典(或类似数据结构)记录有过期时间的键。
- 周期性地(默认间隔、可配置)随机抽取一定数量的这些键来检查是否过期。若发现过期,则删除。
- 如果在一次扫描中发现过期键比例很高,Redis 可能会继续多次扫描以清理更多。
- 为避免后台删除任务导致过高 CPU 使用率,Redis 会限制每个周期里检查的键数,以及整个扫描的最大持续时间或 CPU 时间比例。
这样,被动删除解决访问触发的问题,定期删除解决“不访问也过期”的问题。
内存淘汰(Eviction)策略
当 Redis 的内存使用已达上限(maxmemory 设置)之后,再插入新的键或写操作可能导致内存溢出。Redis 提供多种淘汰策略(eviction policies)来决定哪些键应该被删除,以释放空间。
1. 如何开启内存淘汰
你需要在 redis.conf 或运行时通过 CONFIG SET 设置 maxmemory(最大可用内存),以及设置 maxmemory-policy(淘汰策略)。若不设置 maxmemory,Redis 默认不限制内存,淘汰策略不会被触发。
2. 常见的淘汰策略类型
下面是 Redis 支持的主要淘汰策略类别,以及它们适合的使用场景:
- noeviction:当内存满时,不淘汰任何键,尝试写操作将返回错误。适合不能丢失任何数据且写入可控的场景。
- allkeys-lru:从所有键中淘汰最近最少使用(Least Recently Used)的键。适用于缓存用途,希望保留近期被访问的数据。
- volatile-lru:仅对设置了过期时间的键使用 LRU 淘汰。适合你的数据里部分键设置 TTL,而你希望淘汰那些带过期时间并且访问较少的键。
- allkeys-lfu:对所有键使用最少访问频率(Least Frequently Used)淘汰,经常访问的键更可能被保留。适合访问频率差异大的工作负载。
- volatile-lfu:仅对有过期时间的键使用 LFU 淘汰。
- allkeys-random:从所有键随机淘汰。适合不关心淘汰哪一个键,只要释放空间的场景。
- volatile-random:从设置了 TTL 的键中随机淘汰。
- volatile-ttl:淘汰那些 TTL 剩余时间最短的键(即最快“到期”的键)。适合你希望先淘汰快要过期的键,以保留那些存活时间还长的数据。
3. 策略行为与选关键指标
命中率 (cache hit rate):策略不同可能会导致缓存的命中率差异显著。LRU 与 LFU 通常比随机策略表现好。
内存碎片与开销:LFU 需要额外统计访问频率;过期键太多或扫描策略不好可能导致后台任务占用过多 CPU。
写操作阻塞与延迟:淘汰策略会在插入新键/写入大量数据时触发,可能导致延迟波动。
数据丢失风险:如果你用 Redis 不仅做缓存,而存重要数据,则使用 noeviction 或谨慎选择淘汰策略,以免意外删除关键键。
不同策略的组合与过期键删除 + 淘汰之间的关联
过期键删除机制与内存淘汰策略是两套机制但会互有互动:
- 过期键机制负责清理那些 TTL 到期的键,即使这些键在内存使用没达到上限,也应被删除以释放空间和避免返回陈旧数据。
- 淘汰策略在内存压力大的时候启动,不管键是否过期,只要策略允许,就会删除键以腾出空间。通常会考虑键是否设置过期、使用频率/最近访问时间、剩余 TTL 等因素。
- 在某些策略如 volatile-lru、volatile-ttl、volatile-lfu 中,只有带过期时间的键才可能被淘汰。这和过期键机制有交集:带 TTL 的键既可能被过期机制清理,也可能被淘汰机制用来应急释放内存。
应用场景及建议
下面是几个典型场景,以及在这些场景中如何选择过期键删除 + 淘汰策略的建议。
场景 A:缓存系统(Cache Layer)
你用 Redis 缓存数据库查询结果、API 响应等。缓存允许丢失部分数据,因为数据可从源头重新加载。
建议配置:
- 给缓存键设置合理 TTL(例如几分钟到几小时不等),以保证缓存不过期太久。
- 设置 maxmemory 限制,防止 Redis 占满所有内存。
- 淘汰策略建议用 allkeys-lru 或 allkeys-lfu,保留近期或频繁访问的数据。
- 配合过期机制:即使有 TTL,定期删除过期键以释放空间。被动删除保证访问时过期就清理,主动删除处理未被访问部分。
场景 B:会话/登陆 Token 管理
这些键通常有固定生命周期,例如用户登录 token、会话等,且希望在到期时自动失效。
建议配置:
- 给这些键设置明确 TTL。
- 淘汰策略可选择 volatile-ttl 或 volatile-lru,优先淘汰那些带过期时间且接近到期的 session 键。
- 使用主动删除机制以避免积累大量旧 session。
场景 C:配置/元数据/静态数据
这些键更新少,或需要长期保留,有些可能永不过期。
建议:
- 对于要长期保留的配置,可不设置 TTL。
- 对于可能过期或者更新频繁但不重要的数据(如缓存副本、报告结果等),设置 TTL。
- 淘汰策略如果用于此类数据所在实例,可能要避免删除关键配置,因此最好将配置数据与缓存/过期数据分离,存在不同的 Redis 实例或不同前缀,并用 allkeys-xxx 策略但确保关键数据在另一个实例中不受淘汰影响。
场景 D:高并发/大规模系统
比如电商、广告系统、社交媒体后台等,一刻可能插入大量键、删除大量 TTL 到期键。
建议:
- 注意过期键在短时间内集中到期可能引发 CPU 峰值以及淘汰压力。可以分散设置 TTL 或使用随机偏移策略,使到期分布更均匀。
- 监控 Redis key 个数、过期键数、内存利用率、Eviction 次数与频率。
- 用合适的淘汰策略,可能 volatile-lru 或 allkeys-lru 通用性好;如果访问频度差别非常大,LFU 策略能提升保留热门项的命中率。
- 在写入操作频繁时,注意避免淘汰策略导致写延迟瞬间上升。
总结
- Redis 的过期键删除机制主要由被动删除 + 定期/主动删除组成。这保证了 TTL 到期的键最终能被清理,无论是否被访问。
- 当内存设限(maxmemory)触发时,Redis 的淘汰策略(Eviction Policy)决定哪些键被删除以腾出空间。不同策略有不同优缺点,要根据业务场景选择。
- 在缓存系统中,TTL + allkeys-LRU 或 allkeys-LFU 是常用组合;在对数据稳定性要求高或关键性强的场景中,需要慎用淘汰策略或分实例隔离数据。
- 运维上要监控 key 的 TTL 分布、过期键的积累情况、淘汰操作频率与内存占用趋势,以便及时调整配置。