Laravel 技巧之 定時任務

leo發表於2017-03-08

定時任務 Scheduled Tasks 是 Laravel 提供的元件之一,稍微上點規模的專案應該都會用到,比如開發微信應用時透過定時任務去重新整理access token,比如每天定時發推送提醒使用者要記得簽到。對於定時任務的基本用法,官網文件已經描述得很詳細了,這裡不再多說。

本文主要是介紹定時任務在實際應用中的兩個小技巧:

1. 多個任務並行執行

先簡單介紹一下 Laravel 定時任務元件的基本原理:

當cli初始化完畢之後,系統會呼叫 App\Console\Kernel::schedule 方法,也就是我們定義定時任務列表的地方,這個方法裡每呼叫一次 $schedule->command() 就會生成一個 Illuminate\Console\Scheduling\Event 物件並儲存在 $schedule->events 陣列裡。當執行 php artisan scheduled:run 時,系統會遍歷 $schedule->events,把當前時間需要執行的任務放在一個集合中,最後依次 序列執行 這些任務。

這樣做在大多數情況下是沒有問題的,但有一些特殊的情況,比如在每個月的第一天要給100W個使用者傳送郵件,同一批次的定時任務必須等到這些郵件全部傳送完畢之後才會被執行,假如這些任務裡有對執行時間十分敏感的任務,比每5分鐘一次的資料快照,就會導致那個時間點資料的缺失。

這種情況下如果定時任務能夠並行執行,就不會有這樣的問題。Laravel 實際上提供瞭解決方案,但很奇怪文件裡面並沒有提到,就是 runInBackground 方法,在定義定時任務時 $schedule->command('foo:bar')->everyMinutes()->runInBackground(); 就可以了。

2. 負載均衡

隨著業務邏輯的增多,定時任務也會越來越多,定時任務伺服器的負載也會越來越高,甚至導致任務執行緩慢,然而我們卻只能在一臺伺服器上設定定時任務,如果在多臺伺服器上同時配置了定時任務,還會導致定時任務的重複執行。這個時候我們希望能夠像佇列那樣,將定時任務分散到多臺伺服器上。

截止 v5.4.15,Laravel 還沒有提供內建方案來解決這個問題,但只需要簡單的改造就可以實現我們需要的效果。首先我們把將每個定時任務裡 handle 方法提取出來建立一個新的Job並繼承 ShouldQueue,然後在定時任務的 handle 裡直接 dispatch 對應的Job即可,這樣原本的業務邏輯就會被佇列處理掉,當系統有多臺伺服器在處理佇列時,也就實現了我們需要的負載均衡。

但是這樣畢竟還是麻煩,每個定時任務都要建立一個Command和一個Job,太費勁,於是我提交了一個 Proposal ,目前已經實現並且merge入5.4分支,相信下個版本大家就能用上了。用法也很簡單,只需要建立一個繼承 ShouldQueue的Job,然後在App\Console\Kernel::schedule 方法裡定義

$schedule->job(new FooBarJob())->everyMinutes();

就可以了

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章