Laravel deep dive: Task Scheduling

Lời Nói Đầu

Khi phát triển một ứng dụng nhiều lúc ta sẽ cần phải lên lịch để chạy một task nào đó như: update database, tình toán và ghi file, chạy một lệnh của hệ điều hành để update hoặc lệnh lịch thực hiện job tự động. Để làm điều này Laravel đã hỗ trợ chúng ta khả năng lên lịch cho task thân thiện dễ hiểu so với cách sử dụng file crontab.

Tìm hiểu về CRON

CRON là một tiến trình chạy ngầm trên linux server. Mỗi từng phút nó sẽ kiểm tra và chạy các task được giao cho nó, và ta giao tiếp với CRON thông qua các file crontab, các file này được lưu trữ ở /etc/crontab. Dưới đây là một ví dụ về file crontab này:

0 0 1 * * /home/full-backup
0 0 * * * /home/partial-backup
30 5 10 * * /home/check-subscriptions

Trong file crontab mỗi dòng sẽ là một scheduled job (một công việc đã được lên lịch chaỵ), mỗi job được chia làm 2 phần:

  • Phần * biểu diễn thời gian sẽ chạy job
    • Phần thời gian chạy job sẽ bao gồm 5 thành phần theo thứ tự:
      1. Phút của giờ
      2. Giờ của ngày
      3. Ngày của tháng
      4. Tháng của năm
      5. Thứ của tuần
  • Phần còn lại là câu lệnh sẽ thực hiện khi đến thời gian ở trên.

Ví dụ

  • 0 * * * * : job sẽ được chạy từng giờ.
  • */10 * * * : job sẽ chạy 10 phút một lần.
  • 0 1,15 * * * : job sẽ chạy lúc 1AM và 3PM hằng ngày.

Sau khi định nghĩa thời gian đơn giản cung cấp sau đó câu lệnh sẽ thực hiện khi đến thời gian là ta đã hoàn thành xong file crontab. Cộng việc chạy job sẽ được thực hiện bởi CRON deamon. Tuy nhiên như ta thấy ở trên việc nhìn vào và phát hiểu được luôn thời gian và công việc của job sẽ thực hiện sẽ tồn mất một khoảng thời gian của chúng ta. Ngoài ra khi lượng job nhiều lên việc update và thêm mới job sẽ cũng vô cùng vất vả nếu làm bằng tay. Do đó Laravel cung cấp cho chúng ta một phương thức dễ dàng với cú pháp đơn giản dễ hiểu hơn để lên lịch làm việc cho các job.

$schedule->command('send:offer')
          ->everyFiveMinutes()
          ->wednesdays();

Chúng ta chỉ cần khai báo job và thời gian bằng chú pháp mà Laravel cung cấp, Laravel sẽ thực hiện phần còn lại. Cú pháp trên sẽ tương đương với file crontab sau:

*/5 * * * 3 send:offfer

Vậy Laravel thực hiện việc lên lịch cho job như thế nào?

Laravel thực hiện task scheduling

Trước hết để Laravel có thể lên lịch cho job ta vẫn cần phải thêm một job đầu tiên vào tệp crontab, với các job sau ta sẽ chỉ cần dùng cú pháp mà Laravel cung cấp:

* * * * * php artisan schedule:run >> /dev/null 2>&1

Job ở trên chạy mỗi phút câu lệnh schedule:run giúp lên lịch cho các job ta định nghĩa sau này.

Khi một đối tượng của Console Kernel (chịu trách nhiệm về các hoạt động liên qua tới console) được khởi tạo, laravel đăng kí một listener cho event booted của Kernel, listener này sẽ bind Scheduler với container và gọi phương thức scheduler() của Kernel.

// in Illuminate\Foundation\Console\Kernel

public function __construct(Application $app, Dispatcher $events)
{
    $this->app->booted(function () {
        $this->defineConsoleSchedule();
    });
}

protected function defineConsoleSchedule()
{
     // Register the Scheduler in the Container
    $this->app->instance(
        Schedule::class, $schedule = new Schedule($this->app[Cache::class])
    );

     // Call the schedule() method that we override in our App\Console\Kernel
    $this->schedule($schedule);
}

