rabbitmq安裝與原理講解&&php擴展編譯

消息隊列的實現中,RabbitMQ以其健壯和可靠見長.公司的項目中選擇了它作爲消息隊列的實現.關於MQ的機制和原理網上有很多文章可以看,這裏就不再贅述,只講幾個比較容易混淆的問題

1,binding key和routing key

  binding key和routing key是都不過是自己設置的一組字符,只是用的地方不同,binding key是在綁定交換機和隊列時候通過方法傳遞的字符串,routing key是在發佈消息時候,順便帶上的字符串,有些人說這兩個其實是一個東西,也對也不對,說對,是因爲這兩個可以完全一樣,說不對,是因爲這兩個起的作用不同,一個交換機可以綁定很多隊列,但是每個隊列也許需要的消息類型不同,binding key就是這個綁定時候留在交換機和隊列之間的提示信息,當消息發送出來後,隨着消息一起發送的routing key如果和binding key一樣就說明消息是這個隊列要的東西,如果不一樣那就不要給這個隊列,交換機你找找下個隊列看看要不要.明白了吧,這兩個key就是暗號,對上了就是自己人,對不上那麻煩你再找找去.

  binding key和routing key的配對其實也不是就要完全一樣,還可以'相似'配對,建立交換機的時候,就要告訴MQ,我要聲明的這個交換機和它上面的隊列之間傳輸消息時候要求routing key和binding key完全一樣,這種模式叫Direct,如果routing key和binding key可以'模糊'匹配,這種模式叫Topic,如果不需要匹配,儘管發,叫Fanout.

2,持久化

  交換機和隊列都可以在創建時候設置爲持久化,重啓以後會回覆,但是其中的消息未不會,如果要消息也恢復,將消息發佈到交換機的時候,可以指定一個標誌“Delivery Mode”(投遞模式),  1爲非持久化,2爲持久化.

3,流控機制

  當消息生產的速度更快,而進程的處理能力低時,消息就會堆積起來,佔用內存越來越多,導致MQ崩潰,所以rabbitmq有一個流控機制,當超過限定時候就會阻止接受消息,mq流控有三種機制
  1,主動阻塞住發消息太快的連接,這個無法調整,如果被阻塞了,在abbitmqctl 控制檯上會顯示一個blocked的狀態。
  2,內存超過限量,會阻塞連接,在vm_memory_high_watermark可調

   3,剩餘磁盤在限定以下mq會 主動阻塞所有的生產者,默認爲50m,在disk_free_limit可調.


下面是在centos上面的,rabbitmq安裝.
  
1,必要的支持
yum install ncurses-devel unixODBC unixODBC-devel


2,erlang環境
wget http://www.erlang.org/download/ otp_src_17.3.tar.gz
tar zxvf otp_src_17.3.tar.gz
cd otp_src_17.3
./configure --without-javac
#忽略警告
make && make install

安裝rabbitmq依賴文件,安裝rabbitmq

# 安裝rabbitmq依賴包
yum install xmlto

# 安裝rabbitmq服務端
wget http://www.rabbitmq.com/releases/rabbitmq-server/v3.5.7/rabbitmq-server-3.5.7.tar.gz
tar zxvf rabbitmq-server-3.5.7.tar.gz
cd rabbitmq-server-3.5.7/
make TARGET_DIR=/usr/local/rabbitmq SBIN_DIR=/usr/local/rabbitmq/sbin MAN_DIR=/usr/local/rabbitmq/man DOC_INSTALL_DIR=/usr/local/rabbitmq/doc
make install TARGET_DIR=/usr/local/rabbitmq SBIN_DIR=/usr/local/rabbitmq/sbin MAN_DIR=/usr/local/rabbitmq/man DOC_INSTALL_DIR=/usr/local/rabbitmq/doc

/usr/local/rabbitmq/sbin/rabbitmq-server -detached 啓動rabbitmq
/usr/local/rabbitmq/sbin/rabbitmqctl status 查看狀態
/usr/local/rabbitmq/sbin/rabbitmqctl stop 關閉rabbitmq



4,啓用管理插件


