Rocketmq原理&最佳實踐

weixin_33830216發表於2018-08-05

一、 MQ背景&選型

訊息佇列作為高併發系統的核心元件之一,能夠幫助業務系統解構提升開發效率和系統穩定性。主要具有以下優勢:

  • 削峰填谷(主要解決瞬時寫壓力大於應用服務能力導致訊息丟失、系統奔潰等問題)
  • 系統解耦(解決不同重要程度、不同能力級別系統之間依賴導致一死全死)
  • 提升效能(當存在一對多呼叫時,可以發一條訊息給訊息系統,讓訊息系統通知相關係統)
  • 蓄流壓測(線上有些鏈路不好壓測,可以通過堆積一定量訊息再放開來壓測)

目前主流的MQ主要是Rocketmq、kafka、Rabbitmq,Rocketmq相比於Rabbitmq、kafka具有主要優勢特性有:
• 支援事務型訊息(訊息傳送和DB操作保持兩方的最終一致性,rabbitmq和kafka不支援)
• 支援結合rocketmq的多個系統之間資料最終一致性(多方事務,二方事務是前提)
• 支援18個級別的延遲訊息(rabbitmq和kafka不支援)
• 支援指定次數和時間間隔的失敗訊息重發(kafka不支援,rabbitmq需要手動確認)
• 支援consumer端tag過濾,減少不必要的網路傳輸(rabbitmq和kafka不支援)
• 支援重複消費(rabbitmq不支援,kafka支援)

Rocketmq、kafka、Rabbitmq的詳細對比,請參照下表格:

12619159-ebd12b24d5ae33d9.png
image.png

二、RocketMQ叢集概述

1. RocketMQ叢集部署結構

12619159-a858d38e0b38c406.png
image.png

1) Name Server

Name Server是一個幾乎無狀態節點,可叢集部署,節點之間無任何資訊同步。

2) Broker

Broker部署相對複雜,Broker分為Master與Slave,一個Master可以對應多個Slave,但是一個Slave只能對應一個Master,Master與Slave的對應關係通過指定相同的Broker Name,不同的Broker Id來定義,BrokerId為0表示Master,非0表示Slave。Master也可以部署多個。

每個Broker與Name Server叢集中的所有節點建立長連線,定時(每隔30s)註冊Topic資訊到所有Name Server。Name Server定時(每隔10s)掃描所有存活broker的連線,如果Name Server超過2分鐘沒有收到心跳,則Name Server斷開與Broker的連線。

3) Producer

Producer與Name Server叢集中的其中一個節點(隨機選擇)建立長連線,定期從Name Server取Topic路由資訊,並向提供Topic服務的Master建立長連線,且定時向Master傳送心跳。Producer完全無狀態,可叢集部署。

Producer每隔30s(由ClientConfig的pollNameServerInterval)從Name server獲取所有topic佇列的最新情況,這意味著如果Broker不可用,Producer最多30s能夠感知,在此期間內發往Broker的所有訊息都會失敗。

Producer每隔30s(由ClientConfig中heartbeatBrokerInterval決定)向所有關聯的broker傳送心跳,Broker每隔10s中掃描所有存活的連線,如果Broker在2分鐘內沒有收到心跳資料,則關閉與Producer的連線。

4) Consumer

Consumer與Name Server叢集中的其中一個節點(隨機選擇)建立長連線,定期從Name Server取Topic路由資訊,並向提供Topic服務的Master、Slave建立長連線,且定時向Master、Slave傳送心跳。Consumer既可以從Master訂閱訊息,也可以從Slave訂閱訊息,訂閱規則由Broker配置決定。

Consumer每隔30s從Name server獲取topic的最新佇列情況,這意味著Broker不可用時,Consumer最多最需要30s才能感知。

Consumer每隔30s(由ClientConfig中heartbeatBrokerInterval決定)向所有關聯的broker傳送心跳,Broker每隔10s掃描所有存活的連線,若某個連線2分鐘內沒有傳送心跳資料,則關閉連線;並向該Consumer Group的所有Consumer發出通知,Group內的Consumer重新分配佇列,然後繼續消費。

當Consumer得到master當機通知後,轉向slave消費,slave不能保證master的訊息100%都同步過來了,因此會有少量的訊息丟失。但是一旦master恢復,未同步過去的訊息會被最終消費掉。

消費者對列是消費者連線之後(或者之前有連線過)才建立的。我們將原生的消費者標識由 {IP}@{消費者group}擴充套件為 {IP}@{消費者group}{topic}{tag},(例如xxx.xxx.xxx.xxx@mqtest_producer-group_2m2sTest_tag-zyk)。任何一個元素不同,都認為是不同的消費端,每個消費端會擁有一份自己消費對列(預設是broker對列數量*broker數量)。新掛載的消費者對列中擁有commitlog中的所有資料。

如果有需要,可以檢視Rocketmq更多原始碼解析

三、 Rocketmq如何支援分散式事務訊息

場景

A(存在DB操作)、B(存在DB操作)兩方需要保證分散式事務一致性,通過引入中間層MQ,A和MQ保持事務一致性(異常情況下通過MQ反查A介面實現check),B和MQ保證事務一致(通過重試),從而達到最終事務一致性。

原理:大事務 = 小事務 + 非同步

1. MQ與DB一致性原理(兩方事務)

流程圖

12619159-6f4f6754d6f02058.png
image.png

上圖是RocketMQ提供的保證MQ訊息、DB事務一致性的方案。

MQ訊息、DB操作一致性方案:

1)傳送訊息到MQ伺服器,此時訊息狀態為SEND_OK。此訊息為consumer不可見。

2)執行DB操作;DB執行成功Commit DB操作,DB執行失敗Rollback DB操作。

3)如果DB執行成功,回覆MQ伺服器,將狀態為COMMIT_MESSAGE;如果DB執行失敗,回覆MQ伺服器,將狀態改為ROLLBACK_MESSAGE。注意此過程有可能失敗。

4)MQ內部提供一個名為“事務狀態服務”的服務,此服務會檢查事務訊息的狀態,如果發現訊息未COMMIT,則通過Producer啟動時註冊的TransactionCheckListener來回撥業務系統,業務系統在checkLocalTransactionState方法中檢查DB事務狀態,如果成功,則回覆COMMIT_MESSAGE,否則回覆ROLLBACK_MESSAGE。

說明:

上面以DB為例,其實此處可以是任何業務或者資料來源。

以上SEND_OK、COMMIT_MESSAGE、ROLLBACK_MESSAGE均是client jar提供的狀態,在MQ伺服器內部是一個數字。

TransactionCheckListener 是在訊息的commit或者rollback訊息丟失的情況下才會回撥(上圖中灰色部分)。這種訊息丟失只存在於斷網或者rocketmq叢集掛了的情況下。當rocketmq叢集掛了,如果採用非同步刷盤,存在1s內資料丟失風險,非同步刷盤場景下保障事務沒有意義。所以如果要核心業務用Rocketmq解決分散式事務問題,建議選擇同步刷盤模式。

2. 多系統之間資料一致性(多方事務)

12619159-cb4ce1a4c8b79fb1.png
image.png

當需要保證多方(超過2方)的分散式一致性,上面的兩方事務一致性(通過Rocketmq的事務性訊息解決)已經無法支援。這個時候需要引入TCC模式思想(Try-Confirm-Cancel,不清楚的自行百度)。

以上圖交易系統為例:

1)交易系統建立訂單(往DB插入一條記錄),同時傳送訂單建立訊息。通過RocketMq事務性訊息保證一致性

2)接著執行完成訂單所需的同步核心RPC服務(非核心的系統通過監聽MQ訊息自行處理,處理結果不會影響交易狀態)。執行成功更改訂單狀態,同時傳送MQ訊息。

3)交易系統接受自己傳送的訂單建立訊息,通過定時排程系統建立延時回滾任務(或者使用RocketMq的重試功能,設定第二次傳送時間為定時任務的延遲建立時間。在非訊息堵塞的情況下,訊息第一次到達延遲為1ms左右,這時可能RPC還未執行完,訂單狀態還未設定為完成,第二次消費時間可以指定)。延遲任務先通過查詢訂單狀態判斷訂單是否完成,完成則不建立回滾任務,否則建立。 PS:多個RPC可以建立一個回滾任務,通過一個消費組接受一次訊息就可以;也可以通過建立多個消費組,一個訊息消費多次,每次消費建立一個RPC的回滾任務。 回滾任務失敗,通過MQ的重發來重試。

以上是交易系統和其他系統之間保持最終一致性的解決方案。

3.案例分析

1) 單機環境下的事務示意圖

如下為A給B轉賬的例子。

步驟 動作
1 鎖定A的賬戶
2 鎖定B的賬戶
3 檢查A賬戶是否有1元
4 A的賬戶扣減1元
5 給B的賬戶加1元
6 解鎖B的賬戶
7 解鎖A的賬戶

以上過程在程式碼層面甚至可以簡化到在一個事物中執行兩條sql語句。

2) 分散式環境下事務

和單機事務不同,A、B賬戶可能不在同一個DB中,此時無法像在單機情況下使用事物來實現。此時可以通過一下方式實現,將轉賬操作分成兩個操作。

a) A賬戶

步驟 動作
1 鎖定A的賬戶
2 檢查A賬戶是否有1元
3 A的賬戶扣減1元
4 解鎖A的賬戶

b) MQ訊息
A賬戶資料發生變化時,傳送MQ訊息,MQ伺服器將訊息推送給轉賬系統,轉賬系統來給B賬號加錢。

c) B賬戶

步驟 動作
1 鎖定B的賬戶
2 給B的賬戶加1元
3 解鎖B的賬戶

四、 順序訊息

1. 順序訊息缺陷

傳送順序訊息無法利用叢集Fail Over特性消費順序訊息的並行度依賴於佇列數量佇列熱點問題,個別佇列由於雜湊不均導致訊息過多,消費速度跟不上,產生訊息堆積問題遇到訊息失敗的訊息,無法跳過,當前佇列消費暫停。

2. 原理

produce在傳送訊息的時候,把訊息發到同一個佇列(queue)中,消費者註冊訊息監聽器為MessageListenerOrderly,這樣就可以保證消費端只有一個執行緒去消費訊息。

注意:把訊息發到同一個佇列(queue),不是同一個topic,預設情況下一個topic包括4個queue

3. 擴充套件

可以通過實現傳送訊息的對列選擇器方法,實現部分順序訊息。

舉例:比如一個資料庫通過MQ來同步,只需要保證每個表的資料是同步的就可以。解析binlog,將表名作為對列選擇器的引數,這樣就可以保證每個表的資料到同一個對列裡面,從而保證表資料的順序消費

五、 最佳實踐

1. Producer

1) Topic

一個應用盡可能用一個Topic,訊息子型別用tags來標識,tags可以由應用自由設定。只有傳送訊息設定了tags,消費方在訂閱訊息時,才可以利用tags 在broker做訊息過濾。

2) key

每個訊息在業務層面的唯一標識碼,要設定到 keys 欄位,方便將來定位訊息丟失問題。伺服器會為每個訊息建立索引(雜湊索引),應用可以通過 topic,key來查詢這條訊息內容,以及訊息被誰消費。由於是雜湊索引,請務必保證key 儘可能唯一,這樣可以避免潛在的雜湊衝突。

//訂單Id

String orderId= "20034568923546";

message.setKeys(orderId);

3) 日誌

訊息傳送成功或者失敗,要列印訊息日誌,務必要列印 send result 和key 欄位。

4) send

send訊息方法,只要不拋異常,就代表傳送成功。但是傳送成功會有多個狀態,在sendResult裡定義。

SEND_OK:訊息傳送成功

FLUSH_DISK_TIMEOUT:訊息傳送成功,但是伺服器刷盤超時,訊息已經進入伺服器佇列,只有此時伺服器當機,訊息才會丟失

FLUSH_SLAVE_TIMEOUT:訊息傳送成功,但是伺服器同步到Slave時超時,訊息已經進入伺服器佇列,只有此時伺服器當機,訊息才會丟失

SLAVE_NOT_AVAILABLE:訊息傳送成功,但是此時slave不可用,訊息已經進入伺服器佇列,只有此時伺服器當機,訊息才會丟失

2. Consumer

1) 冪等

RocketMQ使用的訊息原語是At Least Once,所以consumer可能多次收到同一個訊息,此時務必做好冪等。

2) 日誌

消費時記錄日誌,以便後續定位問題。

3) 批量消費

儘量使用批量方式消費方式,可以很大程度上提高消費吞吐量。

六、 參考資料

1. 文件

RocketMQ_design.pdf
RocketMQ_experience.pdf

2. 部落格

分散式開放訊息系統(RocketMQ)的原理與實踐

http://www.jianshu.com/p/453c6e7ff81c

RocketMQ事務消費和順序消費詳解

http://www.cnblogs.com/520playboy/p/6750023.html

ZeroCopy

http://www.linuxjournal.com/article/6345

IO方式的效能資料

http://stblog.baidu-tech.com/?p=851

相關文章