[ Laravel 5.6 文檔 ] 進階系列 —— 任務調度
簡介
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 命令調度器,當 schedule:run
命令執行後,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
方法。
時區
使用 timezone
方法你可以指定調度任務的執行時間在給定時區內切換:
$schedule->command(‘report:generate‘)
->timezone(‘America/New_York‘)
->at(‘02:00‘)
註:請記住有些時區會使用夏令時,當夏令時改變時,你的調度任務有可能會運行兩次或者根本不會運行,因此,建議你最好不要使用這種時區調度。
避免任務重疊
默認情況下,即使前一個任務仍然在運行調度任務也會運行,要避免這樣的情況,可使用 withoutOverlapping
方法:
$schedule->command(‘emails:send‘)->withoutOverlapping();
在本例中,Artisan 命令 emails:send
每分鐘都會運行 —— 如果該命令沒有在運行的話。如果你的任務在執行時經常大幅度的變化,那麽 withoutOverlapping
方法就非常有用,你不必再去預測給定任務到底要消耗多長時間。
如果需要的話,你可以指定”without overlapping”鎖失效前的分鐘數,默認情況下,這個鎖會在 24 小時後失效:
$schedule->command(‘emails:send‘)->withoutOverlapping(10);
在單臺服務器上運行任務
註:要使用這個功能,必須使用
memcached
或redis
緩存驅動作為應用默認的緩存驅動。此外,所有服務器必須和同一個中央緩存服務器通信。
如果你的應用運行在多臺服務器上,可能需要限制調度任務只在某臺服務器上運行。例如,假設你有一個每個星期五晚上生成新報告的調度任務,如果任務調度器運行在三臺服務器上,調度任務會在三臺服務器上運行並且生成三次報告,不夠優雅!
要告知任務只在單臺服務器上運行,在定義調度任務時使用 onOneServer
方法即可。第一臺獲取到該任務的服務器會給任務上一把原子鎖以阻止其他服務器同時運行該任務:
$schedule->command(‘report:generate‘)
->fridays()
->at(‘17:00‘)
->onOneServer();
維護模式
當 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
和exec
方法有效。
任務鉤子
使用 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
[ Laravel 5.6 文檔 ] 進階系列 —— 任務調度