Laravel 队列为不同的后台队列服务提供了统一的 API,例如 Beanstalk,Amazon SQS,Redis,甚至其他基于关系型数据库的队列。队列的目的是将耗时的任务延时处理,比如发送邮件,从而大幅度缩短 Web 请求和响应的时间。
队列配置文件存放在 config/queue.php
。每一种队列驱动的配置都可以在该文件中找到,包括数据库、Beanstalkd、Amazon SQS、Redis以及同步(本地使用)驱动。其中还包含了一个 null
队列驱动用于那些放弃队列的任务。
连接 Vs. 队列
在开始使用 Laravel 队列以前,了解“连接”和“队列”的关系非常重要。在配置文件 config/queue.php
有一个 connections
配置项。该配置项定义了后台队列服务的特定连接,如 Amazon SQS, Beanstalk, 或 Redis。每种队列连接都可以有很多队列,可以想象在银行办理现金业务的各个窗口队列。
请注意 queue
配置文件中的每个连接配置示例都有一个 queue
属性。当新的队列任务被添加到指定的连接时,该配置项的值就是默认监听的队列(名称)。换种说法,如果你没有指派特别的队列名称,那么 queue
的值,也是该任务默认添加到的队列(名称):
//以下的任务将被委派到默认队列...
dispatch(new Job);
//以下任务将被委派到"email"队列...
dispatch((new Job)->onQueue('email'));
有些应用并不需要将任务分配到多个队列,单个队列已经非常适用。但是,应用的任务有优先级差异或者类别差异的时候,推送任务到多个队列将是更好地选择,因为 Laravel 的队列进程支持通过优先级指定处理的队列。举个例子,你可以将高优先级的任务委派到 high
(高优先级)队列,从而让它优先执行。
php artisan queue:work --queue=high,default
驱动预备知识
数据库
要使用 database
队列驱动,你需要数据表保存任务信息。要生成创建这些表的迁移,可以运行 Artisan 命令 queue:table
,迁移被创建之后,可以使用 migrate
命令生成这些表:
php artisan queue:table
php artisan migrate
Redis
要使用 redis
队列驱动,需要在配置文件 config/database.php
中配置 Redis 数据库连接。
Redis 集群
如果 Redis 队列连接使用 Redis Cluster(集群),队列名称必须包含 key hash tag,以确保给定队列对应的所有 Redis keys 都存放到同一个 hash slot:
'redis' => [
'driver' => 'redis' ,
'connection' => 'default' ,
'queue' => '{default}' ,
'retry_after' => 90 ,
],
学院君注:对一般中小型应用推荐使用 Redis 作为队列驱动。
阻塞
使用 Redis 队列时,可以使用 block_for
配置项来指定驱动在迭代队列进程循环并重新轮询 Redis 数据库之前等待可用队列任务的时间。根据队列负载来调整此配置值会比轮询 Redis 数据库来查找新任务更加高效。例如,你可以设置该值为 5 来告诉驱动在等待可用队列任务时需要阻塞五秒:
'redis' => [
'driver' => 'redis' ,
'connection' => 'default' ,
'queue' => 'default' ,
'retry_after' => 90 ,
'block_for' => 5,
]
注:阻塞是一个实验性功能,如果 Redis 服务器或队列进程与检索队列任务同时崩溃,那么队列任务有可能会丢失。
创建任务
生成任务类
通常,所有的任务类都保存在 app/Jobs
目录。如果 app/Jobs
不存在,在运行 Artisan 命令 make:job
的时候,它将会自动创建。你可以通过 Artisan CLI 来生成队列任务类:
php artisan make:job ProcessPodcast
生成的类都实现了 Illuminate\Contracts\Queue\ShouldQueue
接口, 告诉 Laravel 将该任务推送到队列,而不是立即运行:
任务类结构
任务类非常简单,通常只包含处理该任务的 handle
方法,让我们看一个任务类的例子。在这个例子中,我们模拟管理播客发布服务,并在发布以前上传相应的播客文件:
在本示例中,我们将 Eloquent 模型作为参数直接传递到构造函数。因为该任务使用了 SerializesModels
trait,Eloquent 模型将会在任务被执行时优雅地序列化和反序列化。如果你的队列任务在构造函数中接收 Eloquent 模型,只有模型的主键会被序列化到队列,当任务真正被执行的时候,队列系统会自动从数据库中获取整个模型实例。这对应用而言是完全透明的,从而避免序列化整个 Eloquent 模型实例引起的问题。
handle
方法在任务被处理的时候调用,注意我们可以在任务的 handle
方法中进行依赖注入。Laravel 服务容器会自动注入这些依赖。
注:二进制数据,如原生图片内容,在传递给队列任务之前先经过 base64_encode
方法处理,此外,该任务被推送到队列时将不会被序列化为 JSON 格式。
分发任务
创建好任务类后,就可以通过任务自身的 dispatch
方法将其分发到队列。dispatch
方法需要的唯一参数就是该任务的实例:
ProcessPodcast::dispatch($podcast);
延时分发
有时候你可能想要延迟队列任务的执行,这可以通过在分发任务时使用 delay
方法实现。例如你希望将某个任务在创建 10 分钟以后才执行:
ProcessPodcast::dispatch($podcast)->delay(now()->addMinutes(10));
注:Amazon SQS 的队列服务最长延时 15 分钟。
任务链
任务链允许你指定一个需要在一个序列中执行的队列任务列表,如果序列中的某个任务失败,其它任务将不再运行。要执行一个队列任务链,可以使用任意可分发任务上的 withChain
方法:
ProcessPodcast::withChain([
new OptimizePodcast ,
new ReleasePodcast
])->dispatch();
链接连接 & 队列
如果你想要指定任务链使用的默认连接和队列,可以使用 allOnConnection
和 allOnQueue
方法。这些方法指定队列连接和队列名称
自定义队列 & 连接
分发到指定的队列
通过推送任务到不同队列,你可以将队列任务进行“分类”,甚至根据优先级来分配每个队列的进程数。请注意,这并不意味着使用了配置项中那些不同的连接来管理队列,实际上只有单一连接会被用到。要指定队列,请在任务实例使用 onQueue
方法:
ProcessPodcast::dispatch($podcast)->onQueue('processing');
分发到指定的连接
如果你使用了多个连接来管理队列,那么可以分发任务到指定的连接。请在任务实例中使用 onConnection
方法来指定连接:
ProcessPodcast::dispatch($podcast)->onConnection('sqs');
当然,你可以同时使用 onConnection
和onQueue
方法来指定任务的连接和队列:
$job = (new ProcessPodcast($podcast))->onConnection('sqs')->onQueue('processing');
指定最大失败次数/超时时间
最大失败次数
指定队列任务最大失败次数的一种实现方式是通过 Artisan 命令 --tries
切换:
php artisan queue:work --tries=3
不过,你还可以在任务类自身定义最大失败次数来实现更加细粒度的控制,如果最大失败次数在任务中指定,则其优先级高于命令行指定的数值: