一.寫在前面
在上一篇文章中主要簡單的介紹了一下rabbitmq 的基本概念,包括exchange的主要型別以及每種型別分別表示什麼含義。本篇文章主要結合自己的理解,解讀一下rabbitmq 是如何保證訊息不丟失的?
二.rabbitmq 是如何保證訊息傳送時不被丟失的?
如圖所示: producer 傳送訊息到rabbitmq broker,然後有2個消費者consumer1,consumer2進行資訊消費,針對這個簡單的場景,我們心裡難免會有一個疑問:作為producer,我怎麼知道我的訊息已經成功的傳送到了broker 呢? 再一個,我怎麼知道我傳送的訊息已經成功的被consumer消費了呢?還有,如果訊息傳送到broker後,broker機器掛了怎麼辦,訊息會丟失嗎?下面就這些疑問,結合自己的理解一一進行解答.
1) 生產者訊息確認機制:
生產者訊息確認機制主要就是解決訊息成功傳送到rabbitmq broker 的問題,rabbitmq 提供了2種手段用來解決這個問題:
- 通過事務機制實現
- 通過傳送方確認機制實現
事務機制:
rabbitmq 客戶端channel API針對事務機制這塊提供了3個方法:channel.txSelect,channel.txCommit,channel.txRollback .
/**
* Enables TX mode on this channel.
* @see com.rabbitmq.client.AMQP.Tx.Select
* @see com.rabbitmq.client.AMQP.Tx.SelectOk
* @return a transaction-selection method to indicate the transaction was successfully initiated
* @throws java.io.IOException if an error is encountered
*/
Tx.SelectOk txSelect() throws IOException;
/**
* Commits a TX transaction on this channel.
* @see com.rabbitmq.client.AMQP.Tx.Commit
* @see com.rabbitmq.client.AMQP.Tx.CommitOk
* @return a transaction-commit method to indicate the transaction was successfully committed
* @throws java.io.IOException if an error is encountered
*/
Tx.CommitOk txCommit() throws IOException;
/**
* Rolls back a TX transaction on this channel.
* @see com.rabbitmq.client.AMQP.Tx.Rollback
* @see com.rabbitmq.client.AMQP.Tx.RollbackOk
* @return a transaction-rollback method to indicate the transaction was successfully rolled back
* @throws java.io.IOException if an error is encountered
*/
Tx.RollbackOk txRollback() throws IOException;
複製程式碼
txSelect方法主要是用於將通道(channel)設定成事務模式,txCommit 主要用於提交事務,txRollback 主要用於將事務進行回滾。在開啟事務之後,我們便可以將訊息傳送給rabbitmq了,如果在執行tx.commit執行成功時,表示訊息已經成功的傳送到rabbitmq伺服器了,反之則會拋異常。
需要說明的是,這裡的訊息已經成功的傳送到rabbitmq伺服器,指的是訊息已經成功傳送到rabbitmq 伺服器的exchange 了,如果exchange 沒有匹配訊息繫結的佇列,訊息還是會丟失。
說明:rabbitmq 的事務與關聯式資料庫如mysql的事務機制是不一樣的,關聯式資料庫事務關注的是ACID,rabbitmq關心的是訊息是否成功傳送。
複製程式碼
傳送方確認機制
生產者將通道設定成confirm(確認)模式,一旦設定成confirm 模式,當訊息投遞到broker之後,rabbitmq 的broker 會給訊息傳送端發一條BASIC.ACK 的確認訊息,傳送端通過監聽這個確認訊息,可以知道資訊是否已經成功的傳送出去. rabbitmq 客戶端Channel API 裡也提供了相應的API channel.confirmSelect 用來開啟客戶端確認模式:
/**
* Enables publisher acknowledgements on this channel.
* @see com.rabbitmq.client.AMQP.Confirm.Select
* @throws java.io.IOException if an error is encountered
*/
Confirm.SelectOk confirmSelect() throws IOException;
複製程式碼
2種模式的比較:事務機制傳送訊息的過程是同步的,傳送訊息之後在rabbitmq 迴應之前會阻塞,直到收到迴應之後才能傳送下一條訊息,這樣會降低系統的吞吐量。傳送者確認機制是非同步的,生產者在傳送訊息等待通道返回確認訊息的時候繼續傳送下一條資訊。所以相比而言,使用訊息確認機制傳送訊息吞吐量會更高一些。
2)消費端確認機制
消費端確認機制主要是為了確保訊息投遞到消費者之後能夠被成功的消費。 在rabbitmq 的Channel API 中也提供了相應的引數給業務側進行控制,如下:
/**
* Start a non-nolocal, non-exclusive consumer, with
* a server-generated consumerTag.
* @param queue the name of the queue
* @param autoAck true if the server should consider messages
* acknowledged once delivered; false if the server should expect
* explicit acknowledgements
* @param callback an interface to the consumer object
* @return the consumerTag generated by the server
* @throws java.io.IOException if an error is encountered
* @see com.rabbitmq.client.AMQP.Basic.Consume
* @see com.rabbitmq.client.AMQP.Basic.ConsumeOk
* @see #basicConsume(String, boolean, String, boolean, boolean, Map, Consumer)
*/
String basicConsume(String queue, boolean autoAck, Consumer callback) throws IOException;
複製程式碼
在baiscConusme裡有一個引數:autoAck ,該引數為false 的時候表示消費端需要進行手動確認(比如呼叫channel.basicAck進行主動確認),如果消費者在消費完一條訊息之後向broker 傳送確認訊息,然後由於網路原因或者其他原因導致broker 沒有確認這條訊息時,broker 不會刪除這條訊息,當連線重新建立之後,消費者還是會收到這條訊息。
3)持久化機制
rabbitmq 的持久化機制主要是確保生產者傳送的訊息能成功的落盤,確保broker重啟之後未被消費的資訊不會被丟失。
rabbitmq 的持久化機制,主要從以下幾個方面來保障:
- exchange 的持久化
- queue 的持久化
- message 的持久化
需要說明的是,訊息是儲存在queue裡的,所以只有在queue設定為持久化的時候,message的持久化才有意義,否則如果queue是非持久化的,即便message是持久的,在broker重啟之後資訊還是會丟失。
rabbitmq 的Channel API 也提供了相應的引數來設定:
/**
* Actively declare a non-autodelete exchange with no extra arguments
* @see com.rabbitmq.client.AMQP.Exchange.Declare
* @see com.rabbitmq.client.AMQP.Exchange.DeclareOk
* @param exchange the name of the exchange
* @param type the exchange type
* @param durable true if we are declaring a durable exchange (the exchange will survive a server restart)
* @throws java.io.IOException if an error is encountered
* @return a declaration-confirm method to indicate the exchange was successfully declared
*/
Exchange.DeclareOk exchangeDeclare(String exchange, String type, boolean durable) throws IOException;
複製程式碼
exchangeDeclare 介面中的durable 引數用來設定exchange是否持久化,為true表示是持久化的,反之為false
/**
* Declare a queue
* @see com.rabbitmq.client.AMQP.Queue.Declare
* @see com.rabbitmq.client.AMQP.Queue.DeclareOk
* @param queue the name of the queue
* @param durable true if we are declaring a durable queue (the queue will survive a server restart)
* @param exclusive true if we are declaring an exclusive queue (restricted to this connection)
* @param autoDelete true if we are declaring an autodelete queue (server will delete it when no longer in use)
* @param arguments other properties (construction arguments) for the queue
* @return a declaration-confirm method to indicate the queue was successfully declared
* @throws java.io.IOException if an error is encountered
*/
Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete,
Map<String, Object> arguments) throws IOException;
複製程式碼
queueDeclare 介面的durable 引數通exchange類似
/**
* Publish a message.
*
* Publishing to a non-existent exchange will result in a channel-level
* protocol exception, which closes the channel.
*
* Invocations of <code>Channel#basicPublish</code> will eventually block if a
* <a href="http://www.rabbitmq.com/alarms.html">resource-driven alarm</a> is in effect.
*
* @see com.rabbitmq.client.AMQP.Basic.Publish
* @see <a href="http://www.rabbitmq.com/alarms.html">Resource-driven alarms</a>.
* @param exchange the exchange to publish the message to
* @param routingKey the routing key
* @param props other properties for the message - routing headers etc
* @param body the message body
* @throws java.io.IOException if an error is encountered
*/
void basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body) throws IOException;
複製程式碼
訊息的持久化通過訊息的屬性BasicProperties中的deliveryMode引數來設定,deliveryMode為2表示是持久化資訊。
小結
通過訊息傳送端確認機制,消費端確認機制以及持久化,rabbitmq 保證了訊息的可靠性。但是又有一個疑問出現了,如果僅僅部署一臺broker, 即便是訊息持久化了,如果broker 出故障了,沒法恢復了,那訊息不還是會丟失嗎? 為了避免單點故障,提升rabbitmq的可用性,rabbitmq 支援叢集部署,以及提供了映象佇列等機制來確保資訊的可靠性的。 關於rabbitmq的叢集,以及映象佇列等相關方面的知識,在下期的學習之後再進行分享。