本片文章會介紹以下章節,使大家能夠有一些基本的認知。
- 什麼是交換器、路由器、繫結,以及三者的關係
- 交換器型別
- 消費模式
- 交換器、佇列、訊息的持久化
- 訊息傳送的過程以及可靠訊息投遞機制
- 訊息與佇列的過期事件
- 死信佇列
什麼是交換器、路由器、繫結
RabbitMQ的很多強大功能和靈活性來自於AMQP規範。不像HTTP和SMTP協議,AMQP規範不僅定義了一種網路協議,同時也定義了伺服器端的服務和行為。這些資訊就是高階訊息佇列(Advanced Message Queuing,AMQ)模型。針對代理伺服器軟體,AMQ模型在邏輯上定義了三種抽象元件用於指定訊息的路由行為:
■ 交換器(Exchange),訊息代理伺服器中用於把訊息路由到佇列的元件。
■ 佇列(Queue),用來儲存訊息的資料結構,位於硬碟或記憶體中。
■ 繫結(Binding),一套規則,用於告訴交換器訊息應該被儲存到哪個佇列。
交換器、路由鍵、繫結
交換器:Exchange
生產者將訊息,傳送到對應的 Exchange,由Exchange將訊息路由到一個或者多個佇列中去。如果路由不到,或許返回給生產者或許直接丟棄。
路由鍵:RoutingKey
生產者傳送訊息時,一般會指定RoutingKey,用於指定路由規則,RoutingKey需要與交換器型別和繫結鍵聯合使用才能生效。
繫結:Binding
通過繫結把交換器與佇列關聯起來,在繫結的時候一般會指定一個bindkey,這樣mq就知道如何正確的將訊息路由到佇列了。可以把RoutingKey看做是RoutingKey
交換器型別
Fanout
把傳送到此交換機的訊息,路由到所有繫結的佇列。
Direct
把傳送到此交換機的訊息,路由到與BindingKey和RoutingKey完全匹配的佇列中。
如圖,如果傳送 RoutingKey為warning的訊息,會路由到 Queue1、Queue2中,如果key為 info、debug會路由到Queue2中,如果為其他的key,則不會路由到這兩個佇列中。
Topic
Direct是完全匹配,而Topic在匹配規則上進行了擴充套件,與Direct相似,也是將訊息路由到BindKey和RoutingKey相匹配的佇列中,但是匹配規則不同。
以 點號 “.” 作為分隔符,如 com.rabbitmq.client, com.hidden.client,java.util,concurrent,com.hidden.demo。
BindKey,RoutingKey也是通過 “.”分隔的字串。
BindKey中可以存在兩個字元 “*”,“#”,其中,# 標識 零至多個單詞,* 標識 匹配一個單詞
假如我們宣告瞭如下RoutingKey
*.rabbitmq.*
*.*.client
com.#
匹配規則請看下圖
Headers
將訊息中的headers與該Exchange相關聯的所有Binging中的引數進行匹配,如果匹配上了,則傳送到該Binding對應的Queue中。
消費模式
在Rabbitmq中,有兩種消費模式 :PULL(basic_get),PUSH(basic_consume),兩者的區別是
get方法,每次獲取一條,每次都是一條新的請求;而consume將channel(通道)設定為訂閱模式,伺服器會一直推送訊息到消費者直到佇列被消費完。
消費確認與拒絕
為了保證訊息正確的到達消費者。Rabbitmq提供了訊息確認機制。
當消費者在訂閱佇列時,可以指定autoAck引數,等於false時,rabbitmq會顯示的等待消費者回復確認訊號後,才從記憶體或硬碟中刪除訊息,當等於true是,會把傳送的訊息直接刪除,而不管是否真的能到達消費者。
rabbitmq 不會為未確認的訊息設定過期時間,它判斷此訊息是否需要重新投遞的唯一依據是消費者已經斷開。
確認訊息
$channel->basic_ack($deliveryTag,$requeue);
拒絕訊息
$channel->basic_reject();
批量拒絕訊息
$channel->basic_nack($deliveryTag,$multiple,$requeue);
操作與實踐(PHP版本)
新增composer
"php-amqplib/php-amqplib": ">=2.9.0”
生產者
// 連結
$connection = new AMQPStreamConnection('localhost', '5672', 'guest', 'guest', '/');
$channel = $connection->channel();
// 宣告 交換機 指定型別
$channel->exchange_declare('exchange-direct','direct’);
// 宣告佇列
$channel->queue_declare('queue-direct');
$channel->queue_declare('queue-direct1');
// 繫結佇列-交換機-key,若一個佇列裡有多個routingkey,只能在回撥裡過濾
$channel->queue_bind('queue-direct','exchange-direct','a’);
$channel->queue_bind('queue-direct','exchange-direct’,'b’);
$channel->queue_bind('queue-direct1','exchange-direct’,'c’);
// 傳送訊息
$msg = new \PhpAmqpLib\Message\AMQPMessage('hello');
// 訊息會到queue-direct1佇列
$channel->basic_publish($msg, ‘exchange-direct’,’c');
消費者
include_once '../vendor/autoload.php';
$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
$channel = $connection->channel();
$callback = function ($msg) {
echo 'routing_key', $msg->delivery_info['routing_key’],"\n";
echo ' [x] Received ', $msg->body, "\n";
};
$channel->basic_consume('queue-direct1', '', false, false, false, false, $callback);
// 採用 推模式
while ($channel->is_consuming()) {
$channel->wait();
}
持久化
持久化可以提高rabbitmq的可靠性,防止異常情況下(重啟、關閉、當機)的資料丟失。
rabbitmq的持久化分為3部分:交換機、佇列、訊息。
交換機、佇列的持久化通過在宣告時把durable設定為true來實現,但只能保證交換機、佇列的後設資料不會丟失,不能保證佇列訊息不回丟失。
訊息的持久化需要設定 deliveryMode = 2,所以在使用持久化的時候,不能單單對某一項設定持久化。
$msg = new AMQPMessage('這是exchange的訊息',['deliveryMode'=>2]);
需要注意的是,並不是設定為持久化,就100%確保資料不會丟失。
比如在autoAck時,消費者收到訊息,還沒有來得及處理髮生當機這樣也算訊息丟失,可以採用手動ack解決。
其次,持久化是需要寫入硬碟的,但不是為每一條都進行同步存檔(參考fsync)操作,可能暫時儲存在了緩衝區中,如果這個時間段發生異常,也會訊息丟失。可以使用 映象佇列解決。
還可以使用 事務機制和確認機制來保證訊息已經正確傳送且儲存。
訊息傳送的過程以及可靠訊息投遞機制
確認機制有兩種方式
1. 事務確認機制
2. 傳送方確認機制
事務確認機制
- channel.tx_select() 宣告事務
- channel.tx_commit() 提交事務
- channel.tx_rollback() 回滾事務
可以看下圖,AMQP的協議流轉
傳送方確認機制
RABBITMQ 可能會遇到一個問題,傳送方並不知道訊息是否真正到達MQ,在除了事務機制之外,引入了confirm模式,比事務機制更輕量。
生產者將通道設定成 confirm(確認)模式,一旦通道進入 confirm 模式,所有在該通道上面釋出的訊息都會被指派一個唯一的 ID(從 1 開始),一旦訊息被投遞到所有匹配的佇列之後,RabbitMQ 就會傳送一個確認(Basic.Ack)給生產者(包含訊息的唯一 ID),這就使得生產者知曉訊息已經正確到達了目的地了。如果訊息和佇列是可持久化的,那麼確認訊息會在訊息寫入磁碟之後發出。RabbitMQ 回傳給生產者的確認訊息中的 deliveryTag 包含了確認訊息的序號,此外 RabbitMQ 也可以設定 channel.basicAck 方法中的 multiple 引數,表示到這個序號之前的所有訊息都已經得到了處理。
事務機制在一條訊息傳送之後會使傳送端阻塞,以等待 RabbitMQ 的回應,之後才能繼續傳送下一條訊息。相比之下,傳送方確認機制最大的好處在於它是非同步的,一旦釋出一條訊息,生產者應用程式就可以在等通道返回確認的同時繼續傳送下一條訊息,當訊息最終得到確認之後,生產者應用程式便可以通過回撥方法來處理該確認訊息,如果 RabbitMQ 因為自身內部錯誤導致訊息丟失,就會傳送一條 nack(Basic.Nack)命令,生產者應用程式同樣可以在回撥方法中處理該 nack 命令。
- channel.confirm() 將 通道 設定為confirm模式
示例:
// 生產者
// 連結
$connection = new AMQPStreamConnection('localhost', '5672', 'guest', 'guest', '/');
$channel = $connection->channel();
// 宣告 交換機 指定型別
$channel->exchange_declare('exchange-direct','direct’);
// 宣告佇列
$channel->queue_declare('queue-direct');
// 繫結佇列-交換機-key,若一個佇列裡有多個routingkey,只能在回撥裡過濾
$channel->queue_bind('queue-direct','exchange-direct','a’);
// 開啟confirm模式
$channel->confirm_select();
// 設定nack 回撥
$channel->set_nack_handler(function ($msg) {
var_dump($msg->body);
echo 'nack';
});
// 設定ack 回撥
$channel->set_ack_handler(function ($msg) {
var_dump($msg->body);
echo 'ack';
});
// 傳送訊息
$msg = new \PhpAmqpLib\Message\AMQPMessage('hello’);
// 等待
$channel->wait_for_pending_acks_returns();
// 訊息會到queue-direct佇列
$channel->basic_publish($msg, ‘exchange-direct’,’a');
訊息與佇列的過期時間
1.設定訊息的TTL
目前有兩種方法設定,第一種是設定在佇列上即佇列上所有訊息一起過期,第二種是 單獨對訊息進行設定過期時間,當兩者一起使用時以 過期時間最小的為準,訊息一旦過期就會變成 死信 (後邊講)。
第一種:在宣告佇列時,新增x-message-tt資訊
$set = new AMQPTable();
$set->set('x-message-ttl', 3000);
$channel->queue_declare("queue-1", false, false, false, true, false, $set, null);
第二種:在傳送訊息時,新增expiration資訊
$msg = new AMQPMessage('這是exchange-dlx的訊息',['expiration'=>3000]);
2.設定佇列的TTL
控制佇列被自動刪除前處於未使用狀態的時間。
未使用的意思是:佇列上沒有任何消費者,佇列沒有重新宣告,並且在過期時間段內沒有被get過。
Rabbitmq會確保 佇列在過期後刪除佇列,但不能保證很及時,另外Rabbitmq重啟後,持久化佇列的過期時間也會被重新計算。
$set = new AMQPTable();
$set->set('x-expire', 3000);
$channel->queue_declare("queue-1", false, false, false, true, false, $set, null);
死信
訊息變成死信有以下幾種情況:
1.訊息被拒絕(basic.reject/basic.nack),且設定requeue為false。
2.訊息過期
3.佇列到達最大長度
基於訊息的過期時間新增,x-dead-letter-exchange或x-dead-letter-routingkey。
需要注意,在設定引數時,交換機可以不存在,但是在死信到達的時候,交換機及佇列一定要存在,不然會收不到訊息。
$set = new AMQPTable();
$set->set('x-message-ttl', 3000);
$set->set('x-dead-letter-exchange', 'exchange');
$set->set('x-dead-letter-routingkey', ‘routingkey');
$channel->queue_declare("queue-1", false, false, false, true, false, $set, null);