RocketMQ新的订阅组CONSUME_FROM_LAST_OFFSET为啥无效?

一、背景

之前遇到一个问题,就是一个新的订阅组,指定集群方式消费,使用DefaultMQPushConsumer,第一次启动指定的consumeFromWhere是CONSUME_FROM_LAST_OFFSET,但是却消费了好久之前的消息!!!
CONSUME_FROM_LAST_OFFSET官方的解释是一个新的订阅组第一次启动从队列的最后位置开始消费,后续再启动接着上次消费的进度开始消费,但某些情况下却并不是这样!


二、问题追踪

下面从源码的角度分析一下这个问题的原因

  1. DefaultMQPushConsumer集群方式消费的话,消费进度是存在broker上的,但是第一次,应该从哪消费呢?参考RebalancePushImpl的方法:computePullFromWhere(MessageQueue mq)。即从哪消费的偏移量是查询出来的,但是有一点肯定的是,这个方法肯定返回了0,才会从头消费。
  2. 那么computePullFromWhere(MessageQueue mq)为啥返回了0呢?
    接着追踪,偏移量会从RemoteBrokerOffsetStore类的public long readOffset(final MessageQueue mq, final ReadOffsetType type)方法返回,此方法会远程到broker上去查询,那么看来是broker返回的偏移量为0了!
  3. 接着跟踪broker的ConsumerManageProcessor.queryConsumerOffset方法:
// 1. 这里从broker内存中查询consumerGroup消费的topic的队列对应的偏移量,这是第一次订阅启动,并没有这个关系,将返回-1
long offset =
    this.brokerController.getConsumerOffsetManager().queryOffset(
        requestHeader.getConsumerGroup(), requestHeader.getTopic(), requestHeader.getQueueId());

if (offset >= 0) {
    responseHeader.setOffset(offset);
    response.setCode(ResponseCode.SUCCESS);
    response.setRemark(null);
} else {
    // 2. 获取topic对应的队列的最小偏移量。新的队列或消息数据未清理过的话,返回值为0(注意:如果新扩容队列也是新队列)
    long minOffset = this.brokerController.getMessageStore().getMinOffsetInQueue(requestHeader.getTopic(),
            requestHeader.getQueueId());
    if (minOffset <= 0
        // 3. 检查此队列的最老的数据是否还在内存,在内存则返回0
        && !this.brokerController.getMessageStore().checkInDiskByConsumeOffset(
        requestHeader.getTopic(), requestHeader.getQueueId(), 0)) {
        responseHeader.setOffset(0L);
        response.setCode(ResponseCode.SUCCESS);
        response.setRemark(null);
    } else {
        response.setCode(ResponseCode.QUERY_NOT_FOUND);
        response.setRemark("Not found, V3_0_6_SNAPSHOT maybe this group consumer boot first");
    }
}

注意: 上面代码中的注释1,2,3。也就是说,如果broker端的队列中的数据不多,还在内存中,那么偏移量将返回为0,即从头消费。那么什么叫数据不多,还在内存中?请自行参考DefaultMessageStore.checkInDiskByConsumeOffset(String topic, int queueId, long consumeOffset)方法。


三、总结

如果消息数据从未清理过,或新添加了broker,或topic新扩容了队列,那么这几种情况可能会存在RocketMQ认为topic的队列新上线不久,数据不算太多的情形。另外,参考RocketMQ3.2.6源码(4.x已经把注释去掉了,好可惜)的注释可以理解其深意:

订阅组不存在情况下,如果这个队列的消息最小Offset是0,则表示这个Topic上线时间不长,
服务器堆积的数据也不多,那么这个订阅组就从0开始消费。
尤其对于Topic队列数动态扩容时,必须要从0开始消费。

你可能感兴趣的:(java)