深入理解Laravel定時任務調度機制
本篇文章為大家帶來了關於laravel定時任務調度機制的相關知識,其中主要介紹了基本實現邏輯、後台運行以及防止重複的相關問題,希望對大家有幫助。
【相關推薦:laravel影片教學】
1. 基本實作邏輯
#一個複雜的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(); } }
schedule task流程圖
這裡要注意兩個點,第一、如何判斷指令是否已經Due了該執行了。第二、指令的執行順序問題。
首先,crontab表達式所指定的執行時間,是指絕對時間,而不是相對時間。所以只要根據目前時間和crontab表達式,
即可判斷指令是否已經Due了該執行了。如果想要實現相對時間,那麼必須儲存上一次執行的時間,
然後才能進行推算下次執行應該是什麼時候。絕對時間和相對時間的差異可以用下面一幅圖來概括(crontab的執行時間如圖中左側列表所示)。
Laravel中對於crontab表達式的靜態分析與判斷使用的是cron-expression庫(github.com/mtdowling/cron-expression),原理也比較直觀,就是靜態的字元分析比對。
crontab是絕對時間,而非相對時間
第二個問題是執行順序,前面的圖中我們可以看出,如果你在Kernel::schedule方法中註冊了多個任務,
正常情況下它們是順序依序執行的。也就是說必須要等到Task 1執行完成之後,Task 2才會開始執行。
在這種情況下,如果Task 1非常耗時,則會影響到Task 2的按時執行,這一點在開發中是尤其需要注意的。
不過在Kernel::schedule中註冊任務時加上runInBackground即可實現任務的後台執行,這點我們下文詳細討論。
2. 後台運行
前文提到的定時任務佇列順序執行的特性,前面的任務執行時間太長會妨礙後面任務的按時執行。
為解決此問題,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方法内部原理其实就是让最后跑的指令后面加了“&”符号。不过在任务改为后台执行之后,
又有了一个新的问题,即如何触发任务的后置钩子函数。因为后置钩子函数是需要在任务跑完之后立即执行,
所以必须要有办法监测到后台运行的任务结束的一瞬间。我们从源代码中一探究竟(Illuminate/Console/Scheduling/CommandBuilder.php)
// 构建运行在后台的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
该命令被附在了本来要执行的command命令后面,用来检测并执行后置钩子函数。
php artisan schedule:finish
通过比较系统中注册的所有任务的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); }
3. 防止重复
有些定时任务指令需要执行很长时间,而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); }
4. 如何实现30秒任务?
我们知道crontab任务最精细的粒度只能到分钟级别。那么如果我想实现30s执行一次的任务,
需要如何实现?关于这个问题,stackoverflow上面也有一些讨论,有建议说在业务层面实现,自己写个sleep来实现,示例代码如下:
public function handle() { runYourCode(); // 跑业务代码 sleep(30); // 睡30秒 runYourCode(); // 再跑一次业务代码 }
如果runYourCode执行实现不太长的话,上面这个任务每隔1min执行一次,其实相当于runYourCode函数每30秒执行一次。
如果runYourCode函数本身执行时间比较长,那这里的sleep 30秒会不那么精确。
当然,也可以不使用Laravel的定时任务系统,改用专门的定时任务调度开源工具来实现每隔30秒执行一次的功能,
在此推荐一个定时任务调度工具nomad(https://github.com/hashicorp/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 Scheduling定时任务调度的原理分析和注意事项。作为最流行的PHP框架,Laravel大而全,
组件基本包含了web开发的各方面需求。其中很多组件的实现思想,还是很值得深入源码一探究竟的。
【相关推荐:laravel视频教程】
以上是深入理解Laravel定時任務調度機制的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

Laravel 是一款 PHP 框架,用於輕鬆構建 Web 應用程序。它提供一系列強大的功能,包括:安裝: 使用 Composer 全局安裝 Laravel CLI,並在項目目錄中創建應用程序。路由: 在 routes/web.php 中定義 URL 和處理函數之間的關係。視圖: 在 resources/views 中創建視圖以呈現應用程序的界面。數據庫集成: 提供與 MySQL 等數據庫的開箱即用集成,並使用遷移來創建和修改表。模型和控制器: 模型表示數據庫實體,控制器處理 HTTP 請求。

在使用CraftCMS開發網站時,常常會遇到資源文件緩存的問題,特別是當你頻繁更新CSS和JavaScript文件時,舊版本的文件可能仍然被瀏覽器緩存,導致用戶無法及時看到最新的更改。這個問題不僅影響用戶體驗,還會增加開發和調試的難度。最近,我在項目中遇到了類似的困擾,經過一番探索,我找到了wiejeben/craft-laravel-mix這個插件,它完美地解決了我的緩存問題。

想要學習 Laravel 框架,但苦於沒有資源或經濟壓力?本文為你提供了免費學習 Laravel 的途徑,教你如何利用網絡平台、文檔和社區論壇等資源,從入門到掌握,為你的 PHP 開發之旅奠定堅實基礎。

文章摘要:本文提供了詳細分步說明,指導讀者如何輕鬆安裝 Laravel 框架。 Laravel 是一個功能強大的 PHP 框架,它 упростил 和加快了 web 應用程序的開發過程。本教程涵蓋了從系統要求到配置數據庫和設置路由等各個方面的安裝過程。通過遵循這些步驟,讀者可以快速高效地為他們的 Laravel 項目打下堅實的基礎。

在面向初学者的 Laravel 框架版本选择指南中,本文深入探討了 Laravel 的版本差異,旨在協助初學者在眾多版本之間做出明智的選擇。我們將重點介紹每個版本的關鍵特徵、比較它們的優缺點,並提供有用的建議,幫助新手根據他們的技能水準和項目需求挑選最合適的 Laravel 版本。對於初學者來說,選擇一個合適的 Laravel 版本至關重要,因為它可以顯著影響他們的學習曲線和整體開發體驗。

Laravel 提供了一個全面的 Auth 框架,用於實現用戶登錄功能,包括:定義用戶模型(Eloquent 模型)創建登錄表單(Blade 模板引擎)編寫登錄控制器(繼承 Auth\LoginController)驗證登錄請求(Auth::attempt)登錄成功後重定向(redirect)考慮安全因素:哈希密碼、防 CSRF 保護、速率限制和安全標頭。此外,Auth 框架還提供重置密碼、註冊和驗證電子郵件等功能。詳情請參閱 Laravel 文檔:https://laravel.com/doc

Laravel框架內置了多種方法來方便地查看其版本號,滿足開發者的不同需求。本文將探討這些方法,包括使用Composer命令行工具、訪問.env文件或通過PHP代碼獲取版本信息。這些方法對於維護和管理Laravel應用程序的版本控制至關重要。

Laravel 和 ThinkPHP 都是流行的 PHP 框架,在開發中各有優缺點。本文將深入比較這兩者,重點介紹它們的架構、特性和性能差異,以幫助開發者根據其特定項目需求做出明智的選擇。
