roketmq-4.x官方文檔-消息存儲

1 消息存儲

來源 : 官方文檔


rocketmq_design_1.png

消息存儲是RocketMQ中最為復雜和最為重要的一部分,本節將分別從RocketMQ的消息存儲整體架構、PageCache與Mmap內存映射以及RocketMQ中兩種不同的刷盤方式三方面來分別展開敘述。

1.1 消息存儲整體架構

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

(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(索引文件)數據。

1.2 頁緩存與內存映射

頁緩存(PageCache)是OS對文件的緩存,用于加速對文件的讀寫。一般來說,程序對文件進行順序讀寫的速度幾乎接近于內存的讀寫速度,主要原因就是由于OS使用PageCache機制對讀寫訪問操作進行了性能優化,將一部分的內存用作PageCache。對于數據的寫入,OS會先寫入至Cache內,隨后通過異步的方式由pdflush內核線程將Cache內的數據刷盤至物理磁盤上。對于數據的讀取,如果一次讀取文件時出現未命中PageCache的情況,OS從物理磁盤上訪問讀取文件的同時,會順序對其他相鄰塊的數據文件進行預讀取。

在RocketMQ中,ConsumeQueue邏輯消費隊列存儲的數據較少,并且是順序讀取,在page cache機制的預讀取作用下,Consume Queue文件的讀性能幾乎接近讀內存,即使在有消息堆積情況下也不會影響性能。而對于CommitLog消息存儲的日志數據文件來說,讀取消息內容時候會產生較多的隨機訪問讀取,嚴重影響性能。如果選擇合適的系統IO調度算法,比如設置調度算法為“Deadline”(此時塊存儲采用SSD的話),隨機讀的性能也會有所提升。

另外,RocketMQ主要通過MappedByteBuffer對文件進行讀寫操作。其中,利用了NIO中的FileChannel模型將磁盤上的物理文件直接映射到用戶態的內存地址中(這種Mmap的方式減少了傳統IO將磁盤文件數據在操作系統內核地址空間的緩沖區和用戶應用程序地址空間的緩沖區之間來回進行拷貝的性能開銷),將對文件的操作轉化為直接對內存地址進行操作,從而極大地提高了文件的讀寫效率(正因為需要使用內存映射機制,故RocketMQ的文件存儲都使用定長結構來存儲,方便一次將整個文件映射至內存)。

1.3 消息刷盤

rocketmq_design_2.png

(1) 同步刷盤:如上圖所示,只有在消息真正持久化至磁盤后RocketMQ的Broker端才會真正返回給Producer端一個成功的ACK響應。同步刷盤對MQ消息可靠性來說是一種不錯的保障,但是性能上會有較大影響,一般適用于金融業務應用該模式較多。

(2) 異步刷盤:能夠充分利用OS的PageCache的優勢,只要消息寫入PageCache即可將成功的ACK返回給Producer端。消息刷盤采用后臺異步線程提交的方式進行,降低了讀寫延遲,提高了MQ的性能和吞吐量。

2 通信機制

RocketMQ消息隊列集群主要包括NameServe、Broker(Master/Slave)、Producer、Consumer4個角色,基本通訊流程如下:

(1) Broker啟動后需要完成一次將自己注冊至NameServer的操作;隨后每隔30s時間定時向NameServer上報Topic路由信息。

(2) 消息生產者Producer作為客戶端發送消息時候,需要根據消息的Topic從本地緩存的TopicPublishInfoTable獲取路由信息。如果沒有則更新路由信息會從NameServer上重新拉取,同時Producer會默認每隔30s向NameServer拉取一次路由信息。

(3) 消息生產者Producer根據2)中獲取的路由信息選擇一個隊列(MessageQueue)進行消息發送;Broker作為消息的接收者接收消息并落盤存儲。

(4) 消息消費者Consumer根據2)中獲取的路由信息,并再完成客戶端的負載均衡后,選擇其中的某一個或者某幾個消息隊列來拉取消息并進行消費。

從上面1)~3)中可以看出在消息生產者, Broker和NameServer之間都會發生通信(這里只說了MQ的部分通信),因此如何設計一個良好的網絡通信模塊在MQ中至關重要,它將決定RocketMQ集群整體的消息傳輸能力與最終的性能。

