高併發系統設計十六-消息投遞:如何保證消息僅僅被消費一次

消息投遞:如何保證消息僅僅被消費一次

由於系統引入了消息隊列,可能出現 消息在投遞的過程中發生丟失,

1 消息可能在哪裏丟失

消息需要從生產者,到消息隊列,到消費者。在整個鏈路中都有丟失的可能。

  • 消息從消費者寫入到消息隊列的過程
  • 消息在消息隊列中的存儲場景
  • 消息被消費者消費的過程

1.1 在消息產生的過程中丟失信息

消息的生產者一般是 業務服務器,消息隊列是獨立部署在單獨的服務器上,兩個服務之間一般通過內網進行交互,但是可能會出現網絡抖動,消息因爲網絡錯誤而丟失。

1.1.1 採用消息重傳進行解決

發現網絡超時後將消息重新發送一次,但是不可無限制的重傳消息。如果不是消息隊列發生故障或者到消息隊列服務的網絡有問題,重生 2~3 次即可。

消息重複,由於重試發送,可能會造成消息重複。

1.2 在消息隊列中丟失消息

比如,在 Kafka 中,消息是存儲在本地磁盤上的,但是爲了減少消息存儲時對磁盤的隨機 I/O,一般會把消息先寫入操作系統的 Page Cache中,然後再找合適的時機刷新到磁盤上。即Kafka 可以配置當達到某一時間間隔或者積累了一定的消息數量的時候再刷新,異步刷盤

但是如果發生機器掉電或者機器異常重啓,Page Cache 中還沒有來得及刷盤的消息就會丟失。

  • 把刷盤的時間間隔設置短或者設置積累很少的消息就刷盤,這樣會對性能有很大的影響(不推薦使用)
  • 以集羣的方式部署 Kafka 服務,通過部署多個副本備份數據保證消息儘量不丟失。
    • 集羣中 Leader 負責消息的寫入和消費,可有多個 Follower 負責數據的備份。Follower 中有一個特殊的集合叫作 ISR(in-sync replicas),當 Leader 故障時,新選舉出 Leader 會從 ISR 中選擇。但是 Leader 和 Follower 之前的數據異步複製時,也可能存在數據丟失。
    • 爲了解決 Leader 和 Follower 之前數據丟失,Kafka 爲生產者提供了 acks 設置,設置爲 all 時,生產者發送的每一個消息除了發送給 Leader 外還發送給 Follower。必須等所有確定後才被認爲發送成功。

根據自己的業務,判斷消息的丟失容忍,確定自己使用的方式。

1.3 在消費過程中丟失消息

接收消息----》處理消息-----》更新消費進度

一定要等到消息接收和處理完成後才能更新消費進度,但是這也會造成消息重複的問題,比方說某一條消息在處理之後消費者恰好宕機了,那麼因爲沒有更新消費進度,所以當這個消費者重啓之後還會重複地消費這條消息。

2 如何保證消息只被消費一次

在消息的生產和消費過程中都可能會產生重複,所以在這兩個過程中增加消息冪等性。

2.1 消息生產過程中

Kafka 支持 producer idempotency 的特性,即生產過程的冪等性。保證消息雖然可能在生產端重複,但是在消息隊列存儲時只會存儲一份。

它的做法是給每一個生產者一個唯一的 ID,並且爲生產的每一條消息賦予一個唯一 ID,消息隊列的服務端會存儲 < 生產者 ID,最後一條消息 ID> 的映射。當某一個生產者產生新的消息時,消息隊列服務端會比對消息 ID 是否與存儲的最後一條 ID 一致,如果一致就認爲是重複的消息,服務端會自動丟棄。

2.2 在消費端

在通用層和業務層兩個層面來進行考慮。

通用層

在消息被生產的時候使用發號器給它生成一個全局唯一的消息 ID,消息被處理之後把這個 ID 存儲在數據庫中,在處理下一條消息之前查庫是否被消費,如果沒有消費就進行消費。

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

業務層

使用樂觀鎖進行控制。

比如你的消息處理程序需要給一個人的賬號加錢,那麼你可以通過樂觀鎖的方式來解決。

update user set amount = amount + 20, version=version+1 where userId=1 and version=1;

即:我們在更新數據時給數據加了樂觀鎖,這樣在消費第一條消息時,version 值爲 1,SQL 可以執行成功,並且同時把 version 值改爲了 2;在執行第二條相同的消息時,由於 version 值不再是 1,所以這條 SQL 不能執行成功,也就保證了消息的冪等性

3 總結

  • 消息的丟失可以通過生產端的重試、消息隊列配置集羣模式以及消費端合理處理消息進度三種方式來解決
  • 爲了解決消息的丟失通常會造成性能上的問題以及消息的重複問題
  • 通過保證消息處理的冪等性可以解決消息重複問題
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章