thinkphp-queue 淺析

傳統的程序執行流程一般是 即時|同步|串行的,在某些場景下,會存在并發低,吞吐量低,響應時間長等問題。在大型系統中,一般會引入消息隊列的組件,將流程中部分任務抽離出來放入消息隊列,并由專門的消費者做針對性的處理,從而降低系統耦合度,提高系統性能和可用性。

一般來說,可以抽離的任務具有以下的特點:

  • 允許延后|異步|并行處理 (相對于傳統的 即時|同步|串行 的執行方式)
    • 允許延后
      搶購活動時,先快速緩沖有限的參與人數到消息隊列,后續再排隊處理實際的搶購業務;
    • 允許異步
      業務處理過程中的郵件,短信等通知
    • 允許并行
      用戶支付成功之后,郵件通知,微信通知,短信通知可以由多個不同的消費者并行執行,通知到達的時間不要求先后順序。
  • 允許失敗和重試
    • 強一致性的業務放入核心流程處理
    • 無一致性要求或最終一致即可的業務放入隊列處理

thinkphp-queue 是thinkphp 官方提供的一個消息隊列服務,它支持消息隊列的一些基本特性:

  • 消息的發布獲取執行刪除重發失敗處理延遲執行超時控制
  • 隊列的多隊列內存限制啟動停止守護
  • 消息隊列可降級為同步執行

thinkphp-queue 內置了 RedisDatabaseTopthinkSync這四種驅動。本文主要介紹 thinkphp-queue 結合其內置的 redis 驅動的使用方式和基本原理。

一、簡單使用示例
1.1 安裝 thinkphp-queue

composer install thinkphp-queue

1.2 搭建消息隊列的存儲環境
1.3 配置消息隊列的驅動

根據選擇的存儲方式,在\application\extra\queue.php這個配置文件中,添加消息隊列對應的驅動配置

return [
       'connector'  => 'Redis',         // Redis 驅動
       'expire'     => 60,              // 任務的過期時間,默認為60秒; 若要禁用,則設置為 null 
       'default'    => 'default',       // 默認的隊列名稱
       'host'       => '127.0.0.1',     // redis 主機ip
       'port'       => 6379,            // redis 端口
       'password'   => '',              // redis 密碼
       'select'     => 1,               // 使用哪一個 db,默認為 db0
       'timeout'    => 0,               // redis連接的超時時間
       'persistent' => false,           // 是否是長連接
     
   //    'connector' => 'Database',   // 數據庫驅動
   //    'expire'    => 60,           // 任務的過期時間,默認為60秒; 若要禁用,則設置為 null
   //    'default'   => 'default',    // 默認的隊列名稱
   //    'table'     => 'jobs',       // 存儲消息的表名,不帶前綴
   //    'dsn'       => [],

   //    'connector'   => 'Topthink',   // ThinkPHP內部的隊列通知服務平臺 ,本文不作介紹
   //    'token'       => '',
   //    'project_id'  => '',
   //    'protocol'    => 'https',
   //    'host'        => 'qns.topthink.com',
   //    'port'        => 443,
   //    'api_version' => 1,
   //    'max_retries' => 3,
   //    'default'     => 'default',

   //    'connector'   => 'Sync',       // Sync 驅動,該驅動的實際作用是取消消息隊列,還原為同步執行
   ];
1.4 消息的創建與推送

我們在業務控制器中創建一個新的消息,并推送到 helloJobQueue 隊列
新增 \application\index\controller\JobTest.php 控制器,在該控制器中添加 actionWithHelloJob 方法

