分析:這個問題其實換一種問法就是,如何保證消息隊列的冪等性?這個問題可以認為是消息隊列領域的基本問題。換句話來說,是在考察你的設計能力,這個問題的回答可以根據具體的業務場景來答,沒有固定的答案。
?
?
回答:先來說一下為什么會造成重復消費?
其實無論是哪種消息隊列,造成重復消費原因其實都是類似的。正常情況下,消費者在消費消息的時候,消費完畢后,會發送一個確認消息給消息隊列,消息隊列就知道該消息被消費了,就會將該消息從消息隊列中刪除。只是不同的消息隊列發出的確認消息形式不同,例如RabbitMQ是發送一個ACK確認消息,RocketMQ是返回一個CONSUME_SUCCESS成功標志,kafka實際上有個offet的概念,簡單說一下,就是每一個消息都有一個offset,kafka消費過消息后,需要提交offset,讓消息隊列知道自己已經消費過了。
那造成重復消費的原因?,就是因為網絡傳輸等等故障,確認信息沒有傳送到消息隊列,導致消息隊列不知道自己已經消費過該消息了,再次將消息分發給其他的消費者。
其實重復消費不可怕,可怕的是你沒考慮到重復消費之后,怎么保證冪等性。 ?
假設你有個系統,消費一條往數據庫里插入一條,要是你一個消息重復兩次,你不就插入了兩條,這數據不就錯了?但是你要是消費到第二次的時候,自己判斷一下已經消費過了,直接扔了,不就保留了一條數據? ? 一條數據重復出現兩次,數據庫里就只有一條數據,這就保證了系統的冪等性 ? 冪等性,我通俗點說,就一個數據,或者一個請求,給你重復來多次,你得確保對應的數據是不會改變的,不能出錯。
如何解決?這個問題針對業務場景來答,分以下三種情況:
(1)比如,你拿到這個消息做數據庫的insert操作,那就容易了,給這個消息做一個唯一的主鍵,那么就算出現重復消費的情況,就會導致主鍵沖突,避免數據庫出現臟數據。
(2)再比如,你拿到這個消息做redis的set的操作,那就容易了,不用解決,因為你無論set幾次結果都是一樣的,set操作本來就算冪等操作。
(3)如果上面兩種情況還不行,上大招。準備一個第三方介質,來做消費記錄。以redis為例,給消息分配一個全局id,只要消費過該消息,將<id,message>以K-V形式寫入redis.那消費者開始消費前,先去redis中查詢有沒有消費記錄即可,先根據這個id去比如redis里查一下,之前消費過嗎?如果沒有消費過,你就處理,然后這個id寫redis。如果消費過了,那你就別處理了,保證別重復處理相同的消息即可。