目录
1. 基本实现逻辑
2. 后台运行
3. 防止重复
4. 如何实现30秒任务?
首页 php框架 Laravel 深入理解Laravel定时任务调度机制

深入理解Laravel定时任务调度机制

Feb 23, 2022 pm 05:20 PM
laravel

本篇文章给大家带来了关于laravel定时任务调度机制的相关知识,其中主要介绍了基本实现逻辑、后台运行以及防止重复的相关问题,希望对大家有帮助。

深入理解Laravel定时任务调度机制

【相关推荐:laravel视频教程

1. 基本实现逻辑

一个复杂的web系统后台当中,一定会有很多定时脚本或者任务要跑。

例如爬虫系统需要定期去爬取一些网站数据,自动还贷系统需要每个月定时对用户账户扣款结算,

会员系统需要定期检测用户剩余会员天数以便及时通知续费等等。Linux系统中内置的crontab一般被广泛地用于跑定时任务。其任务指令格式如下:

crontab指令解释

命令行crontab -e进入crontab编辑,把自己要执行的指令编辑好之后保存退出即可生效。

不过本文并不会过多讨论crontab的内容,而是要深入分析一下PHP Laravel框架是如何基于crontab封装出功能更加强大的任务调度(Task Scheduling)模块。

对于定时任务,我们当然可以每个任务配置一个crontab指令。只不过这样做的话随着定时任务的增加,crontab指令也线性增长。

毕竟crontab是一项系统级的配置,在业务中我们为了节约机器,往往对于量不大的多个项目会放在同一台服务器上,c

rontab指令多了就容易管理混乱,并且功能也不够灵活强大(无法随心所欲的停启、处理任务间依赖关系等)。

对此Laravel的解决方案是只声明一条crontab,业务中的所有定时任务全都在这一条crontab中做处理和判断,实现在代码层面管理任务:

* * * * * php artisan schedule:run >> /dev/null 2>&1
登录后复制

即php artisan schedule:run每分钟跑一次(crontab的最高频率),至于业务上的具体任务配置,则注册于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任务
    }
}
登录后复制

上述例子中我们可以很清晰的看到系统中注册了三项定时任务,并且提供了everyMinute, everyFifteenMinutes, daily, hourly等语义化的方法来配置任务周期。

本质上,这些语义化的方法只是crontab表示方式的一个别称罢了,最终都会转化为crontab中的表达方式(如 * * * * * 表示每分钟执行一次)。

如此一来,每分钟执行一次的php artisan schedule:run指令,会扫描Kernel::schedule中注册的所有指令并判断该指令配置的执行周期时候已经到期,

如果到期则推入待执行队列。最后依次执行所有的指令。

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

schedule task流程图

这里需要注意两个点,第一、如何判断指令是否已经Due了该执行了。第二、指令的执行顺序问题。

首先,crontab表达式所指定的执行时间,是指绝对时间,而不是相对时间。所以仅仅根据当前时间和crontab表达式,

即可判断出指令是否已经Due了该执行了。如果想要实现相对时间,那么必须存储上一次执行的时间,

然后才能进行推算下次执行应该是什么时候。绝对时间和相对时间的区别可以用下面一幅图概括(crontab的执行时间如图中左侧列表所示)。

Laravel中对于crontab表达式的静态分析和判断使用的是cron-expression库(github.com/mtdowling/cron-expression),原理也比较直观,就是静态的字符分析比对。

crontab是绝对时间,而非相对时间

第二个问题是执行顺序,前面的图中我们可以看出,如果你在Kernel::schedule方法中注册了多个任务,

正常情况下它们是顺序依次执行的。也就是说必须要等到Task 1执行完成之后,Task 2才会开始执行。

在这种情况下,如果Task 1非常耗时,则会影响到Task 2的按时执行,这一点在开发中是尤其需要注意的。

不过在Kernel::schedule中注册任务时加上runInBackground即可实现任务的后台执行,这点我们下文详细讨论。

2. 后台运行

前文提到的定时任务队列顺序执行的特性,前面的任务执行时间太长会妨碍后面任务的按时执行。

为解决此问题,Laravel中提供了使任务后台执行的方法runInBackground。如:

// 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视频教程

