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

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

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

【相关推荐:laravel视频教程】

1. 基本实现逻辑

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

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

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

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

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

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

schedule task流程图

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

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

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

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

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

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

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

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

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

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

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

2. 后台运行

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

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

// Kernel.phpprotected 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方法来让定时任务避免重复。具体锁的实现上,需要实现IlluminateConsoleSchedulingMutex.php接口中所定义的三个接口:

interface Mutex{    // 实现创建锁接口    public function create(Event $event);    // 实现判断锁是否存在的接口    public function exists(Event $event);    // 实现解除锁的接口    public function forget(Event $event);}

该接口当然可以自己实现,Laravel也给了一套默认实现,即利用缓存作为存储锁的载体(可参考IlluminateConsoleSchedulingCacheMutex.php文件)。

在每次跑任务之间,程序都会做出判断,是否需要防止重复,如果重复了,则不再跑任务代码:

// IlluminateConsoleSchedulingEvent.phppublic 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 IlluminateContractsQueueShouldQueue{    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定时任务调度机制的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/71917.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月13日 13:16:15
下一篇 2025年11月13日 14:03:03

相关推荐

  • 在Laravel框架中如何解决“Too many open files”错误?

    在laravel框架中解决“too many open files”错误的方法 在使用php7.3和laravel框架执行定时任务时,你可能会遇到一个错误提示,指出“打开文件太多”,错误信息大致如下: [2023-03-15 00:14:13] local.ERROR: include(/www/v…

    好文分享 2025年12月11日
    000
  • Laravel中如何优雅地实现ThinkPHP风格的外部查询条件组装?

    Laravel灵活构建数据库查询条件 高效灵活地构建数据库查询条件对于任何框架都至关重要。ThinkPHP允许开发者以简洁的数组方式外部组装查询条件,而Laravel则采用链式调用的面向对象方法。本文将探讨如何在Laravel中优雅地模拟ThinkPHP的外部条件组装方式,并对比两种框架的差异。 T…

    2025年12月11日
    000
  • PHP记录:PHP日志分析的最佳实践

    php日志记录对于监视和调试web应用程序以及捕获关键事件,错误和运行时行为至关重要。它为系统性能提供了宝贵的见解,有助于识别问题,并支持更快的故障排除和决策 – 但仅当它有效地实施时。 在此博客中,我概述了PHP记录以及它在Web应用程序中的使用方式。然后,我概述了一些关键的最佳实践,…

    2025年12月11日
    000
  • 告别调试地狱:使用 Spatie/Laravel-Ray 提升 Laravel 应用调试效率

    我最近在开发一个 Laravel 应用,其中涉及到复杂的订单处理流程和用户交互。在调试过程中,我遇到了许多问题:数据库查询缓慢、邮件发送失败、业务逻辑错误等等。传统的调试方法,例如 dd() 和 var_dump(),虽然能提供一些信息,但效率低下,且难以追踪复杂的流程。 日志文件虽然记录了详细的信…

    2025年12月11日
    000
  • Laravel多租户:如何灵活配置不同租户的数据库主机连接?

    Laravel 多租户:实现灵活的数据库主机连接配置 在使用 Laravel 多租户扩展包 stancl/tenancy 时,为每个租户配置独立的数据库主机至关重要。本文将讲解如何灵活配置数据库连接,涵盖租户创建和修改连接的两种场景。 一、创建租户时配置数据库主机 stancl/tenancy 主要…

    2025年12月11日
    000
  • ThinkPHP如何优雅打印错误日志:包含函数参数、行号及原因?

    thinkphp优雅错误日志打印:轻松定位问题 清晰的错误日志对于高效开发至关重要。虽然Laravel框架的日志打印功能简洁明了,但ThinkPHP默认的日志输出却显得冗长且难以提取关键信息。本文将介绍如何在ThinkPHP中实现更优雅的错误日志打印,包含函数参数、行号和错误原因等细节,帮助您快速定…

    2025年12月11日
    000
  • 净化HTML,守护网站安全:Mews/Purifier 的应用实践

    几个月前,我的网站上线了一个用户评论功能。起初一切顺利,直到有一天,我发现网站上出现了恶意脚本,这些脚本能够窃取用户的Cookie和其他敏感信息。经过排查,我发现这些恶意代码都隐藏在用户提交的评论内容中,它们巧妙地伪装成正常的HTML代码,绕过了我之前简单的HTML过滤机制。 这让我意识到,仅仅依靠…

    2025年12月11日
    000
  • 优雅的数据转换:Spatie Laravel Fractal 的实践指南

    最近我负责一个电商项目的 API 开发,需要返回商品列表给前端。数据库中商品数据包含商品ID、名称、价格、描述以及多个图片信息。直接返回数据库原始数据会导致数据冗余,而且前端需要进行额外的处理才能正确显示。为了解决这个问题,我尝试了手动转换数据,但代码很快变得难以维护。这时,我发现了 Spatie …

    2025年12月11日
    000
  • Laravel多租户:如何自定义Stancl/Tenancy租户数据库连接主机?

    灵活配置Laravel多租户数据库连接主机 使用Laravel多租户扩展包stancl/tenancy时,如何为每个租户指定独立的数据库主机是一个常见需求。本文提供解决方案,帮助您实现灵活的数据库连接主机配置。 用户经常需要在创建租户时或之后,修改其数据库连接的主机地址。stancl/tenancy…

    2025年12月11日
    000
  • PHP接收POST数据时$_GET、$_POST、$_REQUEST为空,如何解决?

    PHP接收POST数据时$_GET、$_POST、$_REQUEST为空的解决方案 在PHP开发中,使用POST方法提交数据时,有时会遇到$_GET、$_POST、$_REQUEST均为空的情况,但file_get_contents(‘php://input’)却能获取到数据。这通常是由于PHP运行…

    2025年12月11日
    000
  • Laravel跨域配置生效却报错,问题出在哪?

    Laravel跨域配置疑难解答:看似生效却报错 前后端分离架构中,跨域问题屡见不鲜。本文剖析一个案例:Laravel后端已配置跨域,但前端仍提示跨域错误。 问题:开发者使用Laravel构建后端,并添加了跨域响应头: $response->header(‘Access-Control-Allo…

    2025年12月11日
    000
  • PHP接口中object类型不兼容问题:如何解决“must be compatible with”错误?

    PHP接口类型兼容性:巧妙解决object类型冲突 在PHP开发中,利用接口定义方法签名能有效提升代码的可维护性和扩展性。然而,当接口方法参数类型声明为object时,可能会遭遇类型不兼容问题,导致“must be compatible with”错误。本文将深入探讨此问题,并提供有效的解决方案。 …

    2025年12月11日
    000
  • 高效管理层级数据:Laravel Nested Set 模型的实践指南

    在开发电商网站后台时,需要管理产品分类,这是一个典型的树状结构数据。最初,我尝试使用传统的父子关系模型,每个分类记录都存储其父分类的 ID。然而,随着分类数量的增加,查询子分类、祖先分类以及其他层级相关操作变得越来越慢,特别是当需要递归查询时,性能问题尤为突出。例如,获取某个分类下的所有子分类,需要…

    2025年12月11日
    000
  • 告别版本噩梦:使用 kylekatarnls/update-helper 简化库更新

    作为一名PHP开发者,我深知维护开源库的挑战。其中一个令人头疼的问题就是如何引导用户更新到最新版本。过去,我通常在README文件中添加更新说明,或者在代码中加入简单的版本检查,但这些方法效果并不理想。用户常常错过更新信息,导致他们使用过时的功能,甚至遇到兼容性问题。 这不仅影响了用户的体验,也增加…

    2025年12月11日
    000
  • 高效定位用户:Torann/GeoIP库的实践指南

    我们的项目需要根据用户的IP地址,快速准确地确定其地理位置,例如国家、地区和城市等信息,以便我们根据用户的地理位置提供个性化服务,例如推荐当地热门商品或显示当地语言版本。 最初,我尝试使用一些免费的在线API来获取地理位置信息。然而,这些API存在一些问题: 准确性不足: 一些API的数据库不够完善…

    2025年12月11日
    000
  • Dcat Admin多行表单布局下Radio联动失效如何解决?

    Dcat Admin框架下多行表单布局中Radio联动失效的解决方法 Dcat Admin是一个优秀的Laravel后台管理系统,其表单组件功能丰富。然而,在实际应用中,用户可能会遇到一些问题,例如在多行布局下,Radio单选框的联动功能失效。本文将分析此问题并提供解决方案。 问题描述: 在Dcat…

    2025年12月11日
    000
  • 告别繁琐的前端搭建:Laravel UI 助力快速开发

    最近我接手了一个新的 Laravel 项目,需要快速搭建一个包含用户注册和登录功能的应用。传统的做法需要手动集成前端框架,配置构建工具,处理各种依赖,这无疑是一个耗时且容易出错的过程。为了提高效率,我决定尝试使用 Laravel UI。 Laravel UI 是一个 Composer 包,它提供了预…

    2025年12月11日
    000
  • Vue打包后的前端项目如何与Laravel API服务集成部署?

    Vue.js 前端项目与 Laravel API 服务集成部署指南 许多开发者采用 Vue.js 构建前端后台管理系统,并使用 Laravel 提供 API 服务。本文将详细介绍如何将 Vue.js 打包后的项目 (dist 文件夹) 与 Laravel 项目集成部署,实现前后端一体化运行。 核心问…

    2025年12月11日
    000
  • ThinkPHP如何优雅打印错误日志:包含参数、行号和原因?

    ThinkPHP高效错误日志记录:精准定位问题 高效的错误日志对于快速调试至关重要。Laravel框架以其简洁明了的错误日志输出而闻名,清晰地展现错误位置、原因和调用栈。然而,ThinkPHP默认的错误日志输出较为冗余,难以快速提取关键信息。本文将介绍如何在ThinkPHP中实现类似Laravel的…

    2025年12月11日
    000
  • Laravel 5.4 SQL语句中的问号参数是怎么回事?

    Laravel 5.4 SQL语句中问号参数的解释 在使用Laravel 5.4框架并结合阿里云SQL洞察工具进行开发时,你可能会注意到SQL语句中出现问号(?)参数的现象。本文将解释这种现象背后的原因。 这种问号参数并非Laravel框架的错误,而是数据库预处理语句(prepared statem…

    2025年12月11日
    000

发表回复

登录后才能评论
关注微信