php-resque消息隊列系統的使用

PHP-Resque的使用(一) 安裝php

第二部分咱們使用php-resque做爲隊列系統,這一節講如何安裝php-resque。html

PHP-Resque是依賴Redis的,因此須要先安裝Redis及PHP的Redis擴展。如下是全部須要安裝的組件:git

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

Redis

Redis是一個開源的KV數據庫,數據是保存在電腦RAM中的,速度很是快,因此一般可使用Redis來作緩存,或保存Session等。能夠在Redis的官方網站下載最新穩定版本。Redis的安裝方法本文再也不贅述,安裝完成後不要忘記啓動。github

PHP-Resque

php-resque是resque的PHP版本,不少特性都和原版類似或相同。redis

下載最新版本的zip壓縮包,或克隆它的倉庫:sql

git clone git://github.com/chrisboulton/php-resque.git數據庫


PHPredis以上下載的只是php-resque的庫,只須要把文件夾放在任何你項目須要的位置便可。也可使用Composer安裝php-resque。json

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

相關文章
相關標籤/搜索