12/08/2018, 16:32

queue trong laravel

Cấu hình File cấu hình được lưu trong config/queue.php. Trong file này bạn sẽ muốn tìm cấu hình kết nối cho mỗi queue drivers được đi kèm với framework, bao gồm database, Beanstalkd, Amazon SQS, Redis, và synchronous driver (để sử dụng local). nếu không muốn thực hiện queue job thì để driver là ...

Cấu hình

File cấu hình được lưu trong config/queue.php. Trong file này bạn sẽ muốn tìm cấu hình kết nối cho mỗi queue drivers được đi kèm với framework, bao gồm database, Beanstalkd, Amazon SQS, Redis, và synchronous driver (để sử dụng local).

nếu không muốn thực hiện queue job thì để driver là null

  'default' => env('QUEUE_DRIVER', 'sync'), // driver tương ứng 'sync' là thực hiện ngay lập tức

Cài đặt driver

Database

Để sử dụng driver database, bạn sẽ cần một bảng để lưu trũ thông tin về jobs. Để tạo file migration cho việc tạo bảng, thực thi câu lệnh Artisan queue:table. Một khi migration đã được tạo, bạn có thể migrate vào database sử dụng lệnh migrate:

php artisan queue:table

php artisan migrate

Các dependency khác cho queue

Các packages dưới đây cần thiết cho từng queue driver tương ứng, bạn dùng cái nào thì thêm vào cái đó, nếu dùng table thì không cần thiết. cài đặt bằng cách thêm vào composer.js

  • Amazon SQS: aws/aws-sdk-php ~3.0
  • Beanstalkd: pda/pheanstalk ~3.0
  • Redis: predis/predis ~1.0

Viết Job Classes

Tạo Job Classes

Về mặc định, tất cả các job có thể được queue được lưu trong thư mục app/Jobs. Bạn có thể tạo một job mới sử dụng Artisan:

php artisan make:job SendReminderEmail

Câu lệnh này sẽ tạo ra một class mới trong thư mục app/Jobs, và class sẽ triển khai interface IlluminateContractsQueueShouldQueue, cho Laravel biết job này cần được đẩy vào queue thay vì chạy đồng bộ.

Cấu trúc Job Class

Job class khá đơn giản, bình thường chỉ chứa một hàm duy nhất handle mà được gọi khi job được queue gọi để xử lý. Để bắt đầu:

<?php

namespace AppJobs;

use AppUser;
use AppJobsJob;
use IlluminateContractsMailMailer;
use IlluminateQueueSerializesModels;
use IlluminateQueueInteractsWithQueue;
use IlluminateContractsQueueShouldQueue;

class SendReminderEmail extends Job implements ShouldQueue
{
    use InteractsWithQueue, 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  Mailer  $mailer
     * @return void
     */
    public function handle(Mailer $mailer)
    {
        $mailer->send('emails.reminder', ['user' => $this->user], function ($m) {
            //
        });

        $this->user->reminders()->create(...);
    }
}

Ở ví dụ này, chú ý là chúng 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ó thực hiện tất cả khá rõ ràng trong chương trình và 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.

Khi việc không như ý muốn

Nếu có exception khi đang thực hiện queue, nó sẽ tự động bị đẩy ngược lại vào queue để có thể thực hiện lại lần nữa. Job sẽ thực hiện như thế cho tới khi số lượng lần thử lại đạt tới số lần tối đa cho phép. Số lần thực thi tối đa này được truyền qua thông số --tries của câu lệnh queue:listen hay queue:work. Để biết thêm chi tiết về cách thực thi queue listener.

Tự release job

Nếu bạn muốn release (gỡ, nhả) job thủ công, trait InteractsWithQueue có sẵn trong job class, cho phép gọi hàm release trong queue job. Hàm release nhận một tham số: số giây bạn muốn chờ cho tới khi job có hiệu lực trở lại lần nữa:

public function handle(Mailer $mailer)
{
    if (condition) {
        $this->release(10);
    }
}

Kiểm tra số lần thực thi

Như ghi ở trên, nếu có exception xuất hiện trong khi job đang được thực thi, nó sẽ tự được bị release trở lại queue. Bạn có thể kiểm qua số lần thử được thực hiện bằng hàm attempts:

public function handle(Mailer $mailer)
{
    if ($this->attempts() > 3) {
        //
    }
}

Đưa job vào trong queue

