本文目录导读:

在Laravel中实现任务调度的失败重试机制,有以下几种方案:
使用队列的重试机制
基础配置
// config/queue.php
'connections' => [
'database' => [
'driver' => 'database',
'table' => 'jobs',
'queue' => 'default',
'retry_after' => 90, // 任务失败后重试的延迟时间(秒)
],
],
定义可重试任务
// app/Jobs/ProcessReport.php
class ProcessReport implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 3; // 最大重试次数
public $backoff = [60, 300, 600]; // 每次重试的延迟时间(秒)
public $timeout = 120; // 任务超时时间
public $maxExceptions = 3; // 最大异常次数
public function handle()
{
// 业务逻辑
try {
// 处理任务
} catch (\Exception $e) {
// 记录日志
Log::error('任务失败', [
'error' => $e->getMessage(),
'attempt' => $this->attempts()
]);
throw $e;
}
}
// 失败后的回调
public function failed(\Throwable $exception)
{
Log::error('任务最终失败', [
'message' => $exception->getMessage(),
'job_id' => $this->job->getJobId()
]);
}
}
调度任务中的重试
使用调度器配置
// app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
$schedule->command('report:generate')
->daily()
->withoutOverlapping() // 防止任务重叠
->onOneServer() // 只在单服务器执行
->runInBackground() // 后台运行
->onFailure(function () {
// 失败时发送通知
Notification::send($admins, new TaskFailedNotification());
});
}
手动重试机制
// app/Console/Commands/GenerateReport.php
class GenerateReport extends Command
{
protected $signature = 'report:generate';
protected $maxRetries = 3;
protected $retryDelay = 60; // 秒
public function handle()
{
$attempt = 1;
while ($attempt <= $this->maxRetries) {
try {
$this->processData();
$this->info('报表生成成功');
return 0;
} catch (\Exception $e) {
Log::error("第{$attempt}次尝试失败", [
'error' => $e->getMessage()
]);
if ($attempt < $this->maxRetries) {
$this->info("等待 {$this->retryDelay} 秒后重试...");
sleep($this->retryDelay);
$attempt++;
} else {
$this->error('所有重试都失败了');
return 1;
}
}
}
}
}
使用Redis锁实现分布式重试
// app/Console/Commands/ProcessData.php
class ProcessData extends Command
{
protected $signature = 'data:process';
public function handle()
{
$lock = Cache::lock('data-processing', 120);
try {
if ($lock->get()) {
// 检查是否有限制
$retryCount = Cache::get('data-process-retry', 0);
if ($retryCount >= 3) {
$this->error('超过最大重试次数');
return 1;
}
$this->processData();
// 成功,清除重试计数
Cache::forget('data-process-retry');
return 0;
}
} catch (\Exception $e) {
// 失败,递增重试计数
$retryCount = Cache::increment('data-process-retry');
Cache::put('data-process-retry-time', now(), 3600);
Log::error("处理失败,第{$retryCount}次重试");
// 延迟重试
$this->scheduleRetry($retryCount);
} finally {
$lock->release();
}
}
private function scheduleRetry($retryCount)
{
$delay = [5, 15, 30][$retryCount - 1] ?? 60;
// 通过队列延迟执行
ProcessDataJob::dispatch()->delay(now()->addSeconds($delay));
// 或者通过调度器
$this->line("将在 {$delay} 秒后重试");
Event::dispatch('data.process.retry', new DataProcessRetryEvent($retryCount));
}
}
使用第三方包
supervisord 配置
[program:laravel-scheduler] process_name=%(program_name)s_%(process_num)02d command=php /path/to/project/artisan schedule:work autostart=true autorestart=true user=www-data numprocs=1 redirect_stderr=true stdout_logfile=/path/to/project/storage/logs/scheduler.log stopwaitsecs=3600
Laravel Horizon
// config/horizon.php
'defaults' => [
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['default'],
'balance' => 'auto',
'processes' => 1,
'tries' => 3,
'timeout' => 60,
'retry_after' => 90,
],
],
日志和监控
// app/Http/Middleware/TaskRetryMiddleware.php
class TaskRetryMiddleware
{
public function handle($job, $next)
{
$attempt = $job->attempts();
try {
$response = $next($job);
// 记录成功日志
Log::info('任务执行成功', [
'job' => get_class($job),
'attempts' => $attempt
]);
return $response;
} catch (\Exception $e) {
// 记录失败日志
Log::error('任务执行失败', [
'job' => get_class($job),
'attempts' => $attempt,
'error' => $e->getMessage()
]);
// 根据失败类型决定是否重试
if ($this->shouldRetry($e)) {
$job->release(60); // 延迟60秒重试
} else {
throw $e;
}
}
}
private function shouldRetry($exception)
{
// 不重试某些特定异常
return !($exception instanceof \DomainException);
}
}
最佳实践
// app/Console/Commands/ScheduleTask.php
class ScheduleTask extends Command
{
protected $signature = 'scheduler:retry-task';
public function handle()
{
$maxRetries = 3;
$retryCount = 0;
do {
try {
$this->performTask();
$this->info('任务成功');
break;
} catch (\Exception $e) {
$retryCount++;
$this->error("第 {$retryCount} 次失败: " . $e->getMessage());
if ($retryCount >= $maxRetries) {
$this->notifyFailure($e);
return 1;
}
$this->line('等待重试...');
sleep(pow(2, $retryCount) * 10); // 指数退避
}
} while ($retryCount < $maxRetries);
return 0;
}
}
注意事项
- 幂等性:确保任务可重复执行
- 事务安全:处理数据库事务回滚
- 监控告警:重试失败必须通知相关人员
- 资源控制:防止无限重试耗尽资源
选择合适的方案取决于你的具体需求:简单的任务可用内置重试,重要任务建议用队列+失败记录机制,生产环境建议配置完善的监控和告警系统。