rocketmq-remoting 模塊是 RocketMQ消息隊列中負責網絡通信的模塊,它幾乎被其他所有需要網絡通信的模塊(諸如rocketmq-client、rocketmq-broker、rocketmq-namesrv)所依賴和引用。為了實現客戶端與服務器之間高效的數據請求與接收,RocketMQ消息隊列自定義了通信協議并在Netty的基礎之上擴展了通信模塊。

2.1 Remoting通信類結構

rocketmq_design_3.png

2.2 協議設計與編解碼

在Client和Server之間完成一次消息發送時,需要對發送的消息進行一個協議約定,因此就有必要自定義RocketMQ的消息協議。同時,為了高效地在網絡中傳輸消息和對收到的消息讀取,就需要對消息進行編解碼。在RocketMQ中,RemotingCommand這個類在消息傳輸過程中對所有數據內容的封裝,不但包含了所有的數據結構,還包含了編碼解碼操作。

Header字段 類型 Request說明 Response說明
code int 請求操作碼,應答方根據不同的請求碼進行不同的業務處理 應答響應碼。0表示成功,非0則表示各種錯誤
language LanguageCode 請求方實現的語言 應答方實現的語言
version int 請求方程序的版本 應答方程序的版本
opaque int 相當于reqeustId,在同一個連接上的不同請求標識碼,與響應消息中的相對應 應答不做修改直接返回
flag int 區分是普通RPC還是onewayRPC得標志 區分是普通RPC還是onewayRPC得標志
remark String 傳輸自定義文本信息 傳輸自定義文本信息
extFields HashMap<String, String> 請求自定義擴展信息 響應自定義擴展信息
rocketmq_design_4.png

可見傳輸內容主要可以分為以下4部分:

(1) 消息長度:總長度,四個字節存儲,占用一個int類型;

(2) 序列化類型&消息頭長度:同樣占用一個int類型,第一個字節表示序列化類型,后面三個字節表示消息頭長度;

(3) 消息頭數據:經過序列化后的消息頭數據;

(4) 消息主體數據:消息主體的二進制字節數據內容;

2.3 消息的通信方式和流程

在RocketMQ消息隊列中支持通信的方式主要有同步(sync)、異步(async)、單向(oneway)
三種。其中“單向”通信模式相對簡單,一般用在發送心跳包場景下,無需關注其Response。這里,主要介紹RocketMQ的異步通信流程。

rocketmq_design_5.png

2.4 Reactor多線程設計

RocketMQ的RPC通信采用Netty組件作為底層通信庫,同樣也遵循了Reactor多線程模型,同時又在這之上做了一些擴展和優化。

rocketmq_design_6.png

上面的框圖中可以大致了解RocketMQ中NettyRemotingServer的Reactor 多線程模型。一個 Reactor 主線程(eventLoopGroupBoss,即為上面的1)負責監聽 TCP網絡連接請求,建立好連接,創建SocketChannel,并注冊到selector上。RocketMQ的源碼中會自動根據OS的類型選擇NIO和Epoll,也可以通過參數配置),然后監聽真正的網絡數據。拿到網絡數據后,再丟給Worker線程池(eventLoopGroupSelector,即為上面的“N”,源碼中默認設置為3),在真正執行業務邏輯之前需要進行SSL驗證、編解碼、空閑檢查、網絡連接管理,這些工作交給defaultEventExecutorGroup(即為上面的“M1”,源碼中默認設置為8)去做。而處理業務操作放在業務線程池中執行,根據 RomotingCommand 的業務請求碼code去processorTable這個本地緩存變量中找到對應的 processor,然后封裝成task任務后,提交給對應的業務processor處理線程池來執行(sendMessageExecutor,以發送消息為例,即為上面的 “M2”)。從入口到業務邏輯的幾個步驟中線程池一直再增加,這跟每一步邏輯復雜性相關,越復雜,需要的并發通道越寬。

線程數 線程名 線程具體說明
1 NettyBoss_%d Reactor 主線程
N NettyServerEPOLLSelector_%d_%d Reactor 線程池
M1 NettyServerCodecThread_%d Worker線程池
M2 RemotingExecutorThread_%d 業務processor處理線程池

3 消息過濾

RocketMQ分布式消息隊列的消息過濾方式有別于其它MQ中間件,是在Consumer端訂閱消息時再做消息過濾的。RocketMQ這么做是還是在于其Producer端寫入消息和Consomer端訂閱消息采用分離存儲的機制來實現的,Consumer端訂閱消息是需要通過ConsumeQueue這個消息消費的邏輯隊列拿到一個索引,然后再從CommitLog里面讀取真正的消息實體內容,所以說到底也是還繞不開其存儲結構。其ConsumeQueue的存儲結構如下,可以看到其中有8個字節存儲的Message Tag的哈希值,基于Tag的消息過濾正式基于這個字段值的。

rocketmq_design_7.png

主要支持如下2種的過濾方式
(1) Tag過濾方式:Consumer端在訂閱消息時除了指定Topic還可以指定TAG,如果一個消息有多個TAG,可以用||分隔。其中,Consumer端會將這個訂閱請求構建成一個 SubscriptionData,發送一個Pull消息的請求給Broker端。Broker端從RocketMQ的文件存儲層—Store讀取數據之前,會用這些數據先構建一個MessageFilter,然后傳給Store。Store從 ConsumeQueue讀取到一條記錄后,會用它記錄的消息tag hash值去做過濾,由于在服務端只是根據hashcode進行判斷,無法精確對tag原始字符串進行過濾,故在消息消費端拉取到消息后,還需要對消息的原始tag字符串進行比對,如果不同,則丟棄該消息,不進行消息消費。

(2) SQL92的過濾方式:這種方式的大致做法和上面的Tag過濾方式一樣,只是在Store層的具體過濾過程不太一樣,真正的 SQL expression 的構建和執行由rocketmq-filter模塊負責的。每次過濾都去執行SQL表達式會影響效率,所以RocketMQ使用了BloomFilter避免了每次都去執行。SQL92的表達式上下文為消息的屬性。

4 負載均衡

RocketMQ中的負載均衡都在Client端完成,具體來說的話,主要可以分為Producer端發送消息時候的負載均衡和Consumer端訂閱消息的負載均衡。

4.1 Producer的負載均衡

Producer端在發送消息的時候,會先根據Topic找到指定的TopicPublishInfo,在獲取了TopicPublishInfo路由信息后,RocketMQ的客戶端在默認方式下selectOneMessageQueue()方法會從TopicPublishInfo中的messageQueueList中選擇一個隊列(MessageQueue)進行發送消息。具體的容錯策略均在MQFaultStrategy這個類中定義。這里有一個sendLatencyFaultEnable開關變量,如果開啟,在隨機遞增取模的基礎上,再過濾掉not available的Broker代理。所謂的"latencyFaultTolerance",是指對之前失敗的,按一定的時間做退避。例如,如果上次請求的latency超過550Lms,就退避3000Lms;超過1000L,就退避60000L;如果關閉,采用隨機遞增取模的方式選擇一個隊列(MessageQueue)來發送消息,latencyFaultTolerance機制是實現消息發送高可用的核心關鍵所在。

4.2 Consumer的負載均衡

在RocketMQ中,Consumer端的兩種消費模式(Push/Pull)都是基于拉模式來獲取消息的,而在Push模式只是對pull模式的一種封裝,其本質實現為消息拉取線程在從服務器拉取到一批消息后,然后提交到消息消費線程池后,又“馬不停蹄”的繼續向服務器再次嘗試拉取消息。如果未拉取到消息,則延遲一下又繼續拉取。在兩種基于拉模式的消費方式(Push/Pull)中,均需要Consumer端在知道從Broker端的哪一個消息隊列—隊列中去獲取消息。因此,有必要在Consumer端來做負載均衡,即Broker端中多個MessageQueue分配給同一個ConsumerGroup中的哪些Consumer消費。

1、Consumer端的心跳包發送

