Laravel Task Scheduling tự động hóa công việc trong hệ thống website

Trong các công việc cần thực hiện trên một hệ thống website, đôi khi một số công việc chúng ta cần thực hiện theo một kế hoạch định trước, ví dụ như chúng ta cần xóa các dữ liệu tạm trong các bảng của cơ sở dữ liệu vào giữa đêm hoặc chúng ta muốn tổng hợp các báo cáo tốn rất nhiều tài nguyên vào cuối ngày để hôm sau có thể truy cập được ngay… Laravel Task Scheduling cho phép bạn thực hiện các công việc theo một kế hoạch đặt trước.

1. Thiết lập thời gian biểu

Laravel Task Scheduling cho phép bạn lập thời gian biểu để chạy thực hiện các loại ứng dụng sau:

1. Một lời gọi đến Closure

$schedule->call(function () {
    DB::table('temp_data')->delete();
})->daily();

2. Một câu lệnh Artisan

$schedule->command('emails:send --force')->daily();

3. Một câu lệnh trong hệ điều hành

$schedule->exec('ipconfig -all')->daily();

Các công việc cần thực hiện và thời gian biểu cần được định nghĩa trong phương thức schedule() của lớp App\Console\Kernel (file app\Console\Kernel.php). Trong ví dụ tiếp theo, chúng ta sẽ thực thi một câu lệnh artisan inspire cứ 5 phút một lần vào xuất nội dung vào một file inspire.txt nằm trong ổ C. Trước tiên chúng ta cần định nghĩa trong app\Console\Kernel.php.

<?php

namespace App\Console;

use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
    /**
     * The Artisan commands provided by your application.
     *
     * @var array
     */
    protected $commands = [
        //
    ];

    /**
     * Define the application's command schedule.
     *
     * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
     * @return void
     */
    protected function schedule(Schedule $schedule)
    {

        $schedule->command('inspire')
            ->everyFiveMinutes()
            ->appendOutputTo('C:\inspire.txt');
    }

    /**
     * Register the Closure based commands for the application.
     *
     * @return void
     */
    protected function commands()
    {
        require base_path('routes/console.php');
    }
}

Câu lệnh artisan inspire là câu lệnh hiển thị ngẫu nhiên một câu danh ngôn, Taylor Otwell khá dị nhân khi có những câu lệnh kiểu này :). Bạn mở command line với thư mục hiện hành là thư mục gốc của dự án và thử câu lệnh php artisan inspire:

Artisan inspire

2. Đặt lịch thực hiện trong hệ điều hành

Bản thân laravel không tự thực hiện được các task schedule này mà nó thông qua các công cụ đặt lịch chạy ngầm của hệ điều hành (HĐH). Với Windows chúng ta có Windows Task Scheduler, với Linux chúng ta có CRON, với MacOS có automator. Tiếp theo chúng ta sẽ đi vào đặt lịch thực hiện cho từng HĐH riêng.

2.1 Đặt lịch trong Windows

Trong hệ điều hành Windows có công cụ Windows Task Scheduler chuyên để đặt lịch thực thi các ứng dụng trong hệ thống. Bạn có thể mở công cụ này với các bước như sau:

Cách 1: Trong Ô Search programs and files (phím tắt phím Windows + R), nhập Taskschd.msc và gõ Enter.

Cách 2: Chuột phải vào My Computer chọn Manage, trong tab bên tay trái bạn sẽ thấy Task Scheduler.

Sau khi mở được công cụ Windows Task Scheduler chúng ta tiếp tục các bước để đặt lịch thực hiện.

Bước 1: Tạo một lịch thực hiện, bấm vào Create Task…

Create Task

Bước 2: Các thiết lập căn bản trong Task Scheduler như tên, chế độ chạy, user chạy…

Thiết lập căn bản task scheduler

Bước 3: Thiết lập Trigger, chúng ta muốn Task Scheduler thực thi theo thời gian như thế nào, ví dụ ở đây chúng ta thiết lập chạy hàng ngày với lịch chạy con là 1 lần/1 phút. Chú ý, thời gian thực hiện trong Windows Task Scheduler phải dày đặc hơn thời gian thiết lập trong phương thức schedule().

Thiết lập Trigger trong Task Scheduler

