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, Beanstalkd, Amazon SQS, Redis 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'); } }
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:
- Chúng ta phải giữ nguyên terminal chạy lệnh khởi động queue ở trạng thái mở.
- Có vẫn đề gì xảy ra, chúng ta phải khởi động lại bằng tay.
- 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
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'); }); } }
TestJob::dispatch('TESTER');
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é.