Laravel 13 任务调度详解

任务调度是自动化后台任务的核心功能。Laravel 13 提供了优雅的任务调度系统,可以替代传统的 Cron 配置。

定义调度

调度文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// app/Console/Kernel.php
<?php

namespace App\Console;

use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
protected function schedule(Schedule $schedule): void
{
$schedule->command('inspire')
->hourly();
}

protected function commands(): void
{
$this->load(__DIR__.'/Commands');
}
}

调度命令

基本调度

1
2
3
4
5
6
7
8
use Illuminate\Console\Scheduling\Schedule;

protected function schedule(Schedule $schedule): void
{
$schedule->command('emails:send')->daily();
$schedule->command('emails:send --force')->daily();
$schedule->command(EmailsSendCommand::class, ['--force'])->daily();
}

调度 Artisan 命令

1
2
3
$schedule->command('cache:clear')->daily();
$schedule->command('queue:work --daemon')->everyMinute();
$schedule->command('backup:run')->dailyAt('02:00');

调度频率

常用频率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
$schedule->command('task')->everyMinute();
$schedule->command('task')->everyTwoMinutes();
$schedule->command('task')->everyThreeMinutes();
$schedule->command('task')->everyFourMinutes();
$schedule->command('task')->everyFiveMinutes();
$schedule->command('task')->everyTenMinutes();
$schedule->command('task')->everyFifteenMinutes();
$schedule->command('task')->everyThirtyMinutes();

$schedule->command('task')->hourly();
$schedule->command('task')->hourlyAt(17);
$schedule->command('task')->everyOddHour();

$schedule->command('task')->daily();
$schedule->command('task')->dailyAt('13:00');
$schedule->command('task')->twiceDaily(1, 13);
$schedule->command('task')->twiceDailyAt(1, 13, 15);

$schedule->command('task')->weekly();
$schedule->command('task')->weeklyOn(1, '08:00');

$schedule->command('task')->monthly();
$schedule->command('task')->monthlyOn(4, '15:00');
$schedule->command('task')->twiceMonthly(1, 16, '13:00');
$schedule->command('task')->lastDayOfMonth('15:00');

$schedule->command('task')->quarterly();
$schedule->command('task')->yearly();

$schedule->command('task')->cron('* * * * *');

工作日调度

1
2
3
4
5
6
7
8
9
$schedule->command('task')->weekdays();
$schedule->command('task')->weekends();
$schedule->command('task')->mondays();
$schedule->command('task')->tuesdays();
$schedule->command('task')->wednesdays();
$schedule->command('task')->thursdays();
$schedule->command('task')->fridays();
$schedule->command('task')->saturdays();
$schedule->command('task')->sundays();

时间约束

1
2
3
4
5
6
7
8
9
10
11
$schedule->command('task')
->daily()
->between('08:00', '17:00');

$schedule->command('task')
->daily()
->unlessBetween('12:00', '13:00');

$schedule->command('task')
->daily()
->days([1, 15]);

调度闭包

1
2
3
4
5
6
7
$schedule->call(function () {
User::where('expired', true)->delete();
})->daily();

$schedule->call(function () {
Cache::flush();
})->name('clear-cache')->daily();

调度 Shell 命令

1
2
3
4
5
$schedule->exec('node /home/forge/script.js')->daily();

$schedule->exec('/usr/bin/npm run build')
->daily()
->path('/path/to/project');

调度任务

1
2
3
4
5
use App\Jobs\ProcessPodcast;

$schedule->job(new ProcessPodcast('podcast-id'))->hourly();

$schedule->job(new ProcessPodcast('podcast-id'), 'podcasts')->hourly();

调度条件

环境条件

1
2
3
$schedule->command('task')
->daily()
->environments(['production', 'staging']);

维护模式

1
2
3
$schedule->command('task')
->daily()
->evenInMaintenanceMode();

条件回调

1
2
3
4
5
6
7
8
9
10
11
$schedule->command('task')
->daily()
->when(function () {
return true;
});

$schedule->command('task')
->daily()
->skip(function () {
return true;
});

防止重叠

1
2
3
4
5
6
7
8
9
10
11
12
$schedule->command('task')
->daily()
->withoutOverlapping();

