踩坑: Horizon 队列任务卡死,无法进入重试问题排查分析过程
问题
队列执行中, 未执行完成发生多次重试,队列反复执行
队列执行中,突然挂起后卡在padding中,无法进入failed阶段,代码执行结束无响应
问题发生
项目中遇到一个队列,执行较久(执行某段脚本大约10分钟左右). 因为不想盯着任务执行,索性加上多次失败重试. 将horizon
配置如下, 为了方便测试,我们把超时时间缩短
'long-task' => [
'connection' => 'redis',
'queue' => [QueueNameKeys::EXPORT_TASK],
'balance' => 'auto',
'memory' => 256,
'tries' => 5,
'timeout' => 10,
],
修改了tries=5
,timeout=10
随即将测试代码加入测试执行
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class TestTask implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
use SerializesModels;
public function handle()
{
while (true) {
logger()->info('run in handle');
sleep(1);
}
}
public function failed(\Throwable $exception)
{
logger()->info('run in failed:' . $exception->getMessage());
}
}
将任务派发进入队列,观察执行结果
php artisan tinker
Psy Shell v0.11.4 (PHP 8.1.4 — cli) by Justin Hileman
>>> use App\Jobs\TestTask
>>> TestTask::dispatch()->onQueue('export-task')
=> Illuminate\Foundation\Bus\PendingDispatch {#5500}
这时候发现执行结果为 : 任务停留在Pending Jobs中, 但是日志输出停留在10条 , 等待很久都没有进入重试机制
经过短暂的排查后, 发现 队列超时配置受
redis
的retry_after
配置影响, 通俗来讲就是,当retry_after
配置之后, 队列的timeout
只负责中止程序的执行, 重试需要等待retry_after
. 部分小伙伴也会发现,自己的队列有正常进入重试, 那正是因为retry_after
配置值较小. 达到配置超时后, 队列正常进入重试 .
对这几项配置,我们可以通过下面文章理解队列的retry_after
,timeout
,backoff
到这里问题似乎已经解决到这里问题似乎已经解决
貌似只需要调整 retry_after
我们就可以完美进入重试
但是这里看着以前的配置 retry_after = 90000
陷入沉思. 原来旧业务中有用到队列 基于时间的尝试 , 当时遇到队列反复重启的问题 (达到retry_after
时间队列任未消费完毕, 系统会自动重新启动一个此队列, 会造成队列重复执行, 所以官方给的推荐是 retry_after
配置应该等于 队列中最大的 timeout
值.) 而我当前的队列就是需要执行特别久 . 这个导致了两个问题
- 如果
retry_after
配的小, 我的长任务会被达到超时时间反复执行 - 如果
retry_after
配的大, 我的短任务会在需要重试的时候, 等待这个配置时间结束,才能重试
如何解决这个问题
- 灵活配置
retry_after
?
查看代码
发现这个配置只在
redis
链接的时候可以配置
为什么这个配置不可以单独修改,已经有了timeout
为什么我还需要retry_after
, 这些问题这里暂不在这里讨论
- 给队列配置不同的
redis
连接 - 手动控制重试
1.给不同队列配置不同redis
/config/queue.php
'connections' => [
'redis' => [
'driver' => 'redis',
'connection' => 'default',
'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => 90,
'block_for' => null,
'after_commit' => false,
],
'redis-horizon-10m' => [
'driver' => 'redis',
'connection' => 'default',
'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => 600,
'block_for' => null,
'after_commit' => false,
],
],
/config/horizon.php
'long-task' => [
'connection' => 'redis-horizon-10m',
'queue' => [QueueNameKeys::EXPORT_TASK],
'balance' => 'auto',
'memory' => 256,
'tries' => 3,
'timeout' => 10,
],
2.所有队列不配置重试次数, 再failed
方法中定义自己的重试规则
3.您的其他骚操作(谢谢分享)
本作品采用《CC 协议》,转载必须注明作者和本文链接
这是什么样的业务呀。需要执行10分钟 多少数据量?
10分钟的任务,能不能用到 任务链
其实我再业务中更想用
Artisan::call('xxxx')
去处理 . 但是没有发现如何后台调用 , 所以用队列去处理此文甚好,解决了我最近的难题。因为发送的短信验证码有时候就进入到pending jobs,在然后就到了failed jobs,提示超时。现在结合文章里说的,的确就是retry_after设置的时间过大,超过了timeout,即时重试任务,也成了超时了。