mkdir /etc/rabbitmq
cd  /usr/local/rabbitmq/sbin
./rabbitmq-plugins enable rabbitmq_management  (啓用插件)
./rabbitmq-plugins disable rabbitmq_management (禁用插件)
# 重啓rabbitmq
# 訪問 http://127.0.0.1:15672/
# 如果有iptables
# vi /etc/sysconfig/iptables  增加
#    -A INPUT -m state --state NEW -m tcp -p tcp --dport 15672 -j ACCEPT
# 重啓動iptable   systemctl restart iptables.service



5,創建配置文件


#在/usr/local/rabbitmq/sbin/rabbitmq-defaults 查看config文件路徑
# 創建配置文件
touch/usr/local/rabbitmq/sbin
#vm_memory_high_watermark 內存低水位線,若低於該水位線,則開啓流控機制,阻止所有請求,默認值是0.4,即內存總量的40%,
#vm_memory_high_watermark_paging_ratio 內存低水位線的多少百分比開始通過寫入磁盤文件來釋放內存
vi /usr/local/rabbitmq/sbin/rabbitmq.config 輸入
[
{rabbit, [{vm_memory_high_watermark_paging_ratio, 0.75},
         {vm_memory_high_watermark, 0.7}]}
].

相關配置

hostname mq // 設置hostname名稱
vim /etc/sysconfig/network // 設置hostname
vim /etc/hosts // 編輯hosts
./rabbitmqctl add_user admin admin // 添加用戶
./rabbitmqctl set_user_tags admin administrator // 添加admin 到 administrator分組
./rabbitmqctl set_permissions -p / admin "*." "*." "*." // 添加權限

6,創建環境文件


touch /etc/rabbitmq/rabbitmq-env.conf
#輸入
    RABBITMQ_NODENAME=FZTEC-240088 節點名稱
    RABBITMQ_NODE_IP_ADDRESS=127.0.0.1 監聽IP
    RABBITMQ_NODE_PORT=5672 監聽端口
    RABBITMQ_LOG_BASE=/data/rabbitmq/log 日誌目錄
    RABBITMQ_PLUGINS_DIR=/data/rabbitmq/plugins 插件目錄
    RABBITMQ_MNESIA_BASE=/data/rabbitmq/mnesia 後端存儲目錄



7,安裝php的rabbitmq擴展


yum install librabbitmq-devel.x86_64
wget http://pecl.php.net/get/amqp-1.4.0.tgz
tar zxvf amqp-1.4.0.tgz
cd amqp-1.4.0
/usr/local/php/bin/phpize
./configure --with-php-config=/usr/local/php/bin/php-config --with-amqp
make && make install
    
vim /usr/local/php/etc/php.ini
#輸入
          extension=amqp.so

service nginx reload
service php-fpm restart

phpinfo查看是否擴展成功


操作命令


 查看exchange信息
          /usr/local/rabbitmq/sbin/rabbitmqctl list_exchanges name type durable auto_delete arguments

 查看隊列信息
          /usr/local/rabbitmq/sbin/rabbitmqctl list_queues name durable auto_delete messages consumers me
  查看綁定信息
          /usr/local/rabbitmq/sbin/rabbitmqctl list_bindings
 查看連接信息
          /usr/local/rabbitmq/sbin/rabbitmqctl list_connections




翻譯了部分mq常量設置,不正確的地方,大家以試驗爲準


/**
 * Passing in this constant as a flag will forcefully disable all other flags.
 * Use this if you want to temporarily disable the amqp.auto_ack ini setting.
 * 傳遞這個參數作爲標誌將完全禁用其他標誌,如果你想臨時禁用amqp.auto_ack設置起效
 */
define('AMQP_NOPARAM', 0);

/**
 * Durable exchanges and queues will survive a broker restart, complete with all of their data.
 * 持久化交換機和隊列,當代理重啓動後依然存在,幷包括它們中的完整數據
 */
define('AMQP_DURABLE', 2);

/**
 * Passive exchanges and queues will not be redeclared, but the broker will throw an error if the exchange or queue does not exist.
 * 被動模式的交換機和隊列不能被重新定義,但是如果交換機和隊列不存在,代理將扔出一個錯誤提示
 */
define('AMQP_PASSIVE', 4);

