Chip框架消息隊列組件

簡介

目標: 使用PHP和Redis提供使用簡單而功能強大的消息隊列系統.

安裝

目前內嵌到Chip框架, 也可以獨立使用, 經過簡單的修改可與其他核心模塊完全解耦.

概述

使用說明

配置文件

在Chip裏的conf目錄下創建queue.ini, 根據自己的機器增加以下配置項:

[product]
[dev : product]
queue.redis.0.host = 127.0.0.1      ; 機器IP
queue.redis.0.port = 6379           ; 機器端口
queue.redis.0.db = 0                ; redis 數據庫
queue.redis.0.name = queue          ; 隊列名
queue.redis.0.distributed = 0       ; 是否開啓併發模式
queue.redis.1.host = 127.0.0.1
queue.redis.1.port = 6379
queue.redis.1.db = 0
queue.redis.1.name = queue
queue.redis.1.distributed = 0
[test : dev]

創建隊列

$queue = \Chip\Queue::getInstance();

自定義配置創建

如果不想使用默認配置文件裏的配置,可以自定義配置文件和配置索引

$queue = \Chip\Queue::getInstance('myself.ini', 'mytest');

創建任務

創建任務是將一個消息寫入一個有名字的管道, 下面管道名爲”mytube”

$data = array(
    'name' => 'kevin',
    'content' => array(
        'test'  => 'some string',
        'num' => null,
    ),
);
$queue = Queue::getInstance();
$queue->putInTube('mytube', $data);

 創建延時任務

延時任務是在入隊列後, 不會馬上被處理進程獲取, 在延遲指定時間後纔會被處理.

// 3600秒後纔會被處理
$option = array(
    'delay' => 3600, // 單位爲秒
);
$queue->putInTube('mytube', $data, $option);

創建定時任務

定時任務入隊列後, 在特定時間點纔會被處理進程獲取並處理.

// 在2015-10-29 23:34任務纔會被處理
$option = array(
    'timing' => '2015-10-29 23:34',
);
$queue->putInTube('mytube', $data, $option);

定時參數的格式遵循PHP的日期和時間格式 PHP date and time formats, 可以使用:

  • Next Monday
  • +1 days
  • last day of next month
  • 2013-09-13 00:00:00
  • and so on..

注意的是, 如果創建了一個過去時間的定時任務, 任務不會被丟棄, 而是會馬上被觸發.

 創建週期任務

週期任務可以代替非具體時間的週期性的crontab.

// 每3600秒被觸發一次,不會銷燬.
$option = array(
    'periodic' => 3600,
);
$queue->putInTube('mytube', $data, $option);

 創建失敗重試任務

處理的時候如果處理失敗後, 可以設定一個重試次數, 來重複嘗試處理這個任務:
這個參數可以與上面的時間控制的參數一起使用.

$option = array(
    'attempts' => 5,
);
$queue->putInTube('mytube', $data, $option);

 設定處理時長TTR

每個消息任務都有規定的最大運行時間, 默認是5分鐘, 超時後會被視爲失敗的任務.
可以在創建任務的時候指定處理最大時長:
這個參數可以與上面的時間控制的參數一起使用.

$option = array(
    'ttr' => 600, // 最大處理時長爲10分鐘
);
$queue->putInTube('mytube', $data, $option);

處理任務的時候, 如果時間過長, 也可以主動聲明處理時間:

set_time_limit(600);

或者使用touch來重新計算處理時間:(參考處理任務)

$job->touch();

處理任務

處理消息任務需要創建一個守護進程的腳本, 必須在CLI模式下執行, 這裏可以使用Chip裏的CLI-TASK:

class DaemonTask extends TaskBase
{
    /**
     * 創建守護進程, 需要在CLI模式下執行
     */
    public function indexAction()
    {
        $queue = Queue::getInstance();
        // 創建進程, 監聽'mytube'這個管道, 處理隊列任務
        $queue->doWork('mytube', function(Caster $job) {
            $data = $job->getBody();

            // process

            // 特別注意的一點, 匿名函數裏的程序是以子進程形式存在的
            // 如果正常處理完一個JOB後, 需要發送結束信號: exit(0)
            exit(0);
        });

    }
}

任務狀態調度控制

消息的狀態有四種: 準備,訂閱,延遲,失敗, 這些狀態的生命週期如下所示:

put with delay               release with delay
  ----------------> [DELAYED] <------------.
                        |                   |
                        | (time passes)     |
                        |                   |
   put                  v     reserve       |       delete
  -----------------> [READY] ---------> [RESERVED] --------> *poof*
                       ^  ^                |  |
                       |   \  release      |  |
                       |    `-------------'   |
                       |                      |
                       | kick                 |
                       |                      |
                       |       bury           |
                    [FAILED] <---------------'
                       |
                       |  delete
                        `--------> *poof*

處理進程中的任務處在訂閱狀態(RESERVED), 一般處理完後會自動delete, 有異常的話會bury到失敗隊列.
開發者也可以主動delete,release,或者bury這個任務:

$queue->doWork('mytube', function(Caster $job) {
    $data = $job->getBody();

    if ($data['name'] != 'kevin') {
        $job->bury();
    } else {
        $job->release(120); // 將任務回置爲延遲任務
    }

    exit(0);
});

處理失敗狀態的任務:

$queue = Queue::getInstance();
$ids = $queue->getIdsByState(Caster::STATE_FAILED, 'mytube');
foreach ($ids as $id) {
    $job = Caster::reload($id);
    $job->kick();  // 將失敗的任務重新放置到準備隊列裏
}

併發模式

多數情況,消息隊列保持”FIFO”(先進先出)的原則, 然而也有業務會用到分佈式場景.
分佈式處理消息會提高處理效率,爲了避免因併發讀取而出現的問題,需要將配置文件中distributed 設置爲1

...
queue.redis.1.distributed = 1

設計理念

利用Redis多種數據類型的特點,將每條隊列構建成如下的數據結構:

array(
    'id'           => null,
    'tube'         => null,
    'data'         => array(),
    'state'        => '',    // 任務狀態
    'created_at'   => '',
    'updated_at'   => '',
    'error'        => '',
    'failed_at'    => '',
    'timing'       => 0,    // 定時, 單位毫秒
    'attempts'     => 0,    // 嘗試次數計數器
    'max_attempts' => 3,    // 最大嘗試次數限制
    'retry_times'  => 0,    // 重新處理過的次數
    'periodic'     => 0,    // 週期任務生命週期時長, 單位秒
    'ttr'          => 30,    // 進程運行生命週期時長, 單位秒
);

每條任務會生成對應的Redis中Set、SortSet、Hash等數據類型用以流程控制:

queue:job:[id]              -> key, value       哈希存儲一個任務的業務數據
queue:tubes                 -> tube             集合存儲所有tube類型
queue:jobs                  -> id               有序集合存儲整個任務列表的次序
queue:jobs:[state]          -> id               有序集合存儲特定狀態任務列表的次序
queue:jobs:[tube]:[state]   -> id               有序集合存儲一個tube下的特定狀態的任務列表的次序


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章