post-image

Laravel Queue xử lý công việc kiểu hàng đợi

Tổng quan

Hàng đợi cho phép bạn trì hoãn một công việc mất nhiều thời gian đến một thời điểm nào nó mới xử lý. Laravel cung cấp một API thống nhất cho rất nhiều các hàng đợi ở backend khác nhau. Để hiểu nhanh các khái niệm mới chúng ta hãy bắt đầu bằng ví dụ: Bạn hãy tưởng tượng website của bạn có nhiều người vào xem và có thể đăng ký tài khoản, người đó đang ở trang đăng ký và nhập các thông tin vào form đăng ký. Khi đó chúng ta muốn thực hiện các công việc sau:

  • Kiểm tra thông tin nhập và lưu vào CSDL.
  • Gửi một email Chào mừng đến thành viên mới này.
  • Trả về trang Thankyou.

Sau khi kiểm tra và lưu thông tin vào CSDL, tiếp tục đến phần việc gửi email do mã PHP thực hiện tuần tự từ trên xuống. Người dùng sẽ thấy trang Thankyou nếu email đã được gửi đi, quá trình gửi email có thể nhanh nhưng cũng có thể mất thời gian, vậy tại sao phải bắt người dùng chờ? Laravel Queue sẽ là cứu cánh cho tình huống này.

Laravel Queue là gì?

Một hàng đợi (queue) là một danh sách những việc cần làm (job) được quản lý theo thứ tự. Khi chúng ta muốn thêm một công việc (job) vào hàng đợi, job phải implement interface Illuminate\Contracts\Queue\ShouldQueue. Laravel Queue driver được sử dụng để quản lý các job như thêm job vào hàng đợi, lấy job ra khỏi hàng đợi.

Laravel có thể làm việc với nhiều các driver khác nhau như database, Redis, Amazon SQS… và bạn có thể tự tạo riêng một driver nếu muốn. Trong bài viết này, chúng ta sẽ lưu trữ các job trong database, để thực hiện việc này, thực hiện các câu lệnh artisan để tạo ra các bảng lưu trữ trong database như sau:

c:\xampp\htdocs\laravel-test>php artisan queue:table Migration created successfully! c:\xampp\htdocs\laravel-test>php artisan queue:failed-table Migration created successfully! c:\xampp\htdocs\laravel-test>php artisan migrate Migrated: 2017_04_03_144759_create_jobs_table Migrated: 2017_04_03_150557_create_failed_jobs_table
Code language: CSS (css)

Các câu lệnh này sẽ tạo ra bảng jobs và failed_jobs trong cơ sở dữ liệu. Ngoài ra, cần phải thiết lập

QUEUE_DRIVER=database

trong file cấu hình biến môi trường .env ở thư mục gốc project.

Tạo và thêm Job vào queue

Mặc định, các job được lưu trong app\Jobs, nếu thư mục app\Jobs không có trong project bạn cũng đừng lo, câu lệnh tạo job sẽ tự động tạo ra thư mục này nếu chưa có. Thực hiện tạo một job mới bằng câu lệnh:

php artisan make:job SendWelcomeEmail
Code language: CSS (css)

Nó sẽ tự động sinh ra job SendWelcomeEmail được implement interface Illuminate\Contracts\Queue\ShouldQueue. Class này chứa phương thức handle() sẽ được gọi đến khi job được xử lý trong hàng đợi. Chúng ta hãy xem khung của một job:

<?php namespace App\Jobs; use App\User; use App\AudioProcessor; use Illuminate\Bus\Queueable; use Illuminate\Queue\SerializesModels; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; class SendWelcome Email implements ShouldQueue { use InteractsWithQueue, Queueable, SerializesModels; protected $user; /** * Create a new job instance. * * @param User $user * @return void */ public function __construct(User $user) { $this->user = $user; } /** * Execute the job. * * @param AudioProcessor $processor * @return void */ public function handle(AudioProcessor $processor) { // Process uploaded user... } }
Code language: HTML, XML (xml)

Trong ví dụ trên chúng ta truyền một Eloquent Model User vào phương thức contruct của job. Để thêm job vào một queue, sử dụng phương thức dispatch():

<?php namespace App\Http\Controllers\Auth; use App\User; use App\Http\Controllers\Controller; use Illuminate\Support\Facades\Validator; use Illuminate\Foundation\Auth\RegistersUsers; use App\Jobs\SendWelcomeEmail; class RegisterController extends Controller { ... protected function create(array $data) { $user = User::create([ 'name' => $data['name'], 'email' => $data['email'], 'password' => bcrypt($data['password']), ]); $job = (new SendWelcomeEmail($user))->delay(Carbon::now()->addMinutes(10)); dispatch($job); return $user; } }
Code language: HTML, XML (xml)

Phương thức delay() sẽ dừng lại trước khi thực hiện job trong queue.

Thực thi các jobs trong queue

Số lần thử thực hiện job

Mặc định job sẽ được thực hiện 1 lần, nếu lỗi sẽ được bỏ qua, để thiết lập số lần thử thực hiện lại một job chúng ta có hai cách: hoặc sử dụng câu lệnh artisan cho tất cả các job

php artisan queue:work --tries=3

hoặc đưa vào thuộc tính $tries của từng job

<?php namespace App\Jobs; class SendWelcome implements ShouldQueue { /** * Số lần job sẽ thử thực hiện lại * * @var int */ public $tries = 3; }
Code language: HTML, XML (xml)

Thiết lập thời gian timeout của job trong queue

Bạn có thể thiết lập thời gian timeout của các job bằng cách sử dụng câu lệnh artisan

php artisan queue:work --timeout=60

hoặc thiết lập trong thuộc tính $timeout của từng job

<?php namespace App\Jobs; class SendWelcome implements ShouldQueue { /** * Số giây job có thể chạy trước khi timeout * * @var int */ public $timeout = 60; }
Code language: HTML, XML (xml)

Queue worker, thực thi các job trong queue

Laravel có một queue worker để thực thi các job đang có trong hàng đợi, bạn có thể chạy worker này bằng câu lệnh artisan:

php artisan queue:work
Code language: CSS (css)

Chú ý, câu lệnh này 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 một câu lệnh. 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 bạn 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
Code language: CSS (css)

Thiết lập thời gian nghỉ giữa các lần xử lý job

Các job trong hàng đợi được xử lý liên tục mà không có sự dừng lại nào, tùy chọn sleep sẽ xác định worker dừng lại sau bao lâu trước khi tiếp tục xử lý job tiếp theo:

php artisan queue:work --sleep=3

Sử dụng Supervisor giám sát xử lý hàng đợi trên Linux

Supervisor là một chương trình giám sát các xử lý trong hệ điều hành Linux, nó sẽ tự động khởi động lại xử lý queue:work nếu bị lỗi. Để cài đặt supervisor trên CentOS trước hết phải cài đặt python

$ yum install python-setuptools $ easy_install supervisor

Cài đặt supervisor trên Ubuntu

#sudo apt-get install supervisor
Code language: CSS (css)

Supervisor có file cấu hình nằm trong thư mục //etc/supervisor/conf.d, trong này bạn có thể tạo nhiều file để bắt supervisor giám sát các xử lý. Ví dụ tạo file laravel-worker.conf để giám sát cho queue:worker với nội dung:

[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 autostart=true autorestart=true user=forge numprocs=8 redirect_stderr=true stdout_logfile=/home/forge/app.com/worker.log
Code language: JavaScript (javascript)

numprocs = 8 tức là Supervisor sẽ chạy 8 xử lý queue:work cùng lúc và tự động khởi động lại queue:work khi gặp lỗi. Sau khi thiết lập cấu hình Supervisor xong, bạn phải cập nhật cấu hình và chạy các xử lý bằng các câu lệnh như sau:

$supervisorctl reread $supervisorctl update $supervisorctl start laravel-worker:*
Code language: PHP (php)

Giám sát xử lý hàng đợi trên Windows

Công cụ supervisor chỉ hoạt động trong môi trường Linux, vậy trên Windows chúng ta sẽ xử lý như thế nào. Chúng ta sẽ sử dụng gói Forever, cài đặt Forever bằng npm. npm được tích hợp sẵn trong bộ cài Node.js, bạn tải về và cài đặt. Sau đó cài đặt Forever:

npm install -g forever

Sau khi cài đặt Forever, chúng ta có thể dùng nó để giám sát các xử lý

forever -c php artisan --queue:work --tries=3 --timeout=60 --sleep=5

Laravel Queue trong bài toán xử lý song song

Trong lập trình, hàng đợi giúp cho việc thiết kế các ứng dụng tốt hơn trong hiệu năng xử lý. Chúng ta đến với ví dụ thực tế sau, trong hệ thống corebanking của ngân hàng, cuối ngày sẽ thực hiện chạy khóa ngày COB (Close Of Bussiness), quá trình này là một tập hợp (các batch) rất nhiều các tác vụ khác nhau như cập nhật thông tin hệ thống, tính toán lãi tiền gửi, tiền vay, chuyển trạng thái các hợp đồng, gửi tin nhắn đến khách hàng và cập nhật thông tin các bảng cần thiết cho ngày mới như bảng lãi suất, tỉ giá…

Nếu tất cả các công việc này được thực hiện tuần tự, quá trình COB sẽ mất rất nhiều thời gian mà hệ thống corebanking chỉ được phép chạy trong khoảng 3-5 tiếng do cần phải có thời gian dự phòng.

Vậy giải pháp nào cho những vấn đề tương tự như vậy? Sử dụng hàng đợi sẽ giúp xử lý những vấn đề như trên. Chúng ta sẽ chia các công việc ra thành các Batch, trong mỗi Batch chứa các job khác nhau liên quan đến một bussiness logic nào đó. Khi đó, chúng ta thực hiện chạy nhiều queue:work sẽ giúp tăng tốc độ lên đáng kể. Trong ví dụ về sử dụng Supervisor, giả sử cấu hình máy chủ có thể chạy được 30 session một lúc, nếu chúng ta để

numprocs=30

tốc độ xử lý sẽ nhanh hơn rất nhiều lần. Laravel Queue rất thích hợp cho các bài toán xử lý nhiều việc song song. Do khuôn khổ bài viết, chúng tôi không đi sâu vào một ví dụ cụ thể, tuy nhiên sẽ có một bài viết riêng ở đó chúng ta sẽ quay lại việc kiểm tra xem giữa single queue và multiple queues khác biệt như thế nào nếu chúng ta thiết kế ứng dụng tốt.

Ví dụ gửi email khi đăng ký người dùng sử dụng Laravel Queue

Ok, chúng ta quay trở lại với ví dụ như ở đầu bài viết đã nêu, trong quá trình đăng ký thành viên, việc gửi mail chào mừng thành viên mới sẽ được tách ra thành một job để đưa vào hàng đợi, giúp cho người dùng không phải chờ đợi việc gửi email hoàn thành. Chúng ta cũng sẽ sử dụng sleep() để làm chậm quá trình gửi email cỡ 5 phút để giả lập trường hợp gửi email rất lâu. Trong ví dụ này, chúng ta thực hiện lại từng bước từ đầu, do đó nếu có bước nào các bạn đã thực hiện ở trên rồi không cần thực hiện lại.

Chú ý:

Bước 1: Thêm các bảng sử dụng cho Laravel Queue

c:\xampp\htdocs\laravel-test>php artisan queue:table Migration created successfully! c:\xampp\htdocs\laravel-test>php artisan queue:failed-table Migration created successfully! c:\xampp\htdocs\laravel-test>php artisan migrate Migrated: 2017_04_03_144759_create_jobs_table Migrated: 2017_04_03_150557_create_failed_jobs_table
Code language: CSS (css)

Hai bảng được tạo ra là jobs và failed_jobs để quản lý các job trong hàng đợi.

Bước 2: Cập nhật cấu hình cho .env

Các cấu hình bao gồm, cấu hình cho queue và email.

QUEUE_DRIVER=database MAIL_DRIVER=smtp MAIL_HOST=smtp.gmail.com MAIL_PORT=587 MAIL_USERNAME=allaravel.[email protected] MAIL_PASSWORD=nexnsd33ssonmkod MAIL_ENCRYPTION=tls

MAIL_PASSWORD của Gmail sử dụng là app password, để tạo app password cho tài khoản Google chúng ta vào My Account

Bước 1, vào My account trong Google

My Account chứa các thiết lập về tài khoản, trong đó có phần thiết lập bảo mật cho tài khoản

Bước 2 chọn sign in and security

Trước khi tạo app password chúng ta cần bật chế độ xác thực 2 bước trong Google.

Bước 3, chế độ xác thực 2 lần đang tắt

Click vào 2-Step Verification, cửa sổ giới thiệu xác thực 2 bước là gì xuất hiện.

Giới thiệu về xác thực 2 bước trong Google

Click vào Get Started để bắt đầu thiết lập xác thực 2 bước.

Nhập số điện thoại và sử dụng chế độ SMS để xác thực 2 bước

Một tin nhắn từ Google sẽ được gửi đến số điện thoại của bạn, lấy mã xác thực này nhập vào màn hình tiếp theo.

Nhận tin nhắn chứa verify code từ Google

Như vậy đã xong thiết lập xác thực 2 bước, click vào Turn On để xác nhận bật chế độ này.

Bật chế độ xác thực 2 bước

Ok, tiếp theo chúng ta quay lại Sign In and Security -> Signing in to Google để tạo app password

Chọn menu tạo app password

Chọn lại ứng dụng để tạo app password và click vào Generate.

Chọn loại ứng dụng để tạo app password

Phần mã trong khung màu vàng chính mà app password, bạn copy và đưa vào EMAIL_PASSWORD trong file .env, trong hình ảnh tôi đã cắt bớt các thông tin để đảm bảo tính bảo mật.

app password được tạo

Bước 3: Tạo mẫu email và các khởi tạo mail

Laravel hỗ trợ tạo email class bằng lệnh artisan

php artisan make:mail WelcomeEmail
Code language: CSS (css)

Câu lệnh này sẽ tạo ra class WelcomeEmail trong app\Mail. Chúng ta sẽ thay đổi class này, thêm $user vào phương thức contruct() để truyền người dùng vừa đăng ký vào phần xây dựng nội dung email.

protected $user; public function __construct($user){ // $this->user = $user; }
Code language: PHP (php)

Tiếp theo sửa phương thức build() để gán giao diện email vào.

public function build() { return $this->view('email.welcome')->with('user', $user); }
Code language: PHP (php)

Tiếp đến chúng ta tạo view cho email, tạo một thư mục trong resources/views là email chuyên lưu trữ các view về email. Tạo view welcome.blade.php trong thư mục vừa tạo (resources/views/email/welcome.blade.php) với nội dung như sau:

<html> <head> <title>Welcome to All Laravel</title> </head> <body> <h1>Chào mừng {{ $user->name }} đến với Allaravel.com</h1> <p>All Laravel là website chứa đựng rất nhiều các kiến thức, tài nguyên liên quan đến framework PHP số 1 hiện nay - Laravel. Đến với All Laravel, bạn được tham gia nhiều khóa học miễn phí với rất nhiều các ví dụ thực tế, giúp bạn nhanh chóng nắm bắt được kiến thức.</p> <p>Link đăng nhập hệ thống: <a href="http://laravel.dev/login">Đăng nhập All Laravel</a></p> </body> </html>
Code language: HTML, XML (xml)

Bước 4: Tạo job SendWelcomeEmail

php artisan make:job SendWelcomeEmail
Code language: CSS (css)

Câu lệnh này sẽ tạo ra một job SendWelcomeEmail trong thư mục app\Jobs, điều chỉnh lại nội dung của class này như sau:

<?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; use Mail; use App\Mail\WelcomeEmail; class SendWelcomeEmail implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; protected $user; /** * Create a new job instance. * * @return void */ public function __construct($user) { $this->user = $user; } /** * Execute the job. * * @return void */ public function handle() { sleep(60); echo 'Start send email'; $email = new WelcomeEmail($this->user); Mail::to($this->user->email)->send($email); echo 'End send email'; } }
Code language: HTML, XML (xml)

Bước 5: Đưa job vào hàng đợi khi đăng ký thành viên

Controller RegisterController.php trong app\Http\Controllers\Auth xử lý việc đăng ký thành viên, thêm phương thức register() để ghi đè phương thức register() cha trong trail RegistersUser

/** * Handle a registration request for the application. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function register(Request $request) { $this->validator($request->all())->validate(); event(new Registered($user = $this->create($request->all()))); $this->guard()->login($user); dispatch(new SendWelcomeEmail($user)); //return $this->registered($request, $user)?: redirect($this->redirectPath()); return view('auth.welcome'); }
Code language: PHP (php)

Tiếp theo chúng ta tạo view welcome.blade.php trong resources/views/auth

@extends('layouts.default') @section('title', 'Welcome Allaravel.com') @section('content') Chúcc mừngng bạn @endsection
Code language: JavaScript (javascript)

Bước 6: Chạy queue:work thực thi các job

Mỗi khi người dùng đăng ký mới, một job sẽ được đẩy vào hàng đợi, tiếp theo chúng ta chạy queue:work để lắng nghe nếu có job nào thì thực hiện luôn.

php artisan queue:work
Code language: CSS (css)

Ok, vậy là chúng ta đã hoàn thành xong việc viết code cho ví dụ, bây giờ chúng ta sẽ kiểm thử xem ứng dụng chạy như thế nào: Vào đường dẫn http://laravel.dev/register và nhập các thông tin đăng ký.

Màn hình đăng ký thành viên

Sau khi click vào Đăng ký, ngay lập tức tài khoản người dùng được tạo và người dùng được chuyển hướng đến view auth/welcome

Màn hình thông báo đăng ký thành công

Chúng ta thấy mặc dù trong job gửi email chúng ta đã delay 60 giây, nhưng khi đăng ký người dùng không phải chờ 60 giây này. Kiểm tra bảng jobs trong CSDL laravel-test chúng ta thấy xuất hiện bản ghi thông tin về job gửi email đang thực hiện.

Job gửi email xuất hiện trong bảng jobs

Quay lại màn hình queue:work chờ sau 60 giây, chúng ta thấy nó sẽ thực hiện job gửi email một cách tự động.

Màn hình queue:work tự động thực hiện job gửi email

Kiểm tra lại bảng jobs thì bản ghi job gửi email đã được delete sau khi thực hiện xong. Bây giờ chúng ta kiểm tra email dùng để đăng ký thành viên xem nhận được email không nhé.

Email nhận được từ hệ thống

Ok, vậy là chúng ta đã thực hành xong ví dụ của mình. Chúc các bạn thực hiện thành công, có bất kỳ câu hỏi nào, các bạn comment ở cuối bài nhé.

Cảm ơn các bạn đã đọc.

Các bạn có thể tham khảo các bài viết hay về Laravel tại đây.


Hãy tham gia nhóm Học lập trình để thảo luận thêm về các vấn đề cùng quan tâm.

Nguồn tham khảo: allaravel

Leave a Reply

Your email address will not be published.