Laravel Schedule 调度运行原理简单分析
in LaravelPHP with 0 comment

Laravel Schedule 调度运行原理简单分析

in LaravelPHP with 0 comment

94cd0f9c-4219-4ad0-ba2a-ef7349716eec.png

简述

首先, 此文旨在通过源码分析 Laravel Schedule 调度任务实现的原理, 不讨论 Schedule 的基本用法, 如有需要请参考:

<Laravel 6 中文文档: 任务调度>

<Laravel: Digging Deeper: Task Scheduling>

schedule:run 指令源码分析

我们知道, 如果需要运行已经定义好的调度任务, 需要手动在命令行运行:

php /path/to/artisan schedule:run

或将以上指令添加到系统的 crontab:

* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1

故, 我们找到 artisan schedule:run 对应的文件: /{project}/vendor/laravel/framework/src/Illuminate/Console/Scheduling/ScheduleRunCommand.php

/**
     * Execute the console command.
     *
     * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
     * @param  \Illuminate\Contracts\Events\Dispatcher  $dispatcher
     * @return void
     */
    public function handle(Schedule $schedule, Dispatcher $dispatcher)
    {
        $this->schedule = $schedule;
        $this->dispatcher = $dispatcher;

        foreach ($this->schedule->dueEvents($this->laravel) as $event) {
            if (! $event->filtersPass($this->laravel)) {
                $this->dispatcher->dispatch(new ScheduledTaskSkipped($event));

                continue;
            }

            if ($event->onOneServer) {
                $this->runSingleServerEvent($event);
            } else {
                $this->runEvent($event);
            }

            $this->eventsRan = true;
        }

        if (! $this->eventsRan) {
            $this->info('No scheduled commands are ready to run.');
        }
    }

可以看到核心部分 handle(Schedule $schedule, Dispatcher $dispatcher), 此处接收一个由服务容器自动注入的 \Illuminate\Console\Scheduling\Schedule (以下简称 $schedule)和 \Illuminate\Contracts\Events\Dispatcher 实例.

$schedule 对象分析

$schedule 正是我们在 App\Console\Kernel->schedule(Schedule $schedule) 定义调度任务时使用到的实例, 例:

$schedule->call(new DeleteRecentUsers)->daily();
$schedule->command('emails:send Taylor --force')->daily();
$schedule->job(new Heartbeat, 'heartbeats')->everyFiveMinutes();

根据该实例我们可以找到 command(), call(), job() 等方法的实现基本一致, 均由 \Illuminate\Console\Scheduling\Event 实例化后存入 $schedule->event 数组中.

调度频率的实现分析

得知以上基础后, 我们再回到 schedule:run 的运行阶段不难看出:

// 注意此处的 dueEvents()
foreach ($this->schedule->dueEvents($this->laravel) as $event) {
    // 省略...
}

跟踪 dueEvents() 找到如下源码 /{project}/vendor/dragonmantank/cron-expression/src/Cron/CronExpression.php:

/**
     * Determine if the cron is due to run based on the current date or a
     * specific date.  This method assumes that the current number of
     * seconds are irrelevant, and should be called once per minute.
     *
     * @param string|\DateTimeInterface $currentTime Relative calculation date
     * @param null|string               $timeZone    TimeZone to use instead of the system default
     *
     * @return bool Returns TRUE if the cron is due to run or FALSE if not
     */
    public function isDue($currentTime = 'now', $timeZone = null)
    {
        $timeZone = $this->determineTimeZone($currentTime, $timeZone);

        if ('now' === $currentTime) {
            $currentTime = new DateTime();
        } elseif ($currentTime instanceof DateTime) {
            //
        } elseif ($currentTime instanceof DateTimeImmutable) {
            $currentTime = DateTime::createFromFormat('U', $currentTime->format('U'));
        } else {
            $currentTime = new DateTime($currentTime);
        }
        $currentTime->setTimeZone(new DateTimeZone($timeZone));

        // drop the seconds to 0
        $currentTime = DateTime::createFromFormat('Y-m-d H:i', $currentTime->format('Y-m-d H:i'));

        try {
            return $this->getNextRunDate($currentTime, 0, true)->getTimestamp() === $currentTime->getTimestamp();
        } catch (Exception $e) {
            return false;
        }
    }

谷歌翻译如下:

根据当前日期或特定日期确定cron是否应运行。此方法假定当前的秒数无关紧要,应每分钟调用一次。

至此我们已经理解了 Laravel 中 Schedule 的核心逻辑, 就是通过系统的定时任务每分钟运行一次 schedule:run 指令, 其中 Laravel 会根据你定义的调度任务的执行频率去判断哪些指令应该在此刻执行.

备注

例如我们定义了一个调度任务如下

$schedule->command(Commands\CalcStatistic::class)->dailyAt('00:01'); // 在每天凌晨0点01分执行计算后台的某些统计数据操作

当时间为 00:01 分时, Laravel 就会判断此刻有一个任务需要执行, 继而完成对应的操作, 当时间到 00:02 分时, 没有对应定义的任务, Laravel 就会直接从 handle 方法中的 foreach 跳出.

Responses
icon_mrgreen.gificon_neutral.gificon_twisted.gificon_arrow.gificon_eek.gificon_smile.gificon_confused.gificon_cool.gificon_evil.gificon_biggrin.gificon_idea.gificon_redface.gificon_razz.gificon_rolleyes.gificon_wink.gificon_cry.gificon_surprised.gificon_lol.gificon_mad.gificon_sad.gificon_exclaim.gificon_question.gif