鍍金池/ 教程/ PHP/ 隊列
門面
Laravel Homestead
安裝及配置
測試
HTTP 中間件
加密
升級指南
幫助函數
應用目錄結構
集合
新手入門指南-簡單任務管理系統(tǒng)
任務調度
查詢構建器
視圖
驗證
Laravel Cashier(訂購&支付&發(fā)票)
本地化
隊列
調整器
分頁
文件系統(tǒng)/云存儲
貢獻代碼
哈希
HTTP 控制器
緩存
遷移
HTTP 請求
Laravel Elixir
發(fā)行版本說明
Envoy 任務運行器(SSH任務)
序列化
Session
起步
帶用戶功能的任務管理系統(tǒng)
起步
用戶授權
郵件
事件
填充數據
HTTP 路由
服務提供者
Blade 模板引擎
包開發(fā)
用戶認證
Artisan 控制臺
HTTP 響應
集合
服務容器
關聯關系
一次請求的生命周期
契約
Redis
錯誤&日志

隊列

1、簡介

Laravel 隊列服務為各種不同的后臺隊列提供了統(tǒng)一的 API。隊列允許你推遲耗時任務(例如發(fā)送郵件)的執(zhí)行,從而大幅提高 web 請求速度。

1.1 配置

隊列配置文件存放在config/queue.php。在該文件中你將會找到框架自帶的每一個隊列驅動的連接配置,包括數據庫、Beanstalkd、 IronMQ、 Amazon SQS、 Redis 以及同步(本地使用)驅動。其中還包含了一個 null 隊列驅動以拒絕隊列任務。

1.2 隊列驅動預備知識

1.2.1 數據庫

為了使用 database 隊列驅動,需要一張數據庫表來存放任務,要生成創(chuàng)建該表的遷移,運行 Artisan 命令 queue:table,遷移被創(chuàng)建好了之后,使用 migrate 命令運行遷移:

php artisan queue:table
php artisan migrate

1.2.2 其它隊列依賴

下面是以上列出隊列驅動需要安裝的依賴:

  • Amazon SQS: aws/aws-sdk-php ~3.0
  • Beanstalkd: pda/pheanstalk ~3.0
  • IronMQ: iron-io/iron_mq ~2.0
  • Redis: predis/predis ~1.0

2、編寫任務類

2.1 生成任務類

默認情況下,應用的所有隊列任務都存放在 app/Jobs 目錄。你可以使用 Artisan CLI 生成新的隊列任務:

php artisan make:job SendReminderEmail --queued

該命令將會在 app/Jobs 目錄下生成一個新的類,并且該類實現了 Illuminate\Contracts\Queue\ShouldQueue 接口,告訴 Laravel 該任務應該被推送到隊列而不是同步運行。

2.2 任務類結構

任務類非常簡單,正常情況下只包含一個當隊列處理該任務時被執(zhí)行的 handle 方法,讓我們看一個任務類的例子:

<?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;

    /**
     * 創(chuàng)建一個新的任務實例
     *
     * @param  User  $user
     * @return void
     */
    public function __construct(User $user)
    {
        $this->user = $user;
    }

    /**
     * 執(zhí)行任務
     *
     * @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 模型將會在任務被執(zhí)行是優(yōu)雅地序列化和反序列化。如果你的隊列任務在構造函數中接收 Eloquent 模型,只有模型的主鍵會被序列化到隊列,當任務真正被執(zhí)行的時候,隊列系統(tǒng)會自動從數據庫中獲取整個模型實例。這對應用而言是完全透明的,從而避免序列化整個 Eloquent 模型實例引起的問題。 handle 方法在任務被隊列處理的時候被調用,注意我們可以在任務的 handle 方法中對依賴進行類型提示。Laravel 服務容器會自動注入這些依賴。

2.2.1 出錯

如果任務被處理的時候拋出異常,則該任務將會被自動釋放回隊列以便再次嘗試執(zhí)行。任務會持續(xù)被釋放知道嘗試次數達到應用允許的最大次數。最大嘗試次數通過 Artisan 任務 queue:listenqueue:work 上的--tries 開關來定義。關于運行隊列監(jiān)聽器的更多信息可以在下面看到。

2.2.2 手動釋放任務

如果你想要手動釋放任務,生成的任務類中自帶的 InteractsWithQueue trait 提供了釋放隊列任務的 release 方法,該方法接收一個參數——同一個任務兩次運行之間的等待時間:

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

2.2.3 檢查嘗試運行次數

正如上面提到的,如果在任務處理期間發(fā)生異常,任務會自動釋放回隊列中,你可以通過 attempts 方法來檢查該任務已經嘗試運行次數:

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

3、推送任務到隊列

默認的 Laravel 控制器位于 app/Http/Controllers/Controller.php 并使用了 DispatchesJobs trait。該 trait 提供了一些允許你方便推送任務到隊列的方法,例如 dispatch 方法:

<?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{
    /**
     * 發(fā)送提醒郵件到指定用戶
     *
     * @param  Request  $request
     * @param  int  $id
     * @return Response
     */
    public function sendReminderEmail(Request $request, $id)
    {
        $user = User::findOrFail($id);

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

當然,有時候你想要從應用中路由或控制器之外的某些地方分發(fā)任務,因為這個原因,你可以在應用的任何類中包含 DispatchesJobs trait,從而獲取對分發(fā)方法的訪問,舉個例子,下面是使用該 trait 的示例類:

<?php

namespace App;

use Illuminate\Foundation\Bus\DispatchesJobs;

class ExampleClass{
    use DispatchesJobs;
}

為任務指定隊列 你還可以指定任務被發(fā)送到的隊列。 通過推送任務到不同隊列,你可以對隊列任務進行“分類”,甚至優(yōu)先考慮分配給多個隊列的 worker 數目。這并不會如隊列配置文件中定義的那樣將任務推送到不同隊列“連接”,而只是在單個連接中發(fā)送給特定隊列。要指定該隊列,使用任務實例上的 onQueue 方法,該方法有 Laravel 自帶的基類 App\Jobs\Job 提供:

<?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{
    /**
     * 發(fā)送提醒郵件到指定用戶
     *
     * @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);
    }
}

3.1 延遲任務

有時候你可能想要延遲隊列任務的執(zhí)行。例如,你可能想要將一個注冊 15 分鐘后給消費者發(fā)送提醒郵件的任務放到隊列中,可以通過使用任務類上的 delay 方法來實現,該方法由 Illuminate\Bus\Queueable trait 提供:

<?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{
    /**
     * 發(fā)送提醒郵件到指定用戶
     *
     * @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);
    }
}

在本例中,我們指定任務在隊列中開始執(zhí)行前延遲 60 秒。 注意:Amazon SQS 服務最大延遲時間是 15 分鐘。

3.2 從請求中分發(fā)任務

映射HTTP請求變量到任務中很常見,Laravel 提供了一些幫助函數讓這種實現變得簡單,而不用每次請求時手動執(zhí)行映射。讓我么看一下 DispatchesJobs trait 上的 dispatchFrom 方法。默認情況下,該 trait 包含在 Laravel 控制器基類中:

<?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方法的第三個參數。該數組用于填充所有請求中不存在的構造函數參數:

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

4、運行隊列監(jiān)聽器

開啟任務監(jiān)聽器 Laravel 包含了一個 Artisan 命令用來運行推送到隊列的新任務。你可以使用 queue:listen 命令運行監(jiān)聽器:

php artisan queue:listen

還可以指定監(jiān)聽器使用哪個隊列連接:

php artisan queue:listen connection

注意一旦任務開始后,將會持續(xù)運行直到手動停止。你可以使用一個過程監(jiān)視器如 Supervisor 來確保隊列監(jiān)聽器沒有停止運行。 隊列優(yōu)先級 你可以傳遞逗號分隔的隊列連接列表到 listen 任務來設置隊列優(yōu)先級:

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

在本例中,high隊列上的任務總是在從low 隊列移動任務之前被處理。 指定任務超時參數 你還可以設置每個任務允許運行的最大時間(以秒為單位):

php artisan queue:listen --timeout=60

指定隊列睡眠時間 此外,可以指定輪詢新任務之前的等待時間(以秒為單位):

php artisan queue:listen --sleep=5

需要注意的是隊列只會在隊列上沒有任務時“睡眠”,如果存在多個有效任務,該隊列會持續(xù)運行,從不睡眠。

4.1 Supervisor配置

Supervisor 為 Linux 操作系統(tǒng)提供的進程監(jiān)視器,將會在失敗時自動重啟 queue:listenqueue:work 命令,要在 Ubuntu 上安裝 Supervisor,使用如下命令:

sudo apt-get install supervisor

Supervisor 配置文件通常存放在/etc/supervisor/conf.d目錄,在該目錄中,可以創(chuàng)建多個配置文件指示 Supervisor 如何監(jiān)視進程,例如,讓我們創(chuàng)建一個開啟并監(jiān)視 queue:work 進程的 laravel-worker.conf 文件:

[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 進程并監(jiān)視它們,如果失敗的話自動重啟。配置文件創(chuàng)建好了之后,可以使用如下命令更新 Supervisor 配置并開啟進程:

sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start laravel-worker:*

要了解更多關于 Supervisor 的使用和配置,查看 Supervisor 文檔。此外,還可以使用 Laravel Forge 從 web 接口方便地自動配置和管理 Supervisor 配置。

4.2 后臺隊列監(jiān)聽器

Artisan 命令queue:work包含一個--daemon 選項來強制隊列 worker 持續(xù)處理任務而不必重新啟動框架。相較于 queue:listen 命令該命令對 CPU 的使用有明顯降低:

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 任務來查看所有有效選項。

4.2.1 后臺隊列監(jiān)聽器編碼考慮

后臺隊列 worker 在處理每個任務時不重啟框架,因此,你要在任務完成之前釋放資源,舉個例子,如果你在使用 GD 庫操作圖片,那么就在完成時使用 imagedestroy釋放內存。

類似的,數據庫連接應該在后臺長時間運行完成后斷開,你可以使用 DB::reconnect 方法確保獲取了一個新的連接。

4.3 部署后臺隊列監(jiān)聽器

由于后臺隊列 worker 是常駐進程,不重啟的話不會應用代碼中的更改,所以,最簡單的部署后臺隊列 worker 的方式是使用部署腳本重啟所有 worker,你可以通過在部署腳本中包含如下命令重啟所有 worker:

php artisan queue:restart

該命令會告訴所有隊列 worker 在完成當前任務處理后重啟以便沒有任務被遺漏。

注意:這個命令依賴于緩存系統(tǒng)重啟進度表,默認情況下,APC 在 CLI 任務中無法正常工作,如果你在使用 APC,需要在 APC 配置中添加 apc.enable_cli=1。

5、處理失敗任務

由于事情并不總是按照計劃發(fā)展,有時候你的隊列任務會失敗。別擔心,它發(fā)生在我們大多數人身上!Laravel 包含了一個方便的方式來指定任務最大嘗試執(zhí)行次數,任務執(zhí)行次數達到最大限制后,會被插入到 failed_jobs 表,失敗任務的名字可以通過配置文件 config/queue.php 來配置。

要創(chuàng)建一個 failed_jobs 表的遷移,可以使用 queue:failed-table 命令:

php artisan queue:failed-table

運行隊列監(jiān)聽器的時候,可以在 queue:listen 命令上使用--tries開關來指定任務最大可嘗試執(zhí)行次數:

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

5.1 失敗任務事件

如果你想要注冊一個隊列任務失敗時被調用的事件,可以使用 Queue::failing 方法,該事件通過郵件或 HipChat 通知團隊。舉個例子,我么可以在 Laravel 自帶的 AppServiceProvider 中附件一個回調到該事件:

<?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) {
            // Notify team of failing job...
        });
    }

    /**
     * 注冊服務提供者
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

5.1.1 任務類的失敗方法

想要更加細粒度的控制,可以在隊列任務類上直接定義 failed方法,從而允許你在失敗發(fā)生時執(zhí)行指定動作:

<?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;

    /**
     * 執(zhí)行任務
     *
     * @param  Mailer  $mailer
     * @return void
     */
    public function handle(Mailer $mailer)
    {
        //
    }

    /**
     * 處理失敗任務
     *
     * @return void
     */
    public function failed()
    {
        // Called when the job is failing...
    }
}

5.2 重試失敗任務

要查看已插入到 failed_jobs 數據表中的所有失敗任務,可以使用 Artisan 命令 queue:failed

php artisan queue:failed

該命令將會列出任務 ID,連接,對列和失敗時間,任務 ID 可用于重試失敗任務,例如,要重試一個 ID 為 5 的失敗任務,要用到下面的命令:

php artisan queue:retry 5

如果你要刪除一個失敗任務,可以使用 queue:forget 命令:

php artisan queue:forget 5

要刪除所有失敗任務,可以使用 queue:flush 命令:

php artisan queue:flush
上一篇:Laravel Elixir下一篇:關聯關系