0

Joomla Bài học 19: Quản lý tác vụ chạy ngầm tự động với Task Scheduler (Cronjobs nội bộ trên Joomla)

Trong các hệ thống web lớn, có hàng loạt công việc bảo trì và đồng bộ dữ liệu cần được thực hiện định kỳ (như dọn dẹp bảng Session rác, xóa Cache hết hạn, nén ảnh, gửi email thông báo hoặc đồng bộ tồn kho từ ERP) mà không thể bắt người dùng cuối phải chờ đợi trong chu kỳ HTTP Request thông thường.

Bắt đầu từ Joomla 4.1 và hoàn thiện mạnh mẽ ở Joomla 5, hệ thống đã tích hợp sẵn Task Scheduler (com_scheduler) — một bộ quản lý tác vụ chạy ngầm (Cronjobs nội bộ) cho phép thiết lập lịch trình, theo dõi log thực thi và xử lý hàng đợi tự động. Trong Joomla Bài học 19, chúng ta sẽ mổ xẻ kiến trúc của Task Scheduler, phân biệt 3 cơ chế kích hoạt (Trigger) và tự tay viết một Task Plugin bằng PHP.

1. Kiến trúc của Joomla Task Scheduler (com_scheduler)

Trước đây, mỗi khi một Component bên thứ ba muốn chạy ngầm, lập trình viên phải tự viết file script rời rạc và yêu cầu Admin cấu hình hàng chục dòng lệnh crontab khác nhau trên Linux. Task Scheduler của Joomla 5 chuẩn hóa toàn bộ quy trình này về một mối:

  1. Task Plugins (Nhóm plugin task): Nơi định nghĩa các "công việc" (Routines) có thể thực thi. Mặc định Joomla 5 cung cấp sẵn các plugin cực kỳ hữu ích:

    • Task - Delete Action Logs: Tự động xóa lịch sử thao tác quản trị cũ sau XX ngày.

    • Task - Session Data Purge: Dọn dẹp các phiên đăng nhập đã hết hạn trong bảng #__session.

    • Task - Privacy Consent Expiration: Quản lý thời hạn đồng ý quyền riêng tư (GDPR).

    • Task - Site Offline / Online: Hẹn giờ bật/tắt bảo trì hệ thống tự động.

    • Task - Requests (GET Request): Gọi một URL/Webhook bất kỳ theo định kỳ.

  2. Scheduled Tasks (Danh sách tác vụ): Giao diện tại System →\rightarrow Manage →\rightarrow Scheduled Tasks, nơi Admin tạo các bản ghi lịch trình dựa trên các Task Plugins đã bật, thiết lập tần suất chạy (theo phút, giờ, ngày hoặc biểu thức Cron Expression chuẩn Unix như */15 * * * *).

2. 3 Cơ chế kích hoạt (Triggers) của Task Scheduler

Bản chất của PHP là ngôn ngữ chạy theo sự kiện (Request-driven) chứ không phải một Daemon chạy liên tục trong RAM. Do đó, để Task Scheduler biết khi nào đến giờ chạy một tác vụ, Joomla 5 cung cấp 3 cơ chế kích hoạt (Triggers) từ cơ bản đến chuyên nghiệp:

Cơ chế Trigger Nguyên lý hoạt động Ưu điểm Nhược điểm & Khuyến nghị
1. Lazy Scheduler
(Mặc định)
Nhúng một đoạn JavaScript nhỏ (com_scheduler/scheduler.js) vào cuối trang web. Khi có khách truy cập vào web, trình duyệt sẽ bắn 1 request AJAX ngầm về server để kiểm tra và chạy Task đến hạn. Không cần quyền truy cập SSH/Server, cài xong tự chạy ngay. Phụ thuộc vào lượng truy cập: Ban đêm không có khách vào web thì Task không chạy! Tác vụ nặng có thể giữ kết nối PHP-FPM lâu.
2. Web Cron (URL / Webhook) Mở một đường dẫn API có gắn khóa bảo mật (Token). Bạn dùng dịch vụ bên ngoài (như UptimeRobot, cron-job.org) hoặc curl gọi vào link này mỗi phút. Đúng giờ chính xác kể cả khi web không có khách truy cập. Bị giới hạn bởi max_execution_time của Web Server (Nginx/Apache timeout).
3. CLI Cron
(Chuẩn Production)
Thiết lập duy nhất 1 dòng trong crontab của Linux/Docker gọi thẳng vào php cli/joomla.php scheduler:run. Tốc độ tối đa, không giới hạn Timeout, không tốn worker của PHP-FPM, tách biệt hoàn toàn với người dùng web. Yêu cầu quyền cấu hình Crontab hoặc Docker container riêng.

3. Thiết lập Task Scheduler chuẩn Production với CLI & Web Cron

Phương pháp A: Cấu hình CLI Cron trong Docker / Linux (Khuyên dùng số 1)

Khi đã dùng CLI Cron, bạn nên TẮT Lazy Scheduler đi để trình duyệt của khách truy cập không phải gánh thêm một request AJAX thừa ở mỗi lần tải trang:

  1. Tắt Lazy Scheduler: Truy cập System →\rightarrow Manage →\rightarrow Plugins →\rightarrow Tìm plugin System - Schedule Runner →\rightarrow Mở ra và chuyển mục Enable Lazy Scheduler sang No.

  2. Kiểm tra danh sách và chạy thử bằng Joomla CLI: Mở terminal tại thư mục dự án Docker và chạy các lệnh sau:

    Bash

    # Liệt kê toàn bộ các Scheduled Tasks hiện có cùng trạng thái và lần chạy tiếp theo
    docker exec -it joomla_php php cli/joomla.php scheduler:list
    
    # Thực thi ngay lập tức các Task đã đến hạn (hoặc truyền --id=1 để ép chạy Task số 1)
    docker exec -it joomla_php php cli/joomla.php scheduler:run
    docker exec -it joomla_php php cli/joomla.php scheduler:run --id=1
    
    
  3. Đưa vào Crontab của hệ thống (* * * * * - Mỗi phút 1 lần): Trên máy chủ Linux Production (hoặc container cron), bạn chỉ cần thêm duy nhất 1 dòng vào crontab -e:

    Đoạn mã

    * * * * * /usr/local/bin/php /var/www/html/cli/joomla.php scheduler:run > /dev/null 2>&1
    
    

    Từ lúc này, mọi việc thêm/sửa/xóa lịch trình hay đổi giờ chạy đều được bạn điều khiển trực quan trên giao diện Admin của Joomla mà không bao giờ phải mở lại terminal sửa file crontab nữa!

