本篇內容主要講解“怎么理解Laravel定時任務調度機制”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“怎么理解Laravel定時任務調度機制”吧!
成都創新互聯是網站建設技術企業,為成都企業提供專業的成都網站建設、成都網站制作,網站設計,網站制作,網站改版等技術服務。擁有十余年豐富建站經驗和眾多成功案例,為您定制適合企業的網站。十余年品質,值得信賴!
一個復雜的web系統后臺當中,一定會有很多定時腳本或者任務要跑。
例如爬蟲系統需要定期去爬取一些網站數據,自動還貸系統需要每個月定時對用戶賬戶扣款結算,
會員系統需要定期檢測用戶剩余會員天數以便及時通知續費等等。Linux系統中內置的crontab一般被廣泛地用于跑定時任務
crontab指令解釋
命令行crontab -e進入crontab編輯,把自己要執行的指令編輯好之后保存退出即可生效。
不過本文并不會過多討論crontab的內容,而是要深入分析一下PHP Laravel框架是如何基于crontab封裝出功能更加強大的任務調度(Task Scheduling)模塊。
對于定時任務,我們當然可以每個任務配置一個crontab指令。只不過這樣做的話隨著定時任務的增加,crontab指令也線性增長。
畢竟crontab是一項系統級的配置,在業務中我們為了節約機器,往往對于量不大的多個項目會放在同一臺服務器上,c
rontab指令多了就容易管理混亂,并且功能也不夠靈活強大(無法隨心所欲的停啟、處理任務間依賴關系等)。
對此Laravel的解決方案是只聲明一條crontab,業務中的所有定時任務全都在這一條crontab中做處理和判斷,實現在代碼層面管理任務:
* * * * * php artisan schedule:run >> /dev/null 2>&1
即php artisan schedule:run每分鐘跑一次(crontab的最高頻率),至于業務上的具體任務配置,則注冊于Kernel::schedule()中
class Kernel extends ConsoleKernel { Protected function schedule(Schedule $schedule) { $schedule->command('account:check')->everyMinute(); // 每分鐘執行一次php artisan account:check 指令 $schedule->exec('node /home/username/index.js')->everyFifteenMinutes(); //每15分鐘執行一次node /home/username/index.js 命令 $schedule->job(new MyJob())->cron('1 2 3 10 *'); // 每年的10月3日凌晨2點1分向任務隊列分發一個MyJob任務 } }
上述例子中我們可以很清晰的看到系統中注冊了三項定時任務,并且提供了everyMinute, everyFifteenMinutes, daily, hourly等語義化的方法來配置任務周期。
本質上,這些語義化的方法只是crontab表示方式的一個別稱罷了,最終都會轉化為crontab中的表達方式(如 * * * * * 表示每分鐘執行一次)。
如此一來,每分鐘執行一次的php artisan schedule:run指令,會掃描Kernel::schedule中注冊的所有指令并判斷該指令配置的執行周期時候已經到期,
如果到期則推入待執行隊列。最后依次執行所有的指令。
// ScheduleRunCommand::handle函數 public function handle() { foreach ($this->schedule->dueEvents() as $event) { if (! $event->filtersPass()) { continue; } $event->run(); } }
這里需要注意兩個點,第一、如何判斷指令是否已經Due了該執行了。第二、指令的執行順序問題。
首先,crontab表達式所指定的執行時間,是指絕對時間,而不是相對時間。所以僅僅根據當前時間和crontab表達式,
即可判斷出指令是否已經Due了該執行了。如果想要實現相對時間,那么必須存儲上一次執行的時間,
然后才能進行推算下次執行應該是什么時候。絕對時間和相對時間的區別可以用下面一幅圖概括(crontab的執行時間如圖中左側列表所示)。
Laravel中對于crontab表達式的靜態分析和判斷使用的是cron-expression庫,原理也比較直觀,就是靜態的字符分析比對。
crontab是絕對時間,而非相對時間
第二個問題是執行順序,前面的圖中我們可以看出,如果你在Kernel::schedule方法中注冊了多個任務,
正常情況下它們是順序依次執行的。也就是說必須要等到Task 1執行完成之后,Task 2才會開始執行。
在這種情況下,如果Task 1非常耗時,則會影響到Task 2的按時執行,這一點在開發中是尤其需要注意的。
不過在Kernel::schedule中注冊任務時加上runInBackground即可實現任務的后臺執行,這點我們下文詳細討論。
前文提到的定時任務隊列順序執行的特性,前面的任務執行時間太長會妨礙后面任務的按時執行。
為解決此問題,Laravel中提供了使任務后臺執行的方法runInBackground。如:
// Kernel.php protected function schedule(Schedule $schedule) { $schedule->command('test:hello') // 執行command命令:php artisan test:hello ->cron('10 11 1 * *') // 每月1日的11:10:00執行該命令 ->timezone('Asia/Shanghai') // 設置時區 ->before(function(){/*do something*/}) // 前置hook,命令執行前執行此回調 ->after(function(){/*do something*/}) // 后置鉤子,命令執行完之后執行此回調 ->runInBackground(); // 后臺運行本命令 // 每分鐘執行command命令:php artisan test:world $schedule->command('test:world')->everyMinute(); }
后臺運行的原理,其實也非常簡單。我們知道在linux系統下,命令行的指令最后加個“&”符號,可以使任務在后臺執行。
runInBackground方法內部原理其實就是讓最后跑的指令后面加了“&”符號。不過在任務改為后臺執行之后,
又有了一個新的問題,即如何觸發任務的后置鉤子函數。因為后置鉤子函數是需要在任務跑完之后立即執行,
所以必須要有辦法監測到后臺運行的任務結束的一瞬間。我們從源代碼中一探究竟
// 構建運行在后臺的command指令 protected function buildBackgroundCommand(Event $event) { $output = ProcessUtils::escapeArgument($event->output); $redirect = $event->shouldAppendOutput ? ' >> ' : ' > '; $finished = Application::formatCommandString('schedule:finish').' "'.$event->mutexName().'"'; return $this->ensureCorrectUser($event, '('.$event->command.$redirect.$output.' 2>&1 '.(windows_os() ? '&' : ';').' '.$finished.') > ' .ProcessUtils::escapeArgument($event->getDefaultOutput()).' 2>&1 &' ); }
$finished字符串的內容是一個隱藏的php artisan指令,即php artisan schedule:finish <mutex_name>。
該命令被附在了本來要執行的command命令后面,用來檢測并執行后置鉤子函數。
php artisan schedule:finish <mutex_name>的源代碼非常簡單,用mutex_name來唯一標識一個待執行任務,
通過比較系統中注冊的所有任務的mutex_name,來確定需要執行哪個任務的后置函數。代碼如下:
// Illuminate/Console/Scheduling/ScheduleFinishCommand.php // php artisan schedule:finish指令的源代碼 public function handle() { collect($this->schedule->events())->filter(function ($value) { return $value->mutexName() == $this->argument('id'); })->each->callAfterCallbacks($this->laravel); }
有些定時任務指令需要執行很長時間,而laravel schedule任務最頻繁可以做到1分鐘跑一次。
這也就意味著,如果任務本身跑了1分鐘以上都沒有結束,那么等到下一個1分鐘到來的時候,又一個相同的任務跑起來了。
這很可能是我們不想看到的結果。因此,有必要想一種機制,來避免任務在同一時刻的重復執行(prevent overlapping)。
這種場景非常類似多進程或者多線程的程序搶奪資源的情形,常見的預防方式就是給資源加鎖。
具體到laravel定時任務,那就是給任務加鎖,只有拿到任務鎖之后,才能夠執行任務的具體內容。
Laravel中提供了withoutOverlapping方法來讓定時任務避免重復。具體鎖的實現上,需要實現Illuminate\Console\Scheduling\Mutex.php接口中所定義的三個接口:
interface Mutex { // 實現創建鎖接口 public function create(Event $event); // 實現判斷鎖是否存在的接口 public function exists(Event $event); // 實現解除鎖的接口 public function forget(Event $event); }
該接口當然可以自己實現,Laravel也給了一套默認實現,即利用緩存作為存儲鎖的載體(可參考Illuminate\Console\Scheduling\CacheMutex.php文件)。
在每次跑任務之間,程序都會做出判斷,是否需要防止重復,如果重復了,則不再跑任務代碼:
// Illuminate\Console\Scheduling\Event.php public function run() { // 判斷是否需要防止重復,若需要防重復,并且創建鎖不成功,則說明已經有任務在跑了,這時直接退出,不再執行具體任務 if ($this->withoutOverlapping && ! $this->mutex->create($this)) { return; } $this->runInBackground?$this->runCommandInBackground($container):$this->runCommandInForeground($container); }
我們知道crontab任務最精細的粒度只能到分鐘級別。那么如果我想實現30s執行一次的任務,
需要如何實現?關于這個問題,stackoverflow上面也有一些討論,有建議說在業務層面實現,自己寫個sleep來實現,示例代碼如下:
public function handle() { runYourCode(); // 跑業務代碼 sleep(30); // 睡30秒 runYourCode(); // 再跑一次業務代碼 }
如果runYourCode執行實現不太長的話,上面這個任務每隔1min執行一次,其實相當于runYourCode函數每30秒執行一次。
如果runYourCode函數本身執行時間比較長,那這里的sleep 30秒會不那么精確。
當然,也可以不使用Laravel的定時任務系統,改用專門的定時任務調度開源工具來實現每隔30秒執行一次的功能,
在此推薦一個定時任務調度工具nomad。
如果你確實要用Laravel自帶的定時任務系統,并且又想實現更精確一些的每隔30秒執行一次任務的功能,那么可以結合laravel 的queue job來實現。如下:
public function handle() { $job1 = (new MyJob())->onQueue(“queue-name”); $job2 = (new MyJob())->onQueue(“queue-name”)->delay(30); dispatch($job1); dispatch($job2): } class MyJob implement Illuminate\Contracts\Queue\ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public function handle() { runYourCode(); } }
通過Laravel 隊列功能的delay方法,可以將任務延時30s執行,因此如果每隔1min,我們都往隊列中dispatch兩個任務,其中一個延時30秒。
另外,把自己要執行的代碼runYourCode寫在任務中,即可實現30秒執行一次的功能。不過這里需要注意的是,這種實現中scheduling的防止重合功能不再有效,
需要自己在業務代碼runYourCode中實現加鎖防止重復的功能。
到此,相信大家對“怎么理解Laravel定時任務調度機制”有了更深的了解,不妨來實際操作一番吧!這里是創新互聯網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
網頁名稱:怎么理解Laravel定時任務調度機制
文章位置:http://m.newbst.com/article0/gpijoo.html
成都網站建設公司_創新互聯,為您提供網站排名、網站維護、外貿建站、品牌網站制作、標簽優化、虛擬主機
聲明:本網站發布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創新互聯