RocketMQ 介紹
消息隊列RocketMQ 是阿里巴巴集團基於高可用分佈式集羣技術,自主研發的雲正式商用的專業消息中間件,既可爲分佈式應用系統提供異步解耦和削峯填谷的能力,同時也具備互聯網應用所需的海量消息堆積、高吞吐、可靠重試等特性,是阿里巴巴雙11 使用的核心產品。
RocketMQ 的設計基於主題的發佈與訂閱模式,其核心功能包括消息發送、消息存儲(Broker)、消息消費,整體設計追求簡單與性能第一。
- NameServer 設計及其簡單,RocketMQ 擯棄了業界常用的Zookeeper 充當消息管理的“註冊中心”,而是使用自主研發的NameServer 來實現各種元數據的管理(Topic 路由信息等)
- 高效的I/O 存儲,RocketMQ 追求消息發送的高吞吐量,RocketMQ 的消息存儲設計成文件組的概念,組內單個文件固定大小,引入了內存映射機制,所有主題的消息存儲基於順序讀寫,極大提高消息寫性能,同時爲了兼顧消息消費與消息查找,引入消息消費隊列文件與索引文件
- . 容忍存在設計缺陷,適當將某些工作下放給RocketMQ 的使用者,比如消息只消費一次,這樣極大的簡化了消息中間件的內核,使得RocketMQ的實現發送變得非常簡單與高效。
- RocketMQ 原先阿里巴巴內部使用,與2017 年提交到Apache 基金會成爲Apache 基金會的頂級開源項目,GitHub 代碼庫鏈接:https://github.com/apache/rocketmq.gitRocketMQ 的官網http://rocketmq.apache.org/
核心概念
NameServer
- NameServer 是整個RocketMQ 的“大腦”,它是RocketMQ 的服務註冊中心,所以RocketMQ 需要先啓動NameServer 再啓動Rocket 中的Broker。 Broker 在啓動時向所有NameServer 註冊(主要是服務器地址等),生產者在發送消息之前先從NameServer 獲取Broker 服務器地址列表(消費者一樣),然後根據負載均衡算法從列表中選擇一臺服務器進行消息發送。
NameServer 與每臺Broker 服務保持長連接,並間隔30S 檢查Broker 是否存活,如果檢測到Broker 宕機,則從路由註冊表中將其移除。這樣就可以實現RocketMQ 的高可用。具體細節後續的課程會進行講解。
主題
- 主題,topic,消息主題,一級消費類型,生產者向其發送消息,消費者負責從topic接收消息並消費
生產者
- 生產者:也稱爲消息發佈者,負責生產併發送消息至Topic。
消費者
- 消費者:也稱爲消息訂閱者,負責從Topic 接收並消費消息。
消息
-
消息:生產者或消費者進行消息發送或消費的主題,對於RocketMQ 來說,消息就是字節數組。
Rocket 的整體運轉
- NameServer 先啓動
- Broker 啓動時向NameServer 註冊
- 生產者在發送某個主題的消息之前先從NamerServer 獲取Broker 服務器地址列表(有可能是集羣),然後根據負載均衡算法從列表中選擇一臺Broker 進行消息發送。
- NameServer 與每臺Broker 服務器保持長連接,並間隔30S 檢測Broker 是否存活,如果檢測到Broker 宕機(使用心跳機制,如果檢測超過120S),則從路由註冊表中將其移除。
- 消費者在訂閱某個主題的消息之前從NamerServer 獲取Broker 服務器地址列表(有可能是集羣),但是消費者選擇從Broker 中訂閱消息,訂閱規則由Broker 配置決定。
RocketMQ 的設計理念和目標
設計理念
- 基於主題的發佈和訂閱,其核心功能,消息發送、消息存儲和消息消費。整體設計追求簡單與性能。
- NameServer 性能對比Zookeeper 有極大的提升
- 高效的IO 存儲機制,基於文件順序讀寫,內存映射機制
- 容忍設計缺陷,比如消息只消費一次,Rocket 自身不保證,從而簡化Rocket 的內核使得Rocket 簡單與高效,這個問題交給消費者去實現(冪等)。
設計目標
- 架構模式:發佈訂閱模式,主要組件:消息發送者、消息服務器(消息存儲)、消息消費、路由發現。
- 順序消息:RocketMQ 可以嚴格保證消息有序
- 消息過濾:消息消費是,消費者可以對同一主題下的消息按照規則只消費自己感興趣的消息,可以支持在服務端與消費端的消息過濾機制。
- 消息存儲:一般MQ 核心就是消息的存儲,對存儲一般來說兩個維度:消息堆積能力和消息存儲性能。RocketMQ 追求消息存儲的高性能,引入內存映射機制,所有的主題消息順序存儲在同一個文件中。同時爲了防止無限堆積,引入消息文件過期機制和文件存儲空間報警機制。
- 消息高可用:
- Rocket 關機、斷電等情況下,Rokcet 可以確保不丟失消息(同步刷盤機制不丟失,異步刷盤會丟失少量)。
- 另外如果Rocket 服務器因爲CPU、內存、主板、磁盤等關鍵設備損壞導致無法開機,這個屬於單點故障,該節點上的消息全部丟失,如果開啓了異步複製機制,Rocket 可以確保只丟失很少量消息。
- 如果引入雙寫機制,這樣基本上可以滿足消息可靠性要求極高的場景(畢竟兩臺主服務器同時故障的可能性還是非常小)
- 消息消費低延遲:RocketMQ 在消息不發生消息堆積時,以長輪詢模式實現準實時的消息推送模式。
- 確保消息必須被消費一次:消息確認機制(ACK)來確保消息至少被消費一次,一般ACK 機制只能做到消息只被消費一次,有重複消費的可能。
- 消息回溯:已經消費完的消息,可以根據業務要求重新消費消息。
- 消息堆積:消息中間件的主要功能是異步解耦,還有個重要功能是擋住前端的數據洪峯,保證後端系統的穩定性,這就要求消息中間件具有一定的
- 消息堆積能力,RocketMQ 採用磁盤文件存儲,所以堆積能力比較強,同時提供文件過期刪除機制。
- 定時消息:定時消息,定時消息是指消息發送到Rocket Broker 上之後,不被消費者理解消費,要到等待一定的時間才能進行消費,apache 的版本目前只支持等待指定的時間才能被消費,不支持任意精度的定時消息消費。(一個說法是任意精度的定時消息會帶來性能損耗,但是阿里雲版本的RocketMQ卻提供這樣的功能,充值收費優先策略?)
- 消息重試機制:消息重試是指在消息消費時,如果發送異常,那麼消息中間件需要支持消息重新投遞,RocketMQ 支持消息重試機制。
RocketMq 中消息的發送
- 普通消息是指消息隊列RocketMQ 中無特性的消息,區別於有特性的定時/延時消息、順序消息和事務消息。這些後續會細講。RocketMQ 發送普通消息有三種實現方式:單向(OneWay)發送、可靠同步發送、可靠異步發送。
- 消息生產的客戶端依賴如下:
<!--這裏引入RocketMQ的版本4.4.0與Rocket服務版本對應 -->
<dependencies>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.4.0</version>
</dependency>
</dependencies>
broker.conf
- 是否允許 Broker 自動創建Topic,建議線下開啓,線上關閉 autoCreateTopicEnable=true
- 是否允許 Broker 自動創建訂閱組,建議線下開啓,線上關閉 autoCreateSubscriptionGroup=true
單向(OneWay)發送
/**
* 單向發送
*/
public class OnewayProducer {
public static void main(String[] args) throws Exception {
//生產者實例化
DefaultMQProducer producer = new DefaultMQProducer("zrGroup");//生產者分組
//指定rocket服務器地址
producer.setNamesrvAddr("127.0.0.1:9876");
//啓動實例
producer.start();
for (int i = 0; i < 10; i++) {
//創建一個消息實例,指定topic、tag和消息體
Message msg = new Message("TopicTest" /* Topic */,
"Tagb" /* Tag */,
("Hello RocketMQ " +
i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
);
//發送消息
producer.sendOneway(msg);
System.out.printf("%s%n", new String(msg.getBody()));
}
//生產者實例不再使用時關閉.
producer.shutdown();
}
}
單向(Oneway)發送特點爲發送方只負責發送消息,不等待服務器迴應且沒有回調函數觸發,即只發送請求不等待應答。此方式發送消息的過程耗 時非常短,一般在微秒級別
Producer Group(生產者分組)
生產者組,簡單來說就是多個發送同一類消息的生產者稱之爲一個生產者組。在這裏可以不用關心,只要知道有這麼一個概念即可。RocketMQ 中的生產者組只能有一個在用的生產者。分組的作用如下(簡單的場景不需要了解這個概念):
- 標識一類Producer
- 可以通過運維工具查詢這個發送消息應用下有多個Producer 實例
- 發送分佈式事務消息時,如果Producer 中途意外宕機,Broker 會主動回調Producer Group 內的任意一臺機器來確認事務狀態。
Producer 實例
- Producer 的一個對象實例,不同的Producer 實例可以運行在不同進程內或者不同機器上。Producer 實例線程安全,可在同一進程內多線程之間共享。
Message Key
- Key 一般用於消息在業務層面的唯一標識。對發送的消息設置好Key,以後可以根據這個Key 來查找消息。比如消息異常,消息丟失,進行查找會很方便。RocketMQ 會創建專門的索引文件,用來存儲Key 與消息的映射,由於是Hash 索引,應儘量使Key 唯一,避免潛在的哈希衝突。
- Tag 和Key 的主要差別是使用場景不同,Tag 用在Consumer 代碼中,用於服務端消息過濾,Key 主要用於通過命令進行查找消息
- RocketMQ 並不能保證message id 唯一,在這種情況下,生產者在push 消息的時候可以給每條消息設定唯一的key, 消費者可以通過message key保證對消息冪等處理。
Tag
- 消息標籤,二級消息類型,用來進一步區分某個Topic 下的消息分類。
- Topic 與Tag 都是業務上用來歸類的標識,區分在於Topic 是一級分類,而Tag 可以理解爲是二級分類。
- 以天貓交易平臺爲例,訂單消息和支付消息屬於不同業務類型的消息,分別創建Topic_Order 和Topic_Pay,其中訂單消息根據商品品類以不同的Tag再進行細分,如電器類、男裝類、女裝類、化妝品類,最後他們都被各個不同的系統所接收。通過合理的使用Topic 和Tag,可以讓業務結構清晰,更可以提高效率。
您可能會有這樣的疑問:到底什麼時候該用Topic,什麼時候該用Tag?
- 消息類型是否一致:如普通消息、事務消息、定時(延時)消息、順序消息,不同的消息類型使用不同的Topic,無法通過Tag 進行區分。
- 業務是否相關聯:沒有直接關聯的消息,如淘寶交易消息,京東物流消息使用不同的Topic 進行區分;而同樣是天貓交易消息,電器類訂單、女裝類訂單、化妝品類訂單的消息可以用Tag 進行區分。
- 消息優先級是否一致:如同樣是物流消息,盒馬必須小時內送達,天貓超市24 小時內送達,淘寶物流則相對會慢一些,不同優先級的消息用不同的Topic 進行區分。
- 消息量級是否相當:有些業務消息雖然量小但是實時性要求高,如果跟某些萬億量級的消息使用同一個Topic,則有可能會因爲過長的等待時間而“餓死”,此時需要將不同量級的消息進行拆分,使用不同的Topic。
可靠同步發送
/**
* 同步發送
* 同步發送是指消息發送方發出數據後,同步等待,直到收到接收方發回響應之後才發下一個請求。
*/
public class SyncProducer {
public static void main(String[] args) throws Exception {
DefaultMQProducer producer = new DefaultMQProducer("syncGroup");
//producer.setRetryTimesWhenSendFailed(2);//發送消息失敗重試2次
producer.setNamesrvAddr("localhost:9876");
producer.start();
for (int i = 0; i < 10; i++) {
Message msg = new Message("TopicTest",
"Tag2",
("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET)
);
SendResult sendResult = producer.send(msg);
System.out.printf("%s%n%n%n", sendResult.getSendStatus() + ":(MsgId):"
+ sendResult.getMsgId() + ":(queueId):"
+ sendResult.getMessageQueue().getQueueId()
+ "(value):" + new String(msg.getBody()));
}
producer.shutdown();
}
}
MessageID
- 消息的全局唯一標識(內部機制的 ID 生成是使用機器 IP 和消息偏移量的組成,所以有可能重複,如果是冪等性還是最好考慮 Key),由消息隊列 MQ 系統自動生成,唯一標識某條消息。
SendStatus
- 發送的標識。成功,失敗等
Queue
- RocketMQ 收到消息後,所有主題的消息都存儲在 commitlog 文件中,當消息到達 commitlog 後,將會採用異步轉發到消息隊列,也就是 consumerqueue, Queue 是數據分片的產物,數據分片可以提高消費者的效率。
broker.conf
# 在發送消息時,自動創建服務器不存在的 topic,默認創建的隊列數 defaultTopicQueueNums=4
可靠異步發送
/**
* 異步發送
* 消息發送方在發送了一條消息後,不等接收方發回響應,接着進行第二條消息發送。
* 發送方通過回調接口的方式接收服務器響應,並對響應結果進 行處理
*/
public class AsyncProducer {
public static void main(
String[] args) throws MQClientException, InterruptedException, UnsupportedEncodingException {
//生產者實例化
DefaultMQProducer producer = new DefaultMQProducer("asyncGroup");
//指定rocket服務器地址
producer.setNamesrvAddr("localhost:9876");
//啓動實例
producer.start();
//發送異步失敗時的重試次數(這裏不重試)
producer.setRetryTimesWhenSendAsyncFailed(0);
int messageCount = 10;
final CountDownLatch countDownLatch = new CountDownLatch(messageCount);
for (int i = 0; i < messageCount; i++) {
try {
final int index = i;
Message msg = new Message("TopicTest",
"Tag3",
"OrderID"+index,
("Hello world "+index).getBytes(RemotingHelper.DEFAULT_CHARSET));
//生產者異步發送
producer.send(msg, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
countDownLatch.countDown();
System.out.printf("%-10d OK %s %n", index, new String(msg.getBody()));
}
@Override
public void onException(Throwable e) {
countDownLatch.countDown();
System.out.printf("%-10d Exception %s %n", index, e);
e.printStackTrace();
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
//Thread.sleep(1000);
countDownLatch.await(5, TimeUnit.SECONDS);
producer.shutdown();
}
}
RocketMQ 中消息發送的權衡
三種發送方式的對比
RocketMQ消息消費
基本概念
消息隊列 RocketMQ 是基於發佈/訂閱模型的消息系統。消息的訂閱方訂閱關注的 Topic,以獲取並消費消息。由於訂閱方應用一般是分佈式系統, 以集羣方式部署有多臺機器。因此消息隊列 RocketMQ 約定以下概念。
- 集羣:使用相同 GroupID 的訂閱者屬於同一個集羣。同一個集羣下的訂閱者消費邏輯必須完全一致(包括 Tag 的使用),這些訂閱者在邏輯上可 以認爲是一個消費節點。
- 集羣消費:當使用集羣消費模式時,消息隊列 RocketMQ 認爲任意一條消息只需要被集羣內的任意一個消費者處理即可。
- 廣播消費:當使用廣播消費模式時,消息隊列 RocketMQ 會將每條消息推送給集羣內所有註冊過的客戶端,保證消息至少被每臺機器消費一次。
集羣消費模式
適用場景&注意事項
消費端集羣化部署,每條消息只需要被處理一次。
由於消費進度在服務端維護,可靠性更高。
集羣消費模式下,每一條消息都只會被分發到一臺機器上處理。如果需要被集羣下的每一臺機器都處理,請使用廣播模式。
集羣消費模式下,不保證每一次失敗重投的消息路由到同一臺機器上,因此處理消息時不應該做任何確定性假設。
廣播消費模式
適用場景&注意事項
- 廣播消費模式下不支持順序消息。
- 廣播消費模式下不支持重置消費位點。
- 每條消息都需要被相同邏輯的多臺機器處理。
- 消費進度在客戶端維護,出現重複的概率稍大於集羣模式。
- 廣播模式下,消息隊列 RocketMQ 保證每條消息至少被每臺客戶端消費一次,但是並不會對消費失敗的消息進行失敗重投,因此業務方需要關注消 費失敗的情況。
- 廣播模式下,客戶端每一次重啓都會從最新消息消費。客戶端在被停止期間發送至服務端的消息將會被自動跳過,請謹慎選擇。
- 廣播模式下,每條消息都會被大量的客戶端重複處理,因此推薦儘可能使用集羣模式。 目前僅 Java 客戶端支持廣播模式。
- 廣播模式下服務端不維護消費進度,所以消息隊列 RocketMQ 控制檯不支持消息堆積查詢、消息堆積報警和訂閱關係查詢功能。
使用集羣模式模擬廣播
如果業務需要使用廣播模式,也可以創建多個 GroupID,用於訂閱同一個 Topic。
適用場景&注意事項
- 每條消息都需要被多臺機器處理,每臺機器的邏輯可以相同也可以不一樣。
- 消費進度在服務端維護,可靠性高於廣播模式。
- 對於一個 GroupID 來說,可以部署一個消費端實例,也可以部署多個消費端實例。 當部署多個消費端實例時,實例之間又組成了集羣模式(共同 分擔消費消息)。 假設 GroupID1 部署了三個消費者實例 C1、C2、C3,那麼這三個實例將共同分擔服務器發送給 GroupID1 的消息。 同時,實例之 間訂閱關係必須保持一致。
消費方式
推模式
代碼上使用
- 這種模型下,系統收到消息後自動調用處理函數來處理消息,自動保存 Offset,並且加入新的消費者後會自動做負載均衡。
- 底層實現上,推模式還是使用的 pull 來實現的,pull 就是拉取,push 方式是 Server 端接收到消息後,主動把消息推給 Client 端,實時性高。但是使用 Push 方式有很多弊端,首先加大 Server 端的工作量,其次不同的 Client 端處理能力不同,Client 的狀態不受 Server 控制,如果 Client 不能及時處理 Server 推送過來的消息,會造成各種潛在問題。
- 所以 RocketMQ 是通過“長輪詢”的方式,同時通過 Client 端和 Server 端的配合,達到既擁有 Pull 的優點,又能達到確保實時性的目的。
拉模式
代碼上使用
使用方式類似,但是更加複雜,除了像推模式一樣需要設置各種參數之外,還需要處理額外三件事情:
- 1)獲取 MessageQueues 並遍歷(一個 Topic 包括多個 MessageQueue),如果是特殊情況,也可以選擇指定的 MessageQueue 來讀取消息
- 2)維護 Offsetstore,從一個 MessageQueue 里拉取消息時,要傳入 Offset 參數,隨着不斷的讀取消息,Offset 會不斷增長。這個時候就需要用戶把 Offset 存儲起來,根據實際的情況存入內存、寫入磁盤或者數據庫中。
- 3)根據不同的消息狀態做不同的處理。
消息生產者流程
- 生產者的流程主要講述
- 消息發送的主要流程:驗證消息、查找路由、消息發送(包含異常機制)
- 驗證消息:主要是要求主題名稱、消息體不能爲空、消息長度不能等於 0,且不能超過消息的最大的長度 4M(生產者對象中配置 maxMessageSize=1024*1024*4)
- 查找路由:客戶端(生產者)會緩存 topic 路由信息(如果是第一次發送消息,本地沒有緩存,查詢 NameServer 嘗試獲取),路由信息主要包含了 消息隊列(queue 相關信息),
- 消息發送:選擇消息隊列,發送消息,發送成功則返回。選擇消息隊列兩種方式(一般有兩種,這裏不做詳細講解,後續做詳細講解)
批量消息發送
todo
消息重試機制
todo