/**
 * Valid for queues only, this flag indicates that only one client can be listening to and consuming from this queue.
 * 僅對隊列有效,這個人標誌定義隊列僅允許一個客戶端連接並且從其消費消息
 */
define('AMQP_EXCLUSIVE', 8);

/**
 * For exchanges, the auto delete flag indicates that the exchange will be deleted as soon as no more queues are bound
 * to it. If no queues were ever bound the exchange, the exchange will never be deleted. For queues, the auto delete
 * flag indicates that the queue will be deleted as soon as there are no more listeners subscribed to it. If no
 * subscription has ever been active, the queue will never be deleted. Note: Exclusive queues will always be
 * automatically deleted with the client disconnects.
 * 對交換機而言,自動刪除標誌表示交換機將在沒有隊列綁定的情況下被自動刪除,如果從沒有隊列和其綁定過,這個交換機將不會被刪除.
 * 對隊列而言,自動刪除標誌表示如果沒有消費者和你綁定的話將被自動刪除,如果從沒有消費者和其綁定,將不被刪除,獨佔隊列在客戶斷
 * 開連接的時候將總是會被刪除
 */
define('AMQP_AUTODELETE', 16);

/**
 * Clients are not allowed to make specific queue bindings to exchanges defined with this flag.
 * 這個標誌標識不允許自定義隊列綁定到交換機上
 */
define('AMQP_INTERNAL', 32);

/**
 * When passed to the consume method for a clustered environment, do not consume from the local node.
 * 在集羣環境消費方法中傳遞這個參數,表示將不會從本地站點消費消息
 */
define('AMQP_NOLOCAL', 64);

/**
 * When passed to the {@link AMQPQueue::get()} and {@link AMQPQueue::get()} methods as a flag,
 * the messages will be immediately marked as acknowledged by the server upon delivery.
 * 當在隊列get方法中作爲標誌傳遞這個參數的時候,消息將在被服務器輸出之前標誌爲acknowledged (已收到)
 */
define('AMQP_AUTOACK', 128);

/**
 * Passed on queue creation, this flag indicates that the queue should be deleted if it becomes empty.
 * 在隊列建立時候傳遞這個參數,這個標誌表示隊列將在爲空的時候被刪除
 */
define('AMQP_IFEMPTY', 256);

/**
 * Passed on queue or exchange creation, this flag indicates that the queue or exchange should be
 * deleted when no clients are connected to the given queue or exchange.
 * 在交換機或者隊列建立的時候傳遞這個參數,這個標誌表示沒有客戶端連接的時候,交換機或者隊列將被刪除
 */
define('AMQP_IFUNUSED', 512);

/**
 * When publishing a message, the message must be routed to a valid queue. If it is not, an error will be returned.
 * 當發佈消息的時候,消息必須被正確路由到一個有效的隊列,否則將返回一個錯誤
 */
define('AMQP_MANDATORY', 1024);

/**
 * When publishing a message, mark this message for immediate processing by the broker. (High priority message.)
 * 當發佈消息時候,這個消息將被立即處理.
 */
define('AMQP_IMMEDIATE', 2048);

/**
 * If set during a call to {@link AMQPQueue::ack()}, the delivery tag is treated as "up to and including", so that multiple
 * messages can be acknowledged with a single method. If set to zero, the delivery tag refers to a single message.
 * If the AMQP_MULTIPLE flag is set, and the delivery tag is zero, this indicates acknowledgement of all outstanding
 * messages.
 * 當在調用AMQPQueue::ack時候設置這個標誌,傳遞標籤將被視爲最大包含數量,以便通過單個方法標示多個消息爲已收到,如果設置爲0
 * 傳遞標籤指向單個消息,如果設置了AMQP_MULTIPLE,並且傳遞標籤是0,將所有未完成消息標示爲已收到
 */
define('AMQP_MULTIPLE', 4096);

/**
 * If set during a call to {@link AMQPExchange::bind()}, the server will not respond to the method.The client should not wait
 * for a reply method. If the server could not complete the method it will raise a channel or connection exception.
 * 當在調用AMQPExchange::bind()方法的時候,服務器將不響應請求,客戶端將不應該等待響應,如果服務器無法完成該方法,將會拋出一個異常
 */
define('AMQP_NOWAIT', 8192);

/**
 * If set during a call to {@link AMQPQueue::nack()}, the message will be placed back to the queue.
 * 如果在調用AMQPQueue::nack方法時候設置,消息將會被傳遞迴隊列
 */
define('AMQP_REQUEUE', 16384);

/**
 * A direct exchange type.
 * direct類型交換機
 */
define('AMQP_EX_TYPE_DIRECT', 'direct');

/**
 * A fanout exchange type.
 * fanout類型交換機
 */
define('AMQP_EX_TYPE_FANOUT', 'fanout');

/**
 * A topic exchange type.
 * topic類型交換機
 */
define('AMQP_EX_TYPE_TOPIC', 'topic');

/**
 * A header exchange type.
 * header類型交換機
 */
define('AMQP_EX_TYPE_HEADERS', 'headers');

/**
 * socket連接超時設置
 */
define('AMQP_OS_SOCKET_TIMEOUT_ERRNO', 536870947);
創建mqserver.php

<?php
error_reporting(E_ALL);
$ex_name = "ex_name1";
$route_k = [0=>'key_0',1=>'key_1'];
$que_name= "que_name";

//連接RabbitMQ
$conn_args = array( 'host'=>'127.0.0.1' , 
					'port'=> '5672', 
					'login'=>'guest' , 
					'password'=> 'guest',
					'vhost' =>'/');

$conn = new AMQPConnection($conn_args);
//$conn->setTimeout(1);
$conn->connect() or die('error');
//創建exchange名稱和類型
$channel = new AMQPChannel($conn);
$ex = new AMQPExchange($channel);
$ex->setName($ex_name);
$ex->setType(AMQP_EX_TYPE_DIRECT);
$ex->setFlags(AMQP_DURABLE | AMQP_AUTODELETE);
$ex->declareExchange();
//創建queue名稱,使用exchange,綁定routingkey
/*
$q = new AMQPQueue($channel);
$q->setName($que_name);
$q->setFlags(AMQP_DURABLE | AMQP_AUTODELETE);

$q->declareQueue();
$q->bind($ex_name, '');
*/
//echo $route_k[0];
//消息發佈 
$pagestartime=microtime();

$channel->startTransaction();
$message = "key0 test msg";//json_encode(array('Hello World!','DIRECT'));
//for($i=0;$i<10000;$i++){
	//$rk = $route_k[$i%2];
	//$ex->publish("test ".$i,$rk);
//}
$ex->publish($message,$route_k[0]);
$ex->publish('key1 content1',$route_k[1]);
$ex->publish('key1 content2',$route_k[1]);
$channel->commitTransaction();
$conn->disconnect();


$pageendtime = microtime();
$starttime = explode(" ",$pagestartime);
$endtime = explode(" ",$pageendtime);
$totaltime = $endtime[0]-$starttime[0]+$endtime[1]-$starttime[1];
$timecost = sprintf("%s",$totaltime);
echo "頁面運行時間: $timecost 秒";

?>
運行後運行如下獲取消息端代碼

mqcli.php

<?php
if(isset($_GET['k'])){$k=intval($_GET['k']);
}else{
	echo "not k";
	return false;
}

$ex_name = "ex_name1";
$route_k = [0=>'key_0',1=>'key_1'];
$que_name= $route_k[$k];//隊列名儘量與路由名一致或者避免重複,否則在同一交換機下的不同路由之間會導致數據獲取輪詢,而不是隻獲取特定路由下的消息;
//連接RabbitMQ
$conn_args = array( 'host'=>'127.0.0.1' , 
					'port'=> '5672', 
					'login'=>'guest' , 
					'password'=> 'guest',
					'vhost' =>'/');
$conn = new AMQPConnection($conn_args);
$conn->connect() or die('error');
//設置queue名稱,使用exchange,綁定routingkey
$channel = new AMQPChannel($conn);

/*
$ex = new AMQPExchange($channel);
$ex->setName($ex_name);
$ex->setType(AMQP_EX_TYPE_DIRECT);
$ex->setFlags(AMQP_DURABLE | AMQP_AUTODELETE);
$ex->declareExchange();
*/

