消息隊列如何避免消息的重複消費

消息隊列在數據傳輸的過程中,爲了保證消息傳遞的可靠性,一般會對消息採用ack確認機制,如果消息傳遞失敗,消息隊列會進行重試,此時便可能存在消息重複消費的問題。比如,用戶到銀行取錢後會收到扣款通知短信,如果用戶收到多條扣款信息通知則會有困惑。

取款的基本流程消費失敗有很多種可能性,除開業務本身存在的問題來講,消費失敗有可能是因爲網絡延遲,消息隊列還沒收到消費者返回的ack,消息隊列誤認爲消息消費失敗,也有可能是消費者處理消息的時間比較久,來不及返回消費結果給到消息隊列,另外一種則可能是消費者本身掛掉了。

與消息隊列相關的協議和規範有JMS、AMQP、MQTT以及OpenMessage,而MQTT中規定了三種傳遞標準:

At most once:至多一次。消息傳遞時,最多會被送達一次,沒有什麼可靠性,允許消息丟失。
At least once:至少一次。消息傳遞時,至少會被送打一次,保證消息可靠性,但存在多次消費的可能。
Exactly once:恰好一次。消息傳遞時,只會被送達一次,不允許丟失也不允許重複。

依照以上標準,似乎我們只要保證消息隊列符合Exactly once的標準,就可以在保證消息可靠性的前提下解決重複消費的問題。但按我的理解,沒有做好的設計,只有最合適的設計,採用的無非就是時間和空間的切換,採用Exactly once標準固然符合要求,但也勢必會帶來一定的性能損耗,就跟分佈式鎖類似,而對於At least once,我們則可在業務層面保證數據不會重複消費。

用冪等性解決消息重複

所謂冪等性,就是數據無論操作多少次,所產生的影響跟執行一次是一樣的,比如對於讀操作來說,無論讀取多少次數據,都跟讀取一次的數據是一樣的,所以讀操作是一個冪等性操作,而添加操作,添加多次會有多條記錄,因而寫操作則是非冪等性操作。因而對於以上場景,只要保證消息消費的冪等性,就能解決重複消費的問題。

常見的幾種設計冪等的方法:

1. 利用數據庫唯一約束實現冪等

可以通過給消息的某一些屬性設置唯一約束,比如增加唯一uuid,添加的時候查詢是否存對應的uuid,存在不操作,不存在則添加,那樣對於相同的uuid只會存在一條數據。其實,只要類似“insert if not exist”的操作都可能,但需要保證查詢跟添加的操作必須是原子性操作。例如:上面取款發短信的場景則可以藉助redis的setnx實現。

public class SendServiceImpl implements SendService {

    @Autowired
    private JedisClient jedisClient;
    @Value("channel")
    private String channel;

    @Override
    public boolean sendMessage(Message message) {
        String uuid = message.getUuid();
        // 判斷是否已經發送了
        boolean send = jedisClient.setnx(channel, uuid) == 1;
        if(send){
            // TODO 開始發送短信
        }
        return true;
    }
}
2. 設置前置條件

在更新的時候,可以通過設置一定的前置條件來保證數據冪等,比如給用戶發送短信是非冪等操作,但可以添加前置條件,變成如果該用戶未發送過短信,則給用戶發送短信,此時的操作則是冪等性操作。但在實際上,對於一個問題如何獲取前置條件往往比較複雜,此時可以通過設置版本號version,沒修改一次則版本號+1,在更新時則通過判斷兩個數據的版本號是否一致。

UPDATE message SET m_status = #{status} WHERE uuid = #{uuid} AND version = #{version}
3. 通過全局ID實現

最後的方式就比較暴力也比較通用,通過設置全局Id去實現。實現的思路是,在發送消息時,給每條消息指定一個全局唯一的 ID(可以通過雪花算法去實現),消費時,先根據這個 ID 檢查這條消息是否有被消費過,如果沒有消費過,才更新數據。

雖然看起來好像不復雜,單機環境實現也比較簡單,就是查詢更新的思路,但在分佈式環境上一點也不簡單,因爲必須保證查詢跟更新是原子性的操作,不能查詢完又有另外一個事務去更新了數據。當然,對於這種問題也可以通過分佈式事務和分佈式鎖去實現,但與之的也降低了系統的性能。

小結

以上便是”消息隊列如何避免消息的重複消息“的所有內容,整理了網上關於該問題的幾種解決思路,如果有什麼疑問或者文章有什麼問題,歡迎私信留言交流~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章