Laravel 4.2 中队列服务(queue)的使用感受和踩到的坑

2014-8-16   /   阅读数:28676   /   分类: Laravel

这半个月,我参与重写了一个微信公众号后端系统,首次使用了laravel 4.2,以及laravel引以为傲的队列服务(queue)。

由于整个系统涉及到多端交互,又有大量语音传输、处理的业务,我们在一些地方发现响应时间过长。之前的系统基于node.js和mongoDB,由于node天生就是异步,有守护进程,所以并没有出现过这个问题,而这次重写必然要引入异步流程了。Queue进入了我们的视线。

根据这一页几乎还全是英文的”中文文档 ,laravel恰好在4.2版本中刚刚引入了redis作为队列存储,这是一个非常好的消息。OK,背景介绍到这里,下面扯扯干货。

laravel中的队列服务跟其他队列服务也没有什么不同,都是最符合人类思维的最简单最普遍的流程:有一个地方存放队列信息,一个PHP进程在运行时将任务写入,另外一个PHP守护进程轮询队列信息,将达到执行要求的任务执行并删除。由于PHP是url驱动的同步语言,本身是阻塞的,所以laravel提供一个守护进程工具来查询并执行队列信息也就不足为奇了。

Laravel的queue配置文件是 /app/config/queue.php,在 Default Queue Driver 这一项中,可以选择"sync", "beanstalkd", "sqs", "iron", "redis" 五种驱动器。


1. sync是本地调试用的同步驱动器

2. beanstalkd 是一个专业队列服务驱动器:http://kr.github.io/beanstalkd/ 

3. sqs和iron是国外第三方队列服务

4. 最后一项redis给了我们一个使用redis的理由,这样我们顺便把缓存服务和session服务全部迁移到redis上了。

0. 顺便说一句,session驱动器千万别用mysql,处理时间1S不是梦,哎,看谁呢,说的就是你,1S哥!


队列服务需要专门新建任务类,作为独立类,他们不需要继承类,因为队列里的任务在执行的时候,是由PHP守护进程来独立调用的,当然如果你要use一下别的类再调用,也不会出错。之前我把很多额外服务独立到了一个单独的文件夹 /app/services 里,比如输入信息验证 validator,特殊安全验证模块等,这次queue类们就位于其中。

queue的使用非常简单,下面就是一个简单的示例:

use Queue;
Queue::push('CurlJsonQueue', [
	'url' => $url,
	'json' => $json
]);


这就是一个标准的queue压入流程了。当然,在这里我把CurlJsonQueue类放到了services根目录下,这个目录已经被我注册到composer.json的"autoload"的"classmap"中,是位于顶层命名空间中的,可以直接调用,如果需要调用非顶层命名空间,是可以写 App\OOXX 的。我们的系统需要大量和微信服务器交互,所以就独立出来了这个类。

<?php

class CurlJsonQueue extends BaseController{

	public function fire($job, $data)
	{
		$url = $data['url'];
		$json = $data['json'];

		parent::base_post_curl($url, $json);

		$job->delete();
	}
}


这个类默认的方法是 fire() ,参数也是固定的两个 $job 和 $data,由于我在BaseController中封装了post的curl模块,所以就调用了一下。另外这里还有一个小坑,当时写base_post_curl() 的时候用的protected,导致use BaseController无效,必须继承。

通过执行上面的代码,queue中就被放入了一个新的任务,laravel通过下面的命令开启守护进程:


php artisan queue:listen


然后守护进程就开始处理队列了。此代码中的PHP命令和artisan文件的路径请自行调整。

大家可能注意到了,我们要使用的这个队列系统用到了redis和PHP命令行,如果在测试环境,加个开机启动甚至是手动启动都可以,但是在生产环境就需要更稳固的工具来守护这两个程序,我们用的是supervisor,关于supervisor的安装配置大家可以参考这篇文章: http://blog.segmentfault.com/qianfeng/1190000000532561 注意,文章里有小坑请自行去踩。。。

OK,全部配置好之后,跑起来redis和PHP命令行,整个系统就开始愉快地运行啦~


使用感受:

队列服务超好用,之前一次和app的交互流程需要6-7S,异步以后降低到2S以内,基本就是传输时间和PHP代码运行时间了,耗时的特殊操作已经异步了。不过队列服务默认1S开一个进程检查一次redis中有没有可以运行的服务,在阿里云服务器上,大约能占到单核的10%,消耗略大,而且队列处理时间相对较长,因为没有了之前同步时候的文件加载福利。不过如果有多个任务,PHP进程是会连续执行的,不会1S执行一个的啦。


下面说说坑:


1. 由于queue核心类使用了一个特殊函数,导致没有明确类型的变量会以单元素数组的形式存进json,再存进redis。解决办法就是在每一个要放进去的数据前面加上 ''. 。上面的$url和$json由于都已经在前面用引号进行了类型申明,故没做这一步操作。

2. 如果要传递url给队列,系统queue类会在每一个 / 前面加上两个 \\ 。这对于一些特殊操作可能会造成致命影响。(开玩笑,有上面那个致命么!)


WRITTEN BY