$schedule->command('task')
->daily()
->withoutOverlapping(10); // 10 分钟后过期

$schedule->command('task')
->daily()
->withoutOverlapping()
->onOneServer();

单服务器运行

1
2
3
$schedule->command('task')
->daily()
->onOneServer();

后台运行

1
2
3
$schedule->command('task')
->daily()
->runInBackground();

输出处理

发送输出到文件

1
2
3
4
5
6
7
$schedule->command('task')
->daily()
->sendOutputTo($filePath);

$schedule->command('task')
->daily()
->appendOutputTo($filePath);

发送输出到邮箱

1
2
3
4
5
6
7
$schedule->command('task')
->daily()
->emailOutputTo('admin@example.com');

$schedule->command('task')
->daily()
->emailOutputOnFailure('admin@example.com');

钩子

前置和后置钩子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$schedule->command('task')
->daily()
->before(function () {
// 任务执行前
})
->after(function () {
// 任务执行后
})
->onSuccess(function () {
// 任务成功时
})
->onFailure(function () {
// 任务失败时
});

任务输出

输出到存储

1
2
3
4
5
use Illuminate\Support\Facades\Storage;

$schedule->command('report:generate')
->daily()
->storeOutputIn('reports/daily.log');

监控任务

监控任务运行

1
2
3
4
5
6
7
8
9
$schedule->command('task')
->daily()
->pingBefore($url)
->thenPing($url);

$schedule->command('task')
->daily()
->pingBeforeIf($condition, $url)
->thenPingIf($condition, $url);

启动调度器

Cron 配置

1
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1

手动运行

1
2
3
4
php artisan schedule:run
php artisan schedule:run --verbose
php artisan schedule:test
php artisan schedule:list

调度示例

完整示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
protected function schedule(Schedule $schedule): void
{
$schedule->command('backup:run')
->daily()
->at('02:00')
->onOneServer()
->withoutOverlapping()
->emailOutputOnFailure('admin@example.com');

$schedule->command('analytics:aggregate')
->hourly()
->between('00:00', '06:00')
->environments(['production']);

$schedule->command('subscriptions:expire')
->daily()
->when(fn () => config('subscriptions.enabled'));

$schedule->job(new ProcessPendingOrders)
->everyFiveMinutes()
->withoutOverlapping()
->onOneServer();

$schedule->call(function () {
Cache::put('last_cleanup', now());
User::where('last_activity', '<', now()->subDays(30))->delete();
})
->daily()
->name('cleanup-inactive-users');

$schedule->command('report:weekly')
->weekly()
->mondays()
->at('08:00')
->emailOutputTo(['manager@example.com', 'admin@example.com']);

$schedule->exec('node /path/to/script.js')
->daily()
->path('/path/to/project')
->runInBackground();
}

最佳实践

1. 使用 onOneServer

1
2
3
4
// 好的做法:多服务器环境
$schedule->command('task')
->daily()
->onOneServer();

2. 防止重叠

1
2
3
4
// 好的做法:长时间运行的任务
$schedule->command('task')
->everyMinute()
->withoutOverlapping();

3. 使用任务钩子

1
2
3
4
5
6
7
8
$schedule->command('backup:run')
->daily()
->onSuccess(function () {
Log::info('Backup completed');
})
->onFailure(function () {
Log::error('Backup failed');
});

4. 合理设置频率

1
2
3
4
5
6
7
// 好的做法:根据任务性质设置频率
$schedule->command('cache:clear')->daily();
$schedule->command('queue:work')->everyMinute();
$schedule->command('backup:run')->daily();

// 不好的做法:所有任务都高频运行
$schedule->command('backup:run')->everyMinute();

总结

Laravel 13 的任务调度系统提供了强大而灵活的后台任务自动化能力。通过合理使用调度频率、条件约束、防止重叠和钩子功能,可以构建出可靠的后台任务系统。记住使用 onOneServer 防止多服务器重复执行、使用 withoutOverlapping 防止任务重叠、并充分利用钩子来监控任务状态。任务调度是自动化运维的核心,掌握它对于构建高效的应用程序至关重要。