一定要注意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
passive:聲明一個已存在的交換器的,如果不存在將拋出異常,這個一般用在consume端。因爲一般produce端創建,在consume端建議設置成AMQP_PASSIVE,防止consume創建exchange
durable:持久exchange,該交換器將在broker重啓後生效,一般常使用該選項.
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()); //應答,代表該消息已經消費 }
相關閱讀: