1. 程式人生 > 程式設計 >laravel 定時任務原始碼簡析

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 的兩個邏輯判定

  1. withoutOverlapping 如果開始,並且建立鎖失敗,則直接返回.
  2. 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);
複製程式碼