Heim  >  Artikel  >  PHP-Framework  >  Vertiefendes Verständnis des Planungsmechanismus für geplante Aufgaben von Laravel

Vertiefendes Verständnis des Planungsmechanismus für geplante Aufgaben von Laravel

WBOY
WBOYnach vorne
2022-02-23 17:20:175476Durchsuche

Dieser Artikel vermittelt Ihnen relevantes Wissen über den Zeitplanungsmechanismus von laravel. Er stellt hauptsächlich die grundlegende Implementierungslogik, den Hintergrundbetrieb und die damit verbundenen Probleme zur Verhinderung von Duplikaten vor.

Vertiefendes Verständnis des Planungsmechanismus für geplante Aufgaben von Laravel

【Verwandte Empfehlung: Laravel-Video-Tutorial

1. Grundlegende Implementierungslogik

In einem komplexen Websystem-Hintergrund müssen viele geplante Skripte oder Aufgaben ausgeführt werden.

Zum Beispiel muss das Crawler-System einige Website-Daten regelmäßig crawlen, das System zur automatischen Kreditrückzahlung muss das Benutzerkonto regelmäßig jeden Monat belasten und begleichen,

Das Mitgliedschaftssystem muss regelmäßig die Anzahl der verbleibenden Mitgliedschaftstage ermitteln Benutzer, um den Benutzer rechtzeitig über die Verlängerung usw. zu informieren. Die in Linux-Systemen integrierte Crontab wird im Allgemeinen häufig zum Ausführen geplanter Aufgaben verwendet. Das Aufgabenbefehlsformat lautet wie folgt:

Erläuterung des Crontab-Befehls

Befehlszeile crontab -e, um die Crontab-Bearbeitung aufzurufen, den Befehl zu bearbeiten, den Sie ausführen möchten, zu speichern und zu beenden, und er wird wirksam.

In diesem Artikel wird jedoch nicht zu viel auf den Inhalt von Crontab eingegangen, sondern es wird eine eingehende Analyse dargelegt, wie das PHP-Laravel-Framework ein leistungsfähigeres Aufgabenplanungsmodul (Task Scheduling) basierend auf Crontab kapselt.

Für geplante Aufgaben können wir natürlich für jede Aufgabe einen Crontab-Befehl konfigurieren. Wenn Sie dies jedoch tun, erhöhen sich mit zunehmender Anzahl geplanter Aufgaben auch die Crontab-Anweisungen linear.

Schließlich handelt es sich bei Crontab um eine Konfiguration auf Systemebene. Um im Unternehmen Maschinen zu sparen, legen wir oft mehrere Projekte mit geringem Volumen auf demselben Server ab Verwirrung bewältigen, und Die Funktionen sind auch nicht flexibel und leistungsstark genug (können nicht nach Belieben anhalten und starten, Abhängigkeiten zwischen Aufgaben verarbeiten usw.).

Laravels Lösung für dieses Problem besteht darin, nur eine Crontab zu deklarieren. Alle geplanten Aufgaben im Unternehmen werden in dieser Crontab verarbeitet und beurteilt, um Aufgaben auf Codeebene zu verwalten:

* * * * * php artisan schedule:run >> /dev/null 2>&1

Das heißt, PHP Artisan Schedule: Run wird jede Minute einmal ausgeführt (die höchste Häufigkeit von crontab), was die spezifische Aufgabenkonfiguration im Unternehmen betrifft, wird sie in Kernel::schedule()

class Kernel extends ConsoleKernel
{
    Protected function schedule(Schedule $schedule)
    {
        $schedule->command('account:check')->everyMinute(); // 每分钟执行一次php artisan account:check 指令
        $schedule->exec('node /home/username/index.js')->everyFifteenMinutes(); //每15分钟执行一次node /home/username/index.js 命令
        $schedule->job(new MyJob())->cron('1 2 3 10 *'); // 每年的10月3日凌晨2点1分向任务队列分发一个MyJob任务
    }
}

Im obigen Beispiel können wir deutlich sehen, dass drei geplante Aufgaben im System registriert sind. und bereitstellen Es bietet everyMinute, everyFifteenMinutes, tägliche, stündliche und andere semantische Methoden zum Konfigurieren von Aufgabenzyklen.

Im Wesentlichen sind diese semantischen Methoden nur ein anderer Name für die Crontab-Darstellung und werden schließlich in den Ausdruck in Crontab umgewandelt (z. B. * * * * * bedeutet einmal pro Minute ausgeführt).

