RocketMQ——Broker篇

meilong_whpu發表於2017-08-09

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://&quot; + 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資訊更新。

相關文章