定時任務排程在 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