Auf diese Weise scannt der einmal pro Minute ausgeführte PHP-Handwerker-Schedule:run-Befehl alle in Kernel::schedule registrierten Anweisungen und stellt fest, dass der für die Anweisung konfigurierte Ausführungszyklus abgelaufen ist.

Wenn er abläuft, wird er gepusht Warteschlange, die ausgeführt werden soll. Führen Sie abschließend alle Anweisungen der Reihe nach aus.

// ScheduleRunCommand::handle函数
public function handle()
{
    foreach ($this->schedule->dueEvents() as $event) {
        if (! $event->filtersPass()) {
            continue;
        }
        $event->run();
    }
}

Aufgabenablaufplan planen

Hier sind zwei Punkte zu beachten: Erstens, wie man beurteilt, ob die Anweisung fällig war und ausgeführt werden sollte. Zweitens die Frage der Reihenfolge der Ausführung von Anweisungen.

Erstens bezieht sich die durch den Crontab-Ausdruck angegebene Ausführungszeit auf die absolute Zeit, nicht auf die relative Zeit. So kann

allein anhand der aktuellen Zeit und des Crontab-Ausdrucks feststellen, ob die Anweisung fällig war und ausgeführt werden sollte. Wenn Sie die relative Zeit implementieren möchten, müssen Sie den Zeitpunkt der letzten Ausführung speichern und dann berechnen, wann die nächste Ausführung erfolgen soll. Der Unterschied zwischen absoluter Zeit und relativer Zeit kann durch das folgende Bild zusammengefasst werden (die Ausführungszeit von crontab wird in der Liste auf der linken Seite des Bildes angezeigt).

Laravel verwendet die Cron-Expression-Bibliothek (github.com/mtdowling/cron-expression) für die statische Analyse und Beurteilung von Crontab-Ausdrücken. Das Prinzip ist auch relativ intuitiv, nämlich die Analyse und den Vergleich statischer Zeichen.

crontab ist eine absolute Zeit, keine relative Zeit

Das zweite Problem ist die Ausführungsreihenfolge. Aus dem vorherigen Bild können wir ersehen, dass, wenn Sie mehrere Aufgaben in der Kernel::schedule-Methode registrieren,

Normalerweise sie werden nacheinander ausgeführt. Das heißt, die Ausführung von Aufgabe 2 beginnt erst, wenn Aufgabe 1 abgeschlossen ist.

Wenn in diesem Fall Aufgabe 1 sehr zeitaufwändig ist, wirkt sich dies auf die pünktliche Ausführung von Aufgabe 2 aus, die während der Entwicklung besondere Aufmerksamkeit erfordert.

Durch das Hinzufügen von runInBackground bei der Registrierung einer Aufgabe in Kernel::schedule kann jedoch die Ausführung der Aufgabe im Hintergrund realisiert werden.

2. Hintergrundbetrieb

Die zuvor erwähnte Funktion der sequenziellen Ausführung der geplanten Aufgabenwarteschlange ist zu lang und behindert die pünktliche Ausführung der nachfolgenden Aufgaben.

Um dieses Problem zu lösen, stellt Laravel die Methode runInBackground zur Verfügung, um die Aufgabe im Hintergrund auszuführen. Wie zum Beispiel:

// Kernel.php
protected function schedule(Schedule $schedule)
{
    $schedule->command('test:hello') // 执行command命令:php artisan test:hello
    ->cron('10 11 1 * *') // 每月1日的11:10:00执行该命令
    ->timezone('Asia/Shanghai') // 设置时区
    ->before(function(){/*do something*/}) // 前置hook,命令执行前执行此回调
    ->after(function(){/*do something*/}) // 后置钩子,命令执行完之后执行此回调
    ->runInBackground(); // 后台运行本命令
    // 每分钟执行command命令:php artisan test:world
    $schedule->command('test:world')->everyMinute();
}

后台运行的原理,其实也非常简单。我们知道在linux系统下,命令行的指令最后加个“&”符号,可以使任务在后台执行。

runInBackground方法内部原理其实就是让最后跑的指令后面加了“&”符号。不过在任务改为后台执行之后,

又有了一个新的问题,即如何触发任务的后置钩子函数。因为后置钩子函数是需要在任务跑完之后立即执行,

所以必须要有办法监测到后台运行的任务结束的一瞬间。我们从源代码中一探究竟(Illuminate/Console/Scheduling/CommandBuilder.php)

// 构建运行在后台的command指令
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 &'
    );
}

$finished字符串的内容是一个隐藏的php artisan指令,即php artisan schedule:finish

该命令被附在了本来要执行的command命令后面,用来检测并执行后置钩子函数。

