你還不知道RocketMQ(超詳細)?

一. 簡介

  1. 消息隊列的優勢
    消息隊列作爲高併發系統的核心組件之一,能夠幫助業務系統解構提升開發效率和系統穩定性。主要具有以下優勢:
  • 削峯填谷(主要解決瞬時寫壓力大於應用服務能力導致消息丟失、系統奔潰等問題)
  • 系統解耦(解決不同重要程度、不同能力級別系統之間依賴導致一死全死)
  • 提升性能(當存在一對多調用時,可以發一條消息給消息系統,讓消息系統通知相關係統)
  • 蓄流壓測(線上有些鏈路不好壓測,可以通過堆積一定量消息再放開來壓測)
  1. RocketMQ的優勢
    目前主流的MQ主要是RocketMQ、Kafka、RabbitMQ,RocketMQ相比於RabbitMQ、Kafka具有主要優勢特性有:
  • 支持事務型消息(消息發送和DB操作保持兩方的最終一致性,RabbitMQ和Kafka不支持)
  • 支持結合RocketMQ的多個系統之間數據最終一致性(多方事務,二方事務是前提)
  • 支持18個級別的延遲消息(RabbitMQ和Kafka不支持)
  • 支持指定次數和時間間隔的失敗消息重發(Kafka不支持,RabbitMQ需要手動確認)
  • 支持consumer端tag過濾,減少不必要的網絡傳輸(RabbitMQ和Kafka不支持)
  • 支持重複消費(RabbitMQ不支持,Kafka支持)

二. 功能介紹

  • 發佈/訂閱消息傳遞模型
  • 財務級交易消息
  • 各種跨語言客戶端,例如Java,C / C ++,Python,Go
  • 可插拔的傳輸協議,例如TCP,SSL,AIO
  • 內置的消息跟蹤功能,還支持開放式跟蹤
  • 多功能的大數據和流生態系統集成
  • 按時間或偏移量追溯消息
  • 可靠的FIFO和嚴格的有序消息傳遞在同一隊列中
  • 高效的推拉消費模型
  • 單個隊列中的百萬級消息累積容量
  • 多種消息傳遞協議,例如JMS和OpenMessaging
  • 靈活的分佈式橫向擴展部署架構
  • 快如閃電的批量消息交換系統
  • 各種消息過濾器機制,例如SQL和Tag
  • 用於隔離測試和雲隔離羣集的Docker映像
  • 功能豐富的管理儀表板,用於配置,指標和監視
  • 認證與授權

三. 項目結構

在這裏插入圖片描述
他的核心模塊:

  • rocketmq-broker:接受生產者發來的消息並存儲(通過調用rocketmq-store),消費者從這裏取得消息
  • rocketmq-client:提供發送、接受消息的客戶端API。
  • rocketmq-namesrv:NameServer,類似於Zookeeper,這裏保存着消息的TopicName,隊列等運行時的元信息。
  • rocketmq-common:通用的一些類,方法,數據結構等。
  • rocketmq-remoting:基於Netty4的client/server + fastjson序列化 + 自定義二進制協議。
  • rocketmq-store:消息、索引存儲等。
  • rocketmq-filtersrv:消息過濾器Server,需要注意的是,要實現這種過濾,需要上傳代碼到MQ!(一般而言,我們利用Tag足以滿足大部分的過濾需求,如果更靈活更復雜的過濾需求,可以考慮filtersrv組件)。
  • rocketmq-tools:命令行工具。

四. 四大核心組成

四大核心組成部分:NameServer、Broker、Producer以及Consumer
在這裏插入圖片描述
Tip:我們可以看到RocketMQ啥都是集羣部署的,這是他吞吐量大、高可用的原因之一,集羣的模式也很花哨,可以支持多master 模式、多master多slave異步複製模式、多 master多slave同步雙寫模式。

1. NameServer

主要負責對於源數據的管理,包括了對於Topic和路由信息的管理。

