消费逻辑图:
消费者队列根据Topic、QueueId分别存储相应的消息在CommitLog中的位置信息(offset、size、tagsCode),因此Consume Queue并没有存储实际要消费的信息,而是需要通过实际存储的CommitLog中的位置信息在找一遍真正的要消费的信息
消费顺序简图
ConsumeQueue存储消息
主要有两个服务组件:
- ReputMessageService:用来对消费队列执行写操作
- FlushConsumeQueueService:用来执行消费队列中的消息的刷盘服务,即将消费者队列中的内存消息刷盘到本地磁盘做持久化处理
ReputMessageService
1、只要该线程服务没有停掉,就会一直调用deReput方法生成消息位置信息到消费队列,并且会不断生成消息索引到索引文件(IndexFile)
public void run() {
DefaultMessageStore.log.info(this.getServiceName() + " service started");
while (!this.isStopped()) {
try {
Thread.sleep(1);
this.doReput();
} catch (Exception e) {
DefaultMessageStore.log.warn(this.getServiceName() + " service has exception. ", e);
}
}
DefaultMessageStore.log.info(this.getServiceName() + " service end");
}
2、在这个doReput中,只要CommitLog可以重放消息(通过比较reputFromOffset < DefaultMessageStore.this.commitLog.getMaxOffset()),就会一直进行循环拉取操作,除非消息能够在多个MeesageStore中存储或者reput起始位大于所规定的的固定起始位,doReput方法会直接结束。
private void doReput() {
52: for (boolean doNext = true; this.isCommitLogAvailable() && doNext; ) {
53:
54: // TODO 疑问:这个是啥
55: if (DefaultMessageStore.this.getMessageStoreConfig().isDuplicationEnable() //
56: && this.reputFromOffset >= DefaultMessageStore.this.getConfirmOffset()) {
57: break;
58: }
59:
60: // 获取从reputFromOffset开始的commitLog对应的MappeFile对应的MappedByteBuffer
//这里的reputFromOffset是不断变化的,就好比你读完一条消息后,你再去读下一条消息在CommitLogd中的位置起始点会发生变化;如果这个点到达了了这个CommitLog的最大值,就会找下一个mappedFile再读,再移动这个值
61: SelectMappedBufferResult result = DefaultMessageStore.this.commitLog.getData(reputFromOffset);
62: if (result != null) {
63: try {
64: this.reputFromOffset = result.getStartOffset();
65:
66: // 遍历MappedByteBuffer
67: for (int readSize = 0; readSize < result.getSize() && doNext; ) {
68: // 生成重放消息重放调度请求
69: DispatchRequest dispatchRequest = DefaultMessageStore.this.commitLog.checkMessageAndReturnSize(result.getByteBuffer(), false, false);
70: int size = dispatchRequest.getMsgSize(); // 消息长度
71: // 根据请求的结果处理
72: if (dispatchRequest.isSuccess()) { // 构建重放消息调度请求成功了(将buffer的消息全部读取到这个请求里面去了)
73: if (size > 0) { // 读取到了Message,那么会立即执行将消息放到消费队列的调度任务
74: DefaultMessageStore.this.doDispatch(dispatchRequest);//这个调度任务里面会将非事务消息 或 事务提交消息 建立 消息位置信息 到 ConsumeQueue【putMessagePositionInfo(String topic, int queueId, long offset, int size, long tagsCode, long storeTimestamp,
long logicOffset)实现的】,并且会建立索引信息到 、IndexFile
75: // 并且如果该Broker是主节点且一直保持长连接的话,会立即通知对应的Topic、queue时间监听器将会有新消息到达,让其做好准备
76: if (BrokerRole.SLAVE != DefaultMessageStore.this.getMessageStoreConfig().getBrokerRole()
77: && DefaultMessageStore.this.brokerConfig.isLongPollingEnable()) {
78: DefaultMessageStore.this.messageArrivingListener.arriving(dispatchRequest.getTopic(),
79: dispatchRequest.getQueueId(), dispatchRequest.getConsumeQueueOffset() + 1,
80: dispatchRequest.getTagsCode());
81: }
82: // FIXED BUG By shijia
83: this.reputFromOffset += size;
84: readSize += size;
85: // 统计
86: if (DefaultMessageStore.this.getMessageStoreConfig().getBrokerRole() == BrokerRole.SLAVE) {
87: DefaultMessageStore.this.storeStatsService
88: .getSinglePutMessageTopicTimesTotal(dispatchRequest.getTopic()).incrementAndGet();
89: DefaultMessageStore.this.storeStatsService
90: .getSinglePutMessageTopicSizeTotal(dispatchRequest.getTopic())
91: .addAndGet(dispatchRequest.getMsgSize());
92: }
93: } else if (size == 0