Laravel 13 的任务调度系统提供了强大的定时任务管理能力,本文深入探讨高级用法和最佳实践。

任务调度基础回顾

定义调度任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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();

$schedule->job(new ProcessPodcasts)
->daily()
->at('02:00');

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

高级调度频率

复杂时间表达式

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
42
43
44
45
46
<?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('reports:generate')
->cron('0 2 * * 1,3,5');

$schedule->command('cache:warm')
->everyFifteenMinutes()
->between('08:00', '18:00')
->weekdays();

$schedule->command('analytics:aggregate')
->twiceDaily(1, 13)
->timezone('America/New_York');

$schedule->command('backup:incremental')
->everyOddHour()
->skip(function () {
return now()->isHoliday();
});

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

$schedule->command('invoices:generate')
->monthlyOn(1, '00:00')
->when(function () {
return now()->month !== 12;
});

$schedule->command('quarterly:report')
->quarterly()
->at('00:00');

$schedule->command('yearly:audit')
->yearlyOn(1, 1, '00: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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?php

namespace App\Console;

use App\Models\ScheduledTask;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
protected function schedule(Schedule $schedule): void
{
$tasks = ScheduledTask::where('is_active', true)->get();

foreach ($tasks as $task) {
$event = $schedule->command($task->command, $task->parameters ?? []);

if ($task->expression) {
$event->cron($task->expression);
} else {
match ($task->frequency) {
'every_minute' => $event->everyMinute(),
'hourly' => $event->hourly(),
'daily' => $event->daily(),
'weekly' => $event->weekly(),
'monthly' => $event->monthly(),
default => $event->daily(),
};
}

if ($task->timezone) {
$event->timezone($task->timezone);
}

if ($task->without_overlapping) {
$event->withoutOverlapping($task->overlap_minutes ?? 1440);
}

if ($task->run_in_maintenance) {
$event->evenInMaintenanceMode();
}

$event->onSuccess(function () use ($task) {
$task->update([
'last_run_at' => now(),
'last_run_status' => 'success',
]);
});

$event->onFailure(function () use ($task) {
$task->update([
'last_run_at' => now(),
'last_run_status' => 'failed',
]);
});
}
}
}

任务约束条件

环境约束

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
<?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('telescope:prune')
->daily()
->environments(['production', 'staging']);

$schedule->command('debug:clear')
->daily()
->environments('local');

$schedule->command('reports:generate')
->daily()
->when(function () {
return app()->environment('production');
});

$schedule->command('sync:external')
->daily()
->skip(function () {
return app()->isDownForMaintenance();
});
}
}

条件约束

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
<?php

namespace App\Console;

use App\Models\SystemSetting;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
protected function schedule(Schedule $schedule): void
{
$schedule->command('notifications:send')
->daily()
->when(function () {
return SystemSetting::get('notifications_enabled', true);
});

$schedule->command('billing:process')
->daily()
->when(function () {
return !now()->isWeekend() && !now()->isHoliday();
});

$schedule->command('reports:generate')
->daily()
->skip(function () {
$maintenanceWindow = SystemSetting::get('maintenance_window');
if (!$maintenanceWindow) {
return false;
}

$start = now()->parse($maintenanceWindow['start']);
$end = now()->parse($maintenanceWindow['end']);

return now()->between($start, $end);
});
}
}

任务输出管理

输出到文件

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
<?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('reports:generate')
->daily()
->sendOutputTo(storage_path('logs/reports.log'))
->appendOutput();

$schedule->command('backup:full')
->daily()
->sendOutputTo(storage_path('logs/backup-' . date('Y-m-d') . '.log'));

$schedule->command('import:data')
->daily()
->writeOutputTo(storage_path('logs/import.log'), true);
}
}

输出到邮件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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('reports:critical')
->daily()
->emailOutputTo('admin@example.com')
->emailOutputOnFailure('alerts@example.com');

$schedule->command('backup:full')
->daily()
->emailOutputTo(['admin@example.com', 'ops@example.com'])
->subject('Daily Backup Report');
}
}

任务钩子

前置和后置钩子

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
<?php

namespace App\Console;

use App\Services\TaskLogger;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
protected function schedule(Schedule $schedule): void
{
$schedule->command('reports:generate')
->daily()
->before(function () {
app(TaskLogger::class)->logStart('reports:generate');
})
->after(function () {
app(TaskLogger::class)->logEnd('reports:generate');
});

$schedule->command('backup:full')
->daily()
->onSuccess(function () {
app(TaskLogger::class)->logSuccess('backup:full');
})
->onFailure(function () {
app(TaskLogger::class)->logFailure('backup:full');
});
}
}

任务生命周期管理

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
42
43
44
45
46
47
48
49
50
51
<?php

namespace App\Services;

use App\Models\TaskExecution;
use Illuminate\Support\Facades\Log;

class TaskLifecycleManager
{
protected TaskExecution $execution;

public function start(string $taskName): void
{
$this->execution = TaskExecution::create([
'task_name' => $taskName,
'started_at' => now(),
'status' => 'running',
]);

Log::info("Task started: {$taskName}", [
'execution_id' => $this->execution->id,
]);
}

public function success(): void
{
$this->execution->update([
'finished_at' => now(),
'status' => 'success',
]);

Log::info("Task completed successfully", [
'execution_id' => $this->execution->id,
'duration' => $this->execution->started_at->diffInSeconds(now()),
]);
}

public function failure(\Throwable $exception): void
{
$this->execution->update([
'finished_at' => now(),
'status' => 'failed',
'error_message' => $exception->getMessage(),
]);

Log::error("Task failed", [
'execution_id' => $this->execution->id,
'error' => $exception->getMessage(),
]);
}
}

防止任务重叠

基本防重叠

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
<?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('reports:generate')
->daily()
->withoutOverlapping();

$schedule->command('import:large-dataset')
->daily()
->withoutOverlapping(120);

$schedule->command('sync:external')
->everyMinute()
->withoutOverlapping(10)
->skip(function () {
return cache()->has('sync:external:running');
});
}
}

分布式锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php

namespace App\Console;

use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
use Illuminate\Support\Facades\Cache;

class Kernel extends ConsoleKernel
{
protected function schedule(Schedule $schedule): void
{
$schedule->command('reports:generate')
->daily()
->withoutOverlapping()
->onOneServer();

$schedule->command('billing:process')
->daily()
->withoutOverlapping()
->onOneServer();
}
}

单服务器任务

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
<?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('reports:generate')
->daily()
->onOneServer();

$schedule->command('analytics:aggregate')
->hourly()
->onOneServer();

$schedule->command('cleanup:temp')
->daily()
->onOneServer()
->runInBackground();
}
}

后台任务执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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('reports:generate')
->daily()
->runInBackground();

$schedule->command('analytics:process')
->hourly()
->runInBackground()
->withoutOverlapping();
}
}

维护模式处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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('backup:full')
->daily()
->evenInMaintenanceMode();

$schedule->command('reports:generate')
->daily()
->skip(function () {
return app()->isDownForMaintenance();
});
}
}

自定义调度命令

创建调度命令

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Artisan;

class ScheduleRunCommand extends Command
{
protected $signature = 'schedule:run-custom';
protected $description = 'Run scheduled tasks with custom logic';

public function handle(): int
{
$tasks = $this->getTasksToRun();

foreach ($tasks as $task) {
$this->info("Running: {$task['name']}");

$startTime = microtime(true);

try {
Artisan::call($task['command'], $task['params'] ?? []);
$output = Artisan::output();

$this->logSuccess($task, $output, microtime(true) - $startTime);
} catch (\Throwable $e) {
$this->logFailure($task, $e, microtime(true) - $startTime);
}
}

return self::SUCCESS;
}

protected function getTasksToRun(): array
{
return [
[
'name' => 'Daily Report',
'command' => 'reports:generate',
'params' => ['--type' => 'daily'],
],
[
'name' => 'Cache Warm',
'command' => 'cache:warm',
],
];
}

protected function logSuccess(array $task, string $output, float $duration): void
{
$this->info("✓ {$task['name']} completed in {$duration}s");
}

protected function logFailure(array $task, \Throwable $e, float $duration): void
{
$this->error("✗ {$task['name']} failed: {$e->getMessage()}");
}
}

任务调度最佳实践

任务组织

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php

namespace App\Console;

use App\Console\Schedules\BackupSchedule;
use App\Console\Schedules\ReportSchedule;
use App\Console\Schedules\CleanupSchedule;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
protected function schedule(Schedule $schedule): void
{
app(BackupSchedule::class)($schedule);
app(ReportSchedule::class)($schedule);
app(CleanupSchedule::class)($schedule);
}
}
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
<?php

namespace App\Console\Schedules;

use Illuminate\Console\Scheduling\Schedule;

class BackupSchedule
{
public function __invoke(Schedule $schedule): void
{
$schedule->command('backup:full')
->daily()
->at('02:00')
->onOneServer()
->withoutOverlapping()
->emailOutputOnFailure('admin@example.com');

$schedule->command('backup:incremental')
->hourly()
->onOneServer();

$schedule->command('backup:cleanup')
->weekly()
->sundays()
->at('03: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
<?php

namespace App\Console\Schedules;

use App\Services\ErrorNotifier;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Support\Facades\Log;
use Throwable;

trait HandlesScheduleErrors
{
protected function withErrorHandling(Schedule $schedule, callable $callback): void
{
try {
$callback($schedule);
} catch (Throwable $e) {
Log::error('Schedule configuration error', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);

app(ErrorNotifier::class)->notify($e);
}
}
}

总结

Laravel 13 的任务调度系统提供了丰富的功能来管理定时任务。通过合理使用约束条件、钩子函数和防重叠机制,可以构建可靠的后台任务处理系统。关键是要组织好任务代码,并做好错误处理和监控。