跳到內容

工作排程

簡介

在過去,您可能為需要在伺服器上排程的每個任務編寫一個 cron 設定條目。但是,這很快就會變成一件麻煩事,因為您的任務排程不再受到原始碼控制,而且您必須 SSH 進入您的伺服器才能檢視您現有的 cron 條目或新增其他條目。

Laravel 的命令排程器為管理伺服器上的排程任務提供了一種新穎的方法。排程器讓您可以在 Laravel 應用程式本身內流暢且表達性地定義您的命令排程。當使用排程器時,您的伺服器上只需要一個 cron 條目。您的任務排程通常在應用程式的 routes/console.php 檔案中定義。

定義排程

您可以在應用程式的 routes/console.php 檔案中定義所有排程任務。要開始使用,讓我們看看一個範例。在這個範例中,我們將排程一個 Closure,使其在每天午夜被呼叫。在 Closure 內,我們將執行資料庫查詢以清除資料表

1<?php
2 
3use Illuminate\Support\Facades\DB;
4use Illuminate\Support\Facades\Schedule;
5 
6Schedule::call(function () {
7 DB::table('recent_users')->delete();
8})->daily();

除了使用 Closure 進行排程外,您也可以排程可調用物件。可調用物件是簡單的 PHP 類別,其中包含一個 __invoke 方法

1Schedule::call(new DeleteRecentUsers)->daily();

如果您希望將 routes/console.php 檔案保留僅用於命令定義,您可以使用應用程式 bootstrap/app.php 檔案中的 withSchedule 方法來定義排程任務。此方法接受一個 Closure,該 Closure 接收排程器的實例

1use Illuminate\Console\Scheduling\Schedule;
2 
3->withSchedule(function (Schedule $schedule) {
4 $schedule->call(new DeleteRecentUsers)->daily();
5})

如果您想檢視排程任務的概觀以及下次排程執行的時間,您可以使用 schedule:list Artisan 命令

1php artisan schedule:list

排程 Artisan 命令

除了排程 Closure 外,您還可以排程Artisan 命令和系統命令。例如,您可以使用 command 方法,使用命令的名稱或類別來排程 Artisan 命令。

當使用命令的類別名稱排程 Artisan 命令時,您可以傳遞一個額外的命令列引數陣列,這些引數應在命令被調用時提供給命令

1use App\Console\Commands\SendEmailsCommand;
2use Illuminate\Support\Facades\Schedule;
3 
4Schedule::command('emails:send Taylor --force')->daily();
5 
6Schedule::command(SendEmailsCommand::class, ['Taylor', '--force'])->daily();

排程 Artisan Closure 命令

如果您想排程由 Closure 定義的 Artisan 命令,您可以在命令定義後鏈式調用排程相關的方法

1Artisan::command('delete:recent-users', function () {
2 DB::table('recent_users')->delete();
3})->purpose('Delete recent users')->daily();

如果您需要將引數傳遞給 Closure 命令,您可以將它們提供給 schedule 方法

1Artisan::command('emails:send {user} {--force}', function ($user) {
2 // ...
3})->purpose('Send emails to the specified user')->schedule(['Taylor', '--force'])->daily();

排程佇列任務

job 方法可用於排程佇列任務。此方法提供了一種方便的方式來排程佇列任務,而無需使用 call 方法來定義 Closure 以將任務加入佇列

1use App\Jobs\Heartbeat;
2use Illuminate\Support\Facades\Schedule;
3 
4Schedule::job(new Heartbeat)->everyFiveMinutes();

可以為 job 方法提供可選的第二和第三個引數,它們指定應該用於將任務加入佇列的佇列名稱和佇列連線

1use App\Jobs\Heartbeat;
2use Illuminate\Support\Facades\Schedule;
3 
4// Dispatch the job to the "heartbeats" queue on the "sqs" connection...
5Schedule::job(new Heartbeat, 'heartbeats', 'sqs')->everyFiveMinutes();

排程 Shell 命令

exec 方法可用於向作業系統發出命令

1use Illuminate\Support\Facades\Schedule;
2 
3Schedule::exec('node /home/forge/script.js')->daily();

排程頻率選項

我們已經看過一些關於如何設定任務以在指定間隔執行的範例。但是,您可以為任務分配更多任務排程頻率

