项目场景
项目场景: laravel 数据库备份时任务调度重复执行问题描述
在任何的一个项目中数据库里面的数据都是是要定时的进行备份来以防万一服务器出现问题时数据丢失。 在laravel 项目中我使用任务调度来实现数据库定时备份这个时任务命令
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;
class BackupDatabase extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'db:backup';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Backup the database';
/**
* @var Process
*/
private $process;
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return void
*/
public function handle()
{
info("定时任务************数据库备份开始");
//将备份文件放至 “storage/backup”
$dirPath = storage_path('backup/');
if (!file_exists($dirPath)) {
mkdir($dirPath);
}
//备份文件名以当前日期命名
$filePath = $dirPath.date('Y-m-d').'全库备份'.'.sql' ;
// $cmd[]=sprintf('mysqldump -h%s -p%s -u%s -p%s -b %s > %s',
// config('database.connections.mysql.host'),
// config('database.connections.mysql.port'),
// config('database.connections.mysql.username'),
// config('database.connections.mysql.password'),
// config('database.connections.mysql.database'),
// $filePath);
$cmd='mysqldump -h'. config('database.connections.mysql.host')
.' -p'.config('database.connections.mysql.port')
.' -u'.config('database.connections.mysql.username')
.' -p'.config('database.connections.mysql.password')
.' '.config('database.connections.mysql.database')
.' > '.$filePath;
// $this->process = new Process($cmd);
exec($cmd,$out,$status);
if ($status==0){
info("数据库备份完成!");
return;
}
info("数据库备份失败!");
}
}
任务调度类
<?php
namespace App\Console;
use App\Utils\SmsUtils;
use GuzzleHttp\Client;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
use Illuminate\Support\Facades\Cache;
class Kernel extends ConsoleKernel
{
/**
* The Artisan commands provided by your application.
*
* @var array
*/
protected $commands = [
//
// 备份数据库
\App\Console\Commands\BackupDatabase::class
];
/**
* Define the application's command schedule.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
protected function schedule(Schedule $schedule)
{
//token监控
$schedule->call(function () {
info("微信token获取状态监控执行");
.....
.....
})->hourly();
//数据库每周备份
$schedule->command('db:backup')->weekly();
}
/**
* Register the commands for the application.
*
* @return void
*/
protected function commands()
{
$this->load(__DIR__ . '/Commands');
require base_path('routes/console.php');
}
}
这个造成了本来在周末00.00执行一次的,通过日志查看发现每周末都执行数次
原因分析
有时候一个定时任务执行需要的时间可能会比我们想象的要长,这就会引起一个问题——当前任务还没有执行完毕的时候另一个相同的任务也会执行,从而导致任务重复。例如想象一下我们执行每分钟生成一次报告的任务,在经过一段时间后,数据量变得很大导致执行时间多于1分钟,这样就会导致在上一个任务还没结束的时候另一个相同的任务开始执行。 简单说: 当我们在开会进行激烈的讨论时,我会从我桌子里拿出来一个尖叫鸡。只有手里拿着尖叫鸡的人才能说话,如果你没有拿着尖叫鸡你是不能说话的。你只能向会议主持人请示,只有在你拿到尖叫鸡的时候你才能说话否则只能等待。当你讲话完毕的时候,将尖叫鸡还给会议主持人,主持人会将尖叫鸡给到下一个人来让其说话。这样会确保人们不会互相交谈,同时他们也会有自己的时间来进行讲话。解决方案
大部分情况下是没有什么问题的,但是有时我们需要避免这种情况来保证获得正确的数据。在Laravel中我们可以通过withoutOverlapping()
方法来进行处理
//数据库每周备份 加互斥锁防止重复执行
$schedule->command('db:backup')->weekly()->withoutOverlapping();