In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-02-23 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/01 Report--
This article mainly introduces "what is the operating mechanism of Laravel schedule scheduling". In the daily operation, I believe that many people have doubts about what the operating mechanism of Laravel schedule scheduling is. The editor consulted all kinds of materials and sorted out simple and easy-to-use operation methods. I hope it will be helpful to answer the doubts about "what is the operating mechanism of Laravel schedule scheduling?" Next, please follow the editor to study!
The console command line of Laravel greatly facilitates the setting and running of PHP scheduled tasks. In the past, the process of configuring scheduled tasks through crontab is relatively tedious, and the scheduled tasks set by crontab are difficult to prevent tasks from overlapping.
The so-called overlapping operation of tasks in means that due to the long running time of scheduled tasks and the unreasonable running cycle set by crontab, the tasks that have been started have not finished running, and the system starts new tasks to perform the same operation. If the problem of data consistency is not properly handled within the program, then two tasks manipulating the same data at the same time is likely to lead to serious consequences. ⒈ runInBackground and withoutOverlapping
provides the withoutOverlapping () method to prevent overlapping tasks, and Laravel provides the runInBackground () method to allow multiple tasks to execute in parallel in the background.
⑴ runInBackground () method
Each command on the console command line represents an Event, and the schedule () method in\ App\ Console\ Kernel simply registers the Event represented by these command lines in the attribute $events of Illuminate\ Console\ Scheduling\ Schedule.
/ / namespace\ Illuminate\ Console\ Scheduling\ Schedulepublic 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 operates in two ways: Foreground and Background. The difference between the two is whether multiple Event can be executed in parallel. Event runs in Foreground mode by default, in which multiple Event are executed sequentially, and the subsequent Event needs to wait until the previous Event run is completed.
but in practical applications, we often want multiple Event to be executed in parallel, so we need to call the runInBackground () method of Event to set its operation mode to Background.
The Laravel framework's handling of these two modes of operation differs in the way the command line is assembled and the callback method is called.
/ / namespace\ Illuminate\ Console\ Scheduling\ Eventprotected 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)-> null () } public function buildCommand () {return (new CommandBuilder)-> buildCommand ($this);} / / namespace Illuminate\ Console\ Scheduling\ CommandBuilderpublic 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 &');}
can see from the code that the Event running in Background mode will add a & symbol at the end of the command line to make the command line program run in the background; in addition, the callback method of Event running in Foreground mode is called synchronously, while the after callback of Event running in Background mode is executed through the schedule:finish command line.
⑵ withoutOverlapping () method
When sets the run cycle of Event, due to the continuous change of application scenarios, it is difficult to avoid that a particular Event needs to run for a long time to complete within a certain period of time, or even not completed at the beginning of the next run cycle. If this situation is not dealt with, it will cause multiple identical Event to run at the same time, and if these Event involve the operation of data and the idempotent problem is not properly handled in the program, it is likely to cause serious consequences.
to avoid the above problems, Event provides the withoutOverlapping () method, which, by setting the withoutOverlapping property of Event to TRUE, checks each time the Event is executed to see if the same Event currently being executed exists, and if so, no new Event task is executed.
/ / namespace Illuminate\ Console\ Scheduling\ Eventpublic 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 mutex
When calls the withoutOverlapping () method, this method also implements two other functions: one is to set the timeout, which defaults to 24 hours, and the other is to set the callback of Event.
⑴ timeout
starts by talking about the timeout, which is not the timeout of Event, but the timeout of the property mutex of Event. When you register Event with the property $events of Illuminate\ Console\ Scheduling\ Schedule, the exec () method in Schedule is called. In this method, a new Event object is created, and an eventMutex is passed to the constructor of Event. This is the property mutex in the Event object, for which the timeout is set. EventMutex in Schedule is created by instantiating CacheEventMutex.
/ / namespace\ Illuminate\ Console\ Scheduling\ Schedule$this- > eventMutex = $container- > bound (EventMutex::class)? $container- > make (EventMutex::class): $container- > make (CacheEventMutex::class)
sets withoutOverlapping's Event to first attempt to acquire the mutex mutex before execution, and Event will not execute if the lock is not successfully acquired. The operation of getting the mutex is done by calling the create () method of mutex.
When instantiating CacheEventMutex, you need to pass in an instance of type\ Illuminate\ Contracts\ Cache\ Factory, and eventually pass in an instance of\ Illuminate\ Cache\ CacheManager. When you call the create () method to acquire the mutex, you also need to set up the storage engine by calling the store () method.
/ / namespace\ Illuminate\ Foundation\ Console\ Kernelprotected function defineConsoleSchedule () {$this- > app- > singleton (Schedule::class, function ($app) {return tap ($this- > scheduleTimezone ()), function ($schedule) {$this- > schedule ($schedule- > useCache ($this- > scheduleCache ());});} protected function scheduleCache () {return Env::get ('SCHEDULE_CACHE_DRIVER') } / / namespace\ Illuminate\ Console\ Scheduling\ Schedulepublic function useCache ($store) {if ($this- > eventMutex instanceof CacheEventMutex) {$this- > eventMutex- > useStore ($store);} / *. * / return $this;} / / namespace\ Illuminate\ Console\ Scheduling\ CacheEventMutexpublic function create (Event $event) {return $this- > cache- > store ($this- > store)-> add ($event- > mutexName (), true, $event- > expiresAt * 60) } / / namespace\ Illuminate\ Cache\ CacheManagerpublic 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- > this- ($this-) 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);}
specifies the storage engine of eventMutex when initializing Schedule, which defaults to the value of the configuration item SCHEDULE_CACHE_DRIVER in the environment variable. But usually this configuration does not exist in the environment variable, so the parameter value of useCache () is empty, and so is the value of the store property of eventMutex. Thus, when you call the store () method in the create () method of eventMutex to set the storage engine for it, the parameter value of the store () method is also empty.
when the parameter passed by the store () method is empty, the default storage engine of the application is used (if no modification is made, the default storage engine of cache is file). The default storage engine configuration information (engine, storage path, connection information, etc.) is then obtained, and then the storage engine is instantiated. Finally, the file storage engine instantiates\ Illuminate\ Cache\ FileStore.
Immediately after sets up the storage engine, it calls the add () method to acquire the mutex. Because the store () method returns an instance of type\ Illuminate\ Contracts\ Cache\ Repository, the add () method in Illuminate\ Cache\ Repository is finally called.
/ / namespace\ Illuminate\ Cache\ Repositorypublic function add ($key, $value, $ttl = null) {if ($ttl! = = null) {if ($this- > getSeconds ($ttl) store, 'add')) {$seconds = $this- > getSeconds ($ttl); return $this- > store- > add ($this- > itemKey ($key), $value, $seconds) } if (is_null ($this- > get ($key)) {return $this- > put ($key, $value, $ttl);} return false;} public function get ($key, $default = null) {if (is_array ($key)) {return $this- > many ($key);} $value = $this- > store- > get ($this- > itemKey ($key)) If (is_null ($value)) {$this- > event (new CacheMissed ($key)); $value = value ($default);} else {$this- > event (new CacheHit ($key, $value));} return $value;} / / namespace\ Illuminate\ Cache\ FileStorepublic function get ($key) {return $this- > getPayload ($key) ['data']? Null;} protected function getPayload ($key) {$path = $this- > path ($key); try {$expire = substr ($contents = $this- > files- > get ($path, true), 0,10);} catch (Exception $e) {return $this- > emptyPayload ();} if ($this- > currentTime () > = $expire) {$this- > forget ($key); return $this- > emptyPayload () } try {$data = unserialize (substr ($contents, 10));} catch (Exception $e) {$this- > forget ($key); return $this- > emptyPayload ();} $time = $expire-$this- > currentTime (); return compact ('data',' time');}
needs to be explained here that the essence of the so-called mutex is to write files. If the file does not exist or the content of the file is empty or the expiration time stored in the file is less than the current time, the mutex can be obtained smoothly; otherwise, the mutex cannot be obtained. The content of the file is in a fixed format: timestampb:1.
's so-called timeout is closely related to the value of timestamp here. The timestamp when the mutex is obtained, plus the number of seconds of timeout, is the value of the timestamp here.
because the add () method does not exist in FileStore, the program will directly attempt to call the get () method to get the contents of the file. If the result returned by get () is NULL, the mutex is acquired successfully, and then the put () method of FileStore is called to write the file; otherwise, the same Event is currently running and the new Event will not be run.
When calls the put () method to write a file, it first needs to calculate the number of seconds of the eventMutex timeout based on the passed parameters, and then calls the put () method in FileStore to write the data to the file.
/ / namespace\ Illuminate\ Cache\ Repositorypublic function put ($key, $value, $ttl = null) {/ *... * / $seconds = $this- > getSeconds ($ttl); if ($seconds forget ($key);} $result = $this- > store- > put ($this- > itemKey ($key), $value, $seconds); if ($result) {$this- > event (new KeyWritten ($key, $value, $seconds);} return $result } / / namespace\ Illuminate\ Cache\ FileStorepublic function put ($key, $value, $seconds) {$this- > ensureCacheDirectoryExists ($path = $this- > path ($key)); $result = $this- > files- > put ($path, $this- > expiration ($seconds). Serialize ($value), true); if ($result! = false & $result > 0) {$this- > ensureFileHasCorrectPermissions ($path); return true;} return false } protected function path ($key) {$parts = array_slice (str_split ($hash = sha1 ($key), 2), 0,2); return $this- > directory.'/'.implode ('/', $parts). $hash;} / / namespace\ Illuminate\ Console\ Scheduling\ Schedulepublic function mutexName () {return 'framework'.DIRECTORY_SEPARATOR.'schedule-'.sha1 ($this- > expression.$this- > command);}
What needs to focus on here is the generation method of $key and the generation method of file path. $key is generated by calling the mutexName () method of Event, which requires the $expression and $command properties of Event. Where $command is the command line we defined, passed in when the $schedule- > comand () method is called, and then formatted, and $expression is the run cycle of Event.
takes the command line schedule:test as an example. After formatting, the command line `/ usr/local/php/bin/ php` `artisan` schedule:test. If the command line is set to run every minute, that is, *, the final calculated value of $key is framework/schedule-768a42da74f005b3ac29ca0a88eb72d0ca2b84be. The file path is to split the value of $key into an array of two characters after sha1 calculation again, and then take the first two items of the array to form a secondary directory, while the default storage path of the file engine in the configuration file is storage/framework/cache/data, so the final file path is storage/framework/cache/data/eb/60/eb608bf555895f742e5bd57e186cbd97f9a6f432. The content stored in the file is 1642122685b:1.
⑵ callback method
As for the Event callback set by , calling the withoutOverlapping () method sets two callbacks for Event: one is the callback after the Event has finished running, which is used to release the mutex, that is, to clean up the cache file; the other is to determine whether the mutex is occupied, that is, whether the cache file already exists before running Event.
whether Event runs as Foreground or Background, the callAfterCallbacks () method is called to execute the callback in afterCallbacks after the run is completed, one of which is used to release the mutex and delete the cache file $this- > mutex- > forget ($this). The difference is that Event running as Foreground explicitly calls these callback methods after the run is complete, while Event running as Background requires schedule:finish to call these callback methods.
All registrations of Event in\ App\ Console\ Kernel are dispatched through the command line schedule:run. Before scheduling, it is first determined whether the current point in time meets the requirements of the running cycle configured by each Event. If so, the next step is to determine some filter conditions, including whether the mutex is occupied. Event can only run if the mutex is not occupied.
/ / namespace\ Illuminate\ Console\ Scheduling\ ScheduleRunCommandpublic 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) {$onOneServer > this- ($this-) } else {$this- > runEvent ($event);} $this- > eventsRan = true;} if (! $this- > eventsRan) {$this- > info ('No scheduled commands are ready to run.');} / / namespace\ Illuminate\ Console\ Scheduling\ Schedulepublic function dueEvents ($app) {return collect ($this- > events)-> filter- > isDue ($app) } / / namespace\ Illuminate\ Console\ Scheduling\ Eventpublic function isDue ($app) {/ *... * / return $this- > expressionPasses () & & $this- > runsInEnvironment ($app- > environment ());} protected function expressionPasses () {$date = Carbon::now (); / *. * / return CronExpression::factory ($this- > expression)-> isDue ($date- > toDateTimeString ()) } / / namespace\ Cron\ CronExpressionpublic function isDue ($currentTime = 'now', $timeZone = null) {/ *... * / try {return $this- > getNextRunDate ($currentTime, 0, true)-> getTimestamp () = = $currentTime- > getTimestamp ();} catch (Exception $e) {return false;} public function getNextRunDate ($currentTime =' now', $nth = 0, $allowCurrentDate = false, $timeZone = null) {return $this- > getRunDate ($currentTime, $nth, false, $allowCurrentDate, $timeZone) } sometimes, we may need to kill some command lines that run in the background, but then we will find that these command lines dropped by kill cannot be automatically scheduled according to the set running cycle for a period of time. The reason is that the command line dropped by manual kill does not call schedule:finish to clean up cache files and release mutexes. This causes the mutex to be occupied until the set expiration time is reached, and the new Event will not run again. At this point, the study on "what is the operating mechanism of Laravel schedule scheduling" is over. I hope to be able to solve your doubts. The collocation of theory and practice can better help you learn, go and try it! If you want to continue to learn more related knowledge, please continue to follow the website, the editor will continue to work hard to bring you more practical articles!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.