1. 程式人生 > 實用技巧 >定時任務排程在 Laravel 中的實現

定時任務排程在 Laravel 中的實現

簡介

Cron 是 UNIX、SOLARIS、LINUX 下的一個十分有用的工具,通過 Cron 指令碼能使計劃任務定期地在系統後臺自動執行。這種計劃任務在 UNIX、SOLARIS、LINUX下術語為 Cron Jobs。Crontab 則是用來記錄在特定時間執行的 Cron 的一個指令碼檔案,Crontab 檔案的每一行均遵守特定的格式:

我們可以在伺服器上通過 crontab -e 來新增或編輯 Cron 條目,通過 crontab -l 檢視已存在的 Cron 條目。更多關於 Cron 的原理和使用細節請自行百度或 Google。

在以前,開發者需要為每一個需要排程的任務編寫一個 Cron 條目,這是很讓人頭疼的事。你的任務排程不在原始碼控制中,你必須使用 SSH 登入到伺服器然後新增這些 Cron 條目。

Laravel 命令排程器允許你流式而又不失優雅地在 Laravel 中定義命令排程,並且伺服器上只需要一個 Cron 條目即可。任務排程定義在 app/Console/Kernel.php 檔案的 schedule 方法中,該方法中已經包含了一個示例。

開啟排程器

下面是你唯一需要新增到伺服器的 Cron 條目,如果你不知道如何新增 Cron 條目到伺服器,可以考慮使用諸如 Laravel Forge 這樣的服務來為管理 Cron 條目:

* * * * * php /path-to-your-project/artisan schedule:run >> /dev/null 2>&1

該 Cron 將會每分鐘呼叫一次 Laravel 命令排程器,然後,Laravel 評估你的排程任務並執行到期的任務。

定義排程

你可以在 App\Console\Kernel 類的 schedule 方法中定義所有排程任務。讓我們從一個排程任務的例子開始,在這個例子中,我們將會在每天午夜排程一個被呼叫的閉包。在這個閉包中我們將會執行一個數據庫操作來清空表:

<?php

namespace App\Console;

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

class Kernel extends ConsoleKernel{
    /**
     * 應用提供的Artisan命令
     * 
     * @var array
     */
    protected $commands = [
        // 
    ];

    /**
     * 定義應用的命令排程
     *
     * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
     * @return void
     * @translator laravelacademy.org
     */
    protected function schedule(Schedule $schedule)
    {
        $schedule->call(function () {
            DB::table('recent_users')->delete();
        })->daily();
    }
}

排程 Artisan 命令

除了排程閉包呼叫外,還可以排程 Artisan 命令和作業系統命令。例如,可以使用 command 方法通過命令名或類來排程一個 Artisan 命令:

$schedule->command('emails:send --force')->daily();
$schedule->command(EmailsCommand::class, ['--force'])->daily();

排程佇列任務

job 方法可用於排程一個佇列任務,通過該方法可以很方便地排程任務而不必呼叫 call 方法手動建立閉包來推送任務到佇列:

$schedule->job(new Heartbeat)->everyFiveMinutes();

排程 Shell 命令

exec 方法可用於呼叫作業系統命令:

$schedule->exec('node /home/forge/script.js')->daily();

排程常用選項

當然,你可以分配多種排程到任務:

方法 描述
->cron('* * * * *'); 在自定義Cron排程上執行任務
->everyMinute(); 每分鐘執行一次任務
->everyFiveMinutes(); 每五分鐘執行一次任務
->everyTenMinutes(); 每十分鐘執行一次任務
->everyFifteenMinutes(); 每十五分鐘執行一次任務
->everyThirtyMinutes(); 每三十分鐘執行一次任務
->hourly(); 每小時執行一次任務
->hourlyAt(17); 每小時第十七分鐘執行一次任務
->daily(); 每天凌晨零點執行任務
->dailyAt('13:00'); 每天13:00執行任務
->twiceDaily(1, 13); 每天1:00 & 13:00執行任務
->weekly(); 每週執行一次任務
->monthly(); 每月執行一次任務
->monthlyOn(4, '15:00'); 每月4號15:00執行一次任務
->quarterly(); 每個季度執行一次
->yearly(); 每年執行一次
->timezone('America/New_York'); 設定時區

