Skip to content
虚位以待
虚位以待
赞助商
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待

队列

介绍

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

配置

队列配置文件存储在 config/queue.php 中。在此文件中,您将找到框架中包含的每个队列驱动程序的连接配置,其中包括数据库、BeanstalkdIronMQAmazon 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
  • IronMQ: iron-io/iron_mq ~2.0|~4.0
  • Redis: predis/predis ~1.0

编写作业类

生成作业类

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

php
php artisan make:job SendReminderEmail --queued

此命令将在 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\Bus\SelfHandling;
use Illuminate\Contracts\Queue\ShouldQueue;

class SendReminderEmail extends Job implements SelfHandling, 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,以访问其各种调度方法。例如,这里是一个使用该 trait 的示例类:

php
<?php

namespace App;

use Illuminate\Foundation\Bus\DispatchesJobs;

class ExampleClass
{
    use DispatchesJobs;
}

为作业指定队列

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

通过将作业推送到不同的队列,您可以“分类”排队的作业,甚至可以优先考虑分配给各种队列的工作者数量。这不会将作业推送到由您的队列配置文件定义的不同队列“连接”,而只是推送到单个连接内的特定队列。要指定队列,请在作业实例上使用 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);
    }
}

延迟作业

有时您可能希望延迟排队作业的执行。例如,您可能希望在客户注册后 15 分钟排队发送提醒电子邮件。您可以使用作业类上的 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);

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

在此示例中,我们指定作业应在队列中延迟 60 秒后才可供工作者使用。

NOTE

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

从请求中调度作业

将 HTTP 请求变量映射到作业是非常常见的。因此,Laravel 提供了一些辅助方法,使其变得轻而易举,而不是强迫您为每个请求手动执行此操作。让我们来看一下 DispatchesJobs trait 上可用的 dispatchFrom 方法。默认情况下,此 trait 包含在基本 Laravel 控制器类中:

php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class CommerceController extends Controller
{
    /**
     * 处理给定的订单。
     *
     * @param  Request  $request
     * @param  int  $id
     * @return Response
     */
    public function processOrder(Request $request, $id)
    {
        // 处理请求...

        $this->dispatchFrom('App\Jobs\ProcessOrder', $request);
    }
}

此方法将检查给定作业类的构造函数,并从 HTTP 请求(或任何其他 ArrayAccess 对象)中提取变量以填充作业所需的构造函数参数。因此,如果我们的作业类在其构造函数中接受 productId 变量,作业总线将尝试从 HTTP 请求中提取 productId 参数。

您还可以将数组作为第三个参数传递给 dispatchFrom 方法。此数组将用于填充请求中不可用的任何构造函数参数:

php
$this->dispatchFrom('App\Jobs\ProcessOrder', $request, [
    'taxPercentage' => 20,
]);

作业事件

作业完成事件

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

php
<?php

namespace App\Providers;

use Queue;
use Illuminate\Support\ServiceProvider;

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

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

运行队列监听器

启动队列监听器

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

php
php artisan queue:listen

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

php
php artisan queue:listen connection

请注意,一旦此任务启动,它将继续运行,直到手动停止。您可以使用诸如 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

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

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 --daemon

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

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

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

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

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

同样,您的数据库连接在被长时间运行的守护进程使用时可能会断开。您可以使用 DB::reconnect 方法确保您有一个新的连接。

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

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

php
php artisan queue:restart

此命令将优雅地指示所有队列工作者在完成当前作业后重新启动,以便不会丢失现有作业。

NOTE

此命令依赖于缓存系统来安排重启。默认情况下,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\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * 启动任何应用程序服务。
     *
     * @return void
     */
    public function boot()
    {
        Queue::failing(function ($connection, $job, $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\Bus\SelfHandling;
use Illuminate\Contracts\Queue\ShouldQueue;

class SendReminderEmail extends Job implements SelfHandling, 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