【消息中間件】--- RocketMQ核心概念介紹 + 生產者簡介 + 消息存儲簡介

本文對應源碼地址:https://github.com/nieandsun/rocketmq-study
rocketmq官網:https://rocketmq.apache.org/docs/quick-start/
rocketmq github託管地址(這裏直接給出的是中文docs地址):https://github.com/apache/rocketmq/tree/master/docs/cn

唉。。。加班好累啊,都沒怎麼有時間寫博客了!!!!

1 RocketMQ核心概念介紹

基本全部摘自https://github.com/apache/rocketmq/tree/master/docs/cn

在這裏插入圖片描述

1.1 RocketMQ架構相關的概念

RocketMQ架構上主要分爲四部分,如上圖所示:


Producer
消息發佈的角色,支持分佈式集羣方式部署。Producer通過MQ的負載均衡模塊選擇相應的Broker集羣隊列進行消息投遞,投遞的過程支持快速失敗並且低延遲。


Consumer
消息消費的角色,支持分佈式集羣方式部署。支持以push推,pull拉兩種模式對消息進行消費。同時也支持集羣方式和廣播方式的消費,它提供實時消息訂閱機制,可以滿足大多數用戶的需求。


NameServer
NameServer是一個非常簡單的Topic路由註冊中心,其角色類似Dubbo中的zookeeper,支持Broker的動態註冊與發現。

主要包括兩個功能:

  • Broker管理,NameServer接受Broker集羣的註冊信息並且保存下來作爲路由信息的基本數據。然後提供心跳檢測機制,檢查Broker是否還存活;
  • 路由信息管理,每個NameServer將保存關於Broker集羣的整個路由信息和用於客戶端查詢的隊列信息。

然後Producer和Conumser通過NameServer就可以知道整個Broker集羣的路由信息,從而進行消息的投遞和消費。

NameServer通常也是集羣的方式部署,各實例間相互不進行信息通訊。Broker是向每一臺NameServer註冊自己的路由信息,所以每一個NameServer實例上面都保存一份完整的路由信息。當某個NameServer因某種原因下線了,Broker仍然可以向其它NameServer同步其路由信息。


BrokerServer
消息中轉角色,負責存儲消息、轉發消息。BrokerServer在RocketMQ系統中負責接收從生產者發送來的消息並存儲、同時爲消費者的拉取請求作準備。BrokerServer也存儲消息相關的元數據,包括消費者組、消費進度偏移和主題和隊列消息等。


1.2 其他基本概念


消息(Message)
消息系統所傳輸信息的物理載體,生產和消費數據的最小單位,每條消息必須屬於一個主題。RocketMQ中每個消息擁有唯一的Message ID,且可以攜帶具有業務標識的Key(Message Key)。系統提供了通過Message ID和Key查詢消息的功能。


主題(Topic)— 生產上肯定不允許自動創建主題
表示一類消息的集合,每個主題包含若干條消息,每條消息只能屬於一個主題,是RocketMQ進行消息訂閱的基本單位。


標籤(Tag)— 相當於二級主題
爲消息設置的標誌,用於同一主題下區分不同類型的消息。來自同一業務單元的消息,可以根據不同業務目的在同一主題下設置不同標籤。標籤能夠有效地保持代碼的清晰度和連貫性,並優化RocketMQ提供的查詢系統。消費者可以根據Tag實現對不同子主題的不同消費邏輯,實現更好的擴展性。


生產者組(Producer Group) — 生產上肯定不允許自動創建
同一類Producer的集合,這類Producer發送同一類消息且發送邏輯一致。如果發送的是事務消息且原始生產者在發送之後崩潰,則Broker服務器會聯繫同一生產者組的其他生產者實例以提交或回溯消費。


消費者組(Consumer Group) — 生產上肯定不允許自動創建
同一類Consumer的集合,這類Consumer通常消費同一類消息且消費邏輯一致。消費者組使得在消息消費方面,實現負載均衡和容錯的目標變得非常容易。要注意的是,消費者組的消費者實例必須訂閱完全相同的Topic。RocketMQ 支持兩種消息模式:集羣消費(Clustering)和廣播消費(Broadcasting)。


集羣消費(Clustering)
集羣消費模式下,相同Consumer Group的每個Consumer實例平均分攤消息。


