Laravel 技巧之 定時(shí)任務(wù)

定時(shí)任務(wù) Scheduled Tasks 是 Laravel 提供的組件之一,稍微上點(diǎn)規(guī)模的項(xiàng)目應(yīng)該都會(huì)用到,比如開發(fā)微信應(yīng)用時(shí)通過定時(shí)任務(wù)去刷新access token,比如每天定時(shí)發(fā)推送提醒用戶要記得簽到。對(duì)于定時(shí)任務(wù)的基本用法,官網(wǎng)文檔已經(jīng)描述得很詳細(xì)了,這里不再多說。

本文主要是介紹定時(shí)任務(wù)在實(shí)際應(yīng)用中的兩個(gè)小技巧:

1. 多個(gè)任務(wù)并行執(zhí)行

先簡(jiǎn)單介紹一下 Laravel 定時(shí)任務(wù)組件的基本原理:

當(dāng)cli初始化完畢之后,系統(tǒng)會(huì)調(diào)用 App\Console\Kernel::schedule 方法,也就是我們定義定時(shí)任務(wù)列表的地方,這個(gè)方法里每調(diào)用一次 $schedule->command() 就會(huì)生成一個(gè) Illuminate\Console\Scheduling\Event 對(duì)象并保存在 $schedule->events 數(shù)組里。當(dāng)執(zhí)行 php artisan scheduled:run 時(shí),系統(tǒng)會(huì)遍歷 $schedule->events,把當(dāng)前時(shí)間需要執(zhí)行的任務(wù)放在一個(gè)集合中,最后依次 串行執(zhí)行 這些任務(wù)。

這樣做在大多數(shù)情況下是沒有問題的,但有一些特殊的情況,比如在每個(gè)月的第一天要給100W個(gè)用戶發(fā)送郵件,同一批次的定時(shí)任務(wù)必須等到這些郵件全部發(fā)送完畢之后才會(huì)被執(zhí)行,假如這些任務(wù)里有對(duì)執(zhí)行時(shí)間十分敏感的任務(wù),比每5分鐘一次的數(shù)據(jù)快照,就會(huì)導(dǎo)致那個(gè)時(shí)間點(diǎn)數(shù)據(jù)的缺失。

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

2. 負(fù)載均衡

隨著業(yè)務(wù)邏輯的增多,定時(shí)任務(wù)也會(huì)越來越多,定時(shí)任務(wù)服務(wù)器的負(fù)載也會(huì)越來越高,甚至導(dǎo)致任務(wù)執(zhí)行緩慢,然而我們卻只能在一臺(tái)服務(wù)器上設(shè)置定時(shí)任務(wù),如果在多臺(tái)服務(wù)器上同時(shí)配置了定時(shí)任務(wù),還會(huì)導(dǎo)致定時(shí)任務(wù)的重復(fù)執(zhí)行。這個(gè)時(shí)候我們希望能夠像隊(duì)列那樣,將定時(shí)任務(wù)分散到多臺(tái)服務(wù)器上。

截止 v5.4.15,Laravel 還沒有提供內(nèi)置方案來解決這個(gè)問題,但只需要簡(jiǎn)單的改造就可以實(shí)現(xiàn)我們需要的效果。首先我們把將每個(gè)定時(shí)任務(wù)里 handle 方法提取出來創(chuàng)建一個(gè)新的Job并繼承 ShouldQueue,然后在定時(shí)任務(wù)的 handle 里直接 dispatch 對(duì)應(yīng)的Job即可,這樣原本的業(yè)務(wù)邏輯就會(huì)被隊(duì)列處理掉,當(dāng)系統(tǒng)有多臺(tái)服務(wù)器在處理隊(duì)列時(shí),也就實(shí)現(xiàn)了我們需要的負(fù)載均衡。

但是這樣畢竟還是麻煩,每個(gè)定時(shí)任務(wù)都要?jiǎng)?chuàng)建一個(gè)Command和一個(gè)Job,太費(fèi)勁,于是我提交了一個(gè) Proposal ,目前已經(jīng)實(shí)現(xiàn)并且merge入5.4分支,相信下個(gè)版本大家就能用上了。用法也很簡(jiǎn)單,只需要?jiǎng)?chuàng)建一個(gè)繼承 ShouldQueue的Job,然后在App\Console\Kernel::schedule 方法里定義

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

就可以了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容