NameServer是一個功能齊全的服務器,其角色類似Dubbo中的Zookeeper,但NameServer與Zookeeper相比更輕量。主要是因爲每個NameServer節點之間是互相獨立的,沒有任何信息交互

NameServer壓力不會太大,平時主要開銷是在維持心跳和提供Topic-Broker的關係數據

但有一點需要注意,Broker向NameServer發心跳時, 會帶上當前自己所負責的所有Topic信息,如果Topic個數太多(萬級別),會導致一次心跳中,就Topic的數據就幾十M,網絡情況差的話, 網絡傳輸失敗,心跳失敗,導致NameServer誤認爲Broker心跳失敗。

NameServer 被設計成幾乎無狀態的,可以橫向擴展,節點之間相互之間無通信,通過部署多臺機器來標記自己是一個僞集羣。

每個 Broker 在啓動的時候會到 NameServer 註冊,Producer 在發送消息前會根據 Topic 到 NameServer 獲取到 Broker 的路由信息,Consumer 也會定時獲取 Topic 的路由信息。

我們看一下Dubbo中註冊中心的角色,是不是真的一毛一樣:
在這裏插入圖片描述

2. Broker

消息生產者,負責產生消息,一般由業務系統負責產生消息。

  • Producer由用戶進行分佈式部署,消息由Producer通過多種負載均衡模式發送到Broker集羣,發送低延時,支持快速失敗。

  • RocketMQ 提供了三種方式發送消息:同步、異步和單向

  • 同步發送:同步發送指消息發送方發出數據後會在收到接收方發回響應之後才發下一個數據包。一般用於重要通知消息,例如重要通知郵件、營銷短信。

  • 異步發送:異步發送指發送方發出數據後,不等接收方發回響應,接着發送下個數據包,一般用於可能鏈路耗時較長而對響應時間敏感的業務場景,例如用戶視頻上傳後通知啓動轉碼服務。

  • 單向發送:單向發送是指只負責發送消息而不等待服務器迴應且沒有回調函數觸發,適用於某些耗時非常短但對可靠性要求並不高的場景,例如日誌收集。

3. Producer

消息中轉角色,負責存儲消息,轉發消息。

  • Broker是具體提供業務的服務器,單個Broker節點與所有的NameServer節點保持長連接及心跳,並會定時將Topic信息註冊到NameServer,順帶一提底層的通信和連接都是基於Netty實現的。

  • Broker負責消息存儲,以Topic爲緯度支持輕量級的隊列,單機可以支撐上萬隊列規模,支持消息推拉模型。
    官網上有數據顯示:具有上億級消息堆積能力,同時可嚴格保證消息的有序性。

4. Consumer

消息消費者,負責消費消息,一般是後臺系統負責異步消費。

  • Consumer也由用戶部署,支持PUSH和PULL兩種消費模式,支持集羣消費和廣播消費,提供實時的消息訂閱機制。

  • Pull:拉取型消費者(Pull Consumer)主動從消息服務器拉取信息,只要批量拉取到消息,用戶應用就會啓動消費過程,所以 Pull 稱爲主動消費型。

  • Push:推送型消費者(Push Consumer)封裝了消息的拉取、消費進度和其他的內部維護工作,將消息到達時執行的回調接口留給用戶應用程序來實現。所以 Push 稱爲被動消費類型,但從實現上看還是從消息服務器中拉取消息,不同於 Pull 的是 Push 首先要註冊消費監聽器,當監聽器處觸發後纔開始消費消息。

五. 消息領域模型

在這裏插入圖片描述

1. Message

Message(消息)就是要傳輸的信息。

一條消息必須有一個主題(Topic),主題可以看做是你的信件要郵寄的地址

一條消息也可以擁有一個可選的標籤(Tag)和額處的鍵值對,它們可以用於設置一個業務 Key 並在 Broker 上查找此消息以便在開發期間查找問題。

2. Topic

Topic(主題)可以看做消息的規類,它是消息的第一級類型。比如一個電商系統可以分爲:交易消息、物流消息等,一條消息必須有一個 Topic 。

