在 Laravel 项目中,有些任务不适合让用户一直等待。例如发送邮件、生成报表、处理图片、同步第三方接口、批量导入数据等。如果这些操作都在请求中同步执行,页面响应会变慢,甚至可能因为接口超时导致用户提交失败。Laravel Queue 可以把耗时任务放到后台异步处理,让前台请求更快返回。
一、Laravel Queue 是什么
Laravel Queue 是 Laravel 提供的队列系统。它可以把需要稍后执行的任务放入队列,再由后台 worker 进程逐个处理。
简单理解:
- 用户提交请求;
- 控制器把耗时任务放入队列;
- 页面快速返回结果;
- 后台队列进程继续执行任务;
- 任务失败时可以记录并重试。
队列适合处理不需要立刻完成、但必须可靠执行的任务。
常见使用场景包括:
- 发送注册邮件;
- 发送订单通知;
- 生成 PDF 或报表;
- 处理上传图片;
- 同步第三方接口;
- 批量导入 Excel;
- 推送消息通知;
- 清理日志或缓存。
二、Laravel 支持哪些队列驱动
Laravel 支持多种队列连接方式,包括 database、Redis、Amazon SQS 等。开发测试时可以先使用 database,生产环境常见选择是 Redis。
队列配置文件位于:
config/queue.php
环境变量中可以设置默认队列驱动:
QUEUE_CONNECTION=database
如果使用 Redis:
QUEUE_CONNECTION=redis
如果项目还没有安装和配置 Redis,可以先用 database 驱动完成队列入门测试。
三、使用 database 队列驱动
使用 database 队列前,需要创建队列表。
执行命令:
php artisan queue:table
php artisan migrate
执行后,数据库中会生成 jobs 表,用来保存待执行任务。
如果需要记录失败任务,可以创建失败任务表:
php artisan queue:failed-table
php artisan migrate
之后失败任务会记录到 failed_jobs 表中,方便查看和重试。
四、创建 Job 任务类
使用 Artisan 创建一个任务类:
php artisan make:job SendWelcomeEmail
生成文件位置通常是:
app/Jobs/SendWelcomeEmail.php
基础示例:
<?php
namespace App\Jobs;
use App\Models\User;
use App\Mail\WelcomeMail;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\Mail;
class SendWelcomeEmail implements ShouldQueue
{
use Queueable;
public function __construct(
public User $user
) {
}
public function handle(): void
{
Mail::to($this->user->email)
->send(new WelcomeMail($this->user));
}
}
实现 ShouldQueue 接口后,这个任务就会进入队列异步执行。如果没有实现该接口,任务可能会被同步执行。
handle() 方法中写真正需要执行的逻辑。
五、分发队列任务
在控制器或服务类中,可以使用 dispatch() 分发任务:
use App\Jobs\SendWelcomeEmail;
use App\Models\User;
public function store()
{
$user = User::create([
'name' => request('name'),
'email' => request('email'),
'password' => bcrypt(request('password')),
]);
SendWelcomeEmail::dispatch($user);
return response()->json([
'message' => '注册成功',
]);
}
这样用户注册成功后,接口可以先返回响应,邮件发送任务交给后台队列处理。
也可以延迟执行任务:
SendWelcomeEmail::dispatch($user)->delay(now()->addMinutes(5));
延迟任务适合提醒、通知、定时检查等场景。
六、运行队列 worker
任务进入队列后,需要启动 worker 才会真正执行。
执行命令:
php artisan queue:work
如果只想处理指定队列,可以使用:
php artisan queue:work --queue=emails
在本地开发时,可以直接在终端运行。生产环境中,队列 worker 应由 Supervisor、systemd 或容器进程管理工具托管,保证异常退出后能够自动重启。
七、指定队列名称
不同类型任务可以放入不同队列。例如邮件任务放入 emails 队列,报表任务放入 reports 队列。
分发任务时指定队列:
SendWelcomeEmail::dispatch($user)->onQueue('emails');
运行对应 worker:
php artisan queue:work --queue=emails
如果有多个队列,可以设置优先级:
php artisan queue:work --queue=high,default,emails
Laravel 会优先处理排在前面的队列。
八、配置任务重试次数
任务可能因为网络波动、邮件服务异常、第三方接口超时而失败。可以在 Job 类中设置最大尝试次数:
class SendWelcomeEmail implements ShouldQueue
{
use Queueable;
public int $tries = 3;
public function handle(): void
{
// 执行任务
}
}
也可以设置超时时间:
public int $timeout = 120;
这表示任务执行超过 120 秒后会被视为超时。
如果需要控制重试间隔,可以定义 backoff():
public function backoff(): array
{
return [60, 300, 600];
}
表示第一次失败后 60 秒重试,第二次失败后 300 秒重试,第三次失败后 600 秒重试。
九、处理失败任务
如果任务多次重试后仍然失败,会进入失败任务记录。
查看失败任务:
php artisan queue:failed
重试所有失败任务:
php artisan queue:retry all
重试指定失败任务:
php artisan queue:retry 1
删除所有失败任务:
php artisan queue:flush
如果想在任务最终失败时执行自定义逻辑,可以在 Job 中添加 failed() 方法:
use Throwable;
public function failed(Throwable $exception): void
{
report($exception);
}
这里可以记录日志、发送告警或更新业务状态。
十、避免队列任务中的常见问题
队列任务看起来简单,但实际项目中很容易遇到一些坑。
1、不要传递过大的数据
Job 构造方法中不要传入大数组、大文件内容或复杂对象。更推荐传模型 ID,再在 handle() 中重新查询。
SendWelcomeEmail::dispatch($user->id);
然后在 Job 中查询:
$user = User::findOrFail($this->userId);
这样可以减少队列负担,也降低序列化异常风险。
2、任务要能重复执行
队列任务可能因为失败重试而执行多次,所以任务逻辑要尽量具备幂等性。
例如发送优惠券、扣减库存、调用支付接口等操作,必须避免重复执行带来业务错误。可以通过状态字段、唯一记录或业务流水号进行保护。
3、外部接口必须设置超时
如果队列任务调用第三方 API,一定要设置超时和失败处理。否则 worker 可能长时间卡住,影响后续任务执行。
4、修改代码后要重启 worker
生产环境中,queue:work 是长驻进程。代码更新后,旧 worker 不会自动加载新代码,需要执行:
php artisan queue:restart
该命令会让 worker 在当前任务处理完成后平滑重启。
十一、生产环境如何守护队列进程
生产环境不建议手动开一个终端运行 php artisan queue:work。一旦终端关闭或进程异常退出,队列任务就会停止处理。
常见做法是使用 Supervisor 管理 worker。示例配置:
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/example.com/artisan queue:work redis --sleep=3 --tries=3 --timeout=120
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/example.com/storage/logs/worker.log
stopwaitsecs=3600
配置完成后启动 Supervisor,即可让队列 worker 常驻运行。不同服务器环境路径和用户不同,需要按实际项目调整。
十二、队列和定时任务有什么区别
队列用于异步处理任务,定时任务用于按时间周期触发任务。
例如:
- 用户注册后发送邮件,适合队列;
- 每天凌晨生成报表,适合定时任务;
- 报表生成过程耗时较长,可以由定时任务触发队列任务;
- 批量同步接口,可以定时触发,再交给队列分批处理。
两者经常配合使用,不是替代关系。
十三、常见问题
Laravel 队列任务为什么没有执行?
常见原因是没有启动 queue:work,或者 QUEUE_CONNECTION 配置不正确。使用 database 驱动时,还要确认 jobs 表已经创建。
修改 Job 代码后为什么不生效?
生产环境的队列 worker 是长驻进程,代码更新后需要执行 php artisan queue:restart,让 worker 重新加载代码。
队列适合处理所有耗时任务吗?
大部分耗时任务适合放入队列,但任务本身要有清晰的失败处理和重试机制。涉及支付、库存和外部系统写入时,还要做好幂等设计。
database 和 Redis 队列怎么选?
入门和小项目可以使用 database,配置简单。任务量较大、并发更高或需要更好性能时,Redis 更合适。
队列任务失败后会自动重试吗?
可以自动重试,但需要配置尝试次数、超时时间和失败任务表。最终失败后,可以通过 queue:failed 查看,并使用 queue:retry 重新执行。

