消息消费进度的更新
转载:https://blog.csdn.net/yankunhaha/article/details/100061337
如果消费进度保存在本地的话,那我们的docker环境怎么做到不重复消费
翻了下LocalFileOffsetStore其实也是先刷到内存的 如上。
那我看看下每5秒进行的持久话:
看下LocalFileOffsetStore的persistAll:
storePath的初始化:
格式是dir/clientId/group/offsets.json。dir如果没配置的话默认是home/.rocketmq_offsets/
这样的话,看下我本地的情况吧:
不错还真有
那DefaultMQPushConsumerImpl里的offsetStore是什么时候指定的呢?
看下构造器:
并没有
看一下org.apache.rocketmq.client.impl.consumer.DefaultMQPushConsumerImpl#start方法:
可以看到如果是广播模式的话是用LocalFileOffsetStore的。那我们考虑的问题应该是客观存在的。
load是从文件读到内存的过程
我们观察一个广播消费的topic:
发现它的consumerClient是没有信息的,offset也怪怪得
再看下消息拉取过程:
pullMessageService线程:
广播模式会去读之前load到内存的offset。(对不起,这里我把集群模式看成了广播模式,所以下面的分析一直是对集群模式的分析- -)
还有一个比较关键的pullRequest.nextOffset,我们看看是怎么更新的,在callBack里:、
更新为拉取结果的nextBeginOffset了。
看看Broker拿这两个字段做了什么,查找消息的部分主要是下面这部分逻辑:
然后commitOffset是这么用的
注意到这里有个hasCommitOffsetFlag:
一共有四种状态是通过将16不断左移得到的。
看下客户端怎么组装的:
从实现来看 sysFlag应该是4中状态的叠加态,当是广播模式时commitOffset是打开的状态。好先记住这一点,我们接着看客户端构造requestHeader的过程:
queueOffset实际上就是pullRequest的nextOffset。
所以这个commitOffset到底是干什么用的,最后被刷到了一个topic@group为key的map里,但是如果是广播模式的话,每个group的每个成员都可以刷新这个值有什么意义呢(当然没有意义撒,这是集群模式才会有的逻辑,是你自己看错了呀!但是有个意外收获可以看到,不仅仅是每5秒的定时任务会将broker刷新到broker,集群模式下每次拉取消息也会更新master的offset且broker的更新实际上是以客户端内存的offset为准的。)好吧 广播模式offset的问题还是没弄清楚
另外可以看下
看下rebalance:
主要是想看下一个新的pullRequest里面的nextOffset是怎么初始化的:
com.alibaba.rocketmq.client.impl.consumer.RebalancePushImpl#computePullFromWhere:
public long computePullFromWhere(MessageQueue mq) { long result = -1; final ConsumeFromWhere consumeFromWhere = this.defaultMQPushConsumerImpl.getDefaultMQPushConsumer().getConsumeFromWhere(); final OffsetStore offsetStore = this.defaultMQPushConsumerImpl.getOffsetStore(); switch (consumeFromWhere) { case CONSUME_FROM_LAST_OFFSET_AND_FROM_MIN_WHEN_BOOT_FIRST: case CONSUME_FROM_MIN_OFFSET: case CONSUME_FROM_MAX_OFFSET: case CONSUME_FROM_LAST_OFFSET: { long lastOffset = offsetStore.readOffset(mq, ReadOffsetType.READ_FROM_STORE); if (lastOffset >= 0) { result = lastOffset; } // First start,no offset else if (-1 == lastOffset) { if (mq.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { result = 0L; } else { try { result = this.mQClientFactory.getMQAdminImpl().maxOffset(mq); } catch (MQClientException e) { result = -1; } } } else { result = -1; } break; } case CONSUME_FROM_FIRST_OFFSET: { long lastOffset = offsetStore.readOffset(mq, ReadOffsetType.READ_FROM_STORE); if (lastOffset >= 0) { result = lastOffset; } else if (-1 == lastOffset) { result = 0L; } else { result = -1; } break; } case CONSUME_FROM_TIMESTAMP: { long lastOffset = offsetStore.readOffset(mq, ReadOffsetType.READ_FROM_STORE); if (lastOffset >= 0) { result = lastOffset; } else if (-1 == lastOffset) { if (mq.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { try { result = this.mQClientFactory.getMQAdminImpl().maxOffset(mq); } catch (MQClientException e) { result = -1; } } else { try { long timestamp = UtilAll.parseDate(this.defaultMQPushConsumerImpl.getDefaultMQPushConsumer().getConsumeTimestamp(), UtilAll.YYYYMMDDHHMMSS).getTime(); result = this.mQClientFactory.getMQAdminImpl().searchOffset(mq, timestamp); } catch (MQClientException e) { result = -1; } } } else { result = -1; } break; } default: break; } return result; }
可以看到有三种模式,最大,first和时间戳。具体的还是调用offsetStore。
但是注意这里:
当指定了从last_offset消费的时候,如果readOffset没有会取broker上队列的最大偏移