Controller mặc định của Laravel nằm tại app/Http/Controllers/Controller.php sử dụng trait DispatchesJobs. Trait này cung cấp vài phương thức cho phép bạn đẩy job vào trong queue, ví dụ như hàm dispatch:

<?php

namespace AppHttpControllers;

use AppUser;
use IlluminateHttpRequest;
use AppJobsSendReminderEmail;
use AppHttpControllersController;

class UserController extends Controller
{
    /**
     * Send a reminder e-mail to a given user.
     *
     * @param  Request  $request
     * @param  int  $id
     * @return Response
     */
    public function sendReminderEmail(Request $request, $id)
    {
        $user = User::findOrFail($id);

        $this->dispatch(new SendReminderEmail($user));
    }
}

Trait DispatchesJobs

Dĩ nhiên, thi thoảng bạn muốn dispatch một job từ đâu đó trong ứng dụng ngoài route và controller. Vì lí do đó, bạn có thể thêm vào trait DispatchJobs trong bất cứ class nào để có thể sử dụng các phương thức dispatch. Ví dụ, đây là một class sử dụng trait:

<?php

namespace App;

use IlluminateFoundationBusDispatchesJobs;

class ExampleClass
{
    use DispatchesJobs;
}

Hàm dispatch

Hoặc bạn có thể muốn sử dùng hàm helper dispatch:

Route::get('/job', function () {
    dispatch(new AppJobsPerformTask);

    return 'Done!';
});

Chỉ định queue cho một job

Bạn cũng có thể chỉ định queue nào mà job sẽ được gửi tới.

Bằng cách đẩy job tới các quêu khác nhau, bạn có thể "categorize" các queued job, và ưu tien hoá bao nhiêu worker thiết lập cho các queue. Việc này không đẩy job tới các queue "connections" khác nhau như khai báo về queue trong file cấu hình, nhưng chỉ cho queue cụ thể trong một connection. Để chỉ rõ queue nào, sử dụng hàm onQueue. Hàm onQueue được cung cấp bởi IlluminateBusQueueable, mà nằm sẵn trong class AppJobsJob:

<?php

namespace AppHttpControllers;

use AppUser;
use IlluminateHttpRequest;
use AppJobsSendReminderEmail;
use AppHttpControllersController;

class UserController extends Controller
{
    /**
     * Send a reminder e-mail to a given user.
     *
     * @param  Request  $request
     * @param  int  $id
     * @return Response
     */
    public function sendReminderEmail(Request $request, $id)
    {
        $user = User::findOrFail($id);

        $job = (new SendReminderEmail($user))->onQueue('emails');

        $this->dispatch($job);
    }
}

Trì hoãn Jobs

Đôi lúc bạn sẽ muốn trì hoãn việc thực thi một queued job. Ví dụ, bạn muốn queue một job mà gửi customer một email nhắc nhở sau khi đăng kí tầm 5 phút. Bạn có thể làm việc này sử dụng hàm delay của job class, được cung cấp bởi trait IlluminateBusQueueable:

<?php

namespace AppHttpControllers;

use AppUser;
use IlluminateHttpRequest;
use AppJobsSendReminderEmail;
use AppHttpControllersController;

class UserController extends Controller
{
    /**
     * Send a reminder e-mail to a given user.
     *
     * @param  Request  $request
     * @param  int  $id
     * @return Response
     */
    public function sendReminderEmail(Request $request, $id)
    {
        $user = User::findOrFail($id);

        $job = (new SendReminderEmail($user))->delay(60 * 5);

        $this->dispatch($job);
    }
}

Ở ví dụ này, chúng ta chỉ định job sẽ bị trì hoãn tầm 5 phút trước khi được đưa ra worker.

Lưu ý: Amazon SQS service có thời gian trì hoãn tối đa là 15 phút.

Job Events

Job Lifecycle Events

Hai hàm Queue::before và Queue::after cho phép bạn đăng kí một callback được thực hiện trước khi một queued job được khởi động hay khi nó thực thi thành công. Callback là các hữu hiệu để thực hiện các việc log bổ sung, queue một job con, hay cập nhật số liệu thống kê cho dashboard. Ví dụ, chúng ta có thể gán một callback cho event này từ AppServiceProvider đi kèm với Laravel:

<?php

namespace AppProviders;

use Queue;
use IlluminateSupportServiceProvider;
use IlluminateQueueEventsJobProcessed;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        Queue::after(function (JobProcessed $event) {
            // $event->connectionName
            // $event->job
            // $event->data
        });
    }

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

