Redis缓存击穿_使用本地锁还是分布式锁效果更好
缓存击穿时,本地锁为何失效?分布式锁如何选型?

免费影视、动漫、音乐、游戏、小说资源长期稳定更新! 👉 点此立即查看 👈
当缓存击穿发生时,一个常见的误区是试图用本地锁解决问题。实际上,本地锁在跨进程场景下根本不起作用。更有效的方案是采用基于Redis原子命令(如SET NX EX)或Redission的tryLock实现的分布式锁,并配合空值缓存、key收敛、过期时间随机偏移等策略进行综合治理。
缓存击穿发生时,本地锁根本不起作用
想象一下这个场景:多个请求同时发现某个热点key在Redis中不存在(比如一个爆款商品的详情页),它们都去查询数据库,并且在查完后准备回写缓存。如果此时仅仅依赖本地锁(比如Ja va里的synchronized或ReentrantLock),会发生什么?每个独立部署的应用实例都拥有自己的锁,它们之间互不感知。结果就是,三台机器上可能各有线程在自己的JVM里“成功”加锁,然后各自查询一次数据库,再各自回写一次缓存。数据库压力瞬间翻倍,缓存也被重复刷入。问题的根源在于,本地锁的有效范围仅限于单进程,而缓存击穿恰恰是一个典型的跨进程、跨节点并发问题。
分布式锁必须满足“互斥 + 自动释放”两个硬条件
选择分布式锁方案,关键不在于技术是否新颖,而在于它能否切实解决并发回源的问题。市面上常见的方案各有侧重:
- Redis原子命令:使用
SET key value EX seconds NX是最轻量且通常足够的选择。但这里有个细节必须注意:一定要使用这个原子命令,而不能将其拆分成先SETNX再EXPIRE两个操作。因为在两个命令执行的间隙如果进程崩溃,会导致锁无法自动释放,形成死锁。 - Redission客户端:使用这个成熟的客户端时,尽量避免直接调用不带超时参数的
lock()方法。更稳妥的做法是使用tryLock(waitTime, leaseTime, TimeUnit),明确指定等待获取锁的时间和锁的自动释放时间,以防网络抖动导致锁被永久持有。 - ZooKeeper:基于临时顺序节点的方案虽然能提供强一致性保证,但也意味着引入了额外的运维组件,并且延迟相对较高。对于纯粹的缓存防击穿场景,其性价比可能不如Redis方案。
更推荐“逻辑层兜底 + 简单分布式锁”组合
单纯依赖一把锁,往往只能治标。线上环境追求的是稳定可靠,因此更推荐组合拳策略:
- 获取锁与重试:首先尝试用
SET lock:key token EX 60 NX获取分布式锁。如果获取失败,说明已有其他线程在处理,当前线程可以主动sleep一个短暂时间(例如10到50毫秒)后重试,避免无意义的自旋消耗CPU。 - 锁的释放:成功拿到锁的线程执行数据库查询,并将结果写入缓存。完成后,应立即通过
DEL命令删除锁的key。虽然Redission有看门狗机制自动续期,但手动释放能让控制粒度更细。 - 空值缓存:这是一个关键兜底策略。当数据库查询结果本身为
null时,也应在缓存中写入一个特定的空值对象(例如字符串"null"),并设置一个较短的TTL(比如2分钟)。这能有效防止大量请求穿透到数据库。当然,业务层需要约定好如何识别和处理这个空值标记。 - 异步回写:如果业务对极短时间内的数据一致性要求不高,甚至可以采取更彻底的“异步回写”方案:线程查询数据库后直接返回结果给用户,同时发送一条消息到队列,由独立的消费者异步完成缓存写入,从而完全避开锁竞争。
锁粒度和 key 设计不当,比选哪种锁更容易翻车
技术选型正确只是第一步,锁的使用姿势同样重要。一个典型的反例是:将锁直接套在getProductDetail(long id)这样的方法外部,而方法参数id直接来自前端URL。攻击者如果批量请求/product/1到/product/100000,瞬间就会创建出十万个不同的锁key,导致Redis内存暴涨,系统QPS断崖式下跌。正确的做法是:
- Key收敛设计:锁的key必须具有收敛性。例如,统一使用
lock:product:{id}这样的格式,确保同一个商品id永远对应同一个锁key,而不是生成海量不同的key。 - 放弃锁,采用随机偏移:对于一些访问高频但对一致性要求不苛刻的数据(比如用户头像),可以放弃使用锁。取而代之的是采用“过期时间随机偏移”策略,在设置缓存过期时间时加上一个随机值,例如
EX 3600 + random(0, 600),让大量key的失效时间自然分散开,避免同时失效引发击穿。 - 合理配置 watchdog:如果使用Redission,务必确认
lockWatchdogTimeout(锁看门狗超时时间)配置合理。时间太短可能导致锁在业务执行中被误释放;时间太长则会影响故障场景下的恢复速度。
说到底,锁本身只是一个工具。真正决定系统能否扛住缓存击穿的,是你对并发边界的控制是否清晰、锁的key设计是否可预测、以及空值等边缘情况是否被妥善处理。这些地方哪怕只疏忽一点,换用任何高级的锁方案都无济于事。
游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。
同类文章
SQL怎样统计非重复值的数量_使用COUNT DISTINCT处理
SQL怎样统计非重复值的数量:使用COUNT DISTINCT处理 COUNT DISTINCT 会忽略 NULL 吗? 答案是肯定的。COUNT(DISTINCT column_name) 默认会跳过所有的 NULL 值,它们压根儿不参与去重计数。这意味着,如果你的字段里存在大量 NULL,而你却
MongoDB如何为不同的业务线划分安全边界_利用Logical Database隔离
MongoDB如何为不同的业务线划分安全边界:利用Logical Database隔离? MongoDB 官方并未提供名为“Logical Database”的概念,实际隔离方案依赖于原生的数据库命名空间与基于角色的访问控制。权限必须明确绑定到具体的数据库资源,不能依赖命名前缀或空的数据库字段。 L
海量大数据下如何定时自动数据同步_性能优化与加速迁移策略
定时同步变慢主因是全量读写导致I O与内存压力,应设chunksize、复用engine、禁用自动类型推断;DataX卡住多因Hive小文件或MySQL超时,需调参分片;增量优选自增ID而非时间戳;Kettle假死需状态文件+超时控制;位点必须持久化 用 APScheduler + pandas 做
mysql怎么设置SQL模式以兼容旧版本_调整sql_mode参数去掉严苛模式
MySQL 5 7及以上版本默认启用严格模式,这可能导致旧版应用程序的SQL语句执行报错。解决方法是临时使用SET SESSION sql_mode修改当前会话配置,或永久修改my cnf配置文件中的sql_mode参数并重启服务。对于云数据库,需要通过云平台控制台调整参数。 MySQL 5 7+
.NET如何异步访问Oracle数据库_使用async/await编程
Oracle ManagedDataAccess从19c起才完整支持async方法,需版本≥19 10、连接字符串启用Asynchronous Processing,并配合CommandBeha vior SequentialAccess及OracleTransaction才能实现真异步。 Orac
- 日榜
- 周榜
- 月榜
1
2
3
4
5
6
7
8
9
10
相关攻略
2015-03-10 11:25
2015-03-10 11:05
2021-08-04 13:30
2015-03-10 11:22
2015-03-10 12:39
2022-05-16 18:57
2025-05-23 13:43
2025-05-23 14:01
热门教程
- 游戏攻略
- 安卓教程
- 苹果教程
- 电脑教程
热门话题

