在后台开发中,很多情况需要用到队列来处理业务逻辑,前几天亲自实践了一下Laravel的队列功能,在此分享一蛤。

使用情景

举几个使用到消息队列的栗子

  • 在处理商城的库存问题,简单的实现是当用户的某件商品下单了,库存就相对应减少,但是当并发量一大时,比如A,B两个顾客同时下单,如果不使用队列可能会造成库存错误,使用队列了保证不同时执行两个减库存操作(一个阻塞掉了另外一个)。
  • 当一个服务器要处理一个请求端发来的多组大量数据,而且每组数据处理起来的时间会很慢时,可以采取异步队列,就相当于请求端发过来的数据先存着,放进队列里,然后再异步处理数据(即处理数据的过程不在请求的生命周期里)
  • 由于比如用户登陆了某网站之后,后台就过xx分钟给用户发延迟邮件来推送广告(怎么有点流氓(°Д°)),这里也可以用到异步队列,虽然可以用设计定时任务来轮询检查的笨方法,不过感觉用轮询来实现异步不是真正的异步

扯了这么多,刚回到正题了—实践

实验环境

  • Laravel 5.2
  • 系统: Windows(wamp) or Linux(lnmp)

步骤


配置

laravel提供了多种消息队列驱动: sync(同步) database beanstalkd sqs redis
默认是sync,不过实际上其他几种异步队列多点,本章先用database练练手,前几天刚服务器刚配好redis,迟点再补上redis的吧~

so,先到 /.env (默认根目录为你的项目目录) 中 填上 QUEUE_DRIVER=database ,当然你也可以到/config/queue.php的default中将sync修改,但是建议修改.env


建表

修改完队列驱动后,就用Laravel的migration自动生成队列需要的表,到根目录执行artisan

1
2
3
php artisan queue:table
php artisan queue:failed-table #处理出错队列用的表
php artisan migrate

此时,数据库中就会出现jobs表和failed_jobs表


创建任务类并编写任务类

执行artisan命令快速生成任务类并在/app/jobs/ 目录下面找到刚创建的任务

1
php artisan make:job yourJobName

刚自动生成的任务类包含了两个初始方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Create a new job instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
//
}

构造方法__construct用于你给任务类传值or对象用,至于handle方法则是队列任务的处理(即给排在队列里的任务如何执行业务逻辑)

这时就有个问题了,排在队列里的任务如果处理时失败了,或者因不符合某些业务逻辑需要进行统一的失败操作,那该怎么办呢?

此时可以在这个任务类中添加一个failed方法

1
2
3
4
public function failed()
{
//
}

这样laravel就可以识别得到,当一个任务在执行handle方法时,遇到异常了(Throw Exception),laravel就会自动捕获异常并执行failed方法,并在failed_job表中记录信息。这个用来管控你看不见摸不着的异步队列任务比较有用,你可以根据业务逻辑在failed方法里将错误信息输出到自己的日志或者其他操作。

任务类里自带的几个有用方法

通过artisan生成的任务类里,由于使用(use)了InteractsWithQueue这个trait,所以可以使用到里面有几个自带的方法

  • attempts

    作用:返回任务的已经执行次数(eg.可以用于在handle方法中判断任务执行超过n次时就抛出异常来执行failed方法等等)

    食用方法:在任务类的handle方法或者failed方法中,无参

1
$this->attempts();
  • delete

    作用:在队列中删除该任务(jobs表里删除)

    食用方法:同上

1
$this->delete();
  • release

    作用:将当前任务再次放进队列中,可传入参数,来控制同一个任务两次运行之间的等待时间

    食用方法:同上,不过有参,参数为int类型 表示多少秒

1
$this->release(10); // 同一个任务两次运行之间隔10秒执行

目前任务类里比较常用的就是这几个方法了。


执行任务

代码里要做的事

编写好任务类之后,你可以在你的控制器里面创建任务,并且将它调度执行(dispatch)

1
2
$job=new yourJobName($value); // 按实际情况来传值
dispatch($job);

如果想设置延时

1
2
$job=new yourJobName()->delay(10); // 延迟10秒执行
dispatch($job);

如果想指定队列(分类)

1
2
$job=new yourJobName()->onQueue('test'); //可以查看对应job表的queue字段
dispatch($job);

shell里要做的事

当代码里做的事做好了之后,我们一个请求过去服务器的项目程序就能启动任务了吗?不能,因为之前我们说过,除了sync其他驱动都是异步的,而php是单线程的(意味着一个开启一个php脚本相当于开启一个进程,而这个进程里面只有一个线程),所以我们一个请求只有一个线程,因此我们需要开多一个进程来进行队列监听了,当监听脚本监听到队列有动静时,就对队列进行处理。

1
php artisan queue:listen

测试

  1. 开始监听
  2. 发送请求(可以用postman试试)到指定控制器来调度队列任务
  3. 观察正在执行监听任务的shell(ctrl+c 退出监听)


小结

通过实践,算是掌握了laravel的队列使用,下面就提一下我遇到的坑点以及改进吧~


注意事(坑)项(点)

  • 当你使用了onQueue方法来给任务分类然后调度时,监听的命令该怎么写?

    一开始我天真的认为一句 php artisan queue:listen 就能监听所有分类的任务,结果之后发现我定义的一个分类的任务一直不执行,看了文档又找不到缘故

1
2
$job=new yourJobName()->onQueue('test'); //将一个任务归为test分类
dispatch($job);
1
php artisan queue:listen # 你会发现,是监听不了上面的的任务的

翻了一下帮助命令

1
php artisan help queue:listen

可以看到

所以正确的执行姿势应该是

1
2
php artisan queue:listen --queue test # 以我上面的例子而言 此处队列分类是test
php artisan queue:listen --QUEUE test # help里面说到大写也可以

还有其他queue的命令就自己发掘或者看文档吧~


优化

  • 为了保护监听进程(失败后重启等),我们可以尝试用一下Supervisor(linux)
  • 因为现在很多缓存都用redis,所以这个队列任务如果用redis的话,就可以一举两得了~~

以上两点列入到Todo list中,有空再捣鼓一蛤!!


感谢

感谢 Laravel学院 的中文文档 !