Topic 與生產者和消費者的關係非常鬆散,一個 Topic 可以有0個、1個、多個生產者向其發送消息,一個生產者也可以同時向不同的 Topic 發送消息。

一個 Topic 也可以被 0個、1個、多個消費者訂閱。

3. Tag

Tag(標籤)可以看作子主題,它是消息的第二級類型,用於爲用戶提供額外的靈活性。使用標籤,同一業務模塊不同目的的消息就可以用相同 Topic 而不同的 Tag 來標識。比如交易消息又可以分爲:交易創建消息、交易完成消息等,一條消息可以沒有 Tag

標籤有助於保持您的代碼乾淨和連貫,並且還可以爲 RocketMQ 提供的查詢系統提供幫助。

4. Group

分組,一個組可以訂閱多個Topic

分爲ProducerGroup,ConsumerGroup,代表某一類的生產者和消費者,一般來說同一個服務可以作爲Group,同一個Group一般來說發送和消費的消息都是一樣的

5. Queue

在Kafka中叫Partition,每個Queue內部是有序的,在RocketMQ中分爲讀和寫兩種隊列,一般來說讀寫隊列數量一致,如果不一致就會出現很多問題。

6. Message Queue

Message Queue(消息隊列),主題被劃分爲一個或多個子主題,即消息隊列。

一個 Topic 下可以設置多個消息隊列,發送消息時執行該消息的 Topic ,RocketMQ 會輪詢該 Topic 下的所有隊列將消息發出去。

消息的物理管理單位。一個Topic下可以有多個Queue,Queue的引入使得消息的存儲可以分佈式集羣化,具有了水平擴展能力。

7. Offset

在RocketMQ 中,所有消息隊列都是持久化,長度無限的數據結構,所謂長度無限是指隊列中的每個存儲單元都是定長,訪問其中的存儲單元使用Offset 來訪問,Offset 爲 java long 類型,64 位,理論上在 100年內不會溢出,所以認爲是長度無限。

也可以認爲 Message Queue 是一個長度無限的數組,Offset 就是下標。

六. 消息消費模式

消息消費模式有兩種:Clustering(集羣消費)Broadcasting(廣播消費)

默認情況下就是集羣消費,該模式下一個消費者集羣共同消費一個主題的多個隊列,一個隊列只會被一個消費者消費,如果某個消費者掛掉,分組內其它消費者會接替掛掉的消費者繼續消費

而廣播消費消息會發給消費者組中的每一個消費者進行消費。

七. Message Order

Message Order(消息順序)有兩種:Orderly(順序消費)Concurrently(並行消費)

順序消費表示消息消費的順序同生產者爲每個消息隊列發送的順序一致,所以如果正在處理全局順序是強制性的場景,需要確保使用的主題只有一個消息隊列。

並行消費不再保證消息順序,消費的最大並行數量受每個消費者客戶端指定的線程池限制。

八. 一次完整的通信流程

Producer 與 NameServer集羣中的其中一個節點(隨機選擇)建立長連接,定期從 NameServer 獲取 Topic 路由信息,並向提供 Topic 服務的 Broker Master 建立長連接,且定時向 Broker 發送心跳。

Producer 只能將消息發送到 Broker master,但是 Consumer 則不一樣,它同時和提供 Topic 服務的 Master 和 Slave建立長連接,既可以從 Broker Master 訂閱消息,也可以從 Broker Slave 訂閱消息。

具體如下圖:
在這裏插入圖片描述

1. NameService啓動流程

在org.apache.rocketmq.namesrv目錄下的NamesrvStartup這個啓動類基本上描述了他的啓動過程我們可以看一下代碼:

  • 第一步是初始化配置

  • 創建NamesrvController實例,並開啓兩個定時任務:

  • 每隔10s掃描一次Broker,移除處於不激活的Broker;

  • 每隔10s打印一次KV配置。
    在這裏插入圖片描述

  • 第三步註冊鉤子函數,啓動服務器並監聽Broker。

