高并发下缓存击穿需用redis互斥锁防控:第一步用set key value nx px原子抢锁,value须唯一;第二步失败后带抖动休眠重试;第三步抢锁成功须再查缓存防重复重建;第四步查库失败须删锁并返回null;逻辑过期方案需打包expire时间戳、异步重建加轻量锁、物理ttl仍需设置。

高并发场景下,热门商品详情页或头条新闻缓存过期瞬间,成千上万请求同时穿透Redis直打MySQL,数据库连接池被打满、响应超时甚至宕机——这不是理论风险,而是真实压测中反复复现的线上事故。
用Redis SETNX实现互斥锁
第一步:在缓存未命中时,用SET key value EX seconds NX命令尝试抢锁。key建议固定为"mutex:{$cacheKey}",value必须是唯一随机字符串(如uniqid('', true)),不能写死为"1"或"locked"——否则释放锁时无法校验归属,极易误删他人锁。
第二步:抢锁失败则休眠50~200ms后重试,最多循环3次。休眠时间必须带随机抖动,避免所有请求在同一毫秒醒来再次碰撞。
第三步:抢锁成功后,【必须立刻再查一次缓存】。因为从抢锁到执行这一步之间可能已有其他线程完成重建,跳过此步会导致重复查库、覆盖写入。
第四步:查库成功后写回Redis,TTL设为原始缓存时间;若查库失败(如DB超时),立即删除锁key并返回null,绝不能让其他请求无限等待。
逻辑过期方案落地要点
方法一:改造缓存结构,把原始数据和逻辑过期时间打包成JSON存入Redis。例如:{"data":{"id":123,"title":"iPhone 16发布"},"expire":1723425120},其中expire字段是Unix时间戳,比物理TTL多预留30秒缓冲。
方法二:查询时先解包JSON,比对当前时间与expire字段。若已逻辑过期,立即启动异步线程重建缓存——注意不是阻塞式等待,而是直接返回旧数据。
方法三:异步重建线程内仍需加轻量锁(如APCu内存锁),防止同一key被多个异步任务重复触发。重建失败时,旧缓存继续提供服务,仅记录告警日志。
注意:逻辑过期不等于永不淘汰,Redis里该key仍要设置较长物理TTL(如24小时),避免内存无限膨胀。
ThinkPHP中封装Mutex类避坑指南
不要把锁逻辑写在模型的getAttr方法里——列表页批量调用会触发N次锁竞争,且无法跨请求感知全局状态。
必须使用Lua脚本释放锁:EVAL "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end" 1 mutex:goods:123 1678901234。PHP端传入的value必须和SETNX时完全一致,否则删锁失败。
锁超时时间必须严格小于缓存TTL,推荐按公式lock_ttl = cache_ttl / 10设定,例如缓存60秒,锁就设6秒。超时后自动释放,避免进程崩溃导致死锁。
本地开发环境禁用Redis连接池,改用connect_timeout=1和read_timeout=1,否则锁判定延迟会掩盖真实竞争问题。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