php artisan schedule:finish 的源代码非常简单,用mutex_name来唯一标识一个待执行任务,

通过比较系统中注册的所有任务的mutex_name,来确定需要执行哪个任务的后置函数。代码如下:

// Illuminate/Console/Scheduling/ScheduleFinishCommand.php
// php artisan schedule:finish指令的源代码
public function handle()
{
    collect($this->schedule->events())->filter(function ($value) {
        return $value->mutexName() == $this->argument('id');
    })->each->callAfterCallbacks($this->laravel);
}

3. 防止重复

有些定时任务指令需要执行很长时间,而laravel schedule任务最频繁可以做到1分钟跑一次。

这也就意味着,如果任务本身跑了1分钟以上都没有结束,那么等到下一个1分钟到来的时候,又一个相同的任务跑起来了。

这很可能是我们不想看到的结果。因此,有必要想一种机制,来避免任务在同一时刻的重复执行(prevent overlapping)。

这种场景非常类似多进程或者多线程的程序抢夺资源的情形,常见的预防方式就是给资源加锁。

具体到laravel定时任务,那就是给任务加锁,只有拿到任务锁之后,才能够执行任务的具体内容。

Laravel中提供了withoutOverlapping方法来让定时任务避免重复。具体锁的实现上,需要实现Illuminate\Console\Scheduling\Mutex.php接口中所定义的三个接口:

interface Mutex
{
    // 实现创建锁接口
    public function create(Event $event);
    // 实现判断锁是否存在的接口
    public function exists(Event $event);
    // 实现解除锁的接口
    public function forget(Event $event);
}

该接口当然可以自己实现,Laravel也给了一套默认实现,即利用缓存作为存储锁的载体(可参考Illuminate\Console\Scheduling\CacheMutex.php文件)。

在每次跑任务之间,程序都会做出判断,是否需要防止重复,如果重复了,则不再跑任务代码:

// Illuminate\Console\Scheduling\Event.php
public function run()
{
    // 判断是否需要防止重复,若需要防重复,并且创建锁不成功,则说明已经有任务在跑了,这时直接退出,不再执行具体任务
    if ($this->withoutOverlapping && ! $this->mutex->create($this)) {
        return;
    }
    $this->runInBackground?$this->runCommandInBackground($container):$this->runCommandInForeground($container);
}

4. 如何实现30秒任务?

我们知道crontab任务最精细的粒度只能到分钟级别。那么如果我想实现30s执行一次的任务,

需要如何实现?关于这个问题,stackoverflow上面也有一些讨论,有建议说在业务层面实现,自己写个sleep来实现,示例代码如下:

public function handle()
{
    runYourCode(); // 跑业务代码
    sleep(30); // 睡30秒
    runYourCode(); // 再跑一次业务代码
}

如果runYourCode执行实现不太长的话,上面这个任务每隔1min执行一次,其实相当于runYourCode函数每30秒执行一次。

如果runYourCode函数本身执行时间比较长,那这里的sleep 30秒会不那么精确。

当然,也可以不使用Laravel的定时任务系统,改用专门的定时任务调度开源工具来实现每隔30秒执行一次的功能,

在此推荐一个定时任务调度工具nomad(https://github.com/hashicorp/nomad)。

如果你确实要用Laravel自带的定时任务系统,并且又想实现更精确一些的每隔30秒执行一次任务的功能,那么可以结合laravel 的queue job来实现。如下:

public function handle()
{
    $job1 = (new MyJob())->onQueue(“queue-name”);
    $job2 = (new MyJob())->onQueue(“queue-name”)->delay(30);
    dispatch($job1);
    dispatch($job2):
}

class MyJob implement Illuminate\Contracts\Queue\ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
    
    public function handle()
    {
        runYourCode();
    }
}

通过Laravel 队列功能的delay方法,可以将任务延时30s执行,因此如果每隔1min,我们都往队列中dispatch两个任务,其中一个延时30秒。

另外,把自己要执行的代码runYourCode写在任务中,即可实现30秒执行一次的功能。不过这里需要注意的是,这种实现中scheduling的防止重合功能不再有效,

需要自己在业务代码runYourCode中实现加锁防止重复的功能。

以上,就是使用Laravel Scheduling定时任务调度的原理分析和注意事项。作为最流行的PHP框架,Laravel大而全,

组件基本包含了web开发的各方面需求。其中很多组件的实现思想,还是很值得深入源码一探究竟的。

【相关推荐:laravel视频教程

Das obige ist der detaillierte Inhalt vonVertiefendes Verständnis des Planungsmechanismus für geplante Aufgaben von Laravel. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:csdn.net. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen