php-resque消息队列系统的使用

PHP-Resque的使用(一) 安装

第二部分我们使用php-resque作为队列系统,这一节讲如何安装php-resque。

PHP-Resque是依赖Redis的,所以需要先安装Redis及PHP的Redis扩展。以下是所有需要安装的组件:

  • Redis
  • PHP的Redis扩展(php-redis)
  • php-resque
  • PHP的PCNTL扩展

Redis

Redis是一个开源的KV数据库,数据是保存在电脑RAM中的,速度非常快,所以通常可以使用Redis来做缓存,或保存Session等。可以在Redis的官方网站下载最新稳定版本。Redis的安装方法本文不再赘述,安装完成后不要忘记启动。

PHP-Resque

php-resque是resque的PHP版本,很多特性都和原版相似或相同。

下载最新版本的zip压缩包,或克隆它的仓库:

git clone git://github.com/chrisboulton/php-resque.git


PHPredis以上下载的只是php-resque的库,只需要把文件夹放在任何你项目需要的位置即可。也可以使用Composer安装php-resque。

PHPredis扩展相当于是Redis的PHP API,但它不是PHP使用Redis的唯一接口,类似的库还有redisentrediskapredis、redisentwrap等。但phpredis是其中最快也是最流行的。关于phpredis扩展的安装方法网上也有很多,也就不再赘述了。

PHP PCNTL扩展

PCNTL(进程控制扩展)依赖于Unix系列系统的进程管理,所以php-resque只能运行在UNIX架构的电脑上,如Linux。

一般可以通过编译安装PHP的时候启用PCNTL扩展,如果没有安装也可以:

  • 下载对应版本的PHP源码
  • 解压文件
    tar -zxvf php-x.x.x.tar.gz
  • 进入ext/pcntl目录
    cd php-x.x.x/ext/pcntl/
  • 配置、编译、安装
    `sudo phpize && ./configure && make install`
    
  • 添加extension=pcntl.so到php.ini
  • 重新启动Apache/Nginx

后台任务和PHP-Resque的使用(二) 使用Worker

注意,这篇教程仅适用于Linux和OS X的系统,Windows并不适用。

理解Worker的本质

技术上讲一个Worker就是一个不断运行的PHP进程,并且不断监视新的任务并运行。

一个简单的Worker的代码如下:

while (true) {
    $jobs = pullData(); // 从队列中拉取任务
    foreach ($jobs as $class => $args) { // 循环每个找到的任务
        $job = new $class();
        $job->perform($args); // 执行任务
    }
    sleep(300); // 等待5分钟后再次尝试拉取任务
}


以上这些代码的具体实现都可以交给php-resque。创建一个Worker,php-resque需要以下参数:

QUEUE: 需要执行的队列的名字
INTERVAL:在队列中循环的间隔时间,即完成一个任务后的等待时间,默认是5秒
APP_INCLUDE:需要自动载入PHP文件路径,Worker需要知道你的Job的位置并载入Job
COUNT:需要创建的Worker的数量。所有的Worker都具有相同的属性。默认是创建1个Worker
REDIS_BACKEND:Redis服务器的地址,使用 hostname:port 的格式,如127.0.0.1:6379,或localhost:6379。默认是localhost:6379
REDIS_BACKEND_DB:使用的Redis数据库的名称,默认是0
VERBOSE:啰嗦模式,设置“1”为启用,会输出基本的调试信息
VVERBOSE:设置“1”启用更啰嗦模式,会输出详细的调试信息
PREFIX:前缀。在Redis数据库中为队列的KEY添加前缀,以方便多个Worker运行在同一个Redis数据库中方便区分。默认为空
PIDFILE:手动指定PID文件的位置,适用于单Worker运行方式
以上参数中只有QUEUE是必须的。如果让Worker监视执行多个队列,可以用逗号隔开多个队列的名称,如:”queue1,queue2,queue3”,队列执行是有顺序的,如上queue2和queue3总是会在queue1后面被执行。