$q = new AMQPQueue($channel);
$q->setName($que_name);
$q->setFlags(AMQP_DURABLE | AMQP_AUTODELETE);
$q->declareQueue();
$q->bind($ex_name, $que_name);//$route_k[1]); 
//消息獲取
$messages = $q->get(AMQP_AUTOACK) ;
if ($messages){
var_dump($messages->getBody());
}else{
	var_dump($messages);
}
$conn->disconnect();

?>
運行結果:

mcli.php?k=0   1 次顯示如下

string(13) "key0 test msg"s

mcli.php?k=0   第2 次則顯示如下(路由器key_0下的消息共1條已經被取出完)

bool(false)

mcli.php?k=1   1 次顯示如下

string(13) "key1 content1"

mcli.php?k=1   第2 次顯示如下

string(13) "key1 content2"

mcli.php?k=1        第3 次則顯示如下(路由器key_1下的消息共2條已經被取出完)

bool(false)

接下來 摘取天上星 對rabbitmq進行一點深入瞭解(以下內容來自互聯網):

rabbitmq原理講解

AMPQ協議爲了能夠滿足各種消息隊列需求,在概念上比較複雜。首先,rabbitMQ啓動默認是沒有任何配置的,需要客戶端連接上去,設置交換機等才能工作。不把這些基礎概念弄清楚,後面程序設計就容易產生問題。

1.vhosts : 虛擬主機。

一個RabbitMQ的實體上可以有多個vhosts,用戶與權限設置就是依附於vhosts。對一般PHP應用,不需要用戶權限設定,直接使用默認就存在的"/"就可以了,用戶可以使用默認就存在的"guest"。一個簡單的配置示例:

$conn_args = array(
    'host' => '127.0.0.1',
    'port' => '5672',
    'login' => 'guest',
    'password' => 'guest',
    'vhost'=>'/'
);

2.connection 與 channel :  連接與信道

connection是指物理的連接,一個client與一個server之間有一個連接;一個連接上可以建立多個channel,可以理解爲邏輯上的連接。一般應用的情況下,有一個channel就夠用了,不需要創建更多的channel。示例代碼:

//創建連接和channel
$conn = new AMQPConnection($conn_args);
if (!$conn->connect()) {
    die("Cannot connect to the broker!\n");
}
$channel = new AMQPChannel($conn);

3.exchange 與  routingkey : 交換機與路由鍵

爲了將不同類型的消息進行區分,設置了交換機與路由兩個概念。比如,將A類型的消息發送到名爲‘C1’的交換機,將類型爲B的發送到'C2'的交換機。當客戶端連接C1處理隊列消息時,取到的就只是A類型消息。進一步的,如果A類型消息也非常多,需要進一步細化區分,比如某個客戶端只處理A類型消息中針對K用戶的消息,routingkey就是來做這個用途的。

$e_name = 'e_linvo'; //交換機名
$k_route = array(0=> 'key_1', 1=> 'key_2'); //路由key
//創建交換機
$ex = new AMQPExchange($channel);
$ex->setName($e_name);
$ex->setType(AMQP_EX_TYPE_DIRECT); //direct類型
$ex->setFlags(AMQP_DURABLE); //持久化
echo "Exchange Status:".$ex->declare()."\n";
for($i=0; $i<5; ++$i){
    echo "Send Message:".$ex->publish($message . date('H:i:s'), $k_route[i%2])."\n";
}

由以上代碼可以看到,發送消息時,只要有“交換機”就夠了。至於交換機後面有沒有對應的處理隊列,發送方是不用管的。routingkey可以是空的字符串。在示例中,我使用了兩個key交替發送消息,是爲了下面更便於理解routingkey的作用。

對於交換機,有兩個重要的概念:

A,類型。有三種類型: Fanout類型最簡單,這種模型忽略routingkey;Direct類型是使用最多的,使用確定的routingkey。這種模型下,接收消息時綁定'key_1'則只接收key_1的消息;最後一種是Topic,這種模式與Direct類似,但是支持通配符進行匹配,比如: 'key_*',就會接受key_1和key_2。Topic貌似美好,但是有可能導致不嚴謹,所以還是推薦使用Direct。

