拉取请求
一个队列一个PullRequest
PullRequest
consumerGroup 消费组
MessageQueue Broker的消息队列,MessageQueue有三个属性:topic,brokerName和queueId
ProcessQUeue 拉取到的消息存储到处理队列中,然后提交到消费者线程池消费
lock 读写锁
TreeMap
dropped: 当消息消费时,服务端返回偏移量非法时,设置为true,暂时停止该ProcessQueue的消费,等待下一次消息队列的重新负载。
nextOffset 待拉取的MessageQueue偏移量
ProcessQueue 是MessageQueue在消费端的重现、快照。PullMessageService从Broker默认每次拉取32条消息,按照队列偏移量
顺序放入ProcessQueue中。 消息消费成功后,会将消息从ProcessQueue中移除。 串行拉取消息,拉取完成后放入ProcessQueue中,
然后将ProcessQueue和MessageQueue封装成一个ConsumeRequest任务,丢到线程池中去执行。
ConsumeRequest三个属性,ProcessQueue, 这次拉取收到的消息msgList, MessageQueue.
ConsumeRequest的run方法中执行消费方法consumeMessage。
根据不同的消费结果执行不同的操作,CONSUME_SUCCESS时就成功,如果是RECONSUME_LATER的话就把消息再发送给Broker,如果发送Broker失败就
继续封装成ConsumeRequest丢到线程池稍后在本地消费。如果消费函数抛出异常,则依然是RECONSUME_LATER。
if (null == status) {
log.warn("consumeMessage return null, Group: {} Msgs: {} MQ: {}",
ConsumeMessageConcurrentlyService.this.consumerGroup,
msgs,
messageQueue);
status = ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
SubscriptionData
subString是干嘛的
pullSysFlag是干啥的
classFilter是啥
棘手的问题: 并发就卡死
拉取消息流程:
当commitlog中待消费的偏移量(总内存)
long diff = maxOffsetPy - maxPhyOffsetPulling;
访问消息在内存的比例,百分之40,物理内存的百分之40。
long memory = (long) (StoreUtil.TOTAL_PHYSICAL_MEMORY_SIZE
* (this.messageStoreConfig.getAccessMessageInMemoryMaxRatio() / 100.0));
// TODO: 2019/10/14 建议从slave拉取
getResult.setSuggestPullingFromSlave(diff > memory);
消息服务器查找并返回消息
消息服务器根据偏移量查找消息返回,同时返回下次拉取的偏移量。
服务端返回GetMessageResult
会返回
SUCCESS
PULL_RETRY_IMMEDIATELY 立即重试 这种情况是消息存储在下个commitLog文件中
PULL_OFFSET_MOVED
PULL_NOT_FOUND
处理消息
PullResult
PullStatus 拉取状态,4种。有发现消息;没消息;没有匹配的消息,消息都被过滤了; 偏移量异常
nextBeginOffset 下一次拉取的偏移量
minOffset 消息队列最小偏移量
maxOffset 消息队列最大偏移量
List msgFoundList 具体拉取到的消息列表
拉取成功的回调:
pullResult = DefaultMQPushConsumerImpl.this.pullAPIWrapper.processPullResult(pullRequest.getMessageQueue(), pullResult,
subscriptionData);
PullAPIWrapper中根据tag进行了过滤(如果开启了tag过滤模式,这里猜测,tag是客户端过滤,classFilter是服务端过滤的);执行了钩子函数,
客户端根据响应的返回码映射成拉取状态PUllStatus
switch (response.getCode()) {
case ResponseCode.SUCCESS:
pullStatus = PullStatus.FOUND;
break;
case ResponseCode.PULL_NOT_FOUND:
pullStatus = PullStatus.NO_NEW_MSG;
break;
case ResponseCode.PULL_RETRY_IMMEDIATELY:
pullStatus = PullStatus.NO_MATCHED_MSG;
break;
case ResponseCode.PULL_OFFSET_MOVED:
pullStatus = PullStatus.OFFSET_ILLEGAL;
break;
}
然后根据PullStatus执行不同的处理逻辑。
FOUND:
FOUND时msgList也可能为空,根据tag过滤后依旧为空。
不为空时,将消息存储ProcessQueue,然后发起一个异步消费请求,触发给ConsumeMessageServer消费。
ConsumeRequest consumeRequest = new ConsumeRequest(msgs, processQueue, messageQueue);
try {
this.consumeExecutor.submit(consumeRequest);
} catch (RejectedExecutionException e) {
this.submitConsumeRequestLater(consumeRequest);
}
然后执行下一次拉取,可以根据PullInterval配置看看是否要等待一会
PullAPIWrapper
负载均衡:rebalance
PullRequest是什么时候创建并加入pullRequestQueue中的呢
集群内多个消费者如何负载主题下多个消费队列的,如果有新的消费者加入,消息队列如何重新分布。
rebalance流程:
每个MQClientInstance持有一个RebalanceService的实现,随着MQClientInstance启动而启动,RebalanceService线程每隔20秒执行一次,遍历所有消费者,
执行每个消费者的doRebalance()方法。
1. 根据topic从broker中查询所有消费者的信息。 MQClientInstance会向所有Broker发送心跳包,心跳包中包含 MQClientInstance注册的所有的消费者信息。
Set mqSet = this.topicSubscribeInfoTable.get(topic);
// 找到所有的消费者ID
List cidAll = this.mQClientFactory.findConsumerIdList(topic, consumerGroup);
// 排序,同一个消费组内看到的视图保持一致
Collections.sort(mqAll);
Collections.sort(cidAll);
// AllocateMessageQueueStrategy有5种分配算法
AllocateMessageQueueStrategy strategy = this.allocateMessageQueueStrategy;
allocateResult = strategy.allocate(
this.consumerGroup,
this.mQClientFactory.getClientId(),
mqAll,
cidAll);
根据新分配到的消费队列来决定如何负载。
当一个消费者接受到了原来没有的消费队列,则清空原来该消费队列可能在本地已有的消费进度缓存,然后根据PullFromWhere从Broker拉取消费进度,配置创建PullRequest。
当一个消费者的消息队列失效了,则setDropped丢弃队列。