Beanstalkd消息中間件入門及技術指南

初識Beanstalkd

          Beanstalk,一個高性能、輕量級的分佈式內存隊列系統,最初設計的目的是想通過後臺異步執行耗時的任務來降低高容量Web應用系統的頁面訪問延遲,支持過有9.5 million用戶的Facebook Causes應用。

參考:https://www.cnblogs.com/jkko123/p/8177731.html

安裝

# yum install beanstalkd

查看版本

# beanstalkd -v

啓動beanstalkd

# beanstalkd -l 127.0.0.1 -p 11300 -b /usr/local/beanstalkd/binlog &

-b表示開啓binlog,斷電後重啓自動恢復任務 。 /usr/local/beanstalkd/binlog爲自建的目錄。

關於系統自動啓動beanstalkd的方法,可以參考如下,

linux配置開機啓動進程的方法 https://blog.csdn.net/yan_dk/article/details/104193703

這樣就不用每次都重新啓動beanstalkd了。 

常用知識

一、Beanstalkd是什麼?

Beanstalkd是一個高性能,輕量級的分佈式內存隊列

二、Beanstalkd特性

1、支持優先級(支持任務插隊)
2、延遲(實現定時任務)
3、持久化(定時把內存中的數據刷到binlog日誌)
4、預留(把任務設置成預留,消費者無法取出任務,等某個合適時機再拿出來處理)
5、任務超時重發(消費者必須在指定時間內處理任務,如果沒有則認爲任務失敗,重新進入隊列)

三、Beanstalkd核心元素

生產者 -> 管道(tube) -> 任務(job) -> 消費者

Beanstalkd可以創建多個管道,管道里面存了很多任務,消費者從管道中取出任務進行處理。

job:一個需要異步處理的任務,需要放在一個tube中。

tube:一個有名字的任務隊列,用來存儲統一類型的job,可以創建多個管道

producer:job的生產者

consumer:job的消費者

簡單流程:由 producer 產生一個任務 job ,並將 job 推進到一個 tube 中,然後由 consumer 從 tube 中取出 job 執行

四、任務job狀態

delayed 延遲狀態,延遲執行的任務,任務到期後會自動成爲當前任務
ready 準備好狀態,可以被消費
reserved 消費者把任務讀出來,正在執行的任務
buried 預留狀態,保留的任務: 任務不會被執行,也不會消失,除非有人把它 "踢" 回隊列;將任務取出之後,發現後面執行的邏輯不成熟(比如發郵件,突然發現郵件服務器掛掉了), //或者說還不能執行後面的邏輯,需要把任務先封存起來,等待時機成熟了,再拿出這個任務進行消費

delete 刪除狀態,消息被徹底刪除。Beanstalkd 不再維持這些消息。

七、Pheanstalk使用方法

維護方法
stats() 查看狀態方法
listTubes() 目前存在的管道
listTubesWatched() 目前監聽的管道
statsTube() 管道的狀態
useTube() 指定使用的管道
statsJob() 查看任務的詳細信息
peek() 通過任務ID獲取任務


生產者方法

putInTube() 往管道中寫入數據
put() 配合useTube()使用
消費者方法

watch() 監聽管道,可以同時監聽多個管道
ignore() 不監聽管道
reserve() 以阻塞方式監聽管道,獲取任務
reserveFromTube()
release() 把任務重新放回管道
bury() 把任務預留
peekBuried() 把預留任務讀取出來
kickJob() 把buried狀態的任務設置成ready
kick() 批量把buried狀態的任務設置成ready
peekReady() 把準備好的任務讀取出來
peekDelayed() 把延遲的任務讀取出來
pauseTube() 給管道設置延遲
resumeTube() 取消管道延遲
touch() 讓任務重新計算ttr時間,給任務續命

 

示例

Beanstalk主要包括4個部分。

  1> job:一個需要異步處理的任務,需要放在一個tube中。

  2> tube:一個有名的任務隊列,用來存儲統一類型的job,是producer和consumer操作的對象。

  3> producer:job的生產者,通過put命令來將一個job放到一個tube中。

  4> consumer:job的消費者,通過reserve、release、bury、delete命令來獲取job或改變job的狀態。

我們可以準備composer環境來運行

composer技術可參見:https://mp.csdn.net/postedit/90228559

producer.php

<?php
require "vendor/autoload.php";
try {
//創建一個Pheanstalk對象
    $p = new \Pheanstalk\Pheanstalk("127.0.0.1", 11300);
//模擬任務數據
    $task_data = array('id' => 1, 'name' => 'task-' . date('Ymdhms', time()),);
//向業務任務管道中添加任務,返回任務ID
//put()方法有四個參數:1任務的數據 2任務的優先級,值越小,越先處理;3任務的延遲;4任務的ttr超時時間
    $id = $p->useTube('taskDemo')->put(json_encode($task_data));
//獲取任務
    $job = $p->peek($id);
    var_dump($job);
}catch (Exception $ex){
    var_dump('phbeanstalk生產任務出現異常錯誤=',$ex->getMessage());
}
var_dump('管道狀態=',$p->stats());//查看目前pheanStalkd狀態信息
var_dump('存在的管道=',$p->listTubes());//顯示目前存在的管道

consumer.php

<?php
require "vendor/autoload.php";
try {
//創建一個Pheanstalk對象
    $p = new \Pheanstalk\Pheanstalk("127.0.0.1", 11300);
    while (true){
        $job=$p->watchOnly("taskDemo")->reserve();
        if(!empty($job)){
            var_dump($job->getData());
        }
    }
}catch (Exception $ex){
    var_dump('phbeanstalk消費者任務出現異常錯誤=',$ex->getMessage());
}

1.啓動beanstakld

# beanstalkd -l 127.0.0.1 -p 11300 -b /usr/local/beanstalkd/binlog &

2.運行生產任務

#php producer.php

3.運行消費任務

#php consumer.php

beanstalkd+redis實現方法

可以用redis緩存任務狀態的方式實現。

#php consumer_redis.php

require "vendor/autoload.php";
//演示多個消費者可能同時獲取任務的流程,需要保證任務的同步處理(通過redis保存任務狀態信息)。
//1.任務可靠性問題,假設一個消費者獲取到任務,執行任務時,突然宕機了,導致任務不能正常執行,
//比如訂單付款後增加積分或餘額的任務,沒有被正常執行,那麼數據會不一致,問題就很嚴重,
//2.消費冪等性問題,也就是消費者的任務重複執行問題,如果一個任務被多個消費者重複執行,也會發生很嚴重問題,
//比如,訂單付款後庫存減少,如果被多次執行,會出現庫存不對的情況,
//消息的雙向確認,去重/重傳
//爲保證上述情況能正確處理,避免上述發生情況,我們可以將任務設置狀態,1任務正在執行中,2任務執行完成
$redis=new Predis\Client(['scheme'=>'tcp','host'=>"127.0.0.1",'port'=>6379]);
try {
//創建一個Pheanstalk對象
    $p = new \Pheanstalk\Pheanstalk("127.0.0.1", 11300);
    //從管道中取出任務
    //如果沒有數據會掛起等待數據,所以while不會進入死循環
    while (true){
        $job=$p->watchOnly("taskDemo")->reserve();//每隔3秒執行獲取一個管道任務
        if(!empty($job)){
            $data=json_decode($job->getData(),true);           $msg_id=$data['msg_id'];
            //假設某個消費者在接收任務時,突然宕機了,沒有及時消費任務,可以將任務分發給其他消費者
            $jobKey="job".$msg_id;
            $state=$redis->get($jobKey);
            //var_dump("job".$msg_id.';state='.$state);
            if($state==1){//有消費者正在執行當中
                //重新把任務放入到隊列,否則其他程序無法刪除
                var_dump("任務正在執行中-".$data['msg_id']);
                //階梯式重試
                $p->release($job,0,5);//延遲5秒繼續投遞任務
            }elseif($state==2){
                //$redis->set("job".$msg_id,2);
                var_dump("任務已經執行-".$data['msg_id']);
            }else{
                //假設設置的任務狀態正在執行,但設置完就崩潰或者任務執行失敗
                //設置任務狀態的生存期
                $redis->setex($jobKey,6,1);
//執行任務業務邏輯(發短信、分佣金、加積分)
                sleep(10);
                $redis->set($jobKey,2);
                var_dump($job->getData());
                $p->delete($job);////ack應答機制設計是爲了消息的可靠性
            }
        }
    }
}catch (Exception $ex){
    var_dump('phbeanstalk消費者任務出現異常錯誤='.$ex->getMessage());
}

beanstalkd+swoole實現方法

爲提高任務異步處理性能,我們可以使用swoole來處理。

#php consumer_swoole.php

<?php

require "vendor/autoload.php";

$workerNum = 4;
$pool = new Swoole\Process\Pool($workerNum);

$pool->on("WorkerStart", function ($pool, $workerId) {
    try {
//創建一個Pheanstalk對象
        $p = new \Pheanstalk\Pheanstalk("127.0.0.1", 11300);
        while (true) {
            $job = $p->watchOnly("taskDemo")->reserve();
            if (!empty($job)) {
                var_dump('workerid='.$workerId.";jobdata=".$job->getData());
                //極端情況,剛好取出數據,數據終止了任務沒有執行
               //我取出數據還沒有執行,程序出現了致命錯誤結束掉了
                //一般任務被reserve讀出後,業務處理完成時應及時進行delete操作,防止重新被放回管道
                $p->delete($job);
                //執行業務邏輯,發短信、加積分
            }
        }
    } catch (Exception $ex) {
        var_dump('phbeanstalk消費者任務出現異常錯誤=', $ex->getMessage());
    }
});

$pool->on("WorkerStop", function ($pool, $workerId) {
    echo "Worker#{$workerId} is stopped\n";
});
$pool->start();

 

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