laravel 定时任务原理

简介

过去,你可能需要在服务器上为每一个调度任务去创建 Cron 入口。但是这种方式很快会变得不友好,因为这些任务调度不在源代码中,并且你每次都需要通过 SSH 链接登录到服务器中才能增加 Cron 入口。

Laravel 命令行调度器允许你在 Laravel 中对命令调度进行清晰流畅的定义。且使用这个任务调度器时,你只需要在你的服务器上创建单个 Cron 入口。你的任务调度在 app/Console/Kernel.php 的 schedule 方法中进行定义。

启动调度器

当使用这个调度器时,你只需要把下面的 Cron 入口添加到你的服务器中。如果你不知道怎么在服务器中添加 Cron 入口,可以考虑使用一些服务来管理 Cron 入口,比如 Laravel Forge :

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

这个 Cron 会每分钟执行一次 Laravel 的命令行调度器。当 schedule:run 命令被执行的时候,Laravel 会根据你的调度执行预定的程序。

shedule:run

shedule:run 所在命令文件为

<?php

namespace Illuminate\Console\Scheduling;

use Illuminate\Console\Command;
use Illuminate\Console\Events\ScheduledTaskFinished;
use Illuminate\Console\Events\ScheduledTaskSkipped;
use Illuminate\Console\Events\ScheduledTaskStarting;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Support\Facades\Date;

class ScheduleRunCommand extends Command
{
    /**
     * The console command name.
     *
     * @var string
     */
    protected $name = 'schedule:run';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Run the scheduled commands';

    /**
     * The schedule instance.
     *
     * @var \Illuminate\Console\Scheduling\Schedule
     */
    protected $schedule;

    /**
     * The 24 hour timestamp this scheduler command started running.
     *
     * @var \Illuminate\Support\Carbon
     */
    protected $startedAt;

    /**
     * Check if any events ran.
     *
     * @var bool
     */
    protected $eventsRan = false;

    /**
     * The event dispatcher.
     *
     * @var \Illuminate\Contracts\Events\Dispatcher
     */
    protected $dispatcher;

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->startedAt = Date::now();

        parent::__construct();
    }

    /**
     * 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.');
        }
    }
}

在php artisan 命令时已经创建了 ScheduleRunCommand 实例,属性startedAt为当前时间;

这时候我们看一下

public function handle(Schedule $schedule, Dispatcher $dispatcher)
①Schedule实例化以及赋值操作

参数 Schedule $schedule, Dispatcher $dispatcher 是已经绑定到服务容器中的实例,我们先着重看一下Schedule是什么时候进行实例化的

Kernel.php

namespace Laravel\Lumen\Console;
    /**
     * Create a new console kernel instance.
     *
     * @param  \Laravel\Lumen\Application  $app
     * @return void
     */
    public function __construct(Application $app)
    {
        $this->app = $app;

        if ($this->app->runningInConsole()) {
            $this->setRequestForConsole($this->app);
        }

        $this->app->prepareForConsoleCommand($this->aliases);
        $this->defineConsoleSchedule();
    }

    /**
     * Define the application's command schedule.
     *
     * @return void
     */
    protected function defineConsoleSchedule()
    {
        $this->app->instance(
            'Illuminate\Console\Scheduling\Schedule', $schedule = new Schedule
        );

        $this->schedule($schedule);
    }

从Kernel的构造函数可以知道,在创建Kernel实例的时候就已经绑定了Schedule实例,并且调用了 Kernel schedule函数,这里的schedule函数就是我们在app/Console/Kernel scedule(Schedule $schedule) 中添加的定时任务,如

$schedule->command(Demo::class)
            ->everyMinute()
            ->name('test:test')
            ->withoutOverlapping()
            ->sendOutputTo($output, true);

我们查看一下 $schedule->command()

    /**
     * Add a new Artisan command event to the schedule.
     *
     * @param  string  $command
     * @param  array  $parameters
     * @return \Illuminate\Console\Scheduling\Event
     */
    public function command($command, array $parameters = [])
    {
        if (class_exists($command)) {
            $command = Container::getInstance()->make($command)->getName();
        }

        return $this->exec(
            Application::formatCommandString($command), $parameters
        );
    }

    /**
     * Add a new command event to the schedule.
     *
     * @param  string  $command
     * @param  array  $parameters
     * @return \Illuminate\Console\Scheduling\Event
     */
    public function exec($command, array $parameters = [])
    {
        if (count($parameters)) {
            $command .= ' '.$this->compileParameters($parameters);
        }

        $this->events[] = $event = new Event($this->eventMutex, $command, $this->timezone);

        return $event;
    }

这样的写法就是将定时任务 $comand 对应的event事件放到Scedule 的 events 数组中

②执行定时任务

取当前应该该执行定时任务的事件

$this->schedule->dueEvents($this->laravel) 

进行循环

foreach ($this->schedule->dueEvents($this->laravel) as $event) {
    //event 中 $comands 方法 无法调用 $app->commandClass->handle() 跳过循环
    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.');
    }
}

我们可以看一下runEvent($event)方法

/**
     * Run the given event.
     *
     * @param  \Illuminate\Console\Scheduling\Event  $event
     * @return void
     */
    protected function runEvent($event)
    {
        $this->line('<info>Running scheduled command:</info> '.$event->getSummaryForDisplay());
        //监听事件开始
        $this->dispatcher->dispatch(new ScheduledTaskStarting($event));

        $start = microtime(true);
        //事件执行
        $event->run($this->laravel);
        //监听事件结束
        $this->dispatcher->dispatch(new ScheduledTaskFinished(
            $event,
            round(microtime(true) - $start, 2)
        ));

        $this->eventsRan = true;
    }

事件执行方法 run($this->laravel)

    /**
     * Run the given event.
     *
     * @param  \Illuminate\Contracts\Container\Container  $container
     * @return void
     */
    public function run(Container $container)
    {
        //schedule withoutOverlapping($expiresAt = 1440) 1440 * 60 = 24h , 默认24小时相同定时任务执行时不重复执行
        if ($this->withoutOverlapping &&
            ! $this->mutex->create($this)) {
            return;
        }

        $this->runInBackground
                    ? $this->runCommandInBackground($container)
                    : $this->runCommandInForeground($container);
    }

        /**
     * Run the command in the foreground.
     *
     * @param  \Illuminate\Contracts\Container\Container  $container
     * @return void
     */
    protected function runCommandInForeground(Container $container)
    {
        $this->callBeforeCallbacks($container);

        $this->exitCode = Process::fromShellCommandline($this->buildCommand(), base_path(), null, null, null)->run();

        $this->callAfterCallbacks($container);
    }

    /**
     * Run the command in the background.
     *
     * @param  \Illuminate\Contracts\Container\Container  $container
     * @return void
     */
    protected function runCommandInBackground(Container $container)
    {
        $this->callBeforeCallbacks($container);

        Process::fromShellCommandline($this->buildCommand(), base_path(), null, null, null)->run();
    }

我们可以看出event run 的时候是执行

Process::fromShellCommandline($this->buildCommand(), base_path(), null, null, null)->run();

创建了Process进程,进行执行脚本

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值