本文由 yanglbme 首發於 GitHub 技術社區 Doocs,目前 stars 已超 30k。
項目地址:github.com/doocs/advan…java
如何保證消息不被重複消費?或者說,如何保證消息消費的冪等性?git
其實這是很常見的一個問題,這倆問題基本能夠連起來問。既然是消費消息,那確定要考慮會不會重複消費?能不能避免重複消費?或者重複消費了也別形成系統異常能夠嗎?這個是 MQ 領域的基本問題,其實本質上仍是問你使用消息隊列如何保證冪等性,這個是你架構裏要考慮的一個問題。github
回答這個問題,首先你別聽到重複消息這個事兒,就一無所知吧,你先大概說一說可能會有哪些重複消費的問題。面試
首先,好比 RabbitMQ、RocketMQ、Kafka,都有可能會出現消息重複消費的問題,正常。由於這問題一般不是 MQ 本身保證的,是由咱們開發來保證的。挑一個 Kafka 來舉個例子,說說怎麼重複消費吧。數據庫
Kafka 實際上有個 offset 的概念,就是每一個消息寫進去,都有一個 offset,表明消息的序號,而後 consumer 消費了數據以後,每隔一段時間(定時按期),會把本身消費過的消息的 offset 提交一下,表示「我已經消費過了,下次我要是重啓啥的,你就讓我繼續從上次消費到的 offset 來繼續消費吧」。微信
可是凡事總有意外,好比咱們以前生產常常遇到的,就是你有時候重啓系統,看你怎麼重啓了,若是碰到點着急的,直接 kill 進程了,再重啓。這會致使 consumer 有些消息處理了,可是沒來得及提交 offset,尷尬了。重啓以後,少數消息會再次消費一次。架構
舉個栗子。.net
有這麼個場景。數據 1/2/3 依次進入 kafka,kafka 會給這三條數據每條分配一個 offset,表明這條數據的序號,咱們就假設分配的 offset 依次是 152/153/154。消費者從 kafka 去消費的時候,也是按照這個順序去消費。假如當消費者消費了 offset=153
的這條數據,剛準備去提交 offset 到 zookeeper,此時消費者進程被重啓了。那麼此時消費過的數據 1/2 的 offset 並無提交,kafka 也就不知道你已經消費了 offset=153
這條數據。那麼重啓以後,消費者會找 kafka 說,嘿,哥兒們,你給我接着把上次我消費到的那個地方後面的數據繼續給我傳遞過來。因爲以前的 offset 沒有提交成功,那麼數據 1/2 會再次傳過來,若是此時消費者沒有去重的話,那麼就會致使重複消費。3d
若是消費者乾的事兒是拿一條數據就往數據庫裏寫一條,會致使說,你可能就把數據 1/2 在數據庫裏插入了 2 次,那麼數據就錯啦。code
其實重複消費不可怕,可怕的是你沒考慮到重複消費以後,怎麼保證冪等性。
舉個例子吧。假設你有個系統,消費一條消息就往數據庫裏插入一條數據,要是你一個消息重複兩次,你不就插入了兩條,這數據不就錯了?可是你要是消費到第二次的時候,本身判斷一下是否已經消費過了,如果就直接扔了,這樣不就保留了一條數據,從而保證了數據的正確性。
一條數據重複出現兩次,數據庫裏就只有一條數據,這就保證了系統的冪等性。
冪等性,通俗點說,就一個數據,或者一個請求,給你重複來屢次,你得確保對應的數據是不會改變的,不能出錯。
因此第二個問題來了,怎麼保證消息隊列消費的冪等性?
其實仍是得結合業務來思考,我這裏給幾個思路:
固然,如何保證 MQ 的消費是冪等性的,須要結合具體的業務來看。
歡迎關注個人微信公衆號「Doocs開源社區」,原創技術文章第一時間推送。