v13.39.0 的官方原始碼,將任務排程的基本操作套用到套件開發。提供者的載入請參閱套件自動偵測的內部結構,設定的發佈請參閱套件設定的合併與快取。
在 Schedule 解析後註冊
在一般的 Laravel 應用程式中,FoundationServiceProvider 會將 Schedule::class 註冊為 singleton。其建立流程會呼叫 Console Kernel 的 resolveConsoleSchedule(),並套用應用程式的時區與排程器用的快取儲存。
套件應擴充這個共用的 Schedule,而不是自行建立 new Schedule() 就結束。以下提供者假設 Acme\Courier\ 透過 Composer 的 PSR-4 對應到 src/,且套件中有 config/courier.php。
callAfterResolving() 在尚未解析時會等待之後的解析,若已經解析則會取得實例並當場呼叫回呼。如此一來,即使其他提供者先使用了 Schedule,也能完成註冊。詳情請參閱套件的服務解析後掛鉤。
runningInConsole() 並不是判斷特定的 Artisan 指令。此範例將透過 HTTP 使用 Schedule 的用途排除在外。此外,註冊掛鉤的提供者本身不應設為延遲提供者,因為若 boot() 未在需要的時間點之前執行,掛鉤也不會被註冊。
事件的註冊與任務的執行是兩回事。載入這個提供者並不會當場執行 Heartbeat。單純註冊也不會啟動 cron 或
schedule:work。由使用者明確啟用
套件端的config/courier.php 預設為停用。
schedule.enabled 改為 true。此範例會在註冊時讀取設定值。時區預設為 UTC,若選擇其他時區,也需考慮夏令時間造成的重複執行或漏執行。
由於設定是淺層合併,註冊器中也要為每個鍵指定預設值。如此一來,即使部分覆寫導致 schedule 陣列中的其他鍵消失,仍能以基本設定完成註冊。
防止重複註冊到同一個 Schedule
以下的src/CourierSchedule.php 會註冊一個只記錄一筆日誌的最小 Heartbeat。透過 WeakMap,確保此註冊器對同一個 Schedule 物件只會加入一次。
Schedule::call()、exec()、job() 會將事件加入陣列。它們並不是會找出同名事件並加以替換或去除重複的 API。
事件名稱也與鎖定的相容性有關
name() 是 description() 的別名,但對鎖定名稱的影響會因事件類型而異。以下是預設的 mutex 名稱。
在 CallbackEvent 中,需在
withoutOverlapping() 或 onOneServer() 之前先設定非空的名稱。若未設定名稱,這些方法會擲出例外。
job() 通常會以 Job 類別名稱等作為初始名稱。但若以不同的引數或對象註冊同一個 Job,請為每個對象明確指定名稱。僅變更引數,初始名稱並不會自動變成不同的名稱。
SyncCatalogJob 是套件所提供的 Job 類別範例。Job 的實作方式請參閱 Queue 與 Job。
變更名稱、指令或 cron 運算式時,依事件類型不同,鎖定的識別碼也會改變。在新舊版本程序並存的部署中,即使是相同的處理,也可能取得不同的鎖定。請勿將其視同單純的顯示文字變更,而應將任務名稱作為穩定的公開規格來管理。
分開思考 onOneServer 與 withoutOverlapping
onOneServer()會選出在該執行時刻負責執行的伺服器。經確認,CacheSchedulingMutex會在事件的 mutex 名稱後加上時與分,並以 1 小時的有效期限保存。這與配合任務結束而釋放的防重疊鎖定不同。withoutOverlapping()會在前一次執行仍在進行時防止重疊。所指定期限的單位為分鐘,預設為 1,440 分鐘。期限過短時,鎖定會在處理完成前失效。- 當
job()將工作推送到 Queue 時,排程事件的完成並不代表 Job 處理完成。Worker 上的重複控制,需另外在 Job 端設計唯一 Job 或WithoutOverlapping中介層等機制。
onOneServer() 的使用者,應將官方文件指定的 database、memcached、dynamodb、redis 共用快取配置作為必要條件加以說明。鎖定並不保證業務處理的 exactly-once,處理本身也需要具備重新執行也不會出錯的特性。
重量級相依性在執行時解析
上述 Heartbeat 中的LoggerInterface,是在事件執行時由 CallbackEvent 使用容器的 call() 解析。這是一種在註冊掛鉤時不輸出日誌、也不建立用戶端的配置。
不在建構子中放置外部連線或業務處理的設計,在套件的 Artisan 指令註冊與相依性解析中也很重要。
此外,
call() 與 job() 屬於 CallbackEvent,因此無法使用 runInBackground()。耗時的處理可委派給 Queue,或視需要選擇 command() 的背景執行。
不要搶走整個應用程式的設定
Schedule::useCache() 會變更 Schedule 所持有的 mutex 物件的儲存。這並不是只針對套件事件的設定,也會影響共用同一個 mutex 的事件。請勿在套件的 boot() 中無條件呼叫,而應將其視為由使用端應用程式選擇的設定。
同樣地,套件不應擅自變更 Schedule::alwaysOnOneServer()、排程器的暫停,以及維護模式期間的執行方針。
變更設定後重新建立 Schedule
範例中的enabled、cron 運算式與時區都是註冊時的設定。對已建立的 Schedule 而言,只變更 config() 的值並不會新增或移除事件,也不會重新讀取設定。WeakMap 的判斷也不是用來重新設定的。
在使用設定快取的部署中,請重建快取,並在讀取新設定的程序中確認 schedule:list。對於長時間運作的排程器,以及正在執行未滿 1 分鐘任務的 schedule:run,也要確認運作方式不會殘留舊程式碼或舊設定。子分鐘任務在部署時的中斷,請參閱任務排程。
將註冊與執行分開測試
以下是使用套件的 Composer 自動載入與 PHPUnit 的tests/CourierScheduleTest.php 範例。將 mutex 設為測試替身,不使用外部快取或實際的 cron 來確認註冊結果。
請勿為了讓註冊測試通過,而以
schedule:run 執行使用端應用程式的所有任務。將本體的測試與在受控環境中的排程器整合測試分開,即可避免無關定期處理所造成的副作用。
相關頁面
任務排程
確認執行頻率、cron 的導入、委派給 Queue,以及部署時的運作方式。
套件的服務解析後掛鉤
確認如何套用到尚未解析與已解析的服務,以及重新註冊時的注意事項。
參考的一手資料
- Laravel 官方文件:任務排程
- Laravel 官方文件:套件開發
- FoundationServiceProvider:Schedule 的 singleton 註冊
- Console Kernel:Schedule 的建立與預設設定
- ServiceProvider:callAfterResolving()
- Schedule:事件的新增、Job 的延遲建立、mutex 的共用
- Event:指令事件的 mutex 名稱
- CallbackEvent:名稱的必要條件與執行時的相依性解析
- CacheEventMutex:防重疊鎖定與期限的轉換
- CacheSchedulingMutex:依執行時刻的鎖定
- 官方測試:事件的註冊與時區
- 官方測試:Job 類別名稱的延遲建立