think-queue 落地需分清三类场景并配置 retry_after、failed_jobs、supervisor 等;延迟任务须防 zset score 精度问题;事件中只 dispatch 不执行 io;缓存更新必须异步删除;任务类需实现 fire 方法且用实例投递。

think-queue 是 ThinkPHP 官方队列扩展,但直接装包、写 Queue::push() 就跑起来,线上大概率会卡死或丢任务。真正能落地的异步处理,得先分清你要解决的是哪类问题:「后台耗时任务不阻塞请求」、「毫秒级响应+高并发投递」,还是「强一致性延迟执行」——三者机制不同,不能混用。
用 think-queue 做基础队列,别跳过 retry_after 和 failed_jobs
很多人配完 Redis 驱动就以为万事大吉,结果任务失败后无限重试、堆满队列、CPU 拉满。
-
retry_after必须小于你任务实际最长执行时间(比如发邮件超时设为 60,这里就得设成 90),否则未完成任务会被误判为“失败”而重复入队 - Database 驱动自带
failed_jobs表,Redis 驱动默认不存失败记录——生产环境务必配合php think queue:work --tries=3 --delay=60,或自己监听JobFailed事件写日志 - 启动消费者时加
--daemon是常见误区:php think queue:work --daemon在 PHP-FPM 环境下容易内存泄漏,应改用systemd或supervisor管理常驻进程,每次执行完自动重启
延迟任务别只靠 Queue::later(),注意 Redis ZSET 的 score 精度问题
Queue::later(300, new SendEmail(), $data) 底层其实是把执行时间戳作为 ZSCORE 写进 Redis 的 ZSET。但要注意:
- 如果系统时间被 NTP 同步回拨(比如从 10:00:05 调回 10:00:02),ZSET 中已到时间的任务可能被跳过,导致延迟任务“永远不触发”
- 多个任务在同一秒内投递,
score相同,Redis ZSET 不保证内部顺序——任务实际执行顺序可能和投递顺序不一致 - 解决方案:在
$data里加个单调递增的seq字段,拼到score后面(如time() . '.' . $seq),确保排序唯一
事件中触发异步任务,必须剥离耗时逻辑
event::trigger() 本身是同步的,监听器里写 curl、sleep 或直接发邮件,等于没异步。
- 监听器中只做一件事:调用
dispatch(new SendEmailJob($data))推任务,不执行任何 IO 或计算 - 确保队列驱动已配置为
redis或database,不能是sync(否则还是同步) - 若用 Swoole,需确认监听器运行在协程环境,并用
Swoole\Coroutine\run()包裹耗时操作,否则协程不生效
缓存更新必须异步删,不能在 HTTP 请求里直接 Cache::delete() + save()
用户一提交表单就顺手删缓存再写库,高并发下极易引发缓存击穿、旧数据覆盖、DB 压力倍增。
- 多个并发请求都发现缓存 miss,全去查 DB + 写缓存,DB 压力翻倍
- 删缓存和写 DB 不是原子操作,中间有时间窗口;Redis 缓存若没加分布式锁,仍可能脏写
- 任务类必须实现
fire()方法,且只做一件事:比如Cache::delete('article_'.$this->aid) - 触发端别用
Queue::push()直推裸数组,要用任务类实例:Queue::push(new ClearArticleCacheJob($aid))—— 这样失败可重试、日志可追踪、还能delay(3)规避刚写库还没落盘的瞬间
supervisor 的 queue:work,或者没设 retry_after 的 Redis 驱动,上线后不出三天就会出问题。php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











