rabbit以及php amqp擴展使用

一定要注意php安裝AMQP的版本,版本不同使用的方法不一樣。在官方網站就有2個版本的AMQP
第一版本:xxx,詳細的url找不到了
第二版本:http://docs.php.net/manual/da/book.amqp.php
千萬不要出現這種情況,找到一個官方的版本,然後按照example,怎麼調試都不通….按照PHP安裝 AMQP擴展 安裝的AMQP擴展是最新的,現在和PHP官方給出的第二版本,也有一些區別。主要體現在exchange和queue中有個declare的方法,分別更改成declarExchange()和declarQueue().

一、AMQPConnection

::__construct ([ array $credentials = array() ] )

這方法比較簡單,credentials的英文含義是“憑證”,但我喜歡把它理解爲config。這些配置包括如下:
host、vhost、port、login、password、read_timeout、write_timeout、connect_timeout,如果config省略,php會使用默認的這5個值,初始化,強烈建議帶上config初始化,其中read_timeout、write_timeout、connect_timeout這三個值是最新版本纔有的,PHP官方現在還沒有,單位都是(秒)

::connect ( void )  、  ::pconnect ( void )
::disconnect ( void )   、::pdisconnect ( void )
::reconnect ( void ) 、::preconnect ( void )  
 #注意和__construct 的區別,該方法不帶參數。
::isConnected ( void )
::isPersistent ( void ) 
#是否持久鏈接 ,爲什麼不是isPconnected ?-_-

如果使用持久鏈接,在類方法__destruct中釋放(斷開鏈接)

::setHost ( string $host )
::setPort ( int $port )
::setVhost ( string $vhost )
::setLogin ( string $login )
::setPassword ( string $password )
::getHost ( void )
::getPort ( void )
::getVhost ( void )
::getLogin ( void )
::getPassword ( void )
還有類似的::setTimeout ( int $timeout ) 、::getTime ( void )、::getReadTimeout ( void )、::setReadTimeout ( int $timeout )、::getWriteTimeout (void ) 、setWriteTimeout ( int $timeout)

上面的這些方法,就不細說了,還有2個不常見的方法

::getUsedChannels ( void )  #在鏈接會話期間最後一次使用的channel id
::getMaxChannels ( void )   #單次鏈接能創建最大channel數量

二、AMQPChannel

爲了一個AMQP的鏈接,創建一個channel instance(通道實例),這裏需要了解一下幾個概念,以及她們之間的關係,本人做了一個羅列,水平有限,不對的地方請指正。

client :客戶端Broker:英文含義是代理,這裏指代理服務器,也是通常說的server,也可以指Rabbit cluster的節點,
也就是在AMQPConnection的配置文件中的host、portvirtual_host:虛擬host,這個可以理解爲在Broker中,虛擬出來的消息處理模塊。一個Broker中
可以創建多個virtual_host.exchange:邏輯上屬於virtual_host的子模塊,負責消息轉發到queue中。queue:邏輯上屬於virtual_host的子模塊。channel:通道。鏈接virtual_host的通道。
routingkey:virtual_host和channel的設置都是爲了增加併發性能,降低資源消耗的。
參考文章:RabbitMQ中的AMQP協議規範

::__construct ( AMQPConnection $amqp_connection ) #創建channel
::isConnected ( void ) 
#這裏和AMQPConnection::isConnected一樣.

::getConnection ( void )
#返回的結果是AMQPConnection,從概念上個人理解,channel是建立在connection之上的,是個獨立的鏈接。connection是指物理的連接,一個client與一個server之間有一個連接;一個連接上可以建立多個channel,可以理解爲邏輯上的連接。一般應用的情況下,有一個channel就夠用了,不需要創建更多的channel

::getChannelId ( void )
#返回channel id,在PHP中循環創建通道,發現通道ID始終只有1和2,不知是否和服務器的設置有關?

::setPrefetchSize ( int $size )、::getPrefetchSize ( void )
這個地方有個坑,設置prefetchSize不等於0,會提示not_implemented: “prefetch_size!=0 (65535)”,也就是說目前prefetchSize只能設置爲0,還未實現不等於0的情況。

::setPrefetchCount ( int $count )、::getPrefetchCount ( void )
這個參數設置,接收消息端,接收的最大消息數量(包括使用get、consume),一旦到達這個數量,客戶端不在接收消息。0爲不限制。默認值爲3.

::qos ( int $size , int $count )
同時設置prefetchSize、prefetchCount,但prefetchSize目前只能設置爲0

 ::basicRecover ( boolen )
默認爲true,沒有確認的消息會被再次投遞,這個地方和消息確認機制有關,後面再來說這個問題。

三、 AMQPExchange

::__construct ( AMQPChannel $amqp_channel )

::setName ( string $exchange_name )、::getName ( void )
這個命令的最好使用數字、字母、- _ . 組合(區分大小寫,但最好使用小寫)
也不要使用amq帶頭(內置的exchange都是使用amq.開頭)
如果申明一個已存在的exchange name,如果type不一致會拋出異常.

::setType ( string $exchange_type )、::getType ( void )
這個是rabbitmq的核心,exchange的類型,決定了rabbit實現何種業務模型。
這個地方尤其要注意,AMQ本身沒有默認的exchangeType,所以在申明exchange必需要設置type,否則會拋出異常 Could not declare exchange. Exchanges must have a type,
目前exchange支持4種類型  AMQP_EX_TYPE_DIRECT、 AMQP_EX_TYPE_FANOUT、AMQP_EX_TYPE_HEADERS 、 AMQP_EX_TYPE_TOPIC
可參考:RabbitMQ AMQP 消息模型攻略
記憶方法:amqp_exchange_type_direct、fanout、topic、headers

 ::setFlags ( int $flags )、::getFlags ( void )
默認AMQP_NOPARAM; 可選AMQP_DURABLE, AMQP_PASSIVE、AMQP_AUTODELETE

  1. passive:聲明一個已存在的交換器的,如果不存在將拋出異常,這個一般用在consume端。因爲一般produce端創建,在consume端建議設置成AMQP_PASSIVE,防止consume創建exchange

  2. durable:持久exchange,該交換器將在broker重啓後生效,一般常使用該選項.

  3. auto_delete:該交換器將在沒有消息隊列綁定時自動刪除。一個從未綁定任何隊列的交換器不會自動刪除。解釋有點繞。說明下吧,當有隊列bind到auto_delete的交換器上之後,刪除該隊列。此時交換器也會刪除。一般創建爲臨時交換器。

::setArgument ( string $key , mixed $value )
::setArguments ( array $arguments )
::getArgument ( string $key )
::getArguments ( void )

::declareExchange( void ) //和PHP的官方有差別

::delete ($exchangeName = null, $flags = AMQP_NOPARAM) //和PHP的官方有差別
未指定$exchangeName,刪除當前鏈接的exchange但無法刪除system中內置的exchange.

::bind($exchange_name, $routing_key = ”, array $arguments = array())  //和PHP的官方有差別
::unbind($exchange_name, $routing_key = ”, array $arguments = array())  //和PHP的官方有差別
可以將消息同時發送到多個exchange,稍後補充實際的用法。

::publish ( string $message , string $routing_key [, int $flags = AMQP_NOPARAM [, array $attributes = array() ]] ) //發送消息方法 $attributes參考:RabbitMQ的PHP教程之RPC (六)

參數:$routint_key,在fanout下忽略。在direct類型下,如果該值爲空,則bind到exchange都能收到消息,這個有點像fanout的廣播模式。

參數:$flags可選值 AMQP_MANDATORY、AMQP_IMMEDIATE,默認:AMQP_NOPARAM,此處一般都設置爲false,本人還未測出這2個flag的區別,這裏有一篇文章關於這2個flag的介紹:
http://blog.csdn.net/jiao_fuyou/article/details/21594947

::getChannel()、getConnection() //不再介紹,見上面

四、AMQPQueue

::__construct(AMQPChannel $amqp_channel) //有PHP官方有區別

::setName ( string $queue_name )、::getName ( void ) //隊列名稱

::setFlags ( int $flags )、::getFlags ( void ) //重點
可選參數:AMQP_DURABLE, AMQP_PASSIVE,AMQP_EXCLUSIVE, AMQP_AUTODELETE
默認:auto_delete

auto_delete:當隊列中有消費者,則隊列存在,當沒有消費者鏈接,則隊列刪除

durable:持久化,隊列不刪除,注意僅僅是隊列持久,消息不持久(消息的持久在publish時的增加屬性delivery_mode)。消費的消息,從隊列裏刪除,未消費的消息保存在隊列中,不需要關注是否有消費者。最實用

passive:聲明一個1個已存在的隊列。意義不大,如果隊列不存在會拋出異常。

exclusive:排他隊列,如果一個隊列被聲明爲排他隊列,該隊列僅對首次聲明它的連接可見,並在連接斷開時自動刪除。這裏需要注意三點:

排他隊列是基於連接可見的,同一連接的不同信道是可以同時訪問同一個連接創建的排他隊列的。

"首次",如果一個連接已經聲明瞭一個排他隊列,其他連接是不允許建立同名的排他隊列的,這個與普通隊列不同。

即使該隊列是持久化的,一旦連接關閉或者客戶端退出,該排他隊列都會被自動刪除的。這種隊列適用於只限於一個客戶端發送讀取消息的應用場景。

::setArgument ( string $key , mixed $value )
::setArguments ( array $arguments )
::getArgument ( string $key )
::getArguments ( void )

::declareQueue ( void )
::bind ( string $exchange_name , string $routing_key )
::unbind ( string $exchange_name , string $routing_key )

::get ([ int $flags ] )
//非阻塞,從隊列中檢索下一個可用的消息,flags爲空默認爲amqp.auto_ack(在php.ini中可用設置默認值),可選參數:AMQP_AUTOACK

::consume ( callable $callback [, int $flags = AMQP_NOPARAM ] )
//阻塞,$callback支持數組的寫法,flags爲空默認爲amqp.auto_ack(在php.ini中可用設置默認值),可選參數:AMQP_AUTOACK

::ack ( int $delivery_tag [, int $flags = AMQP_NOPARAM ] )  //複雜的業務邏輯,最好採用手動應答,防止消費處理業務邏輯複雜時,消息丟失。指定務必指定爲AMQP_NOPARAM, 本人測試使用參數 AMQP_MULTIPLE,達不到預期的效果。此處有待高人解決。

更人性化的應答,在使用get、consume時,指定AMQP_NOPARAM時(未指定參數,則會調用PHP.ini的默認的設置),此時未對消費的消息做應答。在處理完複雜的業務邏輯後,使用ack做應答。此時隊列中,會刪除該條消息,如未應答,則會一直存在隊列中。

::nack($delivery_tag, $flags = AMQP_NOPARAM)、reject($delivery_tag, $flags = AMQP_NOPARAM)
用法ack相反,表示not ack.

::purge ( void )
清空隊列中的消息。

::delete ( void )
刪除隊列

::getChannel()、getConnection() //不再介紹,見上面

::cancel ([ string $consumer_tag = “” ] ) //暫時不知該如何使用

五、AMQPEnvelope

該類沒有set的方法,各種get

::getBody ( void )
::getRoutingKey ( void )
::getDeliveryTag ( void )
::getDeliveryMode ( void )
::getExchangeName ( void )
::isRedelivery ( void )
::getContentType ( void )
::getContentEncoding ( void )
::getType ( void )
::getTimeStamp ( void )
::getPriority ( void )
::getExpiration ( void )
::getUserId ( void )
::getAppId ( void )
::getMessageId ( void )
::getReplyTo ( void )
::getCorrelationId ( void )
::getHeaders ( void )
::getHeader($header_key).

六、代碼示例:


生產者示例:

<?php

header('Content-Type:text/html;charset=utf8;');

$params = array(
    'exchangeName' => 'myexchange',
    'queueName' => 'myqueue',
    'routeKey' => 'myroute',
);

$connectConfig = array(
    'host' => 'localhost',
    'port' => 5672,
    'login' => 'rabbitmq',
    'password' => 'rabbitmq',
    'vhost' => '/'
);

//var_dump(extension_loaded('amqp')); 判斷是否加載amqp擴展

