Laravel 5.7 Horizon队列任务未执行/失败及限流问题求助
从你的描述和配置来看,核心问题是API频率限制与队列并发控制不匹配,加上队列配置的一些冲突,导致任务要么无法执行要么触发API限流失败。下面分点拆解问题并给出具体修复方案:
1. 统一API节流规则,避免多键冲突
你现在的两类任务分别用了zohoprojecttasksget和zohoupdatetask两个Redis节流键,但实际上所有任务都共享同一个API的120秒100次限制。这会导致两类任务各自算限流,加起来很容易超过API的总限制,触发30分钟锁定,同时也会让Horizon的任务调度混乱。
修复方案:
把所有调用该API的任务都统一使用同一个节流键,比如zoho-api-throttle,并且调整允许的请求数为90次/120秒(留10次的缓冲空间,避免刚好卡到100次触发限流):
// 夜间同步任务和按需任务都替换成这个节流逻辑 Redis::throttle('zoho-api-throttle') ->allow(90) ->every(120) ->then(function () { // API调用逻辑 }, function () { // 触发限流时,释放任务的时间要合理,建议设置为120秒(刚好等限流周期重置) return $this->release(120); });
2. 解决Supervisor与Horizon的Worker数量冲突
你的Supervisor配置了numprocs=8,但Horizon的production环境设置了processes=10。Horizon是Laravel的队列管理器,Supervisor只是负责启动Horizon进程,Horizon自身会管理内部的Worker数量。这里的冲突会导致Horizon尝试启动10个Worker,但Supervisor只给了8个进程配额,部分Worker无法正常启动,导致任务挂起。
修复方案:
修改Supervisor配置,把numprocs改为1(只需要启动一个Horizon主进程,Horizon自己管理内部Worker):
[program:laravel-worker] process_name=%(program_name)s command=php /home/ubuntu/www/artisan horizon autostart=true autorestart=true user=ubuntu numprocs=1 # 修改为1 redirect_stderr=true stdout_logfile=/home/ubuntu/otmas/worker.log
同时,根据你的任务量,调整Horizon的processes为合适的数量,比如6-8个(不要超过API限流能承载的并发数)。
3. 拆分队列优先级,避免批量任务阻塞按需任务
目前所有任务都放在default队列,夜间950次的批量同步任务会占满所有Worker,导致用户触发的按需任务无法及时执行,甚至挂起。
修复方案:
- 新增两个队列:
nightly-sync(夜间批量任务)和user-request(用户按需任务) - 修改Horizon配置,给不同队列分配不同的Worker数量和优先级:
'environments' => [ 'production' => [ 'nightly-supervisor' => [ 'connection' => 'redis', 'queue' => ['nightly-sync'], 'balance' => 'simple', 'processes' => 4, // 分配4个Worker处理批量任务 'tries' => 3, 'timeout' => 15, ], 'user-supervisor' => [ 'connection' => 'redis', 'queue' => ['user-request'], 'balance' => 'auto', 'processes' => 4, // 分配4个Worker处理按需任务 'tries' => 3, 'timeout' => 15, 'priority' => 10, // 按需任务优先级更高 ], ], // 其他环境配置同理调整 ],
- 任务类中指定队列:
// 夜间同步任务 class GetProjectTasks implements ShouldQueue { public $queue = 'nightly-sync'; // 指定队列 // ... 其他代码 } // 按需任务 class ReplaceTask implements ShouldQueue { public $queue = 'user-request'; // 指定队列 // ... 其他代码 }
4. 调整任务重试与释放逻辑,避免无效重试
- 你的夜间任务设置了
$tries = 540,这意味着任务会重试540次,每次释放85秒,总重试时间会超过API的30分钟锁定时间,导致大量无效重试。建议把tries调整为18(30分钟/120秒≈15次,留3次缓冲),释放时间改为120秒。 - 按需任务的
$tries = 140也过高,改为10左右即可,释放时间统一为120秒。
示例修改:
class GetProjectTasks implements ShouldQueue { public $tries = 18; public $timeout = 15; // ... handle方法中的释放逻辑 return $this->release(120); }
5. 优化队列连接配置,避免Worker阻塞
你的Redis队列配置中block_for = 140,这个值是Worker在队列空时等待的时间,建议改为60,避免Worker长时间阻塞:
'connections' => [ 'redis' => [ 'driver' => 'redis', 'connection' => 'default', 'queue' => 'default', 'retry_after' => 460, 'block_for' => 60, // 修改为60 ], ],
最后验证步骤
- 重启Supervisor:
sudo supervisorctl reread && sudo supervisorctl update && sudo supervisorctl restart laravel-worker - 重启Horizon:
php artisan horizon:terminate(Horizon会自动重启) - 观察Horizon仪表盘,确认任务分配正常,没有挂起或频繁失败的情况
- 查看API调用日志,确认没有触发30分钟锁定的情况
内容的提问来源于stack exchange,提问作者Diego Vargas Sotomayor

