Text of Relipa

Thử sử dụng Queues của Laravel

Trong quá trình xây dựng một ứng dụng web, chúng ta thường không thể tránh khỏi việc phải giải quyết những tác vụ gây tốn thời gian như gửi mail, giao tiếp với API của một bên thứ ba,… Nếu bắt người dùng phải chờ khi các tác vụ này hoàn thành mới thấy được phản hồi thì chắc chắn sẽ gây ảnh hưởng xấu đến trải nghiệm của người dùng. Với những ngôn ngữ mà bản chất là bất đồng bộ như Javascript, đây không phải vấn đề gì đáng lưu tâm, nhưng với những ngôn ngữ đồng bộ như PHP, bắt buộc chúng ta phải sử dụng thêm một công cụ hỗ trợ bên ngoài.

Laravel đã tích hợp sẵn giải pháp Queues bên trong framework, có thể đáp ứng tốt trong trường hợp nhu cầu của chúng ta không quá phức tạp.

1. Config để sử dụng với database

Chúng ta có thể config việc sử dụng queue trong laravel ở file config/queue.php. Trong file, đơn vị dùng để định nghĩa là connection. Trong mỗi connection, Laravel hỗ trợ việc sử dụng Queues từ nhiều driver khác nhau:  database, BeanstalkdAmazon SQSRedis hay là sync trong trường hợp ta không muốn đưa tác vụ vào một queue trung gian nào, mà sẽ thực hiện ngay.

Trong bài viết này, chúng ta sẽ thử dùng database để thiết lập queue:

<?php

return [

  'default'=> env('QUEUE_CONNECTION', 'database'), //thiết lập connection default được lấy từ biến QUEUE_CONNECTION trong file .env

  'connections'=> [
    'sync'=> [ // định nghĩa connection tên sync
      'driver'=>'sync', //sử dụng driver là sync
    ],


    'database'=> [ // định nghĩa connection tên database
      'driver'=>'database', //sử dụng driver là database
      'table'=>'jobs', // định nghĩa bảng tên jobs để lưu trữ thông tin tác vụ cần xử lý, 
      'queue'=>'default', // tên của queue, để default thì khi không chỉ định cụ thể trong code, các tác vụ sẽ tự động sử dụng queue này. Có thể ứng dụng trong các trường hợp như đặt ra nhiều tên queue khác nhau ứng với mỗi độ ưu tiên khác nhau,...
      'retry_after'=>90, // đặt lượng thời gian delay cho đến lúc retry (thực hiện lại), nếu tác vụ thất bại.
    ],
  ],

  'failed'=> [ // định nghĩa bảng failed_jobs để lưu lại thông tin những tác vụ khi xử lý gặp thất bại
    'database'=> env('DB_CONNECTION', 'mysql'),
    'table'=>'failed_jobs',
  ],
];

 

2. Tạo migration cho các bảng dữ liệu phục vụ queue

Tiếp, chúng ta sẽ tạo các file migrations để sinh ra các table trong database phục vụ cho queue. Chúng ta chạy lệnh

php artisan queue:table

Kiểm tra trong thư mục database/migrations, ta thấy hai file đã được tạo. File thứ nhất là xxx_create_jobs_table, định nghĩa bảng jobs để lưu trữ các tác vụ được đưa xuống queue

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateJobsTable extends Migration
{

  /**
  * Run the migrations.
  *
  * @return void
  */

  public function up()
  {
    Schema::create('jobs', function (Blueprint $table) {
      $table->bigIncrements('id');
      $table->string('queue')->index();
      $table->longText('payload');
      $table->unsignedInteger('attempts');
      $table->unsignedInteger('reserved_at')->nullable();
      $table->unsignedInteger('available_at');
      $table->unsignedInteger('created_at');
  });
}

  /**
  * Reverse the migrations.
  *
  * @return void
  */

  public function down()
  {
    Schema::dropIfExists('jobs');
  }
}

File thứ hai là xxx_create_failed_jobs_table, định nghĩa bảng failed_jobs để lưu trữ các tác vụ lấy từ queue ra bị lỗi khi thực hiện

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateFailedJobsTable extends Migration
{

  /**
  * Run the migrations.
  *
  * @return void
  */

  public function up()
  {
    Schema::create('failed_jobs', function (Blueprint $table) {
      $table->bigIncrements('id');
      $table->text('connection');
      $table->text('queue');
      $table->longText('payload');
      $table->longText('exception');
      $table->timestamp('failed_at')->useCurrent();
    });
  }

  /**
  * Reverse the migrations.
  *
  * @return void
  */

  public function down()
  {
    Schema::dropIfExists('failed_jobs');
  }
}
Sau đó, chúng ta chỉ cần chạy lệnh migrate là cả hai bảng trên sẽ được sinh ra trong database
php artisan migrate

3.  Cài đặt supervisord để quản lý các queue worker

Sau khi thiết lập xong môi trường kết nối với cơ sở dữ liệu, chỉ cần vào thư mục dự án và chạy câu lệnh:

php artisan queue:work

là ta đã khởi động xong queue woker để đợi những tác vụ cần xử lý. Tuy nhiên việc chạy câu lệnh trực tiếp bằng tay như vậy có mấy điểm bất lợi như sau:

  1. Chúng ta phải giữ nguyên terminal chạy lệnh khởi động queue ở trạng thái mở.
  2. Có vẫn đề gì xảy ra, chúng ta phải khởi động lại bằng tay.
  3. Khó kiểm soát khi hệ thống chúng ta cần chạy nhiều tác vụ ngầm