2. Producer

鏈路很長涉及的細節也多,我就發一下鏈路圖。
在這裏插入圖片描述
Producer是消息發送方,那他怎麼發送的呢?

通過輪訓,Producer輪訓某個Topic下面的所有隊列實現發送方的負載均衡
在這裏插入圖片描述

2. Broker

Broker在RocketMQ中是進行處理Producer發送消息請求,Consumer消費消息的請求,並且進行消息的持久化,以及HA策略和服務端過濾,就是集羣中很重的工作都是交給了Broker進行處理。

Broker模塊是通過BrokerStartup進行啓動的,會實例化BrokerController,並且調用其初始化方法
在這裏插入圖片描述
大家去看Broker的源碼的話會發現,他的初始化流程很冗長,會根據配置創建很多線程池主要用來發送消息、拉取消息、查詢消息、客戶端管理和消費者管理,也有很多定時任務,同時也註冊了很多請求處理器,用來發送拉取消息查詢消息的。

3. Consumer

在這裏插入圖片描述
Consumer是消息接受,那他怎麼接收消息的呢?
在這裏插入圖片描述
消費端會通過RebalanceService線程,10秒鐘做一次基於Topic下的所有隊列負載。

九. 面試常見問題分析

1. 優缺點

RocketMQ優點:

  • 單機吞吐量:十萬級

  • 可用性:非常高,分佈式架構

  • 消息可靠性:經過參數優化配置,消息可以做到0丟失

  • 功能支持:MQ功能較爲完善,還是分佈式的,擴展性好

  • 支持10億級別的消息堆積,不會因爲堆積導致性能下降

  • 源碼是java,我們可以自己閱讀源碼,定製自己公司的MQ,可以掌控

  • 天生爲金融互聯網領域而生,對於可靠性要求很高的場景,尤其是電商裏面的訂單扣款,以及業務削峯,在大量交易湧入時,後端可能無法及時處理的情況

  • RoketMQ在穩定性上可能更值得信賴,這些業務場景在阿里雙11已經經歷了多次考驗,如果你的業務有上述併發場景,建議可以選擇RocketMQ

RocketMQ缺點:

  • 支持的客戶端語言不多,目前是java及c++,其中c++不成熟

  • 社區活躍度不是特別活躍那種

  • 沒有在 mq 核心中去實現JMS等接口,有些系統要遷移需要修改大量代碼

2. 消息去重

去重原則:使用業務端邏輯保持冪等性

冪等性:就是用戶對於同一操作發起的一次請求或者多次請求的結果是一致的,不會因爲多次點擊而產生了副作用,數據庫的結果都是唯一的,不可變的。

只要保持冪等性,不管來多少條重複消息,最後處理的結果都一樣,需要業務端來實現。

去重策略:保證每條消息都有唯一編號(比如唯一流水號),且保證消息處理成功與去重表的日誌同時出現。

建立一個消息表,拿到這個消息做數據庫的insert操作。給這個消息做一個唯一主鍵(primary key)或者唯一約束,那麼就算出現重複消費的情況,就會導致主鍵衝突,那麼就不再處理這條消息。

消息重複
消息領域有一個對消息投遞的QoS定義,分爲:

  • 最多一次(At most once)
  • 至少一次(At least once)
  • 僅一次( Exactly once)

QoS:Quality of Service,服務質量

幾乎所有的MQ產品都聲稱自己做到了At least once。

既然是至少一次,那避免不了消息重複,尤其是在分佈式網絡環境下。

比如:網絡原因閃斷,ACK返回失敗等等故障,確認信息沒有傳送到消息隊列,導致消息隊列不知道自己已經消費過該消息了,再次將該消息分發給其他的消費者。

不同的消息隊列發送的確認信息形式不同,例如RabbitMQ是發送一個ACK確認消息,RocketMQ是返回一個CONSUME_SUCCESS成功標誌,Kafka實際上有個offset的概念。

RocketMQ沒有內置消息去重的解決方案,最新版本是否支持還需確認。