也可以设置QUEUE为*让Worker以字母顺序执行所有的队列。

Worker 必须以CLI方式启动。你不可以从浏览器启动Worker,因为:

你无法从浏览器执行后台任务
PCNTL扩展只能运行在CLI模式
启动Worker

可以从resque.php启动Worker,这个位置位于php-resque/bin目录下(也可能不带.php后缀)。

在终端中执行:
cd /path/to/php-resque/bin/
php resque.php
很显然Worker不会被启动,因为缺少必须的参数QUEUE,程序将会返回如下错误:
Set QUEUE env var containing the list of queues to work.
php-resque通过getenv获取参数,所以在启动Worker的时候应该传递环境变量过去。所以应该以下面的方式启动Worker:
QUEUE=notification php resque.php
如果启用VVERBOSE模式:
QUEUE=notification VVERBOSE=1 php resque.php
终端将会输出:


*** Starting worker KAMISAMA-MAC.local:84499:notification
** [23:48:18 2012-10-11] Registered signals
** [23:48:18 2012-10-11] Checking achievement
** [23:48:18 2012-10-11] Checking notification
** [23:48:18 2012-10-11] Sleeping for 5
** [23:48:23 2012-10-11] Checking achievement
** [23:48:23 2012-10-11] Checking notification
** [23:48:23 2012-10-11] Sleeping for 5
... etc ...
Worker会自动被命名为KAMISAMA-MAC.local:84499:notification,命名的规则是hostname:process-id:queue-names。

如果觉得这种启动方式太麻烦且难记,可以自己手动写一个bash脚本来帮助你启动Resque,如:


EXPORT QUEUE=notifacation
EXPORT VERBOSE=1
php resque.php
后台运行Worker

通过上面的方法成功启动了Worker,但只有在终端开启的状态下,关闭终端或按下Ctrl+C时Worker就会停止运行。我们可以在命令后面添加一个&来使其后台运行。


QUEUE=notification php resque.php &
这样就可以让resque在后台运行。但如果你开启了VERBOSE模式,所有的输出信息将会丢失。所以我们需要在resque后台运行时把输出的信息保存起来。

我们可以使用nohup来保持resque后台运行,即使是在用户登出后。


nohup QUEUE=notification php resque.php &
记录下Worker的输出

可以使用管道操作的方式重定向输出到文件:


nohup QUEUE=notification php resque.php >> /path/to/your/logfile.log 2>&1 &
这样一来所有的标准及错误输出都会被写入到logfile.log文件中。如果需要监视这个文件的内容:


tail -F /path/to/your/logfile.log
Worker的执行权限

无论何时你在终端中执行命令都是以当前登录用户的权限来执行。如果你登录的jerry的账户,php-resque将会运行于jerry的权限下。以root用户登录时也一样。

如果需要避开当前登录账户以其它用户的权限运行,如Apache通常运行在www-data用户下,让php-resque运行于www-data账户:


nohup sudo -u www-data QUEUE=notification php resque.php >> /path/to/your/logfile.log 2>&1 &
操作执行权限时需要注意:

通过Worker生成的文件无法被其它用户的php代码读取
Worker没有权限创建或编辑其它用户的文件
Let’s play

前面已经讲了如何启动、如何后台运行、以及记录运行日志,下面就用一些例子结束本节的内容。

创建一个执行default队列的Worker,并且每隔10秒检索一次任务:


INTERVAL=10 QUEUE=default php resque.php
创建5个执行default队列的Worker,每隔5秒检索一次任务:


QUEUE=default COUNT=5 php resque.php
INTERVAL参数没有被指定,因为默认值是5秒。

创建一个执行achievement和notification队列的Worker(需要注意队列名的顺序):


QUEUE=achievement,notification php resque.php
创建一个执行所有队列的Worker:


QUEUE=* php resque.php
如果你的Redis服务器在别的地址:


