PHP项目Laravel队列重试次数与间隔

wen PHP项目 3

本文目录导读:

PHP项目Laravel队列重试次数与间隔

  1. 在任务类中定义
  2. 在队列配置文件或运行时设置
  3. 通过排队任务时动态设置
  4. 使用队列事件监听器
  5. 配置示例(Supervisor)
  6. 完整示例:带指数退避的队列任务
  7. 优雅处理重试的方法
  8. 注意事项

在 Laravel 中配置队列任务的重试次数和间隔,主要有以下几种方式:

在任务类中定义

通过属性定义

<?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 SendEmailJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
    /**
     * 任务最大尝试次数
     *
     * @var int
     */
    public $tries = 3;
    /**
     * 任务重试前延迟的秒数
     *
     * @var int
     */
    public $backoff = 60; // Laravel 8.x 及以上
    // 或者使用数组定义不同的延迟时间
    // public $backoff = [10, 30, 60]; // 第1次重试延迟10秒,第2次延迟30秒,第3次延迟60秒
    /**
     * 执行任务
     *
     * @return void
     */
    public function handle()
    {
        // 任务逻辑
    }
}

Laravel 8.x 以下版本使用 retryAfter

<?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 SendEmailJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
    /**
     * 任务最大尝试次数
     *
     * @var int
     */
    public $tries = 3;
    /**
     * 任务重试前的延迟(秒) - Laravel 7.x 及以下
     *
     * @var int
     */
    public $retryAfter = 60;
    public function handle()
    {
        // 任务逻辑
    }
}

在队列配置文件或运行时设置

通过 .env 文件配置

# 队列连接配置
QUEUE_CONNECTION=redis
# 队列进程配置 (supervisor 配置中)
QUEUE_WORKER_TRIES=3
QUEUE_WORKER_TIMEOUT=60
QUEUE_WORKER_SLEEP=3
QUEUE_WORKER_BACKOFF=30
QUEUE_WORKER_MAX_TIME=3600

运行时通过命令行参数

# 指定尝试次数
php artisan queue:work --tries=3
# 指定超时时间
php artisan queue:work --timeout=60
# 指定重试延迟
php artisan queue:work --backoff=30
# 组合使用
php artisan queue:work redis --tries=3 --timeout=60 --backoff=30

通过排队任务时动态设置

<?php
namespace App\Http\Controllers;
use App\Jobs\SendEmailJob;
use Illuminate\Http\Request;
class EmailController extends Controller
{
    public function send()
    {
        // 创建任务时设置尝试次数和延迟
        $job = new SendEmailJob();
        // 动态设置尝试次数
        $job->tries = 5;
        // 动态设置重试延迟
        $job->backoff = [30, 60, 120, 240, 480]; // 每次重试不同的延迟
        // 推送到队列
        dispatch($job);
    }
}

使用队列事件监听器

<?php
namespace App\Providers;
use Illuminate\Queue\Events\JobFailed;
use Illuminate\Queue\Events\JobAttempted;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\ServiceProvider;
class QueueEventServiceProvider extends ServiceProvider
{
    public function boot()
    {
        // 监听队列任务失败
        Queue::failing(function (JobFailed $event) {
            // $event->job->attempts() - 当前尝试次数
            // $event->exception - 异常信息
            logger('队列任务失败', [
                'job' => $event->job->resolveName(),
                'attempts' => $event->job->attempts(),
                'error' => $event->exception->getMessage(),
            ]);
        });
        // 监听队列任务执行(包括重试)
        Queue::after(function (JobAttempted $event) {
            // 任务尝试后的回调
        });
    }
}

配置示例(Supervisor)

[program:worker]
process_name=%(program_name)s_%(process_num)02d
command=php /path/to/project/artisan queue:work redis --tries=3 --timeout=60 --backoff=30
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=8
redirect_stderr=true
stdout_logfile=/path/to/project/storage/logs/worker.log
stopwaitsecs=3600

完整示例:带指数退避的队列任务

<?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;
use Illuminate\Support\Facades\Log;
class ProcessPaymentJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
    /** @var int 最大尝试次数 */
    public $tries = 5;
    /** @var array 重试间隔(秒)- 指数退避策略 */
    public $backoff = [10, 30, 60, 180, 300];
    /** @var int 超时时间(秒) */
    public $timeout = 120;
    /** @var bool 失败后是否删除任务 */
    public $deleteWhenMissingModels = true;
    public function handle()
    {
        try {
            // 业务逻辑
            $this->processPayment();
        } catch (\Exception $e) {
            Log::error('支付处理失败', [
                'message' => $e->getMessage(),
                'attempts' => $this->attempts(),
            ]);
            // 如果还有重试次数,释放任务回去
            if ($this->attempts() < $this->tries) {
                $this->release($this->getNextBackoff());
            } else {
                throw $e; // 达到最大次数,抛出异常标记为永久失败
            }
        }
    }
    /**
     * 获取下一次重试的延迟时间
     */
    protected function getNextBackoff()
    {
        $attempts = $this->attempts();
        if (is_array($this->backoff)) {
            return $this->backoff[$attempts - 1] ?? $this->backoff[count($this->backoff) - 1];
        }
        return $this->backoff;
    }
    /**
     * 任务最终失败的处理器
     */
    public function failed(\Throwable $exception)
    {
        Log::error('支付处理最终失败', [
            'error' => $exception->getMessage(),
        ]);
        // 可以在这里发送通知
    }
}

优雅处理重试的方法

<?php
namespace App\Jobs;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
class HandleExternalApi implements ShouldQueue
{
    use Dispatchable;
    public $tries = 3;
    public $backoff = [15, 60, 300];
    public $timeout = 120;
    public function handle()
    {
        $response = $this->callExternalApi();
        if ($response->status() === 429) {
            // 速率限制,释放任务并延迟重试
            $this->release(300);
        }
        if ($response->status() !== 200) {
            throw new \Exception('外部API错误');
        }
        // 处理成功
    }
}

注意事项

  1. 重试次数配置位置优先级:任务属性 > 队列命令参数 > 队列配置
  2. backoff 属性:Laravel 8.x 及以上使用,替代了旧的 retryAfter
  3. 防止无限重试:确保 tries 不会设置为很大的值
  4. 错误日志:记录每次重试的原因和时间
  5. 监控:使用 Laravel Horizon 或自定义监控来跟踪队列状态

这样配置后,Laravel 会自动处理队列任务的重试机制,当任务执行失败时会按照配置的时间和次数自动重试。

抱歉,评论功能暂时关闭!