3. 消息的可用性

當我們選擇好了集羣模式之後,那麼我們需要關心的就是怎麼去存儲和複製這個數據,RocketMQ對消息的刷盤提供了同步和異步的策略來滿足我們的,當我們選擇同步刷盤之後,如果刷盤超時會給返回FLUSH_DISK_TIMEOUT,如果是異步刷盤不會返回刷盤相關信息,選擇同步刷盤可以盡最大程度滿足我們的消息不會丟失。

除了存儲有選擇之後,我們的主從同步提供了同步和異步兩種模式來進行復制,當然選擇同步可以提升可用性,但是消息的發送RT時間會下降10%左右。

RocketMQ採用的是混合型的存儲結構,即爲Broker單個實例下所有的隊列共用一個日誌數據文件(即爲CommitLog)來存儲。

而Kafka採用的是獨立型的存儲結構,每個隊列一個文件。

RocketMQ採用混合型存儲結構的缺點在於,會存在較多的隨機讀操作,因此讀的效率偏低。同時消費消息需要依賴ConsumeQueue,構建該邏輯消費隊列需要一定開銷。

4. RocketMQ 刷盤實現

Broker 在消息的存取時直接操作的是內存(內存映射文件),這可以提供系統的吞吐量,但是無法避免機器掉電時數據丟失,所以需要持久化到磁盤中。

刷盤的最終實現都是使用NIO中的 MappedByteBuffer.force() 將映射區的數據寫入到磁盤,如果是同步刷盤的話,在Broker把消息寫到CommitLog映射區後,就會等待寫入完成。

異步而言,只是喚醒對應的線程,不保證執行的時機,流程如圖所示。
在這裏插入圖片描述

5. 順序消息

我簡單的說一下我們使用的RocketMQ裏面的一個簡單實現吧。

Tip:爲啥用RocketMQ舉例呢,這玩意是阿里開源的,我問了下身邊的朋友很多公司都有使用,所以讀者大概率是這個的話我就用這個舉例吧,具體的細節我後面會在RocketMQ和Kafka各自章節說到。

生產者消費者一般需要保證順序消息的話,可能就是一個業務場景下的,比如訂單的創建、支付、發貨、收貨。

那這些東西是不是一個訂單號呢?一個訂單的肯定是一個訂單號的說,那簡單了呀。

一個topic下有多個隊列,爲了保證發送有序,RocketMQ提供了MessageQueueSelector隊列選擇機制,他有三種實現:
在這裏插入圖片描述
我們可使用Hash取模法,讓同一個訂單發送到同一個隊列中,再使用同步發送,只有同個訂單的創建消息發送成功,再發送支付消息。這樣,我們保證了發送有序。

RocketMQ的topic內的隊列機制,可以保證存儲滿足FIFO(First Input First Output 簡單說就是指先進先出),剩下的只需要消費者順序消費即可。

RocketMQ僅保證順序發送,順序消費由消費者業務保證!!!

這裏很好理解,一個訂單你發送的時候放到一個隊列裏面去,你同一個的訂單號Hash一下是不是還是一樣的結果,那肯定是一個消費者消費,那順序是不是就保證了?

真正的順序消費不同的中間件都有自己的不同實現我這裏就舉個例子,大家思路理解下。

6. 分佈式事務

Half Message(半消息)
是指暫不能被Consumer消費的消息。Producer 已經把消息成功發送到了 Broker 端,但此消息被標記爲暫不能投遞狀態,處於該種狀態下的消息稱爲半消息。需要 Producer

對消息的二次確認後,Consumer才能去消費它。

消息回查
由於網絡閃段,生產者應用重啓等原因。導致 Producer 端一直沒有對 Half Message(半消息) 進行 二次確認。這是Brock服務器會定時掃描長期處於半消息的消息,會

