RocketMQ——Broker篇
1 Broker的初始化過程
呼叫BrokerController物件的initialize方法進行初始化工作。大致邏輯如下:
1、載入topics.json、consumerOffset.json、subscriptionGroup.json檔案,分別將各檔案的資料存入TopicConfigManager、ConsumerOffsetManager、SubscriptionGroupManager物件中;在初始化BrokerController物件的過程中,初始化TopicConfigManager物件時,預設初始化了"SELF_TEST_TOPIC"、"TBW102"(自動建立Topic功能是否開啟BrokerConfig.autoCreateTopicEnable=false,線上建議關閉)、"BenchmarkTest"、BrokerClusterName(叢集名稱)、BrokerName(Broker的名稱)、"OFFSET_MOVED_EVENT"這5個topic的資訊並存入了topicConfigTable變數中,在向NameServer註冊時發給NameServer進行登記;
2、初始化DefaultMessageStore物件,該物件是應用層訪問儲存層的訪問類;
2.1)AllocateMapedFileService服務執行緒,當需要建立MappedFile時(在MapedFileQueue.getLastMapedFile方法中),向該執行緒的requestQueue佇列中放入AllocateRequest請求物件,該執行緒會在後臺監聽該佇列,並在後臺建立MapedFile物件,即同時建立了物理檔案。
2.2)建立DispatchMessageService服務執行緒,該服務執行緒負責給commitlog資料建立ConsumeQueue資料和建立Index索引。
2.3)建立IndexService服務執行緒,該服務執行緒負責建立Index索引;
2.4)還初始化了如下服務執行緒,在呼叫DispatchMessageService物件的start方法時啟動這些執行緒:
服務類名 | 作用 |
---|---|
FlushConsumeQueueService | 邏輯佇列刷盤服務,每隔1秒鐘就將ConsumeQueue邏輯佇列、TransactionStateService.TranRedoLog變數的資料持久化到磁碟物理檔案中 |
CleanCommitLogService | 清理物理檔案服務,定期清理72小時之前的物理檔案。 |
CleanConsumeQueueService | 清理邏輯檔案服務,定期清理在邏輯佇列中的物理偏移量小於commitlog中的最小物理偏移量的資料,同時也清理Index中物理偏移量小於commitlog中的最小物理偏移量的資料。 |
StoreStatsService | 儲存層內部統計服務 |
HAService | 用於commitlog資料的主備同步 |
ScheduleMessageService | 用於監控延遲訊息,併到期後執行 |
TransactionStateService | 用於事務訊息狀態檔案,在RocketMQ-3.1.9版本中可以檢視原始碼 |
2.5)初始化CommitLog物件,在初始化該物件的過程中,第一,根據刷盤型別初始化FlushCommitLogService執行緒服務,若為同步刷盤(SYNC_FLUSH),則建立初始化為GroupCommitService執行緒服務;若為非同步刷盤(ASYNC_FLUSH)則建立初始化FlushRealTimeService執行緒服務;第二,初始化DefaultAppendMessageCallback物件;
2.6)啟動AllocateMapedFileService、DispatchMessageService、IndexService服務執行緒。
3、呼叫DefaultMessageStore.load載入資料:
3.1)呼叫ScheduleMessageService.load方法,初始化延遲級別列表。將這些級別("1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h")的延時存入延遲級別delayLevelTable:ConcurrentHashMap<Integer /* level */, Long/* delay timeMillis */>變數中,例如1s的kv值為1:1000,5s的kv值為2:5000,key值依次類推;每個延遲級別即為一個佇列。
3.2)呼叫CommitLog.load方法,在此方法中呼叫MapedFileQueue.load方法,將$HOME /store/commitlog目錄下的所有檔案載入到MapedFileQueue的List<MapedFile>變數中;
3.3)呼叫DefaultMessageStore.loadConsumeQueue方法載入consumequeue檔案資料到DefaultMessageStore.consumeQueueTable集合中。
3.4)呼叫TransactionStateService.load()方法載入tranStateTable檔案和tranRedoLog檔案;
3.5)初始化StoreCheckPoint物件,載入$HOME/store/checkpoint檔案,該檔案記錄三個欄位值,分別是物理佇列訊息時間戳、邏輯佇列訊息時間戳、索引佇列訊息時間戳。
3.6)呼叫IndexService.load方法載入$HOME/store/index目錄下的檔案。對該目錄下的每個檔案初始化一個IndexFile物件。然後呼叫IndexFile物件的load方法將IndexHeader載入到物件的變數中;再根據檢查是否存在abort檔案,若有存在abort檔案,則表示Broker表示上次是異常退出的,則檢查checkpoint的indexMsgTimestamp欄位值是否小於IndexHeader的endTimestamp值,indexMsgTimestamp值表示最後刷盤的時間,若小於則表示在最後刷盤之後在該檔案中還建立了索引,則要刪除該Index檔案,否則將該IndexFile物件放入indexFileList:ArrayList<IndexFile>索引檔案集合中。
3.7)恢復記憶體資料。
1)恢復每個ConsumeQueue物件的maxPhysicOffset變數的值(最後一個訊息的物理offset),遍歷consumeQueueTable集合中的每個topic/queueId下面的ConsumeQueue物件,呼叫ConsumeQueue物件recover方法。
2)根據是否有abort檔案來確定選擇何種方法恢復commitlog資料,若有該檔案則呼叫CommitLog物件的recoverAbnormally方法進行恢復,否則呼叫CommitLog物件的recoverNormally方法進行恢復。主要是恢復MapedFileQueue物件的commitedWhere變數值(即刷盤的位置),刪除該commitedWhere值所在檔案之後的commitlog檔案以及對應的MapedFile物件。
3)恢復CommitLog物件的topicQueueTable :HashMap<String/* topic-queueid */, Long/* offset */>變數值;遍歷consumeQueueTable集合,邏輯如下:
A)對每個topic/queueId下面的ConsumeQueue物件,獲取該物件的最大偏移量,等於MapedFileQueue.getMaxOffset/20;然後以topic-queueId為key值,該偏移量為values,存入topicQueueTable變數中;
B)以commitlog的最小物理偏移量修正ConsumeQueue物件的最小邏輯偏移量minLogicOffset;
4)呼叫TransactionStateService.tranRedoLog:ConsumeQueue物件的recover()方法恢復tranRedoLog檔案;
5)呼叫TransactionStateService.recoverStateTable(boolean lastExitOK)方法恢復tranStateTable檔案,
5.1)若存在abort檔案則上一次退出是異常關閉,採用如下恢復方式:
A)刪除tranStateTable檔案;
B)新建tranStateTable檔案,然後從tranRedoLog檔案的開頭開始偏移,找出所有事務訊息狀態為PREPARED的記錄,該記錄為每個事務訊息在commitlog的物理位置;
C)遍歷這些PREPARED狀態的事務訊息的物理位置集合,以物理位置commitlogoffset值從commitlog檔案中找出獲取每個事務訊息的commitlogoffset、msgSize、StoreTimeStamp以及producerGroup的hash值,然後呼叫TransactionStateService.appendPreparedTransaction(long clOffset, int size, int timestamp, int groupHashCode)方法將這些資訊寫入新建的tranStateTable檔案中,並更新TransactionStateService.tranStateTableOffset變數值(表示在tranStateTable檔案中的訊息個數);
5.2)若不存在abort檔案則上一次是正常關閉,採用如下恢復方式:
與呼叫ConsumeQueue物件的recover方法恢復ConsumeQueue資料的邏輯是一樣的;在恢復完tranStateTable檔案資料之後計算該檔案中的訊息個數並賦值給TransactionStateService.tranStateTableOffset變數值,計算方式為MapedFileQueue.getMaxOffset()/24;
4、初始化Netty服務端NettyRemotingServer物件;
5、初始化傳送訊息執行緒池(sendMessageExecutor)、拉取訊息執行緒池(pullMessageExecutor)、管理Broker執行緒池(adminBrokerExecutor)、客戶端管理執行緒池(clientManageExecutor)。
6、註冊事件處理器,包括髮送訊息事件處理器(SendMessageProcessor)、拉取訊息事件處理器、查詢訊息事件處理器(QueryMessageProcessor,包括客戶端的心跳事件、登出事件、獲取消費者列表事件、更新更新和查詢消費進度consumerOffset)、客戶端管理事件處理器、結束事務處理器(EndTransactionProcessor)、預設事件處理器(AdminBrokerProcessor)。
7、啟動如下定時任務:
1)Broker的統計功能;
2)每隔5秒對ConsumerOffsetManager.offsetTable: ConcurrentHashMap<String/* topic@group */, ConcurrentHashMap<Integer, Long>>變數的內容進行持久化操作,持久化到consumerOffset.json檔案中;
3)掃描被刪除的topic,將offsetTable中的對應的消費進度記錄也刪掉。大致邏輯是遍歷ConsumerOffsetManager.offsetTable變數:
A)以group和topic為引數呼叫ConsumerManager.findSubscriptionData(String group, String topic)方法獲取當前該topic和group的訂閱資料;若獲取的資料為空,則認為該topic和group已經不在當前訂閱關係中。
B)遍歷該topic@group下面的所有佇列的消費進度(ConsumerOffsetManager.offsetTable變數的values值),檢查每個佇列的消費進度是否都大於該佇列最小邏輯偏移量;
C)若該topic和group已經不在當前訂閱資料表中,而且在offsetTable中的消費進度已經小於ConsumeQueue的最小邏輯偏移量minLogicOffset,則從offsetTable變數中刪除該記錄。
8、檢查Broker配置的Name Server地址是否為空,若不為空,則更新Broker的Netty客戶端BrokerOuterAPI.NettyRemotingClient的Name Server地址;若為空且配置了允許從地址伺服器找Name Server地址,則啟動定時任務,地址伺服器的路徑是"http://" + WS_DOMAIN_NAME + ":8080/rocketmq/" + WS_DOMAIN_SUBGROUP ;其中WS_DOMAIN_NAME由配置引數rocketmq.namesrv.domain設定,WS_DOMAIN_SUBG由配置引數rocketmq.namesrv.domain.subgroup設定;
9、若該Broker為主用,則啟動定時任務列印主用Broker和備用Broker在Commitlog上的寫入位置相差多少個位元組。
主用Broker的寫入位置計算方式:獲取最後一個MappedFile的fileFromOffset和wrotePostion值,相加即為最新的寫入位置。
備用Broker的寫入位置從HAService. push2SlaveMaxOffset獲得,該值表示主用Broker同步到Slave的最大Offset。
10、若該Broker為備用,首先檢查是否配置了主用Broker的地址,在備用Broker可以在broker.properties配置檔案中設定haMasterAddress引數來指定主用Broker的地址,若設定了,則更新HAService.HAClient.masterAddress的值;若沒有配置,則設定標誌位updateMasterHAServerAddrPeriodically等於true,在該Broker向NameServer註冊的時候根據NameServer返回的主用Broker地址來設定HAClient.masterAddress值;說明在配置引數haMasterAddress設定的主用地址的優先順序要高於NameServer返回的主用Broker地址。
11、若該Broker為備用,設定同步Config檔案的定時任務,每隔60秒呼叫SlaveSynchronize.syncAll()方法向主用Broker請求一次config類檔案的同步。
2 Broker的啟動過程
1、呼叫DefaultMessageStore.start方法啟動DefaultMessageStore物件中的一些服務執行緒。
1.1)啟動FlushConsumeQueueService執行緒服務;
1.2)呼叫CommitLog.start方法,啟動CommitLog物件中的FlushCommitLogService執行緒服務,若是同步刷盤(SYNC_FLUSH)則是啟動GroupCommitService執行緒服務;若是非同步刷盤(ASYNC_FLUSH)則是啟動FlushRealTimeService執行緒服務;
1.3)啟動StoreStatsService執行緒服務;
1.4)在Broker是主用模式的時候,啟動ScheduleMessageService執行緒服務;在啟動的過程中,向每個級別的對應的佇列都增加定時任務,週期性的檢查佇列中是否有延遲訊息存在,若有則到期後就執行該延遲訊息,執行的目的是將真正的訊息寫入commitlog中,並生成consumequeue和index資料。
1.5)在Broker是備用模式的時候,啟動ReputMessageService執行緒服務;首先設定該執行緒服務的reputFromOffset值等於備用Broker本地commitlog檔案的最大物理offset值;在該執行緒的run方法中,不斷地檢查在commitlog檔案中reputFromOffset偏移量之後是否有新資料新增,若有則對這些資料建立邏輯佇列和Index索引;在主從同步時會使用該執行緒服務。
1.6)啟動HAService執行緒服務,進行commitlog資料的主備同步;
1.7)在目錄$HOME/store下面建立abort檔案,沒有任何內容;只是標記是否正常關閉,若為正常關閉,則在關閉時會刪掉此檔案;若未正常關閉則此檔案一直保留,下次啟動時根據是否存在此檔案進行不同方式的記憶體資料恢復。
1.8)設定定時任務,每隔10秒呼叫CleanCommitLogService.run和CleanConsumeQueueService.run方法進行一次資源清理,分別清理物理檔案以及對應的邏輯檔案。
1.9)呼叫TransactionStateService.start方法,在該方法中為每個tranStateTable檔案初始化一個定時任務,該定時任務的作用是每隔1分鐘遍歷一遍tranStateTable檔案中的資料對於處於PREPARED狀態的事務訊息,向Producer回查事務訊息的最新狀態;
2、啟動Broker的Netty服務端NettyRemotingServer。監聽消費者或生產者發起的請求資訊並處理;
3、啟動BrokerOuterAPI中的NettyRemotingClient,即建立與NameServer的連結,用於自身Broker與其他模組的RPC功能呼叫;包括獲取NameServer的地址、註冊Broker、登出Broker、獲取Topic配置、獲取訊息進度資訊、獲取訂閱關係等RPC功能。
4、啟動拉訊息管理服務PullRequestHoldService,當拉取訊息時未發現訊息,則初始化PullRequeset物件放入該服務執行緒的pullRequestTable列表中,由PullRequestHoldService每隔1秒鐘就檢查一遍每個PullRequeset物件要讀取的資料位置在consumequeue中是否已經有資料了,若有則交由PullManageProcessor處理。
5、啟動ClientHousekeepingService服務,在啟動過程中設定定時任務,該定時任務每隔10秒就檢查一次客戶端的連結情況,清除不活動的連結(即在120秒以內沒有資料互動了),包括:
5.1)Producer的連結。檢查ProducerManager.groupChannelTable:HashMap<String, HashMap<Channel, ClientChannelInfo>>變數,檢視每個ClientChannelInfo的lastUpdateTimestamp距離現在是否已經超過了120秒,若是則從該變數中刪除此連結。
5.2)Consumer的連結。檢查ConsumerManager.consumerTable: ConcurrentHashMap<String, ConsumerGroupInfo>變數,檢視每個ClientChannelInfo的lastUpdateTimestamp距離現在是否已經超過了120秒,若是則從該變數中刪除此連結。
5.3)過濾伺服器的連結。檢查FilterServerManager.filterServerTable:ConcurrentHashMap<Channel, FilterServerInfo>,同樣是檢查lastUpdateTimestamp變數值距離現在是否已經超過了120秒,若是則從該變數中刪除此連結。
6、啟動FilterServerManager,每隔30秒定期一次檢查Filter Server個數,若沒有達到16個則建立;
7、首先呼叫BrokerController.registerBrokerAll方法立即向NameServer註冊Broker;然後設定定時任務,每隔30秒呼叫一次該方法向NameServer註冊;
8、啟動每隔5秒對未使用的topic資料進行清理的定時任務。該定時任務呼叫DefaultMessageStore.cleanUnusedTopic(Set<String>topics)方法。
3 向NameServer註冊Broker
呼叫BrokerController.registerBrokerAll方法立即向NameServer註冊Broker。大致步驟如下:
1、將TopicConfigManager.topicConfigTable變數序列化成TopicConfigSerializeWrapper物件;
2、BrokerOuterAPI.registerBrokerAll(String clusterName, StringbrokerAddr, String brokerName, long brokerId, String haServerAddr, TopicConfigSerializeWrapper topicConfigWrapper, List<String> filterServerList, boolean oneway)方法向Name Server註冊;其中haServerAddr等於"該Broker的本地IP地址:埠號(預設為10912)";在該方法中遍歷所有的NameServer地址,對於每個NameServer地址均呼叫BrokerOuterAPI.registerBroker方法,在registerBroker方法中建立RegisterBrokerRequestHeader物件,然後向NameServer傳送REGISTER_BROKER請求碼;
3、根據updateMasterHAServerAddrPeriodically標註位(在初始化時若Broker的配置檔案中沒有haMasterAddress引數配置,則標記為true,表示註冊之後需要更新主用Broker地址)以及NameServer返回的HaServerAddr地址是否為空,若標記位是true且返回的HaServerAddr不為空,則用HaServerAddr地址更新HAService.HAClient.masterAddress的值;該HAClient.masterAddress值用於主備Broker之間的commitlog資料同步之用;
4、用NameServer返回的MasterAddr值更新SlaveSynchronize.masterAddr值,用於主備Broker同步Config檔案使用;
5、根據Name Server上的配置引數ORDER_TOPIC_CONFIG的值來更新Broker端的TopicConfigManager.topicConfigTable變數值,對於在ORDER_TOPIC_CONFIG中配置了的topic,將該topic的配置引數order置為true,否則將該topic的配置引數order置為false;
4 清理未使用的topic資料
呼叫DefaultMessageStore.cleanUnusedTopic(Set<String>topics)方法對未使用的topic資料進行清理。
1、請求引數topics集合取至TopicConfigManager.topicConfigTable列表的key值集合;由Broker的心跳檢測功能來維護該topicConfigTable列表的資料,該列表資料對應的是topics.json檔案中的資料;
2、遍歷DefaultMessageStore.consumeQueueTable集合,若該集合中的某個topic在topicConfigTable列表已經找不到了,則在consumeQueueTable集合中刪除該topic對應的記錄;以及該topic目錄下面的所有物理檔案;然後以該topic和該topic目錄下面所有佇列ID組成的"topic-queueid"為key值刪除CommitLog.topicQueueTable集合中對應的記錄;
5 根據topic和group查詢Consumer訂閱資訊(findSubscriptionData)
呼叫ConsumerManager.findSubscriptionData(String group, String topic)方法查詢當前的訂閱資訊。
1、從consumerTable:ConcurrentHashMap<String/* Group */, ConsumerGroupInfo>變數以group獲取相應的 ConsumerGroupInfo物件;若該物件為空,則返回null;
2、若該物件不為空,則從該ConsumerGroupInfo物件的subscriptionTable:ConcurrentHashMap<String/* Topic */, SubscriptionData>變數中獲取SubscriptionData物件;並返回SubscriptionData物件,即為訂閱資訊。
6 處理Producer發來的訊息
Broker端的NettyRemotingServer監聽請求訊息,收到請求碼為SEND_MESSAGE/SEND_MESSAGE_V2的訊息後轉由SendMessageProcessor處理。為了降低網路傳輸數量,設計了兩種SendMessageRequestHeader物件,一種是物件的變數名用字母簡寫替代,類名是SendMessageRequestHeaderV2,一種是物件的變數名是完整的,類名是SendMessageRequestHeader。大致處理過程如下:
1、解碼收到的訊息,並構建SendMessageRequestHeader物件;
2、若SendMessageProcessor處理器設定了傳送訊息的鉤子sendMessageHookList:List<SendMessageHook>,則呼叫該鉤子類的sendMessageBefore方法,執行傳送前的處理方法,在傳送訊息結果之後呼叫sendMessageAfter 方法。 該鉤子類是為了便於業務層面的擴充套件而設計的。
3、呼叫SendMessageProcessor.sendMessage(ChannelHandlerContext ctx, RemotingCommand request, SendMessageContext mqtraceContext, SendMessageRequestHeader requestHeader)方法處理的訊息並返回處理結果PutMessageResult物件;
3.1)檢查該Broker是否有寫的許可權,該訊息的topic的配置(TopicConfigManager.topicConfigTable)中是否為有序(即屬性order等於true)。若沒有寫的許可權並且為該topic配置為有序,則該Broker不能傳送此訊息;直接返回給Producer響應訊息,訊息程式碼為NO_PERMISSION;
3.2)檢查訊息的topic是否符合要求,等於"TBW102"或者Broker的叢集名字(配置引數brokerClusterName指定的)的都屬於不合規的訊息主題,直接返回給Producer響應訊息,訊息程式碼為SYSTEM_ERROR;
3.3)從TopicConfigManager.topicConfigTable變數中獲取該topic的配置物件TopicConfig,若該物件為null,則呼叫TopicConfigManager. TopicConfigManager.createTopicInSendMessageMethod方法建立該topic的配置資訊TopicConfig;若該TopicConfig物件仍然為null並且topic是以"%RETRY%"開頭則呼叫TopicConfigManager.createTopicInSendMessageBackMethod方法建立TopicConfig物件;最後在堅持一次TopicConfig物件,若為null,則直接返回給Producer響應訊息,訊息程式碼為TOPIC_NOT_EXIST;
3.4)檢查訊息的queueId是否合法,即不能大於該topic配置中的讀寫佇列數的最大值,否則返回給Producer響應訊息,訊息程式碼為SYSTEM_ERROR;
3.5)Broker的配置引數rejectTransactionMessage表示是否接收事務訊息,預設是具有處理事務訊息的權利,在訊息的property屬性中"TRAN_MSG"表示是否為空;若該Broker沒有接受事務訊息的權利但此訊息的property屬性中"TRAN_MSG"不為空(即為事務訊息)則直接返回給Producer響應訊息,訊息程式碼為NO_PERMISSION;
3.6構建MessageExtBrokerInner物件,呼叫呼叫DefaultMessageStore.putMessage(MessageExtBrokerInner msg)方法完成訊息的寫入,將訊息內容寫入commitlog檔案中。
4、上一步返回的PutMessageResult物件中:
4.1)若putMessageStatus等於PUT_OK,則置響應訊息的訊息程式碼為SUCCESS;
4.2)若putMessageStatus等於FLUSH_DISK_TIMEOUT、FLUSH_SLAVE_TIMEOUT或者SLAVE_NOT_AVAILABLE時,則置響應訊息的訊息程式碼為putMessageStatus的值;
4.3)若putMessageStatus等於CREATE_MAPEDFILE_FAILED,則置響應訊息的訊息程式碼為SYSTEM_ERROR;
4.4)若putMessageStatus等於MESSAGE_ILLEGAL,則置響應訊息的訊息程式碼為MESSAGE_ILLEGAL;
4.5)若putMessageStatus等於SERVICE_NOT_AVAILABLE,則置響應訊息的訊息程式碼為SERVICE_NOT_AVAILABLE;
4.5)若putMessageStatus等於UNKNOWN_ERROR,則置響應訊息的訊息程式碼為UNKNOWN_ERROR;
5、對於4.1和4.2的結果,訊息是寫入成功了的,這是在刷盤或者同步方面有問題,仍然將messageId、queueId、邏輯offset分別依次賦值給響應訊息的MsgId、queueId、queueOffset欄位;
7 處理與客戶端的心跳訊息
接受到客戶端傳送的HEART_BEAT請求碼之後,由ClientManageProcessor.processRequest(ChannelHandlerContext ctx, RemotingCommand request)方法處理該請求。其中心跳訊息呼叫heartBeat(ChannelHandlerContext ctx, RemotingCommand request)方法處理。大致邏輯如下:
1、解碼接受訊息,生成HeartbeatData物件;
2、根據連結的Channel、ClientID等資訊初始化ClientChannelInfo物件;
3、若HeartbeatData物件中的ConsumerData集合有資料,則進行Consumer註冊,對於該集合中的每個ConsumerData物件遍歷如下操作步驟;(在接受到Consumer端的心跳資訊後處理下列邏輯)。
3.1)以心跳訊息中的GroupName值呼叫SubscriptionGroupManager.findSubscriptionGroupConfig(String GroupNmae)方法獲得SubscriptionGroupConfig物件;詳見1.8.3小節。
3.2)建立以%RETRY%+GroupName為topic值的topic配置資訊。首先以該topic值在TopicConfigManager.topicConfigTable中查詢是否存在,若不存在則建立TopicConfig物件,並存入topicConfigTable中,同時將該topicConfigTable變數的值持久化到topics.json檔案中;
3.3)呼叫ConsumerManager.registerConsumer(String group, ClientChannelInfo clientChannelInfo, ConsumeType consumeType, MessageModel messageModel, ConsumeFromWhere consumeFromWhere, Set<SubscriptionData> subList)方法進行Consumer的註冊,其中Set<SubscriptionData>集合是訊息中的SubscriptionDataSet變數;
4、若HeartbeatData物件中的ProducerData集合有資料,則對每個ProducerData物件遍歷呼叫ProducerManager.registerProducer(String group,ClientChannelInfo clientChannelInfo)方法進行Producer註冊;在接受到Producer的心跳資訊後處理該邏輯。
5、返回成功;
8 註冊Consumer資訊
呼叫ConsumerManager.registerConsumer(String groupname, ClientChannelInfo clientChannelInfo, ConsumeType consumeType, MessageModel messageModel, ConsumeFromWhere consumeFromWhere, Set<SubscriptionData> subList)方法進行Consumer的註冊。就是更新ConsumerManager.consumerTable:ConcurrentHashMap<String/* Group */, ConsumerGroupInfo>變數的值,大致邏輯如下:
1、以引數groupname從ConsumerManager.consumerTable中獲取ConsumerGroupInfo物件;若沒有,則初始化ConsumerGroupInfo物件並存入consumerTable列表中,並返回該ConsumerGroupInfo物件;
2、更新該ConsumerGroupInfo物件中的對應的渠道物件ClientChannelInfo物件的資訊並返回update值,初始化為false。以該連結的Channel從ConsumerGroupInfo.channelInfoTable:ConcurrentHashMap<Channel, ClientChannelInfo>變數中獲取ClientChannelInfo物件,若該物件為空,則將請求引數中的ClientChannelInfo物件存入該Map變數中,並且認為Channel被更新過故置update=true;若該物件不為空則檢查已有的ClientChannelInfo物件的ClientId值是否與新傳入的ClientChannelInfo物件的ClientId值一致,若不一致,則替換該渠道資訊;最後更新ClientChannelInfo的時間戳;
3、更新該ConsumerGroupInfo物件中的訂閱資訊。遍歷請求引數Set<SubscriptionData>集合中的每個SubscriptionData物件,初始化置update=false;
3.1)以SubscriptionData物件的topic值從ConsumerGroupInfo.subscriptionTable變數中獲取已有的SubscriptionData物件;若獲取的SubscriptionData物件為null,則以topic值為key值將遍歷到的該SubscriptionData物件存入subscriptionTable變數中,並且認為訂閱被更新過故置update=true;否則若已有的SubscriptionData物件的SubVersion標記小於新的SubscriptionData物件的SubVersion標記,就更新subscriptionTable變數中已有的SubscriptionData物件;
3.2)檢查ConsumerGroupInfo.subscriptionTable變數中每個topic,若topic不等於請求引數SubscriptionData集合的每個SubscriptionData物件的topic變數值;則從subscriptionTable集合中將該topic的記錄刪除掉,並且認為訂閱被更新過故置update=true;
3.4)更新ConsumerGroupInfo物件中的時間戳;
4、若上述兩步返回的update變數值有一個為true,則呼叫DefaultConsumerIdsChangeListener.consumerIdsChanged(String group, List<Channel> channels)方法,向每個Channel下的Consumer傳送NOTIFY_CONSUMER_IDS_CHANGED請求碼,在Consumer收到請求之後呼叫執行緒的wakeup方法,喚醒RebalanceService服務執行緒;
9 註冊Producer資訊
呼叫ProducerManager.registerProducer(String groupName,
ClientChannelInfo clientChannelInfo)方法進行Producer註冊。就是更新ProducerManager.groupChannelTable:HashMap<String,HashMap<Channel,ClientChannelInfo>>變數值。大致邏輯如下:
1、以groupName從ProducerManager.groupChannelTable: HashMap<String,HashMap<Channel,ClientChannelInfo>>變數中獲取values值;
2、若沒有獲取到,則建立一個新的HashMap<Channel,ClientChannelInfo>作為values值,並以groupName值為key值存入groupChannelTable遍歷中;
3、以Chanel為key值從上面的values值中獲取ClientChannelInfo物件;若該物件為空,則將該Channel和請求引數clientChannelInfo新增到此values值中。
4、更新更新ClientChannelInfo物件的時間戳;
以相同的邏輯將Producer的clientChannelInfo物件存入hashcodeChannelTable:HashMap<Integer /* group hash code */, List<ClientChannelInfo>>變數中,只是key值為groupName的hash值;
10 查詢消費進度(QUERY_CONSUMER_OFFSET)
Broker接收到QUERY_CONSUMER_OFFSET請求碼之後,呼叫ClientManageProcessor.queryConsumerOffset(ChannelHandlerContext ctx, RemotingCommand request)方法處理。大致步驟如下:
1、呼叫ConsumerOffsetManager.queryOffset(String group, String topic, int queueId),根據topic@consumerGroup、queueId從ConsumerOffsetManager.offsetTable變數中獲取offset值並返回;若從offsetTable中查詢不到,則返回-1;
2、若第1步中返回值大於等於零,則將該值返回給請求者,若返回值小於零,則說明訂閱組不存在,呼叫DefaultMessageStore.getMinOffsetInQuque(String topic,int queueId)方法獲取最小邏輯offset值。
3、若第2步返回的最小邏輯offset值不大於零(可能未來得及建立該topic和queueId下面的consumequeue或者剛啟動還未更新最小邏輯offset),則檢查該consumequeue佇列的第一個訊息單元中的物理偏移量與commitlog中最大物理偏移量的差值是否大於記憶體的最大使用率,若不大於記憶體的最大使用率,認為堆積的資料不多,則返回請求者消費進度為0,表示從0開始消費;否則返回未查詢到結果;
11 更新消費進度(UPDATE_CONSUMER_OFFSET)
Broker接收到UPDATE_CONSUMER_OFFSET請求碼之後,呼叫ClientManageProcessor.updateConsumerOffset(ChannelHandlerContext ctx, RemotingCommand request)方法處理。大致步驟如下:
1、檢查ClientManageProcessor.consumeMessageHookList變數中是否設定了消費訊息之後的回撥類(在BrokerController.registerConsumeMessageHook(ConsumeMessageHook hook)方法中可以設定);若設定了則構建ConsumeMessageContext物件並執行ConsumeMessageHook.consumeMessageAfter(ConsumeMessageContext context)方法;
2、以請求訊息中的topic和group構成"topic"@"consumerGroup"為key值,以queueId和請求訊息中的offset構成Map存入ConsumerOffsetManager.offsetTable: ConcurrentHashMap<String/* topic@group */, ConcurrentHashMap<Integer, Long>>變數中;
12 處理Consumer拉取訊息
在Broker端收到PULL_MESSAGE請求碼之後,初始化一個執行緒然後將該執行緒放入執行緒池中,由該執行緒完成拉取訊息的邏輯處理。在該run方法中,由PullMessageProcessor.processRequest(ChannelHandlerContext ctx, RemotingCommand request)方法處理主用邏輯並返回RemotingCommand物件,若返回的物件RemotingCommand不為空(可能在processRequest方法已經傳送應答了)寫入Channel中。大致邏輯如下:
1、檢查Broker是否有讀許可權;若沒有則返回響應訊息,訊息程式碼為NO_PERMISSION;
2、以請求資訊中的consumerGroup名稱為引數呼叫SubscriptionGroupManager.findSubscriptionGroupConfig(String GroupNmae)方法獲取訂閱資訊。若沒有找到該Consumer的訂閱資訊並且Broker不允許自動建立訂閱組,則返回響應訊息,訊息程式碼為SUBSCRIPTION_GROUP_NOT_EXIST;若找到了則檢查該訂閱組是否可以消費訊息,即SubscriptionGroupConfig.consumeEnable變數是否為true;若不能則直接返回響應訊息,訊息程式碼為NO_PERMISSION;
3、拉取訊息PullMessageRequestHeader物件的sysFlag標誌位,第一位表示commitOffsetEnable,第二位表示suspend;第三位表示subscription;第四位表示classFilterMode,這些欄位都是有應用端設定之後,由buildSysFlag(final boolean commitOffset, final boolean suspend,final boolean subscription, final boolean classFilter)方法生成的sysFlag值;
4、根據請求訊息中的topic值從TopicConfigManager.topicConfigTable中獲取TopicConfig物件,若為空則表示consumer消費的topic不存,直接返回響應訊息,訊息程式碼為TOPIC_NOT_EXIST;
5、若不為空則首先,檢查topicConfig是否有讀的許可權;若沒有則返回響應訊息,訊息程式碼為NO_PERMISSION;然後檢查請求消費的佇列ID是否有效(即要大於0並且小於讀佇列總數),若無效則返回響應訊息,訊息程式碼為SYSTEM_ERROR;
6、檢查PullMessageRequestHeader物件的sysFlag標誌位的第三位subscription標識,若為true,則直接用請求訊息中的consumerGroup、topic、subscription構建SubscriptionData物件;若為false則還是要從ConsumerManager.consumerTable:ConcurrentHashMap<String/* Group */, ConsumerGroupInfo>變數中獲取SubscriptionData物件,並檢查該SubscriptionData物件的SubVersion是否小於請求資訊中的SubVersion,若小於,則返回響應訊息,訊息程式碼為SUBSCRIPTION_NOT_LATEST;
7、呼叫DefaultMessageStore.getMessage(String group,Stringtopic,int queueId,long offset, int maxMsgNums, SubscriptionData subscriptionData)方法讀取訊息,其中引數offset為請求訊息的queueOffset引數值,返回GetMessageResult物件;
8、用DefaultMessageStore.getMessage方法的返回值GetMessageResult物件中的引數值來初始化PullMessageResponseHeader物件;
9、若GetMessageResult.suggestPullingFromSlave等於true,即表示該Broker消費很慢,存在訊息堆積後,設定suggestWhichBrokerId等於1,即將Consumer的消費請求重定向到另外一臺Slave機器。若為false則將suggestWhichBrokerId仍然設定為當前Broker的Id;
10、檢查GetMessageResult.status的值:
10.1)若等於FOUND,這將返回物件RemotingCommand的code設定為SUCCESS,然後檢查Broker是否設定了回撥類PullMessageProcessor.ConsumeMessageHookList;若設定了則執行該回撥類中的consumeMessageBefore方法;
10.2)若等於MESSAGE_WAS_REMOVING(說明物理檔案被刪除了)或者等於NO_MATCHED_MESSAGE,則設定RemotingCommand的code等於PULL_RETRY_IMMEDIATELY;
10.3)若等於NO_MESSAGE_IN_QUEUE;當請求訊息中的讀取開始位置offset值不等於0時,將RemotingCommand的code設定為PULL_OFFSET_MOVED(拉訊息請求的Offset不合法,太小或太大),否則將RemotingCommand的code設定為PULL_NOT_FOUND;
10.4)若等於OFFSET_FOUND_NULL(未獲取到consumequeue資料)或者OFFSET_OVERFLOW_ONE(請求引數offset等於consumequeue中最大物理偏移量)時;則將RemotingCommand的code設定為PULL_NOT_FOUND;
10.5)若等於OFFSET_OVERFLOW_BADLY(請求引數offset大於consumequeue中最大物理偏移量)或者OFFSET_TOO_SMALL(請求引數offset小於consumequeue中最小物理偏移量)時;則將RemotingCommand的code設定為PULL_OFFSET_MOVED;
11、檢查返回物件RemotingCommand的code值:
11.1)若等於SUCCESS,首先,呼叫BrokerStatsManager物件進行統計記錄;然後以訊息的總大小建立訊息頭部,並以該頭部位元組和GetMessageResult物件初始化ManyMessageTransfer物件,該物件實現了Netty的FileRegion介面;再呼叫Channel.WriteAndFlush方法將該ManyMessageTransfer物件直接寫入Channel中將獲取到的資料返回給呼叫者,並新增一個渠道監聽器ChannelFutureListener,該監聽器實現了operationComplete方法,當訊息方式完成後呼叫此監聽器的該方法,在方法中呼叫GetMessageResult物件的release方法,釋放資源;最後置返回物件RemotingCommand為null;
11.2)若等於PULL_NOT_FOUND;並且允許長輪詢(入參brokerAllowSuspend在processRequest(ChannelHandlerContext ctx,
RemotingCommand request)方法呼叫時設定為true),即sysFlag中suspend的標記為true,則初始化PullRequest物件【初始化過程如下:其中該物件的pullFromThisOffset等於請求訊息的queueOffset值;判斷該Broker是否支援長輪詢(BrokerConfig.longPollingEnable設定,預設為true),若是支援則設定該物件的timeoutMillis變數等於請求訊息的suspendTimeoutMillis值,否則認為支援短輪詢,設定timeoutMillis變數等於BrokerConfig.shortPollingTimeMills的值;設定該物件的suspendTimestamp等於當前時間戳】,然後呼叫PullRequestHoldService.suspendPullRequest(String topic, int queueId, PullRequest pullRequest)方法將PullRequest物件存入PullRequestHoldService.pullRequestTable變數中,由PullRequestHoldService服務執行緒定期檢查是否有符合要求的資料;
11.3)若等於PULL_OFFSET_MOVED。當該broker角色是主用或者BrokerConfig.offsetCheckInSlave(是否需要糾正位點,預設為false)等於true時,則首先用topic、queueId、brokerName引數初始化MessageQueue物件,然後初始化OffsetMovedEvent物件,其中該物件的MssageQueue變數等於剛初始化的MessageQueue物件;再呼叫PullMessageProcessor.generateOffsetMovedEvent(OffsetMovedEvent event)方法構建topic值為"OFFSET_MOVED_EVENT"的訊息物件MessageExtBrokerInner,其中訊息內容為OffsetMovedEvent物件的序列化;最後呼叫MessageStore.putMessage(MessageExtBrokerInner msg)方法將該訊息存入CommitLog中;
11.4)當該broker角色是備用或者BrokerConfig.offsetCheckInSlave(是否需要糾正位點,預設為false)等於false時,將suggestWhichBrokerId設定為當前Broker的Id,並且將code設定為RemotingCommand的code設定為PULL_RETRY_IMMEDIATELY;
12、若broker是主用,並且允許長輪詢(入參brokerAllowSuspend=true)並且SysFlag的commitoffset標記位為true則呼叫ConsumerOffsetManager.commitOffset(String group, String topic, int queueId, long offset)方法將請求訊息中的commitOffset值儲存到ConsumerOffsetManager.offsetTable變數中;
13 對未拉取到的訊息進行重試(PullRequestHoldService)
在啟動Broker的過程中啟動該執行緒服務,主要是對於未拉取到的資訊進行補償,在後臺每個1秒鐘進行嘗試拉取資訊。該執行緒run方法的大致邏輯如下:
遍歷PullRequestHoldService.pullRequestTable: ConcurrentHashMap<String/* topic@queueid */,ManyPullRequest>變數中的每個Key值,其中ManyPullRequest物件是一個封裝ArrayList<PullRequest>列表的物件;由Key值解析出topic和queueId,然後呼叫DefaultMessageStore.getMaxOffsetInQuque(String topic,int queueId)方法獲取該topic和queueId下面的佇列的最大邏輯Offset,再呼叫PullRequestHoldService.notifyMessageArriving(String topic, int queueId, long maxOffset)方法。主要的拉取訊息邏輯在此方法中。
1、根據topic和queueId構建key值從pullRequestTable中獲取ManyPullRequest物件,該物件中包含了PullRequest的集合列表;
2、遍歷該PullRequest的集合中的每個PullRequest物件,遍歷步驟如下:
2.1)檢查當前最大邏輯offset(入參maxOffset)是否大於該PullRequest物件的pullFromThisOffset值(該值等於當初請求訊息的queueOffset值);
2.2)若當前最大邏輯offset大於請求的queueOffset值,說明請求的讀取偏移量已經有資料達到了,則呼叫PullMessageProcessor.excuteRequestWhenWakeup(Channel channel, RemotingCommand request)方法進行訊息的拉取,在該方法中的處理邏輯與收到PULL_MESSAGE請求碼之後的處理邏輯基本一致,首先建立一個執行緒,然後將該執行緒放入執行緒池中,該執行緒的主要目的是呼叫PullMessageProcessor.processRequest(Channel channel, RemotingCommand request, boolean brokerAllowSuspend)方法,其中入參brokerAllowSuspend等於false,表示若未拉取到訊息,則不再採用該執行緒的補償機制了;然後繼續遍歷下一個PullRequest物件;
2.3)若當前最大邏輯offset小於請求的queueOffset值;則再次讀取最大邏輯offset進行第2.2步的嘗試,若當前最大邏輯offset還是小於請求的queueOffset值,則檢查該請求是否超時,即PullRequest物件的suspendTimestamp值加上timeoutMillis值是否大於當前時間戳,若已超時則直接呼叫PullMessageProcessor.excuteRequestWhenWakeup(Channel channel, RemotingCommand request)方法進行訊息的拉取,然後繼續遍歷下一個PullRequest物件;若未超時則將該PullRequest物件存入臨時的PullRequest列表中,該列表中的PullRequest物件是資料未到達但是也未超時的請求,然後繼續遍歷下一個PullRequest物件直到PullRequest集合遍歷完為止;
2.4)將臨時的PullRequest列表重新放入PullRequestHoldService.pullRequestTable變數中,等待下一次的遍歷。
14 客戶端順序消費時鎖住MessageQueue佇列的請求(LOCK_BATCH_MQ)
在Broker端收到LOCK_BATCH_MQ請求碼之後,間接地呼叫了RebalanceLockManager.tryLockBatch(String group, Set<MessageQueue> mqs, String clientId)方法,該方法批量鎖MessageQueue佇列,返回鎖定成功的MessageQueue佇列集合,該方法的入參group為ConsumerGroup、mqs為該brokerName對應的MessageQueue集合(Consumer端傳來的),clientId為Consumer端的ClientId,大致邏輯如下:
在Broker端有資料結構RebalanceLockManager.mqLockTable:ConcurrentHashMap<String/* group */, ConcurrentHashMap<MessageQueue, LockEntry>>,表示一個consumerGroup下面的所有MessageQueue的鎖的情況,鎖是由LockEntry類標記的,在LockEntry類中clientId和lastUpdateTimestamp兩個變數,表示某個客戶端(clientId)在某時間(lastUpdateTimestamp)對MessageQueue進行鎖住,鎖的超時時間為60秒;
1、初始化臨時變數lockedMqs:Set<MessageQueue>和notLockedMqs: Set<MessageQueue>,分別儲存鎖住的MessageQueue集合和未鎖住的MessageQueue集合;
2、遍歷請求引數MessageQueue集合,以請求引數group、每個MessageQueue物件、clientId為引數檢查該MessageQueue物件是否被鎖住,邏輯是:以group從RebalanceLockManager.mqLockTable中獲取該consumerGroup下面的所有ConcurrentHashMap<MessageQueue, LockEntry>集合:若該集合不為空再以MessageQueue物件獲取對應的LockEntry標記類,檢查該LockEntry類是否被該clientId鎖住以及鎖是否過期,若是被該clientId鎖住且沒有過期,則更新LockEntry標記類的lastUpdateTimestamp變數;若該集合為空則認為沒有鎖住。若遍歷的MessageQueue物件被該ClientId鎖住且鎖未超期了則將MessageQueue物件存入lockedMqs變數中,否則將MessageQueue物件存入notLockedMqs變數中。
3、若notLockedMqs集合不為空,即表示有未鎖住的MessageQueue佇列,繼續下面的處理;下面的邏輯處理塊是加鎖的,就是說同時只有一個執行緒執行該邏輯塊。
4、以group從RebalanceLockManager.mqLockTable中獲取該consumerGroup下面的所有ConcurrentHashMap<MessageQueue, LockEntry>集合取名groupvalue集合,若該集合為空,則新建一個ConcurrentHashMap<MessageQueue,LockEntry>集合並賦值給groupvalue集合,然後以consumerGroup為key值存入RebalanceLockManager.mqLockTable變數中;
5、遍歷notLockedMqs集合,以每個MessageQueue物件從groupvalue集合中取LockEntry標記類,若該類為null(在第4步新建的集合會出現此情況)則初始化LockEntry類,並設定該類的clientId為該Consumer的clientId並存入groupvalue集合中;若該類不為null,則再次檢查是否被該clientId鎖住,若是則更新LockEntry標記類的lastUpdateTimestamp變數,並新增到lockedMqs集合中,然後繼續遍歷notLockedMqs集合中的下一個MessageQueue物件;若仍然未被鎖則檢查鎖是否超時,若已經超時則設定該LockEntry類的clientId為該Consumer的clientId並更新lastUpdateTimestamp變數;
6、返回lockedMqs集合給Consumer端;
15 客戶端順序消費時解鎖MessageQueue佇列的請求(UNLOCK_BATCH_MQ)
在Broker端收到UNLOCK_BATCH_MQ請求碼之後,間接地呼叫了RebalanceLockManager.unlockBatch(String consumerGroup, Set<MessageQueue> mqs, String clientId)方法,該方法批量解鎖請求中的MessageQueue佇列;
1、以consumerGroup為key值從RebalanceLockManager.mqLockTable:ConcurrentHashMap<String/* group */, ConcurrentHashMap<MessageQueue, LockEntry>>變數中獲取對應的ConcurrentHashMap<MessageQueue, LockEntry>集合;
2、遍歷入參Set<MessageQueue>集合,以該集合中的每個MessageQueue物件從上一步獲得的ConcurrentHashMap<MessageQueue, LockEntry>集合中獲取LockEntry標記類,若該類不為null,則檢查該LockEntry類的ClientId是否等於請求中的ClientId(請求端的ClientId)若相等則將該MessageQueue物件的記錄從ConcurrentHashMap<MessageQueue, LockEntry>集合中移除;其他情況均不處理;
16 獲取consumerGroup名下的所有Consumer的ClientId(GET_CONSUMER_LIST_BY_GROUP)**
在Broker端收到GET_CONSUMER_LIST_BY_GROUP請求碼之後,呼叫ClientManageProcessor.getConsumerListByGroup(ChannelHandlerContext ctx, RemotingCommand request)方法獲取請求訊息中consumerGroup下面的註冊的所有Consumer的ClientId,大致邏輯如下:
1、根據請求訊息中的consumerGroup從ConsumerManager.consumerTable:ConcurrentHashMap<String/* Group */, ConsumerGroupInfo>變數中獲取對應的ConsumerGroupInfo物件;
2、若ConsumerGroupInfo物件不為null,則從ConsumerGroupInfo. channelInfoTable:ConcurrentHashMap<Channel,ClientChannelInfo>變數中獲取每個ClientChannelInfo物件,取該物件的clientId變數,構成clientId的集合;
3、將該集合返回給客戶端;
17 將Consumer消費失敗的訊息寫入延遲訊息佇列中(CONSUMER_SEND_MSG_BACK)**
在Broker端收到CONSUMER_SEND_MSG_BACK請求碼之後,呼叫SendMessageProcessor.consumerSendMsgBack(ChannelHandlerContext ctx, RemotingCommand request)方法進行回傳訊息的寫入處理,大致邏輯如下:
1、若SendMessageProcessor處理器設定了消費訊息的鉤子consumeMessageHookList:List< ConsumeMessageHook>,則呼叫該鉤子類的consumeMessageAfter方法。該鉤子是在註冊監聽器是設定的,但目前沒有設定該鉤子;
2、以請求訊息中的GroupName值呼叫SubscriptionGroupManager.findSubscriptionGroupConfig(String GroupNmae)方法獲得SubscriptionGroupConfig物件;若該物件為null,則賦值響應訊息的code等於SUBSCRIPTION_GROUP_NOT_EXIST,並返回給客戶端;
3、檢查該Broker是否有寫的許可權,若沒有寫的許可權則該Broker不能傳送此訊息;直接返回響應訊息,訊息程式碼為NO_PERMISSION;
4、若SubscriptionGroupConfig.retryQueueNums(重試佇列)為0;則直接丟棄該訊息,返回響應訊息,訊息程式碼為SUCCESS;
5、以請求訊息中的GroupName值構建新的topic值,等於"%RETRY%+consumerGroup";
6、呼叫TopicConfigManager.createTopicInSendMessageBackMethod (String topic, int clientDefaultTopicQueueNums, int perm, int topicSysFlag)方法獲取新構建的topic值的TopicConfig物件;
7、若該物件為null,則直接返回SYSTEM_ERROR的響應訊息,若該物件沒有寫入許可權則直接返回NO_PEMISSION的響應訊息;
8、獲取訊息失敗的訊息內容。以請求訊息中的offset(在消費某訊息失敗之後,該訊息的commitlogoffset值)呼叫MessageStore.lookMessageByOffset (long commitLogOffset)方法,先從CommitLog檔案中獲取該commitLogOffset偏移量的4個位元組(即為此訊息的內容大小),然後呼叫DefaultMessageStore.lookMessageByOffset(long commitLogOffset, int size)方法獲取從指定開始位置讀取size大小的訊息內容(MessageExt物件);
9、若該訊息內容為null,則直接返回SYSTEM_ERROR的響應訊息;
10、從該訊息的properties屬性中獲取"RETRY_TOPIC"屬性值,若為null,則將此訊息的topic值存入properties欄位的"RETRY_TOPIC"屬性中;
11、若該訊息的消費次數(MessageExt.reconsumeTimes)大於最大重試次數(SubscriptionGroupConfig.retryMaxTimes) 或者請求訊息中的delayLevel值小於0,則建立topic值為"%DLQ%+consumerGroup"作為新的topic值;然後呼叫TopicConfigManager.createTopicInSendMessageBackMethod(String topic, int clientDefaultTopicQueueNums, int perm, int topicSysFlag)方法獲取新構建的topic值的TopicConfig物件;若該物件為null則直接返回SYSTEM_ERROR的響應訊息,否則以該新的topic值和TopicConfig物件繼續執行下面的邏輯;
12、若該訊息的消費次數未超過最大重試次數並且請求訊息中的delayLevel值大於等於0,若delayLevel等於0,則更新delayLevel等於重試次數加3,然後該新的delayLevel值存入此訊息的properties欄位的"DELAY"屬性中;
13、隨機的獲取queueId值;
14、從MessageExt訊息的properties屬性中獲取"ORIGIN_MESSAGE_ID"屬性值,若沒有則以MessageExt訊息的msgId來設定新Message資訊的properties屬性的ORIGIN_MESSAGE_ID屬性值,若有該屬性值則將該屬性值存入新Message資訊的properties屬性的ORIGIN_MESSAGE_ID屬性值。保證同一個訊息有多次傳送失敗能獲取到真正訊息的msgId;
14、構建新的MessageExtBrokerInner物件,其中reconsumeTimes等於MessageExt.reconsumeTimes加1;
15、呼叫DefaultMessageStore.putMessage(MessageExtBrokerInner msg)方法將訊息寫入延遲訊息佇列中;
16、若訊息寫入成功則返回SCUEESS的響應訊息;
18 處理結束事務訊息的請求(END_TRANSACTION)
在Broker端收到END_TRANSACTION請求碼之後,呼叫EndTransactionProcessor.processRequest(ChannelHandlerContext ctx, RemotingCommand request)方法將事務訊息重新寫入commitlog中並生成consumequeue和index資料,供Consumer消費,大致邏輯如下:
1、以請求訊息中的commitlogoffset值從CommitLog中獲取一個單元的事務訊息內容,即MessageExt物件;
2、若該MessageExt物件不為空,檢查該訊息的properties中的"PGROUP"屬性值是否等於請求訊息的producerGroup,若不等則直接返回系統錯誤響應訊息;
3、MessageExt物件的queueoffset值是否等於請求訊息的tranStateTableOffset、MessageExt物件的commitlogoffset值是否等於請求訊息的commitlogoffset值,若有其一不等則直接返回系統錯誤響應訊息;
4、根據獲取的MessageExt物件構建MessageExtBrokerInner物件,其中將properties中的"DELAY"屬性值清除;根據Producer端返回的事務訊息的狀態重置訊息的sysflag標記位的第3/4位元組的值;
5、若返回的事務訊息狀態為TransactionRollbackType,則將MessageExtBrokerInner物件的body置為null,表示在Consumer端不進行消費;
6、呼叫DefaultMessageStore.putMessage(MessageExtBrokerInner msg)方法將事務資訊重新寫入commitlog中;
7、將寫入結果返回給Producer端;
19 客戶端發起更新或建立Topic(UPDATE_AND_CREATE_TOPIC)
在Broker端收到UPDATE_AND_CREATE_TOPIC請求碼之後,呼叫AdminBrokerProcessor.updateAndCreateTopic(ChannelHandlerContext ctx, RemotingCommand request)方法進行topic配置的更新以及向NameServer發起註冊請求,大致邏輯如下:
1、檢查收到的topic值是否等於BrokerClusterName值,若等於則直接返回錯誤資訊;
2、根據請求訊息建立TopicConfig物件,然後以topic為key值更新TopicConfigManager.topicConfigTable: ConcurrentHashMap<String, TopicConfig>變數中的TopicConfig物件;在更新dataVersion值(即版本號),然後將TopicConfigManager.topicConfigTable的值持久化到topics.json檔案中。
3、呼叫BrokerController.registerBrokerAll方法立即向NameServer註冊Broker,即對NameServer中的topic資訊更新。
相關文章
- RocketMQ基礎概念之BrokerMQ
- RocketMQ系列:使用systemd管理nameserver和brokerMQServer
- RocketMQ中Broker的刷盤原始碼分析MQ原始碼
- RocketMQ中Broker的啟動原始碼分析(一)MQ原始碼
- RocketMQ(4.8.0)——Broker 的關機恢復機制MQ
- RocketMQ中Broker的訊息儲存原始碼分析MQ原始碼
- RocketMQ 4.2.0 broker JVM優化引數深入刨析MQJVM優化
- RocketMQ - 理論篇MQ
- RocketMQ掃盲篇MQ
- RocketMQ - 應用篇MQ
- 從RocketMQ的Broker原始碼層面驗證一下這兩個點MQ原始碼
- DataGuard broker之一:DataGuard broker簡介
- 持續輸出面試題之RocketMQ篇面試題MQ
- rocketmq常見問題及使用 新手篇MQ
- Javaer 進階必看的 RocketMQ ,就這篇了JavaMQ
- Data Guard Broker系列之二:Data Guard Broker配置實戰
- oracle dataguard broker 配置Oracle
- Broker模組劃分
- RocketMqMQ
- Oracle Data Guard Broker元件Oracle元件
- Oracle DG管理Broker配置Oracle
- RocketMQ(7)---RocketMQ順序消費MQ
- RocketMQ(5)---RocketMQ重試機制MQ
- 大寫的服,看完這篇你還不懂RocketMQ算我輸MQ
- 【RocketMq】商用RocketMq和開源RocketMq的相容問題解決方案MQ
- 【RocketMQ】RocketMQ儲存結構設計MQ
- 1 Oracle Data Guard Broker 概念Oracle
- Oracle Data Guard和Broker概述Oracle
- Oracle 19c Broker配置Oracle
- rocketmq配置MQ
- rocketMQ一MQ
- rocketmq 概念MQ
- ?【Alibaba中介軟體技術系列】「RocketMQ技術專題」Broker配置介紹及傳送流程、異常(XX Busy)問題分析MQ
- 一次 RocketMQ 程式自動退出排查經驗分享(實戰篇)MQ
- 8 Oracle Data Guard Broker 屬性Oracle
- 使用Broker實現DG切換
- ORACLE資料庫Dataguard dg brokerOracle資料庫
- 官方文件學習:data guard broker