Cet article vous apporte des connaissances pertinentes sur le mécanisme de planification des tâches planifiées de laravel Il présente principalement la logique de mise en œuvre de base, le fonctionnement en arrière-plan et les problèmes associés à la prévention des duplications.
【Recommandation connexe : Tutoriel vidéo Laravel】
Dans un arrière-plan de système Web complexe, il doit y avoir de nombreux scripts ou tâches planifiés à exécuter.
Par exemple, le système d'exploration doit explorer régulièrement certaines données du site Web, le système de remboursement automatique du prêt doit débiter et régler le compte utilisateur régulièrement chaque mois,
Le système d'adhésion doit détecter régulièrement le nombre de jours d'adhésion restants du l'utilisateur afin de notifier à temps l'utilisateur du renouvellement, etc. La crontab intégrée aux systèmes Linux est généralement largement utilisée pour exécuter des tâches planifiées. Le format de la commande de tâche est le suivant :
Explication de la commande crontab
Ligne de commande crontab -e pour entrer dans l'édition crontab, modifiez la commande que vous souhaitez exécuter, enregistrez et quittez, et elle prendra effet.
Cependant, cet article ne discutera pas trop du contenu de crontab, mais fournira une analyse approfondie de la façon dont le framework PHP Laravel encapsule un module de planification de tâches (Task Scheduling) plus puissant basé sur crontab.
Pour les tâches planifiées, nous pouvons bien entendu configurer une commande crontab pour chaque tâche. Cependant, si vous faites cela, à mesure que le nombre de tâches planifiées augmente, les instructions crontab augmenteront également de manière linéaire.
Après tout, crontab est une configuration au niveau du système. Afin de sauvegarder les machines en entreprise, nous mettons souvent plusieurs projets de petit volume sur le même serveur. S'il y a trop d'instructions c
ontab, il sera facile de le faire. gérer la confusion, et Les fonctions ne sont pas non plus assez flexibles et puissantes (impossible d'arrêter et de démarrer à volonté, gérer les dépendances entre les tâches, etc.).
La solution de Laravel à ce problème est de déclarer une seule crontab. Toutes les tâches planifiées dans l'entreprise sont traitées et jugées dans cette crontab pour gérer les tâches au niveau du code :
* * * * * php artisan schedule:run >> /dev/null 2>&1
C'est-à-dire que php artisan planning:run s'exécute toutes les minutes une fois. (la fréquence de crontab la plus élevée), quant à la configuration des tâches spécifiques à l'entreprise, elle est enregistrée dans 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任务 } }
Dans l'exemple ci-dessus, on voit clairement que trois tâches planifiées sont enregistrées dans le système, et provide Il fournit des méthodes sémantiques EveryMinute, EveryFifteenMinutes, Daily, Hour et autres pour configurer les cycles de tâches.
Essentiellement, ces méthodes sémantiques ne sont qu'un autre nom pour la représentation crontab, et elles seront éventuellement converties en expression dans crontab (telle que * * * * * signifie exécution une fois par minute).
De cette façon, la commande php artisan planning:run exécutée une fois par minute analysera toutes les instructions enregistrées dans Kernel::schedule et déterminera que le cycle d'exécution configuré pour l'instruction a expiré
S'il expire, il sera poussé. File d'attente à exécuter. Enfin, exécutez toutes les instructions dans l’ordre.
// ScheduleRunCommand::handle函数 public function handle() { foreach ($this->schedule->dueEvents() as $event) { if (! $event->filtersPass()) { continue; } $event->run(); } }
organigramme des tâches de planification
Il y a deux points à noter ici. Premièrement, comment juger si l'instruction est due et doit être exécutée. Deuxièmement, la question de l’ordre d’exécution des instructions.
Tout d'abord, le temps d'exécution spécifié par l'expression crontab fait référence au temps absolu et non au temps relatif. Ainsi, simplement en fonction de l'heure actuelle et de l'expression crontab,
peut déterminer si l'instruction est due et doit être exécutée. Si vous souhaitez implémenter l'heure relative, vous devez stocker l'heure de la dernière exécution, puis vous pouvez calculer quand la prochaine exécution doit avoir lieu. La différence entre le temps absolu et le temps relatif peut être résumée par l'image suivante (le temps d'exécution de crontab est affiché dans la liste à gauche de l'image).
Laravel utilise la bibliothèque d'expressions cron (github.com/mtdowling/cron-expression) pour l'analyse statique et le jugement des expressions crontab. Le principe est également relativement intuitif, qui est l'analyse et la comparaison de caractères statiques.
crontab est une heure absolue, pas une heure relative
Le deuxième problème est l'ordre d'exécution. D'après l'image précédente, nous pouvons voir que si vous enregistrez plusieurs tâches dans la méthode Kernel::schedule,
Normalement. ils sont exécutés séquentiellement. C'est-à-dire que la tâche 2 ne commencera pas son exécution tant que la tâche 1 ne sera pas terminée.
Dans ce cas, si la tâche 1 prend beaucoup de temps, cela affectera l'exécution dans les délais de la tâche 2, qui nécessite une attention particulière lors du développement.
Cependant, l'ajout de runInBackground lors de l'enregistrement d'une tâche dans Kernel::schedule peut réaliser l'exécution en arrière-plan de la tâche. Nous en discuterons en détail ci-dessous.
2. Opération en arrière-plan
Pour résoudre ce problème, Laravel fournit la méthode runInBackground pour exécuter la tâche en arrière-plan. Tel que :
// 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,来确定需要执行哪个任务的后置函数。代码如下:
// 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); }
有些定时任务指令需要执行很长时间,而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); }
我们知道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视频教程】
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!