不能直接在控制器写sleep()或耗时逻辑,因为thinkphp同步阻塞会导致浏览器卡顿、nginx超时、php-fpm进程占用,引发504错误或超时异常;耗时操作必须交由队列异步处理。

为什么不能直接在控制器里写 sleep() 或耗时逻辑
ThinkPHP 默认请求是同步阻塞的,浏览器会一直等 PHP 脚本执行完才返回。哪怕只是 sleep(5),用户就会卡住 5 秒,Nginx 可能先超时断连,PHP-FPM 进程也被占着,高并发下直接拖垮服务。
常见错误现象:504 Gateway Timeout、Maximum execution time of X seconds exceeded、接口响应时间突增且不可控。
- 耗时操作(如发邮件、生成 PDF、调第三方 API)必须剥离出主请求流
- ThinkPHP 自身不带队列运行时,光配
queue.php不启动 worker 就等于没配 - 本地开发用
sync驱动能跑通,但上线切redis后不启 worker,任务就永远“卡在队列里”
如何配置 Redis 队列并让 job 真正跑起来
核心不是“加个配置”,而是确保「投递 → 存储 → 消费」链路全通。Redis 是最常用且 ThinkPHP 原生支持的后端,但容易漏掉两个关键动作:启动监听进程、确认序列化兼容性。
实操建议:
- 在
config/queue.php中把default设为'redis',connections.redis.host指向真实 Redis 地址 - 确保
app\job\SendEmail.php类继承think\queue\Job,且实现fire()方法 - 投递任务用
dispatch(new SendEmail($data)),别用job->execute()手动调用(那还是同步) - 必须手动执行
php think queue:listen或php think queue:work --daemon启动消费者;Supervisor 才是线上守进程的正解
job 失败后自动重试却反复失败怎么办
ThinkPHP 队列默认重试 3 次,失败后进 failed_jobs 表。但很多人只清表或删记录,没查根本原因——多数是 job 里用了闭包、未序列化的资源(如 cURL handle)、或依赖了请求上下文(input()、session() 在 worker 进程里根本不存在)。
排查重点:
- job 构造方法里别传
$request或$this->app,所有数据必须显式传参并可序列化 - 检查
failed_jobs表里的exception字段,90% 的问题藏在里面,比如Call to undefined function think\queue\curl_init() - 临时加日志:在
fire()开头写file_put_contents('/tmp/job.log', print_r($data, 1), FILE_APPEND),确认参数是否丢失 - 重试间隔靠
delay()控制,别指望框架自动退避;连续失败建议加if (app()->isDebug()) { throw new Exception(...); }快速暴露
异步任务需要返回结果给前端?别硬等
队列本质是解耦,没有“等 job 执行完再回传”的机制。想让用户感知进度,得换思路:用数据库/Redis 记状态,前端轮询或 WebSocket 推送。
典型做法:
- 投递前生成唯一
$task_id = uniqid('task_'),存到task_status表,初始status = 'pending' - job 执行中更新该记录:
status = 'running'→'success'或'failed',附带result字段 - 前端用
fetch('/api/task-status?task_id=' + taskId)每 2 秒查一次,直到status !== 'pending' - 千万别在 controller 里写
while ($status === 'pending') { sleep(1); $status = db()->... }—— 这又变同步了
真正难的不是配队列,是把“需要等待结果”的业务逻辑,改造成“状态可查+事件驱动”的结构。这个思维转换,比敲十遍 php think queue:work 都重要。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