Phương pháp B: Cấu hình Web Cron (Khi dùng Hosting không có SSH)

  1. Truy cập System →\rightarrow Manage →\rightarrow Scheduled Tasks →\rightarrow Bấm nút Options (góc trên bên phải).

  2. Chuyển sang tab Web Cron →\rightarrow Bật Web Cron = Yes.

  3. Hệ thống sẽ tự sinh ra một đường dẫn bảo mật chứa khóa băm tại mục Web Cron Link, có dạng:[https://domain.com/index.php?option=com_ajax&plugin=RunSchedulerWebcron&group=system&format=json&hash=AbCdEf123456](https://domain.com/index.php?option=com_ajax&plugin=RunSchedulerWebcron&group=system&format=json&hash=AbCdEf123456)...

  4. Sao chép đường dẫn này đưa vào công cụ ping định kỳ.

4. Quản lý Trạng thái và Xử lý lỗi treo Task (Hanging / Locked Tasks)

Trong giao diện Scheduled Tasks, bạn có thể bấm nút Run Test trên từng tác vụ để chạy thử ngay lập tức và xem kết quả trên màn hình Popup.

Ở tầng cơ sở dữ liệu, mọi tác vụ được lưu trong bảng #__scheduler_tasks. Có một vấn đề thực tế mà Backend Developer cần lưu ý là hiện tượng Zombie / Locked Task:

  • Khi một Task bắt đầu chạy, Joomla ghi thời điểm bắt đầu vào cột locked trong bảng #__scheduler_tasks để ngăn một tiến trình khác chạy trùng lặp (Race Condition).

  • Nếu tiến trình PHP đang chạy giữa chừng mà bị sập nguồn hoặc hết RAM (Out of Memory), cột locked vẫn giữ giá trị khác NULL, khiến Task đó bị treo ở trạng thái đang chạy và không bao giờ kích hoạt ở các chu kỳ sau!

  • Cách xử lý:

    • Trên giao diện Admin: Các Task bị kẹt sẽ hiện biểu tượng ổ khóa. Bạn tích chọn và bấm nút Unlock trên thanh công cụ.

    • Cấu hình tự động nhả khóa: Trong Scheduled Tasks -> Options, mục Task Timeout (seconds) mặc định là 300 giây (5 phút). Sau khoảng thời gian này, nếu Task chưa báo hoàn tất (Status::OK), hệ thống sẽ tự động coi như bị treo và cho phép ghi đè khóa ở lần chạy kế tiếp.

5. Giải phẫu Code PHP: Tự lập trình một Task Plugin chuẩn Joomla 5 (Góc nhìn Backend)

Giả sử trong dự án của bạn, bạn muốn tạo một tác vụ chạy ngầm định kỳ mỗi đêm lúc 02:00 AM để xóa các đơn hàng nháp quá hạn 7 ngày hoặc tự động làm mới file sitemap.xml. Chúng ta sẽ viết một Plugin thuộc nhóm task sử dụng TaskPluginTrait hiện đại của Joomla 5:

PHP

namespace MyCompany\Plugin\Task\CleanupOrders\Extension;

use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\Component\Scheduler\Administrator\Event\ExecuteTaskEvent;
use Joomla\Component\Scheduler\Administrator\Task\Status;
use Joomla\Component\Scheduler\Administrator\Traits\TaskPluginTrait;
use Joomla\Database\DatabaseAwareTrait;
use Joomla\Event\SubscriberInterface;

\defined('_JEXEC') or die;

final class CleanupOrders extends CMSPlugin implements SubscriberInterface
{
    use TaskPluginTrait;
    use DatabaseAwareTrait;

    /**
     * 1. Khai báo danh sách các Routines (Công việc) mà Plugin này cung cấp
     */
    protected const TASKS_MAP = [
        'mycompany.cleanup_draft_orders' => [
            'langConstPrefix' => 'PLG_TASK_CLEANUPORDERS_DRAFT',
            'method'          => 'deleteExpiredDraftOrders', // Tên hàm PHP sẽ được gọi
            'form'            => 'cleanup_params',           // File XML cấu hình tham số (ví dụ: số ngày quá hạn)
        ],
    ];

    /**
     * 2. Đăng ký lắng nghe các sự kiện của Task Scheduler
     */
    public static function getSubscribedEvents(): array
    {
        return [
            'onTaskOptionsList'    => 'advertiseRoutines',
            'onExecuteTask'        => 'standardRoutineHandler',
            'onContentPrepareForm' => 'enhanceTaskItemForm',
        ];
    }

    /**
     * 3. Hàm xử lý logic chính khi đến giờ chạy Cronjob
     */
    protected function deleteExpiredDraftOrders(ExecuteTaskEvent $event): int
    {
        // Lấy các tham số cấu hình từ form của Task trong Admin (mặc định xóa đơn nháp cũ hơn 7 ngày)
        $params   = $event->getArgument('params');
        $daysOld  = (int) ($params->days_threshold ?? 7);

        // Ghi log bắt đầu vào file log của Task Scheduler
        $this->logTask(sprintf('Bắt đầu dọn dẹp các đơn hàng nháp cũ hơn %d ngày...', $daysOld));

        try {
            $db    = $this->getDatabase();
            $query = $db->getQuery(true)
                ->delete($db->quoteName('#__myshop_orders'))
                ->where($db->quoteName('status') . ' = ' . $db->quote('draft'))
                ->where($db->quoteName('created_at') . ' < DATE_SUB(NOW(), INTERVAL :days DAY)')
                ->bind(':days', $daysOld, \Joomla\Database\ParameterType::INTEGER);

            $db->setQuery($query)->execute();
            $deletedRows = $db->getAffectedRows();

            $this->logTask(sprintf('Hoàn tất! Đã xóa thành công %d đơn hàng nháp.', $deletedRows));

            // Trả về mã trạng thái thành công (Status::OK = 0)
            return Status::OK;
        } catch (\Throwable $e) {
            // Ghi log lỗi chi tiết với mức 'error'
            $this->logTask('Lỗi khi xóa đơn hàng nháp: ' . $e->getMessage(), 'error');

            // Trả về mã lỗi để Task Scheduler ghi nhận thất bại và bắn email cảnh báo cho Admin
            return Status::KNOCKOUT;
        }
    }
}

Tính năng thông báo lỗi tự động: Trong cấu hình của mỗi Task trên giao diện Admin (Tab Notifications), bạn có thể bật Notify on Failure = Yes. Ngay khi hàm PHP của bạn trả về mã khác Status::OK (ví dụ Status::KNOCKOUT khi lỗi Database), Joomla sẽ tự động gửi email cảnh báo kèm nội dung bạn đã truyền vào $this->logTask() tới thẳng hộp thư của quản trị viên!

6. Tổng kết bài học

  • Nắm vững kiến trúc Task Scheduler (com_scheduler) giúp quản lý tập trung toàn bộ các tác vụ chạy ngầm ngay trên giao diện Admin.

  • Phân biệt rõ 3 cơ chế kích hoạt: Lazy Scheduler (tiện lợi cho web nhỏ), Web Cron (qua HTTP Token) và CLI Cron (php cli/joomla.php scheduler:run — chuẩn mực cho Production).

  • Hiểu cách xử lý các Task bị treo (locked) trong bảng #__scheduler_tasks và thành thạo việc sử dụng TaskPluginTrait để lập trình các Task Plugin tùy chỉnh bằng PHP.

Trong Bài học 20 (bài học cuối cùng khép lại Phần 2), chúng ta sẽ hoàn thiện quy trình vận hành hệ thống an toàn với: Quy trình sao lưu (Backup), phục hồi với Akeeba Backup và nâng cấp hệ thống an toàn.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí