Rocketmq offset進度管理
下文以DefaultMQPushConsumerImpl叢集模式消費訊息為例。
概述
訊息消費完成後,需要將消費進度儲存起來,即前面提到的offset。廣播模式下,同消費組的消費者相互獨立,消費進度要單獨儲存;叢集模式下,同一條訊息只會被同一個消費組消費一次,消費進度會參與到負載均衡中,故消費進度是需要共享的。
消費者端
提交offset入口
入口在org.apache.rocketmq.client.impl.consumer.ConsumeMessageConcurrentlyService#processConsumeResult中的最後一段邏輯:
public void processConsumeResult(
final ConsumeConcurrentlyStatus status,
final ConsumeConcurrentlyContext context,
final ConsumeRequest consumeRequest
) {
------------------省略-----------------------
long offset = consumeRequest.getProcessQueue().removeMessage(consumeRequest.getMsgs());
if (offset >= 0 && !consumeRequest.getProcessQueue().isDropped()) {
this.defaultMQPushConsumerImpl.getOffsetStore().updateOffset(consumeRequest.getMessageQueue(), offset, true);
}
}
訊息消費完成(不論成功或失敗)後,將訊息從ProcessQueue中移除,同時返回ProcessQueue中最小的offset,使用這個offset值更新消費進度,removeMessage返回的offset有兩種情況,一是已經沒有訊息了,返回
ProcessQueue最大offset+1,二是還有訊息,則返回未消費訊息的最小offset。舉個例子,ProcessQueue中有offset為101-110的10條訊息,如果全部消費完了,返回的offset為111;如果101未消費完成,102-110消費完成,則返回的offset為101,這種情況下如果消費者異常退出,會出現重複消費的風險,所以要求消費邏輯冪等。
updateOffset邏輯
看RemoteBrokerOffsetStore的updateOffset()邏輯,將offset更新到記憶體中,這裡RemoteBrokerOffsetStore使用ConcurrentHashMap儲存MessageQueue的消費進度:
@Override
public void updateOffset(MessageQueue mq, long offset, boolean increaseOnly) {
if (mq != null) {
AtomicLong offsetOld = this.offsetTable.get(mq);
if (null == offsetOld) {
offsetOld = this.offsetTable.putIfAbsent(mq, new AtomicLong(offset));
}
if (null != offsetOld) {
if (increaseOnly) {
MixAll.compareAndIncreaseOnly(offsetOld, offset);
} else {
offsetOld.set(offset);
}
}
}
}
可以看到,這裡將offset更新到記憶體中就返回了,並沒有向broker端提交,具體提交邏輯有兩種方式:
方式1:拉取訊息時順帶提交
來看DefaultMQPushConsumerImpl類的pullMessage方法
public void pullMessage(final PullRequest pullRequest) {
final ProcessQueue processQueue = pullRequest.getProcessQueue();
------------------此處省略500字----------------------
boolean commitOffsetEnable = false;
long commitOffsetValue = 0L;
if (MessageModel.CLUSTERING == this.defaultMQPushConsumer.getMessageModel()) {
commitOffsetValue = this.offsetStore.readOffset(pullRequest.getMessageQueue(), ReadOffsetType.READ_FROM_MEMORY);
if (commitOffsetValue > 0) {
commitOffsetEnable = true;
}
}
int sysFlag = PullSysFlag.buildSysFlag(
commitOffsetEnable, // commitOffset
true, // suspend
subExpression != null, // subscription
classFilter // class filter
);
try {
this.pullAPIWrapper.pullKernelImpl(
pullRequest.getMessageQueue(),
subExpression,
subscriptionData.getExpressionType(),
subscriptionData.getSubVersion(),
pullRequest.getNextOffset(),
this.defaultMQPushConsumer.getPullBatchSize(),
sysFlag,
commitOffsetValue,
BROKER_SUSPEND_MAX_TIME_MILLIS,
CONSUMER_TIMEOUT_MILLIS_WHEN_SUSPEND,
CommunicationMode.ASYNC,
pullCallback
);
} catch (Exception e) {
log.error("pullKernelImpl exception", e);
this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException);
}
}
程式碼中,我們可以看到 通過this.offsetStore.readOffset(pullRequest.getMessageQueue(), ReadOffsetType.READ_FROM_MEMORY);從記憶體中讀到待提交的offset值,並將commitOffsetEnable設定為true. 核心方法pullKernelImpl將引數sysFlag和commitOffsetValue傳遞到broker端。
broker端處理拉取訊息的Processor是PullMessageProcessor。我們重點觀察一下processRequest方法中設定offset的程式碼。
boolean storeOffsetEnable = brokerAllowSuspend;
storeOffsetEnable = storeOffsetEnable && hasCommitOffsetFlag;
storeOffsetEnable = storeOffsetEnable
&& this.brokerController.getMessageStoreConfig().getBrokerRole() != BrokerRole.SLAVE;
if (storeOffsetEnable) {
this.brokerController.getConsumerOffsetManager().commitOffset(RemotingHelper.parseChannelRemoteAddr(channel),
requestHeader.getConsumerGroup(), requestHeader.getTopic(), requestHeader.getQueueId(), requestHeader.getCommitOffset());
}
方式2:拉取訊息時順帶提交
消費端每隔5s,通過persistAll向broker端提交offset值。
上翻邏輯,在MQClientInstance啟動的時候會註冊定時任務,每5s執行一次persistAllConsumerOffset(),最終呼叫到persistAll()。
private void persistAllConsumerOffset() {
Iterator<Entry<String, MQConsumerInner>> it = this.consumerTable.entrySet().iterator();
while (it.hasNext()) {
Entry<String, MQConsumerInner> entry = it.next();
MQConsumerInner impl = entry.getValue();
impl.persistConsumerOffset();
}
}
//DefaultMQPushConsumerImpl
@Override
public void persistConsumerOffset() {
try {
this.makeSureStateOK();
Set<MessageQueue> mqs = new HashSet<MessageQueue>();
Set<MessageQueue> allocateMq = this.rebalanceImpl.getProcessQueueTable().keySet();
mqs.addAll(allocateMq);
this.offsetStore.persistAll(mqs);
} catch (Exception e) {
log.error("group: " + this.defaultMQPushConsumer.getConsumerGroup() + " persistConsumerOffset exception", e);
}
}
//RemoteBrokerOffsetStore
@Override
public void persistAll(Set<MessageQueue> mqs) {
if (null == mqs || mqs.isEmpty())
return;
final HashSet<MessageQueue> unusedMQ = new HashSet<MessageQueue>();
for (Map.Entry<MessageQueue, AtomicLong> entry : this.offsetTable.entrySet()) {
MessageQueue mq = entry.getKey();
AtomicLong offset = entry.getValue();
if (offset != null) {
if (mqs.contains(mq)) {
try {
this.updateConsumeOffsetToBroker(mq, offset.get());
log.info("[persistAll] Group: {} ClientId: {} updateConsumeOffsetToBroker {} {}",
this.groupName,
this.mQClientFactory.getClientId(),
mq,
offset.get());
} catch (Exception e) {
log.error("updateConsumeOffsetToBroker exception, " + mq.toString(), e);
}
} else {
unusedMQ.add(mq);
}
}
}
if (!unusedMQ.isEmpty()) {
for (MessageQueue mq : unusedMQ) {
this.offsetTable.remove(mq);
log.info("remove unused mq, {}, {}", mq, this.groupName);
}
}
}
broker端
broker端的具體邏輯在ConsumerManageProcessor,處理(1)查詢消費者列表(2)更新offset(3)查詢offset 三種請求。具體處理邏輯在ConsumerOffsetManager
ConsumerOffsetManager
使用
ConcurrentMap<String/* topic@group */, ConcurrentMap<Integer/*queueid*/, Long>>
來儲存所有offset資訊,大map的key為topic@group,小map的key為queueid。對記憶體的讀寫操作這裡不再詳細分析。
分析到此處,offset全是儲存在記憶體中,而這個offset必然是要持久化的,持久化的邏輯在哪裡?
ConsumerOffsetManager繼承自ConfigManager,ConfigManager的load方法是從檔案中載入資料到記憶體,persist方法是從記憶體持久化資料到檔案,查詢具體呼叫,在BrokerController中有如下邏輯:
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
BrokerController.this.consumerOffsetManager.persist();
} catch (Throwable e) {
log.error("schedule persist consumerOffset error.", e);
}
}
}, 1000 * 10, this.brokerConfig.getFlushConsumerOffsetInterval(), TimeUnit.MILLISECONDS);
即預設5s將資料持久化到檔案中
參考文件:
1. https://blog.csdn.net/yankunhaha/article/details/100061337
2. https://blog.csdn.net/GAMEloft9/article/details/103999826
相關文章
- 【RocketMQ】主從模式下的消費進度管理MQ模式
- rocketMq 訊息偏移量 OffsetMQ
- 進度管理
- 專案進度管理
- RocketMQ進階技巧MQ
- rocketmq 管理控制檯MQ
- 如何有效管理專案進度
- 用Github管理debug進度Github
- 精華!一張圖進階 RocketMQMQ
- 一張圖進階 RocketMQ - NameServerMQServer
- jQuery offset()jQuery
- 專案管理過程之進度控制 (轉)專案管理
- 【zz】IT專案如何做好進度管理
- 專案管理過程之進度控制(轉)專案管理
- 專案管理中的進度與成本控制專案管理
- 專案進度管理的三個要點
- 【新特性速遞】進度條,進度條,進度條
- 理解Kafka offsetKafka
- Kafka Offset StorageKafka
- 11-專案進度管理(3/10 十大管理)
- MS-SQL 錯誤: The offset specified in a OFFSET clause may not be negativeSQL
- RocketMQ 5.0 API 與 SDK 的演進MQAPI
- 今年專案進度管理軟體哪個好
- 瞭解這個專案進度跟蹤管理工具,輕鬆掌握專案進度
- RocketMQ系列:使用systemd管理nameserver和brokerMQServer
- [MASM拾遺]OffsetASM
- 專案進度管理的基本步驟是什麼
- 掌握進度管理基本指南,保證專案不延期
- 如何有效管理專案進度 都有哪些解決方法
- 如何使用網路圖構造進度管理模型(轉)模型
- 一張圖進階 RocketMQ - 通訊機制MQ
- 一張圖進階 RocketMQ - 訊息傳送MQ
- 一張圖進階 RocketMQ - 整體架構MQ架構
- kafka系列之(3)——Coordinator與offset管理和Consumer RebalanceKafka
- 無法根據TZ_OFFSET的值進行資料訪問
- 從事專案管理的朋友們,是如何有效管理專案進度的?專案管理
- offset與style區別
- PHP字串offset取值特性PHP字串