Maison  >  Article  >  cadre php  >  Cet article vous amènera à comprendre le mécanisme de fonctionnement de la planification Laravel.

Cet article vous amènera à comprendre le mécanisme de fonctionnement de la planification Laravel.

青灯夜游
青灯夜游avant
2022-02-17 19:26:523163parcourir

Cet article parlera du mécanisme de fonctionnement du planning dans Laravel. J'espère qu'il vous sera utile !

Cet article vous amènera à comprendre le mécanisme de fonctionnement de la planification Laravel.

  La ligne de commande de la console de Laravel facilite grandement la configuration et l'exécution des tâches planifiées PHP. Dans le passé, le processus de configuration des tâches planifiées via crontab était relativement fastidieux et il était difficile d'éviter le chevauchement des tâches pour les tâches planifiées définies via crontab. crontab 配置定时任务过程相对比较繁琐,并且通过 crontab 设置的定时任务很难防止任务的交叠运行。

  所谓任务的交叠运行,是指由于定时任务运行时间较长,在 crontab 设置的运行周期不尽合理的情况下,已经启动的任务还没有结束运行,而系统又启动了新的任务去执行相同的操作。如果程序内部没有处理好数据一致性的问题,那么两个任务同时操作同一份数据,很可能会导致严重的后果。

runInBackgroundwithoutOverlapping

  为了防止任务的交叠运行,Laravel 提供了 withoutOverlapping() 方法;为了能让多任务在后台并行执行,Laravel 提供了 runInBackground() 方法。

runInBackground() 方法

  console 命令行中的每一个命令都代表一个 EventAppConsoleKernel 中的 schedule() 方法的作用只是将这些命令行代表的 Event 注册到 IlluminateConsoleSchedulingSchedule 的属性 $events 中。

// namespace \Illuminate\Console\Scheduling\Schedule

public function command($command, array $parameters = [])
{
    if (class_exists($command)) {
        $command = Container::getInstance()->make($command)->getName();
    }

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

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;
}

  Event 的运行方式有两种:ForegroundBackground 。二者的区别就在于多个 Event 是否可以并行执行。Event 默认以 Foreground 的方式运行,在这种运行方式下,多个 Event 顺序执行,后面的 Event 需要等到前面的 Event 运行完成之后才能开始执行。

  但在实际应用中,我们往往是希望多个 Event 可以并行执行,此时就需要调用 EventrunInBackground() 方法将其运行方式设置为 Background

  Laravel 框架对这两种运行方式的处理区别在于命令行的组装方式和回调方法的调用方式。

// namespace \Illuminate\Console\Scheduling\Event
protected function runCommandInForeground(Container $container)
{
    $this->callBeforeCallbacks($container);

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

    $this->callAfterCallbacks($container);
}

