Redis缓存雪崩时用锁机制来挡住那波流量,避免系统被压垮的那些思路
- 问答
- 2026-01-06 18:31:39
- 13
(根据网络技术社区常见讨论与实践经验总结)
当Redis缓存雪崩发生时,大量请求因为拿不到缓存数据,会像洪水一样直接涌向后端数据库,数据库很可能因为瞬间的过高并发压力而崩溃,进而导致整个系统不可用,这里的“锁机制”,核心思想并不是让所有请求都无序地、一窝蜂地去访问数据库,而是想办法让这股“洪流”变得有序,只放行必要的、少量的请求去重建缓存,其他绝大多数请求则被暂时“挡住”或“引导”,从而保护数据库,这就像在一条即将被洪水冲垮的堤坝前,紧急修建了几条有闸门控制的疏导渠,只允许有限的水量通过,大部分洪水被暂时蓄积或引导到安全地带。
用锁来挡流量的思路主要有以下几种实现方式和关键考虑点:

互斥锁(Mutex Lock)思路:只让一个请求去干活
这是最直接、最经典的思路,当缓存失效的那个瞬间,可能会有10个、100个甚至1000个请求同时发现数据不在缓存里。

- 操作过程: 系统设计为,当某个请求发现缓存失效后,它不能立刻去查询数据库,而是必须先尝试获取一个“锁”,这个锁本身可以是一个特殊的Redis键值对(比如Key为
lock:user_123,Value为一个随机值或过期时间),如果获取成功(说明它是第一个发现缓存失效的请求),那么这个“幸运儿”才有资格去执行查询数据库、重建缓存的任务。 - 挡住其他流量: 其他所有后续的、同样需要这个数据的请求,在尝试获取这个锁时都会失败,对于这些“失败”的请求,系统不会让它们傻等着或者直接去冲击数据库,而是采取一些友好的处理方式。
- 返回旧数据或默认值: 如果业务允许,可以短暂地返回一个旧的缓存数据(如果还没被清除)或者一个默认值(如“加载中…”),告知用户稍后再试。
- 短暂等待后重试: 让请求等待几十毫秒,然后重新尝试从缓存中获取数据,因为在这段时间里,那个“幸运儿”很可能已经完成了数据库查询并将新数据写回了缓存,这样,重试的请求就能直接从缓存中拿到结果了。
- 核心好处: 通过这把锁,将可能成百上千的数据库查询请求,缩减到只有一个,数据库的压力瞬间降至最低,缺点是,在第一个请求重建缓存的极短时间内,其他请求可能会有短暂的延迟或看到非实时数据。
逻辑过期时间与锁结合:提前准备,错峰重建
单纯设置一个固定的缓存过期时间(TTL)是导致缓存雪崩的常见原因之一,这个思路是给缓存数据增加一个“逻辑过期时间”的概念,将缓存重建的时机与锁机制结合,主动避免大量缓存同时失效。

- 操作过程:
- 存储在Redis里的缓存数据,除了本身的值,还额外嵌入一个字段,比如叫
logic_expire,表示这个数据的逻辑过期时间(当前时间+30分钟)。 - 而Redis键的实际TTL则设置得比逻辑过期时间长很多(比如设置TTL为1小时),这样做主要是为了防止数据无限期驻留,但核心的过期判断不依赖它。
- 当请求来读取缓存时,程序首先检查数据是否存在,如果存在,再取出其中的
logic_expire字段,与当前时间比较。 - 如果未逻辑过期: 直接返回缓存数据,一切正常。
- 如果已逻辑过期: 这时,数据本身还在(因为实际TTL没到),系统会尝试获取一个“缓存更新锁”,拿到锁的请求,会开启一个异步线程(或任务)去更新数据库和缓存,而当前请求则立即返回那个已经逻辑过期但内容仍可用的旧数据给用户,其他没拿到锁的请求,同样直接返回旧数据。
- 存储在Redis里的缓存数据,除了本身的值,还额外嵌入一个字段,比如叫
- 核心好处: 用户几乎无感知,因为始终有数据返回(哪怕是稍旧的),缓存的重建工作是后台异步、平滑进行的,并且通过锁控制了只有一个线程去执行更新,完美避开了“缓存失效瞬间”的惊群效应,这是一种非常优雅的“提前准备,错峰重建”策略。
锁的实现细节与注意事项
无论采用哪种思路,锁本身的实现至关重要,否则锁可能失效,甚至成为新的问题点。
- 锁的原子性: 判断缓存不存在和设置锁这两个操作必须是原子的,不能分开,在Redis中,通常使用
SET key value NX PX timeout这样的命令来实现,NX表示只在键不存在时设置,PX设置锁的过期时间,一条命令完成,保证原子性。 - 锁的过期时间: 必须给锁设置一个合理的过期时间,这是为了防止那个拿到锁的请求在更新缓存时意外崩溃或长时间阻塞,导致锁永远无法释放,其他请求也就永远被挡住(死锁),过期时间应该略大于预估的数据库查询与缓存写入时间。
- 释放锁的安全性: 只能由加锁的那个请求来释放锁,在实现时,锁的Value最好是一个唯一标识(如UUID),在释放锁时,先检查当前锁的Value是否与自己设置的一致,一致才删除,这是为了防止误删其他请求后来设置的锁。(请求A加锁,但处理超时,锁自动过期;请求B趁机加锁;此时如果请求A处理完,直接执行删除操作,就会错误地删掉请求B的锁)。
锁机制的局限性
锁机制虽然有效,但也不是银弹。
- 系统复杂度增加: 引入锁意味着业务代码变得更复杂,需要处理加锁、解锁、锁超时、获取锁失败等各种情况。
- 可能增加延迟: 对于获取锁失败的请求,无论是等待还是返回旧数据,都可能增加用户的请求延迟(即使是毫秒级)。
- Redis单点风险: 如果锁是建立在Redis上的,那么Redis本身一旦宕机,所有锁都会失效,锁机制就瘫痪了,对于高可用要求极高的场景,可能需要更复杂的分布式锁方案(如RedLock),但这又会引入更大的复杂性。
总结一下核心思路: 面对缓存雪崩的流量洪峰,锁机制扮演了一个“交通警察”或“泄洪闸门”的角色,它的目标不是硬扛所有流量,而是通过“互斥”或“逻辑过期+异步更新”的策略,将无序、并发的数据库访问请求,变得串行化、可控化,用极小的代价(少量请求的短暂延迟或非最新数据)换取整个系统的稳定性和数据库的安全,成功“挡住”那波足以压垮系统的流量。
本文由召安青于2026-01-06发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:https://www.haoid.cn/wenda/75725.html