方法 描述
->cron('* * * * *'); 在自訂 cron 排程上執行任務。
->everySecond(); 每秒執行任務。
->everyTwoSeconds(); 每兩秒執行任務。
->everyFiveSeconds(); 每五秒執行任務。
->everyTenSeconds(); 每十秒執行任務。
->everyFifteenSeconds(); 每十五秒執行任務。
->everyTwentySeconds(); 每二十秒執行任務。
->everyThirtySeconds(); 每三十秒執行任務。
->everyMinute(); 每分鐘執行任務。
->everyTwoMinutes(); 每兩分鐘執行任務。
->everyThreeMinutes(); 每三分鐘執行任務。
->everyFourMinutes(); 每四分鐘執行任務。
->everyFiveMinutes(); 每五分鐘執行任務。
->everyTenMinutes(); 每十分鐘執行任務。
->everyFifteenMinutes(); 每十五分鐘執行任務。
->everyThirtyMinutes(); 每三十分鐘執行任務。
->hourly(); 每小時執行任務。
->hourlyAt(17); 在每小時的第 17 分執行任務。
->everyOddHour($minutes = 0); 每隔一小時執行任務。
->everyTwoHours($minutes = 0); 每兩小時執行任務。
->everyThreeHours($minutes = 0); 每三小時執行任務。
->everyFourHours($minutes = 0); 每四小時執行任務。
->everySixHours($minutes = 0); 每六小時執行任務。
->daily(); 每天午夜執行任務。
->dailyAt('13:00'); 每天 13:00 執行任務。
->twiceDaily(1, 13); 每天 1:00 和 13:00 執行任務。
->twiceDailyAt(1, 13, 15); 每天 1:15 和 13:15 執行任務。
->weekly(); 每週日 00:00 執行任務。
->weeklyOn(1, '8:00'); 每週一 8:00 執行任務。
->monthly(); 每月第一天 00:00 執行任務。
->monthlyOn(4, '15:00'); 每月 4 號 15:00 執行任務。
->twiceMonthly(1, 16, '13:00'); 每月 1 號和 16 號 13:00 執行任務。
->lastDayOfMonth('15:00'); 在每個月的最後一天 15:00 執行任務。
->quarterly(); 每季的第一天 00:00 執行任務。
->quarterlyOn(4, '14:00'); 每季的第 4 號 14:00 執行任務。
->yearly(); 每年第一天 00:00 執行任務。
->yearlyOn(6, 1, '17:00'); 每年 6 月 1 號 17:00 執行任務。
->timezone('America/New_York'); 設定任務的時區。

這些方法可以與其他約束結合使用,以建立更精細調整的排程,這些排程僅在每週的特定幾天執行。例如,您可以排程一個命令每週一執行

1use Illuminate\Support\Facades\Schedule;
2 
3// Run once per week on Monday at 1 PM...
4Schedule::call(function () {
5 // ...
6})->weekly()->mondays()->at('13:00');
7 
8// Run hourly from 8 AM to 5 PM on weekdays...
9Schedule::command('foo')
10 ->weekdays()
11 ->hourly()
12 ->timezone('America/Chicago')
13 ->between('8:00', '17:00');

其他排程約束的列表可以在下面找到

方法 描述
->weekdays(); 將任務限制在平日。
->weekends(); 將任務限制在週末。
->sundays(); 將任務限制在星期日。
->mondays(); 將任務限制在星期一。
->tuesdays(); 將任務限制在星期二。
->wednesdays(); 將任務限制在星期三。
->thursdays(); 將任務限制在星期四。
->fridays(); 將任務限制在星期五。
->saturdays(); 將任務限制在星期六。
->days(array|mixed); 將任務限制在特定日期。
->between($startTime, $endTime); 將任務限制在開始時間和結束時間之間執行。
->unlessBetween($startTime, $endTime); 限制任務不在開始時間和結束時間之間執行。
->when(Closure); 根據真值測試限制任務。
->environments($env); 將任務限制在特定環境。

日期約束

days 方法可用於將任務的執行限制在每週的特定日期。例如,您可以排程一個命令在星期日和星期三每小時執行一次

1use Illuminate\Support\Facades\Schedule;
2 
3Schedule::command('emails:send')
4 ->hourly()
5 ->days([0, 3]);

或者,您可以在定義任務應執行的日期時使用 Illuminate\Console\Scheduling\Schedule 類別上可用的常數

