news 2026/9/8 11:48:57

Redis过期时间机制详解:从命令选型到分布式锁与缓存雪崩的防护

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Redis过期时间机制详解:从命令选型到分布式锁与缓存雪崩的防护

1. 为什么一个“过期时间”值得单独拿出来写一篇

先问个问题:你写 redis 的时候,是不是经常就是set key value,然后在某些需要过期的场景下想起来用一下EXPIRE?我见过太多项目里 expiry 用得相当随意的代码——有的同学给 key 设了过期时间,结果发现内存只增不减;有的用 SETEX 存会话,却没搞明白它对已存在的 key 是怎么处理的;还有人把分布式锁的过期时间拍脑袋定了个 5 秒,然后在高并发下眼睁睁看着锁提前失效,多个线程同时怼进临界区。

这事的本质是什么?redis 的过期时间不是一个“锦上添花”的功能,它直接关系到你的缓存命中率、内存水位、数据一致性,甚至整个系统的可用性。最典型的场景就摆在那:验证码 5 分钟失效、登录 token 7 天过期、订单未支付 30 分钟自动关闭、限流窗口按秒滑动。这些都是靠 redis 过期机制在背后撑着。你要是没把这套机制的原理、命令选型、底层策略和那些隐藏很深的坑搞清楚,线上迟早要出事。

这篇文章我就不讲那些入门文档里抄来抄去的概念了,直接围绕“设置过期时间”这个点,把它拆开揉碎:命令怎么选、底层怎么删、分布式下怎么用、哪些参数会反直觉、线上遇到问题怎么排查。适合刚接手 redis 的初级开发,也适合用了两年 redis 但没系统性踩过坑的中级工程师。看完你至少能回答这几个问题:为什么我的 key 过期后内存没有立刻释放?为什么设置了过期时间的锁还是不安全?为什么同一批 key 的过期时间不能设成完全一样?

2. 过期时间的基本操作:命令层你未必用对了

2.1 设置过期时间的四种姿势

先用一张表把最常用的命令摆出来,这是基础中的基础,但我敢说有不少人是混着用、没想过差别的:

命令语法过期时机适用场景
EXPIREEXPIRE key seconds对已有 key 追加过期时间
PEXPIREPEXPIRE key milliseconds毫秒需要毫秒级精度的短时控制
SETEXSETEX key seconds value设置字符串值并同时指定过期
SET + EX/PXSET key value EX seconds秒/毫秒原子设置值和过期时间,推荐用这个
SETNX + EXSET key value NX EX seconds分布式锁的标准写法

注意看第三行和第四行。SETEXSET key value EX seconds本质上都能达到同样的效果,但很多老文档推荐后者,原因在于SET命令在 Redis 2.6.12 之后支持了EXPXNXXX这些参数,一句话就能完成“设置值 + 过期时间 + 不存在才写”的组合操作,减少了一次网络 RTT。在有脚本化或流水线的场景里,这个优势会被进一步放大。

还有一个经常被忽略的细节:EXPIRE是给已经存在的 key 追加过期时间,SETEX是写新值并覆盖旧的过期设置。如果你业务上有“写缓存 = 覆盖式更新”的习惯,用SETEXSET ... EX反而更安全,因为它天然把旧 key 的剩余存活时间给清掉重算了。而EXPIRE单独使用的时候,要小心别在一个已经有过期时间的热 key 上重复调用,导致过期时间被无意中延长——这个问题后面在坑位表里细说。

2.2 查询与删除:TTL 是你排障的第一抓手

设置完过期时间,你总得验证吧?和过期时间直接相关的查询命令是这三个:

  • TTL key:返回剩余存活秒数。
  • PTTL key:返回剩余存活毫秒数。
  • PERSIST key:去掉过期时间,让 key 永久保留。

TTL 的返回值一共有三类情况,这点特别重要:

返回值含义
正整数剩余存活时间(秒/毫秒)
-1key 存在但没有设置过期时间
-2key 不存在(或已过期被删除)

