Hướng dẫn laravel queue

  • April 13, 2021
  • 4089

Laravel queue được sử dụng khi thực hiện các tác vụ có thể xử lí độc lập, công việc tốn nhiều thời gian, công việc lập lại sau 1 thời gian như: gửi email, callback api, push notification, generate QA code... Việc sử dụng queue giúp tăng tốc thời gian trả về kết quả của ứng dụng, tăng trải nghiệm người dùng...

Laravel Queue là gì?

Laravel queue(hàng đợi) có thể hiểu là một danh sách các công việc(job) được thực hiện tuần tự, Laravel đã xây dựng sẵn các hàm giúp chúng ta có thể tạo nhiều queue khác nhau, thêm các job vào queue, thực hiện job sau một thời gian xác định, thực hiện lại job khi có lỗi xảy ra...

Cấu hình laravel queue

Các cấu hình laravel queue được lưu trong file config/queue.php khai báo QUEUE_CONNECTION ở file .env để kết nối với các queue driver đã được định nghĩa sẵn trong file config


// Queue mặc định sử dụng ở local, các job sẽ được xử lí ngay lặp tức
QUEUE_CONNECTION=sync
// Sử dụng database queue
QUEUE_CONNECTION=database
// Sử dụng redis queue
QUEUE_CONNECTION=redis
// Bỏ qua queue
QUEUE_CONNECTION=null

2 queue drive hay được sử dụng nhất là database và redis.


Các yêu cầu khi sử dụng queue

1. Database queue

Để sử dụng database queue cần tạo bảng để quản lý. Laravel đã cung cấp sẵn các command để thực hiện điều này


// Tạo ra các file migration
php artisan queue:table
php artisan queue:failed-table
// Tạo bảng để quản lý job
php artisan migrate

Khi chạy 2 lệnh trên laravel sẽ tạo ra 2 bảng: jobs quản lí các job, failed_jobs quản lí các job bị false


// Jobs table
+--------------+---------------------+------+-----+---------+----------------+
| Field        | Type                | Null | Key | Default | Extra          |
+--------------+---------------------+------+-----+---------+----------------+
| id           | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| queue        | varchar(191)        | NO   | MUL | NULL    |                |
| payload      | longtext            | NO   |     | NULL    |                |
| attempts     | tinyint(3) unsigned | NO   |     | NULL    |                |
| reserved_at  | int(10) unsigned    | YES  |     | NULL    |                |
| available_at | int(10) unsigned    | NO   |     | NULL    |                |
| created_at   | int(10) unsigned    | NO   |     | NULL    |                |
+--------------+---------------------+------+-----+---------+----------------+

// Giải thích các columnns // queue: tên của queue, queue mặc định là default // payload: các job được thực thi dựa vào payload // attempts: default là 0, mỗi một lần job chạy lại thì tăng 1, dùng để xử lí max retry job // reserved_at: thời gian job chạy, đánh dấu là job đang được chạy // available_at: thời gian job sẽ được chạy // Failed_job table +------------+---------------------+------+-----+-------------------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+---------------------+------+-----+-------------------+----------------+ | id | bigint(20) unsigned | NO | PRI | NULL | auto_increment | | connection | text | NO | | NULL | | | queue | text | NO | | NULL | | | payload | longtext | NO | | NULL | | | exception | longtext | NO | | NULL | | | failed_at | timestamp | NO | | CURRENT_TIMESTAMP | | +------------+---------------------+------+-----+-------------------+----------------+ // Giải thích các columns // connection: quản lí job conncetion có thể là databas, redis ... // queue: tên queue // payload: các job được thực thi dựa vào payload // exception: lí do job lỗi // failed_at: thời gian job bị lỗi

2. Redis queue

Khi sử dụng redis cần phải cái đặt package predis/predis ~1.0 hoặc cài đặt phpredis PHP extension

Tạo một job

Mặc định các job được tạo ra trong thư mục app/Jobs để tạo 1 job tự động sử dụng command


php artisan make:job SendEmail

Các job đều phải implements Illuminate\Contracts\Queue\ShouldQueue điều này giúp laravel biết rằng job này sẽ được đẩy vào queue thay vì xử lí đồng bộ.

Cấu trúc của 1 job


<?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;
ues App\Model\User;
use App\Mail\UserEmail;
class SendEmail implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $user;
     
    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct(User $user)
    {
        $this->user = $user;
    }

    /**
     * Execute the job.
* * @return void. */ public function handle(UserEmail $userMail) { Mail::to($this->user->email)->send($userMail); } }

Ở ví dụ này, chúng ta có thể truyền vào một Eloquent model trực tiếp vào trong hàm khởi tạo của Job. Vì có trait SerializesModels trong Job class, nên các Eloquent model sẽ được serialize và unserialize khi job được xử lý. Nếu queue job nhận một model vào hàm khởi tạo, thì chỉ có id của model sẽ được serialize vào trong queue. Khi job thực sự được xử lý, hệ thống queue sẽ tự động lấy ra model đầy đủ từ database. Nó giúp ngăn chặn các vấn đề phát sinh từ việc serialize nguyên một Eloquent model.

Hàm handle được gọi khi job được queue xử lý. Chú ý chúng ta có thể type-hint dependency trong hàm handle của job. Laravel service container sẽ tự động nhúng các dependency tương ứng vào.

Đưa job vào trong queue

Dùng helper dispatch() để add một job vào queue


use App\Jobs\SendEmail;

$user = User::findOrFail($id);
// Job sẽ được thêm vào queue default
dispatch(new SendEmail($user));

Thêm job vào một queue xác định, sử dụng hàm onQueue()


use App\Jobs\SendEmail;

$user = User::findOrFail($id);
// Job sẽ được thêm vào queue emails
$job = (new SendEmail($user))->onQueue('emails');
dispatch($job);

Delay job, thiết lập job chạy sau một thời gian xác định


use App\Jobs\SendEmail;

$user = User::findOrFail($id);
// Job sẽ được xử lí sau 2 ngày, kể từ khi nó được dispatch
$job = (new SendEmail($user))->delay(now()->addDays(2));
dispatch($job);

Thực thi các job trong queue

Thực thi các job trong queue bằng command


// Chạy các job trong queue defalut
php artisan queue:work

// Chạy các job trong queue xác định
php artisan queue:work --queue=email

// Chạy các job trong nhiều queue
php artisan queue:work --queue=default,email

// Giới hạn thời gian mỗi job được phép chạy
php artisan queue:work --timeout=60

// Giới hạn số lần chạy lại của job, nếu không có thiết lập này, mà job bị failed thì nó sẽ bị thử lại mãi khônng dừng, setting số lần thử lại là 3
php artisan queue:work --retries=3

// Thiết lập thời gian nghỉ trước khi thực hiện job mới
php artisan queue:work --sleep=3

// Gộp các chỉ thị lại
php artisan queue:work --retries=3  --sleep=3 --timeout=60

Chú ý, lệnh php artisan:queue work khi đã thực hiện sẽ chạy cho đến khi đóng cửa sổ dòng lệnh hoặc dừng nó bằng lệnh php artisan queue:restart. Queue worker là các tiến trình có thời gian sống dài do đó nó sẽ không cập nhật code khi có thay đổi, khi thay đổi code chương trình, bạn cần khởi động lại queue worker bằng câu lệnh


php artisan queue:restart

Khi chạy lệnh này, các queue worker sẽ thoát khi thực hiện hết công việc của mình, đảm bảo không job nào bị mất.

Giám sát queue job với supervisor

Khi sử dụng queue bạn cần phải giữ cho queue:work processes luôn được chạy. Các queue:work processes có thể bị dừng lại vì nhiều nguyên nhân khác nhau, ví dụ worker bị timeout, chạy lệnh php artisan queue:restart. Vì vậy cần cài đặt supervisor để giám sát queue:work processes nếu nó bị dừng, supervisor sẽ tự khởi động lại nó

Cài đặt supervisor

Cài đặt supervisor hệ điều hành linux


sudo apt-get install supervisor

Cấu hình supervisor

Các file config supervisor được lưu trữ trong thư mục /etc/supervisor/conf.d bạn có thể tạo thêm bất kì file config nào trong thư mục này, để supervisor giám sát các process bạn muốn. Tạo laravel-worker.conf để giám sát queue:work processes


[program:api-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/html/demo/artisan queue:work --sleep=3 --tries=3 --timeout=3600
autostart=true
autorestart=true
user=ubuntu
numprocs=4
redirect_stderr=true
stdout_logfile=/var/www/html/demo/storage/logs/worker.log

Khởi động supervisor


// Read config
sudo supervisorctl reread

// Load config
sudo supervisorctl update

// Sart supervisor
sudo supervisorctl start api-worker:*

// Check supervisor
sudo supervisorctl status
// Kết quả như bên này là đã cài đặt xong supervisor :D
api-worker:api-worker_00   RUNNING   pid 11148, uptime 2 days, 13:09:34
api-worker:api-worker_01   RUNNING   pid 11146, uptime 2 days, 13:09:34
api-worker:api-worker_02   RUNNING   pid 11147, uptime 2 days, 13:09:34
api-worker:api-worker_03   RUNNING   pid 11171, uptime 2 days, 13:09:33

Xử lí thế nào khi job bị lỗi?

Laravel đã cung cấp sẵn các command giúp bạn liệt kê, retry, xoá các failed job, các failed job được quản lí trong bảng failed_jobs


// Get all job failed
php artisan queue:failed

// Retry job failed được lưu trong bảng failed_jobs có id là 6
php artisan queue:retry 5

// Retries nhiều job: failed_jobs id là 5,6
php artisan queue:retry 5, 6

// Retry all failed job
php artisan queue:retry all

// Xoá failed job by id
php artisan queue:forget 5

// Xoá all failed job
php artisan queue:flush

Cảm ơn các bạn đã dành thời gian để đọc bài viết này. Hi vọng bài viết có ích cho các bạn khi làm việc với queue job sử dụng laravel!