<?php
namespace app\index\controller;
use think\Queue;
class JobTest
{
    /*
     * 測試隊列action
     * */
    public function actionWithHelloJob(){
        // 1.當前任務將由哪個類來負責處理。
        // 當輪到該任務時,系統將生成一個該類的實例,并調用其 fire 方法
        $jobHandlerClassName  = 'app\index\job\Hello@fire';
        // 2.當前任務歸屬的隊列名稱,如果為新隊列,會自動創建
        $jobQueueName     = "helloJobQueue";
        // 3.當前任務所需的業務數據 . 不能為 resource 類型,其他類型最終將轉化為json形式的字符串
        // ( jobData 為對象時,需要在先在此處手動序列化,否則只存儲其public屬性的鍵值對)
        $jobData          = [ 'name' => 'test'.rand(), 'password'=>rand()] ;
        // 4.將該任務推送到消息隊列,等待對應的消費者去執行
        $time2wait = strtotime('2018-09-08 11:15:00') - strtotime('now');  // 定時執行
        $isPushed = Queue::later($time2wait, $jobHandlerClassName , $jobData , $jobQueueName );
        // database 驅動時,返回值為 1|false  ;   redis 驅動時,返回值為 隨機字符串|false
        if( $isPushed !== false ){
            echo date('Y-m-d H:i:s') . " a new Hello Job is Pushed to the MQ"."<br>";
        }else{
            echo 'Oops, something went wrong.';
        }
    }
}
  • 除了 Queue::push( jobHandlerClassName , jobData , jobQueueName );這種方式之外,還可以直接傳入 Queue::push( jobHandlerObject ,null , jobQueueName ); 這時,需要在 jobHandlerObject 中定義一個 handle() 方法,消息隊列在執行到該任務時會自動反序列化該對象,并調用其 handle()方法。 該方式的缺點是無法傳入自定義數據。
1.5 消息的消費與刪除

編寫 Hello 消費者類,用于處理 helloJobQueue 隊列中的任務
新增 \application\index\job\Hello.php 消費者類,并編寫其 fire() 方法

<?php
/**
 * 文件路徑: \application\index\job\Hello.php
 * 這是一個消費者類,用于處理 helloJobQueue 隊列中的任務
 */
namespace app\index\job;
use think\queue\Job;
use think\Db;

class Hello {
    /**
     * fire方法是消息隊列默認調用的方法
     * @param Job            $job      當前的任務對象
     * @param array|mixed    $data     發布任務時自定義的數據
     */
    public function fire(Job $job,$data){
        // 如有必要,可以根據業務需求和數據庫中的最新數據,判斷該任務是否仍有必要執行.
        $isJobStillNeedToBeDone = $this->checkDatabaseToSeeIfJobNeedToBeDone($data);
        if(!$isJobStillNeedToBeDone){
            $job->delete();
            return;
        }

        $isJobDone = $this->doHelloJob($data);

        if ($isJobDone) {
            //如果任務執行成功, 記得刪除任務
            $job->delete();
        }else{
            if ($job->attempts() > 3) {
                //通過這個方法可以檢查這個任務已經重試了幾次了
                $job->delete();
                // 也可以重新發布這個任務
                //$job->release(2); //$delay為延遲時間,表示該任務延遲2秒后再執行
            }
        }
    }

    /**
     * 有些消息在到達消費者時,可能已經不再需要執行了
     * @param array|mixed    $data     發布任務時自定義的數據
     * @return boolean                 任務執行的結果
     */
    private function checkDatabaseToSeeIfJobNeedToBeDone($data){
        return true;
    }

    /**
     * 根據消息中的數據進行實際的業務處理
     * @param array|mixed    $data     發布任務時自定義的數據
     * @return boolean                 任務執行的結果
     */
    private function doHelloJob($data) {
        // 根據消息中的數據進行實際的業務處理...
        // test
        Db::name('admin')->insert([
            'name'=>$data['name'],
            'password'=>$data['password']
        ]);
        return true;
    }
}
1.6 發布任務

在瀏覽器中訪問 http://localhost/test/public/index.php/index/job_test/actionWithHelloJob 可以看到消息推送成功。

image.png

1.7 處理任務

切換當前終端窗口的目錄到項目根目錄下,執行
php think queue:work --queue helloJobQueue
可以看到執行的結果類似如下:

image.png

同時打開 redis 可視化工具 Redis Desktop Manager (ps:附上百度網盤下載鏈接:鏈接:https://pan.baidu.com/s/1-k79ZEGD77EtUQFolJosXA 密碼:xcfm)
image.png

  • 可見由于我設置了定時執行,所以隊列處于待執行狀態。當執行時間到了,隊列會自動執行。
至此,我們成功地經歷了一個消息的 創建 -> 推送 -> 消費 -> 刪除 的基本流程
二、詳細介紹

介紹 thinkphp-queue 的詳細使用方法。如配置介紹,基本原理,各種特殊情況的處理等
注:在此篇文章不做詳細說明,如需了解請參鑒
https://github.com/coolseven/notes/blob/master/thinkphp-queue/README.md

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,762評論 2 372

推薦閱讀更多精彩內容