
點戳藍字“架構之美”關注我們哦!
? 前言?
今天我們聊一個話題,這個話題大家可能在面試過程中,或者是工作當中經常遇到 ?如何保證 Kafka 消息不重復消費?我們在做開發的時候為了程序的健壯性,在使用 Kafka 的時候一般都會設置重試的次數,但是因為網絡的一些原因,設置了重試就有可能導致有些消息重復發送了(當然導致消息重復也有可能是其他原因),那么怎么解決消息重復這個問題呢?關于這個問題,我這兒提供了如下三種解決方案,供大家參考。
?解決方案
方案一? /? 保存并查詢
給每個消息都設置一個獨一無二的 key,消費的時候把 key 記錄下來,然后每次消費新的消息的時候都查詢一下,看當前消息的這個 key 是否消費過,如果沒有消費過才進行消費。(這種方式好想,但是其實實現起來一點也不簡單)

方案二?/??利用冪等冪等(Idempotence)在數學上是這樣定義的,如果一個函數 f(x) 滿足:f(f(x)) = f(x),則函數 f(x) 滿足冪等性。這個概念被拓展到計算機領域,被用來描述一個操作、方法或者服務。一個冪等操作的特點是,其任意多次執行所產生的影響均與一次執行的影響相同。一個冪等的方法,使用同樣的參數,對它進行多次調用和一次調用,對系統產生的影響是一樣的。所以,對于冪等的方法,不用擔心重復執行會對系統造成任何改變。我們舉個例子?來說明一下。在不考慮并發的情況下,“將 X 老師的賬戶余額設置為 100 萬元”,執行一次后對系統的影響是,X 老師的賬戶余額變成了 100 萬元。只要提供的參數 100萬元不變,那即使再執行多少次,X 老師的賬戶余額始終都是 100萬元,不會變化,這個操作就是一個冪等的操作。再舉一個例子?,“將 X 老師的余額加 100 萬元”,這個操作它就不是冪等的,每執行一次,賬戶余額就會增加 100 萬元,執行多次和執行一次對系統的影響(也就是賬戶的余額)是不一樣的。所以,通過這兩個例子,我們可以想到如果系統消費消息的業務邏輯具備冪等性,那就不用擔心消息重復的問題了,因為同一條消息,消費一次和消費多次對系統的影響是完全一樣的。也就可以認為,消費多次等于消費一次。那么,如何實現冪等操作呢?最好的方式就是,從業務邏輯設計上入手,將消費的業務邏輯設計成具備冪等性的操作。但是,不是所有的業務都能設計成天然冪等的,這里就需要一些方法和技巧來實現冪等。下面我們介紹一種常用的方法:利用數據庫的唯一約束實現冪等。例如,我們剛剛提到的那個不具備冪等特性的轉賬的例子:將 X 老師的賬戶余額加 100 萬元。在這個例子中,我們可以通過改造業務邏輯,讓它具備冪等性。首先,我們可以限定,對于每個轉賬單每個賬戶只可以執行一次變更操作,在分布式系統中,這個限制實現的方法非常多,最簡單的是我們在數據庫中建一張轉賬流水表,這個表有三個字段:轉賬單 ID、賬戶 ID 和變更金額,然后給轉賬單 ID 和賬戶 ID 這兩個字段聯合起來創建一個唯一約束,這樣對于相同的轉賬單 ID 和賬戶 ID,表里至多只能存在一條記錄。這樣,我們消費消息的邏輯可以變為:“在轉賬流水表中增加一條轉賬記錄,然后再根據轉賬記錄,異步操作更新用戶余額即可。”在轉賬流水表增加一條轉賬記錄這個操作中,由于我們在這個表中預先定義了“賬戶 ID 轉賬單 ID”的唯一約束,對于同一個轉賬單同一個賬戶只能插入一條記錄,后續重復的插入操作都會失敗,這樣就實現了一個冪等的操作。

方案三 /??設置前置條件
為更新的數據設置前置條件另外一種實現冪等的思路是,給數據變更設置一個前置條件,如果滿足條件就更新數據,否則拒絕更新數據,在更新數據的時候,同時變更前置條件中需要判斷的數據。
這樣,重復執行這個操作時,由于第一次更新數據的時候已經變更了前置條件中需要判斷的數據,不滿足前置條件,則不會重復執行更新數據操作。
比如,剛剛我們說過,“將 X 老師的賬戶的余額增加 100 萬元”這個操作并不滿足冪等性,我們可以把這個操作加上一個前置條件,變為:“如果X老師的賬戶當前的余額為 500萬元,將余額加 100萬元”,這個操作就具備了冪等性。
對應到消息隊列中的使用時,可以在發消息時在消息體中帶上當前的余額,在消費的時候進行判斷數據庫中,當前余額是否與消息中的余額相等,只有相等才執行變更操作。
但是,如果我們要更新的數據不是數值,或者我們要做一個比較復雜的更新操作怎么辦?用什么作為前置判斷條件呢?更加通用的方法是,給你的數據增加一個版本號屬性,每次更數據前,比較當前數據的版本號是否和消息中的版本號一致,如果不一致就拒絕更新數據,更新數據的同時將版本號 +1,一樣可以實現冪等。

?最后
今天給大家提供的消息重復的解決方案,也參考了《消息隊列高手課》里的思路,大家如果有什么好的解決方案,歡迎討論!!大家加油!!

·end·
奈學教育科技技術人成長之路的指路人

點贊支持技術創業?