1. 程式人生 > 其它 >使用訊息中介軟體時,如何保證訊息不丟失且僅僅被消費一次

使用訊息中介軟體時,如何保證訊息不丟失且僅僅被消費一次

技術標籤:面試經驗kafka

1、如何保證訊息不丟失

一條訊息從生產到消費這條鏈路中,有三個地方可能會造成訊息丟失,分別如下:

  • 訊息從生產者寫入到訊息佇列的過程投遞失敗。
  • 訊息在訊息佇列中,持久化失敗
  • 訊息被消費者消費的過程出現異常

1.1 在訊息生產過程中投遞失敗
訊息生產者和訊息系統一般都是獨立部署在不同的伺服器上,兩臺伺服器之間要通訊就要通過網路來完成,網路不穩定可能會發生抖動,那麼資料就有可能會丟失,網路發生抖動會有以下兩種情況:

在這裡插入圖片描述

  • 情形一:訊息在傳給訊息系統的過程中會發生網路抖動,資料直接丟失。
  • 情形二:訊息已經達到訊息系統,但是訊息系統再給生產這伺服器返回資訊室,網路發生抖動,此時的資料不一定真正的丟失,很可能只是生產者認為資料丟失。

針對訊息在訊息生產時丟失,可以採用重投機制,當程式檢測到網路異常時,小訊息再次投到訊息系統。但是當重新投遞在情形二情況下,可能造成資料重複,如何解決這個問題後面會提到。

1.2 在訊息佇列中持久化失敗
訊息系統是可以對訊息進行持久化的,一般都是講訊息儲存到本地磁碟中,當然也有少數訊息中介軟體支援資料持久化到資料庫中,那麼訊息系統的效能可能會下降。
如果你對redis的持久化有一定的瞭解的話,你會發現redis在持久化資料時並不是每次新增一條就立即存入本地磁碟,而是將資料先寫入到作業系統的page cache中,當滿足一定條件時,再將page cache中的資料刷入到磁碟。因為這樣可以減少對磁碟的隨機I/O操作,我們知道隨機I/O操作時非常耗時的,這樣也提高了系統的效能,訊息中介軟體也不例外,在持久化時也採用這種方式。

在某些極端情況下,可能會造成page cache中的資料丟失,比如突然斷電或者機器異常重啟操作。要解決pagecache中資料丟失問題,可採用叢集部署的方式,來儘量保證資料不丟失。

1.3 在消費過程中存在訊息丟失
訊息在消費過程中也是會發生丟失的,而且在消費過程中丟失的概率要比前兩種大很多。一條訊息消費過程大概分為三步:

  • 消費者拉取訊息
  • 消費者處理訊息
  • 消費系統更新消費進度
    在這裡插入圖片描述

第一步在訊息拉取訊息時會發生網路抖動異常,第二步在處理訊息的時候可能發生一些業務異常,而導致而導致流程並沒有走完,如果在第一步第二步發生異常的情況下通知訊息系統更新消費進度,那麼這條失敗的訊息就永遠不會再處理了,自然就丟失了,其實我們的業務並沒有跑完。

要避免訊息在消費時丟失的情況,可以在訊息接收和處理完成之後才更新消費進度,但是在極端情況下會出現訊息重複消費的問題,比如某一條訊息在處理完成之後消費者宕機了,這時還沒有更新消費進度,消費者重啟後,這條訊息還是會被消費到。

2、如何保證訊息只被消費一次
訊息系統本身不能保證訊息僅被消費一次,因為:

  • 消費本身可能重複
  • 下游系統啟動拉取重複
  • 失敗重試帶來的重複
  • 補償邏輯導致的重複
    以上幾點都有可能造成重複訊息,要保證訊息僅被消費一次可以利用冪等性來實現

等冪是數學上的概念,就是多次執行同一操作和執行一次操作,最終得到的結果是相同的
從等冪的概念上就可以看出來,就算訊息執行多次也不會對系統造成影響,那麼在使用訊息系統時如何保證冪等性呢?因為生產者和消費者都有可能產生重複訊息,所以要在生產者和消費者兩端都保證等冪性。

  • 保證生產者等冪性

保證生產者等冪性,再生產訊息的時候,利用雪花演算法給訊息生成一個全域性id,在訊息系統中維護訊息與id的對映關係,如果在對映表中已經存在相同id,則丟掉這條訊息,雖然訊息被投遞了兩次,但實際上就儲存了一條,避免了訊息重複問題。
生產者等冪性跟所選的訊息中介軟體有關係,因為絕大多數情況下訊息系統不需要我們自己實現,所以等冪性不太好控制的,消費者等冪性才是我們開發人員控制的重點方向。

  • 保證消費者等冪性

在通用層面,在消費訊息時產生全域性唯一id,訊息被處理成功後,把這個全域性id存入資料庫中,在處理下一條訊息之前,先從資料庫中查詢這個全域性id是否存在,如果存在,則直接放棄該訊息。

利用這個全域性id就實現了訊息等冪性,虛擬碼如下:

boolean isIDExisted = selectByID(ID); // 判斷ID是否存在
if(isIDExisted) {
  return; //存在則直接返回
} else {
  process(message); //不存在,則處理訊息
  saveID(ID);   //儲存ID
}