關於MQ的幾件小事(四)如何保證訊息不丟失

一條路上的鹹魚發表於2019-05-22

1.mq原則

資料不能多,也不能少,不能多是說訊息不能重複消費,這個我們上一節已解決;不能少,就是說不能丟失資料。如果mq傳遞的是非常核心的訊息,支撐核心的業務,那麼這種場景是一定不能丟失資料的。

2.丟失資料場景

丟資料一般分為兩種,一種是mq把訊息丟了,一種就是消費時將訊息丟了。下面從rabbitmq和kafka分別說一下,丟失資料的場景,
(1)rabbitmq
A:生產者弄丟了資料 生產者將資料傳送到rabbitmq的時候,可能在傳輸過程中因為網路等問題而將資料弄丟了。
B:rabbitmq自己丟了資料 如果沒有開啟rabbitmq的持久化,那麼rabbitmq一旦重啟,那麼資料就丟了。所依必須開啟持久化將訊息持久化到磁碟,這樣就算rabbitmq掛了,恢復之後會自動讀取之前儲存的資料,一般資料不會丟失。除非極其罕見的情況,rabbitmq還沒來得及持久化自己就掛了,這樣可能導致一部分資料丟失。
C:消費端弄丟了資料 主要是因為消費者消費時,剛消費到,還沒有處理,結果消費者就掛了,這樣你重啟之後,rabbitmq就認為你已經消費過了,然後就丟了資料。

rabbitmq資料丟失示意圖.png
(2)kafka
A:生產者弄丟了資料 生產者沒有設定相應的策略,傳送過程中丟失資料。
B:kafka弄丟了資料 比較常見的一個場景,就是kafka的某個broker當機了,然後重新選舉partition的leader時。如果此時follower還沒來得及同步資料,leader就掛了,然後某個follower成為了leader,他就少了一部分資料。
C:消費者弄丟了資料 消費者消費到了這個資料,然後消費之自動提交了offset,讓kafka知道你已經消費了這個訊息,當你準備處理這個訊息時,自己掛掉了,那麼這條訊息就丟了。

kafka丟失資料示意圖.png

3.如何防止訊息丟失

(1)rabbitmq
A:生產者丟失訊息
①:可以選擇使用rabbitmq提供是事物功能,就是生產者在傳送資料之前開啟事物,然後傳送訊息,如果訊息沒有成功被rabbitmq接收到,那麼生產者會受到異常報錯,這時就可以回滾事物,然後嘗試重新傳送;如果收到了訊息,那麼就可以提交事物。

  channel.txSelect();//開啟事物
  try{
      //傳送訊息
  }catch(Exection e){
      channel.txRollback();//回滾事物
      //重新提交
  }
複製程式碼

缺點: rabbitmq事物已開啟,就會變為同步阻塞操作,生產者會阻塞等待是否傳送成功,太耗效能會造成吞吐量的下降。

②:可以開啟confirm模式。在生產者哪裡設定開啟了confirm模式之後,每次寫的訊息都會分配一個唯一的id,然後如何寫入了rabbitmq之中,rabbitmq會給你回傳一個ack訊息,告訴你這個訊息傳送OK了;如果rabbitmq沒能處理這個訊息,會回撥你一個nack介面,告訴你這個訊息失敗了,你可以進行重試。而且你可以結合這個機制知道自己在記憶體裡維護每個訊息的id,如果超過一定時間還沒接收到這個訊息的回撥,那麼你可以進行重發。

    //開啟confirm
    channel.confirm();
    //傳送成功回撥
    public void ack(String messageId){
      
    }

    // 傳送失敗回撥
    public void nack(String messageId){
        //重發該訊息
    }
複製程式碼

二者不同 事務機制是同步的,你提交了一個事物之後會阻塞住,但是confirm機制是非同步的,傳送訊息之後可以接著傳送下一個訊息,然後rabbitmq會回撥告知成功與否。 一般在生產者這塊避免丟失,都是用confirm機制。
B:rabbitmq自己弄丟了資料 設定訊息持久化到磁碟。設定持久化有兩個步驟:
①建立queue的時候將其設定為持久化的,這樣就可以保證rabbitmq持久化queue的後設資料,但是不會持久化queue裡面的資料。
②傳送訊息的時候講訊息的deliveryMode設定為2,這樣訊息就會被設為持久化方式,此時rabbitmq就會將訊息持久化到磁碟上。 必須要同時開啟這兩個才可以。

而且持久化可以跟生產的confirm機制配合起來,只有訊息持久化到了磁碟之後,才會通知生產者ack,這樣就算是在持久化之前rabbitmq掛了,資料丟了,生產者收不到ack回撥也會進行訊息重發。
C:消費者弄丟了資料 使用rabbitmq提供的ack機制,首先關閉rabbitmq的自動ack,然後每次在確保處理完這個訊息之後,在程式碼裡手動呼叫ack。這樣就可以避免訊息還沒有處理完就ack。

(2)kafka
A:消費端弄丟了資料 關閉自動提交offset,在自己處理完畢之後手動提交offset,這樣就不會丟失資料。

B:kafka弄丟了資料 一般要求設定4個引數來保證訊息不丟失:
①給topic設定 replication.factor引數:這個值必須大於1,表示要求每個partition必須至少有2個副本。

②在kafka服務端設定min.isync.replicas引數:這個值必須大於1,表示 要求一個leader至少感知到有至少一個follower在跟自己保持聯絡正常同步資料,這樣才能保證leader掛了之後還有一個follower。

③在生產者端設定acks=all:表示 要求每條每條資料,必須是寫入所有replica副本之後,才能認為是寫入成功了

④在生產者端設定retries=MAX(很大的一個值,表示無限重試):表示 這個是要求一旦寫入事變,就無限重試

C:生產者弄丟了資料 如果按照上面設定了ack=all,則一定不會丟失資料,要求是,你的leader接收到訊息,所有的follower都同步到了訊息之後,才認為本次寫成功了。如果沒滿足這個條件,生產者會自動不斷的重試,重試無限次。

上一篇《如何保證訊息不重複消費
下一篇《如何保證訊息按順序執行

相關文章