1use Illuminate\Support\Facades;
2use Illuminate\Console\Scheduling\Schedule;
3 
4Facades\Schedule::command('emails:send')
5 ->hourly()
6 ->days([Schedule::SUNDAY, Schedule::WEDNESDAY]);

時間之間約束

between 方法可用於根據一天中的時間限制任務的執行

1Schedule::command('emails:send')
2 ->hourly()
3 ->between('7:00', '22:00');

同樣地,unlessBetween 方法可用於排除在一段時間內執行任務

1Schedule::command('emails:send')
2 ->hourly()
3 ->unlessBetween('23:00', '4:00');

真值測試約束

when 方法可用於根據給定的真值測試結果限制任務的執行。換句話說,如果給定的 Closure 返回 true,則只要沒有其他約束條件阻止任務執行,該任務就會執行

1Schedule::command('emails:send')->daily()->when(function () {
2 return true;
3});

skip 方法可以被視為 when 的反向。如果 skip 方法返回 true,則排程任務將不會執行

1Schedule::command('emails:send')->daily()->skip(function () {
2 return true;
3});

當使用鏈式 when 方法時,只有當所有 when 條件都返回 true 時,排程命令才會執行。

環境約束

environments 方法可用於僅在給定環境(由 APP_ENV 環境變數定義)中執行任務

1Schedule::command('emails:send')
2 ->daily()
3 ->environments(['staging', 'production']);

時區

使用 timezone 方法,您可以指定排程任務的時間應在給定的時區內解釋

1use Illuminate\Support\Facades\Schedule;
2 
3Schedule::command('report:generate')
4 ->timezone('America/New_York')
5 ->at('2:00')

如果您重複將相同的時區分配給所有排程任務,您可以透過在應用程式的 app 設定檔中定義 schedule_timezone 選項來指定應分配給所有排程的時區

1'timezone' => 'UTC',
2 
3'schedule_timezone' => 'America/Chicago',

請記住,某些時區使用日光節約時間。當日光節約時間變更發生時,您的排程任務可能會執行兩次,甚至根本不執行。因此,我們建議盡可能避免時區排程。

防止任務重疊

預設情況下,即使任務的先前實例仍在執行,排程任務也會執行。為防止這種情況,您可以使用 withoutOverlapping 方法

1use Illuminate\Support\Facades\Schedule;
2 
3Schedule::command('emails:send')->withoutOverlapping();

在這個範例中,如果 emails:send Artisan 命令尚未執行,則每分鐘執行一次。如果您有執行時間差異很大的任務,並且阻止您準確預測給定任務需要多長時間,則 withoutOverlapping 方法特別有用。

如果需要,您可以指定在「不重疊」鎖定過期之前必須經過多少分鐘。預設情況下,鎖定將在 24 小時後過期

1Schedule::command('emails:send')->withoutOverlapping(10);

在幕後,withoutOverlapping 方法利用您應用程式的快取來取得鎖定。如有必要,您可以使用 schedule:clear-cache Artisan 命令清除這些快取鎖定。這通常僅在任務因意外伺服器問題而卡住時才需要。

在單一伺服器上執行任務

若要使用此功能,您的應用程式必須使用 databasememcacheddynamodbredis 快取驅動程式作為應用程式的預設快取驅動程式。此外,所有伺服器都必須與同一個中央快取伺服器通訊。

如果您的應用程式排程器在多個伺服器上執行,您可以將排程任務限制為僅在單一伺服器上執行。例如,假設您有一個排程任務,每週五晚上產生一份新的報告。如果任務排程器在三台工作伺服器上執行,則排程任務將在所有三台伺服器上執行,並產生三次報告。這可不好!

若要指示任務應僅在單一伺服器上執行,請在定義排程任務時使用 onOneServer 方法。第一個取得任務的伺服器將安全地鎖定該任務,以防止其他伺服器同時執行相同的任務

1use Illuminate\Support\Facades\Schedule;
2 
3Schedule::command('report:generate')
4 ->fridays()
5 ->at('17:00')
6 ->onOneServer();

命名單一伺服器任務

有時您可能需要排程同一個任務以使用不同的參數進行調度,同時仍然指示 Laravel 在單一伺服器上執行任務的每個排列。為了實現這一點,您可以透過 name 方法為每個排程定義分配一個唯一的名稱

