這篇文章主要描述如何解決消息重發的問題,目前主流的消息隊列產品都採用了At least once的服務質量,這就導致了很難避免消息重發的情況,我們可以將消費者業務邏輯設計成冪等服務來解決消息重發問題。 ...
消息隊列在消息傳遞的過程中,如果出現傳遞失敗的情況,發送方會重試,在重試的過程中,可能會產生重覆的消息。
消息重覆的情況必然存在
關於傳遞消息時能夠提供的服務質量標準,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/ 本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。