B,持久化。指定了持久化的交換機,在重新啓動時才能重建,否則需要客戶端重新聲明生成才行。

需要特別明確的概念:交換機的持久化,並不等於消息的持久化。只有在持久化隊列中的消息,才能持久化;如果沒有隊列,消息是沒有地方存儲的;消息本身在投遞時也有一個持久化標誌的,PHP中默認投遞到持久化交換機就是持久的消息,不用特別指定。

4.queue: 隊列

講了這麼多,纔講到隊列呀。事實上,隊列僅是針對接收方(consumer)的,由接收方根據需求創建的。只有隊列創建了,交換機纔會將新接受到的消息送到隊列中,交換機是不會在隊列創建之前的消息放進來的。換句話說,在建立隊列之前,發出的所有消息都被丟棄了。下面這個圖比RabbitMQ官方的圖更清楚——Queue是屬於ReceiveMessage的一部分。


接下來看一下創建隊列及接收消息的示例:

$e_name = 'e_linvo'; //交換機名
$q_name = 'q_linvo'; //隊列名
$k_route = ''; //路由key

//創建連接和channel
$conn = new AMQPConnection($conn_args);
if (!$conn->connect()) {
    die("Cannot connect to the broker!\n");
}
$channel = new AMQPChannel($conn);

//創建交換機
$ex = new AMQPExchange($channel);
$ex->setName($e_name);
$ex->setType(AMQP_EX_TYPE_DIRECT); //direct類型
$ex->setFlags(AMQP_DURABLE); //持久化
echo "Exchange Status:".$ex->declare()."\n";

//創建隊列
$q = new AMQPQueue($channel);
$q->setName($q_name);
$q->setFlags(AMQP_DURABLE); //持久化

//綁定交換機與隊列,並指定路由鍵
echo 'Queue Bind: '.$q->bind($e_name, $k_route)."\n";

//阻塞模式接收消息
echo "Message:\n";
$q->consume('processMessage', AMQP_AUTOACK); //自動ACK應答

$conn->disconnect();

/**
* 消費回調函數
* 處理消息
*/
function processMessage($envelope, $queue) {
    var_dump($envelope->getRoutingKey);
    $msg = $envelope->getBody();
    echo $msg."\n"; //處理消息
}

從上述示例中可以看到,交換機既可以由消息發送端創建,也可以由消息消費者創建。

創建一個隊列(line:20)後,需要將隊列綁定到交換機上(line:25)隊列才能工作,routingkey也是在這裏指定的。有的資料上寫成bindingkey,其實一回事兒,弄兩個名詞反倒容易混淆。

消息的處理,是有兩種方式:

A,一次性。用 $q->get([...]),不管取到取不到消息都會立即返回,一般情況下使用輪詢處理消息隊列就要用這種方式;

B,阻塞。用 $q->consum( callback, [...] ) 程序會進入持續偵聽狀態,每收到一個消息就會調用callback指定的函數一次,直到某個callback函數返回FALSE才結束。

關於callback,這裏多說幾句: PHP的call_back是支持使用數組的,比如: $c = new MyClass(); $c->counter = 100; $q->consume( array($c,'myfunc') ) 這樣就可以調用自己寫的處理類。MyClass中myfunc的參數定義,與上例中processMessage一樣就行。

在上述示例中,使用的$routingkey = '', 意味着接收全部的消息。我們可以將其改爲 $routingkey = 'key_1',可以看到結果中僅有設置routingkey爲key_1的內容了。

注意: routingkey = 'key_1'  與 routingkey = 'key_2' 是兩個不同的隊列。假設: client1 與 client2 都連接到 key_1 的隊列上,一個消息被client1處理之後,就不會被client2處理。而 routingkey = '' 是另類,client_all綁定到 '' 上,將消息全都處理後,client1和client2上也就沒有消息了。

在程序設計上,需要規劃好exchange的名稱,以及如何使用key區分開不同類型的標記,在消息產生的地方插入發送消息代碼。後端處理,可以針對每一個key啓動一個或多個client,以提高消息處理的實時性。





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