Skip to content

队列

介绍

Laravel 队列服务提供了跨多种不同队列后端的统一 API。队列允许您将耗时的任务(如发送电子邮件)的处理推迟到稍后时间,从而显著加快应用程序的 Web 请求速度。

配置

队列配置文件存储在 config/queue.php 中。在此文件中,您将找到框架中包含的每个队列驱动程序的连接配置,其中包括数据库、BeanstalkdAmazon SQSRedis 和同步(用于本地使用)驱动程序。

还包括一个 null 队列驱动程序,它只是简单地丢弃排队的作业。

驱动程序先决条件

数据库

要使用 database 队列驱动程序,您需要一个数据库表来保存作业。要生成创建此表的迁移,请运行 queue:table Artisan 命令。迁移创建后,您可以使用 migrate 命令迁移数据库:

php
php artisan queue:table

php artisan migrate

其他队列依赖项

以下是列出的队列驱动程序所需的依赖项:

  • Amazon SQS: aws/aws-sdk-php ~3.0
  • Beanstalkd: pda/pheanstalk ~3.0
  • Redis: predis/predis ~1.0

编写作业类

生成作业类

默认情况下,应用程序的所有可排队作业都存储在 app/Jobs 目录中。您可以使用 Artisan CLI 生成新的排队作业:

php
php artisan make:job SendReminderEmail

此命令将在 app/Jobs 目录中生成一个新类,并且该类将实现 Illuminate\Contracts\Queue\ShouldQueue 接口,指示 Laravel 该作业应被推送到队列而不是同步运行。

作业类结构

作业类非常简单,通常只包含一个 handle 方法,该方法在作业被队列处理时调用。让我们从一个示例作业类开始:

php
<?php

namespace App\Jobs;

use App\User;
use App\Jobs\Job;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;

class SendReminderEmail extends Job implements ShouldQueue
{
    use InteractsWithQueue, SerializesModels;

    protected $user;

    /**
     * 创建一个新的作业实例。
     *
     * @param  User  $user
     * @return void
     */
    public function __construct(User $user)
    {
        $this->user = $user;
    }

    /**
     * 执行作业。
     *
     * @param  Mailer  $mailer
     * @return void
     */
    public function handle(Mailer $mailer)
    {
        $mailer->send('emails.reminder', ['user' => $this->user], function ($m) {
            //
        });

        $this->user->reminders()->create(...);
    }
}

在此示例中,请注意我们能够将 Eloquent 模型 直接传递到排队作业的构造函数中。由于作业使用的 SerializesModels trait,Eloquent 模型将在作业处理时被优雅地序列化和反序列化。如果您的排队作业在其构造函数中接受 Eloquent 模型,则只有模型的标识符会被序列化到队列中。当作业实际处理时,队列系统将自动从数据库中重新检索完整的模型实例。这对您的应用程序来说是完全透明的,并防止了序列化完整 Eloquent 模型实例时可能出现的问题。

handle 方法在作业被队列处理时调用。请注意,我们能够在作业的 handle 方法上进行依赖注入。Laravel 服务容器 会自动注入这些依赖项。

当事情出错时

如果在作业处理时抛出异常,它将自动被释放回队列,以便可以再次尝试。作业将继续被释放,直到它已被尝试的次数达到应用程序允许的最大次数。最大尝试次数由 queue:listenqueue:work Artisan 作业上使用的 --tries 开关定义。有关运行队列监听器的更多信息 可以在下面找到

手动释放作业

如果您希望手动 release 作业,InteractsWithQueue trait(已包含在生成的作业类中)提供了对队列作业 release 方法的访问。release 方法接受一个参数:您希望等待的秒数,直到作业再次可用:

php
public function handle(Mailer $mailer)
{
    if (condition) {
        $this->release(10);
    }
}

检查运行尝试次数

如上所述,如果在作业处理时发生异常,它将自动被释放回队列。您可以使用 attempts 方法检查已尝试运行作业的次数:

php
public function handle(Mailer $mailer)
{
    if ($this->attempts() > 3) {
        //
    }
}

将作业推送到队列

位于 app/Http/Controllers/Controller.php 的默认 Laravel 控制器使用 DispatchesJobs trait。此 trait 提供了几种方法,允许您方便地将作业推送到队列,例如 dispatch 方法:

php
<?php

namespace App\Http\Controllers;

use App\User;
use Illuminate\Http\Request;
use App\Jobs\SendReminderEmail;
use App\Http\Controllers\Controller;

class UserController extends Controller
{
    /**
     * 向给定用户发送提醒电子邮件。
     *
     * @param  Request  $request
     * @param  int  $id
     * @return Response
     */
    public function sendReminderEmail(Request $request, $id)
    {
        $user = User::findOrFail($id);

        $this->dispatch(new SendReminderEmail($user));
    }
}

DispatchesJobs Trait

当然,有时您可能希望从应用程序中的其他地方而不是路由或控制器中调度作业。为此,您可以在应用程序中的任何类上包含 DispatchesJobs trait,以访问其各种调度方法。例如,这里是一个使用该 trait 的示例类:

php
<?php

namespace App;

use Illuminate\Foundation\Bus\DispatchesJobs;

class ExampleClass
{
    use DispatchesJobs;
}

dispatch 函数

或者,您可以使用 dispatch 全局函数:

php
Route::get('/job', function () {
    dispatch(new App\Jobs\PerformTask);

    return 'Done!';
});

为作业指定队列

您还可以指定作业应发送到的队列。

通过将作业推送到不同的队列,您可以“分类”排队的作业,甚至可以优先考虑分配给各种队列的工作者数量。这不会将作业推送到队列配置文件中定义的不同队列“连接”,而只是推送到单个连接内的特定队列。要指定队列,请在作业实例上使用 onQueue 方法。onQueue 方法由 Illuminate\Bus\Queueable trait 提供,该 trait 已包含在 App\Jobs\Job 基类上:

php
<?php

namespace App\Http\Controllers;

use App\User;
use Illuminate\Http\Request;
use App\Jobs\SendReminderEmail;
use App\Http\Controllers\Controller;

class UserController extends Controller
{
    /**
     * 向给定用户发送提醒电子邮件。
     *
     * @param  Request  $request
     * @param  int  $id
     * @return Response
     */
    public function sendReminderEmail(Request $request, $id)
    {
        $user = User::findOrFail($id);

        $job = (new SendReminderEmail($user))->onQueue('emails');

        $this->dispatch($job);
    }
}
lightbulb

DispatchesJobs trait 将作业推送到默认队列连接内的队列。

为作业指定队列连接

如果您正在处理多个队列连接,您可以指定将作业推送到哪个连接。要指定连接,请在作业实例上使用 onConnection 方法。onConnection 方法由 Illuminate\Bus\Queueable trait 提供,该 trait 已包含在 App\Jobs\Job 基类上:

php
<?php

namespace App\Http\Controllers;

use App\User;
use Illuminate\Http\Request;
use App\Jobs\SendReminderEmail;
use App\Http\Controllers\Controller;

class UserController extends Controller
{
    /**
     * 向给定用户发送提醒电子邮件。
     *
     * @param  Request  $request
     * @param  int  $id
     * @return Response
     */
    public function sendReminderEmail(Request $request, $id)
    {
        $user = User::findOrFail($id);

        $job = (new SendReminderEmail($user))->onConnection('alternate');

        $this->dispatch($job);
    }
}

当然,您还可以链式调用 onConnectiononQueue 方法,以指定作业的连接和队列:

php
public function sendReminderEmail(Request $request, $id)
{
    $user = User::findOrFail($id);

    $job = (new SendReminderEmail($user))
                    ->onConnection('alternate')
                    ->onQueue('emails');

    $this->dispatch($job);

}

延迟作业

有时您可能希望延迟排队作业的执行。例如,您可能希望在客户注册后 5 分钟内排队发送提醒电子邮件。您可以使用作业类上的 delay 方法来实现这一点,该方法由 Illuminate\Bus\Queueable trait 提供:

php
<?php

namespace App\Http\Controllers;

use App\User;
use Illuminate\Http\Request;
use App\Jobs\SendReminderEmail;
use App\Http\Controllers\Controller;