我第一次排查线上缓存失效问题时,就是靠 TTL 的分布情况定位的。比如执行TTL user:123:token返回 -1,说明这个 key 当初写入的时候压根没带上过期时间,那问题不出在过期策略,而出现在写入代码里。返回 -2,则说明 key 已经没了,要么是过期被清掉了,要么是内存淘汰策略把它逐出了,要么是被人 DELETE 了。先把 TTL 分清楚,再往下查原因,成功率会高很多。

另外,PERSIST这个命令虽然看着简单,实战里会用的人不多。比如你在运营后台临时要把某个活动页缓存延长一天,正确的做法不是把值重新写入一边,而是直接PERSIST key去掉过期,或者EXPIRE key 86400重新设定。但要注意,PERSIST 是 O(1) 操作,在热 key 上没问题,如果长时间持有的 key 突然需要统一清理,那就要考虑批量扫描 + 删除,别指望逐条 PERSIST 能撑住大规模场景。

2.3 一个练习:5 分钟快速验证你对过期时间的理解

这个验证方法很简单,在本机起个 redis,然后开两个窗口操作。

窗口 A 执行:

127.0.0.1:6379> SET page:home "<html>...</html>" EX 30 OK

窗口 B 每秒执行一次TTL page:home,观察变化。这个过程本身没什么难度,但请你同时做以下几件事:

  1. DEBUG OBJECT page:home看内部编码和 idle 时间,了解这个 key 的性质。
  2. 在 5 秒时用PERSIST page:home,再观察 TTL 是否变回 -1。
  3. 在 15 秒时用GETSET page:home "<new>",观察过期时间还会不会沿用(不会,会清掉)。
  4. 在 20 秒时强行DEL page:home,再查EXISTS page:homeTTL,确认都是 0 和 -2。

这一轮下来,你对命令层的行为模式就会有肌肉记忆。别小看这一步——后面讲到底层策略、分布式场景的坑,全都是建立在这些基础行为之上的。

3. 底层原理:过期 key 是怎么被删掉的,为什么删得不及时

3.1 redis 怎么记录过期时间

你执行EXPIRE key 60之后,redis 其实干了两件事:在当前 key 的 dict 里找到对应 entry,再把“过期时间”这个元数据记录到另一个专门的结构里。

这个结构叫expires字典,在 redis 源码里是dict *expires。它的 key 是指向主字典某个 entry 的指针,value 是一个 64 位整数,存的是绝对的过期时刻(毫秒级时间戳)。这样设计的好处是:主字典里存真正的数据,过期字典里只存“什么时候该删”的元数据,两套结构互不干扰,查找和删除的性能都保持在 O(1)。

举个例子。你SET foo bar EX 100,redis 内部会先往主 dict 写入foo -> bar,然后往 expires dict 写入foo -> 当前毫秒时间戳 + 100000。注意,这里存的是绝对过期时刻,不是相对存活时长。所以当系统时间发生跳变时,redis 对过期时间的判断会立刻受影响——这就是“为什么我明明设了 10 小时过期,结果系统时间调了一下,key 反而全没了”的根源。这个问题在生产环境因为 NTP 校时偶尔会遇到,后面排查章节细说。

3.2 被动删除:惰性删除

既然 redis 知道了每个 key 的绝对过期时刻,那它是不是搞一个后台线程每秒扫描所有 key、把过期的一把删掉?绝对不是。redis 默认是单线程事件循环模型,如果每隔几毫秒就全库扫一遍,其他所有命令都被卡住,性能直接崩掉。

所以 redis 采用了“惰性删除 + 定期删除”的组合策略。

惰性删除是什么意思?就是你访问一个 key 时,redis 才去检查它到底过没过期。逻辑在expireIfNeeded函数里,执行路径在lookupKeyRead/lookupKeyWrite这类入口。大致流程:

  1. 在 expires dict 里找这个 key 的过期时间。
  2. 如果没找到,说明没设过期,正常返回。
  3. 如果找到了,用当前时间戳和绝对过期时刻比较。如果还没过期,正常返回。
  4. 如果已经过期,就把这个 key 从主 dict 和 expires dict 里同时删掉,然后再告诉调用方“这个 key 不存在”。

