最近在开发的过程中,遇到了这样一个场景:用户评论完之后需要延时一段时间来给代表推送企业微信消息,要求延时时间可以后台配置。这个时候我们首先就考虑到用队列实现。
队列之前也是经常使用,当时laravel的队列倒是第一次尝试,花了一个小时看英文文档,然后就可以按部就班的实现一个小的队列任务了。
laravel Queue
原理:它就类似写一个死循环的脚本在Linux系统持续运行,新的队列任务不断push到这个脚本中。所谓队列,会有数据的生产者和消费者之分。生产者向队列中投递数据,消费者从队列中获取数据。
使用场景:一种是高并发的情况,一种是耗时的操作,可以将任务放到队列中去,消费者从队列取任务执行,当然还有失败的情况如何处理,以及延迟,重试,更复杂的情况还有优先级的实现。
系统配置:laravel queue可配置多种队列驱动,包括 "sync", "database", "beanstalkd", "sqs", "redis", "null"(具体参见app/config/queue.php)
其中sync为同步,database为使用数据库,后面三种为第三方队列服务,最后一种为不使用队列。
通过在 .env 中的 QUEUE_CONNECTION 选项,来决定选择何种驱动。
如 QUEUE_CONNECTION=database 即为选择数据库驱动队列。
使用数据库驱动队列
(1)生成任务表
在终端下输入
php artisan queue:table
php artisan migrate
在数据库连接正常的情况下,会在数据库中出现jobs表:
[id] bigint
[queue] nvarchar(255)
[payload] nvarchar(max)
[attempts] tinyint
[reserved_at] int
[available_at] int
[created_at] int
(2) 创建任务类
php artisan make:job sendToRepMessage
在终端内执行上述命令,会自动生成 app/Jobs/sendToRepMessage.php 文件
class sendToRepMessage implements ShouldQueue{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private $data;
/**
* 任务最大尝试次数。
*
* @var int
*/
public $tries = 3;
/**
* 任务运行的超时时间。
*
* @var int
*/
public $timeout = 180;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct($data)
{
//
$this->data=$data;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
//逻辑代码
}
}
在该文件的handle方法中,可以放置任务处理逻辑。
(3) 发送任务
在任意位置,均可像下面一样调用 dispatch 发送任务
SendMail::dispatch($email);
对于队列中很常用的一个场景,延迟任务,我们只需调用delay()方法
ProcessPodcast::dispatch($podcast)->delay(now()->addMinutes(10)) //延时10分钟
完成上述步骤后,可以在数据库中发现一条记录(导出为insert SQL语句):
INSERT INTO [jobs]([id], [queue], [payload], [attempts], [reserved_at], [available_at], [created_at]) VALUES (6, N'default', N'{"displayName":"App\\Jobs\\sendToRepMessage","job":"Illuminate\\Queue\\CallQueuedHandler@call","maxTries":null,"timeout":null,"timeoutAt":null,"data":{"commandName":"App\\Jobs\\sendToRepMessage","command":"O:23:\"App\\Jobs\\sendToRepMessage\":8:{s:29:\"\u0000App\\Jobs\\sendToRepMessage\u0000data\";s:6:\"111222\";s:6:\"\u0000*\u0000job\";N;s:10:\"connection\";N;s:5:\"queue\";N;s:15:\"chainConnection\";N;s:10:\"chainQueue\";N;s:5:\"delay\";N;s:7:\"chained\";a:0:{}}"}}', 0, NULL, 1545980176, 1545980176);
此时任务已经放置在数据库内,只有将队列运行起来后,队列才能主动调用回调方法。
(4)运行队列监听器
// 运行队列监听器(执行队列)
php artisan queue:listen
//在linux中,如果想让它在后台执行,可以这样:
php artisan queue:listen &
如果修改了队列的代码,我们需要重启队列。
//重启队列
php artisan queue:restart
(5)失败队列
//生成失败迁移表
php artisan queue:failed-table
php artisan migrate
//查看失败任务
php artisan queue:failed
//执行某个失败任务
// retry id
php artisan queue:retry id
//全部 all
php artisan queue:all
//删除某个任务
//forget id
php artisan queue:forget id
//删除所有任务
php artisan queue:flush
(6)守护进程
为了保证应用服务的稳定性,需要开启守护进程。
Linux下,一般使用 Supervisor ,Windows下使用 Forever.
我们再来说一下queue:listen 和 queue:work --daemon 的区别
queue:work
默认只执行一次队列请求, 当请求执行完成后就终止;queue:listen
监听队列请求, 只要运行着, 就能一直接受请求, 除非手动终止;queue:work --daemon
同listen
一样, 只要运行着, 就能一直接受请求, 不一样的地方是在这个运行模式下, 当新的请求到来的时候,不重新加载整个框架
, 而是直接 fire 动作.
能看出来, queue:work --daemon
是最高级的,也是在 Laravel 4.2 以后才加入的,一般推荐使用这个来处理队列监听.
注意: 使用
queue:work --daemon
, 当更新代码的时候, 需要停止, 然后重新启动, 这样才能把修改的代码应用上.
配置完成后,本地的环境是搭建好了。问题是如果push到线上,则需要特别注意的是,更改线上的.env文件,否则就不会生效。
整体来说,看英文文档还是非常有必要的,英文原版的一些内容在中文翻译版中可能直接减掉了,虽然不是一些主要功能,但是对于开发流畅性还是有一定影响的。再就是原版英文文档的描述更清晰一些,尤其是某些定义、参数的设置,在中文翻译中可能就被省略掉了,这会在开发中埋下雷,早晚会踩。