redis list 不支持过期时间,expire 仅作用于键层级且不自动刷新;应使用 lua 脚本原子化 ltrim 控制长度,或改用 sorted set 实现时间维度过期。

Redis List 本身不支持过期时间,硬设 EXPIRE 无效
直接对 LPUSH 或 RPUSH 写入的 List 键执行 EXPIRE 确实能成功返回 1,但实际效果是:整个 List 键会在过期后被删除——这看似“有效”,但问题在于,List 是动态结构,你往往只想保留最近 N 条,而非等整条链表“自然死亡”。一旦有新元素持续写入,EXPIRE 就彻底失效(因为 Redis 不会自动刷新过期时间),最终内存只增不减。
常见错误现象:EXPIRE mylist 60 执行后,TTL mylist 显示正常,但一小时后发现 mylist 还在,且长度暴涨;或者更糟,某次 LPUSH 后 TTL 突然变成 -1(表示无过期)。
- 根本原因:Redis 的过期机制只作用于键(key)层级,不感知 List 内部结构变化
-
LPUSH/RPUSH等写操作不会重置 TTL,除非显式调用EXPIRE或PEXPIRE - 无法靠客户端定时轮询
LLEN+LTRIM来“模拟过期”,网络延迟和并发会导致竞态(比如两个客户端同时判断长度超限,都执行LTRIM,可能删多或删少)
用 Lua 脚本原子化实现“长度上限 + 自动截断”
真正可控的方式,是把“检查长度 → 超限时截断”封装进一个 Lua 脚本,利用 Redis 单线程执行 Lua 的原子性,避免竞态。这不是“给 List 加过期”,而是用固定长度约束来达成等效效果。
典型场景:日志队列、消息缓存、最近搜索记录——你关心的是“最新的 100 条”,不是“存在了多久的 100 条”。
Redis 8.2.3 是一款安全优先的高性能键值存储系统。该版本紧急修复了可能引发远程代码执行(RCE)的高危漏洞(CVE-2025-62507),并解决了 HyperLogLog 及 Cuckoo Filter 等数据结构在特定场景下的崩溃问题。建议所有用户立即升级,以保障生产环境的系统稳定与数据安全。
eval "local len = redis.call('LLEN', KEYS[1]); if len > tonumber(ARGV[1]) then redis.call('LTRIM', KEYS[1], -tonumber(ARGV[1]), -1); end" 1 mylist 100
-
KEYS[1]是 List 键名(如mylist),ARGV[1]是最大允许长度(如100) -
LTRIM key start stop的-100, -1表示保留最后 100 个元素,符合“最新优先”直觉 - 脚本必须每次写入后立即执行(比如在
LPUSH后紧跟EVAL),不能依赖后台定时任务——否则中间窗口期仍可能超长 - 如果用
RPOP/LPOP消费,也建议在消费后补一次检查(防止消费变慢导致堆积)
需要真·时间维度过期?改用 Sorted Set + 时间戳
当业务逻辑明确要求“只保留 5 分钟内的数据”,而不是“只保留最新 100 条”,List 就不是合适的数据结构。Sorted Set 支持按 score(可设为 Unix 时间戳)排序和范围删除,才是正解。
操作流程:ZADD myzset <timestamp> "item"</timestamp> 写入,再用 ZREMRANGEBYSCORE myzset 0 <cut_off_timestamp></cut_off_timestamp> 清理过期项。这个清理动作可以封装成 Lua 脚本,也可以由客户端在写入前/后主动触发。
- 优势:score 可精确到毫秒,
ZREMRANGEBYSCORE原子、高效,时间语义清晰 - 注意点:score 必须是数字,中文或 UUID 不能直接当 score;若需保留原始顺序,可用
timestamp + sequence_id拼接成唯一数字 - 性能影响:
ZREMRANGEBYSCORE时间复杂度是 O(log(N)+M),M 是被删元素数,只要不过度堆积(比如每秒删几万),基本无压力
别忽略客户端配合与监控告警
再好的 Lua 脚本也救不了错误的使用姿势。最容易被忽略的是:没有在客户端层面兜底。
- 所有写入路径(包括异常分支、重试逻辑)都必须包含长度控制脚本,漏掉一处就可能让 List 暴涨
- 定期用
MEMORY USAGE <key></key>和LLEN <key></key>监控 List 实际内存占用,单纯看长度可能误判(比如元素本身很大) - 设置告警:当某个 List 的
LLEN持续超过阈值(如 10 倍预期最大值),说明上游控制逻辑失效,需人工介入 - 不要在 Lua 脚本里做耗时操作(比如循环遍历上千元素做条件过滤),Redis 会阻塞其他请求;复杂过滤应放到客户端
真正的难点不在写脚本,而在于把“长度约束”这个规则,严丝合缝地嵌入所有数据写入路径,并持续验证它是否始终生效。