Bước 4: Thiết lập Action chính là cái gì chúng ta cần thực hiện

Thiết lập Action trong Task Scheduler

Chúng ta đang cần thực hiện câu lệnh php artisan inspire một cách định kỳ, thực chất là chúng ta thực hiện câu lệnh php artisan schedule:run, bạn có thể chạy thử câu lệnh này tại thư mục gốc của dự án và kiểm tra file C:/inspire.txt.

artisan schedule run

Trong đây có một số chú ý:

  • Program/script chính là ứng dụng cần thực hiện, ở đây chính là ứng dụng php.exe thường nằm trong đường dẫn cài đặt Webserver (ở máy mình sử dụng XAMPP nên đường dẫn là C:/xampp/php/php.exe).
  • Add arguments: Đây chính là các tham số đưa vào ứng dụng, ở đây chúng ta nhập vào C:/xampp/htdocs/allaravel.com/artisan schedule:run.

OK, như vậy bạn đã đặt xong lịch chạy php artisan schedule:run trên môi trường Windows. Bạn kiểm tra và cứ 5 phút một câu danh ngôn sẽ được đưa vào file C:/inspire.txt.

2.2 Đặt lịch trong Unix, Linux

Cron là một công cụ đặt lịch thực hiện theo thời gian trong các hệ điều hành như Unix, Linux, các công việc, nhiệm vụ (job, task) được gọi là cron job. Cron là một ứng dụng daemon, là một ứng dụng chạy ngầm và được khởi tạo cùng hệ thống. Thời gian biểu chạy các ứng dụng sẽ được đưa vào một file cấu hình và file này được gọi là crontab. Chúng ta cùng xem xét một cron job như sau:

* * * * * /usr/bin/php /www/virtual/allaravel.com/artisan schedule:run >> /dev/null 2>&1

Một cron job sẽ có hai phần chính:

  • Phần đầu * * * * * là thời gian biểu thực hiện một ứng dụng
  • Phần còn lại là câu lệnh thực hiện ứng dụng, câu lệnh này giống như khi chúng ta chạy trong màn hình dòng lệnh.

2.2.1 Cú pháp thời gian biểu trong cron job

Cú pháp thời gian biểu trong Cron job

Mỗi một dấu * trong phần thời gian biểu cron job thể hiện thời gian lặp lại trong phút, giờ, ngày trong tháng, tháng và ngày trong tuần, nó có thể là một giá trị số cụ thể.
Chúng ta cùng xem một số ví dụ sau:
* * * * * [command] lịch này sẽ thực hiện mỗi phút một lần.
15 * * * * [command] lịch này thực hiện mỗi giờ một lần và vào phút thứ 15.
15 2 * * 1 [command] thực hiện vào mỗi thứ hai hàng tuần vào lúc 2:15 am.

2.2.2 Câu lệnh thực hiện

Câu lệnh thực hiện bao gồm 3 phần:

  • Ứng dụng cần thực hiện, ở đây chúng ta thực hiện ứng dụng php nằm trong thư mục /usr/bin/php.
  • Tham số cho ứng dụng, /www/virtual/allaravel.com/artisan schedule:run.
  • >> /dev/null 2>&1 là phần quản lý việc xuất dữ liệu của câu lệnh, sử dụng > nếu muốn ghi đè file và >> nếu muốn mở file và ghi thêm vào.

3. Một số các tùy chọn trong thiết lập task schedule trong Laravel

3.1 Các tùy chọn tần suất trong lịch thực hiện

Một chú ý là tại sao trong phần đặt lịch của Hệ điều hành chúng ta đã thực hiện đặt lịch nhưng trong Task Scheduling của Laravel chúng ta lại đặt lịch tiếp? Cái nào ưu việt hơn? Nếu bạn muốn đặt lịch trong Laravel thì lịch của hệ điều hành nên để là thực hiện mỗi phút một lần (trong cron job để là * * * * *) và lịch chi tiết sẽ thực hiện trong Laravel. Mỗi cách đặt lịch có một số ưu điểm:

  • Đặt lịch chính xác thông qua hệ điều hành sẽ tối ưu hóa về thực hiện, ví dụ chúng ta có lịch thực hiện 1 tuần một lần, nếu đặt lịch hệ thống là mỗi phút một lần như vậy sẽ có nhiều trigger bung ra để thực hiện lệnh trong Laravel.
  • Đặt lịch hệ thống mỗi phút một lần và lịch chính xác trong Laravel giúp chúng ta có thể thiết lập lịch trong ứng dụng, có thể lưu trữ lịch thực hiện trong database.

