首頁  >  文章  >  Java  >  【面試】如何保障訊息100%投遞成功?如何保證訊息冪等性?

【面試】如何保障訊息100%投遞成功?如何保證訊息冪等性?

Java学习指南
Java学习指南轉載
2023-07-26 14:58:33708瀏覽


【面試】如何保障訊息100%投遞成功?如何保證訊息冪等性?

一、前言

我們小夥伴應該都聽說夠訊息中間件MQ,如:RabbitMQ,RocketMQ,Kafka等。引入中間件的好處可以起到抗高並發,削峰,業務解耦的作用。

【面試】如何保障訊息100%投遞成功?如何保證訊息冪等性?

如上圖:

(1)訂單服務投遞訊息給MQ中介軟體(2)物流服務監聽MQ中介軟體,從而進行消費

我們這篇文章討論一下,如何保障訂單服務把訊息成功投遞給MQ中間件,以RabbitMQ舉例。

二、分析問題

小夥伴們對此會有些疑問,訂單服務發起訊息服務,回傳成功不就成功了嗎?如下面的偽代碼:

【面試】如何保障訊息100%投遞成功?如何保證訊息冪等性?

上面程式碼中,一般發送訊息就是這麼寫的,朋友們覺得有什麼問題嗎?

下邊說一個場景,如果MQ伺服器突然宕機了會出現什麼狀況?是不是我們訂單服務寄過去的訊息全部沒有了嗎?是的,一般MQ中間件為了提高系統的吞吐量會把訊息保存在記憶體中,如果不作其他處理,MQ伺服器一旦宕機,訊息就會全部遺失。這個是業務不允許的,造成很大的影響。

三、持久化

有經驗的小夥伴會說,我知道一個方法就是把訊息持久化,RabbitMQ中發送訊息的時候會有個durable參數可以設置,設定為true,就會持久化。

【面試】如何保障訊息100%投遞成功?如何保證訊息冪等性?

這樣的話MQ伺服器即使宕機,重啟後磁碟檔案中有訊息的存儲,這樣就不會遺失了吧。是的這樣就一定機率的保障了訊息不遺失。

但還會有個場景,就是訊息剛剛儲存到MQ記憶體中,但還沒來得及更新到磁碟檔案中,突然宕機了。 (我靠,這個時間這麼短,也會出現,機率太低了吧),這個場景在持續的大量訊息投遞的過程中,會很常見。

那怎麼辦?我們要如何作才能保障一定會持久化到磁碟上面呢?

四、confirm機制

上面問題出現在,沒有人告訴我們持久化是否成功。還好很多MQ有回呼通知的特性,RabbitMQ就有confirm機制來通知我們是否持久化成功?

【面試】如何保障訊息100%投遞成功?如何保證訊息冪等性?

confirm機制的原理:

(1)訊息生產者把訊息傳送給MQ,如果接收成功,MQ會回傳一個ack訊息給生產者;

(2)如果訊息接收不成功,MQ會回傳一個nack訊息給生產者;

【面試】如何保障訊息100%投遞成功?如何保證訊息冪等性?

上面的偽代碼,有兩個處理訊息方式,就是ack回調和nack回調。

這樣是不是就可以保障100%訊息不遺失了呢?

我們來看看confirm的機制,試想一下,如果我們生產者每發一則訊息,都要MQ持久化到磁碟中,然後再發起ack或nack的回呼。這樣的話是不是我們MQ的吞吐量很不高,因為每次都要把訊息持久化到磁碟中。寫入磁碟這個動作是很慢的。這個在高並發場景下是不能夠接受的,吞吐量太低了。

所以MQ持久化磁碟真實的實現,是透過非同步呼叫處理的,他是有一定的機制,如:等到有幾千個訊息的時候,會一次性的刷盤到磁碟上面。而不是每來一則訊息,就刷盤一次。

所以comfirm機制其實是一個非同步監聽的機制,是為了確保系統的高吞吐量,這樣就導致了還是不能夠100%保障訊息不遺失,因為即使加上了confirm機制,訊息在MQ記憶體中還沒刷盤到磁碟就宕機了,還是沒辦法處理。

說了這麼多,還是沒辦法確保,那該怎麼辦呢? ? ?

五、訊息提前持久化 定時任務

#其實本質的原因是無法確定是否持久化?那我們是不是可以自己讓消息持久化呢?答案是可以的,我們的方案再一步的演化。

【面試】如何保障訊息100%投遞成功?如何保證訊息冪等性?

上圖流程:

(1)訂單服務生產者再投遞訊息之前,先把訊息持久化到Redis或DB中,建議Redis ,高性能。訊息的狀態為發送中。

(2)confirm機制監聽訊息是否發送成功?如ack成功訊息,刪除Redis中此訊息。

(3)如果nack不成功的訊息,這個可以根據自身的業務選擇是否重發此訊息。也可以刪除此訊息,由自己的業務決定。

(4)這邊加了個定時任務,來拉取隔一定時間了,訊息狀態還是為發送中的,這個狀態就表明,訂單服務是沒有收到ack成功訊息。

(5)定時任務會作補償性的投遞訊息。這時候如果MQ回呼ack成功接收了,再把Redis中這個訊息刪除。

這樣的機制其實就是補償機制,我不管MQ有沒有真正的接收到,只要我的Redis中的訊息狀態也是為【發送中】,就表示此訊息沒有正確成功投遞。再啟動定時任務去監控,發起補償投遞。

當然定時任務那邊我們還可以加上一個補償的次數,如果大於3次,還是沒有收到ack訊息,那就直接把訊息的狀態設定為【失敗】,由人工去排查到底是為什麼?

這樣的話方案就比較完美了,保障了100%的訊息不遺失(當然不包含磁碟也壞了,可以做主從方案)。

不過這樣的方案,就會有可能發送多次相同的訊息,很有可能MQ已經收到了訊息,就是ack訊息回呼時出現網路故障,沒有讓生產者收到。

那就要要求消費者一定在消費的時候保障冪等性!

六、冪等意義

我們先了解什麼叫冪等?在分散式應用中,冪等是非常重要的,也就是相同條件下對一個業務的操作,不管操作多少次,結果都是一樣。

6.1、為什麼要有冪等這種場景?

為什麼要有冪等這種場景?因為在大型的系統中,都是分散式部署,如:訂單業務 和 庫存業務有可能都是獨立部署的,都是單獨的服務。用戶下訂單,會呼叫到訂單服務和庫存服務。

【面試】如何保障訊息100%投遞成功?如何保證訊息冪等性?

因為分散式部署,很有可能在呼叫庫存服務時,因為網路等原因,訂單服務呼叫失敗,但其實庫存服務已經處理完成,只是返回給訂單服務處理結果時出現了異常。這時候一般系統會作補償方案,也就是訂單服務再此放起庫存服務的調用,庫存減1。

【面試】如何保障訊息100%投遞成功?如何保證訊息冪等性?

這樣就出現了問題,其實上一次呼叫已經減了1,只是訂單服務沒有收到處理結果。現在又呼叫一次,又要減1,這樣就不符合業務了,多扣了。

冪等這個概念就是,不管庫存服務在相同條件下呼叫幾次,處理結果都一樣。這樣才能保證補償方案的可行性。

6.2、樂觀鎖定方案

借鏡資料庫的樂觀鎖定機制,如:

【面試】如何保障訊息100%投遞成功?如何保證訊息冪等性?

根據version版本,也就是在操作庫存前先取得目前商品的version版本號,然後操作的時候帶上此version號。我們梳理下,我們第一次操作庫存時,得到version為1,調用庫存服務version變成了2;但返回給訂單服務出現了問題,訂單服務又一次發起調用庫存服務,當訂單服務傳如的version還是1,再執行上面的sql語句時,就不會執行;因為version已經變成2了,where條件就不成立。這樣就保證了不管呼叫幾次,只會真正的處理一次。

6.3、唯一ID 指紋碼

#原理就是利用資料庫主鍵去重,在業務完成後插入主鍵標識

【面試】如何保障訊息100%投遞成功?如何保證訊息冪等性?
  • #唯一ID就是業務表的唯一的主鍵,如商品ID

  • 指紋碼就是為了區別每次正常操作的碼,每次操作時產生指紋碼;可以用時間戳 業務編號的方式。

上面的sql語句:

  • #傳回如果為0 表示沒有操作過,那麼業務操作後就可以insert into t_check(唯一ID 指紋碼)

  • #返回如果大於0 表示操作過,就直接回傳

##好處:實作簡單

壞處:高並發下資料庫瓶頸

解決方案:根據ID進行分庫分錶進行演算法路由
##### ####6.4、Redis原子操作#########利用redis的原子操作,做個操作完成的標記。這個性能就比較好。但會遇到一些問題。 ######第一:我們是否需要把業務結果進行資料落庫,如果落庫,關鍵解決的問題時資料庫和redis操作如何做到原子性? #########這個意思是庫存減1了,但redis進行操作完成標記時,失敗了怎麼辦?也就是一定要保證落庫和redis 要嘛一起成功,要嘛一起失敗#########第二:如果不進行落庫,那麼都儲存到快取中,如何設定定時同步策略? #########這個意思是庫存減1,不落庫,直接先操作redis操作完成標記,然後由另外的同步服務進行庫存落庫,這個就是增加了系統複雜性,而且同步策略如何設定#########

以上是【面試】如何保障訊息100%投遞成功?如何保證訊息冪等性?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:Java学习指南。如有侵權,請聯絡admin@php.cn刪除