深入淺出RocketMQ

基本架構

    RocketMQ由name server,broker,producer,consumer四個組件構成,每個組件都沒有單點問題,整體架構圖如下:

 

每個組件的用途如下:

    name server:提供輕量級服務發現和路由,每個服務器記錄完整的路由信息,用來提供完整的讀寫服務。name server記錄了broker,topic等狀態信息,其他角色的服務器向name server上報狀態信息,並記錄到內存中(一般不開啓持久化存儲);

    broker:爲topic和queue提供輕量級存儲,支持推和拉模式,內置了容錯、削峯填谷、萬億級消息有序能力;除此之外,broker提供了災難恢復、豐富的統計功能、報警等傳統消息系統缺乏的功能,broker是RocketMQ集羣的核心;

    producer:支持分佈式、快速失敗和低延遲特性,RocketMQ支持同步、異步、單向三種消息發送方式;

    consumer:支持分佈式、推模式和拉模式(推模式是對拉模式的封裝);支持集羣模式和廣播模式兩種,RocketMQ支持局部消息順序消費,但不支持全局消息順序消費(將topic的隊列數設爲1可以達到全局有序,但會失去高可用);

    topic:topic用於將消息按主題劃分。producer和consumer都是按topic進行生產和消費的,topic是邏輯概念,每個topic都是由分佈在broker上的queue構成的;

    Tag:對topic的進一步細化,用來標記在同一個topic中不同用途的消息;


心跳機制:

    name server是高可用的核心,name server集羣之間的服務器互不通信,broker啓動時向name server中的所有發送註冊信息,name server接受broker集羣的註冊並通過心跳檢查broker是否在線,心跳間隔爲30s,心跳請求中包括broker所有的topic信息、生產者和消費者信心,name server發現2分鐘內某個broker沒有心跳就認爲broker下線,但name server不會主動通知producer、consumer關於broker宕機的信息;

    producer每隔30秒從name server獲取topic跟broker的映射關係,之後跟topic涉及的所有broker建立長連接,心跳間隔也是30秒,心跳包包含生產者所有信息,如果broker檢查到某個producer在2分鐘內沒有心跳則斷開連接;

    consumer從name server獲取訂閱當前topic存在哪些broker上,之後跟broker建立長連接,同樣每隔30秒發送一次心跳,心跳包包含消費者的所有信息,broker發現某個consumer在2分鐘之內沒有心跳,就斷開連接;


高可用機制:

    name server:name server是輕量級、無狀態集羣,每個name server服務器都保存全量元數據信息,只要有一臺name server服務器可用,name server就可以提供註冊、元數據服務;

    broker:broker分爲master和slave,master可讀可寫,slave只讀;一個master和多個slave組成一個broker set,同一個broker set內的brokerName相同,集羣中master之間是相互獨立的;從可用性角度講,一個set中作爲master的broker宕機後,沒有其他broker接替執行master角色,broker本身是沒有高可用機制的;

    producer:從上面的分析可以看出來,producer寫數據的broker master宕機時不會得到通知,失敗後默認重試2次,重試過程中選擇隊列的機制比較關鍵,默認不啓用broker故障延遲機制,方法的主要目的是規避上次故障的broker,代碼如下:

public MessageQueue selectOneMessageQueue(final String lastBrokerName) {
    if (lastBrokerName == null) {
        return selectOneMessageQueue();
    } else {
        int index = this.sendWhichQueue.getAndIncrement();
        for (int i = 0; i < this.messageQueueList.size(); i++) {
            int pos = Math.abs(index++) % this.messageQueueList.size();
            if (pos < 0) {
                pos = 0;
            }
            MessageQueue mq = this.messageQueueList.get(pos);
            if (!mq.getBrokerName().equals(lastBrokerName)) {
                return mq;
            }
        }
    return selectOneMessageQueue();
  }
}
public MessageQueue selectOneMessageQueue() {
    int index = this.sendWhichQueue.getAndIncrement();
    int pos = Math.abs(index) % this.messageQueueList.size();
    if (pos < 0)
        pos = 0;
  return this.messageQueueList.get(pos);
}

    consumer:consumer消費topic消息時,遇到broker宕機一樣不會得到通知,廣播模式下,每個consumer都會得到消息,而在集羣模式下,每個消息只能被一個consumer處理掉,所以consumer集羣消費topic消息時,按照queue進行負載均衡,一個queue只被一個consumer消費,如果某個consumer掛掉,其他consumer會接替掛掉的consumer繼續消費queue裏的消息。消費的流程非常複雜,核心的方法有兩個:隊列的負載均衡機制與重新分佈機制;消費進度管理機制。後面專門寫文章介紹這兩個機制。


底層存儲:

        消息底層有CommitLog,ConsumeQueue,IndexFile三種文件協同:

CommitLog:存儲broker上所有topic的全量消息內容,完全順序寫;

ConsumeQueue:由於CommitLog是所有隊列的消息內容,消費者從CommitLog讀消息的時候就會變成隨機讀,爲了提高消費消息時的讀性能,RocketMQ設計了ConsumeQueue,;爲了節省磁盤空間,ConsumeQueue不保存全量數據,默認保留30萬條消息,消費者從ConsumeQueue讀取消息時,就是連續讀,大幅優化讀性能;消息進行持久化到CommitLog時,異步轉發到ConsumeQueue中;

IndexFile:消息索引文件是爲了提高消費topic消息時的定位速度而設計,主要存儲消息Key與Offset的對應關係。

底層存儲架構圖如下:

 


高性能:

    高性能寫:寫的場景是producer發送消息時進行持久化,高性能寫的關鍵機制是順序寫和PageCache,在broker上,所有的topic的內存存儲在同一個CommitLog上,滿1G時建立新文件,RocketMQ默認的CommitLog保留時間爲3天或者磁盤容量達到75%時從最老的數據文件開始清理;

    高性能讀:讀的場景是consumer消費消息時,broker從磁盤上讀取消息數據,高性能讀的關鍵機制是零拷貝複製,跳躍表和預讀,其中預讀是操作系統的特性。由於同一個topic的消息數據文件在CommitLog上是順序但不連續的,所以讀採用ConsumeQueue消息隊列文件作爲輔助,來提升讀性能。

 

個人公衆號:

        自己在軟件這個行業10來年的工作和學習歷程中犯了不少錯誤,也走了不少彎路,在此公衆號分享自己的成長心路和工作心得,希望給後來的從業者一個參照,不要再犯我犯過的錯誤,不要再走我走過的彎路。在此我也會分享工作中遇到的技術問題和自己研究技術的記錄與心得;在項目過程中遇到的風險和暴露於化解風險的過程和方法;在團隊管理過程中的心得和體會。後續會發布一系列專題技術文章,程序員成長系列文章,項目的系列文章,行業發展分析和展望的文章,甚至會包含婚戀和育兒的心得文章,我是個不專注卻熱愛生活的工程師!


二維碼

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