這些方法可以和額外的約束一起聯合起來建立一週特定時間執行的、更加細粒度的排程,例如,要在每週一排程一個命令:

$schedule->call(function () {
    // 每週星期一13:00執行一次...
})->weekly()->mondays()->at('13:00');

// 工作日的上午8點到下午5點每小時執行...
$schedule->command('foo')
         ->weekdays()
         ->hourly()
         ->timezone('America/Chicago')
         ->between('8:00', '17:00');

下面是額外的排程約束列表:

方法 描述
->weekdays(); 只在工作日執行任務
->sundays(); 每個星期天執行任務
->mondays(); 每個星期一執行任務
->tuesdays(); 每個星期二執行任務
->wednesdays(); 每個星期三執行任務
->thursdays(); 每個星期四執行任務
->fridays(); 每個星期五執行任務
->saturdays(); 每個星期六執行任務
->between($start, $end); 基於特定時間段執行任務
->when(Closure); 基於特定測試執行任務

介於時間的約束條件
between 方法用於限定一天中特定時間段的任務執行:

$schedule->command('reminders:send')
         ->hourly()
         ->between('7:00', '22:00');

類似地,unlessBetween 方法用於排除指定時間段任務的執行:

$schedule->command('reminders:send')
         ->hourly()
         ->unlessBetween('23:00', '4:00');

真理測試的約束條件

when 方法用於限制任務基於給定真理測試的結果執行。換句話說,如果給定閉包返回true,只要沒有其它約束條件阻止任務執行,該任務就會執行:

$schedule->command('emails:send')->daily()->when(function () {
    return true;
});

skip 方法和 when 相反,如果 skip 方法返回true,排程任務將不會執行:

$schedule->command('emails:send')->daily()->skip(function () {
    return true;
});

使用 when 方法鏈的時候,排程命令將只會執行返回 true 的 when 方法。

避免任務重疊

預設情況下,即使前一個任務仍然在執行排程任務也會執行,要避免這樣的情況,可使用 withoutOverlapping 方法:

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

在本例中,Artisan 命令 emails:send 每分鐘都會執行 —— 如果該命令沒有在執行的話。如果你的任務在執行時經常大幅度的變化,那麼 withoutOverlapping 方法就非常有用,你不必再去預測給定任務到底要消耗多長時間。

如果需要的話,你可以指定"without overlapping"鎖失效前的分鐘數,預設情況下,這個鎖會在 24 小時後失效:

$schedule->command('emails:send')->withoutOverlapping(10);

維護模式

當 Laravel 處於維護模式時,排程任務不會執行,不過,如果你想要在維護模式期間強制執行任務,可以使用 evenInMaintenanceMode 方法:

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

任務輸出

Laravel 排程器為處理排程任務輸出提供了多個方便的方法。首先,使用sendOutputTo 方法,你可以傳送輸出到檔案以便稍後檢查:

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

如果你想要追加輸出到給定檔案,可以使用 appendOutputTo 方法:

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

使用 emailOutputTo 方法,你可以將輸出通過郵件傳送給接收人。使用郵件傳送任務輸出之前,需要配置 Laravel 的郵件服務:

$schedule->command('foo')
     ->daily()
     ->sendOutputTo($filePath)
     ->emailOutputTo('[email protected]');

注:emailOutputTo、 sendOutputTo 和 appendOutputTo 方法只對 command 方法有效,不支援 call 方法。

任務鉤子

使用 before 和 after 方法,你可以指定在排程任務完成之前和之後要執行的程式碼:

$schedule->command('emails:send')
         ->daily()
         ->before(function () {
             // 任務即將開始...
         })
         ->after(function () {
             // 任務已經完成...
         });

Ping URL

使用 pingBefore 和 thenPing方法,排程器可以在任務完成之前和之後自動 ping 給定的 URL。該方法在通知外部服務時很有用,例如 Laravel Envoyer,在排程任務開始或完成的時候:

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

使用 pingBefore($url) 或 thenPing($url) 特性需要安裝 HTTP 庫 Guzzle,可以使用 Composer 包管理器來安裝 Guzzle 依賴到專案:

composer require guzzlehttp/guzzle