php-resque操作详解

php-resque中的角色:

  • Job,需要在后台处理的任务。php-resque将任务抽象成PHP的类。每一个任务都是一个PHP类的对象。
  • Queue, 消息队列,php-resque将任务以List的形式保存。
  • Work, 消息处理者。php-resque 以daemon的形式在后台运行。

php-resque的流程:

Created with Raphaël 2.1.0 Start enqueue job,将Job类名及参数加入队列 php queue.php开启Worker,并指定Worker需要处理的任务。 Worker以daemon运行在后台,实时监控是否有需要处理的任务。 End

enqueue, 将Job插入到队列

当调用Resque::enqueue后,php-resque会执行下列操作

  1. enqueue方法内部调用Resque_Job::create()方法,将接收到的参数传递到create()。
  2. Resque_Job::create()方法检查参数列表中的$args(Resqueue::enqueue()的第三个参数)为空,还是是个数组。
  3. Resque_Job::create()声称job ID(大多数文章称之为"token")。
  4. Resque_Job::create()将job插入制定的队列(Resqueue::enqueue()的第一个参数)。
  5. 如果Resqueue::enqueue()指定了监控job状态(第四个参数),Resque_Job::create()调用Resque_Job_Status::create()方法,job ID作为参数。
  6. Resque_Job_Status::create()在Redis中生成一个key,key name中包含job ID,当前的status为这个key的value和一对timestamp,然后将控制权交还给Resque_Job::create()
  7. Resque_Job::create()将控制权交还给Resque::enqueue(),并返回job ID。
  8. Resque::enqueue() 出发afterEnqueue事件,将控制权交还给应用,返回job ID。

Worker的工作

