RocketMQ 一之消息發送方式/消息消費方式

RocketMQ 介紹

       消息隊列RocketMQ 是阿里巴巴集團基於高可用分佈式集羣技術,自主研發的雲正式商用的專業消息中間件,既可爲分佈式應用系統提供異步解耦和削峯填谷的能力,同時也具備互聯網應用所需的海量消息堆積、高吞吐、可靠重試等特性,是阿里巴巴雙11 使用的核心產品。
       RocketMQ 的設計基於主題的發佈與訂閱模式,其核心功能包括消息發送、消息存儲(Broker)、消息消費,整體設計追求簡單與性能第一。

  1.  NameServer 設計及其簡單,RocketMQ 擯棄了業界常用的Zookeeper 充當消息管理的“註冊中心”,而是使用自主研發的NameServer 來實現各種元數據的管理(Topic 路由信息等)
  2.  高效的I/O 存儲,RocketMQ 追求消息發送的高吞吐量,RocketMQ 的消息存儲設計成文件組的概念,組內單個文件固定大小,引入了內存映射機制,所有主題的消息存儲基於順序讀寫,極大提高消息寫性能,同時爲了兼顧消息消費與消息查找,引入消息消費隊列文件與索引文件
  3. . 容忍存在設計缺陷,適當將某些工作下放給RocketMQ 的使用者,比如消息只消費一次,這樣極大的簡化了消息中間件的內核,使得RocketMQ的實現發送變得非常簡單與高效。
  4. 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 的整體運轉

  1.  NameServer 先啓動
  2.  Broker 啓動時向NameServer 註冊
  3. 生產者在發送某個主題的消息之前先從NamerServer 獲取Broker 服務器地址列表(有可能是集羣),然後根據負載均衡算法從列表中選擇一臺Broker 進行消息發送。
  4. NameServer 與每臺Broker 服務器保持長連接,並間隔30S 檢測Broker 是否存活,如果檢測到Broker 宕機(使用心跳機制,如果檢測超過120S),則從路由註冊表中將其移除。
  5.  消費者在訂閱某個主題的消息之前從NamerServer 獲取Broker 服務器地址列表(有可能是集羣),但是消費者選擇從Broker 中訂閱消息,訂閱規則由Broker 配置決定。

RocketMQ 的設計理念和目標

設計理念

 

  • 基於主題的發佈和訂閱,其核心功能,消息發送、消息存儲和消息消費。整體設計追求簡單與性能。
  • NameServer 性能對比Zookeeper 有極大的提升
  • 高效的IO 存儲機制,基於文件順序讀寫,內存映射機制
  • 容忍設計缺陷,比如消息只消費一次,Rocket 自身不保證,從而簡化Rocket 的內核使得Rocket 簡單與高效,這個問題交給消費者去實現(冪等)。

設計目標

 

  • 架構模式:發佈訂閱模式,主要組件:消息發送者、消息服務器(消息存儲)、消息消費、路由發現。
  • 順序消息:RocketMQ 可以嚴格保證消息有序
  • 消息過濾:消息消費是,消費者可以對同一主題下的消息按照規則只消費自己感興趣的消息,可以支持在服務端與消費端的消息過濾機制。
  • 消息存儲:一般MQ 核心就是消息的存儲,對存儲一般來說兩個維度:消息堆積能力和消息存儲性能。RocketMQ 追求消息存儲的高性能,引入內存映射機制,所有的主題消息順序存儲在同一個文件中。同時爲了防止無限堆積,引入消息文件過期機制和文件存儲空間報警機制。
  • 消息高可用:
  1.  Rocket 關機、斷電等情況下,Rokcet 可以確保不丟失消息(同步刷盤機制不丟失,異步刷盤會丟失少量)。
  2. 另外如果Rocket 服務器因爲CPU、內存、主板、磁盤等關鍵設備損壞導致無法開機,這個屬於單點故障,該節點上的消息全部丟失,如果開啓了異步複製機制,Rocket 可以確保只丟失很少量消息。
  3.  如果引入雙寫機制,這樣基本上可以滿足消息可靠性要求極高的場景(畢竟兩臺主服務器同時故障的可能性還是非常小)
  • 消息消費低延遲: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 

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