Do vậy, tùy từng loại công việc, nhiệm vụ cần thực hiện bạn nên lựa chọn đặt lịch chính xác bằng HĐH hay thông qua Laravel. Laravel hỗ trợ tùy chọn tần suất thực hiện cũng giống như trong HĐH như trong danh sách sau:

Phương thức Mô tả
->cron('* * * * * *'); Đặt lịch kiểu cron job
->everyMinute(); Thực hiện công việc mỗi phút một lần
->everyFiveMinutes(); Thực hiện công việc 5 phút một lần
->everyTenMinutes(); Thực hiện công việc 10 phút một lần
->everyThirtyMinutes(); Thực hiện công việc 30 phút một lần
->hourly(); Thực hiện công việc mỗi giờ một lần
->hourlyAt(15); Thực hiện công việc mỗi giờ một lần vào phút thứ 15
->daily(); Thực hiện công việc hàng ngày vào đúng nửa đêm
->dailyAt('13:00'); Thực hiện công việc hàng ngày vào lúc 13:00
->twiceDaily(1, 13); Thực hiện công việc 2 lần một ngày vào lúc 1:00 và 13:00
->weekly(); Thực hiện công việc hàng tuần
->monthly(); Thực hiện công việc hàng tháng
->monthlyOn(4, '15:00'); Thực hiện công việc vào 15:00 ngày thứ 4 của tháng hàng tháng
->quarterly(); Thực hiện công việc hàng quý
->yearly(); Thực hiện công việc hàng năm
->timezone('Asia/Saigon'); Thiết lập thời gian từng vùng

Chúng ta cùng xem một số ví dụ sau:

$schedule->exec('ipconfig')
          ->weekdays()
          ->hourly()
          ->timezone('Asia/Saigon')
          ->between('8:00', '17:00');

Ví dụ trên sẽ thực hiện câu lệnh ipconfig (câu lệnh xem các thông tin về địa chỉ IP trên máy) từ 8:00 đến 17:00 hàng giờ vào các ngày cuối tuần với múi giờ là giờ Việt Nam.

3.2 Thiết lập điều kiện thực hiện lịch

Cũng như trong đặt lịch sử dụng lịch hệ điều hành, Laravel cho phép thiết lập các điều kiện thực hiện lịch chi tiết hơn rất nhiều do có thể lập trình được. Laravel hỗ trợ một số phương thức ràng buộc điều kiện như sau:

Phương thức Mô tả
->weekdays(); Giới hạn thực hiện lịch chỉ vào ngày cuối tuần
->sundays(); Giới hạn thực hiện lịch chỉ vào Chủ nhật
->mondays(); Giới hạn thực hiện lịch chỉ vào thứ Hai
->tuesdays(); Giới hạn thực hiện lịch chỉ vào thứ Ba
->wednesdays(); Giới hạn thực hiện lịch chỉ vào thứ Tư
->thursdays(); Giới hạn thực hiện lịch chỉ vào thứ Năm
->fridays(); Giới hạn thực hiện lịch chỉ vào thứ Sáu
->saturdays(); Giới hạn thực hiện lịch chỉ vào thứ Bảy
->between($start, $end); Giới hạn thực hiện lịch ở giữa khoảng thời gian từ $start đến $end
->when(Closure); Giới hạn thực hiện lịch chỉ khi nào Closure trả về true.

Laravel cho phép bạn thực hiện các điều kiện phức tạp trong thực hiện lịch công việc với việc sử dụng phương thức when và skip bằng cách truyền vào một Closure. Với phương thức when() nếu Closure trả về true thì lịch sẽ được thực hiện, phương thức skip() thì ngược lại, nếu Closure trả về true thì lịch sẽ được bỏ qua. Ví dụ:

$schedule->call(function () {
        DB::table('temp_users')->delete();
    })->daily()
    ->when(function () {
        return (DB::table('loging_user')->get() != null)?false:true;
    });

Ví dụ trên sẽ xóa bảng tạm chứa thông tin người dùng hàng ngày nếu tại thời điểm đó không có người dùng nào đang trong hệ thống.

