laravel 定時任務原始碼簡析
題目設定
有一個動態配置陣列,每個陣列元素定時啟動任務. 如何實現?
原始碼基於 laravel 5.5.45.
如何基於 laravel 實現一個定時任務
app/Console/Kernel.php
class Kernel extends ConsoleKernel
protected function schedule(Schedule $schedule)
{
$schedule->command("test_b",['rule-content'])->runInBackground()->withoutOverlapping()->sendOutputTo(storage_path('logs/xx.log' ));
}
}
複製程式碼
crontab 配置每分鐘呼叫檢測
* * * * * php /path/to/artisan schedule:run
複製程式碼
上面是一個普通定時任務的一種寫法,當然我們這是是根據配置 動態的執行任務.
laravel 可以解析成任務、又可以執行任務,我們能不能基於它來實現呢
從定時任務的起源 Schedule 類說起
vendor/laravel/framework/src/Illuminate/Console/Scheduling/ScheduleRunCommand.php
複製程式碼
這個定時任務是laravel直接提供的,作為我們 (schedule:run) 定時任務的執行檔案。 和我們自建的任務類沒什麼區別 他是每分鐘執行檢測是需要執行到期的任務.
我們看下這個檔案的 handle 實現
public function handle()
{
$eventsRan = false;
foreach ($this->schedule->dueEvents($this->laravel) as $event) {
if (! $event->filtersPass($this->laravel)) {
continue;
}
$event->run($this->laravel);
$eventsRan = true;
}
...
}
複製程式碼
最核心的是任務執行起來 執行了 event 類的 run 方法
event 類是什麼
我們前提是 我們如何生成 event 物件,我們先從命令宣告開始
$schedule->command('inspire');
複製程式碼
這個是我們定時任務的寫法,我們看下 Schedule 類的 command 方法.
public function command($command,array $parameters = [])
{
if (class_exists($command)) {
$command = Container::getInstance()->make($command)->getName();
}
return $this->exec(
Application::formatCommandString($command),$parameters
);
}
複製程式碼
傳入我們的 spire 命令,呼叫 exec 執行命令.
在執行裡面,呼叫了 Application::formatCommandString 返回了我們想要的命令基本雛形.
'/usr/local/php7/bin/php' 'artisan' inspire
複製程式碼
呼叫的exec方法實現:
public function exec($command,array $parameters = [])
{
if (count($parameters)) {
$command .= ' '.$this->compileParameters($parameters);
}
$this->events[] = $event = new Event($this->mutex,$command);
return $event;
}
複製程式碼
如果存在引數的話,呼叫 compileParameters 對引數進行安全處理並返回回來,拼接到我們的執行命令後面,然後我們發現將命令傳入 event,並 return 了 event 物件.
其它
當然針對 event 類的方法還有很多,比如我們使用 withoutOverlapping 方法上鎖,防止任務超時再次執行.
我們將任務放到後臺執行,防止影響下面的任務執行,可以使用 runInBackground
完整示例
$schedule->command("test_b",['rule-content'])->runInBackground()->withoutOverlapping()
複製程式碼
具體 runInBackground 和 withoutOverlapping 實現方式請往下看.
呼叫執行
啟動任務 event run 類實現
public function run(Container $container)
{
if ($this->withoutOverlapping &&
! $this->mutex->create($this)) {
return;
}
$this->runInBackground
? $this->runCommandInBackground($container)
: $this->runCommandInForeground($container);
}
複製程式碼
我們可以看到剛才我們提到的關於 withoutOverlapping 和 runInBackground 的兩個邏輯判定
- withoutOverlapping 如果開始,並且建立鎖失敗,則直接返回.
- runInBackground 如果開啟,則執行 runCommandInBackground 方法,
以上原始碼實現也很簡單,感興趣可以研究看看
命令構造器 CommandBuilder 類
run 方法呼叫 runCommandInBackground 後臺執行任務實現
protected function runCommandInBackground(Container $container)
{
$this->callBeforeCallbacks($container);
(new Process(
$this->buildCommand(),base_path(),null,null
))->run();
}
複製程式碼
這裡使用了 syfomy 的 process類,建立一個新程式. 我們不再深究 process的實現,我們來看 buildCommand
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 &'
);
}
複製程式碼
這是生成後臺執行程式任務的核心實現,和另一個前臺執行相比主要多了一個 & 號
我們列印看看這是個什麼樣子的命令?
$sh = $schedule->command("test_b",['rule-content'])->runInBackground()->withoutOverlapping()->buildCommand();
echo $sh;
複製程式碼
最終命令輸出情況
('/usr/local/php7/bin/php' 'artisan' test_b 'rule-content' > '/dev/null' 2>&1 ; '/usr/local/php7/bin/php' 'artisan' schedule:finish "framework/schedule-8d9802e101a46785c4a1222384c28652b39a03a6") > '/dev/null' 2>&1 &
複製程式碼
完成排程實現:
由上可知,我們如果手動實現呼叫的話,可以直接呼叫 event 裡面的 run方法即可,實現如下(不同版本實現不一樣,但大概思路一致,以下基於laravel 5.5)
$schedule = app(Schedule::class);
$event = $schedule->command("test_b",['rule-content'])->runInBackground()->withoutOverlapping()->run($this->laravel);
複製程式碼