如何保障消息中間件100%消息投遞成功?如何保證消息冪等性?

如何保障消息中間件100%消息投遞成功?如何保證消息冪等性?

 

1、前言

咱們小夥伴應該都據說夠消息中間件MQ,如:RabbitMQ,RocketMQ,Kafka等。引入中間件的好處能夠起到抗高併發,削峯,業務解耦的做用。redis

如何保障消息中間件100%消息投遞成功?如何保證消息冪等性?

 

 

如上圖:算法

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

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

2、分析問題

小夥伴們對此會有些疑問,訂單服務發起消息服務,返回成功不就成功了嗎?以下面的僞代碼:數據庫

如何保障消息中間件100%消息投遞成功?如何保證消息冪等性?

 

 

上面代碼中,通常發送消息就是這麼寫的,小夥伴們以爲有什麼問題嗎?緩存

下邊說一個場景,若是MQ服務器忽然宕機了會出現什麼狀況?是否是咱們訂單服務發過去的消息所有沒有了嗎?是的,通常MQ中間件爲了提升系統的吞吐量會把消息保存在內存中,若是不做其餘處理,MQ服務器一旦宕機,消息將所有丟失。這個是業務不容許的,形成很大的影響。服務器

3、持久化

有經驗的小夥伴會說,我知道一個方法就是把消息持久化,RabbitMQ中發消息的時候會有個durable參數能夠設置,設置爲true,就會持久化。網絡

如何保障消息中間件100%消息投遞成功?如何保證消息冪等性?

 

 

這樣的話MQ服務器即便宕機,重啓後磁盤文件中有消息的存儲,這樣就不會丟失了吧。是的這樣就必定機率的保障了消息不丟失。併發

但還會有個場景,就是消息剛剛保存到MQ內存中,但尚未來得及更新到磁盤文件中,忽然宕機了。(我靠,這個時間這麼短,也會出現,機率過低了吧),這個場景在持續的大量消息投遞的過程當中,會很常見。異步

那怎麼辦?咱們如何做才能保障必定會持久化到磁盤上面呢?分佈式

4、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內存中尚未刷盤到磁盤就宕機了,仍是無法處理。

說了這麼多,仍是無法確保,那怎麼辦呢???

5、消息提早持久化 + 定時任務

其實本質的緣由是沒法肯定是否持久化?那咱們是否是能夠本身讓消息持久化呢?答案是能夠的,咱們的方案再一步的演化。

如何保障消息中間件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、冪等含義

咱們先了解一下什麼叫冪等?在分佈式應用中,冪等是很是重要的,也就是相同條件下對一個業務的操做,無論操做多少次,結果都是同樣。

6.一、爲何要有冪等這種場景?

爲何要有冪等這種場景?由於在大的系統中,都是分佈式部署,如:訂單業務 和 庫存業務有可能都是獨立部署的,都是單獨的服務。用戶下訂單,會調用到訂單服務和庫存服務。

如何保障消息中間件100%消息投遞成功?如何保證消息冪等性?

 

 

由於分佈式部署,頗有可能在調用庫存服務時,由於網絡等緣由,訂單服務調用失敗,但其實庫存服務已經處理完成,只是返回給訂單服務處理結果時出現了異常。這個時候通常系統會做補償方案,也就是訂單服務再此放起庫存服務的調用,庫存減1。

 

如何保障消息中間件100%消息投遞成功?如何保證消息冪等性?

 

 

 

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

冪等這個概念就是,無論庫存服務在相同條件下調用幾回,處理結果都同樣。這樣才能保證補償方案的可行性。

6.二、樂觀鎖方案

借鑑數據庫的樂觀鎖機制,如:

 

如何保障消息中間件100%消息投遞成功?如何保證消息冪等性?

 

 

 

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

6.三、惟一ID + 指紋碼

原理就是利用數據庫主鍵去重,業務完成後插入主鍵標識

 

如何保障消息中間件100%消息投遞成功?如何保證消息冪等性?

 

 

 

  • 惟一ID就是業務表的惟一的主鍵,如商品ID
  • 指紋碼就是爲了區別每次正常操做的碼,每次操做時生成指紋碼;能夠用時間戳+業務編號的方式。

上面的sql語句:

  • 返回若是爲0 表示沒有操做過,那業務操做後就能夠insert into t_check(惟一ID+指紋碼)
  • 返回若是大於0 表示操做過,就直接返回

好處:實現簡單

壞處:高併發下數據庫瓶頸

解決方案:根據ID進行分庫分表進行算法路由

6.四、Redis原子操做

利用redis的原子操做,作個操做完成的標記。這個性能就比較好。但會遇到一些問題。

第一:咱們是否須要把業務結果進行數據落庫,若是落庫,關鍵解決的問題時數據庫和redis操做如何作到原子性?

這個意思就是庫存減1了,但redis進行操做完成標記時,失敗了怎麼辦?也就是必定要保證落庫和redis 要麼一塊兒成功,要麼一塊兒失敗

第二:若是不進行落庫,那麼都存儲到緩存中,如何設置定時同步策略?

這個意思就是庫存減1,不落庫,直接先操做redis操做完成標記,而後由另外的同步服務進行庫存落庫,這個就是增長了系統複雜性,並且同步策略如何設置
若是想學習Java工程化、高性能及分佈式、深刻淺出。微服務、Spring,MyBatis,Netty源碼分析的朋友能夠加個人Java高級交流:787707172,羣裏有阿里大牛直播講解技術,以及Java大型互聯網技術的視頻免費分享給你們。
相關文章
相關標籤/搜索