1Schedule::job(new CheckUptime('https://laravel.dev.org.tw'))
2 ->name('check_uptime:laravel.com')
3 ->everyFiveMinutes()
4 ->onOneServer();
5 
6Schedule::job(new CheckUptime('https://vapor.laravel.com'))
7 ->name('check_uptime:vapor.laravel.com')
8 ->everyFiveMinutes()
9 ->onOneServer();

同樣地,如果排程 Closure 打算在單一伺服器上執行,則必須為其分配一個名稱

1Schedule::call(fn () => User::resetApiRequestCount())
2 ->name('reset-api-request-count')
3 ->daily()
4 ->onOneServer();

背景任務

預設情況下,同時排程的多個任務將根據它們在 schedule 方法中定義的順序依序執行。如果您有長時間執行的任務,這可能會導致後續任務的開始時間比預期的晚得多。如果您想在背景中執行任務,以便它們可以同時執行,您可以使用 runInBackground 方法

1use Illuminate\Support\Facades\Schedule;
2 
3Schedule::command('analytics:report')
4 ->daily()
5 ->runInBackground();

runInBackground 方法只能在透過 commandexec 方法排程任務時使用。

維護模式

當應用程式處於維護模式時,您的應用程式排程任務將不會執行,因為我們不希望您的任務干擾您可能在伺服器上執行的任何未完成的維護。但是,如果您想強制任務即使在維護模式下也執行,您可以在定義任務時調用 evenInMaintenanceMode 方法

1Schedule::command('emails:send')->evenInMaintenanceMode();

排程群組

當定義具有相似設定的多個排程任務時,您可以使用 Laravel 的任務群組功能來避免重複每個任務的相同設定。群組任務簡化了您的程式碼,並確保相關任務之間的一致性。

若要建立排程任務群組,請調用所需的任務設定方法,然後調用 group 方法。group 方法接受一個 Closure,該 Closure 負責定義共用指定設定的任務

1use Illuminate\Support\Facades\Schedule;
2 
3Schedule::daily()
4 ->onOneServer()
5 ->timezone('America/New_York')
6 ->group(function () {
7 Schedule::command('emails:send --force');
8 Schedule::command('emails:prune');
9 });

執行排程器

現在我們已經學習了如何定義排程任務,讓我們討論如何在我們的伺服器上實際執行它們。schedule:run Artisan 命令將評估您的所有排程任務,並根據伺服器的目前時間判斷它們是否需要執行。

因此,當使用 Laravel 的排程器時,我們只需要在我們的伺服器上新增一個 cron 設定條目,該條目每分鐘執行一次 schedule:run 命令。如果您不知道如何將 cron 條目新增到您的伺服器,請考慮使用託管平台,例如 Laravel Cloud,它可以為您管理排程任務的執行

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

次分鐘排程任務

在大多數作業系統上,cron 工作限制為最多每分鐘執行一次。但是,Laravel 的排程器允許您排程以更頻繁的間隔執行的任務,甚至頻繁到每秒一次

1use Illuminate\Support\Facades\Schedule;
2 
3Schedule::call(function () {
4 DB::table('recent_users')->delete();
5})->everySecond();

當在您的應用程式中定義次分鐘任務時,schedule:run 命令將繼續執行到目前分鐘結束,而不是立即退出。這允許命令在整個分鐘內調用所有需要的次分鐘任務。

由於執行時間超過預期的次分鐘任務可能會延遲後續次分鐘任務的執行,因此建議所有次分鐘任務調度佇列任務或背景命令來處理實際的任務處理

1use App\Jobs\DeleteRecentUsers;
2 
3Schedule::job(new DeleteRecentUsers)->everyTenSeconds();
4 
5Schedule::command('users:delete')->everyTenSeconds()->runInBackground();

中斷次分鐘任務

由於當定義次分鐘任務時,schedule:run 命令在調用後的整個分鐘內執行,因此您有時可能需要在部署應用程式時中斷命令。否則,已經在執行的 schedule:run 命令實例將繼續使用您應用程式先前部署的程式碼,直到目前分鐘結束。

若要中斷正在進行的 schedule:run 調用,您可以將 schedule:interrupt 命令新增到應用程式的部署腳本中。此命令應在您的應用程式完成部署後調用

1php artisan schedule:interrupt

在本機執行排程器

通常,您不會將排程器 cron 條目新增到您的本機開發機器。相反,您可以使用 schedule:work Artisan 命令。此命令將在前台執行,並每分鐘調用排程器,直到您終止命令。當定義次分鐘任務時,排程器將在每分鐘內繼續執行以處理這些任務