avatar

评论:

我是我风我zzz
2017-12-04 18:00

Argument 1 passed to Illuminate\Redis\Database::__construct() must be of the type array, null given, called in /Applications/XAMPP/xamppfiles/htdocs/chengjiyun/vendor/laravel/framework/src/Illuminate/Redis/RedisServiceProvider.php on line 23 and defined

请问一下这个如何解决?

2017-06-16 23:17
不知道为什么当使用rabbitmq作为laravel的队列服务时候,在mac下消费速度可以达到800-900/s左右,但在服务器只能跑到25/s,服务器是centos7
环境nginx+php-fpm,环境都一致, 配置处于同等水平,真是百思不得其解
风的颜色
2016-08-08 23:55
我还是用的4.2,supervisor里守护php artisan queue:listen --queue=XXX 是ok的,队列跑起来了。看了好多地方说php artisan queue:work效率高,因此尝试在supervisor里修改命令为 php artisan queue:work --queue=XXX ,发现队列执行一次就不执行了,然后后面加上--daemon后,再试,秒执行,但是突然发现redis里存的队列竟然消失了,为啥没执行的队列job会消失呢?不知道是不是4.2中不能靠守护进程用 php artisan queue:work --queue=XXX --daemon 监视队列而只能用listen监视?5以上可以???
kris
2016-06-28 11:19
这个目录为啥总是提示我“Class CurlJsonQueue does not exist”;?????
JohnLui
2016-06-28 11:40
@kris:这是 4.2 时代的文章了,现在 5 时代 CurlJsonQueue 前面需要加上命名空间了
lion
2016-05-24 12:00
laravel5.1使用mysql做队列
跑php artisan queue:listen 。程序正常执行队列
查看文档queue:work推荐效率更高。
但是执行php artisan queue:work --daemon 只执行了一次
文档的完整是php artisan queue:work connection --daemon  
我该怎么让work持续执行,或者说这个connection 该如何确定?
JohnLui
2016-05-24 16:38
@lion:就是队列配置文件里那个connection,默认是同步的,所以执行一次就退出了。
yang
2016-01-15 17:58
我来说一下 supervisor的坑吧,就是服务全部都是需要前台运行的,比如redis.conf 里面的 daemonize 必须是 no,github 链接 https://github.com/tyua07/supervisor_lnmp,这个是一个完整的例子!
Lai
2015-11-05 17:41
Mail::queue('emails.card', ['model' => $model], function($message) use($model) {
      $message->to($model->mail)->subject('订单号: ' . $model->order_id);
});
邮件队列保存到redis后,在执行队列时,出现如下错误:
  [Illuminate\Contracts\Queue\EntityNotFoundException]
  Queueable entity [Card\Models\CardSell] not found for ID [].
请问这个怎么解决?
JohnLui
2015-11-06 00:39
@Lai:看起来是命名空间问题。
phper
2015-10-27 10:28
我用的laravel5,CurlJsonQueue.php在app/services目录下,redis已经有job了,php artisan queue:listen为什么一直提示[ReflectionException]
Class CurlJsonQueue does not exist
JohnLui
2015-10-27 11:18
@phper:应该是命名空间问题。你调用的类名应该是绝对地址,不能在顶部用 use。
lijinma
2015-09-17 19:50
正式环境使用 php artisan queue:listen 不太好吧。
her
2015-09-16 16:44
使用lumen  php artisan queue:listen
报错:
[InvalidArgumentException]                                  
  No handler registered for command [__PHP_Incomplete_Class]  
想请问下 是什么原因
JohnLui
2015-09-16 18:08
@her:lumen 是个坑,千万别跳。。。
sun
2015-09-29 09:05
@her:我在使用Lumen的队列时也遇到了这个问题,你解决了吗?
sun
2015-10-13 10:46
@sun:后来调试发现在使用redis作为queue的驱动时,如果指定queue的名字为default会出错
'redis' => [
            'driver' => 'redis',
            'connection' => 'default',
            'queue'  => 'q1',  //使用default会导致异常
            'expire' => 60,
        ],/**/

不晓得是冲突了还是什么原因,修改之后就正常运行了
Barnett
2015-07-13 11:25
大神,我再用lumen  php artisan queue:listen  命令的时候开始出现[PDOException]     SQLSTATE[HY000] [2002] No such file or directory   按照你博客下地一个回复把数据库host=localhost 改成了host=127.0.0.1  但是再运行这个命令的时候却出现[PDOException]                        SQLSTATE[HY000] [2002] Connection refused     但是我地数据库账户密码都没有问题。。。
JohnLui
2015-07-13 11:27
@Barnett:lumen 是个坑。不过 Connection refused 就是账号密码有问题。
Barnett
2015-07-13 18:08
@JohnLui:这个问题解决了~原来是我得数据库端口配置错了~不过还是要把host改成127.0.0.1   不够有一个新问题,就是我在测试队列监听,数据库有配置,但是为何不会把队列信息记录到数据库里?
我行我速的风
2015-03-26 15:14
队列处理类不建议继承baseController

发表评论:

© 2011-2018 岁寒  |  Powered by Emlog