以上是深入理解Laravel定时任务调度机制的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
2 周前 By 尊渡假赌尊渡假赌尊渡假赌
仓库:如何复兴队友
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
Hello Kitty Island冒险:如何获得巨型种子
4 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

PHP 与 Flutter 的比较:移动端开发的最佳选择 PHP 与 Flutter 的比较:移动端开发的最佳选择 May 06, 2024 pm 10:45 PM

PHP和Flutter是移动端开发的流行技术。Flutter胜在跨平台能力、性能和用户界面,适合需要高性能、跨平台和自定义UI的应用程序。PHP则适用于性能较低、不跨平台的服务器端应用程序。

PHP中如何使用对象-关系映射(ORM)简化数据库操作? PHP中如何使用对象-关系映射(ORM)简化数据库操作? May 07, 2024 am 08:39 AM

使用ORM可简化PHP中的数据库操作,它将对象映射到关系数据库中。Laravel中的EloquentORM允许使用面向对象的语法与数据库交互,可通过定义模型类、使用Eloquent方法或在实战中构建博客系统等方式来使用ORM。

Laravel - Artisan 命令 Laravel - Artisan 命令 Aug 27, 2024 am 10:51 AM

Laravel - Artisan 命令 - Laravel 5.7 提供了处理和测试新命令的新方法。它包括测试 artisan 命令的新功能,下面提到了演示?

PHP 单元测试工具的优缺点分析 PHP 单元测试工具的优缺点分析 May 06, 2024 pm 10:51 PM

PHP单元测试工具分析:PHPUnit:适用于大型项目,提供全面功能,易于安装,但可能冗长且速度较慢。PHPUnitWrapper:适合小型项目,易于使用,针对Lumen/Laravel优化,但功能受限,不提供代码覆盖率分析,社区支持有限。

Laravel和CodeIgniter的最新版本对比 Laravel和CodeIgniter的最新版本对比 Jun 05, 2024 pm 05:29 PM

Laravel9和CodeIgniter4的最新版本提供了更新的特性和改进。Laravel9采用MVC架构,提供数据库迁移、身份验证和模板引擎等功能。CodeIgniter4采用HMVC架构,提供路由、ORM和缓存。在性能方面,Laravel9的基于服务提供者设计模式和CodeIgniter4的轻量级框架使其具有出色的性能。在实际应用中,Laravel9适用于需要灵活性和强大功能的复杂项目,而CodeIgniter4适用于快速开发和小型应用程序。

Laravel 和 CodeIgniter 中数据处理能力的比较如何? Laravel 和 CodeIgniter 中数据处理能力的比较如何? Jun 01, 2024 pm 01:34 PM

比较Laravel和CodeIgniter的数据处理能力:ORM:Laravel使用EloquentORM,提供类对象关系映射,而CodeIgniter使用ActiveRecord,将数据库模型表示为PHP类的子类。查询构建器:Laravel具有灵活的链式查询API,而CodeIgniter的查询构建器更简单,基于数组。数据验证:Laravel提供了一个Validator类,支持自定义验证规则,而CodeIgniter的验证功能内置较少,需要手动编码自定义规则。实战案例:用户注册示例展示了Lar

PHP 代码单元测试与集成测试 PHP 代码单元测试与集成测试 May 07, 2024 am 08:00 AM

PHP单元和集成测试指南单元测试:关注单个代码单元或函数,使用PHPUnit创建测试用例类进行验证。集成测试:关注多个代码单元协同工作的情况,使用PHPUnit的setUp()和tearDown()方法设置和清理测试环境。实战案例:使用PHPUnit在Laravel应用中进行单元和集成测试,包括创建数据库、启动服务器以及编写测试代码。

Laravel和CodeIgniter:哪种框架更适合大型项目? Laravel和CodeIgniter:哪种框架更适合大型项目? Jun 04, 2024 am 09:09 AM

在选择大型项目框架时,Laravel和CodeIgniter各有优势。Laravel针对企业级应用程序而设计,提供模块化设计、依赖项注入和强大的功能集。CodeIgniter是一款轻量级框架,更适合小型到中型项目,强调速度和易用性。对于具有复杂需求和大量用户的大型项目,Laravel的强大功能和可扩展性更合适。而对于简单项目或资源有限的情况下,CodeIgniter的轻量级和快速开发能力则更为理想。

See all articles