1php artisan schedule:work

任務輸出

Laravel 排程器提供了幾種方便的方法來處理排程任務產生的輸出。首先,使用 sendOutputTo 方法,您可以將輸出傳送到檔案以供稍後檢查

1use Illuminate\Support\Facades\Schedule;
2 
3Schedule::command('emails:send')
4 ->daily()
5 ->sendOutputTo($filePath);

如果您想將輸出附加到給定檔案,您可以使用 appendOutputTo 方法

1Schedule::command('emails:send')
2 ->daily()
3 ->appendOutputTo($filePath);

使用 emailOutputTo 方法,您可以將輸出透過電子郵件發送到您選擇的電子郵件地址。在透過電子郵件發送任務的輸出之前,您應該設定 Laravel 的電子郵件服務

1Schedule::command('report:generate')
2 ->daily()
3 ->sendOutputTo($filePath)
4 ->emailOutputTo('[email protected]');

如果您只想在排程的 Artisan 或系統命令以非零退出代碼終止時透過電子郵件發送輸出,請使用 emailOutputOnFailure 方法

1Schedule::command('report:generate')
2 ->daily()
3 ->emailOutputOnFailure('[email protected]');

emailOutputToemailOutputOnFailuresendOutputToappendOutputTo 方法是 commandexec 方法專有的。

任務掛鉤

使用 beforeafter 方法,您可以指定在排程任務執行之前和之後要執行的程式碼

1use Illuminate\Support\Facades\Schedule;
2 
3Schedule::command('emails:send')
4 ->daily()
5 ->before(function () {
6 // The task is about to execute...
7 })
8 ->after(function () {
9 // The task has executed...
10 });

onSuccessonFailure 方法允許您指定在排程任務成功或失敗時要執行的程式碼。失敗表示排程的 Artisan 或系統命令以非零退出代碼終止

1Schedule::command('emails:send')
2 ->daily()
3 ->onSuccess(function () {
4 // The task succeeded...
5 })
6 ->onFailure(function () {
7 // The task failed...
8 });

如果您的命令有輸出可用,您可以透過類型提示 Illuminate\Support\Stringable 實例作為掛鉤的 Closure 定義的 $output 引數,在 afteronSuccessonFailure 掛鉤中存取它

1use Illuminate\Support\Stringable;
2 
3Schedule::command('emails:send')
4 ->daily()
5 ->onSuccess(function (Stringable $output) {
6 // The task succeeded...
7 })
8 ->onFailure(function (Stringable $output) {
9 // The task failed...
10 });

Ping URL

使用 pingBeforethenPing 方法,排程器可以在任務執行之前或之後自動 Ping 給定的 URL。此方法對於通知外部服務(例如 Envoyer)您的排程任務正在開始或已完成執行非常有用

1Schedule::command('emails:send')
2 ->daily()
3 ->pingBefore($url)
4 ->thenPing($url);

pingOnSuccesspingOnFailure 方法可用於僅在任務成功或失敗時 Ping 給定的 URL。失敗表示排程的 Artisan 或系統命令以非零退出代碼終止

1Schedule::command('emails:send')
2 ->daily()
3 ->pingOnSuccess($successUrl)
4 ->pingOnFailure($failureUrl);

pingBeforeIfthenPingIfpingOnSuccessIfpingOnFailureIf 方法可用於僅在給定條件為 true 時 Ping 給定的 URL

1Schedule::command('emails:send')
2 ->daily()
3 ->pingBeforeIf($condition, $url)
4 ->thenPingIf($condition, $url);
5 
6Schedule::command('emails:send')
7 ->daily()
8 ->pingOnSuccessIf($condition, $successUrl)
9 ->pingOnFailureIf($condition, $failureUrl);

事件

Laravel 在排程過程中調度各種事件。您可以為以下任何事件定義監聽器

事件名稱
Illuminate\Console\Events\ScheduledTaskStarting
Illuminate\Console\Events\ScheduledTaskFinished
Illuminate\Console\Events\ScheduledBackgroundTaskFinished
Illuminate\Console\Events\ScheduledTaskSkipped
Illuminate\Console\Events\ScheduledTaskFailed

Laravel 是最具生產力的方式來
建構、部署和監控軟體。