Để tiện lợi hơn cho việc vận hành, chúng ta nên dùng một tool hỗ trợ cho việc quan sát và điều khiển những process đang chạy trong hệ thống. Trong bài viết này, chúng ta sẽ thử sử dụng supervisord trong môi trường CentOS 7.

Đầu tiên là chạy lệnh cài đặt supervisord:

yum install supervisor

Tiếp theo dùng lệnh vi để tạo file config:

sudo vi /etc/supervisor.d/laravel-worker.ini

File config sẽ có nội dung như sau:

[program:laravel-worker]  ; Đặt tên cho program chúng ta muốn config là laravel-worker
process_name=%(program_name)s_%(process_num)02d  ; Đặt tên cho process bằng cách đính số thứ tự process với tên của program
command=php /var/www/test/artisan queue:work  ; Câu lệnh khởi động queue từ thư mục của dự án: /var/www/test 
autostart=true  ; Đặt là true => Nếu khởi động supervisord  thì các process của program cũng được tự khởi động theo
autorestart=true ; Đặt là true => Nếu khởi động lại superviosrd ,thì các process của program có trạng thái running cũng được khởi động theo
user=apache ; User dùng để chạy queue là apache
priority=1 ; Độ ưu tiên của program. Nếu chỉ số này bé thì khi chạy lênh khởi động tất cả program, nó sẽ được khởi động trước và khi chạy lệnh tắt tất cả program, nó sẽ được tắt sau.
numprocs=4 ; Số lượng process sinh ra cho program này là 4
redirect_stderr=true ; Log khi lỗi và log khi hoạt động bình thường đều được đưa ra cùng một file
stdout_logfile=/var/www/test/worker.log ; Đường dẫn cùa file log

Sau khi tạo xong file config, ta chạy các lệnh để khởi động supervisord:

sudo systemctl enable supervisord
sudo systemctl restart supervisord

Kiểm tra xem sau khi khởi động supervisord, các procress của program chúng ta vừa định nghĩa có khởi động theo không:

sudo supervisorctl

Chúng ta thấy bốn process đều đang ở trạng thái running:

 Các trạng thái mà một process của supervisord có thể có là:

STOPPED (0): Process đó đã được dừng hoặc chưa từng được khởi động

STARTING (10): Process được khởi động

RUNNING (20): Process đang chạy

BACKOFF (30): Process được khởi động nhưng bị thoát khỏi quá nhanh trước khi vào trạng thái RUNNING.

STOPPING (40): Process đang được dừng

EXITED (100): Process thoái khỏi từ trạng thái RUNNING.

FATAL (200): Process bị lỗi không thể khởi tạo thành công.

UNKNOWN (1000): Trạng thái chưa xác định

Ta có sơ đồ di chuyển các trạng thái của process trong supervisord

Sơ đồ di chuyển các trạng thái của process trong supervisord

4. Thử sử dụng Queues với tác vụ gửi mail

Như vậy chúng ta sẽ thiết lập xong Queues trong Laravel. Giờ chúng ta sẽ thử viết một tác vụ gửi mail đơn giản để thử nghiệm với queues mà chúng ta vừa thiết lập.

Đầu tiên chúng ta sẽ thêm (hoặc sửa) hai thông tin sau trong file .env nằm ở thư mục của dự án Laravel:

MAIL_DRIVER=sendmail // Config sử dụng driver sendmail để gửi mail. Cách cài đặt cho sendmail xin phép không đề cập ở bài blog này. Các bạn có thể tham khảo ở đây
QUEUE_CONNECTION=database // Config sử dụng database cho chức năng queues

Tiếp, chúng ta sẽ tạo một class thực thi interface ShouldQueue

<?php

namespace App\Jobs;

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

class TestJob implements ShouldQueue
{

  use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
  protected $textMail;

  /**
  * @return void
  */
  publicfunction __construct($text)
  {
      $this->textMail = $text;
  }

  /**
  * Execute the job.
  *
  * @return void
  */

  publicfunction handle()
  {
    \Mail::raw('Hello', function($message){
        $message->subject('Hello '.$this->textMail)->to('123456@relipasoft.com');
    });
  }
}
Để trở thành một tác vụ có thể đưa vào Queue, ngoài interface ShouldQueue, chúng ta cùng cần dùng thêm các trait Dispatchable, InteractsWithQueue, Queueable,SerializesModels. Nội dung của tác vụ phía trên rất đơn giản, ta chỉ nhận từ lúc khởi tạo một chuỗi string, gán chuỗi đó vào tiêu đề rồi gửi mail đi.
Ở phía Controller, để tạo một thực thể (instance) của tác vụ rồi đưa vào queue, chúng ta gọi đến hàm dispatch:
TestJob::dispatch('TESTER');
Gửi request tới Controller đó, chúng ta sẽ nhận được mail tới; kiểm tra log của supervisord, sẽ thấy dòng thông báo tác vụ hoàn thành trong đó:

Như vậy, bài viết đã trình bày xong cách thiết lập Queues trong Laravel. Hãy tìm cách sử dụng nó trong các ứng dụng web của mình để làm hài lòng khách hàng hơn nhé.