thinkphp 6中set_time_limit(0)无效,因app::run()末尾强制重置max_execution_time为10秒;需在app_init事件中设置具体值(如300),并配合nginx/apache超时、redis brpop等待与任务执行超时分离、失败任务入dead letter队列、超时降级及supervisor stopwaitsecs调优。

为什么set_time_limit(0)在ThinkPHP里没用
因为ThinkPHP 6在App::run()末尾会强制重置max_execution_time为10秒,无论你之前怎么设。你在控制器里写set_time_limit(0)或ini_set('max_execution_time', '0'),都晚了——框架已经覆盖过了。
真正起效的位置是app_init事件钩子,在app/event.php里注册:
return [ 'app_init' => [ function() { if (php_sapi_name() !== 'cli') { ini_set('max_execution_time', '300'); } } ] ];
- 只对Web请求生效,CLI环境天然不限制(但你自己设了也会生效)
- 别设
0,生产环境必须给具体值(如300),防死循环拖垮FPM进程 - 这个配置只影响PHP-FPM,Nginx/Apache/负载均衡器还有自己的idle timeout,通常60–300秒,超时后用户端就断连了
brpop阻塞超时和任务实际执行超时是两回事
用Redis做队列时,brpop('email_tasks', 30)里的30只是等待队列有数据的最长秒数,不是任务本身的执行时限。一旦拿到任务,脚本就开始跑,此时仍受PHP的max_execution_time约束。
所以消费者脚本必须自己控制单任务耗时:
- 在
try块开头记录microtime(true) - 执行完关键逻辑后检查已用时间,超过阈值(比如60秒)就
break或continue,避免卡死整个worker - 失败任务别直接
die或抛未捕获异常,要推到email_failed这类dead letter队列,留人工干预入口
超时降级:从“等结果”变成“保可用”
异步任务本身不能无限等。当检测到某类任务(比如调第三方翻译API)频繁超时,就得主动降级,而不是让worker反复重试把队列堵死。
典型做法是在消费者中加开关逻辑:
- 用Redis的
INCR+EXPIRE统计5分钟内失败次数,超阈值(如10次)就自动写入feature_flag:translate_down标记 - 后续任务读到这个标记,跳过真实调用,改走本地缓存兜底或返回默认文案
- 同时发告警,等运维确认后再用
DEL feature_flag:translate_down恢复
这种降级不依赖外部配置中心,纯Redis实现,轻量且worker重启后状态不丢失。
Supervisor配置里漏掉stopwaitsecs会导致超时任务被暴力杀掉
Supervisor默认stopwaitsecs=10,意思是发SIGTERM后等10秒,超时就发SIGKILL强杀。如果你的worker正在处理一个60秒的任务,它可能刚跑到第55秒就被干掉,既没完成也没进失败队列。
必须显式延长:
[program:email-worker] command=php worker.php autostart=true autorestart=true stopwaitsecs=90 ; 至少比单任务最大容忍时间多30秒
否则你看到的日志永远停在“开始处理”,没有“完成”或“失败”,排查时会误判成代码卡死。
真正麻烦的是那些没显式设置超时、又没配好Supervisor的worker——它们看起来在跑,其实早被SIGKILL切成碎片了,只剩孤儿进程占着Redis连接。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











