【RabbitMQ】RabbitMQ 如何保證消息的可靠傳輸

一、可靠傳輸

本篇文章主要講 RabbitMQ 如何保證消息的可靠傳輸,所以在講RabbitMQ的實現之前,我們需要先來搞懂一個問題,就是什麼是消息的可靠傳輸。

在 RabbitMQ 中,一個消息從產生到被消費大致需要經過三個步驟,即生產者生產消息,消息投遞到 RabbitMQ,RabbitMQ 再將消息推送消費者(或者是消費者拉取),最終消費者將這條消息成功消費。

所以消息丟失也可以劃分爲三種情況——生產者、消息隊列、消費者,如下圖所示:

所以消息的可靠傳輸,就是確保消息能夠百分百從生產者發送到服務器,再從服務器發送到消費者。接下來我們就針對這三種情況分別討論解決方案。

二、生產者投遞消息失敗

1. 事務機制

使用 RabbitMQ 的事務功能,此時可以選擇用 RabbitMQ 提供的事務功能,就是生產者發送數據之前開啓 RabbitMQ 事務 channel.txSelect,然後發送消息,如果消息沒有成功被 RabbitMQ 接收到,那麼生產者會收到異常報錯,此時就可以回滾事務 channel.txRollback,然後重試發送消息;如果收到了消息,那麼可以提交事務 channel.txCommit

// 開啓事務
channel.txSelect();
try {
    // 發送消息
} catch(Exception e) {
    channel.txRollback();
	// 重發消息
}
// 提交事務
channel.txCommit();

事務機制可以基本確保生產者投遞消息成功,但是這種方式有比較大的缺點,基本上 RabbitMQ 事務機制(同步)一搞,基本上吞吐量會下來,因爲太耗性能了。

2. confirm機制

針對上述問題,如果還要確保 RabbitMQ 生產者的消息正確投遞,可以開啓 confirm 模式,在生產者端設置開啓 confirm 模式之後,你每次寫的消息都會分配一個唯一的 id,然後如果寫入了 RabbitMQ 中,RabbitMQ 會給你回傳一個 ACK 消息,告訴你說這個消息 ok 了。

如果 RabbitMQ 沒能處理這個消息,會回調你一個 nack 接口,告訴你這個消息接收失敗,你可以重試。而且你可以結合這個機制自己在內存裏維護每個消息 id 的狀態,如果超過一定時間還沒接收到這個消息的回調,那麼你可以重發。

try {
    channel.confirmSelect(); //將信道置爲 publisher confirm 模式
    // 之後正常發送消息
    channel.basicPushlish("exchange", "routingKey", null, 
                          "publisher confirm test".getBytes());
    if (!channel.waitFormConfirms()) {
        System.out.println("Send message failed");
    	// do something else...
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

事務機制和 confirm 機制最大的不同在於,事務機制是同步的,你提交一個事務之後會阻塞在那兒,但是 confirm 機制是異步的,你發送個消息之後就可以發送下一個消息,然後那個消息 RabbitMQ 接收之後會異步回調你一個接口通知你這個消息接收到了。

所以一般生產者到 RabbitMQ 這塊避免數據丟失,會採用 confirm 機制更多些。

三、消息隊列自身丟失

就是 RabbitMQ 自己弄丟了數據,這個你必須開啓 RabbitMQ 的持久化,就是消息寫入之後會持久化到磁盤,哪怕是 RabbitMQ 自己掛了,恢復之後會自動讀取之前存儲的數據,一般數據不會丟。除非極其罕見的是,RabbitMQ 還沒持久化,自己就掛了,可能導致少量數據丟失,但是這個概率較小。

設置持久化有兩個步驟:

  • 創建 queue 的時候將其設置爲持久化

    這樣就可以保證 RabbitMQ 持久化 queue 的元數據,但是不會持久化 queue 裏的數據。

  • 發送消息時將消息的 deliveryMode 設置爲 2

    就是將消息設置爲持久化,此時 RabbitMQ 就會將消息持久化到磁盤上去。

必須要同時設置這兩個持久化纔行,RabbitMQ 哪怕是掛了,再次重啓,也會從磁盤上重啓恢復 queue,恢復這個 queue 裏的數據。

同時持久化也可以跟生產者那邊的 confirm 機制配合起來,只有消息被持久化到磁盤後,纔會通知生產者 ack 了,所以哪怕是在持久化到磁盤之前,RabbitMQ 掛了,數據丟了,生產者收不到 ACK,你也是可以自己重發的。

注意,哪怕是你給 RabbitMQ 開啓了持久化機制,也有一種可能,就是這個消息寫到了 RabbitMQ 中,但是還沒來得及持久化到磁盤上,結果不巧,此時 RabbitMQ 掛了,就會導致內存中的數據丟失。

四、消費者宕機

對於消費者端所產生的情況就是:消費者成功接收到消息,但是還未將消息處理完畢就宕機了。針對這種情況,可以利用 RabbitMQ 提供的 消息確認機制

1. 消息確認機制

爲了保證消息從隊列可靠地到達消費者,RabbitMQ 提供了消息確認機制(message acknowledgement)。消費者在訂閱隊列時,可以指定 autoAck 參數:

  • 當 autoAck 等於 true 時,RabbitMQ 會自動把發送出去的消息置爲確認,然後從內存(或者磁盤)中刪除,而不管消費者是否真正地消費到了這些消息;
  • 當 autoAck 等於 false 時,RabbitMQ 會等待消費者顯示地回覆確認信號後才從內存(後者磁盤)中移去消息(實質上是先打上刪除標記,之後再刪除)。

所以對於消費者可能發生宕機地情況,我們可以將 autoAck 參數置爲 false,消費者就有足夠的時間處理這條消息,不用擔心處理消息過程中消費者進程掛掉後消息丟失的問題,因爲 RabbitMQ 會一直等待並且持有這條消息,直到消費者顯示調用 Basic.Ack 命令爲止。

當 autoAck 參數設置爲 false,對於 RabbitMQ 服務端而言,隊列中的消息分成了兩個部分:一部分是等待投遞給消費者的消息,另一部分是已經投遞給消費者,但是還沒有收到消費者確認信號的消息。如果 RabbitMQ 一直沒有收到消費者的確認信號,並且消費此消息的消費者已經斷開連接,則 RabbitMQ 會安排該消息重新進入隊列,等待投遞給下一個消費者,當然也可能還是原來的那個消費者。

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