使用Beanstalk搭建隊列服務

使用Beanstalk搭建隊列服務

Beanstalkd介紹

一個高性能、輕量級的分佈式內存隊列系統。高性能離不開異步,異步離不開隊列,而其內部都是Producer-Consumer模式的原理。

組成部分

在這裏插入圖片描述

組件 說明
管道(tube) 一個有名稱的任務隊列,用來存儲統一類型的job,是producer和consumer的操作對象
任務(job) 一個需要異步處理的任務,需要放在tube中
生產者(producer) job的生產者,通過put命令來將一個job放到一個tube中
消費者(consumer) job的消費者,通過reserve、release、bury、delete命令來獲取job或改變job的狀態

特性

  • 優先級: 可以設置任務的優先級
  • 延遲: 設置任務多少秒後才允許被消費者使用
  • 持久化: 定時刷新數據到文件,服務器掛掉後數據依舊存在
  • 超時控制: 消費者必須在指定時間內完成任務,否則就會重新放入管道任務預留:消費者先暫時跳過任務不處理
  • 分佈式容錯: 分佈式設計和Memcached類似,beanstalkd各個server之間並不知道彼此的存在,都是通過client來實現分佈式以及根據tube名稱去特定server獲取job。

任務狀態

狀態 說明
ready 已經準備好的任務,可以給消費者獲取
delayed 延遲執行的任務,設置時候設置了延遲時間
reserved 已被消費者獲取,正在執行的任務,Beanstalkd服務負責檢查任務是否 在TTR(time-to-run)內完成
buried 保留的任務,任務不會被執行,也不會消失
delete 消息被徹底刪除,Beanstalkd不再維護這些消息

適用場景

  • 用作延時隊列: 比如可以用於如果用戶30分鐘內不操作,任務關閉。
  • 用作循環隊列: 用release命令可以循環執行任務,比如可以做負載均衡任務分發。
  • 用作兜底機制: 比如一個請求有失敗的概率,可以用Beanstalk不斷重試,設定超時時間,時間內嘗試到成功爲止。
  • 用作定時任務: 比如可以用於專門的後臺任務。
  • 用作異步操作: 這是所有消息隊列都最常用的,先將任務仍進去,順序執行。

服務安裝

  1. 下載beanstalkd-1.11
wget https://codeload.github.com/beanstalkd/beanstalkd/tar.gz/v1.11
  1. 安裝
tar xzvf beanstalkd-1.11.tar.gz
cd  beanstalkd-1.11
make & make install
beanstalkd -v
  1. 啓動服務
beanstalkd -l 0.0.0.0 -p 11300 -b /log/beanstalkd/binlog -F

隊列應用(PHP)

composer安裝 Pheanstalk 類庫

//PHP版本要求 7.1+
composer require pda/pheanstalk:~4.0

執行composer後,在項目composer.json配置文件中將增加pda/pheanstalk依賴包

"require": {
       "pda/pheanstalk": "~4.0"
}

Producer添加任務

//創建實例
$client = Pheanstalk::create($host, $port, $timeout);

//設置使用的tube,添加任務數據
//$data 任務數據
//$priority 任務優先級.小優先級數值的job將會排在大優先級 數值的job前面執行。
//最高優先級是0,最低優先級是4,294,967,295
//$delay 任務延遲執行秒數
//$ttr 允許一個消費者執行該job的秒數
$client->useTube($tube)->put($data, $priority, $delay, $ttr);

Consumer消費任務

ini_set('default_socket_timeout', 24*60*60);

$client = Pheanstalk::create($host, $port, $timeout);
$client->watchOnly($tube);
while (true) {    
       
    //阻塞獲取任務    
    $job = $client->reserve();   
    if (is_null($job)) {        
        continue;   
    }    
    //設置重新計算ttr
    $client->touch($job);    
    //獲取任務數據
    $data = $job->getData();
    
    //開始執行任務
    
    //任務執行邏輯
    $res = true
    
    //結束任務執行 
    
    //刪除任務    
    $client->delete($job); 
    
    if ($res === true) { 
        //任務執行成功,刪除任務
        $client->delete($job);    
    } else {        
        //否則將任務重新放回隊列
        $client->release($job, 1024, 10);   
    }
 }

default_socket_timeout 這個參數是一定要加的,php 默認一般是 60s,假如您沒有在代碼裏面設置,採用默認的話(60s),60s 之內如果沒有 job 產生,腳本就會報 socket 錯誤。

客戶端操作類

以下基於pda/pheanstalk依賴包實現的Beanstalk操作類,供參考。。

<?php

namespace App\Libs;
use Pheanstalk\Pheanstalk;

/**
 * Beanstalk工具類
 * @since 2020-02-26
 */
class Beanstalk {
    
	/**
	 * Beanstalk配置信息
	 * @var array
	 */
	protected $configs = [];
	
	/**
	 * client實例
	 * @var array
	 */
	protected $clients = [];
	
	/**
	 * 當前連接的服務端的配置名稱
	 * @var string
	 */
	protected $connection = 'default';
	
	/**
	 * 連接超時時間
	 * @var int
	 */
	protected $clientTimeOut = 3000;
	
	/**
	 * 初始化配置信息
	 * @param array $configs
	 * @return void
	 */
	public function __construct(array $configs = []) {
	    $this->configs = $configs;
	}

    /**
     * 添加任務
     * @param string $tube 隊列管道
     * @param array $parameters
     * @param int $priority 優先級
     * @param int $delay 延遲執行時間
     * @param int $ttr 任務超時時間
     * @return bool|mixed
     */
	public function addTask($tube, array $parameters,
                            $priority = Pheanstalk::DEFAULT_PRIORITY,
                            $delay = Pheanstalk::DEFAULT_DELAY,
                            $ttr = Pheanstalk::DEFAULT_TTR) {

        $client = $this->createClient();
        $stream = serialize($parameters);
        $client->useTube($tube)->put($stream, $priority, $delay, $ttr);
	}

    /**
     * 創建客戶端連接
     * @return mixed
     */
	public function createClient() {
	    
	    if (! isset($this->clients[$this->connection])) {
            $client = Pheanstalk::create($this->configs[$this->connection]['host'], $this->configs[$this->connection]['port'], $this->clientTimeOut);
	        $this->clients[$this->connection] = $client;
	    }
	    
	    return $this->clients[$this->connection];
	}

    /**
     * 創建後臺工作進程
     * @param $tube
     * @param array $service
     * @return mixed
     * @author huangweizhang
     * @throws \Exception
     */
	public function createWorker($tube, array $service) {

        ini_set('default_socket_timeout', 24*60*60);
        $client = Pheanstalk::create($this->configs[$this->connection]['host'], $this->configs[$this->connection]['port'], $this->clientTimeOut);
        $client->watchOnly($tube);
        while (true) {

            if (count($service) != 2) {
                throw new \Exception('parameter service error.');
            }

            list($classname, $method) = $service;

            if (! class_exists($classname)) {
                throw new \Exception('worker service class not exists.');
            }
            if (! method_exists($classname, $method)) {
                throw new \Exception('worker service method not exists.');
            }

            //獲取任務
            $job = $client->reserve();
            if (is_null($job)) {
                continue;
            }
            $client->touch($job);

            //獲取任務參數
            $stream = $job->getData();
            $parameters = unserialize($stream);

            try {
                //執行任務
                $class = new $classname();
                call_user_func_array(array($class, $method), array($parameters));
                unset($class);

                //刪除任務
                $client->delete($job);
            } catch (\Exception $e) {
                //任務執行失敗操作
            }
        }
	}
	
	/**
	 * 設置連接
	 * @param string $connection
	 */
	public function setConnection($connection) {
	    
	    $this->connection = $connection;
	}
	
	/**
	 * 設置客戶端連接超時時間
	 * @param int $clientTimeOut
	 */
	public function setClientTimeOut($clientTimeOut) {
	    
	    $this->clientTimeOut = $clientTimeOut;
	}
}


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