在Consumer啟動后,它就會通過定時任務不斷地向RocketMQ集群中的所有Broker實例發送心跳包(其中包含了,消息消費分組名稱、訂閱關系集合、消息通信模式和客戶端id的值等信息)。Broker端在收到Consumer的心跳消息后,會將它維護在ConsumerManager的本地緩存變量—consumerTable,同時并將封裝后的客戶端網絡通道信息保存在本地緩存變量—channelInfoTable中,為之后做Consumer端的負載均衡提供可以依據的元數據信息。

2、Consumer端實現負載均衡的核心類—RebalanceImpl

在Consumer實例的啟動流程中的啟動MQClientInstance實例部分,會完成負載均衡服務線程—RebalanceService的啟動(每隔20s執行一次)。通過查看源碼可以發現,RebalanceService線程的run()方法最終調用的是RebalanceImpl類的rebalanceByTopic()方法,該方法是實現Consumer端負載均衡的核心。這里,rebalanceByTopic()方法會根據消費者通信類型為“廣播模式”還是“集群模式”做不同的邏輯處理。這里主要來看下集群模式下的主要處理流程:

(1) 從rebalanceImpl實例的本地緩存變量—topicSubscribeInfoTable中,獲取該Topic主題下的消息消費隊列集合(mqSet);

(2) 根據topic和consumerGroup為參數調用mQClientFactory.findConsumerIdList()方法向Broker端發送獲取該消費組下消費者Id列表的RPC通信請求(Broker端基于前面Consumer端上報的心跳包數據而構建的consumerTable做出響應返回,業務請求碼:GET_CONSUMER_LIST_BY_GROUP);

(3) 先對Topic下的消息消費隊列、消費者Id排序,然后用消息隊列分配策略算法(默認為:消息隊列的平均分配算法),計算出待拉取的消息隊列。這里的平均分配算法,類似于分頁的算法,將所有MessageQueue排好序類似于記錄,將所有消費端Consumer排好序類似頁數,并求出每一頁需要包含的平均size和每個頁面記錄的范圍range,最后遍歷整個range而計算出當前Consumer端應該分配到的記錄(這里即為:MessageQueue)。

rocketmq_design_8.png

(4) 然后,調用updateProcessQueueTableInRebalance()方法,具體的做法是,先將分配到的消息隊列集合(mqSet)與processQueueTable做一個過濾比對。

rocketmq_design_9.png
  • 上圖中processQueueTable標注的紅色部分,表示與分配到的消息隊列集合mqSet互不包含。將這些隊列設置Dropped屬性為true,然后查看這些隊列是否可以移除出processQueueTable緩存變量,這里具體執行removeUnnecessaryMessageQueue()方法,即每隔1s 查看是否可以獲取當前消費處理隊列的鎖,拿到的話返回true。如果等待1s后,仍然拿不到當前消費處理隊列的鎖則返回false。如果返回true,則從processQueueTable緩存變量中移除對應的Entry;

  • 上圖中processQueueTable的綠色部分,表示與分配到的消息隊列集合mqSet的交集。判斷該ProcessQueue是否已經過期了,在Pull模式的不用管,如果是Push模式的,設置Dropped屬性為true,并且調用removeUnnecessaryMessageQueue()方法,像上面一樣嘗試移除Entry;

最后,為過濾后的消息隊列集合(mqSet)中的每個MessageQueue創建一個ProcessQueue對象并存入RebalanceImpl的processQueueTable隊列中(其中調用RebalanceImpl實例的computePullFromWhere(MessageQueue mq)方法獲取該MessageQueue對象的下一個進度消費值offset,隨后填充至接下來要創建的pullRequest對象屬性中),并創建拉取請求對象—pullRequest添加到拉取列表—pullRequestList中,最后執行dispatchPullRequest()方法,將Pull消息的請求對象PullRequest依次放入PullMessageService服務線程的阻塞隊列pullRequestQueue中,待該服務線程取出后向Broker端發起Pull消息的請求。其中,可以重點對比下,RebalancePushImpl和RebalancePullImpl兩個實現類的dispatchPullRequest()方法不同,RebalancePullImpl類里面的該方法為空,這樣子也就回答了上一篇中最后的那道思考題了。

消息消費隊列在同一消費組不同消費者之間的負載均衡,其核心設計理念是在一個消息消費隊列在同一時間只允許被同一消費組內的一個消費者消費,一個消息消費者能同時消費多個消息隊列。

5 事務消息

Apache RocketMQ在4.3.0版中已經支持分布式事務消息,這里RocketMQ采用了2PC的思想來實現了提交事務消息,同時增加一個補償邏輯來處理二階段超時或者失敗的消息,如下圖所示。

rocketmq_design_10.png

5.1 RocketMQ事務消息流程概要

上圖說明了事務消息的大致方案,其中分為兩個流程:正常事務消息的發送及提交、事務消息的補償流程。

1.事務消息發送及提交:

(1) 發送消息(half消息)。

(2) 服務端響應消息寫入結果。

(3) 根據發送結果執行本地事務(如果寫入失敗,此時half消息對業務不可見,本地邏輯不執行)。

(4) 根據本地事務狀態執行Commit或者Rollback(Commit操作生成消息索引,消息對消費者可見)

2.補償流程:

(1) 對沒有Commit/Rollback的事務消息(pending狀態的消息),從服務端發起一次“回查”

(2) Producer收到回查消息,檢查回查消息對應的本地事務的狀態

(3) 根據本地事務狀態,重新Commit或者Rollback

其中,補償階段用于解決消息Commit或者Rollback發生超時或者失敗的情況。

5.2 RocketMQ事務消息設計

1.事務消息在一階段對用戶不可見

在RocketMQ事務消息的主要流程中,一階段的消息如何對用戶不可見。其中,事務消息相對普通消息最大的特點就是一階段發送的消息對用戶是不可見的。那么,如何做到寫入消息但是對用戶不可見呢?RocketMQ事務消息的做法是:如果消息是half消息,將備份原消息的主題與消息消費隊列,然后改變主題為RMQ_SYS_TRANS_HALF_TOPIC。由于消費組未訂閱該主題,故消費端無法消費half類型的消息,然后RocketMQ會開啟一個定時任務,從Topic為RMQ_SYS_TRANS_HALF_TOPIC中拉取消息進行消費,根據生產者組獲取一個服務提供者發送回查事務狀態請求,根據事務狀態來決定是提交或回滾消息。

在RocketMQ中,消息在服務端的存儲結構如下,每條消息都會有對應的索引信息,Consumer通過ConsumeQueue這個二級索引來讀取消息實體內容,其流程如下:

rocketmq_design_11.png

RocketMQ的具體實現策略是:寫入的如果事務消息,對消息的Topic和Queue等屬性進行替換,同時將原來的Topic和Queue信息存儲到消息的屬性中,正因為消息主題被替換,故消息并不會轉發到該原主題的消息消費隊列,消費者無法感知消息的存在,不會消費。其實改變消息主題是RocketMQ的常用“套路”,回想一下延時消息的實現機制。

2.Commit和Rollback操作以及Op消息的引入

在完成一階段寫入一條對用戶不可見的消息后,二階段如果是Commit操作,則需要讓消息對用戶可見;如果是Rollback則需要撤銷一階段的消息。先說Rollback的情況。對于Rollback,本身一階段的消息對用戶是不可見的,其實不需要真正撤銷消息(實際上RocketMQ也無法去真正的刪除一條消息,因為是順序寫文件的)。但是區別于這條消息沒有確定狀態(Pending狀態,事務懸而未決),需要一個操作來標識這條消息的最終狀態。RocketMQ事務消息方案中引入了Op消息的概念,用Op消息標識事務消息已經確定的狀態(Commit或者Rollback)。如果一條事務消息沒有對應的Op消息,說明這個事務的狀態還無法確定(可能是二階段失敗了)。引入Op消息后,事務消息無論是Commit或者Rollback都會記錄一個Op操作。Commit相對于Rollback只是在寫入Op消息前創建Half消息的索引。

3.Op消息的存儲和對應關系

RocketMQ將Op消息寫入到全局一個特定的Topic中通過源碼中的方法—TransactionalMessageUtil.buildOpTopic();這個Topic是一個內部的Topic(像Half消息的Topic一樣),不會被用戶消費。Op消息的內容為對應的Half消息的存儲的Offset,這樣通過Op消息能索引到Half消息進行后續的回查操作。

rocketmq_design_12.png

