訊息佇列在訊息傳遞的程序中,如果出現傳遞失敗的情況,發送方會重試,在重試的程序中,可能會產生重復的訊息,
訊息重復的情況必然存在
關于傳遞訊息時能夠提供的服務質量標準,MQTT協議給出了三種不同的標準:
- At most once:至多一次,訊息在傳遞時,最多會被送達一次,一般適用于對訊息可靠性要求不高的監控場景,
- At least once:至少一次,訊息在傳遞時,至少會被送達一次,不允許丟訊息,但是允許有少量重復訊息,
- Exactly once:恰好一次,訊息在傳遞時,只會被送達一次,不允許丟失也不允許重復,
我們常用的大部分訊息佇列提供的服務質量都是At least once,包括RocketMQ、RabbitMQ和Kafka,所以說訊息佇列很難保證訊息不重復,
怎么解決訊息重發的問題
既然訊息佇列不可避免的會有訊息重發的問題,那么我們應該怎么去解決呢?
一般解決重復訊息的辦法是在消費端,讓我們的消費訊息的操作具有冪等性,
一個冪等操作的特點是將其任意多次執行所產生的影響與一次執行的回應相同,一個冪等的方法, 使用同樣的引數,對它進行多次呼叫和一次呼叫,對系統產生的影響是樣的,所以我們不需要擔心針對冪等的方法執行多次會對系統造成任何改變,
從對系統的影響結果來看:At least once + 冪等消費 = Exactly once,
如何實作冪等操作呢?可以從業務邏輯設計上少,將消費的業務邏輯設計成具有冪等性的操作,
接下來,我們來看三種不同方式來實作冪等,
利用資料庫的唯一約束實作冪等
首先,我們可以限定,對于每個轉賬單每個賬戶只可以執行一次變更操作,在分布式系統中,這個限制實作的方法非常多,最簡單的是我們在資料庫中建一張轉賬流水表,這個表有三個欄位:轉賬單 ID、賬戶 ID 和變更金額,然后給轉賬單 ID 和賬戶 ID 這兩個欄位聯合起來創建一個唯一約束,這樣對于相同的轉賬單 ID 和賬戶 ID,表里至多只能存在一條記錄,
這樣,我們消費訊息的邏輯可以變為:“在轉賬流水表中增加一條轉賬記錄,然后再根據轉賬記錄,異步操作更新用戶余額即可,”在轉賬流水表增加一條轉賬記錄這個操作中,由于我們在這個表中預先定義了“賬戶ID轉賬單ID”的唯一約束,對于同一個轉賬單同一個賬戶只能插入一條記錄,后續重復的插入操作都會失敗,這樣就實作了一個冪等的操作,我們只要寫一個 SQL,正確地實作它就可以了,
只要是支持類似“INSERT IF NOT EXIST”語意的存盤類系統都可以用于實作冪等操作,
為更新的資料設定前置條件
這種方式的思路是:給資料變更設定一個前置條件,如果滿足條件就更新資料,否則就拒絕更新資料,在更新資料的同時,變更前置條件中需要判斷的資料,這樣在重復執行這個操作時,由于第一次更新資料的時候已經變更了前置條件中需要判斷的依據,不滿足前置條件,則不會重復執行資料更新操作,
如果我們更新的是一個復雜的業務相關資料,我們可以為資料增加一個版本號屬性,,每次更新之前,比較當前資料版本號是否和訊息中的版本號一致,如果不一致就拒絕更新資料,更新資料的同時將版本號加1,
記錄并檢查操作
這種方式的思路是:在發送訊息的時候,給每條訊息指定一個全域唯一ID,消費時,先根據這個ID檢查這條訊息是否有被消費過,如果沒有消費過,才更新資料,不然就將消費狀態置為已消費,
作者:李潘 出處:http://wing011203.cnblogs.com/ 本文著作權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利,轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/546367.html
標籤:其他
上一篇:怎么處理訊息重發的問題?
下一篇:如何正確理解并科學實踐DDD