主動詢問 Producer端 該消息的最終狀態(Commit或者Rollback),該消息即爲 消息回查。
在這裏插入圖片描述

  • 服務先發送個Half Message給Brock端,消息中攜帶 B服務 即將要+100元的信息。
  • 當A服務知道Half Message發送成功後,那麼開始第3步執行本地事務。
  • 執行本地事務(會有三種情況1、執行成功。2、執行失敗。3、網絡等原因導致沒有響應)
  • 如果本地事務成功,那麼Product像Brock服務器發送Commit,這樣B服務就可以消費該message。
  • 如果本地事務失敗,那麼Product像Brock服務器發送Rollback,那麼就會直接刪除上面這條半消息。
  • 如果因爲網絡等原因遲遲沒有返回失敗還是成功,那麼會執行RocketMQ的回調接口,來進行事務的回查。

7. 消息過濾

  • Broker端消息過濾  
    在Broker中,按照Consumer的要求做過濾,優點是減少了對於Consumer無用消息的網絡傳輸。缺點是增加了Broker的負擔,實現相對複雜。
  • Consumer端消息過濾
    這種過濾方式可由應用完全自定義實現,但是缺點是很多無用的消息要傳輸到Consumer端。

8. Broker的Buffer問題

Broker的Buffer通常指的是Broker中一個隊列的內存Buffer大小,這類Buffer通常大小有限。

另外,RocketMQ沒有內存Buffer概念,RocketMQ的隊列都是持久化磁盤,數據定期清除。

RocketMQ同其他MQ有非常顯著的區別,RocketMQ的內存Buffer抽象成一個無限長度的隊列,不管有多少數據進來都能裝得下,這個無限是有前提的,Broker會定期刪除過期的數據。

例如Broker只保存3天的消息,那麼這個Buffer雖然長度無限,但是3天前的數據會被從隊尾刪除。

9. 回溯消費

回溯消費是指Consumer已經消費成功的消息,由於業務上的需求需要重新消費,要支持此功能,Broker在向Consumer投遞成功消息後,消息仍然需要保留。並且重新消費一般是按照時間維度。

例如由於Consumer系統故障,恢復後需要重新消費1小時前的數據,那麼Broker要提供一種機制,可以按照時間維度來回退消費進度。

RocketMQ支持按照時間回溯消費,時間維度精確到毫秒,可以向前回溯,也可以向後回溯。

10. 消息堆積

消息中間件的主要功能是異步解耦,還有個重要功能是擋住前端的數據洪峯,保證後端系統的穩定性,這就要求消息中間件具有一定的消息堆積能力,消息堆積分以下兩種情況:

  • 消息堆積在內存Buffer,一旦超過內存Buffer,可以根據一定的丟棄策略來丟棄消息,如CORBA Notification規範中描述。適合能容忍丟棄消息的業務,這種情況消息的堆積能力主要在於內存Buffer大小,而且消息堆積後,性能下降不會太大,因爲內存中數據多少對於對外提供的訪問能力影響有限。
  • 消息堆積到持久化存儲系統中,例如DB,KV存儲,文件記錄形式。 當消息不能在內存Cache命中時,要不可避免的訪問磁盤,會產生大量讀IO,讀IO的吞吐量直接決定了消息堆積後的訪問能力。
  • 評估消息堆積能力主要有以下四點:
  1. 消息能堆積多少條,多少字節?即消息的堆積容量。
  2. 消息堆積後,發消息的吞吐量大小,是否會受堆積影響?
  3. 消息堆積後,正常消費的Consumer是否會受影響?
  4. 消息堆積後,訪問堆積在磁盤的消息時,吞吐量有多大?

11. 定時消息

定時消息是指消息發到Broker後,不能立刻被Consumer消費,要到特定的時間點或者等待特定的時間後才能被消費。

如果要支持任意的時間精度,在Broker層面,必須要做消息排序,如果再涉及到持久化,那麼消息排序要不可避免的產生巨大性能開銷。

RocketMQ支持定時消息,但是不支持任意時間精度,支持特定的level,例如定時5s,10s,1m等。

可參考:https://github.com/DillonDong/notes/tree/master/RocketMQ

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