任務(wù)調(diào)度
任務(wù)調(diào)度
任務(wù)調(diào)度
簡介
過去,你可能需要在服務(wù)器上為每一個調(diào)度任務(wù)去創(chuàng)建 Cron 入口。但是這種方式很快就會變得不友好,因為這些任務(wù)調(diào)度不在源代碼中,并且你每次都需要通過 SSH 鏈接登錄到服務(wù)器中才能增加 Cron 入口。
Laravel 命令行調(diào)度器允許你在 Laravel 中對命令調(diào)度進(jìn)行清晰流暢的定義。且使用這個任務(wù)調(diào)度器時,你只需要在你的服務(wù)器上創(chuàng)建單個 Cron 入口接口。你的任務(wù)調(diào)度在 app/Console/Kernel.php
的 schedule
方法中進(jìn)行定義。為了幫助你更好的入門,這個方法中有個簡單的例子。
啟動調(diào)度器
當(dāng)使用這個調(diào)度器時,你只需要把下面的 Cron 入口添加到你的服務(wù)器中即可。如果你不知道怎么在服務(wù)器中添加 Cron 入口,可以考慮使用一些服務(wù)來管理 Cron 入口,比如 Laravel Forge:
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
這個 Cron 為每分鐘執(zhí)行一次 Laravel 的命令行調(diào)度器。當(dāng) schedule:run
命令被執(zhí)行的時候,Laravel 會根據(jù)你的調(diào)度執(zhí)行預(yù)定的程序。
定義調(diào)度
你可以在 App\Console\Kernel
類的 schedule
方法中定義所有的調(diào)度任務(wù)。在開始之前,讓我們來看一個例子。在這個例子中,我們計劃每天午夜調(diào)用一個閉包。在閉包中,我們執(zhí)行一個數(shù)據(jù)庫查詢來清空一張表:
<?php namespace App\Console; use Illuminate\Support\Facades\DB; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; class Kernel extends ConsoleKernel{ /** * 應(yīng)用里的自定義 Artisan 命令 * * @var array */ protected $commands = [ // ]; /** * 定義計劃任務(wù) * * @param \Illuminate\Console\Scheduling\Schedule $schedule * @return void */ protected function schedule(Schedule $schedule) { $schedule->call(function () { DB::table('recent_users')->delete(); })->daily(); } }
除了使用閉包來定義任務(wù)調(diào)度外,你也可以用 invokable objects。
$schedule->call(new DeleteRecentUsers)->daily();
Artisan 命令調(diào)度
除了使用調(diào)用閉包這種方式來調(diào)度外,你還可以調(diào)用 Artisan 命令 和操作系統(tǒng)命令。比如,你可以給 command
方法傳遞命令名稱或者類來調(diào)度一個 Artisan 命令。
$schedule->command('emails:send --force')->daily(); $schedule->command(EmailsCommand::class, ['--force'])->daily();
隊列任務(wù)調(diào)度
job
方法可以用來調(diào)度 隊列任務(wù)。此方法提供了一種快捷的方式來調(diào)度任務(wù),而無需使用 call
方法創(chuàng)建閉包來調(diào)度任務(wù)。
$schedule->job(new Heartbeat)->everyFiveMinutes(); // 分發(fā)任務(wù)到「heartbeats」隊列... $schedule->job(new Heartbeat, 'heartbeats')->everyFiveMinutes();
Shell 命令調(diào)度
exec
方法可用于向操作系統(tǒng)發(fā)送命令:
$schedule->exec('node /home/forge/script.js')->daily();
調(diào)度頻率設(shè)置
當(dāng)然了,你可以給你的任務(wù)分配多種調(diào)度計劃:
方法 | 描述 |
---|---|
->cron('* * * * *'); | 自定義 Cron 時間表執(zhí)行任務(wù) |
->everyMinute(); | 每分鐘執(zhí)行一次任務(wù) |
->everyFiveMinutes(); | 每五分鐘執(zhí)行一次任務(wù) |
->everyTenMinutes(); | 每十分鐘執(zhí)行一次任務(wù) |
->everyFifteenMinutes(); | 每十五分鐘執(zhí)行一次任務(wù) |
->everyThirtyMinutes(); | 每三十分鐘執(zhí)行一次任務(wù) |
->hourly(); | 每小時執(zhí)行一次任務(wù) |
->hourlyAt(17); | 每小時第 17 分鐘執(zhí)行一次任務(wù) |
->daily(); | 每天午夜執(zhí)行一次任務(wù)(譯者注:每天零點) |
->dailyAt('13:00'); | 每天 13 點執(zhí)行一次任務(wù) |
->twiceDaily(1, 13); | 每天 1 點 和 13 點分別執(zhí)行一次任務(wù) |
->weekly(); | 每周執(zhí)行一次任務(wù) |
->weeklyOn(1, '8:00'); | 每周一的 8 點執(zhí)行一次任務(wù) |
->monthly(); | 每月執(zhí)行一次任務(wù) |
->monthlyOn(4, '15:00'); | 每月 4 號的 15 點執(zhí)行一次任務(wù) |
->quarterly(); | 每季度執(zhí)行一次任務(wù) |
->yearly(); | 每年執(zhí)行一次任務(wù) |
->timezone('America/New_York'); | 設(shè)定時區(qū) |
結(jié)合其他一些特定條件,我們可以生成在一周中特定時間運行的任務(wù)。舉個例子,在每周一執(zhí)行命令:
// 每周一 13:00 執(zhí)行... $schedule->call(function () { // })->weekly()->mondays()->at('13:00'); // 工作日(周一至周五) 8點 至 17 點每小時執(zhí)行一次... $schedule->command('foo') ->weekdays() ->hourly() ->timezone('America/Chicago') ->between('8:00', '17:00');
額外的限制條件列表如下:
方法 | 描述 |
---|---|
->weekdays(); | 限制任務(wù)在工作日執(zhí)行 |
->weekends(); | 限制任務(wù)在周末執(zhí)行 |
->sundays(); | 限制任務(wù)在周日執(zhí)行 |
->mondays(); | 限制任務(wù)在周一執(zhí)行 |
->tuesdays(); | 限制任務(wù)在周二執(zhí)行 |
->wednesdays(); | 限制任務(wù)在周三執(zhí)行 |
->thursdays(); | 限制任務(wù)在周四執(zhí)行 |
->fridays(); | 限制任務(wù)在周五執(zhí)行 |
->saturdays(); | 限制任務(wù)在周六執(zhí)行 |
->between($start, $end); | 限制任務(wù)在 $start 和 $end 之間執(zhí)行 |
->when(Closure); | 當(dāng)閉包返回為真時執(zhí)行 |
->environments($env); | 限制任務(wù)在特定環(huán)境中執(zhí)行 |
時間范圍限制
使用 between
來限制任務(wù)在一天中的某個時間段來執(zhí)行:
$schedule->command('reminders:send') ->hourly() ->between('7:00', '22:00');
或者使用 unlessBetween
方法來為任務(wù)排除一個時間段:
$schedule->command('reminders:send') ->hourly() ->unlessBetween('23:00', '4:00');
閉包測試限制
使用 when
方法來根據(jù)測試結(jié)果來執(zhí)行任務(wù)。也就是說,如果給定的閉包返回結(jié)果為 true
,只要沒有其他約束條件阻止任務(wù)運行,任務(wù)就會一直執(zhí)行下去:
$schedule->command('emails:send')->daily()->when(function () { return true; });
skip
方法可以看做是 when
方法的逆過程。如果 skip
方法返回 true
,任務(wù)就不會執(zhí)行:
$schedule->command('emails:send')->daily()->skip(function () { return true; });
使用鏈?zhǔn)秸{(diào)用 when
方法時,只有所有的 when
都返回 true
時,任務(wù)才會執(zhí)行。
環(huán)境約束
environments
方法可用于僅在給定環(huán)境中執(zhí)行任務(wù):
$schedule->command('emails:send') ->daily() ->environments(['staging', 'production']);
時區(qū)
使用 timezone
方法,你可以指定任務(wù)在給定的時區(qū)內(nèi)執(zhí)行:
$schedule->command('report:generate') ->timezone('America/New_York') ->at('02:00')
如果要為所有計劃任務(wù)分配相同的時區(qū),則可能希望在 app/Console/Kernel.php
文件中定義 scheduleTimezone
方法。 此方法應(yīng)返回應(yīng)分配給所有計劃任務(wù)的默認(rèn)時區(qū):
/** * 獲取默認(rèn)情況下應(yīng)為預(yù)定事件使用的時區(qū)。 * * @return \DateTimeZone|string|null */ protected function scheduleTimezone(){ return 'America/Chicago'; }
{note} 請記住,有些時區(qū)會使用夏令時。當(dāng)夏時制時間發(fā)生更改時,你的任務(wù)可能會執(zhí)行兩次,甚至根本不會執(zhí)行。所以我們建議盡可能避免使用時區(qū)來安排計劃任務(wù)。
避免任務(wù)重復(fù)
默認(rèn)情況下,即使之前的任務(wù)還在執(zhí)行,調(diào)度內(nèi)任務(wù)也會執(zhí)行。你可以使用 withoutOverlapping
方法來避免這種情況:
$schedule->command('emails:send')->withoutOverlapping();
在這個例子中,如果 emails:send
Artisan 命令 沒有正在運行,它將會每分鐘執(zhí)行一次。如果你的任務(wù)執(zhí)行時間不確定,且你又不能準(zhǔn)確預(yù)估出任務(wù)的執(zhí)行時間,那么 withoutOverlapping
方法會顯得特別有用。
如果有需要,你可以指定「without overlapping」鎖指定的時間范圍。默認(rèn)情況下,鎖將在 24 小時后過期。
$schedule->command('emails:send')->withoutOverlapping(10);
任務(wù)只運行在一臺服務(wù)器上
{note} 要使用這個特性,你的應(yīng)用默認(rèn)緩存驅(qū)動必須是
memcached
或者redis
。除此之外,所有的服務(wù)器必須使用同一個中央緩存服務(wù)器通信。
如果你的應(yīng)用在多個服務(wù)器上運行,你可能需要限制你的調(diào)度任務(wù)只在單個服務(wù)器上運行。假設(shè)你有一個調(diào)度任務(wù):每周五晚生成一份新報告。如果這個任務(wù)調(diào)度器在三個服務(wù)器上運行,那么這個任務(wù)會在三臺服務(wù)器上運行且生成三份報告。這樣不好!
為了說明任務(wù)應(yīng)該在單個服務(wù)器上運行,在定義調(diào)度任務(wù)時使用 onOneServer
方法。第一個獲取到任務(wù)的服務(wù)器會生成一個原子鎖,用來防止其他服務(wù)器在同一時刻執(zhí)行相同任務(wù)。
$schedule->command('report:generate') ->fridays() ->at('17:00') ->onOneServer();
后臺任務(wù)
默認(rèn)情況下,同時調(diào)度的多個命令將按順序執(zhí)行。如果你有長時間運行的命令,這可能會導(dǎo)致后續(xù)命令的啟動時間比預(yù)期的要晚。因此,你想在后臺同時運行命令,可以使用 runInBackground
方法:
$schedule->command('analytics:report') ->daily() ->runInBackground();
維護(hù)模式
Laravel 的隊列任務(wù)在 維護(hù)模式 下不會運行。因為我們不想你的調(diào)度任務(wù)干擾到你服務(wù)器上可能還未完成的項目。不過,如果你確實是想在維護(hù)模式下強(qiáng)制調(diào)度任務(wù)執(zhí)行,你可以使用 evenInMaintenanceMode
方法:
$schedule->command('emails:send')->evenInMaintenanceMode();
任務(wù)輸出
Laravel 調(diào)度器提供了一些方便的方法來處理調(diào)度任務(wù)輸出。首先,你可以使用 sendOutputTo
方法來輸出到文件以便于后續(xù)檢查:
$schedule->command('emails:send') ->daily() ->sendOutputTo($filePath);
如果希望將輸出 附加
到給定文件,可以使用 appendOutputTo
方法
$schedule->command('emails:send') ->daily() ->appendOutputTo($filePath);
使用 emailOutputTo
方法,你可以將輸出發(fā)送到指定郵箱。在使用郵件發(fā)送之前,你需要配置 Laravel 的 郵件服務(wù):
$schedule->command('foo') ->daily() ->sendOutputTo($filePath) ->emailOutputTo('foo@example.com');
{note}
emailOutputTo
,sendOutputTo
和appendOutputTo
方法是command
和exec
獨有的。
任務(wù)鉤子
使用 before
和 after
方法,你可以在調(diào)度任務(wù)執(zhí)行前或者執(zhí)行后來執(zhí)行特定代碼:
$schedule->command('emails:send') ->daily() ->before(function () { // Task is about to start... }) ->after(function () { // Task is complete... });
Ping 網(wǎng)址
使用 pingBefore
和 thenPing
方法,你可以在任務(wù)執(zhí)行前或者執(zhí)行后來 ping 指定的 URL。這個方法在通知外部服務(wù)(比如 Laravel Envoyer)時將會特別有用:
$schedule->command('emails:send') ->daily() ->pingBefore($url) ->thenPing($url);
只有在給定條件為 true
時,才能使用 pingBeforeIf
和 thenPingIf
方法 ping 指定的 URL:
$schedule->command('emails:send') ->daily() ->pingBeforeIf($condition, $url) ->thenPingIf($condition, $url);
所有 ping 方法都需要 Guzzle HTTP 庫。你可以使用 Composer 來添加 Guzzle 到你的項目中:
composer require guzzlehttp/guzzle