利用redis和php-resque实现后台任务

在PHP的页面编程过程中,我们总遇到这样一个问题,即是PHP是一个顺序运行的过程,仅仅能在一个任务完毕后接着去实现下一个任务,而这当中存在一个问题,就是假如当中一个任务耗费大量时间的时候,我们可能就必须要等待。借助redis能够将耗时任务放到后台去运行,从而降低等待时间。

Redis 是一个高性能的key-value数据库。能够帮助我们有效的实现后台任务,将耗费大量时间的任务迁移到后台去运行,能够节约非常多的时间。

php-resque是来自Ruby的项目Resque的一个PHP扩展,正是由于Resque清晰简单的攻克了后台任务带来的一系列问题。

在Resque中后台任务的角色划分:
在Resque中,一个后台任务被抽象为由三种角色共同完毕:

Job | 任务 : 一个Job就是一个须要在后台完毕的任务,比方发送邮件。就能够抽象为一个Job。

在Resque中一个Job就是一个Class。

Queue | 队列 : 也就是上文的消息队列,在Resque中,队列则是由Redis实现的。Resque还提供了一个简单的队列管理器,能够实现将Job插入/取出队列等功能。

Worker | 运行者 : 负责从队列中取出Job并运行,能够以守护进程的方式运行在后台。

那么基于这个划分。一个后台任务在Resque下的基本流程是这种:

1、将一个后台任务编写为一个独立的Class,这个Class就是一个Job。
2、在须要使用后台程序的地方,系统将Job Class的名称以及所需參数放入队列。

3、以命令行方式开启一个Worker,并通过參数指定Worker所须要处理的队列。

4、Worker作为守护进程运行,而且定时检查队列。 5、当队列中有Job时。Worker取出Job并运行,即实例化Job Class并运行Class中的方法。

至此就能够完整的运行完一个后台任务。

在Resque中,另一个非常重要的设计:一个Worker。能够处理一个队列,也能够处理非常多个队列,而且能够通过添加Worker的进程/线程数来加快队列的运行速度。

注:本文中的安装等操作。均在Linux下完毕。

步骤一、php-resque的安装
此处可參阅:PHP的轻量消息队列php-resque使用说明

须要提前说明的是,由于涉及到进程的开辟与管理,php-resque使用了php的PCNTL函数,所以仅仅能在Linux下运行,而且须要php编译PCNTL函数。假设希望用Windows做相同的工作,那么能够去找找Resque的其他语言版本号。php在Windows下非常不适合做后台任务。

安装Redis

apt-get install redis-server

安装Composer

apt-get install curl
cd /usr/local/bin
curl -s http://getcomposer.org/installer | php
chmod a+x composer.phar
alias composer='/usr/local/bin/composer.phar'

使用Composer安装php-resque
假设web文件夹在/opt/htdocs

apt-get install git git-core
cd /opt/htdocs
git clone git://github.com/chrisboulton/php-resque.git
cd php-resque
composer install

至此php-resque就可以完毕,能够进行其使用。

步骤二:php-resque的使用

首先须要运行Worker。


此处可參阅:后台任务和PHP-Resque的使用介绍

1、理解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:Redisserver的地址。使用 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模式

2、启动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

3、后台运行Worker

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

QUEUE=notification php resque.php &

这样就能够让resque在后台运行。但假设你开启了VERBOSE模式。全部的输出信息将会丢失。所以我们须要在resque后台运行时把输出的信息保存起来。

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

nohup QUEUE=notification php resque.php &

4、确认你的Worker成功运行了

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

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

ps -ef|grep resque.php

将会输出名称中包括resque.php的进程。当中第二列是进程的PID。
使用这种方法能够非常好的知道Worker是否正在运行,以及有没有意外终止。

5、暂停和停止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:

kill -USR2 YOUR-WORKER-PID

kill -CONT YOUR-WORKER-PID

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

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

使用require_once '/path/to/php-resque/lib/Resque.php';包括php-resque的库文件,它会自己主动连接到Redisserver,假设你的Redisserver不是默认的localhost:6379,你须要使用Resque::setBackent('192.168.1.56:3680');这种格式来设置你的Redisserver的地址。相同setBackent支持可选的第二个參数为使用的Redis数据库名,默觉得0。

如今php-resque已经准备好了,使用下面代码加入一个任务到队列:

Resque::enqueue('default', 'Mail', array('dest@mail.com', 'hi!', 'this is a test content'));
第一个參数。’default’是指队列的名字(即上文中QUEUE后的參数)。演示样例中将会把任务推送到名为default的队列中
第二个參数是Job的类名,表示要运行哪个Job
第三个參数是要发送给Job的參数也能够使用关联数组的形式

传递给Job的參数(上面第三个參数)能够是普通数组、关联数组的形式。也能够是一个字符串,但使用数组能够非常方便的传递很多其他的信息给Job。

全部的參数在推送到队列前都会经过json_encode处理。

步骤三、Job类创建和使用:

1、编写一个Worker,创建job类。

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

全部的Job类都应该包括一个perform()方法,使用Resque::enqueue()传递的第三个參数能够在perform()方法中使用$this->args来得到。

class PHP_Job
{
    public function perform()
    {
        sleep(120);
        fwrite(STDOUT, 'Hello!');
    }
}

在Resque的设计中,一个Job必须存在一个perform方法,Worker则会自己主动运行这种方法。
Job类也能够包括setUp()和tearDown()方法,可选的这两个方法分别会在perform()方法之前和之后运行。

class Mail{
    public function setUp(){
        # 这种方法会在perform()之前运行,能够用来做一些初始化工作
        # 如连接数据库、处理參数等
    }

    public function perform(){
        # 运行Job
    }

    public function tearDown(){
        # 会在perform()之后运行,能够用来做一些清理工作
    }
}

2、包括Job类,将job插入队列。
在实例化Job类之前,必须让Worker找到并包括这个类。有非常多种方法能够做到。
(1)、使用include_path
当PHP运行于Apache model方式的时候能够使用.htaccess设置包括:

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

(2)、通过php.ini

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

(3)、使用APP_INCLUDE包括
上一节说了使用APP_INCLUDE指定Worker运行时要包括的PHP文件的路径。如:

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

loader.php的内容能够是下面的那样:(当中包括了全部的job类)

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

当然也能够使用PHP的autoloader方法——sql_autoloader。

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

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

class User{
    # functions(){}  // 其他函数

    public function updateLocation($location) {
        $db->updateUserTable($this->userId, 'location', $location);
        $this->recomputeNewFriends(); # 此操作耗时较长
    }

    public function recomputeNewFriends() {
        # 查找新的朋友
    }
}

把以上代码改成:

class User {
    # functions(){}  // 其他函数

    public function updateLocation($location) {
        $db->updateUserTable($this->userId, 'location', $location);
        # 把任务加入到队列
        # 这里的队列名为 'queueName'
        # 任务名为 'FriendRecommendator'
        Resque::enqueue('queueName', 'FriendRecommendator', array('id' => $this->userId));
    }
}

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

class FriendRecommendator {
    function perform() {
        # 这里没有User类,须要创建一个User类对象
        $user = new User($this->args['id']);
        # 查找新朋友的操作
    }
}

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

perform()方法有个缺点,即一个Job类仅仅能包括一个perform()方法,也就是说一个Job类仅仅能运行一种后台任务。

3、程序
此处可參阅:PHP的轻量消息队列php-resque使用说明

当中须要注意的几点:
Hack的方法Resque::enqueue()的第三个參数必须是一个数组。而且它的第一个元素是要运行的任务的方法名,而且这个元素会在运行时从$args数组中移除。

必须在每次改动Job类后又一次启动你的Worker。

本文综合下面几篇文章:
PHP的轻量消息队列php-resque使用说明
后台任务和PHP-Resque的使用介绍
Background jobs with php and resque

原文地址:https://www.cnblogs.com/wgwyanfs/p/7256967.html