简介
过去,你可能需要在服务器上为每一个调度任务去创建 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进程,进行执行脚本