3.3 Ngăn chặn thực hiện công việc chồng chéo

Khi thực hiện các công việc bằng lịch tự động, rất có thể một công việc trước đó chưa hoàn thành, ví dụ bạn thực hiện tự động tổng hợp số liệu 5 phút một lần, tuy nhiên lần tổng hợp trước đó do dữ liệu quá lớn vẫn chưa có kết quả, như vậy bạn không muốn thực hiện tiếp lần tổng hợp tiếp theo sau 5 phút. Với việc sử dụng phương thức withoutOverlapping, Laravel sẽ bỏ qua lịch thực hiện nếu công việc trước đó chưa hoàn thành.

$schedule->command('report:summary')->withoutOverlapping();

Tại sao Laravel có thể làm được việc này, nó sử dụng mutex là một khái niệm trong lập trình để giải quyết vấn đề liên quan đến xử lý đa luồng. Mutex sử dụng một cờ chung, nó hoạt động giống như người gác cổng, cho phép một luồng có thể truy nhập trong khi những luồng khác bị chặn. Nó đảm bảo rằng các mã được kiểm soát sẽ chỉ được chạy bởi một luồng đơn tại một thời điểm.

Laravel tạo ra một mutex khi bắt đầu thực hiện một công việc và mỗi lần thực thi nó sẽ kiểm tra nếu mutex tồn tại, công việc đó sẽ không được thực hiện. Bạn có thể xem cách mutex hoạt động trong phương thức withoutOverlapping trong class Illuminate/Console/Scheduling/Event.php:

    public function withoutOverlapping()
    {
        $this->withoutOverlapping = true;

        return $this->then(function () {
            $this->mutex->forget($this);
        })->skip(function () {
            return $this->mutex->exists($this);
        });
    }

Laravel tạo ra một phương thức callback để lọc và thông báo với Schedule Manager bỏ qua công việc nếu một mutex vẫn đang tồn tại, nó cũng tạo ra một callback để xóa mutex sau khi công việc hoàn thành. Trước khi thực hiện công việc, Laravel thực hiện kiểm tra trong phương thức run của Illuminate/Console/Scheduling/Event:

    public function run(Container $container)
    {
        if ($this->withoutOverlapping &&
            ! $this->mutex->create($this)) {
            return;
        }

        $this->runInBackground
                    ? $this->runCommandInBackground($container)
                    : $this->runCommandInForeground($container);
    }

Trong khi một đối tượng được tạo từ Illuminate\Console\Scheduling\Schedule, trong phương thức khởi tạo của Schedule, Laravel sẽ kiểm tra nếu một thực thi của interface Illuminate\Console\Scheduling\Mutex được gắn vào container không, nếu có nó sẽ sử dụng đối tượng này, nếu không nó sử dụng một đối tượng từ class Illuminate\Console\Scheduling\CacheMutex:

    public function __construct()
    {
        $container = Container::getInstance();

        $this->mutex = $container->bound(Mutex::class)
                                ? $container->make(Mutex::class)
                                : $container->make(CacheMutex::class);
    }

Khi đó Schedule Manager sẽ đăng ký các sự kiện này và truyền vào một instance của mutex:

$this->events[] = new Event($this->mutex, $command);

Laravel mặc định sử dụng mutex có sử dụng bộ đệm, bạn có thể sử dụng phương pháp khác. Class CacheMutex chứa 3 phương thức đơn giản sử dụng các event name như là key:

<?php

namespace Illuminate\Console\Scheduling;

use Illuminate\Contracts\Cache\Repository as Cache;

class CacheMutex implements Mutex
{
    /**
     * The cache repository implementation.
     *
     * @var \Illuminate\Contracts\Cache\Repository
     */
    public $cache;

    /**
     * Create a new overlapping strategy.
     *
     * @param  \Illuminate\Contracts\Cache\Repository  $cache
     * @return void
     */
    public function __construct(Cache $cache)
    {
        $this->cache = $cache;
    }

    /**
     * Attempt to obtain a mutex for the given event.
     *
     * @param  \Illuminate\Console\Scheduling\Event  $event
     * @return bool
     */
    public function create(Event $event)
    {
        return $this->cache->add($event->mutexName(), true, 1440);
    }

