以前我对于定时任务功能都是使用 Linux 自带的 crontab
来完成,最近在 Laravel 的项目中又遇到定时任务相关的功能了,所以研究了一下 Laravel 自带的任务调度,它也是基于 crontab
完成的。
它相比直接用 crontab
运行脚本,减少了代码对外的耦合程度,更方便让我们在框架中完成定时任务的功能,所以本文记录一下我是如何在 Laravel 5.6 中使用定时任务的。
调度方式
需要调度的任务放在 app/Console/Kernel.php 中的 schedule
方法来完成, Laravel 提供了 4 种方式来完成调度任务:
- Closure Call(闭包)
- Command(artisan 命令)
- Job(队列)
- Shell(脚本)
定义调度
在任务不多的情况可以使用 Closure Call
的方式来直接完成,直接将逻辑写在回调函数中,传递给 call
方法,用法如下:
<?php
namespace App\Console;
use DB;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
/**
* 应用提供的 Artisan 命令
*
* @var array
*/
protected $commands = [
//
];
/**
*定义应用的命令调度
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
protected function schedule(Schedule $schedule)
{
$schedule->call(function () {
DB::table('recent_users')->delete();
})->daily();
}
}
Artisan 命令调度
在任务较多时,建议使用 Command 的方式,例如我的项目种有定时发布视频、删除用户浏览记录等很多定时任务,所以我就使用了 Command 的方式来完成。
定义 Artisan 命令
在使用这种方式时,首先需要定义 Artisan 命令
打开编辑器的控制台,或者 Windows 打开 CMD 命令行,进入项目的根目录并运行命令:
php artisan make:command publishVideo
如上我创建了一个名为 publishVideo
的命令,这个命令的同名 PHP 文件被放在了 app/Console/Commands 目录下,文件的内容如下:
<?php
namespace App\Console\Commands;
use App\Services\VideoService;
use Illuminate\Console\Command;
class PublishVideo extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'video:publish';
/**
* The console command description.
*
* @var string
*/
protected $description = 'timing publish video';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
public function handle()
{
VideoService::timingPublish();
}
}
可以看到,这个 PublishVideo
类中有 $signature
和 $description
两个属性,以及 handle
方法。
顾名思义,$signature
属性就是命令的唯一签名、标识,我为定义为 video:publish
,可以使用 php artisan list
查看当前 Laraval 项目的所有的命令标识
$description
即命令的描述,这个可以随便定义。
最后是 handle
方法,我们把需要运行的逻辑代码放在此方法即可,如果逻辑较多,可以将逻辑放在 Service
类中,减少可以看到我在这个方法中调用了 VideoService
服务的 timingPublish
方法。
调度 Artisan 命令
Artisan 命令创建完成后,接着就可以和 Closure Call 的方式一样调度 Artisan 命令了,在 app/Console/Kernel.php 中的 schedule
方法中:
protected function schedule(Schedule $schedule)
{
$schedule->command('video:publish')->withoutOverlapping();
$schedule->command('video:updateSrc')->withoutOverlapping();
$schedule->command('video:storeViews')->daily();
$schedule->command('user:destroyBrowses')->dailyAt('04:00');
}
如上,我分别调度了 4 个 Artisan 命令,执行频率分别是:每分钟、每分钟、每天凌晨 00:00
、每天凌晨 04:00
。
调度频率设置
除了我例子中的设置,Laravel 还提供了如下频率设置:
方法 | 描述 |
---|---|
->cron('* * * * *'); |
在自定义的 Cron 时间表上执行该任务 |
->everyMinute(); |
每分钟执行一次任务 |
->everyFiveMinutes(); |
每五分钟执行一次任务 |
->everyTenMinutes(); |
每十分钟执行一次任务 |
->everyFifteenMinutes(); |
每十五分钟执行一次任务 |
->everyThirtyMinutes(); |
每半小时执行一次任务 |
->hourly(); |
每小时执行一次任务 |
->hourlyAt(17); |
每小时的第 17 分钟执行一次任务 |
->daily(); |
每天午夜执行一次任务 |
->dailyAt('13:00'); |
每天的 13:00 执行一次任务 |
->twiceDaily(1, 13); |
每天的 1:00 和 13:00 分别执行一次任务 |
->weekly(); |
每周执行一次任务 |
->monthly(); |
每月执行一次任务 |
->monthlyOn(4, '15:00'); |
在每个月的第四天的 15:00 执行一次任务 |
->quarterly(); |
每季度执行一次任务 |
->yearly(); |
每年执行一次任务 |
->timezone('America/New_York'); |
设置时区 |
更多频率设置,请参考官方文档。
启动调度
进入 shell
中,运行 crontab -e
命令,按 i
键进入编辑模式,添加如下配置:
* * * * * /usr/local/php/bin/php /home/api/admin/artisan schedule:run >> /dev/null 2>&1
其中 /usr/local/php/bin/php 是 php 命令所在目录,/home/api/admin 是 Laravel 项目的根目录
添加后按 ESC
键,退出编辑模式,输入 wq
并回车,保存 crontab
任务,Laravel 的任务调度就启动了。
数据库定时备份功能
数据库定时备份数据,对每个项目来说都是非常有必要的,能够有效地避免删库跑路这种操作。
我还是使用调度 command 的方式来完成这个功能,首先在项目根目录运行命令:
php artisan make:command BackupDatabase
该命令创建了一个同名 PHP 文件,被放在了 app/Console/Commands 目录下
BackupDatabase.php 的内容如下:
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;
class BackupDatabase extends Command
{
protected $signature = 'db:backup';
protected $description = 'Backup the database';
protected $process;
public function __construct()
{
parent::__construct();
$this->process = new Process(sprintf(
'mysqldump -u%s -p%s %s > %s',
config('database.connections.mysql.username'),
config('database.connections.mysql.password'),
config('database.connections.mysql.database'),
storage_path('backups/backup.sql')
));
}
public function handle()
{
try {
$this->process->mustRun();
$this->info('The backup has been proceed successfully.');
} catch (ProcessFailedException $exception) {
$this->error('The backup process has been failed.');
}
}
}
上面的构造函数中,我使用了 Symfony 框架的 Process
组件,这是一个很强大的组件,功能类似于 shell_exec
函数,可以执行 shell 命令。
我使用 Process
组件执行了 mysqldump
命令,mysqldump
命令的使用格式如下:
mysqldump [options] [db_name [tbl_name ...]]
BackupDatabase.php 运行后会将数据备份在 backups/backup.sql
中,并且当任务执行出错时会记录错误。
最后一步,和之前一样在 app/Console/Kernel.php 中的 schedule
方法中完成对 db:backup
命令的调度:
protected function schedule(Schedule $schedule)
{
$schedule->command('db:backup')->dailyAt('03:30');
}
因为每天凌晨 03:30
用户活跃度最低,所以选择在该时间备份一次数据。
