Tiếp tục series về Laravel 9, cùng mình tìm hiểu và làm việc cùng queue. Tài liệu Laravel ghi rất đầy đủ tuy nhiên đối với anh em mới tiếp cận thì cần một góc nhìn đơn giản hơn về Queue sau đó mới có thể hiểu vào chi tiết trên documentation của Laravel.
Queue Laravel là gì?
Queue trong Laravel hay còn gọi là hàng đợi, nơi chứa các công việc (jobs) cần xử lý. Queue thực chất là một chuỗi các tác vụ (Jobs) được lưu trữ và xử lý ngầm theo thứ tự FIFO (first in first out).
Mục đích hiểu đơn giản để tối ưu thời gian phản hồi của ứng dụng để người dùng không phải đợi lâu.
Giả sử người dùng submit một yêu cầu nhưng vấn đề này cần tốn thời gian rất lâu để xử lý thì chúng ta có thể phản hồi người dùng trước rồi các tác vụ đó sẽ được chạy ngầm và xử lý sau.
Đặc điểm của Queue
- Dữ liệu các Jobs sẽ được lưu trữ vào các cơ sở dữ liệu như database, Redis, AWS ...
- Các jobs sẽ được chạy ngầm bên trong ứng dụng web
- Jobs nào được lưu trữ trước thì sẽ được thực hiện trước
Ở đây mình sẽ trình bày phạm vi jobs được lưu trữ trong databases. Các bước thực hiện và các lưu ý sẽ được đề cập bên dưới
Cấu hình và khởi tạo queue
Đầu tiên để tạo queue, chúng ta vào file .env của dự án thay đổi QUEUE_CONNECTTION
QUEUE_CONNECTION=database
Tiếp theo là tạo table để lưu trữ các jobs
php artisan queue:table
php artisan migrate
Xóa cache config khi chỉnh sửa file .env
php artisan config:cache
Tạo job và lưu trữ Job vào Queue
Khác với những example hay ví dụ về việc gửi mail. Ở đây mình sẽ lấy ví dụ về dùng queue để thực hiện việc zip một file trong máy cho cụ thể dễ hiểu nhé.
Sau khi chuẩn bị xong database để lưu trữ các jobs thì chúng ta thực hiện tạo job bằng command.
php artisan make:job ZipFileJob
Sau khi chạy lệnh trên, anh em sẽ thấy một file ZipFileJob.php được tạo ra tại mục App/Jobs. Trong file này sẽ tạo sẵn 2 method là __constructor và handle
- __constructor(): phương thức khởi tạo của job. Dùng để truyền các tham số khởi tạo, thường dùng Dependency Injection để truyền.
- handle(): method này là xử lý chính việc thực thi của job
Giả sử ở đây mình truyền vào ngày zip của file từ một Service bên ngoài vào thì ở constructor làm như sau:
class ZipFileJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private $date;
public function __construct($date)
{
$this->date = $date;
}
}
Tại phương thức handle() có thể truyền vào một service như sau:
/** * Execute the job. * * @return void */
public function handle(DataService $data_service) {
try {
$data_service->ZipFiles($this->date);
} catch(\Throwable $exception) {
if ($this->attempts() > 2) { throw $exception; }
}
}
Tạo một DataService có method ZipFiles() như bên dưới. Method này có chức năng zip các file trong một folder cụ thể. Code minh họa như sau:
public function ZipFiles(string $date)
{
//tên file zip
$zip_name = $date;
$path_folder = "/Users/lanhvo/Desktop";
//kiểm tra OS hiện tại là Windows
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
$all_files = Storage::allFiles($path_folder);
$zip_file = new ZipArchive();
if ($zip_file->open($path_folder . $zip_name, ZipArchive::CREATE) === true) {
foreach ($all_files as $file) {
$zip_file->addFile($path_folder . basename($file), basename($file));
}
$zip_file->close();
} else {
throw new \Exception("Error zip");
}
} else {
//với linux và mac chạy lệnh dưới
exec("cd $path_folder && zip {$zip_name} *");
}
}
Để thêm một job vào queue ta dùng helper dispatch hoặc có thể gọi thông qua Fancades.
Copy dòng bên dưới ở Controller hoặc Service, khi dòng này được thực thi thì một job sẽ được lưu trữ vào database.
dispatch(new ZipFileJob(Date('Ymd'));
Sau khi job được lưu trữ vào db thì vẫn chưa chạy nhé. Ở localhost, để thực hiện jobs chạy lệnh:
dispatch(new ZipFileJob(Date('Ymd'));
Xử lý jobs bị failed
Trường hợp khi chạy bị fail để kiểm tra lỗi thì có nhiều cách. Đầu tiên có thể kiểm tra bằng database lưu trữ các jobs bị fail. Chạy migrate bên dưới để tạo thêm bảng lưu jobs bị fail.
php artisan queue:failed-table
php artisan migrate
Lúc này khi chạy queue bị fail thì vào table failed_jobs trong db sẽ có lưu lỗi cụ thể. Hoặc cũng có thể dùng method failed() để log ra lỗi vào file laravel.log.
public function failed(\Throwable $exception) {
Log::error("Lỗi: ". $exception);
}
Một số lỗi thường gặp khi chạy queue
MaxAttemptsExceededException:
has been attempted too many times or run too long. The job may have previously timed out
Xảy ra khi một công việc hết thời gian chờ hoặc khi số lần thử công việc vượt quá giá trị đã đặt.
Điều kiện mà ngoại lệ này xảy ra
Ba tham số sau có liên quan đến ngoại lệ này.
- timeout
- retry_after
- tries
Nếu bất kỳ tham số nào trong 3 tham số trên cấu hình không chính xác sẽ gặp lỗi trên và job sẽ bị trường hợp killed.
class ExampleJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, Notifiable;
public $timeout = 1; // Thời gian chờ, đơn vị giây
public $tries = 1; // Số lần thử lại khi job bị fail
...
}
Hoặc khi chạy command:
$ php artisan queue:work --timeout=1 --tries=1
retry_after được cấu hình tại config/queue.php
//config/queue.php
'connections' => [
...
'database' => [
'driver' => 'database',
'table' => 'jobs',
'queue' => 'default',
'retry_after' => 1,
],
...
],
Cần lưu ý một số vấn đề sau:
- Nếu bạn đặt thời gian chờ, bạn cũng phải đặt retry_ after
- Mặc định tham số timeout sẽ có giá trị là 60s.
Giá trị --timeout phải ngắn hơn cài đặt retry_ after ít nhất vài giây. Điều này đảm bảo rằng job đang xử lý công việc đã cho sẽ bị giết trước khi công việc được thử lại.
Nếu bạn đặt tùy chọn --timeout lâu hơn cài đặt retry_ after, công việc sẽ chạy hai lần.
Trường hợp nếu không cài đặt timeout thì ứng dụng sẽ không có đủ thời gian xử lý và sẽ bị killed trước khi job hiện trạng thái processed.
Nếu bạn không đặt retry_ after lâu hơn thời gian chờ, công việc có thể vô tình được thử lại, dẫn đến MaxAttemptsExceededException.
Lấy ví dụ bạn cài đặt timeout = 120 thì cần vào config/queue.php thay đổi retry_after > 120 thì sẽ hết lỗi/
- Nếu tries = 0, phương thức failed() trong class Job sẽ không hoạt động và không thể ném ra được ngoại lệ, do vậy nên đặt giá trị của tries là 1 hoặc nhiều hơn.
Ngoài ra còn một số vấn đề khác, tuy nhiên ở đây mình chỉ lưu lại một số vấn đề gặp phải khi lần đầu làm việc với queue, nếu có vấn đề cần đóng góp vui lòng để lại comment nhé!
bài viết rất hữu ích cho người mới
May mà xem bài viết fix được bug 😀
Thanks man, ae chỉ cần thả 1 comment là có động lực viết bài quá ^^