Thực thi Queue Listener

Laravel có chứa một câu lệnh Artisan mà sẽ chạy các job mới khi chúng được đưa vào queue. Bạn có thể chạy listener sử dụng lệnh queue:listen:

php artisan queue:listen

Bạn cũng có thể chỉ định queue connection nào mà listener nên xử lý:

php artisan queue:listen connection-name

Chú ý là một khi task được bắt đầu thì nó sẽ thực hiện tiếp tục cho tới khi được dừng thủ công. Bạn có thể sử dụng process monitor như Supervisor để đảm bảo queue listener không bị dừng.

Độ ưu tiên của queue

Bạn có thể truyền vào danh sách các queue ngăn cách bằng dấu , tới job được listen để thiết lập độ ưu tiên:

php artisan queue:listen --queue=high,low

In this example, jobs on the high queue will always be processed before moving onto jobs from the low queue. Ở ví dụ này, job ở high queue sẽ luôn luôn được thực hiện trước các job low.

Chỉ định timeout

Bạn có thể set giới hạn thời gian mỗi job được phép chạy:

php artisan queue:listen --timeout=60

Chỉ định thời gian sleep

Bạn có thể thiét lập thời gian sleep trước khi thực hiện job mới:

php artisan queue:listen --sleep=5

Chú ý là queue chỉ "sleep" nếu không có job nào trong queue. Nếu còn nhiều job, thì queue sẽ thực hiện liên tục mà không sleep.

Thực thi job đầu tiên trên queue

Để chỉ thực hiện job đầu tiên trên queue, bạn có thể sử dụng queue:work:

php artisan queue:work

Cấu hình Supervisor

Supervisoer là một process monitor cho hệ điều hành Linux, và sẽ tự động khởi động lại queue:listen hay queue:work nếu thất bại. Để cài Supervisor trên Ubuntu, bạn có thể sử dụng câu lệnh sau:

sudo apt-get install supervisor

File cấu hình cho Supervisor cơ bản được lưu trong /etc/supervisor/conf.d. Bên trong thư mục này, bạn có thể tạo bao nhiêu file cấu hình tuỳ ý để chỉ dẫn supervisor nên monitor process nào. Ví dụ, hãy tạo file laravel-work.conf để khởi động và monitor queue:work:

[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 --daemon
autostart=true
autorestart=true
user=forge
numprocs=8
redirect_stderr=true
stdout_logfile=/home/forge/app.com/worker.log

Ở ví dụ này, khoá numprocs sẽ chỉ Supervisor chạy 8 tiến trình queue:work và monitor tất cả chúng, tự động khởi động lại nếu thất bại. Dĩ nhiên, bạn nên đổi phần queue:work sqs của khoá command để tương ứng với driver mà bạn sử dụng.

Khi file cấu hình được tạo, bạn có thể cập nhật cấu hình Supervisor và khởi động lại tiến trình sử dụng câu lệnh sau:

sudo supervisorctl reread

sudo supervisorctl update

sudo supervisorctl start laravel-worker:*

Để biết thêm chi tiết về cấu hình và sử dụng Supervisor, tham khảo tài liệu của Supervisor. Ngoài ra, bạn có thể sử dụng Laravel Forge để tự động cấu hình và quản lý cấu hình Supervisor từ giao diện web.

Daemon Queue Listener

Câu lệnh queue:work có tuỳ chọn là --daemon để làm cho queue worker tiếp tục xử lý các job mà không cần khởi động lại framework. Điều này giảm thiểu được CPU usage rõ rệt so với sử dụng queue:listen:

Để khởi động queue worker ở chế độ daemon, sử dụng cờ --daemon:

php artisan queue:work connection-name --daemon

php artisan queue:work connection-name --daemon --sleep=3

php artisan queue:work connection-name --daemon --sleep=3 --tries=3

Như bạn thấy, job queue:work hỗ trợ hầu hết các thông số giống với queue:listen. Bạn có thể gõ lệnh php artisan help queue:work để xem các tuỳ chọn có thể sử dụng.

Daemon queue worker không khởi động lại framework trước khi xử lý mỗi job. Vì thế, bạn cần phải chú ý giải phóng bất cứ tài nguyên nặng nào trước khi job kết thúc. Ví dụ, nếu ban jđang xử lý ảnh với thư viện GB, bạn nên giải phóng bộ nhớ với imagedestroy khi hoàn thiện.

Triển khai Daemon Queue Listeners

Vì daemon queue workers là các tiến trình tồn tại lâu, chúng sẽ không chọn thay đổi trong code bạn mà không cần khởi động lại. Vì vậy, đơn giản nhất để triển khai một chương trình sử dụng daemon queue worker là khởi động lại worker trong deployment script. Bạn có thể dễ dàng khởi động lại tất cả các worker bằng cách thêm vào câu lệnh sau trong deployment script:

php artisan queue:restart

Câu lệnh này sẽ nhẹ nhàng chỉ dẫn các queue worker "die" sau khi hoàn thiện job hiện tại để đảm bảo không có job nào bị mất. Nhớ là, queue workers sẽ "die" khi lệnh queue:restart được thực thi, vì thế bạn nên chạy một trình quản lý process như Supervisor để tự động khởi động lại các queue driver.

Chú ý: Câu lệnh này phụ thuộc vào hệ thống cache để đặt lịch khởi động. Về mặc định, APCu không làm việc với CLI job. Nếu bạn đang sử dụng APCu, thêm vào apc.enable_cli=1 trong cấu hình APCu.

Xử lý các failed job

Vì mọi thứ không phải lúc nào cũng như mong muốn, sẽ có lúc queued job thất bại. Đừng lo, đó là điều tốt! Laravel có một cấu hình giúp chúng ta chỉ định số lần muốn thử lại cho failed job. Sau khi job vượt quá số lần thử tối đa, nó sẽ chèn vào trong bảng failed_jobs. Tên của bảng có thể thay đổi trong file cấu hình config/queue.php.

Để tạo migration cho bảng failed_jobs, bạn có thể sử dụng lệnh queue:failed-table:

php artisan queue:failed-table

Khi chạy các queue listener, bạn có thể chỉ định số lần thử lại tối đa cho một job bằng tuỳ chọn --tries trên câu lệnh queue:listen:

php artisan queue:listen connection-name --tries=3

Failed Job Events

Nếu bạn muốn đăng kí event lắng nghe khi một queued job thất bại, bạn có thể sử dụng hàm Queue::failing. Event này là một cách tốt để thông báo cho team của bạn qua email hoặc HipChat. Ví dụ, chúng ta có thể gắn một callback cho event này từ AppServiceProvider có sẵn trong Laravel:

<?php

namespace AppProviders;

use Queue;
use IlluminateQueueEventsJobFailed;
use IlluminateSupportServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        Queue::failing(function (JobFailed $event) {
            // $event->connectionName
            // $event->job
            // $event->data
        });
    }

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

Các hàm xử lý thất bại trong job

Để điều khiển tốt hơn, bạn có thể thêm hàm failed trực tiếp trong queue job class, cho phép bạn thực hiện một hành động cụ thể khi thất bại:

<?php

namespace AppJobs;

use AppJobsJob;
use IlluminateContractsMailMailer;
use IlluminateQueueSerializesModels;
use IlluminateQueueInteractsWithQueue;
use IlluminateContractsQueueShouldQueue;

class SendReminderEmail extends Job implements ShouldQueue
{
    use InteractsWithQueue, SerializesModels;

    /**
     * Execute the job.
     *
     * @param  Mailer  $mailer
     * @return void
     */
    public function handle(Mailer $mailer)
    {
        //
    }

    /**
     * Handle a job failure.
     *
     * @return void
     */
    public function failed()
    {
        // Called when the job is failing...
    }
}

Thử lại Failed Jobs

Để xem tất cả các failed jobs đã insert vào trong bảng failed_jobs, bạn có thể sử dụng câu lệnh queue:failed:

php artisan queue:failed

Câu lệnh queue:failed sẽ liệt kế Job ID, connection, queue và thời gian thất bại. Job ID có thể được dùng để thử chạy lại. Ví dụ để thử chạy lại một failed job có ID là 5, gõ lệnh như sau:

php artisan queue:retry 5

Để thực thi lại tất cả các failed jobs, sử dụng queue:retry với all thay cho ID:

php artisan queue:retry all

Nếu bạn muốn xoá một failed job, sử dụng queue:forget:

php artisan queue:forget 5

Để xoá tất cả các failed jobs, bạn có thể sử dụng queue:flush:

php artisan queue:flush

Tài liệu

  • Bài viết được dịch từ https://laravel.com/docs/5.5/queues
0