class UserController extends Controller
{
    /**
     * 向给定用户发送提醒电子邮件。
     *
     * @param  Request  $request
     * @param  int  $id
     * @return Response
     */
    public function sendReminderEmail(Request $request, $id)
    {
        $user = User::findOrFail($id);

        $job = (new SendReminderEmail($user))->delay(60 * 5);

        $this->dispatch($job);
    }
}

在此示例中,我们指定作业应在队列中延迟 5 分钟,然后再提供给工作者。

lightbulb

Amazon SQS 服务的最大延迟时间为 15 分钟。

作业事件

作业生命周期事件

Queue::beforeQueue::after 方法允许您注册一个回调,以在排队作业开始之前或成功执行时执行。回调是执行额外日志记录、排队后续作业或为仪表板增加统计数据的绝佳机会。例如,我们可以从 Laravel 附带的 AppServiceProvider 中附加一个回调到此事件:

php
<?php

namespace App\Providers;

use Queue;
use Illuminate\Support\ServiceProvider;
use Illuminate\Queue\Events\JobProcessed;

class AppServiceProvider extends ServiceProvider
{
    /**
     * 启动任何应用程序服务。
     *
     * @return void
     */
    public function boot()
    {
        Queue::after(function (JobProcessed $event) {
            // $event->connectionName
            // $event->job
            // $event->data
        });
    }

    /**
     * 注册服务提供者。
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

运行队列监听器

启动队列监听器

Laravel 包含一个 Artisan 命令,可以在新作业推送到队列时运行它们。您可以使用 queue:listen 命令运行监听器:

php
php artisan queue:listen

您还可以指定监听器应使用哪个队列连接:

php
php artisan queue:listen connection-name

请注意,一旦此任务启动,它将继续运行,直到手动停止。您可以使用诸如 Supervisor 之类的进程监视器,以确保队列监听器不会停止运行。

队列优先级

您可以将逗号分隔的队列连接列表传递给 listen 作业,以设置队列优先级:

php
php artisan queue:listen --queue=high,low

在此示例中,high 队列上的作业将始终在处理 low 队列上的作业之前进行处理。

指定作业超时参数

您还可以设置每个作业允许运行的时间长度(以秒为单位):

php
php artisan queue:listen --timeout=60

指定队列休眠时间

此外,您可以指定在轮询新作业之前等待的秒数:

php
php artisan queue:listen --sleep=5

请注意,队列仅在队列上没有作业时“休眠”。如果有更多作业可用,队列将继续处理它们而不休眠。

处理队列上的第一个作业

要仅处理队列上的第一个作业,您可以使用 queue:work 命令:

php
php artisan queue:work

Supervisor 配置

Supervisor 是 Linux 操作系统的进程监视器,将自动重启您的 queue:listenqueue:work 命令(如果它们失败)。要在 Ubuntu 上安装 Supervisor,您可以使用以下命令:

php
sudo apt-get install supervisor

Supervisor 配置文件通常存储在 /etc/supervisor/conf.d 目录中。在此目录中,您可以创建任意数量的配置文件,指示 Supervisor 如何监视您的进程。例如,让我们创建一个 laravel-worker.conf 文件,启动并监视一个 queue:work 进程:

php
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /home/forge/app.com/artisan queue:work sqs --sleep=3 --tries=3 --daemon
autostart=true
autorestart=true
user=forge
numprocs=8
redirect_stderr=true
stdout_logfile=/home/forge/app.com/worker.log

在此示例中,numprocs 指令将指示 Supervisor 运行 8 个 queue:work 进程并监视所有这些进程,如果它们失败,将自动重启它们。当然,您应该更改 command 指令中的 queue:work sqs 部分,以反映您选择的队列驱动程序。

创建配置文件后,您可以使用以下命令更新 Supervisor 配置并启动进程:

php
sudo supervisorctl reread

sudo supervisorctl update

sudo supervisorctl start laravel-worker:*

有关配置和使用 Supervisor 的更多信息,请查阅 Supervisor 文档。或者,您可以使用 Laravel Forge 从方便的 Web 界面自动配置和管理您的 Supervisor 配置。

守护进程队列监听器

queue:work Artisan 命令包括一个 --daemon 选项,用于强制队列工作者在不重新启动框架的情况下继续处理作业。与 queue:listen 命令相比,这显著减少了 CPU 使用量:

要在守护进程模式下启动队列工作者,请使用 --daemon 标志:

php
php artisan queue:work connection-name --daemon

php artisan queue:work connection-name --daemon --sleep=3

php artisan queue:work connection-name --daemon --sleep=3 --tries=3

如您所见,queue:work 作业支持大多数 queue:listen 可用的选项。您可以使用 php artisan help queue:work 作业查看所有可用选项。

守护进程队列监听器的编码注意事项

守护进程队列工作者在处理每个作业之前不会重新启动框架。因此,您应该小心在作业完成之前释放任何重资源。例如,如果您正在使用 GD 库进行图像处理,您应该在完成时使用 imagedestroy 释放内存。

使用守护进程队列监听器进行部署

由于守护进程队列工作者是长时间运行的进程,因此它们不会在不重新启动的情况下拾取代码中的更改。因此,使用守护进程队列工作者部署应用程序的最简单方法是在部署脚本中重新启动工作者。您可以通过在部署脚本中包含以下命令来优雅地重新启动所有工作者:

php
php artisan queue:restart

此命令将优雅地指示所有队列工作者在完成当前作业后“死亡”,以便不会丢失现有作业。请记住,当执行 queue:restart 命令时,队列工作者将死亡,因此您应该运行一个进程管理器(如 Supervisor),以自动重启队列工作者。

lightbulb

此命令依赖于缓存系统来安排重启。默认情况下,APCu 不适用于 CLI 作业。如果您使用的是 APCu,请在 APCu 配置中添加 apc.enable_cli=1

处理失败的作业

由于事情并不总是按计划进行,有时您的排队作业会失败。别担心,这发生在我们最优秀的人身上!Laravel 提供了一种方便的方法来指定作业应尝试的最大次数。在作业超过此尝试次数后,它将被插入到 failed_jobs 表中。表的名称可以通过 config/queue.php 配置文件进行配置。

要为 failed_jobs 表创建迁移,您可以使用 queue:failed-table 命令:

php
php artisan queue:failed-table

在运行 队列监听器 时,您可以使用 queue:listen 命令上的 --tries 开关指定作业应尝试的最大次数:

php
php artisan queue:listen connection-name --tries=3

失败作业事件

如果您希望注册一个事件,该事件将在排队作业失败时调用,您可以使用 Queue::failing 方法。此事件是通过电子邮件或 HipChat 通知您的团队的绝佳机会。例如,我们可以从 Laravel 附带的 AppServiceProvider 中附加一个回调到此事件:

php
<?php

namespace App\Providers;

use Queue;
use Illuminate\Queue\Events\JobFailed;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * 启动任何应用程序服务。
     *
     * @return void
     */
    public function boot()
    {
        Queue::failing(function (JobFailed $event) {
            // $event->connectionName
            // $event->job
            // $event->data
        });
    }

