>PHP 프레임워크 >Laravel >이 글은 Laravel 일정 스케줄링의 작동 메커니즘을 이해하는 데 도움이 될 것입니다.

이 글은 Laravel 일정 스케줄링의 작동 메커니즘을 이해하는 데 도움이 될 것입니다.

青灯夜游
青灯夜游앞으로
2022-02-17 19:26:523339검색

이 글은 Laravel의 스케줄 운영 메커니즘에 대해 설명하겠습니다. 도움이 되길 바랍니다!

이 글은 Laravel 일정 스케줄링의 작동 메커니즘을 이해하는 데 도움이 될 것입니다.

  Laravel의 콘솔 명령줄은 PHP 예약 작업의 설정 및 실행을 크게 촉진합니다. 과거에는 crontab을 통해 예약된 작업을 구성하는 과정이 상대적으로 번거로웠고, 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

  소위 작업 중복 실행은 예약된 작업의 실행 시간이 길어서 crontab에서 설정한 실행 주기가 합리적이지 않을 때 시작된 작업이 아직 완료되지 않고 시스템이 새 작업을 시작하는 것을 의미합니다. . 동일한 작업을 수행하는 작업입니다. 프로그램 내에서 데이터 일관성 문제가 제대로 처리되지 않으면 동일한 데이터에 대해 동시에 두 가지 작업이 실행되면 심각한 결과를 초래할 수 있습니다.

runInBackgroundwithoutOverlapping

  중복되는 작업을 방지하기 위해 Laravel은 withoutOverlapping을 제공합니다. () 메서드; 여러 작업이 백그라운드에서 병렬로 실행될 수 있도록 Laravel은 runInBackground() 메서드를 제공합니다. 🎜

runInBackground() 메서드

🎜  console 명령줄의 각 명령은 이벤트를 나타냅니다. 에서 AppConsoleKernelschedule() 메서드는 이러한 명령줄로 표시되는 이벤트에 등록하는 데에만 사용됩니다. IlluminateConsoleSchedulingSchedule$events 속성. 🎜
// namespace \Illuminate\Console\Scheduling\Schedule
$this->eventMutex = $container->bound(EventMutex::class)
                                ? $container->make(EventMutex::class)
                                : $container->make(CacheEventMutex::class);
🎜  이벤트를 실행하는 방법에는 포그라운드백그라운드 두 가지가 있습니다. 둘 사이의 차이점은 여러 이벤트를 병렬로 실행할 수 있는지 여부에 있습니다. 이벤트는 기본적으로 포그라운드 모드에서 실행됩니다. 이 실행 모드에서는 여러 이벤트가 순차적으로 실행되며 다음 이벤트가 실행됩니다. > code>는 실행을 시작하기 전에 이전 이벤트가 완료될 때까지 기다려야 합니다. 🎜🎜  하지만 실제 애플리케이션에서는 여러 Event가 병렬로 실행되기를 바라는 경우가 많습니다. 이 경우 EventrunInBackground()를 호출해야 합니다. /code> 이 메서드는 실행 모드를 Background로 설정합니다. 🎜🎜  Laravel 프레임워크가 이 두 가지 실행 메서드를 처리하는 방법의 차이점은 명령줄을 구성하는 방식과 콜백 메서드를 호출하는 방식에 있습니다. 🎜
// 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));
}
🎜  이벤트백그라운드 모드에서 실행되면 끝에 &amp;가 추가되는 것을 코드에서 볼 수 있습니다. Symbol의 기능은 명령줄 프로그램을 백그라운드에서 실행하는 것이며 Foreground 모드에서 실행되는 Event의 콜백 메서드는 다음과 같습니다. 동기적으로 호출되며, 모드에서 Background For <code>Event 실행을 사용하는 동안 해당 after 콜백은 schedule:finish를 통해 실행됩니다. 명령줄. 🎜

withoutOverlapping() 메서드

🎜 — 이벤트의 실행 주기 설정 > 현재 애플리케이션 시나리오의 지속적인 변화로 인해 특정 이벤트가 특정 기간 내에 완료되는 데 오랜 시간이 걸리거나 심지어 완료되지 않는 경우도 피하기 어렵습니다. 다음 실행 주기의 시작입니다. 이 상황을 처리하지 않으면 여러 개의 동일한 이벤트가 동시에 실행되고, 이러한 이벤트가 데이터에 대한 작업을 포함하고 프로그램에서 제대로 처리되지 않는 경우 멱등성 문제는 심각한 결과를 초래할 수 있습니다. 🎜🎜  위 문제를 방지하기 위해 EventEventwithoutOverlappingwithoutOverlapping() 메서드를 제공합니다. /code> 속성이 TRUE로 설정되어 있습니다. 이벤트가 실행될 때마다 현재 동일한 이벤트가 있는지 확인합니다. 실행됩니다. 존재하는 경우 새 이벤트 작업이 실행되지 않습니다. 🎜
// 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 Mutex lock

🎜  withoutOverlapping() 메서드를 호출할 때 이 메서드는 Two도 구현합니다. 다른 기능도 제공됩니다. 하나는 기본적으로 24시간으로 설정된 시간 제한을 설정하는 것이고, 다른 하나는 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视频教程

위 내용은 이 글은 Laravel 일정 스케줄링의 작동 메커니즘을 이해하는 데 도움이 될 것입니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.cn에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제