QUEUE=default REDIS_BACKENT=192.168.1.56:6380 php resque.php
使用自动载入php文件:


QUEUE=default APP_INCLUDE=/path/to/autoloader.php php resque.php
确认你的Worker成功运行了

通过管道操作无法知道Worker是否成功启动,当前通过查看log文件中有没有输出*** Starting worker .....的内容也可以知道是否启动。

也可以通过查看系统进程的方法确认Worker是否正在运行。


ps -ef|grep resque.php
将会输出名称中包含resque.php的进程,其中第二列是进程的PID。

使用这个方法可以很好的知道Worker是否正在运行,以及有没有意外终止。

暂停和停止Worker

要停止一个Worker,直接kill掉它的进程就行了。可以通过ps -ef|grep resque.php查看Worker进程的PID。当然通过这个命令你无法知道哪个PID代码的哪个Worker。

如果要结束一个PID是86681的进程:


kill 86681
这个命令将会立即结束掉PID为86681的进程及子进程。如果Worker正在执行一个任务也不会等待任务执行完成(未完成的部分将会丢失)。

有一个可以平滑的停止Worker的方法,可以通过给kill命令发送一个SIGSPEC信号来告诉kill应该怎么做,这需要PCNTL扩展的支持。

当然下面所讲述的所有命令都需要PCNTL扩展支持。

通过PCNTL扩展,Worker可以支持以下信号:

QUIT - 等待子进程结束后再结束
TERM / INT - 立即结束子进程并退出
USR1 - 立即结束子进程,但不退出
USR2 - 暂停Worker,不会再执行新任务
CONT - 继续运行Worker
当没有信号发出时默认是TERM / INT信号。

如果想在所有当前正在运行的任务都完成后再停止,使用QUIT信号:


kill -QUIT YOUR-WORKER-PID
结束所有子进程,但保留Worker:


kill -USR1 YOUR-WORKER-PID
暂停和继续执行Worker:

后台任务和PHP-Resque的使用(五) 创建任务

到目前为止已经让Worker运行了,我们需要创建并添加任务。这一节主要了解什么是任务(Job),以及如何使用任务。

简单的说,任务就是传递给Worker要执行的内容。我们需要把Job依次添加到Queue来执行。

要把任务添加到队列,程序必须要包含php-resque库以及Redis。

使用require_once '/path/to/php-resque/lib/Resque.php';包含php-resque的库文件,它会自动连接到Redis服务器,如果你的Redis服务器不是默认的localhost:6379,你需要使用Resque::setBackent('192.168.1.56:3680');这样的格式来设置你的Redis服务器的地址,同样setBackent支持可选的第二个参数为使用的Redis数据库名,默认为0。

现在php-resque已经准备好了,使用以下代码添加一个任务到队列:

 

1

 

Resque::enqueue('default', 'Mail', array('dest@mail.com', 'hi!', 'this is a test content'));

  • 第一个参数,’default’是指队列的名字,示例中将会把任务推送到名为default的队列中
  • 第二个参数是Job的类名,表示要执行哪个Job
  • 第三个参数是要发送给Job的参数也可以使用关联数组的形式

传递给Job的参数(上面第三个参数)可以是普通数组、关联数组的形式,也可以是一个字符串,但使用数组可以很方便的传递更多的信息给Job。所有的参数在推送到队列前都会经过json_encode处理。

创建一个Job

如上面的例子中,第一个参数是队列的名字(还记得上一节里面启动php resque.php时传递的QUEUE环境变量吗?),第二个参数是Job的类名,即要执行的Job。Mail类就是一个Job类。

所有的Job类都应该包含一个perform()方法,使用Resque::enqueue()传递的第三个参数可以在perform()方法中使用$this->args来得到。一个典型的Job类如下所示:

 

1

2

3

4

5

 

class Mail{

public function perform(){

var_dump($this->args);

}

}

Job类也可以包含setUp()tearDown()方法,可选的这两个方法分别会在perform()方法之前和之后运行。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

 

