您好,登錄后才能下訂單哦!
這篇文章主要介紹“怎么用版本號的方式來保證MQ消費消息的冪等性”,在日常操作中,相信很多人在怎么用版本號的方式來保證MQ消費消息的冪等性問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”怎么用版本號的方式來保證MQ消費消息的冪等性”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
靈魂拷問
MQ消息的消費為什么有時候要求冪等性?
你們都說可以用版本號來解決冪等性消費?
什么才是消息冪等性消費的根本性問題?
隨著系統的復雜性不斷增加,多數系統都會引入MQ來進行解耦,其實從引入MQ的初衷來說,多數系統是為了解耦多個模塊帶來的復雜性,而有些“架構師”卻說的:為了解決性能問題。。。當然我不排除MQ有流量削峰的作用,我只是說大部分系統引入MQ最初的初衷應該是系統解耦。
當一個大的單體系統逐漸被拆分為多個小系統,也就是所謂的微服務拆分之后,無論是微服務之間的通信,還是分布式事務,幾乎都需要MQ的支持,這也充分體現了分布式系統中MQ的重要性。這個時候整個系統間的交互就類似于下圖所示
image
生產消息
既然引入了MQ這個組件,必然意味著同時存在消息的生產者和消費者,這也是典型的訂閱模式。在消息數據的整個生命周期中,會依次經過生產者=》MQ=》消費者,三個主要部分。在生產者角度,消息的可靠投遞是首要的任務,由于網絡的不可靠性,所以消息理論上是不可能100%都投遞成功的,針對這種情況,一般的解決方案就是消息重傳。
當然重傳機制并非無限制的重傳,可以根據業務制定具體的重傳策略,比如:可以設置最大重傳次數為10次,而重傳的時間間隔依次增加。這種方案雖然簡單,但是帶來的副作用就是消息重復投遞的問題。
為什么需要冪等性消費
冪等是一個數學上的概念理論,它的意思是多次執行同一個操作和執行一次操作,最終得到的結果是相同的。
舉一個業務不恰當但是很準確的栗子:你的女朋友出軌一次和出軌多次,對于你來說,結果其實是一樣的:你被綠了。所以出軌一次和出軌多次的結果對于你來說是相同的。
對于MQ來說,退一萬步講,就算MQ的消息無重復投遞的問題,在消費端的業務中,那些對于消息消費敏感的業務,我們在設計程序架構的時候也要把消息的冪等性消費考慮在其中,比如:用戶購買商品贈送紅包或者積分的業務場景,這樣的場景對于消息的重復消費很敏感,如果程序處理不當,出現重復給用戶送紅包的情況,估計程序員又要背鍋來祭天了。
冪等性其實很好做
任何業務場景接口的冪等性設計,都要找出冪等性產生的數據標識。
MQ消息的重復性問題,從消息的整個流轉過程來看,大體上可以在兩個方向來解決:
消息產生的時候避免投遞重復性消息,既:消息生產者來保證消息唯一性
MQ本身提供重復消息的過濾功能
消息被消費的時候避免被重復消費
image
在消息被消費之前的前半部分流程中,生產者可以利用唯一的消息id和ACK機制來做消息被重復投遞的保證工作,但是這樣會大大降低生產者業務的性能,一般情況下生產者都需要異步的來發送MQ消息,如果在發送的時候還需要檢查消息是否被發送過,這無疑不是一個好的設計,而且你這樣做的檢查效果,只為命中很渺小的一部分數據,得不償失,所以在生產者很少有人主動去做消息的重復投遞檢查工作。
至于在MQ的內部,有的MQ確實會提供冪等性的存儲設計,比如Kafka引入了Producer ID(即PID)和Sequence Number。
PID。每個新的Producer在初始化的時候會被分配一個唯一的PID,這個PID對用戶是不可見的。
Sequence Numbler。(對于每個PID,該Producer發送數據的每個都對應一個從0開始單調遞增的Sequence Number。
Broker端在緩存中保存了這seqnumber,對于接收的每條消息,如果其序號比Broker緩存中序號大于1則接受它,否則將其丟棄。這樣就可以實現了消息重復提交了。但是,只能保證單個Producer對于同一個的Exactly Once語義。不能保證同一個Producer一個topic不同的partion冪等。
然而這些都不是我們今天要說的重點,實際的業務中,消息的冪等性消費也更傾向于在消費端做,在消息的終點徹底解決問題,無論是在系統設計,還是在可擴展性上無疑都是最好的。
剛才也提到,消息既然要做到冪等性消費,必須要提供一個用于判斷重復的標識,可以是自定義的消息ID,也可以是消息中幾個字段聯合起來的類似數據表中的主鍵,目前主流的做法是在生產方根據業務特點生成消息id,例如:給用戶添加因為下單而贈送積分的消息id,就可以根據userid_orderId_積分數量來生成唯一的消息id。
有了唯一的消息id,消費者就可以把已經消費的消息id,本地存儲下來用于過濾重復消息,當然如果數據量比較大的話,很早之前的歷史數據完全可以刪除或者轉移到其他的備份表,畢竟同一個消息不可能過了很長時間再次被投遞。以下是一個本地消息表的例子:
字段 | 說明 |
---|---|
MsgId | 消息id |
CreateTime | 創建時間 |
... | 其他有用的業務字段 |
當消費新消息的時候,執行以下類似以下的sql語句,拿到消息是否已經消費過的結果來判斷當前消息是否需要重復消費
select count(0) from table where MsgId='消息id'
當然,這里還會有問題,如果只有一個消費者進行消費,不會有任何問題,如果有多個消費者在并行的進行消費,在判斷重復消息的時候你會需要鎖來保證同樣數據的順序化,這個時候你可能需要分布式鎖。
鄭重提示
除了生成消息id這種方式之外,網上有很多文章指出可以利用版本號來解決冪等性問題,試問:這種方案又有多少人親自實踐過?今天我們就以給用戶添加積分這個案例來庖丁解牛一下這個方案的做法:
用戶的積分表中需要添加版本號(Version)字段
消息的生產者在消息投遞中添加版本號字段
消費者根據消息的版本號來執行sql具體的sql類似:
update user set amount=amount+10 ,version=version+1 where userid=100 and version=1
對于同一條消息的重復投遞來說,這樣做確實可以做到冪等性消費,畢竟程序利用數據庫的鎖機制來保證了一致性。那有什么問題呢?
消息的版本號問題
所有的分布式系統都面臨著同樣的問題,就是數據的一致性問題,MQ的消費場景也不例外。以上邊用戶加積分為案例,因為消息的生產者在投遞消息的時候需要查詢當前的版本號,類似于以下sql
select version from table where userid=100
當查詢到版本號信息自后,會把版本號作為消息體的一部分投遞到MQ,那在并發的情況下會發生什么情況呢?假設當前的版本號為1:
線程A查詢版本號為1,然后投遞了版本號為1,消息id為x的消息,于此同時線程B也查詢了當前用戶版本,數值也為1,然后投遞了消息id為Y的消息,這個時候消費端無論是先消費消息X還是消息Y,數據庫的版本號都會增加,則導致了另外一個消息由于版本號的不符而消費失敗。
到此,關于“怎么用版本號的方式來保證MQ消費消息的冪等性”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。