//exit();
try {
    $conn = new AMQPConnection($connectConfig);
    $conn->connect();
    if (!$conn->isConnected()) {
        //die('Conexiune esuata');
        //TODO 記錄日誌
        echo 'rabbit-mq 連接錯誤:', json_encode($connectConfig);
        exit();
    }
    $channel = new AMQPChannel($conn);
    if (!$channel->isConnected()) {
        // die('Connection through channel failed');
        //TODO 記錄日誌
        echo 'rabbit-mq Connection through channel failed:', json_encode($connectConfig);
        exit();
    }
    $exchange = new AMQPExchange($channel);
    $exchange->setFlags(AMQP_DURABLE);//持久化
    $exchange->setName($params['exchangeName']?:'');
    $exchange->setType(AMQP_EX_TYPE_DIRECT); //direct類型
    $exchange->declareExchange();

    //$channel->startTransaction();

    $queue = new AMQPQueue($channel);
    $queue->setName($params['queueName']?:'');
    $queue->setFlags(AMQP_DURABLE);
    $queue->declareQueue();

    //綁定
    $queue->bind($params['exchangeName'], $params['routeKey']);
} catch(Exception $e) {

}


$num = mt_rand(100, 500);

//生成消息
for($i = $num; $i <= $num+5; $i++)
{
    $exchange->publish("this is {$i} message..", $params['routeKey'], AMQP_MANDATORY, array('delivery_mode'=>2));
}


消費者示例:

<?php

header('Content-Type:text/html;charset=utf8;');

$params = array(
    'exchangeName' => 'myexchange',
    'queueName' => 'myqueue',
    'routeKey' => 'myroute',
);

$connectConfig = array(
    'host' => 'localhost',
    'port' => 5672,
    'login' => 'rabbitmq',
    'password' => 'rabbitmq',
    'vhost' => '/'
);

//var_dump(extension_loaded('amqp'));

//exit();

try {
    $conn = new AMQPConnection($connectConfig);
    $conn->connect();
    if (!$conn->isConnected()) {
        //die('Conexiune esuata');
        //TODO 記錄日誌
        echo 'rabbit-mq 連接錯誤:', json_encode($connectConfig);
        exit();
    }
    $channel = new AMQPChannel($conn);
    if (!$channel->isConnected()) {
        // die('Connection through channel failed');
        //TODO 記錄日誌
        echo 'rabbit-mq Connection through channel failed:', json_encode($connectConfig);
        exit();
    }
    $exchange = new AMQPExchange($channel);
    $exchange->setFlags(AMQP_PASSIVE);//聲明一個已存在的交換器的,如果不存在將拋出異常,這個一般用在consume端
    $exchange->setName($params['exchangeName']?:'');
    $exchange->setType(AMQP_EX_TYPE_DIRECT); //direct類型
    $exchange->declareExchange();

    //$channel->startTransaction();

    $queue = new AMQPQueue($channel);
    $queue->setName($params['queueName']?:'');
    $queue->setFlags(AMQP_DURABLE);
    $queue->declareQueue();

    //綁定
    $queue->bind($params['exchangeName'], $params['routeKey']);
} catch(Exception $e) {
    echo $e->getMessage();
    exit();
}

function callback(AMQPEnvelope $message) {
    global $queue;
    if ($message) {
        $body = $message->getBody();
        echo $body . PHP_EOL;
        $queue->ack($message->getDeliveryTag());
    } else {
        echo 'no message' . PHP_EOL;
    }
}

//$queue->consume('callback');  第一種消費方式,但是會阻塞,程序一直會卡在此處

//第二種消費方式,非阻塞
$message = $queue->get();
if(!empty($message))
{
    echo $message->getBody();
    $queue->ack($message->getDeliveryTag());    //應答,代表該消息已經消費
}


相關閱讀:

  1. RabbitMQ的原理與操作示例

  2. RabbitMQ AMQP 消息模型攻略

  3. PHP中的AMQP類

  4. RabbitMQ的PHP教程之入門 (一)

  5. RabbitMQ的PHP教程之工作隊列 (二)

  6. RabbitMQ的PHP教程之發佈/訂閱(三)

  7. RabbitMQ的PHP教程之Routing (四)

  8. RabbitMQ的PHP教程之topic (五)

  9. RabbitMQ的PHP教程之RPC (六)


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