1. 程式人生 > 其它 >如何保證訊息消費的冪等性

如何保證訊息消費的冪等性

如何保證訊息不被重複消費?

或者說,如何保證訊息消費的冪等性?

業務場景

在本專案中,新增員工介面,會有郵件傳送,在測試介面的過程中,我們可能會有重複增加的操作,相對應的消費者端會收到兩個郵件。

但是我們的使用者已經收到了入職郵件。

顯然這種情況,我們在生產中是不希望發生的。(會被員工在背後罵的,這SB,真麼又來了!)為了避免捱罵,我們還是要做點處理的。

專業點的說,使用者對於同一操作發起的一次請求或者多次請求的結果是一致的。

比如資料庫的樂觀鎖,在執行更新操作前,先去資料庫查詢 version,然後執行更新語句,以version作為條件,如果執行更新時有其他人先

更新了這張表的資料,那麼這個條件就不生效了,也就不會執行操作了,通過這種樂觀鎖的機制來保障冪等性.

Consumer(消費端)冪等性

消費端實現冪等性,就意味著,我們的訊息永遠不會消費多次,即使我們收到了多條一樣的訊息。

在業務高峰期最容易產生訊息重複消費問題,當Con(消費者)消費完訊息時,在給Pro(生產者)返回 ack 時由於網路中斷,導致Pro(生產端)未收到確認資訊,該條訊息就會(被我們的定時任務)重新發送並被Con(消費者)消費,但實際上該消費者已成功消費了該條訊息,這就造成了重複消費。

主流冪等性實現方案

唯一ID+指紋碼

唯一ID +指紋碼機制,利用DB主鍵去重。

SELECT COUNT(1) FROM T_ORDER 
		WHERE ID = 唯一ID +指紋碼
  • 唯一ID:業務表的主鍵
  • 指紋碼:為了區別每次正常操作的碼,每次操作時生成指紋碼;可以用時間戳+業務編號或者標誌位(具體視業務場景而定),在本業務中我們使用的是UUID。

這個方案,實現簡單。但是在高併發的情況下,有資料庫寫入的瓶頸。但是也有解決方案,根據ID進行分庫分表演算法路由。

這個在高併發的解決方案,還要繼續研究。

在本專案的管理系統,其實高併發的情況很少,我們就採用這種方案來避免消費端的冪等性。

具體的實施流程:

  • 首先我們需要根據訊息生成一個全域性唯一ID,然後還需要加上一個指紋碼。這個指紋碼它並不一定是系統去生成的,而是一些外部的規則或者內部的業務規則去拼接,它的目的就是為了保障這次操作是絕對唯一的。

  • 將ID + 指紋碼拼接好的值作為資料庫主鍵,就可以進行去重了。即在消費訊息前呢,先去資料庫查詢這條訊息的指紋碼標識是否存在,沒有就執行insert操作,如果有就代表已經被消費了,就不需要管了

相關的方案

利用Redis的原子性

需要考慮的問題:

  • 是否要進行資料落庫,若落庫,資料庫和快取如何做到原子性(快取(redis是一致性的那和關係型資料庫合併)和資料庫的一致性的問題)
  • 若不落庫,那麼都儲存到快取中,如何設定定時同步策略(如何將快取中的資料同步到資料庫中去)

這裡只提用Redis的原子性去解決MQ冪等性重複消費的問題

MQ的冪等性問題 根本在於的是生產端未正常接收ACK,可能是網路抖動、網路中斷導致

可能的方案

Con在消費開始時將 ID放入到Redis的BitMap中,Pro每次生產資料時,從Redis的BitMap對應位置若不能取出ID,則生產訊息傳送,否則不進行訊息傳送。

但是有人可能會說,萬一Con,ProRedis命令執行失敗了怎麼辦,雖然又出現重複消費又出現Redis非正常執行命令的可能性極低,但是萬一呢?

OK,我們可以在Redis命令執行失敗時,將訊息落庫,每日用定時器,對這種極特殊的訊息進行處理。