您好,登錄后才能下訂單哦!
這篇文章給大家介紹消息中間件MQ中消息冪等性是什么,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
我們小伙伴應該都聽說夠消息中間件MQ,如:RabbitMQ,RocketMQ,Kafka等。引入中間件的好處可以起到抗高并發,削峰,業務解耦的作用。
如上圖:
(1)訂單服務投遞消息給MQ中間件 (2)物流服務監聽MQ中間件消息,從而進行消費
我們這篇文章討論一下,如何保障訂單服務把消息成功投遞給MQ中間件,以RabbitMQ舉例。
小伙伴們對此會有些疑問,訂單服務發起消息服務,返回成功不就成功了嗎?如下面的偽代碼:
上面代碼中,一般發送消息就是這么寫的,小伙伴們覺得有什么問題嗎?
下邊說一個場景,如果MQ服務器突然宕機了會出現什么情況?是不是我們訂單服務發過去的消息全部沒有了嗎?是的,一般MQ中間件為了提高系統的吞吐量會把消息保存在內存中,如果不作其他處理,MQ服務器一旦宕機,消息將全部丟失。這個是業務不允許的,造成很大的影響。
有經驗的小伙伴會說,我知道一個方法就是把消息持久化,RabbitMQ中發消息的時候會有個durable參數可以設置,設置為true,就會持久化。
這樣的話MQ服務器即使宕機,重啟后磁盤文件中有消息的存儲,這樣就不會丟失了吧。是的這樣就一定概率的保障了消息不丟失。
但還會有個場景,就是消息剛剛保存到MQ內存中,但還沒有來得及更新到磁盤文件中,突然宕機了。(我靠,這個時間這么短,也會出現,概率太低了吧),這個場景在持續的大量消息投遞的過程中,會很常見。
那怎么辦?我們如何做才能保障一定會持久化到磁盤上面呢?
上面問題出現在,沒有人告訴我們持久化是否成功。好在很多MQ有回調通知的特性,RabbitMQ就有confirm機制來通知我們是否持久化成功?
confirm機制的原理:
(1)消息生產者把消息發送給MQ,如果接收成功,MQ會返回一個ack消息給生產者;
(2)如果消息接收不成功,MQ會返回一個nack消息給生產者;
上面的偽代碼,有兩個處理消息方式,就是ack回調和nack回調。
這樣是不是就可以保障100%消息不丟失了呢?
我們看一下confirm的機制,試想一下,如果我們生產者每發一條消息,都要MQ持久化到磁盤中,然后再發起ack或nack的回調。這樣的話是不是我們MQ的吞吐量很不高,因為每次都要把消息持久化到磁盤中。寫入磁盤這個動作是很慢的,這個在高并發場景下是不能夠接受的,吞吐量太低了。
所以MQ持久化磁盤真實的實現,是通過異步調用處理的,他是有一定的機制,如:等到有幾千條消息的時候,會一次性的刷盤到磁盤上面。而不是每來一條消息,就刷盤一次。
所以comfirm機制其實是一個異步監聽的機制,是為了保證系統的高吞吐量,這樣就導致了還是不能夠100%保障消息不丟失,因為即使加上了confirm機制,消息在MQ內存中還沒有刷盤到磁盤就宕機了,還是沒法處理。
說了這么多,還是沒法確保,那怎么辦呢???
其實本質的原因是無法確定是否持久化?那我們是不是可以自己讓消息持久化呢?答案是可以的,我們的方案再一步的演化。
上圖流程:
(1)訂單服務生產者在投遞消息之前,先把消息持久化到Redis或DB中,建議Redis,高性能。消息的狀態為發送中。
(2)confirm機制監聽消息是否發送成功?如ack成功消息,刪除Redis中此消息。
(3)如果nack不成功的消息,這個可以根據自身的業務選擇是否重發此消息。也可以刪除此消息,由自己的業務決定。
(4)這邊加了個定時任務,來拉取隔一定時間了,消息狀態還是為發送中的,這個狀態就表明,訂單服務是沒有收到ack成功消息。
(5)定時任務會作補償性的投遞消息。這個時候如果MQ回調ack成功接收了,再把Redis中此消息刪除。
這樣的機制其實就是一個補償機制,我不管MQ有沒有真正的接收到,只要我的Redis中的消息狀態也是為【發送中】,就表示此消息沒有正確成功投遞。再啟動定時任務去監控,發起補償投遞。
當然定時任務那邊我們還可以加上一個補償的次數,如果大于3次,還是沒有收到ack消息,那就直接把消息的狀態設置為【失敗】,由人工去排查到底是為什么?
這樣的話方案就比較完美了,保障了100%的消息不丟失(當然不包含磁盤也壞了,可以做主從方案)。
不過這樣的方案,就會有可能發送多次相同的消息,很有可能MQ已經收到了消息,就是ack消息回調時出現網絡故障,沒有讓生產者收到。
那就要要求消費者一定在消費的時候保障冪等性!
我們先了解一下什么叫冪等?在分布式應用中,冪等是非常重要的,也就是相同條件下對一個業務的操作,不管操作多少次,結果都是一樣。
為什么要有冪等這種場景?因為在大的系統中,都是分布式部署,如:訂單業務 和 庫存業務有可能都是獨立部署的,都是單獨的服務。用戶下訂單,會調用到訂單服務和庫存服務。
因為分布式部署,很有可能在調用庫存服務時,因為網絡等原因,訂單服務調用失敗,但其實庫存服務已經處理完成,只是返回給訂單服務處理結果時出現了異常。這個時候一般系統會作補償方案,也就是訂單服務再次放起庫存服務的調用,庫存減1。
這樣就出現了問題,其實上一次調用已經減了1,只是訂單服務沒有收到處理結果。現在又調用一次,又要減1,這樣就不符合業務了,多扣了。
冪等這個概念就是,不管庫存服務在相同條件下調用幾次,處理結果都一樣。這樣才能保證補償方案的可行性。
借鑒數據庫的樂觀鎖機制,如:
唯一ID就是業務表的唯一的主鍵,如商品ID
指紋碼就是為了區別每次正常操作的碼,每次操作時生成指紋碼;可以用時間戳+業務編號的方式。
上面的sql語句:
返回如果為0 表示沒有操作過,那業務操作后就可以insert into t_check(唯一ID+指紋碼)
返回如果大于0 表示操作過,就直接返回
好處:實現簡單
壞處:高并發下數據庫瓶頸
解決方案:根據ID進行分庫分表進行算法路由
利用redis的原子操作,做個操作完成的標記。這個性能就比較好。但會遇到一些問題。
第一:我們是否需要把業務結果進行數據落庫,如果落庫,關鍵解決的問題時數據庫和redis操作如何做到原子性?
這個意思就是庫存減1了,但redis進行操作完成標記時,失敗了怎么辦?也就是一定要保證落庫和redis 要么一起成功,要么一起失敗
第二:如果不進行落庫,那么都存儲到緩存中,如何設置定時同步策略?
這個意思就是庫存減1,不落庫,直接先操作redis操作完成標記,然后由另外的同步服務進行庫存落庫,這個就是增加了系統復雜性,而且同步策略如何設置
關于消息中間件MQ中消息冪等性是什么就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。