protected function runCommandInBackground(Container $container)
{
    $this->callBeforeCallbacks($container);

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

public function buildCommand()
{
    return (new CommandBuilder)->buildCommand($this);
}

// namespace Illuminate\Console\Scheduling\CommandBuilder
public function buildCommand(Event $event)
{
    if ($event->runInBackground) {
        return $this->buildBackgroundCommand($event);
    }

    return $this->buildForegroundCommand($event);
}

protected function buildForegroundCommand(Event $event)
{
    $output = ProcessUtils::escapeArgument($event->output);

    return $this->ensureCorrectUser(
        $event, $event->command.($event->shouldAppendOutput ? ' >> ' : ' > ').$output.' 2>&1'
    );
}

protected function buildBackgroundCommand(Event $event)
{
    $output = ProcessUtils::escapeArgument($event->output);

    $redirect = $event->shouldAppendOutput ? ' >> ' : ' > ';

    $finished = Application::formatCommandString('schedule:finish').' "'.$event->mutexName().'"';

    if (windows_os()) {
        return 'start /b cmd /c "('.$event->command.' & '.$finished.' "%errorlevel%")'.$redirect.$output.' 2>&1"';
    }

    return $this->ensureCorrectUser($event,
        '('.$event->command.$redirect.$output.' 2>&1 ; '.$finished.' "$?") > '
        .ProcessUtils::escapeArgument($event->getDefaultOutput()).' 2>&1 &'
    );
}

  从代码中可以看出,采用 Background 方式运行的 Event ,其命令行在组装的时候结尾会增加一个 & 符号,其作用是使命令行程序进入后台运行;另外,采用 Foreground 方式运行的 Event ,其回调方法是同步调用的,而采用 Background 方式运行的 Event ,其 after 回调则是通过 schedule:finish 命令行来执行的。

withoutOverlapping() 方法

  在设置 Event 的运行周期时,由于应用场景的不断变化,很难避免某个特定的 Event 在某个时间段内需要运行较长的时间才能完成,甚至在下一个运行周期开始时还没有执行完成。如果不对这种情况进行处理,就会导致多个相同的 Event 同时运行,而如果这些 Event 当中涉及到对数据的操作并且程序中没有处理好幂等问题,很可能会造成严重后果。

  为了避免出现上述的问题,Event 中提供了 withoutOverlapping() 方法,该方法通过将 EventwithoutOverlapping 属性设置为 TRUE ,在每次要执行 Event 时会检查当前是否存在正在执行的相同的 Event ,如果存在,则不执行新的 Event 任务。

// namespace Illuminate\Console\Scheduling\Event
public function withoutOverlapping($expiresAt = 1440)
{
    $this->withoutOverlapping = true;

    $this->expiresAt = $expiresAt;

    return $this->then(function () {
        $this->mutex->forget($this);
    })->skip(function () {
        return $this->mutex->exists($this);
    });
}

public function run(Container $container)
{
    if ($this->withoutOverlapping &&
        ! $this->mutex->create($this)) {
        return;
    }

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

mutex 互斥锁

  在调用 withoutOverlapping() 方法时,该方法还实现了另外两个功能:一个是设置超时时间,默认为 24 小时;另一个是设置 Event

  Le soi-disant exécution superposée des tâches signifie qu'en raison de la longue durée d'exécution des tâches planifiées, lorsque le cycle d'exécution défini par crontab n'est pas raisonnable, les tâches démarrées n'ont pas encore fini de s'exécuter et le système en démarre de nouvelles. . tâches pour effectuer la même opération. Si le problème de cohérence des données n’est pas bien géré au sein du programme, alors deux tâches opérant simultanément sur les mêmes données peuvent avoir de graves conséquences.

runInBackground et sans chevauchement

  Pour éviter les tâches qui se chevauchent, Laravel fournit sans chevauchement () ; afin de permettre l'exécution de plusieurs tâches en parallèle en arrière-plan, Laravel fournit la méthode runInBackground(). 🎜

⑴ Méthode runInBackground()

🎜  console Chaque commande de la ligne de commande représente un Event , la méthode schedule() dans AppConsoleKernel est uniquement utilisée pour enregistrer le Event représenté par ces lignes de commande dans In la propriété <code>$events de IlluminateConsoleSchedulingSchedule. 🎜
// namespace \Illuminate\Console\Scheduling\Schedule
$this->eventMutex = $container->bound(EventMutex::class)
                                ? $container->make(EventMutex::class)
                                : $container->make(CacheEventMutex::class);
🎜  Il existe deux façons d'exécuter un Événement : Premier plan et Arrière-plan. La différence entre les deux réside dans la possibilité d'exécuter plusieurs Event en parallèle. Event s'exécute en mode Foreground par défaut. Dans ce mode d'exécution, plusieurs Event sont exécutés séquentiellement, et le Event suivant. > code> doit attendre que l'Événement précédent soit terminé avant que l'exécution puisse commencer. 🎜🎜  Mais dans les applications réelles, nous espérons souvent que plusieurs Event pourront être exécutés en parallèle. Dans ce cas, nous devons appeler runInBackground() de Event<.> La méthode définit son mode d'exécution sur <code>Arrière-plan. 🎜🎜  La différence entre la façon dont le framework Laravel gère ces deux méthodes d'exécution réside dans la façon dont la ligne de commande est assemblée et la façon dont la méthode de rappel est appelée. 🎜
// namespace \Illuminate\Foundation\Console\Kernel
protected function defineConsoleSchedule()
{
    $this->app->singleton(Schedule::class, function ($app) {
        return tap(new Schedule($this->scheduleTimezone()), function ($schedule) {
            $this->schedule($schedule->useCache($this->scheduleCache()));
        });
    });
}

protected function scheduleCache()
{
    return Env::get('SCHEDULE_CACHE_DRIVER');
}

// namespace \Illuminate\Console\Scheduling\Schedule
public function useCache($store)
{
    if ($this->eventMutex instanceof CacheEventMutex) {
        $this->eventMutex->useStore($store);
    }

    /* ... ... */
    return $this;
}

// namespace \Illuminate\Console\Scheduling\CacheEventMutex
public function create(Event $event)
{
    return $this->cache->store($this->store)->add(
        $event->mutexName(), true, $event->expiresAt * 60
    );
}

// namespace \Illuminate\Cache\CacheManager
public function store($name = null)
{
    $name = $name ?: $this->getDefaultDriver();

    return $this->stores[$name] = $this->get($name);
}

public function getDefaultDriver()
{
    return $this->app['config']['cache.default'];
}

protected function get($name)
{
    return $this->stores[$name] ?? $this->resolve($name);
}

protected function resolve($name)
{
    $config = $this->getConfig($name);

    if (is_null($config)) {
        throw new InvalidArgumentException("Cache store [{$name}] is not defined.");
    }

    if (isset($this->customCreators[$config['driver']])) {
        return $this->callCustomCreator($config);
    } else {
        $driverMethod = 'create'.ucfirst($config['driver']).'Driver';

        if (method_exists($this, $driverMethod)) {
            return $this->{$driverMethod}($config);
        } else {
            throw new InvalidArgumentException("Driver [{$config['driver']}] is not supported.");
        }
    }
}

protected function getConfig($name)
{
    return $this->app['config']["cache.stores.{$name}"];
}

protected function createFileDriver(array $config)
{
    return $this->repository(new FileStore($this->app['files'], $config['path'], $config['permission'] ?? null));
}
🎜  Il ressort du code que lorsque l'Événement est exécuté en mode Arrière-plan, un &amp; sera ajouté à la fin de la ligne de commande lors de l'assemblage de Symbol, sa fonction est de faire exécuter le programme de ligne de commande en arrière-plan. De plus, la méthode de rappel de Event exécutée en mode Foreground est ; appelé de manière synchrone, tout en utilisant Background Pour <code>Event exécuté en mode , son rappel after est exécuté via le schedule:finish ligne de commande. 🎜

⑵ Méthode withoutOverlapping()

🎜 — Définition du cycle d'exécution de Event À l'heure actuelle, en raison des changements constants dans les scénarios d'application, il est difficile d'éviter qu'un Événement spécifique prenne beaucoup de temps à se terminer dans un certain laps de temps, voire ne soit pas terminé à le début du prochain cycle d’exécution. Si cette situation n'est pas gérée, plusieurs Événements identiques s'exécuteront en même temps, et si ces Événements impliquent des opérations sur les données et ne sont pas bien gérés dans le programme Les problèmes d’idempotence risquent d’avoir de graves conséquences. 🎜🎜  Afin d'éviter les problèmes ci-dessus, Event fournit la méthode withoutOverlapping(), qui modifie le withoutOverlappingEvent. L'attribut /code> est défini sur TRUE Chaque fois que Event doit être exécuté, il sera vérifié si le même Event est actuellement en cours. exécutée Si elle existe, la nouvelle tâche Evénement ne sera pas exécutée. 🎜
// namespace \Illuminate\Cache\Repository
public function add($key, $value, $ttl = null)
{
    if ($ttl !== null) {
        if ($this-&amp;gt;getSeconds($ttl) &amp;lt;= 0) {
            return false;
        }

        if (method_exists($this-&amp;gt;store, &amp;amp;#39;add&amp;amp;#39;)) {
            $seconds = $this-&amp;gt;getSeconds($ttl);

            return $this-&amp;gt;store-&amp;gt;add(
                $this-&amp;gt;itemKey($key), $value, $seconds
            );
        }
    }

    if (is_null($this-&amp;gt;get($key))) {
        return $this-&amp;gt;put($key, $value, $ttl);
    }

    return false;
}

public function get($key, $default = null)
{
    if (is_array($key)) {
        return $this-&amp;gt;many($key);
    }

    $value = $this-&amp;gt;store-&amp;gt;get($this-&amp;gt;itemKey($key));

    if (is_null($value)) {
        $this-&amp;gt;event(new CacheMissed($key));

        $value = value($default);
    } else {
        $this-&amp;gt;event(new CacheHit($key, $value));
    }

    return $value;
}

// namespace \Illuminate\Cache\FileStore
public function get($key)
{
    return $this-&amp;gt;getPayload($key)[&amp;amp;#39;data&amp;amp;#39;] ?? null;
}

protected function getPayload($key)
{
    $path = $this-&amp;gt;path($key);

    try {
        $expire = substr(
            $contents = $this-&amp;gt;files-&amp;gt;get($path, true), 0, 10
        );
    } catch (Exception $e) {
        return $this-&amp;gt;emptyPayload();
    }

    if ($this-&amp;gt;currentTime() &amp;gt;= $expire) {
        $this-&amp;gt;forget($key);

        return $this-&amp;gt;emptyPayload();
    }

    try {
        $data = unserialize(substr($contents, 10));
    } catch (Exception $e) {
        $this-&amp;gt;forget($key);

        return $this-&amp;gt;emptyPayload();
    }

    $time = $expire - $this-&amp;gt;currentTime();

    return compact(&amp;amp;#39;data&amp;amp;#39;, &amp;amp;#39;time&amp;amp;#39;);
}

mutex Verrouillage mutex

🎜 Lors de l'appel de la méthode withoutOverlapping(), cette méthode implémente également Two d'autres fonctions sont fournies : l'une consiste à définir le délai d'attente, qui est par défaut de 24 heures ; l'autre consiste à définir le rappel de Event. 🎜

⑴ 超时时间

  首先说超时时间,这个超时时间并不是 Event 的超时时间,而是 Event 的属性 mutex 的超时时间。在向 Illuminate\Console\Scheduling\Schedule 的属性 $events 中注册 Event 时,会调用 Schedule 中的 exec() 方法,在该方法中会新建 Event 对象,此时会向 Event 的构造方法中传入一个 eventMutex ,这就是 Event 对象中的属性 mutex ,超时时间就是为这个 mutex 设置的。而 Schedule 中的 eventMutex 则是通过实例化 CacheEventMutex 来创建的。

// namespace \Illuminate\Console\Scheduling\Schedule
$this-&amp;gt;eventMutex = $container-&amp;gt;bound(EventMutex::class)
                                ? $container-&amp;gt;make(EventMutex::class)
                                : $container-&amp;gt;make(CacheEventMutex::class);

  设置了 withoutOverlappingEvent 在执行之前,首先会尝试获取 mutex 互斥锁,如果无法成功获取到锁,那么 Event 就不会执行。获取互斥锁的操作通过调用 mutexcreate() 方法完成。

  CacheEventMutex 在实例化时需要传入一个 \Illuminate\Contracts\Cache\Factory 类型的实例,其最终传入的是一个 \Illuminate\Cache\CacheManager 实例。在调用 create() 方法获取互斥锁时,还需要通过调用 store() 方法设置存储引擎。

// namespace \Illuminate\Foundation\Console\Kernel
protected function defineConsoleSchedule()
{
    $this-&amp;gt;app-&amp;gt;singleton(Schedule::class, function ($app) {
        return tap(new Schedule($this-&amp;gt;scheduleTimezone()), function ($schedule) {
            $this-&amp;gt;schedule($schedule-&amp;gt;useCache($this-&amp;gt;scheduleCache()));
        });
    });
}

protected function scheduleCache()
{
    return Env::get(&amp;amp;#39;SCHEDULE_CACHE_DRIVER&amp;amp;#39;);
}

// namespace \Illuminate\Console\Scheduling\Schedule
public function useCache($store)
{
    if ($this-&amp;gt;eventMutex instanceof CacheEventMutex) {
        $this-&amp;gt;eventMutex-&amp;gt;useStore($store);
    }

    /* ... ... */
    return $this;
}

// namespace \Illuminate\Console\Scheduling\CacheEventMutex
public function create(Event $event)
{
    return $this-&amp;gt;cache-&amp;gt;store($this-&amp;gt;store)-&amp;gt;add(
        $event-&amp;gt;mutexName(), true, $event-&amp;gt;expiresAt * 60
    );
}

// namespace \Illuminate\Cache\CacheManager
public function store($name = null)
{
    $name = $name ?: $this-&amp;gt;getDefaultDriver();

    return $this-&amp;gt;stores[$name] = $this-&amp;gt;get($name);
}

public function getDefaultDriver()
{
    return $this-&amp;gt;app[&amp;amp;#39;config&amp;amp;#39;][&amp;amp;#39;cache.default&amp;amp;#39;];
}

protected function get($name)
{
    return $this-&amp;gt;stores[$name] ?? $this-&amp;gt;resolve($name);
}

protected function resolve($name)
{
    $config = $this-&amp;gt;getConfig($name);

    if (is_null($config)) {
        throw new InvalidArgumentException(&amp;quot;Cache store [{$name}] is not defined.&amp;quot;);
    }

    if (isset($this-&amp;gt;customCreators[$config[&amp;amp;#39;driver&amp;amp;#39;]])) {
        return $this-&amp;gt;callCustomCreator($config);
    } else {
        $driverMethod = &amp;amp;#39;create&amp;amp;#39;.ucfirst($config[&amp;amp;#39;driver&amp;amp;#39;]).&amp;amp;#39;Driver&amp;amp;#39;;

        if (method_exists($this, $driverMethod)) {
            return $this-&amp;gt;{$driverMethod}($config);
        } else {
            throw new InvalidArgumentException(&amp;quot;Driver [{$config[&amp;amp;#39;driver&amp;amp;#39;]}] is not supported.&amp;quot;);
        }
    }
}

protected function getConfig($name)
{
    return $this-&amp;gt;app[&amp;amp;#39;config&amp;amp;#39;][&amp;quot;cache.stores.{$name}&amp;quot;];
}

protected function createFileDriver(array $config)
{
    return $this-&amp;gt;repository(new FileStore($this-&amp;gt;app[&amp;amp;#39;files&amp;amp;#39;], $config[&amp;amp;#39;path&amp;amp;#39;], $config[&amp;amp;#39;permission&amp;amp;#39;] ?? null));
}

  在初始化 Schedule 时会指定 eventMutex 的存储引擎,默认为环境变量中的配置项 SCHEDULE_CACHE_DRIVER 的值。但通常这一项配置在环境变量中并不存在,所以 useCache() 的参数值为空,进而 eventMutexstore 属性值也为空。这样,在 eventMutexcreate() 方法中调用 store() 方法为其设置存储引擎时,store() 方法的参数值也为空。

  当 store() 方法的传参为空时,会使用应用的默认存储引擎(如果不做任何修改,默认 cache 的存储引擎为 file)。之后会取得默认存储引擎的配置信息(引擎、存储路径、连接信息等),然后实例化存储引擎。最终,file 存储引擎实例化的是 \Illuminate\Cache\FileStore

  在设置完存储引擎之后,紧接着会调用 add() 方法获取互斥锁。由于 store() 方法返回的是 \Illuminate\Contracts\Cache\Repository 类型的实例,所以最终调用的是 Illuminate\Cache\Repository 中的 add() 方法。

// namespace \Illuminate\Cache\Repository
public function add($key, $value, $ttl = null)
{
    if ($ttl !== null) {
        if ($this-&amp;gt;getSeconds($ttl) &amp;lt;= 0) {
            return false;
        }

        if (method_exists($this-&amp;gt;store, &amp;amp;#39;add&amp;amp;#39;)) {
            $seconds = $this-&amp;gt;getSeconds($ttl);

            return $this-&amp;gt;store-&amp;gt;add(
                $this-&amp;gt;itemKey($key), $value, $seconds
            );
        }
    }

    if (is_null($this-&amp;gt;get($key))) {
        return $this-&amp;gt;put($key, $value, $ttl);
    }

    return false;
}

public function get($key, $default = null)
{
    if (is_array($key)) {
        return $this-&amp;gt;many($key);
    }

    $value = $this-&amp;gt;store-&amp;gt;get($this-&amp;gt;itemKey($key));

    if (is_null($value)) {
        $this-&amp;gt;event(new CacheMissed($key));

        $value = value($default);
    } else {
        $this-&amp;gt;event(new CacheHit($key, $value));
    }

    return $value;
}

// namespace \Illuminate\Cache\FileStore
public function get($key)
{
    return $this-&amp;gt;getPayload($key)[&amp;amp;#39;data&amp;amp;#39;] ?? null;
}

protected function getPayload($key)
{
    $path = $this-&amp;gt;path($key);

    try {
        $expire = substr(
            $contents = $this-&amp;gt;files-&amp;gt;get($path, true), 0, 10
        );
    } catch (Exception $e) {
        return $this-&amp;gt;emptyPayload();
    }

    if ($this-&amp;gt;currentTime() &amp;gt;= $expire) {
        $this-&amp;gt;forget($key);

        return $this-&amp;gt;emptyPayload();
    }

    try {
        $data = unserialize(substr($contents, 10));
    } catch (Exception $e) {
        $this-&amp;gt;forget($key);

        return $this-&amp;gt;emptyPayload();
    }

    $time = $expire - $this-&amp;gt;currentTime();

    return compact(&amp;amp;#39;data&amp;amp;#39;, &amp;amp;#39;time&amp;amp;#39;);
}

  这里需要说明,所谓互斥锁,其本质是写文件。如果文件不存在或文件内容为空或文件中存储的过期时间小于当前时间,则互斥锁可以顺利获得;否则无法获取到互斥锁。文件内容为固定格式:timestampb:1

  所谓超时时间,与此处的 timestamp 的值有密切的联系。获取互斥锁时的时间戳,再加上超时时间的秒数,即是此处的 timestamp 的值。

  由于 FileStore 中不存在 add() 方法,所以程序会直接尝试调用 get() 方法获取文件中的内容。如果 get() 返回的结果为 NULL,说明获取互斥锁成功,之后会调用 FileStoreput() 方法写文件;否则,说明当前有相同的 Event 在运行,不会再运行新的 Event

  在调用 put() 方法写文件时,首先需要根据传参计算 eventMutex 的超时时间的秒数,之后再调用 FileStore 中的 put() 方法,将数据写入文件中。

// namespace \Illuminate\Cache\Repository
public function put($key, $value, $ttl = null)
{
    /* ... ... */

    $seconds = $this-&amp;gt;getSeconds($ttl);

    if ($seconds &amp;lt;= 0) {
        return $this-&amp;gt;forget($key);
    }

    $result = $this-&amp;gt;store-&amp;gt;put($this-&amp;gt;itemKey($key), $value, $seconds);

    if ($result) {
        $this-&amp;gt;event(new KeyWritten($key, $value, $seconds));
    }

    return $result;
}

// namespace \Illuminate\Cache\FileStore
public function put($key, $value, $seconds)
{
    $this-&amp;gt;ensureCacheDirectoryExists($path = $this-&amp;gt;path($key));

    $result = $this-&amp;gt;files-&amp;gt;put(
        $path, $this-&amp;gt;expiration($seconds).serialize($value), true
    );

    if ($result !== false &amp;amp;&amp;amp; $result &amp;gt; 0) {
        $this-&amp;gt;ensureFileHasCorrectPermissions($path);

        return true;
    }

    return false;
}

protected function path($key)
{
    $parts = array_slice(str_split($hash = sha1($key), 2), 0, 2);

    return $this-&amp;gt;directory.&amp;amp;#39;/&amp;amp;#39;.implode(&amp;amp;#39;/&amp;amp;#39;, $parts).&amp;amp;#39;/&amp;amp;#39;.$hash;
}

// namespace \Illuminate\Console\Scheduling\Schedule
public function mutexName()
{
    return &amp;amp;#39;framework&amp;amp;#39;.DIRECTORY_SEPARATOR.&amp;amp;#39;schedule-&amp;amp;#39;.sha1($this-&amp;gt;expression.$this-&amp;gt;command);
}

  这里需要重点说明的是 $key 的生成方法以及文件路径的生成方法。$key 通过调用 EventmutexName() 方法生成,其中需要用到 Event$expression$command 属性。其中 $command 为我们定义的命令行,在调用 $schedule->comand() 方法时传入,然后进行格式化,$expression 则为 Event 的运行周期。

  以命令行 schedule:test 为例,格式化之后的命令行为 `/usr/local/php/bin/php` `artisan` schedule:test,如果该命令行设置的运行周期为每分钟一次,即 * * * * * ,则最终计算得到的 $key 的值为 framework/schedule-768a42da74f005b3ac29ca0a88eb72d0ca2b84be 。文件路径则是将 $key 的值再次进行 sha1 计算之后,以两个字符为一组切分成数组,然后取数组的前两项组成一个二级目录,而配置文件中 file 引擎的默认存储路径为 storage/framework/cache/data ,所以最终的文件路径为 storage/framework/cache/data/eb/60/eb608bf555895f742e5bd57e186cbd97f9a6f432 。而文件中存储的内容则为 1642122685b:1

⑵ 回调方法

  再来说设置的 Event 回调,调用 withoutOverlapping() 方法会为 Event 设置两个回调:一个是 Event 运行完成之后的回调,用于释放互斥锁,即清理缓存文件;另一个是在运行 Event 之前判断互斥锁是否被占用,即缓存文件是否已经存在。

  无论 Event 是以 Foreground 的方式运行,还是以 Background 的方式运行,在运行完成之后都会调用 callAfterCallbacks() 方法执行 afterCallbacks 中的回调,其中就有一项回调用于释放互斥锁,删除缓存文件 $this->mutex->forget($this) 。区别就在于,以 Foreground 方式运行的 Event 是在运行完成之后显式的调用这些回调方法,而以 Background 方式运行的 Event 则需要借助 schedule:finish 来调用这些回调方法。

  所有在 \App\Console\Kernel 中注册 Event,都是通过命令行 schedule:run 来调度的。在调度之前,首先会判断当前时间点是否满足各个 Event 所配置的运行周期的要求。如果满足的话,接下来就是一些过滤条件的判断,这其中就包括判断互斥锁是否被占用。只有在互斥锁没有被占用的情况下,Event 才可以运行。

// namespace \Illuminate\Console\Scheduling\ScheduleRunCommand
public function handle(Schedule $schedule, Dispatcher $dispatcher)
{
    $this-&amp;gt;schedule = $schedule;
    $this-&amp;gt;dispatcher = $dispatcher;

    foreach ($this-&amp;gt;schedule-&amp;gt;dueEvents($this-&amp;gt;laravel) as $event) {
        if (! $event-&amp;gt;filtersPass($this-&amp;gt;laravel)) {
            $this-&amp;gt;dispatcher-&amp;gt;dispatch(new ScheduledTaskSkipped($event));

            continue;
        }

        if ($event-&amp;gt;onOneServer) {
            $this-&amp;gt;runSingleServerEvent($event);
        } else {
            $this-&amp;gt;runEvent($event);
        }

        $this-&amp;gt;eventsRan = true;
    }

    if (! $this-&amp;gt;eventsRan) {
        $this-&amp;gt;info(&amp;amp;#39;No scheduled commands are ready to run.&amp;amp;#39;);
    }
}

// namespace \Illuminate\Console\Scheduling\Schedule
public function dueEvents($app)
{
    return collect($this-&amp;gt;events)-&amp;gt;filter-&amp;gt;isDue($app);
}

// namespace \Illuminate\Console\Scheduling\Event
public function isDue($app)
{
    /* ... ... */
    return $this-&amp;gt;expressionPasses() &amp;amp;&amp;amp;
           $this-&amp;gt;runsInEnvironment($app-&amp;gt;environment());
}

protected function expressionPasses()
{
    $date = Carbon::now();
    /* ... ... */
    return CronExpression::factory($this-&amp;gt;expression)-&amp;gt;isDue($date-&amp;gt;toDateTimeString());
}

// namespace \Cron\CronExpression
public function isDue($currentTime = &amp;amp;#39;now&amp;amp;#39;, $timeZone = null)
{
   /* ... ... */
   
    try {
        return $this-&amp;gt;getNextRunDate($currentTime, 0, true)-&amp;gt;getTimestamp() === $currentTime-&amp;gt;getTimestamp();
    } catch (Exception $e) {
        return false;
    }
}

public function getNextRunDate($currentTime = &amp;amp;#39;now&amp;amp;#39;, $nth = 0, $allowCurrentDate = false, $timeZone = null)
{
    return $this-&amp;gt;getRunDate($currentTime, $nth, false, $allowCurrentDate, $timeZone);
}
  有时候,我们可能需要 kill 掉一些在后台运行的命令行,但紧接着我们会发现这些被 kill 掉的命令行在一段时间内无法按照设置的运行周期自动调度,其原因就在于手动 kill 掉的命令行没有调用 schedule:finish 清理缓存文件,释放互斥锁。这就导致在设置的过期时间到达之前,互斥锁会一直被占用,新的 Event 不会再次运行。

【相关推荐:laravel视频教程

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer