laravel 定時任務源碼簡析

題目設置

有一個動態配置數組, 每一個數組元素定時啓動任務. 如何實現?php

源碼基於 laravel 5.5.45.laravel

如何基於 laravel 實現一個定時任務

app/Console/Kernel.phpwindows

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 能夠解析成任務、又能夠執行任務, 咱們能不能基於它來實現呢bash

從定時任務的起源 Schedule 類提及

vendor/laravel/framework/src/Illuminate/Console/Scheduling/ScheduleRunCommand.php
複製代碼

這個定時任務是laravel直接提供的, 做爲咱們 (schedule:run) 定時任務的執行文件。 和咱們自建的任務類沒什麼區別 他是每分鐘執行檢測是須要執行到期的任務.php7

咱們看下這個文件的 handle 實現app

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 方法ui

event 類是什麼

咱們前提是 咱們如何生成 event 對象, 咱們先從命令聲明開始this

$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, 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);
複製代碼

相關文章
相關標籤/搜索