PHP-Resque 简介

转载于:http://blog.hsatac.net/2012/01/php-resque-introduction/

Resque 是 Github 基於 Redis 开发的 background job 系统。相较其他肥大的 queue 系统, Resque 的设计真的非常单纯简洁,充分利用 Redis 的特性。更多介绍可以看原作者的 Blog

PHP-Resque 是把 Resque porting 到 PHP 的专案。使用和 原本 Resque 一样的概念和设计。甚至连 Redis 的 key 命名都一样,因此也可以使用 Ruby 版本的 resque-web 来监控 PHP-Resque 的运行状况。

设计

Resque 的设计有两个角色: Job 和 Worker。 每个 Job 都是定义成类别,新增 Job 的时候会将 Job 的类别和相关参数 json_encode 后储存到不同的 queue 裡面,而 Worker(s) 则会依序从 redis 读取 Job 出来执行。

执行的时候并不是这个 Worker 本身去执行,而是会 fork 一个 process 来执行。这样设计是為了避免时间一长, Worker 的记忆体管理不良导致卡死的状况。

读取 queue 时会依据你启动 worker 的时候给的 queue 顺序来读取,因此优先权较高的 queue 要设定在前面。 Redis 可以是单机或 RedisCluster。而许多不同伺服器上可以按需求部属执行不同 queue 的 worker。

Resque Job 执行失败并不会自动重试,而是把它丢到 fail 的 queue 裡面。如果你有重试的需求可能要自己处理。若是有特别重要的 Job 需要监控执行状态的,可以参考 README 中的 Tracking Job Statuses 一节。

以下先来介绍如何使用 PHP-Resque:

安装 PHP-Resque

安装非常容易,只要 git clone https://github.com/chrisboulton/php-resque.git 下来,放到你想要的地方,由於 Resque 没有 config 档的设计,设定都是写在环境变数中再执行就可以了。

环境变数

PHP-Resque 支援的环境变数有:

  • QUEUE – 这个是必要的,会决定 worker 要执行什麼任务,重要的在前,例如 QUEUE=notify,mail,log 。也可以设定為 QUEUE=* 表示执行所有任务。

  • APP_INCLUDE – 这也可以说是必要的,因為 Resque 的 Job 都是写成物件,那 worker 执行的时候当然要把物件的档案引入进来。可以设成 APP_INCLUDE=require.php 再在 require.php 中引入所有 Job 的 Class 档案即可。

  • COUNT – 设定 worker 数量,预设是1 COUNT=5

  • REDIS_BACKEND – 设定 Redis 的 ip, port。如果没设定,预设是连 localhost:6379

  • LOGGING, VERBOSE – 设定 log, VERBOSE=1 即可。

  • VVERBOSE – 比较详细的 log, VVERBOSE=1 debug 的时候可以开出来看。

  • INTERVAL – worker 检查 queue 的间隔,预设是五秒 INTERVAL=5

  • PIDFILE – 如果你是开单 worker,可以指定 PIDFILE 把 pid 写入,例如 PIDFILE=/var/run/resque.pid

有一个 Resque 支援,但 PHP-Resque 没有的参数叫 BACKGROUND 可以把 resque 丢到背景执行。不过这个其实不太重要,有需要的话自己加个 php resque.php & 就可以了。

所以,你的指令最后可能会变这样:

1
QUEUE=* APP_INCLUDE=require.php COUNT=5 VVERBOSE=1 php resque.php

如果觉得太长,可以写一支啟动 script 来辅助你,我有写一支可供参考:

#!/bin/bash
export APP_INCLUDE=resque/require.php
export QUEUE=*
export COUNT=1
export VVERBOSE=1 # for debugging
export REDIS_BACKEND=localhost:6379
. /etc/rc.d/init.d/functions
 
start() {
/usr/bin/php ../lib/sdk/php-resque/resque.php
}
 
stop() {
ps -ef | grep resque | grep -v grep | grep -v resque-web | awk '{print $2}' | xargs kill -15
}
kill() {
ps -ef | grep resque | grep -v grep | grep -v resque-web | awk '{print $2}' | xargs kill -9
}
case "$1" in
start)
number=$(ps aux | grep php-resque/resque.php | grep -v grep | wc -l)
if [ $number -gt 0 ]
then
echo "php-resque is running. ($number workers)"
echo "You may wanna stop them before you start."
else
start
fi
;;
 
stop)
stop
;;
 
kill)
kill
;;
 
status)
number=$(ps aux | grep php-resque/resque.php | grep -v grep | wc -l)
if [ $number -gt 0 ]
then
echo "php-resque is running. ($number workers)"
else
echo "php-resque is not running."
fi
;;
 
*)
echo -n "Usage: $0 {start|stop|status}"
esac

使用 PHP-Resque

把档案抓下来以后一定想先试验看看的,确定你的 redis-server 都有正常啟动后,在 demo 资料夹下面有几个档案可以先试验看看。

切到 demo 目录后,执行 VVERBOSE=1 QUEUE=* php resque.php 应该会看到 resque 已经开始执行了。

执行 php queue.php PHP_Jobphp queue.php Bad_PHP_Jobphp queue.php Long_PHP_Jobphp queue.php PHP_Error_Job 可以把工作丢进 queue 裡面,看看执行的结果。

后面带的名称其实就是 Job class 的名称,所以 PHP-Resque 在执行时也要把相关的 class 档案设定在 APP_INCLUDE 引入才行。

