ROCKETMQ(消息消费,待完善)

  1. 问题:
    常用配置参数
    消费流程
    消费短如何负载均衡,消息拉取选择哪个ProcessQueue呢
    拉取消息流控
    顺序消息实现
    超时消息
    如何高可用 每20秒rebalance一次
    如果拉取后,消费失败了会怎么样,应该怎么操作。 重新消费,那重新消费多少次呢。通过maxConsumeRetryTimes可以控制
    消费进度管理,偏移量校正
    消息过滤。

拉取请求

一个队列一个PullRequest
PullRequest
consumerGroup 消费组
MessageQueue Broker的消息队列,MessageQueue有三个属性:topic,brokerName和queueId
ProcessQUeue 拉取到的消息存储到处理队列中,然后提交到消费者线程池消费
lock 读写锁
TreeMap msgTreeMap消息存储器,KEY是消息在消息队列中的偏移量,VALUE是消息实体。
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是啥

棘手的问题: 并发就卡死

  1. 创建拉取请求。
  2. 选择消费者
  3. 消费者拉取消息

拉取消息流程:

  1. 拉取请求封装
    进行消息流控,
    如果当前ProcessQueue队列长度超过1000(可配置)时,或者队列中消息体大于100M时,放弃本次拉取请求,将拉取请求放回队列,50ms后再尝试
    对于非顺序消费:如果ProcessQueue的msgTreeMap的最大KEY - msgTreeMap的最小KEY 大于2000时,进行流控。 ProcessQueue中在
    消息队列中的最大最小偏移量之差不能差距太大。 这里请思考一下为什么,为什么太大不行,为什么只针对非顺序消费。
    根据主题拉取主题订阅信息SubscriptionData
    构造拉取回调函数,用于处理拉取结果
    构建拉取标记pullSysFlag是干啥的
    调用接口与服务端交互,拉取消息
    根据brokerName,brokerId从MQClientInstance中获取Broker地址。 在整个Broker部署结构中,相同brokerName的构成主从结构,brokerId不同。
    每次拉取消息后,FindBrokerResult会给出一个建议,下次拉取是从主节点还是从节点拉取。
    FindBrokerResult
    brokerAddr broker地址
    slave 是否从节点
    问题:那什么时候从master拉取,什么时候从master拉取呢(DefaultMessageStore中的setSuggestPullingFromSlave方法,好像当消费很慢的时候,当commitlog中待消费的消息所占内存大于)。从主节点和从节点拉取有什么区别呢,来回切换会不会有什么问题? 最坏就是Master有消息,slave没有。或者拉取请求的偏移量大于slave中存储的偏移量,也就是slave中的所有消息都消费了,会越界,会报错OFFSET_OVERFLOW_BADLY。

当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);

  1. 消息服务器查找并返回消息
    消息服务器根据偏移量查找消息返回,同时返回下次拉取的偏移量。
    服务端返回GetMessageResult
    会返回
    SUCCESS
    PULL_RETRY_IMMEDIATELY 立即重试 这种情况是消息存储在下个commitLog文件中
    PULL_OFFSET_MOVED
    PULL_NOT_FOUND

  2. 处理消息
    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丢弃队列。

你可能感兴趣的:(中间件)