一、背景
之前遇到一個問題,就是一個新的訂閱組,指定集羣方式消費,使用DefaultMQPushConsumer
,第一次啓動指定的consumeFromWhere是CONSUME_FROM_LAST_OFFSET
,但是卻消費了好久之前的消息!!!
CONSUME_FROM_LAST_OFFSET
官方的解釋是一個新的訂閱組第一次啓動從隊列的最後位置開始消費,後續再啓動接着上次消費的進度開始消費
,但某些情況下卻並不是這樣!
二、問題追蹤
下面從源碼的角度分析一下這個問題的原因
DefaultMQPushConsumer
集羣方式消費的話,消費進度是存在broker上的,但是第一次,應該從哪消費呢?參考RebalancePushImpl
的方法:computePullFromWhere(MessageQueue mq)
。即從哪消費的偏移量是查詢出來的,但是有一點肯定的是,這個方法肯定返回了0,纔會從頭消費。- 那麼
computePullFromWhere(MessageQueue mq)
爲啥返回了0呢?
接着追蹤,偏移量會從RemoteBrokerOffsetStore
類的public long readOffset(final MessageQueue mq, final ReadOffsetType type)
方法返回,此方法會遠程到broker上去查詢,那麼看來是broker返回的偏移量爲0了! - 接着跟蹤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開始消費。