缓存击穿指热点key过期瞬间大量请求穿透至数据库,需用redis互斥锁(如set key value nx px)+重试机制解决;缓存雪崩指大批key集中失效或redis宕机,须加随机过期时间、连接池优化及降级兜底。

ThinkPHP 5.1 在高并发场景下用 Redis 做缓存,缓存击穿和雪崩不是“会不会发生”的问题,而是“什么时候发生”的问题。关键不在是否加缓存,而在缓存怎么设、锁怎么管、失效怎么控。
缓存击穿:单个热 key 过期时的并发回源
比如商品详情页 ID=10086 的缓存刚好过期,1000 个请求同时查不到,全部打数据库——这就是击穿。ThinkPHP 默认的 Cache::get() 和 Cache::set() 不带互斥逻辑,必须自己补。
- 用
SET key value NX PX 5000原子加锁,不能只靠setnx:TP 5.1 的Cache::handler()->set($lockKey, 1, ['nx', 'ex' => 5])才等效,且必须确认底层驱动是 Redis 并启用了persistent => true - 锁释放必须进
finally或catch:业务查库失败、序列化异常、网络超时,都不能跳过del $lockKey,否则锁永远不释放 - 重试要设上限:递归调用或循环 sleep 最多 3~5 次,每次间隔 20–100ms,避免请求堆积放大成雪崩
- 锁 key 和缓存 key 必须同 DB、同命名空间:比如缓存用
user:123,锁就该用lock:user:123,且配置中'select' => 0保证在同一个 Redis db 内操作
缓存雪崩:大批量 key 集中失效或 Redis 整体不可用
雪崩不是单个 key 的事,是时间维度上的“集体失守”。比如所有商品缓存统一设 30 分钟过期,整点一到全失效;或者主节点宕机、连接池耗尽,导致整个缓存层瘫痪。
- 过期时间必须加随机偏移:写缓存时不要写死
3600,改用3600 + rand(0, 600)(即 1 小时 ± 10 分钟),打散失效峰 - Redis 连接配置不能省:
'timeout' => 5(太短易误判超时)、'persistent' => true(避免频繁建连拖慢锁响应)、'select' => 0(确保锁与数据在同一 DB) - 降级兜底不能只靠 try-catch:TP 5.1 可配合
think\cache\driver\Redis的connect()异常监听,在 Redis 不可用时自动切到本地缓存(如 file)或返回默认值,而不是抛错中断 - 热点数据可考虑“永不过期 + 主动更新”:不是真永不删,而是用定时任务或消息队列异步刷新,但需配套版本号或时间戳校验,防止脏数据覆盖
别踩这些 ThinkPHP 专属坑
很多问题不是 Redis 不行,是 TP 5.1 的封装掩盖了细节,导致你以为在用分布式锁,其实没生效。
-
Cache::tag()不是锁:它只是给缓存打标签,clear()是批量删,反而会触发多 key 同时失效,加剧雪崩 - 文件缓存驱动不支持跨进程互斥:开发环境若没切 Redis,
setnx在多 PHP-FPM 进程下完全无效,测试不出问题,上线就崩 - 锁 key 命名没隔离:用
lock_user_123看似合理,但运维误执行DEL lock_*就全挂;建议统一前缀如tp:mutex:user:123 - 没配
noeviction就不敢永不过期:Redis 内存满时 LRU 淘汰可能误删热点 key,但noeviction只防淘汰,不防 OOM 或人为 flush,得配合内存监控+告警
一个能跑通的最小闭环示例
不是贴完整代码,而是点出 TP 5.1 下真正起作用的几行核心:
- 初始化 Redis 缓存驱动时确认:
'type' => 'redis', 'host' => '127.0.0.1', 'port' => 6379, 'timeout' => 5, 'persistent' => true, 'select' => 0 - 加锁重建逻辑里:
$lockKey = 'tp:mutex:product:' . $id; $result = $cache->handler()->set($lockKey, 1, ['nx', 'ex' => 5]); - 查库后写缓存:
$cache->set($key, $data, 3600 + rand(0, 600)); - 无论成功失败,最后都:
$cache->handler()->delete($lockKey);
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











