简述
首先, 此文旨在通过源码分析 Laravel Schedule 调度任务实现的原理, 不讨论 Schedule 的基本用法, 如有需要请参考:
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 跳出.
本文由 root 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: Dec 27, 2019 at 05:29 pm