class Mail{

public function setUp(){

# 这个方法会在perform()之前运行,可以用来做一些初始化工作

# 如连接数据库、处理参数等

}

public function perform(){

# 执行Job

}

public function tearDown(){

# 会在perform()之后运行,可以用来做一些清理工作

}

}

包含Job类

在实例化Job类之前,必须让Worker找到并包含这个类。有很多种方法可以做到。

使用include_path

当PHP运行于Apache model方式的时候可以使用.htaccess设置包含:

 

1

 

php_value include_path ".:/already/existing/path:/path/to/job-classes"

或者通过php.ini

 

1

 

include_path = ".:/php/includes:/path/to/job-classes"

使用APP_INCLUDE包含

上一节说了使用APP_INCLUDE指定Worker执行时要包含的PHP文件的路径,如:

 

1

 

QUEUE=default APP_INCLUDE=/path/to/loader.php php resque.php

loader.php的内容可以是下面的那样:

 

1

2

3

4

 

include '/path/to/Mail.php';

include '/path/to/AnotherJobClass.php';

include '/path/to/somewhere/AnotherJobClass.php';

include '/JobClass.php';

当然也可以使用PHP的autoloader方法——sql_autoloader

在你的项目中使用后台任务

以下面的代码为例,把耗时较多的工作交给后台任务来做。

 

1

2

3

4

5

6

7

8

9

10

11

12

 

class User{

# functions(){} // 其它函数

public function updateLocation($location) {

$db->updateUserTable($this->userId, 'location', $location);

$this->recomputeNewFriends(); # 此操作耗时较长

}

public function recomputeNewFriends() {

# 查找新的朋友

}

}

把以上代码改成:

 

1

2

3

4

5

6

7

8

9

10

11

 

class User {

# functions(){} // 其它函数

public function updateLocation($location) {

$db->updateUserTable($this->userId, 'location', $location);

# 把任务添加到队列

# 这里的队列名为 'queueName'

# 任务名为 'FriendRecommendator'

Resque::enqueue('queueName', 'FriendRecommendator', array('id' => $this->userId));

}

}

以下是任务FriendRecommendator类的实现代码:

 

1

2

3

4

5

6

7

 

class FriendRecommendator {

function perform() {

# 这里没有User类,需要创建一个User类对象

$user = new User($this->args['id']);

# 查找新朋友的操作

}

}

简单的说,你只需要把你的执行任务的代码放到Job类中并改名为perform()即可,只要你愿意甚至可以将普通类改成Job类,但并不推荐这样做。

perform()方法有个缺点,即一个Job类只能包含一个perform()方法,也就是说一个Job类只能执行一种后台任务。例如你有一个发送通知信息的后台任务,但又有发送给用户和发送给管理员两个不同的需求,一般来说就得需要两个Job类才能实现。不过这里有个小小的Hack可以使一个Job能执行多个类型的任务。

首先就是给你的Job分类,把相似工作的Job放在同一个Job类中,因为完全不相关的Job即使放在同一个类中也没有任何意义。然后通过给Resque::enqueue()方法传递一个表示不同Job的参数过去。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

 

# Job类中的写法

class Notification{

function sentToUser(){

# Code..

}

function sentToAdmin{

# code..

}

function perform(){

$action = $this->{array_shift($this->args)};

if(method_exists($this, $action)){

$this->$action();

}

}

}

# 添加任务时的写法

Resque::enqueue('default', 'Notification', array('sendToAdmin', 'this is content'));

也可以使用其它类继承Job类以获取相同的perform()方法,但要注意必须同时包含这些类文件。

另外需要注意的是使用这种Hack的方法Resque::enqueue()的第三个参数必须是一个数组,并且它的第一个元素是要执行的任务的方法名,并且这个元素会在执行时从$args数组中移除。

必须在每次修改Job类后重新启动你的Worker

转载于:https://my.oschina.net/u/591525/blog/729108

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值