也就是说,一个已经过了期的 key,如果你一直不去访问它,它可以继续躺在内存里不释放。只有在你下一次访问它时,redis 才意识到“哦这家伙过期了”,然后把它清理掉。这种策略叫“被动删除”,好处是对 redis 主流程的干扰最小,坏处就是“过期数据”在内存里滞留的时间不可控。

3.3 主动删除:定期删除

如果只靠惰性删除,那些设置了过期时间但从此不再被访问的 key 就会永远占着内存,这显然不可接受。所以 redis 还有一个定期删除的机制,源码主要在activeExpireCycle函数里。

简单来说,redis 会在它的时间事件循环里,定期调用这个函数,按一定策略采样一批 key 来检查和处理。注意,不是全库扫描,而是采样。具体逻辑大致是:

  1. 从 expires dict 中随机取出一批 key(默认每个数据库检查一定数量的 key)。
  2. 检查它们的绝对过期时间,如果过期则删除。
  3. 控制删除操作的总耗时,避免阻塞主线程太久。
  4. 如果被采样的 key 中过期比例仍然很高(超过一定阈值),会继续多轮采样,把“该清的过期 key”尽量清掉。

这个策略有个特点:它消耗的时间是有限的、有上限的,所以 redis 不会因为清理过期 key 导致自身卡死。但代价是:如果瞬时出现了大量 key 同时过期,定期删除那一轮只能处理一部分,剩下的过期 key 会继续占据内存,直到下一次轮询或惰性删除把它们清掉。这也是为什么后面我会反复提醒:给 key 设置过期时间时,千万不要让大量 key 在同一秒内同时过期。

3.4 内存淘汰策略和过期时间的关系

还有一个概念容易混淆:过期删除 ≠ 内存淘汰。虽然两者都可能导致 key 消失,但事件完全不同:

  • 过期删除:针对的是设置了 TTL、且时间到了的 key。这些 key 在 expires dict 里有明确的过期时刻。
  • 内存淘汰:针对的是当 redis 内存达到maxmemory阈值时,按照配置的策略逐出某些 key。被逐出的 key 可能根本没设置过期时间。

配置项是maxmemory-policy,常见的有这些:

策略含义与过期时间的关系
noeviction内存满时新写命令直接报错不淘汰任何 key,过期 key 只靠惰性删除和定期删除
allkeys-lru从全量 key 中基于 LRU 淘汰可能淘汰掉没有过期时间的长驻 key
volatile-lru只从设置过 TTL 的 key 里按 LRU 淘汰未设置 TTL 的 key 永不被淘汰
allkeys-random随便挑一个 key 淘汰不区分是否有 TTL
volatile-random只从设置了 TTL 的 key 里随机淘汰同上

这里我想特别提一下volatile-*系列。如果你的缓存架构里同时存在“永久 key”和“带 TTL 的 key”,而你想让“带 TTL 的 key”在内存吃紧时优先被逐出,那volatile-lru是很直观的选择。但它有一个潜在风险:如果 Redis 里大部分 key 都没设 TTL,那在内存满的时候,volatile-lru 可淘汰的范围非常小,几乎等于 noeviction,新写入命令可能会直接报 OOM 错误。所以选型之前,最好用INFO keyspace看一眼各种 key 的数量分布。

4. 实操场景一:缓存治理中的过期时间设计

4.1 缓存过期时间设多少才合理

理论讲完,来点实打实的东西。最常见的需求是:把数据库的查询结果缓存到 redis,比如用户信息、商品详情、配置项。那么过期时间设多少?有人说 5 分钟,有人说 1 小时,还有人直接不设置。这些拍脑袋的方案真的没问题吗?