Worker是怎么处理队列的呢?

  1. Resque_Worker::work(), worker主进程,调用Resque_Worker->reserve()方法来查看是否有待处理任务。
  2. Resque_Worker->reserve() 方法检查是否使用block pop从队列取出任务然后做出相应的处理:
    • Blocking Pop
      • Resque_Worker->reserve()调用 Resque_Job::reserveBlocking() 方法,整个队列和timeout作为参数。
      • Resque_Job::reserveBlocking() 调用 Resque::blpop() (这个方法调用Redis的blpop命令, 队列做好blpop的准备后,最终返回控制权[包括队列和检索的内容]给Resque_Job::reserveBlocking()之前, 处理跟这个lib其他方面关于一致性方面的回应。
      • Resque_Job::reserveBlocking()检查任务内容是否是一个数组(数组中包含任务的类型(class),参数,和job ID), 是否中断处理。
      • Resque_Job::reserveBlocking() 创建一个新的Resque任务对象返回给Resque_Worker->reserve(), 实例话对象时利用队列和内容作为参数来初始化任务。
    • 队列轮询
      • Resque_Worker->reserve() 迭代整个队列,调用Resque_job::reserve()iterates through the queue list, calling Resque_Job::reserve() with the current queue’s name as the sole argument on each pass
      • Resque_Job::reserve() passes the queue name on to Resque::pop(), which in turn calls Redis’ lpop with the same argument, then returns control (and the job content, if any) to Resque_Job::reserve()
      • Resque_Job::reserve() checks whether the job content is an array (as before, it should contain the job’s type [class], payload [args], and ID), and aborts processing if not
      • Resque_Job::reserve() creates a new Resque_Job object in the same manner as above, and also returns this object (along with control of the process) to Resque_Worker->reserve()
  3. In either case, Resque_Worker->reserve() returns the new Resque_Job object, along with control, up to Resque_Worker::work(); if no job is found, it simply returns FALSE
    • No Jobs
      • If blocking mode is not enabled, Resque_Worker::work() sleeps for INTERVAL seconds; it calls usleep() for this, so fractional seconds are supported
    • Job Reserved
      • Resque_Worker::work() triggers a beforeFork event
      • Resque_Worker::work() calls Resque_Worker->workingOn() with the new Resque_Job object as its argument
      • Resque_Worker->workingOn() does some reference assignments to help keep track of the worker/job relationship, then updates the job status from WAITING to RUNNING
      • Resque_Worker->workingOn() stores the new Resque_Job object’s payload in a Redis key associated to the worker itself (this is to prevent the job from being lost indefinitely, but does rely on that PID never being allocated on that host to a different worker process), then returns control to Resque_Worker::work()
      • Resque_Worker::work() forks a child process to run the actual perform()
      • The next steps differ between the worker and the child, now running in separate processes:
        • Worker
          1. The worker waits for the job process to complete
            If the exit status is not 0, the worker calls Resque_Job->fail() with a Resque_Job_DirtyExitException as its only argument.
          2. Resque_Job->fail() triggers an onFailure event
            Resque_Job->fail() updates the job status from RUNNING to FAILED
          3. Resque_Job->fail() calls Resque_Failure::create() with the job payload, the Resque_Job_DirtyExitException, the internal ID of the worker, and the queue name as arguments
          4. Resque_Failure::create() creates a new object of whatever type has been set as the Resque_Failure “backend” handler; by default, this is a Resque_Failure_Redis object, whose constructor simply collects the data passed into Resque_Failure::create() and pushes it into Redis in the failed queue
          5. Resque_Job->fail() increments two failure counters in Redis: one for a total count, and one for the worker
          6. Resque_Job->fail() returns control to the worker (still in Resque_Worker::work()) without a value
      • Job
        1. The job calls Resque_Worker->perform() with the Resque_Job as its only argument.
        2. Resque_Worker->perform() sets up a try…catch block so it can properly handle exceptions by marking jobs as failed (by calling Resque_Job->fail(), as above)
        3. Inside the try…catch, Resque_Worker->perform() triggers an afterFork event
        4. Still inside the try…catch, Resque_Worker->perform() calls Resque_Job->perform() with no arguments
        5. Resque_Job->perform() calls Resque_Job->getInstance() with no arguments
        6. If Resque_Job->getInstance() has already been called, it returns the existing instance; otherwise:
        7. Resque_Job->getInstance() checks that the job’s class (type) exists and has a perform() method; if not, in either case, it throws an exception which will be caught by Resque_Worker->perform()
        8. Resque_Job->getInstance() creates an instance of the job’s class, and initializes it with a reference to the Resque_Job itself, the job’s arguments (which it gets by calling Resque_Job->getArguments(), which in turn simply returns the value of args[0], or an empty array if no arguments were passed), and the queue name
        9. Resque_Job->getInstance() returns control, along with the job class instance, to Resque_Job->perform()
        10. Resque_Job->perform() sets up its own try…catch block to handle Resque_Job_DontPerform exceptions; any other exceptions are passed up to Resque_Worker->perform()
        11. Resque_Job->perform() triggers a beforePerform event
        12. Resque_Job->perform() calls setUp() on the instance, if it exists
        13. Resque_Job->perform() calls perform() on the instance
        14. Resque_Job->perform() calls tearDown() on the instance, if it exists
        15. Resque_Job->perform() triggers an afterPerform event
        16. The try…catch block ends, suppressing Resque_Job_DontPerform exceptions by returning control, and the value FALSE, to Resque_Worker->perform(); any other situation returns the value TRUE along with control, instead
        17. The try…catch block in Resque_Worker->perform() ends
        18. Resque_Worker->perform() updates the job status from RUNNING to COMPLETE, then returns control, with no value, to the worker (again still in Resque_Worker::work())
        19. Resque_Worker::work() calls exit(0) to terminate the job process cleanly
      • SPECIAL CASE: Non-forking OS (Windows)
        1. Same as the job above, except it doesn’t call exit(0) when done
    • Resque_Worker::work() calls Resque_Worker->doneWorking() with no arguments
    • Resque_Worker->doneWorking() increments two processed counters in Redis: one for a total count, and one for the worker
    • Resque_Worker->doneWorking() deletes the Redis key set in Resque_Worker->workingOn(), then returns control, with no value, to Resque_Worker::work()
  4. Resque_Worker::work() returns control to the beginning of the main loop, where it will wait for the next job to become available, and start this process all over again
ThinkPHP3.2 集成 php-resque: PHP Resque Worker =========================================== php-resquephp环境中一个轻量级的队列服务。具体队列服务是做什么用的,请自行百度! ## 运行环境 ## * PHP 5.2+ * Redis 2.2+ ## 集成方法 ## ### 将源码放到ThinkPHP的Vendor目录中 ### 将源码更新到 ThinkPHP/Library/Vendor/php-resque/ 目录中 <font color=red>注意要定义应用目录,之前发的内容没定义应用目录导致部分小伙伴引发了找不到Queue类的异常</font> ### 在项目根目录中创建resque入口脚本 ### #!/usr/bin/env php <?php ini_set('display_errors', true); error_reporting(E_ERROR); set_time_limit(0); // 定义应用目录 define('APP_PATH','./Application/'); define('MODE_NAME', 'cli'); // 自定义cli模式 define('BIND_MODULE', 'Home'); // 绑定到Home模块 define('BIND_CONTROLLER', 'Queue'); // 绑定到Queue控制器 define('BIND_ACTION', 'index'); // 绑定到index方法 // 处理自定义参数 $act = isset($argv[1]) ? $argv[1] : 'start'; putenv("Q_ACTION={$act}"); putenv("Q_ARGV=" . json_encode($argv)); require './ThinkPHP/ThinkPHP.php'; ### 创建Queue控制器 ### 在`Home`模块的`Controller`中创建`Queue`控制器 <?php namespace Home\\\\\\\\Controller; if (!IS_CLI) die('The file can only be run in cli mode!'); use Exception; use Resque; /*** * queue入口 * Class Worker * @package Common\\\\\\\\Controller */ class QueueController { protected $vendor; protected $args = []; protected $keys = []; protected $queues = '*'; public function __construct() { vendor('php-resque.autoload'); $argv = json_decode(getenv('Q_ARGV')); foreach ($argv as $item) { if (strpos($item, '=')) { list($key, $val) = explode('=', $item); } else { $key = $val = $item; } $this->keys[] = $key; $this->args[$key] = $val; } $this->init(); } /** * 执行队列 * 环境变量参数值: * --queue|QUEUE: 需要执行的队列的名字 * --interval|INTERVAL:在队列中循环的间隔时间,即完成一个任务后的等待时间,默认是5秒 * --app|APP_INCLUDE:需要自动载入PHP文件路径,Worker需要知道你的Job的位置并载入Job * --count|COUNT:需要创建的Worker的数量。所有的Worker都具有相同的属性。默认是创建1个Worker * --debug|VVERBOSE:设置“1”启用更啰嗦模式,会输出详细的调试信息 * --pid|PIDFILE:手动指定PID文件的位置,适用于单Worker运行方式 */ private function init() { $is_sington = false; //是否单例运行,单例运行会在tmp目录下建立一个唯一的PID // 根据参数设置QUEUE环境变量 $QUEUE = in_array('--queue', $this->keys) ? $this->args['--queue'] : '*'; if (empty($QUEUE)) { die("Set QUEUE env var containing the list of queues to work.\n"); } $this->queues = explode(',', $QUEUE); // 根据参数设置INTERVAL环境变量 $interval = in_array('--interval', $this->keys) ? $this->args['--interval'] : 5; putenv("INTERVAL={$interval}"); // 根据参数设置COUNT环境变量 $count = in_array('--count', $this->keys) ? $this->args['--count'] : 1; putenv("COUNT={$count}"); // 根据参数设置APP_INCLUDE环境变量 $app = in_array('--app', $this->keys) ? $this->args['--app'] : ''; putenv("APP_INCLUDE={$app}"); // 根据参数设置PIDFILE环境变量 $pid = in_array('--pid', $this->keys) ? $this->args['--pid'] : ''; putenv("PIDFILE={$pid}"); // 根据参数设置VVERBOSE环境变量 $debug = in_array('--debug', $this->keys) ? $this->args['--debug'] : ''; putenv("VVERBOSE={$debug}"); } public function index() { $act = getenv('Q_ACTION'); switch ($act) { case 'stop': $this->stop(); break; case 'status': $this->status(); break; default: $this->start(); } } /** * 开始队列 */ public function start() { // 载入任务类 $path = COMMON_PATH . "Job"; $flag = \FilesystemIterator::KEY_AS_FILENAME; $glob = new \FilesystemIterator($path, $flag); foreach ($glob as $file) { if('php' === pathinfo($file, PATHINFO_EXTENSION)) require realpath($file); } $logLevel = 0; $LOGGING = getenv('LOGGING'); $VERBOSE = getenv('VERBOSE'); $VVERBOSE = getenv('VVERBOSE'); if (!empty($LOGGING) || !empty($VERBOSE)) { $logLevel = Resque\Worker::LOG_NORMAL; } else { if (!empty($VVERBOSE)) { $logLevel = Resque\Worker::LOG_VERBOSE; } } $APP_INCLUDE = getenv('APP_INCLUDE'); if ($APP_INCLUDE) { if (!file_exists($APP_INCLUDE)) { die('APP_INCLUDE (' . $APP_INCLUDE . ") does not exist.\n"); } require_once $APP_INCLUDE; } $interval = 5; $INTERVAL = getenv('INTERVAL'); if (!empty($INTERVAL)) { $interval = $INTERVAL; } $count = 1; $COUNT = getenv('COUNT'); if (!empty($COUNT) && $COUNT > 1) { $count = $COUNT; } if ($count > 1) { for ($i = 0; $i < $count; ++$i) { $pid = pcntl_fork(); if ($pid == -1) { die("Could not fork worker " . $i . "\n"); } // Child, start the worker else { if (!$pid) { $worker = new Resque\Worker($this->queues); $worker->logLevel = $logLevel; fwrite(STDOUT, '*** Starting worker ' . $worker . "\n"); $worker->work($interval); break; } } } } // Start a single worker else { $worker = new Resque\Worker($this->queues); $worker->logLevel = $logLevel; $PIDFILE = getenv('PIDFILE'); if ($PIDFILE) { file_put_contents($PIDFILE, getmypid()) or die('Could not write PID information to ' . $PIDFILE); } fwrite(STDOUT, '*** Starting worker ' . $worker . "\n"); $worker->work($interval); } } /** * 停止队列 */ public function stop() { $worker = new Resque\Worker($this->queues); $worker->shutdown(); } /** * 查看某个任务状态 */ public function status() { $id = in_array('--id', $this->keys) ? $this->args['--id'] : ''; $status = new \Resque\Job\Status($id); if (!$status->isTracking()) { die("Resque is not tracking the status of this job.\n"); } echo "Tracking status of " . $id . ". Press [break] to stop.\n\n"; while (true) { fwrite(STDOUT, "Status of " . $id . " is: " . $status->get() . "\n"); sleep(1); } } } ### 新增队列配置 ### 在公共`config.php`中新增队列配置,如下 /* 消息队列配置 */ 'QUEUE' => array( 'type' => 'redis', 'host' => '127.0.0.1', 'port' => '6379', 'persistent' => false, //是否启用 'prefix' => 'queue', 'password' => '', // 密码 ), ### 新增队列初始化行为 ### 在`app_init`行为中新增队列初始化的行为,`run`内容为 public function run() { // 处理队列配置 $config = C('QUEUE'); if ($config) { vendor('php-resque.autoload'); // 初始化队列服务 $select = isset($config['select']) ? $config['select'] : 0; $password = isset($config['password']) ? $config['password'] : null; $persistent = isset($config['persistent']) ? $config['persistent'] : false; $timeout = isset($config['timeout']) ? $config['timeout'] : 30; $server = $config['host'] . ":" . $config['port']; \Resque::setBackend($server, $select, $password, $persistent, $timeout); // 初始化缓存前缀 if(isset($config['prefix']) && !empty($config['prefix'])){ \Resque\Redis::prefix($config['prefix']); } } } 到此,整个队列服务基本已配置完成。 接下来就要创建队列执行的任务了 ## Jobs ## ### 创建 Jobs ### 目前任务类固定在`Common`模块的`Job`中,命名格式为`XxxxJob.class.php` <?php namespace Common\Job; class XxxxJob { public function perform() { $args = $this->args; fwrite(STDOUT, json_encode($args) . PHP_EOL); } } 要获取队列中传入的参数值请使用`$this->args` 任务perform方法中抛出的任何异常都会导致任务失败,所以在写任务业务时要小心,并且处理异常情况。 任务也有`setUp`和`tearDown`方法,如果定义了一个`setUp`方法,那么它将在`perform`方法之前调用,如果定义了一个`tearDown`方法,那么它将会在`perform`方法之后调用。 <?php namespace Common\Job; class XxxxJob { public function setUp() { // ... Set up environment for this job } public function perform() { // .. Run job } public function tearDown() { // ... Remove environment for this job } } ### 添加任务到队列中 ### 在程序控制器的任意方法中引入队列类库时,使用`Resque::enqueue`方法执行入栈,`Resque::enqueue`方法有四个参数,第一个是当前的队列名称,第二个参数为任务类,第三个是传入的参数,第四个表示是否返回工作状态的令牌 vendor('php-resque.autoload'); // 引入队列类库 $job = '\\Common\\Job\\XxxxJob'; // 定义任务类 // 定义参数 $args = array( 'time' => time(), 'array' => array( 'test' => 'test', ), ); // 入栈 $jobId = \Resque::enqueue('default', $job, $args, true); echo "Queued job ".$jobId."\n\n"; 如果要查看当前任务的工作状态可以使用如下方法: $status = new \Resque\Job\Status($jobId); echo $status->get(); // Outputs the status 任务的工作状态值有专门的常量``\Resque\Job\Status``对应类。 具体的对应关系如下: * `Resque\Job\Status::STATUS_WAITING` - 任务在队列中 * `Resque\Job\Status::STATUS_RUNNING` - 任务正在运行 * `Resque\Job\Status::STATUS_FAILED` - 任务执行失败 * `Resque\Job\Status::STATUS_COMPLETE` - 任务执行完成 * `false` - 无法获取状态 - 检查令牌是否有效? 任务的过期时间为任务完成后的24小时后,也可以定义过期类的`stop()`方法 ## 队列任务启动 ## 在命令行中转到项目根目录,执行 $ php resque start 即可启动服务 启动时也可以加入部分参数: * `--queue` - 需要执行的队列的名字,可以为空,也可以多个以`,`分割 * `--interval` -在队列中循环的间隔时间,即完成一个任务后的等待时间,默认是5秒 * `--count` - 需要创建的Worker的数量。所有的Worker都具有相同的属性。默认是创建1个Worker * `--debug` - 设置“1”启用更啰嗦模式,会输出详细的调试信息 * `--pid` - 手动指定PID文件的位置,适用于单Worker运行方式 如: $ php resque start --queue=default --pid=/tmp/resque.pid --debug=1 如果要使用守护进程方式启动则需要在最后加入`&`即可 如: $ php resque start --queue=default --pid=/tmp/resque.pid --debug=1 & 也可以配合supervisord实现进程长驻 更多的操作请参考php-resque官方文档。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值