    /**
     * Determine if a mutex exists for the given event.
     *
     * @param  \Illuminate\Console\Scheduling\Event  $event
     * @return bool
     */
    public function exists(Event $event)
    {
        return $this->cache->has($event->mutexName());
    }

    /**
     * Clear the mutex for the given event.
     *
     * @param  \Illuminate\Console\Scheduling\Event  $event
     * @return void
     */
    public function forget(Event $event)
    {
        $this->cache->forget($event->mutexName());
    }
}

Như ở trên đã nói, Schedule Manager có một callback để xóa mutex liên quan sau khi công việc hoàn thành, với một công việc chạy một lệnh của hệ điều hành, có thể không đảm bảo rằng mutex được xóa nhưng với callback, script có thể dừng trong khi thực thi callback để ngăn chặn một fallback được thêm vào Illuminate\Console\Scheduling\CallbackEvent::run()

    public function run(Container $container)
    {
        if ($this->description && $this->withoutOverlapping &&
            ! $this->mutex->create($this)) {
            return;
        }

        register_shutdown_function(function () {
            $this->removeMutex();
        });

        try {
            $response = $container->call($this->callback, $this->parameters);
        } finally {
            $this->removeMutex();
        }

        parent::callAfterCallbacks($container);

        return $response;
    }

3.4 Chế độ bảo trì hệ thống

Nếu bạn chuyển hệ thống sang chế độ bảo trì trong Laravel, toàn bộ các lịch thực hiện tự động thông thường sẽ không hoạt động, nếu bạn muốn một lịch tự động thực hiện ngay cả khi hệ thống được chuyển sang chế độ bảo trì, bạn có thể sử dụng phương thức evenInMaintenanceMode() khi định nghĩa lịch thực hiện.

3.5 Xuất dữ liệu khi chạy lịch thực hiện công việc

Laravel cung cấp rất nhiều phương thức hỗ trợ xuất dữ liệu đầu ra khi thực hiện các công việc:

Phương thức sendOutputTo xuất dữ liệu ra một file trong một thư mục:

$schedule->exec('ipconfig')
         ->daily()
         ->sendOutputTo($filePath);

Nếu bạn muốn ghi tiếp một file, sử dụng phương thức appendOutputTo:

$schedule->exec('ipconfig')
         ->daily()
         ->appendOutputTo($filePath);

Bạn cũng có thể gửi email các dữ liệu đầu ra khi thực hiện một công việc theo lịch với phương thức emailOutputTo, trước khi định nghĩa lịch thực hiện này bạn cần thiết lập Laravel Mail:

$schedule->exec('ipconfig')
         ->daily()
         ->appendOutputTo($filePath)
         ->emailOutputTo('admin@allaravel.com');

3.6 Task Hook

Hook là những phương thức được thực hiện trong một thời điểm nào đó của vòng đời một đối tượng, ở đây đối tượng chính là các công việc được thực hiện theo lịch. Laravel cho phép bạn có thể thực hiện một số các công việc trước hoặc sau khi một lịch thực hiện công việc được bắt đầu hoặc kết thúc thông qua các phương thức before(), after():

$schedule->command('emails:send')
         ->daily()
         ->before(function () {
             // Task is about to start...
         })
         ->after(function () {
             // Task is complete...
         });

Ngoài ra Laravel có các phương thức pingBefore, thenPing để có thể thực hiện ping một URL một cách tự động trước hoặc sau khi một nhiệm vụ được hoàn thành. Phương thức này là cực kỳ hữu ích để thông báo cho một dịch vụ ở ngoài.

$schedule->command('emails:send')
         ->daily()
         ->pingBefore($url)
         ->thenPing($url);

3 thoughts on “Laravel Task Scheduling tự động hóa công việc trong hệ thống website

  1. muốn chạy lịch định kỳ 20 phút một lần thì làm thế nào, Laravel chỉ có 1,5,10 và 30 phút / lần?

    1. bạn có thể dùng phương thức cron(), nó định nghĩa lịch thực hiện giống như khai báo lịch trong CRON của Unix, Linux. Ví dụ muốn thực hiện định kỳ 20 phút một lần thì sử dụng:
      $schedule->call(function () {
      //
      })->cron(‘*/20 * * * * *’);

Add Comment