
开篇先抛一个结论Redis里给键设置过期时间绝对不只是“存进去了顺手调一个EXPIRE”那么简单。我见过太多项目因为过期时间用得不讲究线上出了缓存雪崩、锁误删、数据不一致的幺蛾子。这篇文章把Redis过期时间的原理、命令、策略、常见坑一次讲透尤其是那些你在官方文档和面试题里未必看得全的细节全是我实际运维和开发中反复验证过的经验。1. 为什么非要给Redis键设置过期时间三个最核心的动机先说动机不然你根本不知道这个参数用好了能救多大场子。Redis是内存数据库内存是有限的不像磁盘那么“便宜”而且Redis主线程是单线程模型如果内存被垃圾数据占满会导致写入失败、淘汰风暴、甚至OOM崩溃。给键设置过期时间最直接的作用就是防止内存无限膨胀这个不用多解释。但除了省内存过期时间更核心的价值是给业务语义兜底。举个例子用户登录之后生成一个session id存到Redis里如果不设置过期时间那每个登录用户都会在Redis里占一个键永远不清理的话数据库里删了用户、改了密码、封了号Redis里的session还活着这就有安全风险了。再比如手机验证码发出去之后5分钟内有效这本身就是业务规则必须让Redis在5分钟后把这个键干掉。也就是说过期时间不只是“防止内存被占满”的运维手段它本身就是业务逻辑的一部分。第三个动机可能很多人没意识到用过期时间可以替代一部分定时任务的活儿。比如限流计数器按秒、按分钟做滑动窗口如果每个窗口都自己起一个定时任务去清理既麻烦又容易出并发问题。用Redis的过期时间让键自动消失窗口自动重置代码量直接少一大截。还有分布式锁场景没有过期时间就死锁了这个后面细讲。所以你在决定“这个键要不要设置过期时间、设置多长”的时候脑子里应该同时过三件事内存压力、业务有效性、系统自动恢复能力。三件事都站得住脚这个过期时间才算是设对了。2. 过期时间怎么设5种常用命令与3个容易混淆的细节2.1 从无到有设置过期时间EXPIRE、PEXPIRE与SETEX最常用的命令就是EXPIRE key seconds。注意单位是秒这是绝大多数人每天在敲的命令。如果你需要更精细的控制可以用毫秒级别的PEXPIRE key milliseconds不过日常开发里用秒基本够了毫秒级更多出现在某些限流算法、短时效token里。在设置新键的时候更推荐直接用SETEX key seconds value它把“写入值”和“设置过期时间”合并成一个原子操作。为什么强调“原子”因为如果你先SET key value再EXPIRE key seconds这两条命令之间存在一个时间窗口如果进程在这两条命令之间崩了这个键就成了永不过期的僵尸键。在分布式环境下这个窗口还可能被其他线程读到产生脏数据。SETEX就规避了这个风险。Redis 6.2以后官方还推荐用SET key value EX seconds这种写法效果等价于SETEX而且可以附带NX不存在才设置等选项后面讲分布式锁的时候会用到。还有几个变体需要知道expireat key unix-timestamp设置的是“到什么时间点过期”接受秒级时间戳pexpireat key unix-timestamp-ms是毫秒级时间戳。什么时候用EXPIREAT典型的例子会员到2025年12月31日23:59:59过期、优惠券到某个固定时间点失效。这类业务语义是“绝对时间点”不是“相对时长”就必须用EXPIREAT。而验证码、临时缓存这种“从写入时刻开始算5分钟”的场景用EXPIRE就行。2.2 键已经存在但想调整过期时间PERSIST与TTL三兄弟把EXPIRE理解为“给键设置或更新一个生存时长”更准确。同一个键可以反复调用EXPIRE每次调用都会覆盖上次设置的过期时间。这个特性在分布式锁的续期场景里至关重要后面单独说。如果你想取消过期时间让它变成永久键用PERSIST key。那怎么看一个键还剩多久的生命有三个命令TTL key返回剩余秒数。如果是-1表示永不过期如果是-2表示键不存在或者已经过期被删了。PTTL key返回剩余毫秒数比TTL更精细。TTL命令在键不存在和键已过期两种情况下都返回-2所以排查问题时需要配合EXISTS key来判断到底是不存在还是过期了。这个细节很关键。我做过一个线上问题复盘某个任务队列从Redis里取数据一直报“键不存在”查了半天最后发现是键虽然存在但已经被后台惰性删除线程回收了EXISTS返回0。如果当时早点用EXISTS和TTL组合排查至少能省两个小时。2.3 过期时间的“生命周期”规则覆盖、持久化与负面案例三个经验性的规则说一下全是从实际踩坑里总结出来的。规则一SET、GETSET这类会覆盖键值的命令通常会清掉旧的过期时间。也就是说你如果对一个设置了EXPIRE的键执行了SET它又变成永久的了。这个反直觉吧在很多人的认知里“我只是改了值过期时间应该还在啊”但Redis不是这么设计的。要避免这个问题要么在SET的时候重新带上EX参数要么直接用SETEX要么用GETSET之前看清楚语义。规则二INCR、LPUSH、HSET这类修改值内容的命令不会清掉过期时间。这个其实更符合直觉但很多人在排查“为什么这个计数器还活着”的时候会甩锅给INCR其实INCR没事。规则三重命名键RENAME之后过期时间会跟着走目标键原本的过期时间会被覆盖。如果你RENAME key-a key-b而key-b原来设了10秒过期新键key-b会继承key-a的过期时间旧key-b的过期时间直接作废。这个行为在官方文档里写得很明确但绝大多数人没注意过。命令行快速试一下redis SET mykey hello OK redis EXPIRE mykey 60 (integer) 1 redis TTL mykey (integer) 57 redis SET mykey hello again OK redis TTL mykey (integer) -1看到没一个SET就把过期时间给清了这就是实战中极容易忽略的“规则一”。3. 过期键的内部机制为什么它不按你设的时间“准时”消失3.1 Redis的过期键删除策略不是实时的而是“惰性定期”混合很多人设了个EXPIRE 10然后第10秒整点去查这个键发现它还能被读到就开始怀疑Redis是不是出Bug了。其实不是这涉及到Redis的过期键删除机制。Redis默认采用的不是“时间一到立即物理删除”的策略而是“惰性删除 定期删除”的组合。惰性删除是什么意思就是当你去访问一个键的时候Redis才检查它是否过期如果过期就先删除再返回空。这种策略的好处是省CPU不用浪费资源去扫描根本没人访问的键坏处是如果一个键过期了但始终没人访问它它就会一直躺在内存里占用空间。定期删除是用来弥补惰性删除不足的。Redis会以一定的频率默认每秒十次左右随机抽取一部分设置了过期时间的键检查它们是否过期过期的就删掉。注意“随机抽取”这个词它不是全量扫描而是抽一部分做检测每轮抽多少、耗时上限都有内部限制用来平衡CPU和内存的占用。这就是为什么你会发现一个键过期了十几秒甚至更久它还占着内存没被清掉——它可能就是没被抽到也没人访问它。所以如果你要严格保证某个键“过期后立刻消失”Redis原生机制是做不到的你只能在业务层面做妥协要么接受一个极短的“残留窗口期”要么在业务代码里读取时主动判断业务时间戳。后一种方案在秒杀、抢红包这种高一致性的场景尤其常见。3.2 内存淘汰策略与过期时间的关系maxmemory-policy怎么选当Redis的内存达到maxmemory上限时它会根据maxmemory-policy配置来执行淘汰策略。这部分经常和“过期时间”纠缠不清因为很多人以为内存满了就只删过期的键这是错的。Redis的内存淘汰策略分两大类针对所有键的以及只针对设置了过期时间的键。常用配置有noeviction内存满了直接拒绝写入返回错误。这个是默认值在线上如果没人改而且数据量超过内存会出现写入报错OOM command not allowed when used memory maxmemory。allkeys-lru从所有键里按LRU最近最少使用淘汰不管有没有设过期时间。volatile-lru只从设置了过期时间的键里按LRU淘汰没设过期的键不受影响。allkeys-random/volatile-random随机淘汰所有键 / 只随机淘汰设置了过期时间的键。volatile-ttl从设置了过期时间的键里优先淘汰剩余存活时间最短的也就是快过期的先被干掉。allkeys-lfu和volatile-lfuRedis 4.0之后引入的LFU最不经常使用策略按访问频率淘汰。这个选择会直接影响你的业务表现。比如说你给某个热点新闻缓存设置了1小时过期时间结果到了内存紧张时如果配的是volatile-lru那这个热点键就可能提前被淘汰缓存命中率下降大量请求穿透到数据库如果配的是allkeys-lru它还有可能活得更久因为访问频率高。所以这里没有绝对的对错关键是你得知道自己配的是哪一个。一个我常用的判断方式缓存类数据即使设置了过期时间用allkeys-lru或allkeys-lfu因为Redis里存的绝大多数键本质都是缓存性质的数据都该被内存压力淘汰只有需要严格保活的业务键比如分布式锁、session才希望它们不受淘汰影响那就要把它们设计成不带过期时间的永久键同时接受它们可能长期占用内存的风险——这其实又是一个取舍。3.3 主从复制下的过期键处理主库删了从库怎么知道这个问题很隐蔽但线上一定会碰到。在主从架构里过期键的删除并不是主库删完直接通知从库删而是从库会记录一下“这个键已经过期了”当客户端访问从库的这个键时从库按惰性删除处理返回空。也就是说从库的过期键删除是“被动”的它依赖客户端的访问或者主库同步下来的DEL命令。主库在定期删除或者惰性删除一个过期键时会向所有从库发送一条DEL命令告诉它们“这个键已经没了你们也删掉”。所以正常情况下主从最终是一致的但中间会有一个时间窗口主库已经删了从库可能还在等同步这个窗口内客户端如果读了从库可能还会拿到数据从库返回已经过期的值。这个窗口带来的危害很典型如果你做读写分离写走主库、读走从库缓存数据在主库过期了但读请求在从库还能读到旧值那么业务上就表现为“数据明明应该失效了某些请求还是拿到旧数据”。避免这个问题的路子有两条一是对一致性要求高的数据直接放弃读写分离读写都走主库二是在业务代码里把过期时间冗余到value里读取时自己校验业务时间戳发现过期就主动丢弃并触发穿透回源。第二种方案我强烈建议核心业务数据都用上它是纯客户端逻辑不依赖Redis内部机制可靠性反而更高。还有一个主从场景容易忽略从库的内存淘汰策略一般建议设置为和主库一致否则可能出现主库保留某些键、从库却因为内存压力把它们淘汰掉的诡异现象导致主从数据不一致。4. 三个高频场景的过期时间实战缓存、分布式锁、验证码4.1 缓存场景过期时间绝不是“拍脑袋定个5分钟”缓存数据设多长过期时间是运行时最容易出问题、也最值得花时间思考的配置。设短了命中率低大量请求穿透到数据库设长了数据新鲜度差而且一旦库里数据变了缓存可能很久都更新不过来。业界常用的一个方法是“业务容忍时间窗口”想一想“这条数据即使晚5分钟才更新用户能接受吗”能就设5分钟只能接受30秒那就设30秒。这个值没有绝对标准完全取决于业务属性。但是光设一个固定值是不够的。我见过很多项目用同一个过期时间常量来给所有缓存键设置过期时间结果在某次大促或运营活动时上线的一批数据几乎同一时间过期所有请求一瞬间全部打到数据库数据库直接被打爆。这就是经典的缓存雪崩。怎么避免两个简单有效的办法第一个是给过期时间加一个随机抖动。比如基础过期时间是300秒实际设置的时候在240到360秒之间随机取值。这个改动极小但对削峰填谷效果非常明显相当于让键的过期时间点在整个时间轴上都散开了。代码示例Redis 6.2客户端Java伪代码风格int baseExpireSeconds 300; int jitterSeconds ThreadLocalRandom.current().nextInt(-60, 61); int finalExpireSeconds baseExpireSeconds jitterSeconds; redisTemplate.opsForValue().set(key, value, finalExpireSeconds, TimeUnit.SECONDS);第二种方法是手动错开“天然大流量”的过期时刻。比如每个小时整点大量更新缓存就把过期时间设置成“当前时间到下一个非整点时刻的时长”避免所有键围着同一个时间点过期。缓存击穿和缓存穿透也顺便提一嘴因为它们和过期时间设置的关联也很大。击穿指某个热点键过期的瞬间大量请求同时穿透到数据库穿透指查一个根本不存在的键每次都穿透。应对击穿可以给热点键“不设过期时间、主动更新”或者设置一个比较长的过期时间作为“兜底”然后靠定时任务提前刷新。应对穿透可以让过期时间配合“空值缓存”来实现——查不到时也写一个带过期时间的空值避免每次请求都打到数据库。4.2 分布式锁场景过期时间设多长才不会误删别人的锁分布式锁可能是“过期时间设不好”危害最严重的场景。用Redis实现分布式锁最常见的写法是Boolean locked redisTemplate.opsForValue() .setIfAbsent(lockKey, requestId, 30, TimeUnit.SECONDS);setIfAbsent对应SET key value NX配合EX参数实现“不存在才设置并且自动过期”。关键坑点过期时间设得太长如果拿到锁的线程真正执行任务的时间远超预期——比如数据库慢查询、依赖的接口超时——锁早就过期释放了另一个线程拿到锁进来了两个线程同时执行临界区代码分布式锁就形同虚设。设得太短单个任务稍微多跑一会儿就会把锁提前释放导致并发进入。我之前踩过一次很深的坑一个数据同步任务平时几秒就跑完结果上线那天依赖的数据源突然变慢整个任务跑了将近一分钟锁30秒就过期了。另一个节点拿到锁重新跑同样的任务两边同时写了重复数据第二天数据团队找到我的时候我都不知道该怎么解释。所以现在我在设计分布式锁时会坚持三件事第一锁的过期时间要设一个“比预估最坏情况更长”的值而不是比“平均时长”更长。比如平均5秒跑完的任务我会把锁过期时间设成30秒给突发情况留足余量。第二业务代码里要加“续期”逻辑也就是常说的看门狗机制。Redis官方Redisson框架里自带一个watch dog默认每10秒续期一次把锁的过期时间延长到30秒。如果你不用Redisson自己实现也不难用一个后台定时任务在锁还持有的情况下每隔一段时间执行一次EXPIRE lockKey 30任务结束后主动DEL释放锁。关键是要给这个“续期线程”加一个标识确保只有持有锁的线程能续期、能释放。第三释放锁时不要用DEL key要用Lua脚本校验value是不是自己的。原因很简单如果锁已经过期被另一个线程拿走了你直接DEL会把别人的锁删掉。正确写法if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end用Java的话Spring Data Redis的DefaultRedisScript可以完美承载这段Lua脚本把校验和删除原子化。再说一个容易搞错的点同一把锁如果次获取时用了不同的key那过期时间就要分开设。很多人会“图省事”把所有锁的过期时间统一成一个常量结果不同业务场景的锁时长需求不一样必然有一方会出问题。正确做法是按业务维度定义不同的过期时间常量让每把锁的时长都贴合自己的业务特征。4.3 验证码与限流场景短TTL的陷阱与安全写法验证码、短信发送限制、登录失败次数这些短时效数据是过期时间最直观的用途。我的建议是一律用SETEX写时效从“当前时刻”开始计算不要拼接业务逻辑去计算“到23:59:59还剩多少秒”再写入除非业务真的要求按自然日清零。举个典型例子短信验证码5分钟内有效SETEX sms:code:13800138000 300 482913这样写的好处是一个命令完成写入和设过期效果上就是验证码5分钟自动失效。唯一要注意的是如果用户重新获取验证码这个键会被新的验证码覆盖并且过期时间重新从300秒开始倒数这是合理的业务语义。限流场景里有个细节特别值得说滑动窗口限流用Redis的实现很多人喜欢用INCR配合EXPIRE比如“每分钟最多100次”INCR login:limit:user:123 EXPIRE login:limit:user:123 60这段代码有什么问题如果第一次INCR结果恰好是1那这个键刚创建EXPIRE成功没问题。但如果这个键已经存在很长时间了——比如用户连续请求导致键一直存在——EXPIRE每次都把过期时间重置为60秒本来应该“每分钟窗口独立”结果变成了“从最后一次请求开始往后推60秒”窗口被无限续命限流失效。这其实是我上面说的“规则二”的一个应用变体INCR不清过期时间但你的EXPIRE每次都重置了等于你亲手把窗口拉长了。正确的做法是在INCR之后判断返回值只有返回值等于1时才去设置过期时间或者直接用Lua脚本保证原子性local current redis.call(INCR, KEYS[1]) if current 1 then redis.call(EXPIRE, KEYS[1], ARGV[1]) end return current这段脚本在并发下不会出问题因为INCR和EXPIRE被包裹在一个Lua脚本里Redis是单线程执行脚本的天然原子。这也是我在限流场景里主推的写法。顺带说一句如果你用Redisson或Lettuce的特性封装很多客户端已经有类似固定窗口限流的现成组件直接调API更安全。再看一个容易被忽视的细节验证码这类业务过期时间设得好还不够最好在value里再存一个“创建时间”或者“业务有效截止时间”读取时再做一次业务判断。为什么因为刚才说了Redis的过期删除不是实时的而且如果这个验证码键在主从切换时被重新加载到新主库过期时间可能丢失取决于持久化策略和RDB/AOF的具体行为。把业务时效冗余在value里等于给“Redis过期机制可能失效”兜底了一层。这不是多余的设计我线上处理过类似事故值里没有业务时间戳只能干瞪眼。5. 常见问题与排查技巧实录5.1 “明明设了过期时间键却还在”的三大原因这个问题我至少被问了二十次。按照我的排查顺序基本三步走第一步确认键是不是真的设置了过期时间。用TTL key查一下如果返回-1说明过期时间丢了或者根本没设置成功。丢失的原因很多最常见的就是我开头提到的设置过期时间之后又对这个键执行了不带EX参数的SET把老的过期时间清掉了。这个可以通过在写入时强制用SET key value EX seconds来彻底规避。第二步确认是不是内存淘汰策略导致的“键还在但实际已失效”。如果TTL返回-2但EXISTS还是1说明键已经被逻辑过期了只是还没物理删除。另外如果开启了持久化RDB或AOF重启恢复时如果有大量过期键Redis不会在加载阶段立即清掉它们而是等到访问或定期扫描时才删除这也可能造成“重启后键还在”的观感。第三步确认是不是主从延迟导致的。从库可能还保留着主库已经删除的过期键你通过从库读到了旧数据。解决办法上面说过了业务时间戳兜底。5.2 设置过期时间后大量键同时消失缓存雪崩排查思路如果你发现Redis里某个时间点突然大量键同时过期而恰好这个时间点数据库压力暴增那基本就是缓存雪崩。排查起点很简单查日志里数据库慢查询的时间再比对Redis里过期键的分布。怎么主动预防上面提到过随机抖动我再给一个更精细的方案同一批数据写入时让它们的过期时间围绕业务指标散落开比如“基础时长随机数”随机数的范围最好能覆盖基础时长的10%到20%。这样即使基础时长设得再集中实际过期时间点也不会重叠。还有一种隐蔽的“雪崩”是定时任务造成的一批缓存数据每天凌晨2点由定时任务统一刷新结果这批数据都是前一天凌晨2点设置的过期时间也都在凌晨2点附近。如果定时任务延迟几分钟缓存先过期定时任务还没跑所有请求全部穿透到数据库。这种情况的修复方案是把定时任务的刷新逻辑改成“先更新缓存再删旧值”或者给过期时间加随机偏移让它们错开。5.3 通过info命令和日志判断当前过期键的状况排查问题离不开数据支撑。redis-cli里几个命令值得熟记redis-cli info stats重点关注expired_keys字段它表示累计发生过多少次键过期删除。如果这个值在一定时间内急剧增长说明系统里有大量短时效键如果持续为零但内存又一直涨那大概率是有些键根本没设置过期时间。还有一个高频命令redis-cli --bigkeys它会扫描并输出大key大key如果是设置了过期时间的一次过期删除会带来更高的CPU和内存开销在删除大key时可能造成主线程阻塞。遇到这种情况建议把大key的过期时间设成一个比较远的未来再通过后台任务在业务低峰期手动拆分成小批量删除。info memory里的used_memory、maxmemory以及mem_fragmentation_ratio也不可忽略。如果used_memory波动异常配合expired_keys的增长曲线基本能判断是不是过期键堆积和批量删除造成的。5.4 排查工具与命令行实测别忽视慢日志里的删除命令排查过期键问题还有一种很常见的路径通过SLOWLOG GET去看慢日志。有些大key的过期删除惰性删除触发的那一刻可能会执行一个非常耗时的扫描或内存释放操作导致Redis主线程卡顿。这种慢日志不是你发出的GET、SET造成的而可能是一次内部删除行为。举个例子你有一个大哈希几百MB设置了过期时间某一天某个用户请求恰好触发了惰性删除Redis要释放这几百MB的内存这个过程是同步的可能阻塞服务几十毫秒甚至几百毫秒。这在高峰期是致命的。排查方法就是查慢日志看是否有类似del或expire相关的命令注意这个del可能是Redis内部触发的不一定是业务代码发的。对于这种情况我总结了一句话越大的key越不应该依赖Redis自动过期机制去删除。正确方式是设置一个很久以后的过期时间然后自己在业务低峰期分批次HSCANHDEL或者SCANDEL手工拆删。5.5 批量设置过期时间的实战技巧Pipeline、Lua与SCAN的正确姿势线上批量为一批键设置过期时间比如用户活动结束后要把该用户相关的所有临时键设置5分钟后过期。最不建议的方案是for循环里一条条发EXPIRE在N很大的情况下网络往返开销完全不可接受。正确做法是用Pipeline批量发送或者用Lua脚本在服务端一次性处理。Pipeline方式的伪代码JavaListObject results redisTemplate.executePipelined((RedisCallbackObject) connection - { for (String key : keys) { connection.expire(key.getBytes(), 300); } return null; });注意Pipeline不是事务它只是把多条命令打包一次性发给Redis减少RTT。如果中间某一条命令失败其他命令不受影响。如果要求“要么全部成功要么全部失败”那就应该用MULTI/EXEC事务或者用Lua脚本包一层。Lua脚本的一个典型场景是“批量设置不同过期时间并返回结果”for i 1, #KEYS do redis.call(EXPIRE, KEYS[i], ARGV[i]) end return 1实际开发中如果你用的是Spring Boot可以这样组织脚本参数DefaultRedisScriptLong script new DefaultRedisScript(); script.setScriptText(for i 1, #KEYS do redis.call(EXPIRE, KEYS[i], ARGV[i]) end return 1); script.setResultType(Long.class); redisTemplate.execute(script, keys, expireSecondsArray);还有一点如果你的批量键是通过SCAN模式匹配出来的记得一次SCAN的游标循环里处理完后需要再次调用EXPIRE设置不要在SCAN迭代期间对键做删除操作可能会导致游标跳跃或漏键。5.6 大数据量下的过期键监控redis-cli的--stat与采样技巧最后分享一个排查高峰期问题的小技巧不要一上来就全量MONITOR或者--bigkeys线上大实例直接跑--bigkeys可能会卡主线程慎重。我会先看redis-cli --stat它每秒输出一行的关键指标包括expired_keys的变化速率。如果看到速率突然飙高再想办法定位是哪些键在过期、是不是大key。如何定位是哪些键在过期其实没有直接的命令去查询“刚刚过期了哪些键”。我的经验做法是在业务代码里统一对关键缓存做一层拦截记录每个缓存键的过期时间并在读取时打点如果发现某个时间段内大量键的TTL同时趋近于0就说明这批键即将集体过期需要提前处理。这个“TTL统一监控”的方案很多团队是拿SCAN配合MEMORY USAGETTL来实现的但注意SCAN会遍历整个键空间在高负载实例上要控制批次大小建议在从库上执行采样。6. 我的最终实操建议清单说再多原理不如给一份可以直接参考的经验清单。以下是我在多个项目里沉淀下来的Redis过期时间使用规范第一写入时尽量用SET key value EX seconds或者SETEX避免“SET后再EXPIRE”的中间态。Redis 6.2之后SET的EX、PX、NX、XX、KEEPTTL等选项非常齐全几乎可以满足所有基础写入需求。KEEPTTL这个选项要特别记住它表示覆盖值的时候保留原有的过期时间解决了我前面说的“SET会清掉过期时间”的坑。第二业务判断时不要依赖Redis的过期时间作为唯一标准。把业务上的有效截止时间冗余到value里或者单独存一个辅助键。尤其是在高一致性场景Redis过期机制只是兜底真正业务是否有效要以value里的时间戳为准。第三过期时间尽量加随机抖动所有缓存类数据都要遵守。抖动的意义不只是防雪崩还能让内存淘汰策略更平滑不至于某个时刻大量键同时被标记为volatile候选。第四分布式锁的过期时间一定要设置而且一定要配合续期和Lua校验删除。宁可把过期时间设长一点加续期也不要设短了出现并发执行。锁被误删这个问题比锁长时间不释放造成的危害更大。第五大key不要依赖过期机制来删除。要么不设过期时间靠业务主动更新清理要么设一个很远的过期时间平时自己分片删除。第六监控不可少。info stats里的expired_keys、info memory的内存变化、SLOWLOG里的删除慢命令至少要做一个周期巡检的报表工具。很多问题不是没有征兆而是你根本没看。7. 最后分享一个小技巧用MONITOR对比TTL排查过期时间的隐性Bug这个技巧是我有一次半夜排查问题时候悟出来的。当时线上某个缓存数据总是比预期提前失效但代码里明明设置的过期时间是10分钟让人百思不得其解。后来我临时开了MONITOR发现每隔几分钟就有一条EXPIRE命令被发往Redis而且来自某个异步线程。追下去才发现同事在另一个人任务里写了一个“定期刷新这批键过期时间”的逻辑把本来10分钟的有效期强制刷新成了5分钟。如果你也遇到这种“过期时间神秘变化”的问题MONITOR对着线上输出抓一段时间再按EXPIRE、SETEX、SET这些关键词过滤通常几秒钟就能定位到是谁在动你的键。生产环境执行MONITOR会有性能开销不建议长时间开启但短时间、在低峰期、针对单个Redis实例做定向排查是非常高效的手段。Redis过期时间这个功能表面上是调用一个命令、传个数字实际上牵扯到删除策略、主从同步、内存淘汰、业务语义、并发控制等多个层面。把这套机制吃透了你不仅能避免大量线上事故还能在一些架构设计比如缓存治理、分布式锁、限流算法上游刃有余。希望这篇文章能帮你把这些坑都绕过去。