网站每天生成的临时文件、过期记录和统计数据,通常需要定期处理。如果为每项操作单独维护一条服务器 Cron,任务多了以后很难统一管理。Laravel 的任务调度器可以把执行时间写进项目代码,服务器只需保留一个调度入口。
下面以“每天清理过期缓存锁记录”为例,介绍定时命令、调度配置和运行检查。示例采用 Laravel 11、12 常见的项目结构,调度规则写在 routes/console.php;旧版项目通常在 app/Console/Kernel.php 的 schedule() 方法中配置。
一、任务调度与队列有什么区别
任务调度决定某项工作什么时候执行,例如每天凌晨生成统计报告,或者每五分钟检查一次服务状态。
队列负责把任务交给后台进程处理,适合发送邮件、生成文件等耗时操作。两者可以配合使用,例如调度器每天触发一个报表 Job,再由队列 worker 完成计算。
如果尚未熟悉 Artisan 命令、数据库配置和队列,可以先查看站内的 Laravel教程,再继续配置下面的定时任务。
二、创建一个定时执行的命令
执行以下命令:
php artisan make:command PruneExpiredCacheLocks
打开生成的 app/Console/Commands/PruneExpiredCacheLocks.php,修改为:
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
class PruneExpiredCacheLocks extends Command
{
protected $signature = 'maintenance:prune-cache-locks';
protected $description = '清理已过期的数据库缓存锁记录';
public function handle(): int
{
$deleted = DB::table('cache_locks')
->where('expiration', '<', now()->timestamp)
->delete();
$this->info("已清理 {$deleted} 条过期缓存锁记录");
return self::SUCCESS;
}
}
这个示例要求数据库中存在 Laravel 默认结构的 cache_locks 表,并使用默认数据库连接。它只删除已过期的锁记录,不会清空缓存表。
如果项目使用 Redis 缓存,或者缓存锁表采用了自定义名称、连接,就不要直接执行这个示例,应换成符合项目实际结构的清理命令。
先手动运行,确认命令能正常执行:
php artisan maintenance:prune-cache-locks
三、定义执行时间
在 routes/console.php 中引入 Schedule,添加调度规则:
use Illuminate\Support\Facades\Schedule;
Schedule::command('maintenance:prune-cache-locks')
->dailyAt('02:00')
->withoutOverlapping(30)
->appendOutputTo(storage_path('logs/cache-lock-cleanup.log'));
这条规则表示每天凌晨 2 点执行清理,并将命令输出追加到日志文件。
withoutOverlapping(30) 用于避免前一次任务尚未完成时再次执行,参数表示重叠锁的过期时间为 30 分钟。它不会把任务执行时间限制在 30 分钟内,因此锁有效期应大于任务正常情况下的最长运行时间。
常用执行频率还包括:
->everyMinute()
->everyFiveMinutes()
->hourly()
->daily()
->weeklyOn(1, '03:00')
最后一项表示每周一凌晨 3 点运行。
执行时间受应用调度时区影响,上线前应确认时区配置。使用存在夏令时切换的时区时,还要考虑任务可能跳过或重复执行的情况。
四、配置服务器Cron
服务器需要每分钟调用一次 Laravel 调度器,再由调度器判断哪些任务到期。
使用运行该项目的系统用户执行:
crontab -e
添加:
* * * * * cd /var/www/example && /usr/bin/php artisan schedule:run >> /dev/null 2>&1
将 /var/www/example 替换为项目根目录,将 /usr/bin/php 替换为实际 PHP 路径。可以通过下面的命令检查路径:
command -v php
Cron 用户需要具备项目访问权限,以及 storage、bootstrap/cache 等目录所需的写入权限。
本地开发时,可以直接运行:
php artisan schedule:work
它会在前台持续运行调度器,关闭终端后便会停止。服务器通常使用 Cron 作为调度入口。
五、检查任务与执行日志
查看已注册的任务及下一次执行时间:
php artisan schedule:list
检查清理日志:
tail -n 50 storage/logs/cache-lock-cleanup.log
如果需要收到执行结果通知,可以增加回调:
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schedule;
Schedule::command('maintenance:prune-cache-locks')
->dailyAt('02:00')
->withoutOverlapping(30)
->appendOutputTo(storage_path('logs/cache-lock-cleanup.log'))
->onSuccess(function () {
Log::info('过期缓存锁清理完成');
})
->onFailure(function () {
Log::error('过期缓存锁清理失败');
});
这段配置应替换前面的调度定义,避免把同一任务注册两次。
appendOutputTo() 会持续追加文件,应配合日志轮转,避免文件长期增长。
六、多台服务器如何避免重复执行
如果多个应用节点都运行了相同 Cron,同一任务可能在多台服务器上同时触发。可以增加 onOneServer():
Schedule::command('maintenance:prune-cache-locks')
->dailyAt('02:00')
->onOneServer()
->withoutOverlapping(30);
onOneServer() 需要各节点使用支持该机制的同一个共享缓存存储,例如共享 Redis 或数据库缓存,并保持相关配置一致。每台服务器各用自己的本地缓存,无法实现跨服务器协调。
另外,withoutOverlapping() 也依赖缓存锁。缓存服务不可用、锁提前过期或人工清除锁,都可能影响防重叠效果。

