保證MQ消息傳遞的一致性 頂 原

1.保證消息傳遞與一致性

1.1生產者確保消息自主性

當生產者發送一條消息時,它必須完成他的所有業務操作。

如下圖:

這保證消費者接受到消息時,生產者已處理完畢相關業務,也就是1PC的基礎。

1.2 MQ保存並轉發消息

消息標記爲持久化,MQ將會利用保存並轉發機制,來履行它與發送者之間的契約。

至於activemq高可用部分,詳見另外一篇blog:https://my.oschina.net/floor/blog/1574213

一般MQ保存並轉發的流程如下:

1.3消費者確認模式的選擇

JMS提供的3中模式:

  • auto_acknowledge:

               消費者接收到消息後,立即自動向MQ發送確認消息。

  • dups_ok_acknowledge

              一條消息可以被多次消費,爲了確保“一次且僅僅一次發送語義”。影響MQ性能。

  • client_acknowledge

             client端確認模式,手動確認消息已消費。

我是如何選擇的:

dups_ok_acknowledge首先被排除,是因爲它影響MQ的性能,我公司使用的activemq的性能本就一般,所以沒有選擇。

client_acknowledge 需要程序員編碼確認消息被消費,可能存在1條消息很久沒有消費掉,MQ堵住的情況,所以沒有選擇。

我們最終選擇了auto_acknowledge ,原因是SImple is best。至於各位如何選擇,按需而定吧!

1.4 漏洞與補救辦法

經過之前的選擇,我們的MQ流程圖如下

稍微看下這個流程,大家就會發現,如果消費者,在自動確認消息後,在還沒有消費消息時,若消費者掛掉了 ,由於MQ在auto_acknowledge下,當前生產者不會重新發送,這就產生了消息不一致的情況,即生產者端已處理,消費者端未處理的問題。

1.4.1. 2種方式處理消息不一致的情況

1.生產者再發一條消息,2.消費者查詢下生產者業務是否完畢。2種方式均可實現。下邊主要說下我公司的方式,方案不唯一僅供參考。

1.4.1.1 生產者再次發送,即定時任務補償+冪等消費的方式

這裏不使用MQ的dups_ok_acknowledge,是因爲會影響MQ的性能。生產者再次發送方式存在3個問題:

  • 生產者如何知道消費者沒有消費一條消息?
  • 生產者重新發送的頻率是多少?
  • 消費者如何處理重複的消息?

生產者如何知道消費者沒有消費一條消息?

我們公司加入了一個event表,作爲生產者與消費者之間的橋樑,用來維護消息的消費狀態。event表的核心字段有,JMS隊列名稱,業務ID(我公司發送的消息都是業務ID,這裏可以是業務bean),完成狀態(完成,未完成),其他字段可按需而定。

生產者重新發送的頻率是多少?

需要根據業務的實時性要求和消費者的能力和可以堆積的信息進行判斷。經過分析與壓測後,設定定時補償的頻率。

消費者如何處理重複的消息?

    爲何會出現重複的消息?

舉例說明:

假設正常情況下一條消息消費需要2s(即單節點一分鐘消費30條),我們3分補償一次。

消息隊列中已堆積了199條消息,第200條爲當前發送的消息,爲了簡單僅僅考慮單一消費者的情況,當3分鐘後,才消費完90條消息,還有堆積110,注意這個時候消費者還沒有消費生產者3分鐘前發送的消息,而補償機制又發送了一條消息進入MQ,這就出現了消費者接受到重複消息的情況。

    如何解決:

我在生產中的解決辦法是在:利用event表,redis實現分佈式事務鎖,實現冪等消費。

當消費者接受到消息後,按如下步驟處理:

  1. 先查詢event中的消息狀態,如果消息存在且未處理,繼續往下,若已處理直接返回。
  2. 利用redis,加鎖,可以使用redission框架,也可以利用String類型的並設置失效時間的簡單方式實現不可重入的鎖,個人推薦推薦redission,但是我公司使用的是String類型的並設置失效時間的簡單方式。
  3. 加鎖成功後,再查詢event中的消息狀態,如果消息存在且未處理,繼續往下,若已處理直接返回。
  4. 消費者處理自己的業務
  5. 更新event表中的狀態爲已處理
  6. 解鎖操作

熟悉併發的朋友,會發現1-3步驟是DCL模型。

綜上所述,以生產者再次發送的方式,保證消費者消費消息的整體流程如下:

看到這個模型圖,可能覺得比較複雜,除了第8步,我們都可以在基類中實現了,並且由於event表數據獨立於MQ,我們可以做一個監控(僅僅自己考慮公司沒有實現),針對event,查詢消息的消費情況,還能實現人工重發功能。以上模式,已在在生產模式中大量使用。

該模式的優點是可以確保消息最終一致性,生產者,MQ,消費者壓力均不大,我們公司利用該模式實現核心業務,例如 票購買後的,拆票操作,追號觸發追一期操作等。

1.4.1.2.消費者進行查詢,推拉結合方式。

按理說消費者可以啓動一個定時任務,查詢生產者需要它消費的數據。模型類似之前,但是我公司並沒有使用這種方式。因爲是不想影響消費者的消費能力。

但是我們在通知業務中,實現了一種簡易的推拉結合的方式,該方式個人認爲使用面比較窄,但對通知業務有一定的適用性,在這裏做下簡要介紹

實現方式:

僅僅提供了一個http接口供用戶查詢,該http接口不一定在生產者,這裏僅僅是畫在生產者中。

其模型圖如下:

這個模式使用的前提是:

消費者不消費數據,也對業務沒有影響。

案例說明:

以通知3D的開獎號碼爲例:

生產者爲抓取服務,當抓取服務,抓到的3D彩果後,針對每一個訂閱者生存獨一無二的消息數據,之後發送MQ。

消費者爲push服務(實際上是調用第三方推送),接收的消息發給訂閱的用戶。

由於消息已入庫,會在通知中心中展示,而用戶是否接受到推送並不重要,他可以在app的消息中心中查詢。

2.擴展,使用Event-Sourcing+MQ解決RPC類型的分佈式事務‘

這個方式,來自於黃勇老師,我們目前在工作中用在用戶支付與訂單狀態更新上,下邊大部分的文字和截圖的,都是來自黃勇老師的《架構探險-輕量級微服務架構下冊》,在這裏感謝黃勇老師的傳道與解惑。

2.1什麼是Event-Sourcing?

它是一種基於事件回溯的解決方案,一般將它應用在領域對象模型中。事件不可枚舉,事件類型可以枚舉。我們以event表示事件表,其大體內容如下:

  • ID event的唯一表示
  • EventType: 事件類型:CREATE,UPDATE,DELETE等。
  • Model Name:表示模型名稱:例如Foo,Bar等。
  • MOdel ID: 模型ID
  • Create Time 創建時間,精確到毫秒。

2.2實現方式:

第一步,操作模型表與時間表並寫入消息隊列

第二步,從消息隊列中獲取事件 ,操作模型表,若有異常,將事件再寫入消息隊列

第三步,從消息隊列中獲取原事件,操作事件表與模型表,進行“事件回溯”

回溯操作的特殊說明:

  • INSERT,的逆向操作爲DELETE,但是一般業務是標記刪除,也就是說逆向操作爲UPDATE。
  • UPDATE,的逆向操作爲UPDATE
  • DELETE,由於一般是標記刪除,所有逆向操作也是UPDATE。

2.3Event-Sourcing和MQ的RPC事務的控制流程圖

總結

這篇文章,是我工作中使用MQ的感悟,可能存在不對的地方,歡迎各位指正。

本文3中模式,

  • 定時補償+冪等消費
  • 推拉結合
  • Event-Sourcing和MQ,實現RPC式分佈式事務,(來自@黃勇 ,老師)

 

 

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