队列

某些较耗时的工作像是寄信、发通知等,如果卡在处理请求的过程中的话就会造成使用者要多等上数秒才能收到回应,不过这些工作本身对於回应毫无影响,这时候就能考虑将这些工作放入队列中等待执行,然後马上回应使用者正在寄信或发通知中。

config

要建立队列的话必须指定存放的地方,像是资料库。

定义队列存放的方式写在 config/queue.php 中。

<?php

return [


    'default' => env('QUEUE_CONNECTION', 'sync'),



    'connections' => [
       
        //...

    ],  

    'failed' => [ 
    
        //...
        
    ],

];

connection

首先看 connections ,这就是定义伫列存放位置的地方。

'connections' => [

    'sync' => [
        'driver' => 'sync',
    ],

    'database' => [
        'driver' => 'database',
        'table' => 'jobs',
        'queue' => 'default',
        'retry_after' => 90,
        'after_commit' => false,
    ],
    
    // ... 
],

要注意的是 connection 只是定义存放位置、跟一些执行队列的方法,而各个 connection 中可以有复数个队列,例如我可以在 database 中建立 deafault 队列、 email 队列、 notification 队列,并在执行任务时指定不同的队列。

如果没指定队列的话, Laravel 就会将工作放入各个 connection 定义的 default 队列。

而如果没指定 connection ,就会用 config/queue.php 定义的 default connection

'default' => env('QUEUE_CONNECTION', 'sync'),

跟其他 default 一样有用环境变数,记得在 .env 改。

sync

sync 其实没有队列,就是及时执行工作的意思,适合在开发环境用。

database

database 需要在资料库中建表单来储存队列工作,预设资料表名称是 jobs

可以利用预设的指令来产生建立 jobs 资料表的 migration 。

sail artisan queue:table
sail artisan migrate

failed

至於 failed 定义对於执行时出现错误的工作,该存到什麽地方等待确认。

可以用指令产生预设的 database table。

php artisan queue:failed-table

php artisan migrate

Job

能够被存入队列的物件需要特别定义,可以先用指令产生 Job 类别,预设放在 app/Jobs/ 目录。

sail artisan make:job ProcessMail

app/Notifications/VerifyNotification.php

<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class ProcessMail implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function __construct()
    {
        //
    }

    public function handle()
    {
        //
    }
}

引用了很多模组,最主要的是 ShouldQueue 介面以及 Queueable,有这两个属性的话任何类别都能被存入队列等待执行,例如 Notification。

<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;

class VerifyNotification extends Notification implements ShouldQueue
{
    use Queueable;
    
    //...
    
}

可以帮 Notification 加上 ShouldQueueQueueable ,这样用 notify 时发送通知的工作就会先被存入队列,不会即时执行。

use App\Notifications\VerifyNotification;

$user->notify(new VerifyNotification());

回到我们刚刚建立的 Job ,还有一个重点是 Job 必需要有 handle() 函式,实际被存入队列而後执行的会是 handle() 中的内容。

<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Mail;

class ProcessMail implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;


    protected $user;
    protected $mail;


    public function __construct( $user , $mail )
    {
        $this->user = $user;
        $this->mail = $mail;
    }

    public function handle()
    {
        Mail::to($this->user)->send($this->mail);
    }
}

不过参数是在建立 Job 物件时传入的,所以要在 __construct() 中设定属性。

dispatch

建好 Job 後就能用 dispatch() 将任务放入队列等待执行。

用 Job 改写寄验证信的功能。

其实 Laravel 原本的验证信功能用的 Notification 只要加上 ShouldQueue 就可以被队列执行了,不过为了讲解来画蛇添足一下。

use App\Jobs\ProcessMail;

class User extends Authenticatable implements MustVerifyEmail
{
    //...

    public function sendEmailVerificationNotification()
    {
        ProcessMail::dispatch( $this , new CustomVerifyMail);
    }     
} 

这时候可以试着申请帐号寄验证信,不过可能会发现请求执行很久而解信件马上就被寄出。

可以确认有没有将 .env 中的设定改为 sync 以外的 connection,我是改成用 database

.env

QUEUE_CONNECTION=database

改完 .env 记得要让服务器重新载入才能够应用。

sail artisan config:cache

再寄一次可以看到工作被存入资料库了。

指定队列

像前面说的执行工作时没指定的话会把工作排到 default connection 里的 default queue ,要指定的话在 dispatch 时指定:

ProcessPodcast::dispatch($podcast)
              ->onConnection('sqs')
              ->onQueue('processing');

执行队列任务

不过只是存入队列并不会执行,要用指令开启队列执行功能。

sail artisan queue:work

queue:work 会以目前的程序码进行任务处理,如果改了程序码要先关掉队列 (Ctrl + C)後再开 queue:work

如果想要省着一步的话可以用 queue:listen,不过执行速度会慢一点。

如果没指定的话 queue:work 会处理的是 default connection 中的 default队列任务,可以在执行 queue:work 时指定要处理哪个 connection。

sail artisan queue:work redis

或进一步指定用处理个队列

php artisan queue:work redis --queue=emails

Failed Job

当工作执行时出现 EXception 且未被 catch 的话就会失败, queue:work 会在失败时试图重复执行,预设执行 3 次,可以在执行时决定重复尝试的次数。

php artisan queue:work redis --tries=3 --backoff=3

--backoff 则是每次尝试要间隔几秒。

另外可以在 Job 类别中定义当工作失败时要执行的 failed() 函式。

class ProcessMail implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;


    protected $user;
    protected $mail;

    public function __construct( $user , $mail )
    {
        $this->user = $user;
        $this->mail = $mail;
    }

    public function handle()
    {
        Mail::to($this->user)->send($this->mail);
    }


    public function failed(Throwable $exception)
    {
        // log failure ...
    }
}

failed() 会收到 handle() 抛出的 Exception ,除此之外并不会跟 handle() 共用任何物件中的参数,执行 handle() 跟执行 failed() 的物件是分开的。


<<:  Day 30:结束後的下一步

>>:  Day 29 - State Monad IV

LeetCode解题 Day25

1293. Shortest Path in a Grid with Obstacles Elimi...

在 Kolla-Ansible 使用 Custom Config

在上篇文章介绍了 Kolla 跟 Kolla-Ansible 部署 OpenStack 的方法。在设...

[Android Studio] 汇入向量图档 .svg 时出现错误 <text> is not supported 的解决方式

今天因应工作上的需求 在专案中汇入 .svg 的向量图档 却出现了以下错误画面 这才发现原来这张 ....

坚持己见的厉害之处

前阵子托朋友的福,去了富士山第一排露营。露营的好处就是不用想行程,光是准备吃的东西跟睡觉的地方就够忙...

【把玩Azure DevOps】Day4 版本控制系统Repos:初探Git Repo

Azure DevOps上的Repos可以分成Git Repo和TFVC Repo,这篇文章就先简单...