RabbitMQ消息可靠性分析
消息中間件的可靠性是指對消息不丟失的保障程度;而消息中間件的可用性是指無故障運行的時間百分比,通常用幾個 9 來衡量。不存在絕對的可靠性只能盡量趨向完美。並且通常可靠性也意味著影響性能和付出更大的成本,因此實際應用時還要根據業務需求,對真正關鍵的信息來做可靠性保證,並要從生產者、消息隊列、消費者三個維度來努力。
1、生產者發送信息的可靠性
生產者客戶端發送出去之後可以發生網絡丟包、網絡故障等造成消息丟失。一般情況下如果不采取措施,生產者無法感知消息是否已經正確無誤的發送到交換器中。如果消息在傳輸到Exchange的過程中發生失敗而可以讓生產者感知的話,生產者可以進行進一步的處理動作,比如重新投遞相關消息以確保消息的可靠性。
事務確實能夠解決消息發送方和RabbitMQ之間消息確認的問題,只有消息成功被RabbitMQ接收,事務才能提交成功,否則我們便可在捕獲異常之後進行事務回滾,與此同時可以進行消息重發。但是使用事務機制的話會“吸幹”RabbitMQ的性能,那麽有沒有更好的方法既能保證消息發送方確認消息已經正確送達,又能基本上不帶來性能上的損失呢?從AMQP協議層面來看並沒有更好的辦法,但是RabbitMQ提供了一個改進方案,即發送方確認機制(publisher confirm)。事務機制在一條消息發送之後會使發送端阻塞,以等待RabbitMQ的回應,之後才能繼續發送下一條消息。相比之下,發送方確認機制最大的好處在於它是異步的,一旦發布一條消息,生產者應用程序就可以在等信道返回確認的同時繼續發送下一條消息,當消息最終得到確認之後,生產者應用便可以通過回調方法來處理該確認消息,如果RabbitMQ因為自身內部錯誤導致消息丟失,就會發送一條nack(Basic.Nack)命令,生產者應用程序同樣可以在回調方法中處理該nack命令。
2、消息路由到隊列的可靠性
消息發送到交換器,如果沒有和它匹配隊列的話,消息也會丟失,mandatory或者AE可以讓消息在路由到隊列之前得到極大的可靠性保障。當mandatory參數設為true時,交換器無法根據自身的類型和路由鍵找到一個符合條件的隊列的話,那麽RabbitMQ會調用Basic.Return命令將消息返回給生產者。當mandatory參數設置為false時,出現上述情形的話,消息直接被丟棄。
備份交換器,英文名稱Alternate Exchange,簡稱AE,或者更直白的可以稱之為“備胎交換器”。生產者在發送消息的時候如果不設置mandatory參數,那麽消息在未被路由的情況下將會丟失,如果設置了mandatory參數,那麽需要添加ReturnListener的編程邏輯,生產者的代碼將變得復雜化。如果你不想復雜化生產者的編程邏輯,又不想消息丟失,那麽可以使用備份交換器,這樣可以將未被路由的消息存儲在RabbitMQ中,再在需要的時候去處理這些消息。 可以通過在聲明交換器(調用channel.exchangeDeclare方法)的時候添加alternate-exchange參數來實現,也可以通過策略的方式實現。如果兩者同時使用的話,前者的優先級更高,會覆蓋掉Policy的設置。
3、消息在隊列中持久化
持久化可以提高隊列的可靠性,以防在異常情況(重啟、關閉、宕機等)下的數據丟失。隊列的持久化是通過在聲明隊列時將durable參數置為true實現的,如果隊列不設置持久化,那麽在RabbitMQ服務重啟之後,相關隊列的元數據將會丟失,此時數據也會丟失。
隊列的持久化能保證其本身的元數據不會因異常情況而丟失,但是並不能保證內部所存儲的消息不會丟失。要確保消息不會丟失,需要將其設置為持久化。通過將消息的投遞模式(BasicProperties中的deliveryMode屬性)設置為2即可實現消息的持久化。
如果在發送消息時采用了事務機制或者publisher confirm機制的話,服務端的返回是在消息落盤之後執行的,這樣可以進一步的提高了消息的可靠性。但是即便如此也無法避免單機故障且無法修復(比如磁盤損毀)而引起的消息丟失,這裏就需要引入鏡像隊列。鏡像隊列相當於配置了副本,絕大多數分布式的東西都有多副本的概念來確保HA。在鏡像隊列中,如果主節點(master)在此特殊時間內掛掉,可以自動切換到從節點(slave),這樣有效的保證了高可用性,除非整個集群都掛掉。雖然這樣也不能完全的保證RabbitMQ消息不丟失(比如機房被炸。。。),但是配置了鏡像隊列要比沒有配置鏡像隊列的可靠性要高很多,在實際生產環境中的關鍵業務隊列一般都會設置鏡像隊列。
4、消息消費階段的可靠性
為了保證消息從隊列可靠地達到消費者,RabbitMQ提供了消息確認機制(message acknowledgement)。消費者在訂閱隊列時,可以指定autoAck參數,當autoAck等於false時,RabbitMQ會等待消費者顯式地回復確認信號後才從內存(或者磁盤)中移去消息(實質上是先打上刪除標記,之後再刪除)。當autoAck等於true時,RabbitMQ會自動把發送出去的消息置為確認,然後從內存(或者磁盤)中刪除,而不管消費者是否真正的消費到了這些消息。RabbitMQ不會為未確認的消息設置過期時間,它判斷此消息是否需要重新投遞給消費者的唯一依據是消費該消息的消費者連接是否已經斷開,這麽設計的原因是RabbitMQ允許消費者消費一條消息的時間可以很久很久。
如果消息消費失敗,也可以調用Basic.Reject或者Basic.Nack來拒絕當前消息而不是確認,如果只是簡單的拒絕那麽消息會丟失,需要將相應的requeue參數設置為true,那麽RabbitMQ會重新將這條消息存入隊列,以便可以發送給下一個訂閱的消費者。如果requeue參數設置為false的話,RabbitMQ立即會把消息從隊列中移除,而不會把它發送給新的消費者。
還有一種情況需要考慮: requeue的消息是存入隊列頭部的,即可以快速的又被發送給消費,如果此時消費者又不能正確的消費而又requeue的話就會進入一個無盡的循環之中。對於這種情況,筆者的建議是在出現無法正確消費的消息時不要采用requeue的方式來確保消息可靠性,而是重新投遞到新的隊列中,比如設定的死信隊列中,以此可以避免前面所說的死循環而又可以確保相應的消息不丟失。對於死信隊列中的消息可以用另外的方式來消費分析,以便找出問題的根本。
RabbitMQ消息可靠性分析
RabbitMQ消息可靠性分析