廣播消費(Broadcasting)
廣播消費模式下,相同Consumer Group的每個Consumer實例都接收全量的消息。


拉取式消費(Pull Consumer)
Consumer消費的一種類型,應用通常主動調用Consumer的拉消息方法從Broker服務器拉消息、主動權由應用控制。一旦獲取了批量消息,應用就會啓動消費過程。


推動式消費(Push Consumer)
Consumer消費的一種類型,該模式下Broker收到數據後會主動推送給消費端,該消費模式一般實時性較高。


普通順序消息(Normal Ordered Message) — 本篇文章暫不涉及
普通順序消費模式下,消費者通過同一個消費隊列收到的消息是有順序的,不同消息隊列收到的消息則可能是無順序的。


嚴格順序消息(Strictly Ordered Message) — 本篇文章暫不涉及
嚴格順序消息模式下,消費者收到的所有消息均是有順序的。


2 消息發送

本節內容基本全部摘自官網:https://github.com/apache/rocketmq/tree/master/docs/cn

  • 使用RocketMQ發送三種類型的消息:同步消息、異步消息和單向消息。其中前兩種消息是可靠的,因爲會有發送是否成功的應答。

2.1 單向發送消息

2.1.1 示例代碼

這種方式主要用在不特別關心發送結果的場景,例如日誌發送。

@Slf4j
public class OneWayProducer {
    public static void main(String[] args) throws Exception {
        //Instantiate with a producer group name. --- 生產者組
        DefaultMQProducer producer = new DefaultMQProducer("nrsc-group");
        // Specify name server addresses.
        producer.setNamesrvAddr("localhost:9876");
        //Launch the instance.
        producer.start();
        for (int i = 0; i < 5; i++) {
            //Create a message instance, specifying topic, tag and message body.
            Message msg = new Message(
                    "Topic-NRSC" /* Topic */,
                    "TagA-NRSC" /* Tag */,
                    ("Hello RocketMQ " +
                            i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
            );
            //Call send message to deliver message to one of brokers.
            producer.sendOneway(msg);
            log.info("send msg is {}", msg);

        }
        //Shut down once the producer instance is not longer in use.
        producer.shutdown();
    }
}

2.1.2 示例代碼(生成者)中涉及到的RocketMQ核心概念

上面這段代碼包含了RocketMQ諸多的諸多核心概念:如生產者、生產者組 、消息 、主題 等,有興趣的可以進行一一對應。

這裏我們看一下Message的構造函數,如下:
在這裏插入圖片描述
可以看到:

  • 一個Message 必須要有一個主題(Topic)和一個消息主體(Message body) — 消息主體裏存放了真正的消息內容;
  • 且消息主體必須爲一個字節數組。

官網總結如下:https://github.com/apache/rocketmq/blob/master/docs/cn/best_practice.md

在這裏插入圖片描述


2.1.3 從可視化控制檯看一下發送的消息

在這裏插入圖片描述
注意: 圖中的MESSAGE KEY即2.1.2中Message構造函數中的keys,在我們的項目中這個東東是必須有的。
我看在官網的最佳實踐(https://github.com/apache/rocketmq/blob/master/docs/cn/best_practice.md)裏也寫了關於該變量使用的好處:
在這裏插入圖片描述


2.2 發送同步消息


2.2.1 什麼是同步???

其實很好理解,該模式工作原理基本如下,即發送一個消息,必須要等到響應結果,再發第二個消息。
在這裏插入圖片描述


2.2.2 示例代碼

這種可靠性同步地發送方式使用的比較廣泛,比如:重要的消息通知,短信通知。

    public static void main(String[] args) throws Exception {
        // 實例化消息生產者Producer
        DefaultMQProducer producer = new DefaultMQProducer("sync-group");
        // 設置NameServer的地址
        producer.setNamesrvAddr("localhost:9876");
        // 啓動Producer實例
        producer.start();
        for (int i = 0; i < 5; i++) {
            // 創建消息,並指定Topic,Tag和消息體
            Message msg = new Message("Topic-sync" /* Topic */,
                    "TagA-sync" /* Tag */,
                    ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
            );
            // 發送消息到一個Broker
            SendResult sendResult = producer.send(msg);
            // 通過sendResult返回消息是否成功送達
            System.out.printf("%s%n", sendResult);
        }
        // 如果不再發送消息,關閉Producer實例。
        producer.shutdown();
    }
}

2.2.3 簡單看一下發送同步消息獲得的返回結果 — 在本文第3部分進行詳解

SendResult [sendStatus=SEND_OK, msgId=C0A8007352B818B4AAC256417E8F0000, offsetMsgId=C0A8940100002A9F0000000000000E06, messageQueue=MessageQueue [topic=Topic-sync, brokerName=LAPTOP-V7UUU6JC, queueId=1], queueOffset=0]
SendResult [sendStatus=SEND_OK, msgId=C0A8007352B818B4AAC256417EA30001, offsetMsgId=C0A8940100002A9F0000000000000EBE, messageQueue=MessageQueue [topic=Topic-sync, brokerName=LAPTOP-V7UUU6JC, queueId=2], queueOffset=0]
SendResult [sendStatus=SEND_OK, msgId=C0A8007352B818B4AAC256417EA50002, offsetMsgId=C0A8940100002A9F0000000000000F76, messageQueue=MessageQueue [topic=Topic-sync, brokerName=LAPTOP-V7UUU6JC, queueId=3], queueOffset=0]
SendResult [sendStatus=SEND_OK, msgId=C0A8007352B818B4AAC256417EA70003, offsetMsgId=C0A8940100002A9F000000000000102E, messageQueue=MessageQueue [topic=Topic-sync, brokerName=LAPTOP-V7UUU6JC, queueId=0], queueOffset=0]
SendResult [sendStatus=SEND_OK, msgId=C0A8007352B818B4AAC256417EA90004, offsetMsgId=C0A8940100002A9F00000000000010E6, messageQueue=MessageQueue [topic=Topic-sync, brokerName=LAPTOP-V7UUU6JC, queueId=1], queueOffset=1]

2.3 發送異步消息

異步消息通常用在對響應時間敏感的業務場景,即發送端不能容忍長時間地等待Broker的響應。


2.3.1什麼是異步???

有網絡編程基礎的,其實對同步、異步啥的我感覺理解起來應該都很輕鬆。
發送異步消息的工作原理基本如下,即消息發送方在發送了一條消息後,不等接收方發回響應,接着進行第二條消息發送。發送方通過回調接口的方式接收服務器響應,並對響應結果進行處理。
在這裏插入圖片描述


2.3.2 示例代碼

public class AsyncProducer {
    public static void main(
            String[] args) throws MQClientException, InterruptedException {
        //生產者實例化
        DefaultMQProducer producer = new DefaultMQProducer("async");
        //指定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",
                        "TagC",
                        "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();
    }
}

2.3.3 簡單看一下發送異步消息獲得的返回結果

在這裏插入圖片描述
有興趣的可以把整個sendResult打印出來看一下,這裏不進行展示了。


2.4 簡單總結

在這裏插入圖片描述


3 SendResult簡單解讀 + 消息存儲簡介

本節內容基本全部摘自官網:https://github.com/apache/rocketmq/blob/master/docs/cn/design.md


3.1 RocketMQ消息存儲整體架構

從2.2.3打印出的SendResult內容可以看到裏面有一個MessageQueue,而五條消息的queueId分別爲:1,2,3,0,1 —> 這幾個數字是什麼意思呢??? —> 這就不得不提一下RocketMQ的整體存儲架構了,其架構圖如下:

在這裏插入圖片描述
首先來看一下官網的介紹:

消息存儲架構圖中主要有下面三個跟消息存儲相關的文件構成。


(1) CommitLog:消息主體以及元數據的存儲主體,存儲Producer端寫入的消息主體內容,消息內容不是定長的。單個文件大小默認1G ,文件名長度爲20位,左邊補零,剩餘爲起始偏移量,比如00000000000000000000代表了第一個文件,起始偏移量爲0,文件大小爲1G=1073741824;當第一個文件寫滿了,第二個文件爲00000000001073741824,起始偏移量爲1073741824,以此類推。消息主要是順序寫入日誌文件,當文件滿了,寫入下一個文件;


(2) ConsumeQueue:消息消費隊列,引入的目的主要是提高消息消費的性能,由於RocketMQ是基於主題topic的訂閱模式,消息消費是針對主題進行的,如果要遍歷commitlog文件中根據topic檢索消息是非常低效的。Consumer即可根據ConsumeQueue來查找待消費的消息。其中,ConsumeQueue(邏輯消費隊列)作爲消費消息的索引,保存了指定Topic下的隊列消息在CommitLog中的起始物理偏移量offset,消息大小size和消息Tag的HashCode值。consumequeue文件可以看成是基於topic的commitlog索引文件,故consumequeue文件夾的組織方式如下:topic/queue/file三層組織結構,具體存儲路徑爲:$HOME/store/consumequeue/{topic}/{queueId}/{fileName}。同樣consumequeue文件採取定長設計,每一個條目共20個字節,分別爲8字節的commitlog物理偏移量、4字節的消息長度、8字節tag hashcode,單個文件由30W個條目組成,可以像數組一樣隨機訪問每一個條目,每個ConsumeQueue文件大小約5.72M;


(3) IndexFile:IndexFile(索引文件)提供了一種可以通過key或時間區間來查詢消息的方法。Index文件的存儲位置是:$HOME \store\index\${fileName},文件名fileName是以創建時的時間戳命名的,固定的單個IndexFile文件大小約爲400M,一個IndexFile可以保存 2000W個索引,IndexFile的底層存儲設計爲在文件系統中實現HashMap結構,故rocketmq的索引文件其底層實現爲hash索引。


在上面的RocketMQ的消息存儲整體架構圖中可以看出,RocketMQ採用的是混合型的存儲結構,即爲Broker單個實例下所有的隊列共用一個日誌數據文件(即爲CommitLog)來存儲。RocketMQ的混合型存儲結構(多個Topic的消息實體內容都存儲於一個CommitLog中)針對Producer和Consumer分別採用了數據和索引部分相分離的存儲結構,Producer發送消息至Broker端,然後Broker端使用同步或者異步的方式對消息刷盤持久化,保存至CommitLog中。只要消息被刷盤持久化至磁盤文件CommitLog中,那麼Producer發送的消息就不會丟失。正因爲如此,Consumer也就肯定有機會去消費這條消息。當無法拉取到消息後,可以等下一次消息拉取,同時服務端也支持長輪詢模式,如果一個消息拉取請求未拉取到消息,Broker允許等待30s的時間,只要這段時間內有新消息到達,將直接返回給消費端。這裏,RocketMQ的具體做法是,使用Broker端的後臺服務線程—ReputMessageService不停地分發請求並異步構建ConsumeQueue(邏輯消費隊列)和IndexFile(索引文件)數據。

由上面的介紹可知RocketMQ 收到消息後, 所有主題的消息都存儲在 commitlog 文件中, 當消息到達 commitlog 後, 將會採用異步轉發到消息隊列, 也就是 consumerqueue,Queue 是數據分片的產物, 數據分片可以提高消費者的消費效率。


3.2 按照官方文檔的描述看一下消息的物理存儲

根據官網描述,我們其實可以在我們自己的電腦或服務器上找到這三個文件,比如我在win上安裝了RocketMQ,這三個文件所在的目錄如下:
在這裏插入圖片描述


接着我們來看一下consumequeue的子文件夾:
在這裏插入圖片描述
可以看到,consumequeue按照Topic又劃分了許多子文件夾。


再來看一下某個Topic下的內容:
在這裏插入圖片描述
可以看到它又默認分了四個文件,其實到這裏你肯定就知道爲什麼2.2.3中五條消息的queueId分別爲:1,2,3,0,1了。 —> 即在默認情況下RocketMQ又將某個topic下的消息按照一定的算法將其分攤到了4個隊列中。

這時候再看本節開頭的那個消息存儲架構圖是不是就感覺輕鬆多了。


最後,這些東西其實都可以通過可視化控制檯看到,我在工作中還是比較喜歡點這些東西的,有興趣的可以研究一下,我們公司裏控制檯跟這個不一樣(我這個的下載地址爲:https://codeload.github.com/apache/rocketmq-externals/zip/master),可能是版本的原因,但是我看內容都差不多。
在這裏插入圖片描述
再看一下可視化控制檯的Topic頁面 —> 這些東西在真實開發場景下都是可以用得到的,有興趣的可以多研究研究。
在這裏插入圖片描述


end!!!

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