4.Half消息的索引構建

在執行二階段Commit操作時,需要構建出Half消息的索引。一階段的Half消息由于是寫到一個特殊的Topic,所以二階段構建索引時需要讀取出Half消息,并將Topic和Queue替換成真正的目標的Topic和Queue,之后通過一次普通消息的寫入操作來生成一條對用戶可見的消息。所以RocketMQ事務消息二階段其實是利用了一階段存儲的消息的內容,在二階段時恢復出一條完整的普通消息,然后走一遍消息寫入流程。

5.如何處理二階段失敗的消息?

如果在RocketMQ事務消息的二階段過程中失敗了,例如在做Commit操作時,出現網絡問題導致Commit失敗,那么需要通過一定的策略使這條消息最終被Commit。RocketMQ采用了一種補償機制,稱為“回查”。Broker端對未確定狀態的消息發起回查,將消息發送到對應的Producer端(同一個Group的Producer),由Producer根據消息來檢查本地事務的狀態,進而執行Commit或者Rollback。Broker端通過對比Half消息和Op消息進行事務消息的回查并且推進CheckPoint(記錄那些事務消息的狀態是確定的)。

值得注意的是,rocketmq并不會無休止的的信息事務狀態回查,默認回查15次,如果15次回查還是無法得知事務狀態,rocketmq默認回滾該消息。

6 消息查詢

RocketMQ支持按照下面兩種維度(“按照Message Id查詢消息”、“按照Message Key查詢消息”)進行消息查詢。

6.1 按照MessageId查詢消息

RocketMQ中的MessageId的長度總共有16字節,其中包含了消息存儲主機地址(IP地址和端口),消息Commit Log offset。“按照MessageId查詢消息”在RocketMQ中具體做法是:Client端從MessageId中解析出Broker的地址(IP地址和端口)和Commit Log的偏移地址后封裝成一個RPC請求后通過Remoting通信層發送(業務請求碼:VIEW_MESSAGE_BY_ID)。Broker端走的是QueryMessageProcessor,讀取消息的過程用其中的 commitLog offset 和 size 去 commitLog 中找到真正的記錄并解析成一個完整的消息返回。

6.2 按照Message Key查詢消息

“按照Message Key查詢消息”,主要是基于RocketMQ的IndexFile索引文件來實現的。RocketMQ的索引文件邏輯結構,類似JDK中HashMap的實現。索引文件的具體結構如下:

rocketmq_design_13.png

IndexFile索引文件為用戶提供通過“按照Message Key查詢消息”的消息索引查詢服務,IndexFile文件的存儲位置是:HOME\store\index\{fileName},文件名fileName是以創建時的時間戳命名的,文件大小是固定的,等于40+500W*4+2000W*20= 420000040個字節大小。如果消息的properties中設置了UNIQ_KEY這個屬性,就用 topic + “#” + UNIQ_KEY的value作為 key 來做寫入操作。如果消息設置了KEYS屬性(多個KEY以空格分隔),也會用 topic + “#” + KEY 來做索引。

其中的索引數據包含了Key Hash/CommitLog Offset/Timestamp/NextIndex offset 這四個字段,一共20 Byte。NextIndex offset 即前面讀出來的 slotValue,如果有 hash沖突,就可以用這個字段將所有沖突的索引用鏈表的方式串起來了。Timestamp記錄的是消息storeTimestamp之間的差,并不是一個絕對的時間。整個Index File的結構如圖,40 Byte 的Header用于保存一些總的統計信息,4*500W的 Slot Table并不保存真正的索引數據,而是保存每個槽位對應的單向鏈表的頭。20*2000W 是真正的索引數據,即一個 Index File 可以保存 2000W個索引。

“按照Message Key查詢消息”的方式,RocketMQ的具體做法是,主要通過Broker端的QueryMessageProcessor業務處理器來查詢,讀取消息的過程就是用topic和key找到IndexFile索引文件中的一條記錄,根據其中的commitLog offset從CommitLog文件中讀取消息的實體內容。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,382評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,038評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,616評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,112評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,355評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,869評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,727評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,928評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,165評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,585評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,892評論 2 372

推薦閱讀更多精彩內容