缓存更新必须异步进行,因同步操作在高并发下易引发缓存击穿、旧数据覆盖及数据库压力倍增;应使用 think-queue 将删缓存任务推入 Redis 队列,由独立消费者执行,确保可控、可追溯、可重试,并配合监控与进程守护。

缓存更新必须异步做,否则高并发下数据库和响应延迟会直接崩掉。
为什么不能在 HTTP 请求里直接删缓存 + 写库?
用户一提交表单,你顺手 Cache::delete('user_'.$id) 再 save(),看起来干净——但这是最危险的写法。当 100 个请求同时改同一条数据,可能产生“缓存击穿 + 多次重复重建”,甚至出现旧数据覆盖新数据(比如先删缓存、后写库的请求反而比先写库后删缓存的更快完成)。
- 缓存删除和 DB 更新不是原子操作,中间有时间窗口
- 多个并发请求都发现缓存 miss,全去查 DB + 写缓存,DB 压力翻倍
- TP6 默认文件缓存不支持锁,Redis 缓存若没加分布式锁,仍可能脏写
用 think-queue 推任务到 Redis 队列
ThinkPHP 6 的队列机制不是“后台常驻进程”的替代品,而是把“删缓存”这个动作从主流程里剥离开,交给独立消费者执行。关键不是“快”,是“可控”和“可追溯”。
- 确保已安装:
composer require topthink/think-queue -
.env中设好驱动:QUEUE_CONNECTION=redis,并配对REDIS_HOST、REDIS_PORT - 任务类必须实现
fire()方法,且只做一件事:比如Cache::delete('article_'.$this->aid) - 触发端别用
Queue::push()直推裸数组,要用任务类实例:Queue::push(new ClearArticleCacheJob($aid))
这样做的好处是:失败可重试、日志可追踪、任务能延迟(比如 delay(3) 避开刚写库还没落盘的瞬间)。
cache:clear 命令不能代替业务级缓存清理
运行 php think cache:clear 是清整个缓存目录,属于运维操作;而业务中要清理的是特定键,比如 user_profile_12345 或 category_tree_v2。前者会误伤大量无关缓存,导致后续请求集体回源,雪崩风险极高。
- TP6 的
Cache::tag()能打标分组,但 Redis 驱动下 tag 功能实际依赖 key 前缀模拟,不推荐用于高频更新场景 - 真正安全的做法:更新 DB 后,只推送「精准 key 清理」任务,而不是清 tag 或全库
- 如果用了多级缓存(如 APCu + Redis),异步任务里得显式调用两级清除,不能只清 Redis
消费者进程必须单独起,且要防止单点失效
很多人把 php think queue:listen 放进后台就以为万事大吉,结果服务器重启、OOM 或队列积压时没人知道。这不是“配好了”,只是“跑起来了”。
- 必须用进程管理工具(如 Supervisor)守护消费者,配置自动拉起、内存超限重启
- 不要共用 Web 服务的 Redis 连接池;队列消费者应使用独立连接,避免阻塞主应用
- 加简单监控:比如每分钟检查
redis-cli llen 'queue:default'是否持续 >1000,发告警
异步更新真正的难点不在代码怎么写,而在“谁来保证这条消息一定被消费、且只消费一次”。Redis 自带的可靠性有限,业务关键路径上,建议在任务执行前先查 DB 当前版本号,再决定是否真删缓存——这一步,文档里永远不提,但线上出过三次故障。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