    /**
     * 注册服务提供者。
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

作业类上的失败方法

为了更细粒度的控制,您可以直接在队列作业类上定义一个 failed 方法,允许您在发生故障时执行作业特定的操作:

php
<?php

namespace App\Jobs;

use App\Jobs\Job;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;

class SendReminderEmail extends Job implements ShouldQueue
{
    use InteractsWithQueue, SerializesModels;

    /**
     * 执行作业。
     *
     * @param  Mailer  $mailer
     * @return void
     */
    public function handle(Mailer $mailer)
    {
        //
    }

    /**
     * 处理作业失败。
     *
     * @return void
     */
    public function failed()
    {
        // 作业失败时调用...
    }
}

重试失败的作业

要查看已插入到 failed_jobs 数据库表中的所有失败作业,您可以使用 queue:failed Artisan 命令:

php
php artisan queue:failed

queue:failed 命令将列出作业 ID、连接、队列和失败时间。作业 ID 可用于重试失败的作业。例如,要重试 ID 为 5 的失败作业,应发出以下命令:

php
php artisan queue:retry 5

要重试所有失败的作业,请使用 queue:retry 并将 all 作为 ID:

php
php artisan queue:retry all

如果您希望删除失败的作业,可以使用 queue:forget 命令:

php
php artisan queue:forget 5

要删除所有失败的作业,您可以使用 queue:flush 命令:

php
php artisan queue:flush