volatile-lru策略下,永不过期的key绝不会被删除,因其仅从expires字典(含TTL的key)中筛选淘汰目标;TTL返回-1表示永不过期且被忽略,-2表示key不存在;误判常源于SDK默认过期、代码混用或运维脚本覆盖TTL;验证需实查TTL值、监控evicted_keys与expired_keys,并在写入时显式声明持久化。

volatile-lru 策略下,永不过期的 key 绝对不会被删除——它只在设置了 EXPIRE 或 PEXPIRE 的键中筛选淘汰目标。但这个“安全”是有前提的:你得确认哪些 key 真的没设过期时间,且没被误操作覆盖。
为什么 volatile-lru 不会动你没设过期时间的 key
Redis 的 volatile-lru 淘汰逻辑非常明确:它只从 Redis 内部维护的 expires 字典(即所有带 TTL 的 key 集合)里取样、排序、淘汰。任何没进这个字典的 key,比如用 SET key value 直接写入的、或执行过 PERSIST key 的,根本不会出现在候选池里。
-
TTL key返回-1→ 表示该 key 永不过期,volatile-lru完全忽略它 -
TTL key返回-2→ 表示 key 不存在,不是“永不过期”,而是压根没存 - 即使你用
SET key value EX 3600写入后又立刻PERSIST key,它也会从expires字典中移除,从此免疫volatile-lru
常见误判场景:你以为它永不过期,其实不是
很多线上问题都源于“自以为没设过期时间”,结果 key 被悄悄淘汰了。排查重点不在策略本身,而在 key 的实际状态:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 客户端 SDK(如 Spring Data Redis)自动加了默认过期时间:比如
redisTemplate.opsForValue().set("k", "v")在某些配置下会隐式调用SETEX,务必检查RedisTemplate的defaultExpiration属性是否为-1 - 业务代码里混用了
SET和SETEX,或使用了封装方法(如cache.put(k, v, 24, HOURS)),漏看参数默认值 - 运维脚本或监控工具批量执行了
EXPIRE,比如按 pattern 扫描 key 并统一设置 TTL,把本该常驻的 key 也覆盖了 -
CONFIG SET maxmemory-policy volatile-lru是运行时生效的,但如果你之前用的是allkeys-lru,那些已存在的无过期 key 可能已被淘汰过一次,数据已丢失
验证和加固:三步确认你的 key 真的“免疫”
别只信代码注释或配置项,直接查 Redis 实例状态:
- 用
redis-cli --raw -h host -p port KEYS "video:*"抽样几个核心 key,再对每个执行TTL key_name,确认返回全是-1 - 用
INFO memory查看mem_clients_normal和mem_clients_slave外,重点关注expired_keys和evicted_keys计数器——如果evicted_keys持续上涨但expired_keys几乎为 0,说明淘汰确实发生在 volatile 键上,而非你怀疑的“误删常驻 key” - 在关键写入路径加日志:比如每次写入视频元数据前,打印
redisTemplate.execute((RedisCallback<boolean>) con -> con.set(key.getBytes(), value.getBytes(), Expiration.persistent(), RedisStringCommands.SetOption.UPSERT))</boolean>这类显式声明持久化的调用,避免依赖默认行为
真正容易被忽略的点是:volatile-lru 的“安全区”只由 expires 字典决定,而这个字典的入口完全取决于你每一次写入时是否带了过期参数——它不看业务语义,也不记历史,只认当前状态。哪怕你上周刚用 PERSIST 解除了过期,只要今天某段代码又执行了一次 EXPIRE key 3600,它就立刻进入淘汰候选池。

















