Laravel 技巧之 定时任务
114

定时任务 Scheduled Tasks 是 Laravel 提供的组件之一,稍微上点规模的项目应该都会用到,比如开发微信应用时通过定时任务去刷新access token,比如每天定时发推送提醒用户要记得签到。对于定时任务的基本用法,官网文档已经描述得很详细了,这里不再多说。

本文主要是介绍定时任务在实际应用中的两个小技巧:

1. 多个任务并行执行

先简单介绍一下 Laravel 定时任务组件的基本原理:

当cli初始化完毕之后,系统会调用 App\Console\Kernel::schedule 方法,也就是我们定义定时任务列表的地方,这个方法里每调用一次 $schedule->command() 就会生成一个 Illuminate\Console\Scheduling\Event 对象并保存在 $schedule->events 数组里。当执行 php artisan scheduled:run 时,系统会遍历 $schedule->events,把当前时间需要执行的任务放在一个集合中,最后依次 串行执行 这些任务。

这样做在大多数情况下是没有问题的,但有一些特殊的情况,比如在每个月的第一天要给100W个用户发送邮件,同一批次的定时任务必须等到这些邮件全部发送完毕之后才会被执行,假如这些任务里有对执行时间十分敏感的任务,比每5分钟一次的数据快照,就会导致那个时间点数据的缺失。

这种情况下如果定时任务能够并行执行,就不会有这样的问题。Laravel 实际上提供了解决方案,但很奇怪文档里面并没有提到,就是 runInBackground 方法,在定义定时任务时 $schedule->command('foo:bar')->everyMinutes()->runInBackground(); 就可以了。

2. 负载均衡

随着业务逻辑的增多,定时任务也会越来越多,定时任务服务器的负载也会越来越高,甚至导致任务执行缓慢,然而我们却只能在一台服务器上设置定时任务,如果在多台服务器上同时配置了定时任务,还会导致定时任务的重复执行。这个时候我们希望能够像队列那样,将定时任务分散到多台服务器上。

截止 v5.4.15,Laravel 还没有提供内置方案来解决这个问题,但只需要简单的改造就可以实现我们需要的效果。首先我们把将每个定时任务里 handle 方法提取出来创建一个新的Job并继承 ShouldQueue,然后在定时任务的 handle 里直接 dispatch 对应的Job即可,这样原本的业务逻辑就会被队列处理掉,当系统有多台服务器在处理队列时,也就实现了我们需要的负载均衡。

但是这样毕竟还是麻烦,每个定时任务都要创建一个Command和一个Job,太费劲,于是我提交了一个 Proposal ,目前已经实现并且merge入5.4分支,相信下个版本大家就能用上了。用法也很简单,只需要创建一个继承 ShouldQueue的Job,然后在App\Console\Kernel::schedule 方法里定义

$schedule->job(new FooBarJob())->everyMinutes();

就可以了

本帖由 Summer 于 1年前 加精
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 17
Summer

:+1: 有一个非常棒的功能

1年前
Summer

1年前
RryLee

不错,非常实用

1年前

棒!

1年前

赞?

1年前
overtrue

厉害了我的哥

1年前
Destiny

很厉害。。。原来是你。。。

1年前

刚看了下Laravel 5.1.45并没有 runInBackground 方法,刚准备上线一个很重的定时任务。。。

1年前
leo

@binafor 看了下提交日志,应该是5.2才引入的

1年前

我刚在5.1 下测试了下,貌似5.1 本来就是并行运行的,知道我这样的测试有没有问题?

//command 1
public function handle()
    {
        Log::info(1);
        sleep(40);
        $this->comment(PHP_EOL.Inspiring::quote().PHP_EOL);
        Log::info(2);
    }

//command 2
public function handle()
    {
        Log::info('test');
        sleep(30);
        Log::info('end');
    }

//app/Console/Kernel.php
protected function schedule(Schedule $schedule)
    {
        $schedule->command('test')->everyMinute();
        $schedule->command('inspire')->everyMinute();
    }

file
file

从日志来看,启动是同时的,从进程来看,刚到整00s时,我检测到 同时启动了两个进程

1年前

@leo 知道我这样的测试有没有问题? -> 不知道我这样的测试有没有问题?
新输入法打个字漏洞百出。。见谅

1年前
LDL1023

laravel 中怎样实现秒级的定时任务呢?
比如我有个任务要 5 秒跑一次。

10个月前
leo

@LDL1023 Laravel 原生是不支持秒级的,这个需要自己找方法实现了

10个月前
LDL1023

@leo 好的

10个月前
wanghan

@leo 楼主今天新建了一个5.4.30的项目用来测试定时任务,可是我发现$schedule->call('foo')->before('bar'),其中before不会被执行,百度上也找不到答案,楼主遇见过吗?

2个月前

  • 请注意单词拼写,以及中英文排版,参考此页
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`, 更多语法请见这里 Markdown 语法
  • 支持表情,使用方法请见 Emoji 自动补全来咯,可用的 Emoji 请见 :metal: :point_right: Emoji 列表 :star: :sparkles:
  • 上传图片, 支持拖拽和剪切板黏贴上传, 格式限制 - jpg, png, gif
  • 发布框支持本地存储功能,会在内容变更时保存,「提交」按钮点击时清空
  请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!