我先把设计原则说清楚,再给一个参考表:

  1. 过期时间不能比业务上“可容忍的数据不一致窗口”还长。比如你缓存的是库存数量,那 5 分钟可能都嫌长;如果你缓存的是文章标题,10 分钟也问题不大。
  2. 过期时间要配合缓存更新策略看。如果你有主动更新的机制(比如收到 MQ 消息后主动删缓存),那过期时间更多是兜底性质,可以设长一点;如果只能靠过期以后回源数据库,那设置宜短不宜长,否则会长时间读到脏数据。
  3. 同一批 key 的过期时间要加一个随机偏移量,这个后面专门说。
数据类型过期时间建议备注
用户 Session / Token30 分钟 ~ 2 小时滑动续期,每次访问重设过期
验证码3 ~ 5 分钟不宜过长,且校验后立即删除
商品库存10 ~ 30 秒配合数据库扣减,缓存只做短窗口保护
配置项10 ~ 30 分钟更推荐用发布订阅主动刷新
运营活动页5 ~ 10 分钟大范围涌流时回源数据库压力大,建议加大随机量

4.2 缓存穿透、击穿、雪崩和过期时间的关系

这三个概念和过期时间直接绑在一块,我用最直白的大白话解释。

缓存穿透:压根不存在的数据被高并发请求打到数据库。比如用户查一个不存在的订单号,你的代码先查缓存、没命中就查库、库里也没有、于是没有回写缓存。下次同一个订单号还是会穿透。这时设置过期时间管用吗?不管用,因为压根没数据可缓存。标准方案是缓存一个空的占位符并设置短过期时间(比如 30 秒),把“不存在的查询”也挡住。注意这个空值缓存的过期时间不能太长,否则数据真出现了之后,缓存里还是空,造成一致性问题。

缓存击穿:一个热点 key 恰好在某个时刻过期,而同时有大量请求打过来,全部穿透到数据库。这个问题的根源就是一个 key 的过期时间到了。解决办法:热点 key 可以考虑不设置过期时间,改为后台主动刷新;或者使用互斥锁,只允许一个线程重建缓存;再或者用逻辑过期——在 value 里存一个逻辑过期时间,应用层自己去判断。

缓存雪崩:大量 key 在同一时间段内集中过期,导致大量请求同时穿到数据库。这就是为什么我反复强调,同一批 key 的过期时间不能一刀切。给过期时间加随机偏移量是一种最简单、最有效的预防手段。比如你原本计划设 600 秒,实际设置的时候生成 600 + random(0, 60) 秒,这样过期就不会集中在同一秒。

4.3 实战:Spring Boot 下正确的缓存过期时间写法

写代码这块,我用 Spring Boot + RedisTemplate 来示范。注意这不是唯一方案,但能反映大多数项目的实际情况。

定义一个带随机偏移的过期时间工具:

public class RedisTtlUtil { private static final ThreadLocalRandom RANDOM = ThreadLocalRandom.current(); /** * 生成一个带随机偏移的过期时间,避免缓存雪崩 * 基础秒数 baseSeconds,实际在 [baseSeconds, baseSeconds + jitterSeconds] 区间浮动 */ public static Duration randomTtl(long baseSeconds, long jitterSeconds) { long realSeconds = baseSeconds + RANDOM.nextLong(0, jitterSeconds + 1); return Duration.ofSeconds(realSeconds); } }

然后写缓存的读改写逻辑:

@Service public class ProductService { @Autowired private StringRedisTemplate redisTemplate; @Autowired private ProductMapper productMapper; private static final String KEY_PREFIX = "product:detail:"; public Product getById(Long productId) { String key = KEY_PREFIX + productId; String json = redisTemplate.opsForValue().get(key); if (json != null) { return JSON.parseObject(json, Product.class); } // 互斥锁重建,防止缓存击穿 String lockKey = "lock:product:" + productId; boolean locked = redisTemplate.opsForValue() .setIfAbsent(lockKey, "1", Duration.ofSeconds(10)); if (locked) { try { json = redisTemplate.opsForValue().get(key); if (json != null) { return JSON.parseObject(json, Product.class); } Product product = productMapper.selectById(productId); if (product == null) { // 空值缓存,防穿透 redisTemplate.opsForValue().set(key, "", Duration.ofSeconds(30)); return null; } String productJson = JSON.toJSONString(product); // 带随机偏移的过期时间,防雪崩 redisTemplate.opsForValue().set(key, productJson, RedisTtlUtil.randomTtl(600, 60)); return product; } finally { redisTemplate.delete(lockKey); } } else { // 睡一小会儿再重新读取,实际生产用 while + 超时上限 try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } json = redisTemplate.opsForValue().get(key); if (json != null) { return JSON.parseObject(json, Product.class); } } // 兜底:走到这里说明锁竞争失败且缓存没恢复,直接查库 return productMapper.selectById(productId); } }

这里值得注意的坑点有两个:

  1. setIfAbsent就是SET key value NX EX,它在设置锁的同时指定了锁的过期时间。这个过期时间一定要设置,否则进程宕了锁永远不会释放。
  2. 缓存重建之后的过期时间一定要带随机偏移。你可能觉得“我把过期时间设成 10 分钟,一小时后所有 key 同时过期,查库尖峰不是还能接受吗?”——放在单机上可以,但在分布式集群里,所有请求打到同一个数据库时,尖峰就非常致命。加随机量几乎零成本,收益却很高。

5. 实操场景二:分布式锁里过期时间怎么定才安全

5.1 固定过期时间的锁为什么危险

分布式锁用 redis 实现时,最经典的模式就是SET lockKey ownerId NX EX expireTime。锁的持有者靠过期时间保证:如果持有者进程挂了,锁也能自动释放,不至于死锁。但问题来了,这个过期时间设多大?

设短了,比如 5 秒,但你的业务逻辑执行需要 6 秒。第 5 秒锁自动过期,另一个线程拿到锁进来,两个线程同时执行,分布式锁形同虚设。最典型的场景就是定时任务跨节点重复执行,你以为锁住了,结果两台机器都跑了同一批数据。

设长了,比如 30 秒,如果持有者真挂了,其他线程要等 30 秒才能抢到锁,系统可用性下降。而且如果业务异常导致持有者既没执行完、也没释放锁,那这段时间就是人为造成的“死锁窗口”。

5.2 一个可行的解决方案:续期机制

真正的解决方案不是把过期时间调大,而是动态续期。在 Java 生态里,Redisson 的watch dog机制干的就是这件事。它的行为大致是:

  • 默认lockWacthdogTimeout是 30 秒,加锁成功后,后台有一个定时任务每 10 秒检查一次锁是否还在。
  • 如果锁还在,且业务没执行完,就自动把锁的过期时间重新续到 30 秒。
  • 如果业务执行完了,主动释放锁,后台任务也被取消。

这样无论业务执行多久,只要进程活着,锁就不会因为“固定过期时间太短”而提前失效。但如果进程挂了呢?后台定时任务也没了,30 秒后锁自动释放,不会死锁。

如果你不想引入 Redisson,自己写续期也不难。一个简单的思路:加锁成功后,启动一个定时任务,每过期时间的三分之一时间去检查并调用PEXPIRE续期。在 finally 块里释放锁并取消定时任务。

下面给出一个简易版本的锁续期工具思路:

public class RenewSchedule { private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2); public void renew(String key, String owner, long expireMillis) { scheduler.scheduleAtFixedRate(() -> { // 检查锁是否还是自己的 String current = redisTemplate.opsForValue().get(key); if (owner.equals(current)) { redisTemplate.expire(key, Duration.ofMillis(expireMillis)); } }, expireMillis / 3, expireMillis / 3, TimeUnit.MILLISECONDS); } }

这个实现是简化版,生产上需要考虑 scheduler 的优雅关闭、续期失败时的告警、锁竞争竞争的公平性等等。但核心思路不变:过期时间不是用来限制业务执行时长的,而是兜底防死锁的

5.3 续期之外:锁的 owner 标识为什么不能省

还有一个细节容易被忽略——设置锁的时候,value 一定不能是固定的“1”,必须是当前线程或节点的唯一标识。因为释放锁时要做校验:先判断 value 是不是自己的,是才删除。如果不做这个校验,可能出现下面的情况:

  1. 线程 A 加锁,value 是 "A"。
  2. 线程 A 执行时间太长,锁自动过期。
  3. 线程 B 加锁成功,value 是 "B"。
  4. 线程 A 执行完了,执行DEL key
  5. 锁被 A 释放了,但此时持锁的人明明是 B。

所以释放锁的标准姿势是用 Lua 脚本保证“判断 + 删除”的原子性:

if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end

这个脚本的存在,本质上就是在“谁加的锁谁才能释放”这个前提上加了一层保护。和过期时间配合,构成了分布式锁的完整闭环:过期时间防止死锁,owner 标识防止误释放。

6. 那些反直觉的坑:过期时间相关的常见问题排查实录

6.1 我设的过期时间明明很长,为什么 key 还是消失了

这类问题我见过好几次。先按优先级排查这样几个方向:

  1. 内存淘汰策略:执行CONFIG GET maxmemory-policy,看是不是allkeys-lruallkeys-random。如果是,redis 内存达到上限时会从所有 key 里淘汰数据,不区分你有没有设过期时间。解决办法:调整策略为volatile-lru,或者增大内存上限,或者优化数据存储、去掉冗余 key。
  2. key 被主动删除:查代码,看有没有定时任务、MQ 消费逻辑、运营后台手动删除。很多时候不是 redis 的问题,是上层业务动了手。
  3. 系统时间跳变:redis 里的过期时间是基于服务器时间戳判断的。如果服务器因为 NTP 校时等原因回拨或前跳,可能导致 key 集体提前过期或无限延长。这种情况比较隐蔽,可以用INFO server看 uptime 和系统时间比对一下。

6.2 key 过期了但内存没降,为什么

答案就是上面讲的惰性删除机制。过期 key 在访问之前不会立刻释放内存,只有定期删除的那轮扫描才会清掉一部分。如果你压测时往 redis 里塞了几百万个设置了 1 分钟后过期的 key,然后什么也不做,等 10 分钟后看INFO memory,你会发现 used_memory 可能几乎没变。敲一下SCAN会触发 key 的惰性删除,但要注意大规模 SCAN 对线上 redis 的影响。

如果想让过期 key 更快释放,可以调定期删除的频率相关参数,但一般不建议手动去调,默认行为在绝大多数场景下是合理的。

6.3 大量 key 同一秒过期导致请求抖动

这也是经典问题。原因和上面说的雪崩一样:批量设置缓存时过期时间没加随机偏移。排查时需要看监控——redis 的慢查询日志、数据库的连接数曲线、接口的 RT 曲线,三者对齐以后能明显看出周期性尖峰。

解决方案就不要只盯着 redis 了,要在代码层解决:加随机偏移、热点 key 不设过期改逻辑过期、对回源数据库做限流/加锁。

下面整理出一张速查表,方便你遇到问题时直接对着查:

现象可能原因排查命令/手段解决方案
key 提前消失内存淘汰CONFIG GET maxmemory-policy改用 volatile-lru、增大内存
key 全部消失系统时间跳变比对服务器时间与 NTP 状态校正时间、缩短 key 过期范围
过期后内存不降惰性删除机制INFO memory+SCAN验证按需访问触发删除,或用 unlink 异步清理
同一时间大量请求打库缓存雪崩观察慢查询与 DB 连接数尖峰过期时间加随机偏移
锁提前失效,多人进入临界区过期时间短于业务耗时日志记录锁持有时间续期机制 / 逻辑延长过期时间
锁释放了别人的锁没有 owner 校验查看锁 value 是否是固定值用 Lua 脚本校验 + 删除

6.4 关于 key 过期删除对主从的影响,几句话讲明白

很多人不知道,redis 的过期删除在主从架构里有一个需要留意的行为。在主节点上,key 过期后会主动执行 DEL 命令并同步给从节点;但如果从节点自己收到的读请求访问到一个已过期的 key,它是不会直接删除自己的副本的(在旧版本中只返回空结果,但数据还留在内存里)。这会导致一个局部的内存浪费,但不会影响读的一致性。Redis 4.0 之后从节点也会维护自己的过期键表,在收到读请求时多做一次过期判断。

另外一个坑是:主从切换时,如果旧主节点上有大量尚未被清理的过期 key,切换后新的主节点(原来的从节点)需要重新依赖自身的过期检测机制,可能会导致少量过期 key 短暂存活,极端情况下产生读脏数据。这个属于架构层面需要考虑的问题,一般的业务系统不用过度纠结,但如果你在做强一致需求,就要在代码层多做一次过期判断,不能完全依赖 redis 的删除时机。

7. 实操总结:给初学者的几条落地建议

如果把上面所有的内容浓缩成几条可以立刻上手执行的原则,我觉得是这些:

第一,设置过期时间时,优先用SET key value EX seconds这种原子写法,不要把“设置值”和“设过期”拆成两步。拆两步不但多一次 RTT,还会在中间窗口留下一个没有过期时间的 key,万一进程崩了,那个 key 就成了永久垃圾。

第二,批量生成缓存 key 时,过期时间必须加随机偏移。代码里一句new Random().nextInt(60),就能把雪崩的尖峰削平一大半。这是成本最低、收益最明显的一个优化。

第三,分布式锁必须设置过期时间,且必须配合续期机制。不要把过期时间当业务的“最大执行时长”,它只是一个兜底防线。锁的 value 必须携带唯一标识,释放锁必须用 Lua 脚本校验。

第四,排查问题时,先看 TTL,再看内存淘汰策略,再查代码里的 DEL 调用。按这个顺序走,80% 的“key 莫名其妙消失”问题都能找出根因。

第五,监控比什么都重要。把 redis 的慢日志、内存曲线、命中率,以及业务侧的回源数据库 QPS 都接上监控。很多和过期时间相关的问题,不是一夜之间出现的,而是在曲线的某个尖峰里酝酿了好几天。你有了监控,才能把问题和“过期时间设置不合理”这件事快速关联起来。

我在实际项目中踩过的坑,印象最深的就是缓存雪崩那次。上线前代码 review 时所有人都觉得“600 秒过期没问题”,结果一到零点大促,数据库连接池被打满,应用网关超时率飙升。查到最后发现罪魁祸首就是一批 key 的过期时间全是整 600 秒,同一时刻全部失效。后来在缓存工具类里统一加了随机偏移和互斥锁重建,同样的流量模式下,数据库的负载曲线几乎是一条平线。所以不要小看这个基础功能,它的每一个参数背后都对应着线上真实的数据形态和资源水位,设计得好,是少花钱多办事;设计得糙,分分钟变成事故导火索。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 11:48:13

雷电模拟器+GG宠物助手:QQ宠物怀旧挂机完整配置指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/8 11:45:13

用C语言实现AES-128:从原理到工程实践的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/8 11:44:14

汽车评论多标签情感分析实战:从TF-IDF到深度学习融合

简介&#xff1a;这是CCF-BDCI 2018年汽车行业用户观点主题及情感识别挑战赛第7名解决方案的完整Python项目&#xff0c;面向机器学习、自然语言处理方向的竞赛选手和求职开发者&#xff0c;可用来学习如何从用户评论中识别主题和情感倾向。压缩包共39个文件&#xff0c;以31个…

作者头像 李华
网站建设 2026/9/8 11:42:51

智能车视觉组工程复盘:走马观碑赛项的闭环调试与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/8 11:42:51

Ubuntu 20下open62541与Qt构建OPC UA服务器/客户端实践

简介&#xff1a;面向工业自动化与智能制造领域的QT/C开发者&#xff0c;这份资源聚焦Ubuntu 20环境下基于open62541库的OPC UA服务器与客户端搭建&#xff0c;帮助读者跨越协议理解与工程实现的障碍&#xff0c;适合需要快速上手或参考工程结构的初中级开发人员。压缩包共12个…

作者头像 李华