Khi CRON deamon chạy php artisan schedule:run mỗi phút, Console Kernel sẽ được booted và các job ta define trong phương thức App\Console\Kernel::schedule() sẽ được lên lịch. Phương thức này nhận một đối tượng của Illuminate\Console\Scheduling\Schedule làm tham số duy nhất, đối tượng này giúp quản lý các job mà nó được giao và quyết định nên chạy job nào một khi CRON deamon ping nó.

Các thuộc tính của Event.

Các thành phần ta thêm vào phương thức App\Console\Kernel::schedule() được biến dổi thành một đối tượng của class Illuminate\Console\Scheduling\Event và lưu vào $events thuộc tính của class Scheduler. Một đối tượng event bao gồm:

  • Câu lệnh để thực hiện.
    • Một callback
    • Một câu lệnh chạy trên OS
    • Câu lệnh artisan.
    • Một job để lên lịch
  • Biểu thức CRON
  • Timezone sử dụng để ước lượng thời gian
  • Hệ điều hành để chạy câu lệnh trên.
  • Danh sách môi trường để chạy câu lệnh
  • Cấu hình cho xử lý output
  • Cấu hình câu lệnh chạy nền hay không
  • Callback cho trước và sau câu lệnh.
  • Mô tả cho câu lệnh
  • Các điều kiện...

CallBack

Để sử dụng callback như là một câu lệnh ta sử dụng phương thức static Schedule::call(), ta cần truyền vào một callback hoặc một string đại diện cho tên của phương thức của class:

protected function schedule(Schedule $schedule)
{
	\\truyền vào một callback delete table
    $schedule->call(function () {
        DB::table('recent_users')->delete();
    })->daily();
    
    \\truyền vào tên của phương thức của class
    $schedule->call('[email protected]')->daily();
}

Câu lệnh của hệ điều hành

Để chạy câu lệnh của OS ta sử dụng phương thức exec();

$schedule->exec('php /home/sendmail.php --user=10 --attachInvoice')->monthly();

Ta có thể param cho câu lệnh dưới dạng mảng:

$schedule->exec('php /home/sendmail.php', [
    '--user=10',
    '--subject' => 'Reminder',
    '--attachInvoice'
])->monthly();

Câu lệnh artisan

$schedule->command('mail:send --user=10')->monthly();

Queue và thực hiện job

Ta có thể lên lịch thực hiện job sử dụng phương job và truyền vào một class hoặc đối tượng của job.

$schedule->job('App\Jobs\SendOffer')->monthly();

$schedule->job(new SendOffer(10))->monthly();

Sau khi khai báo như trên thực chất Laravel sẽ tạo một callback gọi đến phương thức dispatch() để lên lịch thực hiện cho job

Tạo biểu thức cron

Để lên thời gian cho các task Laravel có các phương thức thân thiện với người dùng tương ứng với các biểu thức cron

hourly() <-> * * * * *
dailyAt('13:30') <-> 30 13 *  * *

Tuy nhiên ta có thể tự tạo biểu thức cron thông qua phương thức cron(). Ngoài ra ta cũng có thể setting timezone('Europe/London');

Các điều kiện khác

Ta có thể thiết lập thêm các tham số khi chạy các câu lệnh.

Thiết lập khoảng thời gian chạy task

Ngoài sử dụng biểu thức cron ta có thể sử dụng phương thức between() hoặc unlessBetween() để thiết khoảng ngày chạy task

Điều kiện về môi trường

Sử dụng phương thức enviroments() để thiết lập điều kiện môi trường mà task được phép chạy

Chế độ bảo trì

Mặc định các task sẽ không chạy khi ứng dụng ở chế độ bảo trì, tuy nhiên ta có thể tắt chết độ này bằng phương thức evenInMaintenanceMode()

Callback cho trước hoặc sau khi task chạy

Sử dụng các phương thức before() hoặc then() hoặc pingBefore() and pingThen().

Tài Liệu Tham Khảo

  1. https://divinglaravel.com/task-scheduling