zoukankan      html  css  js  c++  java
  • 利用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

  • 相关阅读:
    解析URL
    文件上传
    MyEclipse自动生成hibernate实体类和配置文件攻略
    <form>表单提交时注意
    W2UI /W2Toolbar的click响应事件
    JS 读写文件
    select 美化(bootstrap)
    安装MySQL for Windows 数据库
    java环境配置—配置Tomcat8环境
    对进程、线程、应用程序域的理解
  • 原文地址:https://www.cnblogs.com/wgwyanfs/p/7256967.html
Copyright © 2011-2022 走看看