Job 的 class 很简单,大概长这样:

<?php
class My_Job
{
    public function perform()
    {
        // Work work work
        echo $this->args['name'];
    }
}
?>

只要定义 perform 方法, Worker 就会把 Job new 出来以后执行 perform 。

当然,也可以定义 setUp()tearDown() 方法,前者会在 perform() 执行前执行,后者会在 perform() 执行后执行。

需要注意的是,Job exit 后都视為正常执行,如果要让他判断失败丢到 fail queue 中的话,需要 throw exception。

将 Job 塞入 queue 的方式是:

<?php
require_once 'lib/Resque.php';

Resque::setBackend('localhost:6379');

$args = array(
    'name' => 'Chris'
);
Resque::enqueue('default', 'My_Job', $args);
?>

其中第一个参数 default 就是你的 queue 名称,例如你可以设定 notify, mail, image 之类,至於為什麼要这样设计,在后面的篇幅再叙述。

值得一提的是,在原 ruby 版 rescue 每个 Job 属於哪个 queue 是直接定义在 class 中的,PHP 版则是 enqueue 时才传入,不知道為什麼要这样设计,可能会导致一些意料之外的结果,需要注意。

PHP-Resque 的使用方法大致就是这样,接下来讲一些其他的小细节。

Hooks

PHP-Resque 可以定义 Event Hooks 让你能在相对应的事件发生时执行你想要的动作。支援的事件有很多,请各位自行参考原专案的 README。在专案目录下的 extra 目录下有 sample.plugin.php 可以看 Event hook 的范例写法。

有一点需要注意的是,很直觉我们会把这隻 sample.plugin.php 丢到 APP_INCLUDE 变数中,这样没错,但要注意跟 enqueue 有关的 event 并不是由 worker 来触发,因此你在新增 Job 的那段程式也需要引入 sample.plugin.php 才能触发到 AFTERENQUEUE

监控

resque-web

前面有提到可以直接使用 resque-web 来监控 PHP-Resque 的状态,相当建议使用,非常清楚易懂,要看 Redis 相关的数据也可以看,不用进 redis-cli 自己打指令。

安装方法:gem install resque

执行:resque-web -p 3000 即可运行在 3000 port。

首页有 live reload 按钮可以按, debug 时非常方便。

screenshot:

Supervisord

在专案的 extra 目录下另有 resque.monit 档案,这是供 Supervisord 使用的设定档。他会在 worker 吃掉 300MB 以上的记忆体,或者是跑了 10 次轮迴后砍掉重开。可以参考看看。

proctitle

如果你 ps | grep resque 只会看到 php resuqe.php 的讯息,还记得 Worker 会 fork 出一个 process 来执行 Job 吗?这样就有两倍的 processes 但是你完全分不出来哪个是哪个。原本 ruby 版本的设计是可以轻易看出 Worker fork 了哪个 process 而被 fork 出来的正在执行什麼工作。

Ruby 只要改 $0 就可以了, PHP 就没这麼简单了,要使用 setproctitle

但是这要安装 pecl 的 proctitle 模组才能使用。

安装方式:pecl install proctitle channel://pecl.php.net/proctitle-0.1.1

记得要去 php.ini 读进 .so 的 extension 。

完成后再执行 ps -e -o pid,command | grep [r]esque 时,就会从原本的

10486 php resque.php
10487 php resque.php

变成好读易懂的:

10621 resque-1.0: Waiting for notify,mail,image,default
10622 resque-1.0: Forked 10632 at 2012-01-16 13:42:55
10632 resque-1.0: Processing default since 2012-01-16 13:42:55

佈署

之前提到可以除了预设的 default 以外,还可以设定不同的 queue,為什麼要这样做呢?除了执行优先权外,(捞 queue 时会按你给 worker 的设定,在前面 queue 的会先捞,就会先执行到) 还有多伺服器佈署的原因。

假如今天你有个 queue 专门要处理使用者图片的东西,当然一般图片会有自己的伺服器。於是在你的主 web 伺服器上你就可以执行 QUEUE=notify,mail 而在图片伺服器上就可以执行 QUEUE=images 的 worker。

另外就是由於 Worker 啟动时已经将 APP_INCLUDE 的档案都读入,持续执行。因此如果有修改引入的 Job 或 hook plugin 等档案的话,deploy 时要将 worker 停止,重新啟动才会读入新的 APP_INCLULDE 档案。

已知问题

首先,PHP-Resque 使用的是 Redisent 这套 Redis interface。但因為和另一套 php module phpredis 同样都定义了 RedisException 这个类别,所以会衝突,必须把 phpredis 移除才能使用。

再来,在部属时常常 REDIS_BACKEND 是设到别台机器的,而且一般我们都会开不只一个 worker ,这时候有一个已知 issue 就是有时 lpop 拉回来的 Job 错误,是一个阵列,导致喷出 json_decode 的错误,而且这个 Job 就不会执行,会 missing 。 (see #32)

目前还不清楚确实问题所在,不过有一个 workaround 的解法是,不要用 COUNT=5 去开,而是设 COUNT=1 然后执行 5 次,就不会有这个问题產生。

结语

Resque 真的是一个很棒很轻巧的设计,感谢有人把它 porting 到 PHP 。希望越来越多人来使用,一起来发展维护 PHP-Resque。

原文地址:https://www.cnblogs.com/zl0372/p/PHP-Resque2.html