有一个动态配置数组, 每一个数组元素定时启动任务. 如何实现?php
源码基于 laravel 5.5.45.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
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 对象, 咱们先从命令声明开始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 实现方式请往下看.
public function run(Container $container)
{
if ($this->withoutOverlapping &&
! $this->mutex->create($this)) {
return;
}
$this->runInBackground
? $this->runCommandInBackground($container)
: $this->runCommandInForeground($container);
}
复制代码
咱们能够看到刚才咱们提到的关于 withoutOverlapping 和 runInBackground 的两个逻辑断定
以上源码实现也很简单, 感兴趣能够研究看看
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);
复制代码