rocketmq/patch022-backport-Support-KV-Storage-for-ConsumeQueue.patch

29481 lines
1.5 MiB
Diff
Raw Normal View History

From 0a046b85611bdc93c529650dc951f4cabbb61db9 Mon Sep 17 00:00:00 2001
From: zhiliatom <zhiliatox@163.com>
Date: Fri, 8 Dec 2023 09:25:41 +0800
Subject: [PATCH] [ISSUE #7064] [RIP-66-2] Support KV(RocksDB) Storage for
ConsumeQueue
---
.../rocketmq/broker/BrokerController.java | 74 +-
.../broker/controller/ReplicasManager.java | 4 +-
.../offset/RocksDBConsumerOffsetManager.java | 8 +-
.../broker/processor/AckMessageProcessor.java | 2 +-
.../processor/AdminBrokerProcessor.java | 28 +-
.../ChangeInvisibleTimeProcessor.java | 4 +-
.../processor/PopBufferMergeService.java | 4 +-
.../broker/processor/PopMessageProcessor.java | 2 +-
.../broker/processor/PopReviveService.java | 6 +-
.../RocksDBSubscriptionGroupManager.java | 10 +-
.../topic/RocksDBTopicConfigManager.java | 8 +-
.../processor/PopReviveServiceTest.java | 4 +-
common/pom.xml | 2 +-
.../org/apache/rocketmq/common/MixAll.java | 1 +
.../rocketmq/common/attribute/CQType.java | 3 +-
.../common/config/AbstractRocksDBStorage.java | 112 +-
.../common/config/ConfigRocksDBStorage.java | 2 +-
.../common/message/MessageExtBrokerInner.java | 13 +
.../rocketmq/common/topic/TopicValidator.java | 2 +
.../rocketmq/common/utils/DataConverter.java | 2 +-
.../rocketmq/example/quickstart/Producer.java | 2 +-
metrics | 241 ++
patch009-modify-CRLF-to-LF.patch | 127 +
patch010-backport-add-some-fixes | 1286 ++++++
patch011-backport-optimize-config | 1390 +++++++
...optimize-opentelemetry-metric-config.patch | 2081 ++++++++++
...-backport-enhance-rockdbconfigtojson.patch | 2920 +++++++++++++
patch013-backport-enhance-admin-output.patch | 892 ++++
...ueue-Selection-Strategy-Optimization.patch | 2023 +++++++++
patch015-backport-fix-some-bugs.patch | 1894 +++++++++
...rt-Optimize-fault-tolerant-mechanism.patch | 520 +++
...port-Convergent-thread-pool-creation.patch | 2243 ++++++++++
...ckport-enhancement-of-tiered-storage.patch | 601 +++
patch019-backport-some-bugfix.patch | 1499 +++++++
patch020-backport-add-goaway-mechanism.patch | 3696 +++++++++++++++++
patch021-backport-some-enhancements.patch | 344 ++
pom.xml | 4 +-
store/pom.xml | 4 +
.../org/apache/rocketmq/store/CommitLog.java | 94 +-
.../rocketmq/store/CommitLogDispatcher.java | 5 +-
.../apache/rocketmq/store/ConsumeQueue.java | 93 +-
.../rocketmq/store/DefaultMessageStore.java | 279 +-
.../apache/rocketmq/store/MessageStore.java | 54 +-
.../rocketmq/store/RocksDBMessageStore.java | 169 +
.../apache/rocketmq/store/RunningFlags.java | 22 +-
.../store/config/MessageStoreConfig.java | 33 +-
.../store/dledger/DLedgerCommitLog.java | 53 +-
.../apache/rocketmq/store/ha/HAService.java | 3 +-
.../ha/autoswitch/AutoSwitchHAClient.java | 2 +-
.../ha/autoswitch/AutoSwitchHAService.java | 7 +-
.../plugin/AbstractPluginMessageStore.java | 42 +-
.../queue/AbstractConsumeQueueStore.java | 105 +
.../store/queue/BatchConsumeQueue.java | 20 +
.../store/queue/ConsumeQueueInterface.java | 27 +-
.../store/queue/ConsumeQueueStore.java | 293 +-
.../queue/ConsumeQueueStoreInterface.java | 289 ++
.../rocketmq/store/queue/MultiDispatch.java | 76 +
.../store/queue/QueueOffsetOperator.java | 8 +
.../store/queue/RocksDBConsumeQueue.java | 437 ++
.../queue/RocksDBConsumeQueueOffsetTable.java | 641 +++
.../store/queue/RocksDBConsumeQueueStore.java | 441 ++
.../store/queue/RocksDBConsumeQueueTable.java | 312 ++
.../ConsumeQueueCompactionFilterFactory.java | 47 +
.../rocksdb/ConsumeQueueRocksDBStorage.java | 133 +
.../store/rocksdb/RocksDBOptionsFactory.java | 161 +
.../store/timer/TimerMessageStore.java | 65 +-
.../store/DefaultMessageStoreTest.java | 5 +-
.../rocketmq/store/MultiDispatchTest.java | 8 +-
.../store/RocksDBMessageStoreTest.java | 1060 +++++
.../apache/rocketmq/store/StoreTestUtil.java | 9 +-
.../rocketmq/store/ha/HAServerTest.java | 16 +-
.../store/ha/autoswitch/AutoSwitchHATest.java | 3 +-
.../tieredstore/TieredMessageStore.java | 15 +-
.../ExportMetadataInRocksDBCommand.java | 8 +-
.../metadata/RocksDBConfigToJsonCommand.java | 118 +
75 files changed, 26543 insertions(+), 668 deletions(-)
create mode 100644 metrics
create mode 100644 patch009-modify-CRLF-to-LF.patch
create mode 100644 patch010-backport-add-some-fixes
create mode 100644 patch011-backport-optimize-config
create mode 100644 patch011-backport-optimize-opentelemetry-metric-config.patch
create mode 100644 patch012-backport-enhance-rockdbconfigtojson.patch
create mode 100644 patch013-backport-enhance-admin-output.patch
create mode 100644 patch014-backport-Queue-Selection-Strategy-Optimization.patch
create mode 100644 patch015-backport-fix-some-bugs.patch
create mode 100644 patch016-backport-Optimize-fault-tolerant-mechanism.patch
create mode 100644 patch017-backport-Convergent-thread-pool-creation.patch
create mode 100644 patch018-backport-enhancement-of-tiered-storage.patch
create mode 100644 patch019-backport-some-bugfix.patch
create mode 100644 patch020-backport-add-goaway-mechanism.patch
create mode 100644 patch021-backport-some-enhancements.patch
create mode 100644 store/src/main/java/org/apache/rocketmq/store/RocksDBMessageStore.java
create mode 100644 store/src/main/java/org/apache/rocketmq/store/queue/AbstractConsumeQueueStore.java
create mode 100644 store/src/main/java/org/apache/rocketmq/store/queue/ConsumeQueueStoreInterface.java
create mode 100644 store/src/main/java/org/apache/rocketmq/store/queue/MultiDispatch.java
create mode 100644 store/src/main/java/org/apache/rocketmq/store/queue/RocksDBConsumeQueue.java
create mode 100644 store/src/main/java/org/apache/rocketmq/store/queue/RocksDBConsumeQueueOffsetTable.java
create mode 100644 store/src/main/java/org/apache/rocketmq/store/queue/RocksDBConsumeQueueStore.java
create mode 100644 store/src/main/java/org/apache/rocketmq/store/queue/RocksDBConsumeQueueTable.java
create mode 100644 store/src/main/java/org/apache/rocketmq/store/rocksdb/ConsumeQueueCompactionFilterFactory.java
create mode 100644 store/src/main/java/org/apache/rocketmq/store/rocksdb/ConsumeQueueRocksDBStorage.java
create mode 100644 store/src/main/java/org/apache/rocketmq/store/rocksdb/RocksDBOptionsFactory.java
create mode 100644 store/src/test/java/org/apache/rocketmq/store/RocksDBMessageStoreTest.java
create mode 100644 tools/src/main/java/org/apache/rocketmq/tools/command/metadata/RocksDBConfigToJsonCommand.java
diff --git a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java
index d4bded600..9f1fd0ad0 100644
--- a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java
+++ b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java
@@ -16,7 +16,33 @@
*/
package org.apache.rocketmq.broker;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
import com.google.common.collect.Lists;
+
import org.apache.rocketmq.acl.AccessValidator;
import org.apache.rocketmq.acl.plain.PlainAccessValidator;
import org.apache.rocketmq.broker.client.ClientHousekeepingService;
@@ -126,7 +152,7 @@ import org.apache.rocketmq.store.DefaultMessageStore;
import org.apache.rocketmq.store.MessageArrivingListener;
import org.apache.rocketmq.store.MessageStore;
import org.apache.rocketmq.store.PutMessageResult;
-import org.apache.rocketmq.store.StoreType;
+import org.apache.rocketmq.store.RocksDBMessageStore;
import org.apache.rocketmq.store.config.BrokerRole;
import org.apache.rocketmq.store.config.MessageStoreConfig;
import org.apache.rocketmq.store.dledger.DLedgerCommitLog;
@@ -141,31 +167,6 @@ import org.apache.rocketmq.store.timer.TimerCheckpoint;
import org.apache.rocketmq.store.timer.TimerMessageStore;
import org.apache.rocketmq.store.timer.TimerMetrics;
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.util.AbstractMap;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-
public class BrokerController {
protected static final Logger LOG = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME);
private static final Logger LOG_PROTECTION = LoggerFactory.getLogger(LoggerName.PROTECTION_LOGGER_NAME);
@@ -308,7 +309,7 @@ public class BrokerController {
this.setStoreHost(new InetSocketAddress(this.getBrokerConfig().getBrokerIP1(), getListenPort()));
this.brokerStatsManager = messageStoreConfig.isEnableLmq() ? new LmqBrokerStatsManager(this.brokerConfig.getBrokerClusterName(), this.brokerConfig.isEnableDetailStat()) : new BrokerStatsManager(this.brokerConfig.getBrokerClusterName(), this.brokerConfig.isEnableDetailStat());
this.broadcastOffsetManager = new BroadcastOffsetManager(this);
- if (isEnableRocksDBStore()) {
+ if (this.messageStoreConfig.isEnableRocksDBStore()) {
this.topicConfigManager = messageStoreConfig.isEnableLmq() ? new RocksDBLmqTopicConfigManager(this) : new RocksDBTopicConfigManager(this);
this.subscriptionGroupManager = messageStoreConfig.isEnableLmq() ? new RocksDBLmqSubscriptionGroupManager(this) : new RocksDBSubscriptionGroupManager(this);
this.consumerOffsetManager = messageStoreConfig.isEnableLmq() ? new RocksDBLmqConsumerOffsetManager(this) : new RocksDBConsumerOffsetManager(this);
@@ -747,7 +748,12 @@ public class BrokerController {
public boolean initializeMessageStore() {
boolean result = true;
try {
- DefaultMessageStore defaultMessageStore = new DefaultMessageStore(this.messageStoreConfig, this.brokerStatsManager, this.messageArrivingListener, this.brokerConfig, topicConfigManager.getTopicConfigTable());
+ DefaultMessageStore defaultMessageStore;
+ if (this.messageStoreConfig.isEnableRocksDBStore()) {
+ defaultMessageStore = new RocksDBMessageStore(this.messageStoreConfig, this.brokerStatsManager, this.messageArrivingListener, this.brokerConfig, topicConfigManager.getTopicConfigTable());
+ } else {
+ defaultMessageStore = new DefaultMessageStore(this.messageStoreConfig, this.brokerStatsManager, this.messageArrivingListener, this.brokerConfig, topicConfigManager.getTopicConfigTable());
+ }
if (messageStoreConfig.isEnableDLegerCommitLog()) {
DLedgerRoleChangeHandler roleChangeHandler =
@@ -944,16 +950,16 @@ public class BrokerController {
this.transactionalMessageService = ServiceProvider.loadClass(TransactionalMessageService.class);
if (null == this.transactionalMessageService) {
this.transactionalMessageService = new TransactionalMessageServiceImpl(
- new TransactionalMessageBridge(this, this.getMessageStore()));
+ new TransactionalMessageBridge(this, this.getMessageStore()));
LOG.warn("Load default transaction message hook service: {}",
- TransactionalMessageServiceImpl.class.getSimpleName());
+ TransactionalMessageServiceImpl.class.getSimpleName());
}
this.transactionalMessageCheckListener = ServiceProvider.loadClass(
- AbstractTransactionalMessageCheckListener.class);
+ AbstractTransactionalMessageCheckListener.class);
if (null == this.transactionalMessageCheckListener) {
this.transactionalMessageCheckListener = new DefaultTransactionalMessageCheckListener();
LOG.warn("Load default discard message hook service: {}",
- DefaultTransactionalMessageCheckListener.class.getSimpleName());
+ DefaultTransactionalMessageCheckListener.class.getSimpleName());
}
this.transactionalMessageCheckListener.setBrokerController(this);
this.transactionalMessageCheckService = new TransactionalMessageCheckService(this);
@@ -2412,8 +2418,4 @@ public class BrokerController {
public void setColdDataCgCtrService(ColdDataCgCtrService coldDataCgCtrService) {
this.coldDataCgCtrService = coldDataCgCtrService;
}
-
- public boolean isEnableRocksDBStore() {
- return StoreType.DEFAULT_ROCKSDB.getStoreType().equalsIgnoreCase(this.messageStoreConfig.getStoreType());
- }
}
diff --git a/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java b/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java
index a989e6e68..a1d711cb2 100644
--- a/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java
+++ b/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java
@@ -224,7 +224,7 @@ public class ReplicasManager {
public synchronized void changeBrokerRole(final Long newMasterBrokerId, final String newMasterAddress,
final Integer newMasterEpoch,
- final Integer syncStateSetEpoch, final Set<Long> syncStateSet) {
+ final Integer syncStateSetEpoch, final Set<Long> syncStateSet) throws Exception {
if (newMasterBrokerId != null && newMasterEpoch > this.masterEpoch) {
if (newMasterBrokerId.equals(this.brokerControllerId)) {
changeToMaster(newMasterEpoch, syncStateSetEpoch, syncStateSet);
@@ -234,7 +234,7 @@ public class ReplicasManager {
}
}
- public void changeToMaster(final int newMasterEpoch, final int syncStateSetEpoch, final Set<Long> syncStateSet) {
+ public void changeToMaster(final int newMasterEpoch, final int syncStateSetEpoch, final Set<Long> syncStateSet) throws Exception {
synchronized (this) {
if (newMasterEpoch > this.masterEpoch) {
LOGGER.info("Begin to change to master, brokerName:{}, replicas:{}, new Epoch:{}", this.brokerConfig.getBrokerName(), this.brokerAddress, newMasterEpoch);
diff --git a/broker/src/main/java/org/apache/rocketmq/broker/offset/RocksDBConsumerOffsetManager.java b/broker/src/main/java/org/apache/rocketmq/broker/offset/RocksDBConsumerOffsetManager.java
index 5695a3356..05b53b0bc 100644
--- a/broker/src/main/java/org/apache/rocketmq/broker/offset/RocksDBConsumerOffsetManager.java
+++ b/broker/src/main/java/org/apache/rocketmq/broker/offset/RocksDBConsumerOffsetManager.java
@@ -33,7 +33,7 @@ public class RocksDBConsumerOffsetManager extends ConsumerOffsetManager {
public RocksDBConsumerOffsetManager(BrokerController brokerController) {
super(brokerController);
- this.rocksDBConfigManager = new RocksDBConfigManager(this.brokerController.getMessageStoreConfig().getMemTableFlushInterval());
+ this.rocksDBConfigManager = new RocksDBConfigManager(brokerController.getMessageStoreConfig().getMemTableFlushIntervalMs());
}
@Override
@@ -49,7 +49,7 @@ public class RocksDBConsumerOffsetManager extends ConsumerOffsetManager {
@Override
protected void removeConsumerOffset(String topicAtGroup) {
try {
- byte[] keyBytes = topicAtGroup.getBytes(DataConverter.charset);
+ byte[] keyBytes = topicAtGroup.getBytes(DataConverter.CHARSET_UTF8);
this.rocksDBConfigManager.delete(keyBytes);
} catch (Exception e) {
LOG.error("kv remove consumerOffset Failed, {}", topicAtGroup);
@@ -58,7 +58,7 @@ public class RocksDBConsumerOffsetManager extends ConsumerOffsetManager {
@Override
protected void decode0(final byte[] key, final byte[] body) {
- String topicAtGroup = new String(key, DataConverter.charset);
+ String topicAtGroup = new String(key, DataConverter.CHARSET_UTF8);
RocksDBOffsetSerializeWrapper wrapper = JSON.parseObject(body, RocksDBOffsetSerializeWrapper.class);
this.offsetTable.put(topicAtGroup, wrapper.getOffsetTable());
@@ -93,7 +93,7 @@ public class RocksDBConsumerOffsetManager extends ConsumerOffsetManager {
}
private void putWriteBatch(final WriteBatch writeBatch, final String topicGroupName, final ConcurrentMap<Integer, Long> offsetMap) throws Exception {
- byte[] keyBytes = topicGroupName.getBytes(DataConverter.charset);
+ byte[] keyBytes = topicGroupName.getBytes(DataConverter.CHARSET_UTF8);
RocksDBOffsetSerializeWrapper wrapper = new RocksDBOffsetSerializeWrapper();
wrapper.setOffsetTable(offsetMap);
byte[] valueBytes = JSON.toJSONBytes(wrapper, SerializerFeature.BrowserCompatible);
diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java
index 244b459d6..59a3e63b2 100644
--- a/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java
+++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java
@@ -253,7 +253,7 @@ public class AckMessageProcessor implements NettyRequestProcessor {
MessageExtBrokerInner msgInner = new MessageExtBrokerInner();
msgInner.setTopic(reviveTopic);
- msgInner.setBody(JSON.toJSONString(ackMsg).getBytes(DataConverter.charset));
+ msgInner.setBody(JSON.toJSONString(ackMsg).getBytes(DataConverter.CHARSET_UTF8));
msgInner.setQueueId(rqId);
if (ackMsg instanceof BatchAckMsg) {
msgInner.setTags(PopAckConstants.BATCH_ACK_TAG);
diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessor.java
index e77120e15..dd4ec960f 100644
--- a/broker/src/main/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessor.java
+++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessor.java
@@ -539,14 +539,18 @@ public class AdminBrokerProcessor implements NettyRequestProcessor {
final Set<String> groups = this.brokerController.getConsumerOffsetManager().whichGroupByTopic(topic);
// delete pop retry topics first
- for (String group : groups) {
- final String popRetryTopic = KeyBuilder.buildPopRetryTopic(topic, group);
- if (brokerController.getTopicConfigManager().selectTopicConfig(popRetryTopic) != null) {
- deleteTopicInBroker(popRetryTopic);
+ try {
+ for (String group : groups) {
+ final String popRetryTopic = KeyBuilder.buildPopRetryTopic(topic, group);
+ if (brokerController.getTopicConfigManager().selectTopicConfig(popRetryTopic) != null) {
+ deleteTopicInBroker(popRetryTopic);
+ }
}
+ // delete topic
+ deleteTopicInBroker(topic);
+ } catch (Throwable t) {
+ return buildErrorResponse(ResponseCode.SYSTEM_ERROR, t.getMessage());
}
- // delete topic
- deleteTopicInBroker(topic);
response.setCode(ResponseCode.SUCCESS);
response.setRemark(null);
return response;
@@ -2081,7 +2085,11 @@ public class AdminBrokerProcessor implements NettyRequestProcessor {
public RemotingCommand cleanExpiredConsumeQueue() {
LOGGER.info("AdminBrokerProcessor#cleanExpiredConsumeQueue: start.");
final RemotingCommand response = RemotingCommand.createResponseCommand(null);
- brokerController.getMessageStore().cleanExpiredConsumerQueue();
+ try {
+ brokerController.getMessageStore().cleanExpiredConsumerQueue();
+ } catch (Throwable t) {
+ return buildErrorResponse(ResponseCode.SYSTEM_ERROR, t.getMessage());
+ }
LOGGER.info("AdminBrokerProcessor#cleanExpiredConsumeQueue: end.");
response.setCode(ResponseCode.SUCCESS);
response.setRemark(null);
@@ -2781,7 +2789,11 @@ public class AdminBrokerProcessor implements NettyRequestProcessor {
final ReplicasManager replicasManager = this.brokerController.getReplicasManager();
if (replicasManager != null) {
- replicasManager.changeBrokerRole(requestHeader.getMasterBrokerId(), requestHeader.getMasterAddress(), requestHeader.getMasterEpoch(), requestHeader.getSyncStateSetEpoch(), syncStateSetInfo.getSyncStateSet());
+ try {
+ replicasManager.changeBrokerRole(requestHeader.getMasterBrokerId(), requestHeader.getMasterAddress(), requestHeader.getMasterEpoch(), requestHeader.getSyncStateSetEpoch(), syncStateSetInfo.getSyncStateSet());
+ } catch (Exception e) {
+ throw new RemotingCommandException(e.getMessage());
+ }
}
response.setCode(ResponseCode.SUCCESS);
response.setRemark(null);
diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java
index 2ccdf07f6..bdfffff09 100644
--- a/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java
+++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java
@@ -180,7 +180,7 @@ public class ChangeInvisibleTimeProcessor implements NettyRequestProcessor {
}
msgInner.setTopic(reviveTopic);
- msgInner.setBody(JSON.toJSONString(ackMsg).getBytes(DataConverter.charset));
+ msgInner.setBody(JSON.toJSONString(ackMsg).getBytes(DataConverter.CHARSET_UTF8));
msgInner.setQueueId(rqId);
msgInner.setTags(PopAckConstants.ACK_TAG);
msgInner.setBornTimestamp(System.currentTimeMillis());
@@ -216,7 +216,7 @@ public class ChangeInvisibleTimeProcessor implements NettyRequestProcessor {
ck.addDiff(0);
ck.setBrokerName(brokerName);
- msgInner.setBody(JSON.toJSONString(ck).getBytes(DataConverter.charset));
+ msgInner.setBody(JSON.toJSONString(ck).getBytes(DataConverter.CHARSET_UTF8));
msgInner.setQueueId(reviveQid);
msgInner.setTags(PopAckConstants.CK_TAG);
msgInner.setBornTimestamp(System.currentTimeMillis());
diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java
index b7ba8ad4a..8a85dd8fe 100644
--- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java
+++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java
@@ -633,7 +633,7 @@ public class PopBufferMergeService extends ServiceThread {
ackMsg.setQueueId(point.getQueueId());
ackMsg.setPopTime(point.getPopTime());
msgInner.setTopic(popMessageProcessor.reviveTopic);
- msgInner.setBody(JSON.toJSONString(ackMsg).getBytes(DataConverter.charset));
+ msgInner.setBody(JSON.toJSONString(ackMsg).getBytes(DataConverter.CHARSET_UTF8));
msgInner.setQueueId(pointWrapper.getReviveQueueId());
msgInner.setTags(PopAckConstants.ACK_TAG);
msgInner.setBornTimestamp(System.currentTimeMillis());
@@ -673,7 +673,7 @@ public class PopBufferMergeService extends ServiceThread {
batchAckMsg.setQueueId(point.getQueueId());
batchAckMsg.setPopTime(point.getPopTime());
msgInner.setTopic(popMessageProcessor.reviveTopic);
- msgInner.setBody(JSON.toJSONString(batchAckMsg).getBytes(DataConverter.charset));
+ msgInner.setBody(JSON.toJSONString(batchAckMsg).getBytes(DataConverter.CHARSET_UTF8));
msgInner.setQueueId(pointWrapper.getReviveQueueId());
msgInner.setTags(PopAckConstants.BATCH_ACK_TAG);
msgInner.setBornTimestamp(System.currentTimeMillis());
diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java
index 0d9bdf143..f5d07c5aa 100644
--- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java
+++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java
@@ -685,7 +685,7 @@ public class PopMessageProcessor implements NettyRequestProcessor {
MessageExtBrokerInner msgInner = new MessageExtBrokerInner();
msgInner.setTopic(reviveTopic);
- msgInner.setBody(JSON.toJSONString(ck).getBytes(DataConverter.charset));
+ msgInner.setBody(JSON.toJSONString(ck).getBytes(DataConverter.CHARSET_UTF8));
msgInner.setQueueId(reviveQid);
msgInner.setTags(PopAckConstants.CK_TAG);
msgInner.setBornTimestamp(System.currentTimeMillis());
diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java
index d5174d3d1..4f80752e1 100644
--- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java
+++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java
@@ -356,7 +356,7 @@ public class PopReviveService extends ServiceThread {
}
for (MessageExt messageExt : messageExts) {
if (PopAckConstants.CK_TAG.equals(messageExt.getTags())) {
- String raw = new String(messageExt.getBody(), DataConverter.charset);
+ String raw = new String(messageExt.getBody(), DataConverter.CHARSET_UTF8);
if (brokerController.getBrokerConfig().isEnablePopLog()) {
POP_LOGGER.info("reviveQueueId={},find ck, offset:{}, raw : {}", messageExt.getQueueId(), messageExt.getQueueOffset(), raw);
}
@@ -371,7 +371,7 @@ public class PopReviveService extends ServiceThread {
firstRt = point.getReviveTime();
}
} else if (PopAckConstants.ACK_TAG.equals(messageExt.getTags())) {
- String raw = new String(messageExt.getBody(), DataConverter.charset);
+ String raw = new String(messageExt.getBody(), DataConverter.CHARSET_UTF8);
if (brokerController.getBrokerConfig().isEnablePopLog()) {
POP_LOGGER.info("reviveQueueId={},find ack, offset:{}, raw : {}", messageExt.getQueueId(), messageExt.getQueueOffset(), raw);
}
@@ -395,7 +395,7 @@ public class PopReviveService extends ServiceThread {
}
}
} else if (PopAckConstants.BATCH_ACK_TAG.equals(messageExt.getTags())) {
- String raw = new String(messageExt.getBody(), DataConverter.charset);
+ String raw = new String(messageExt.getBody(), DataConverter.CHARSET_UTF8);
if (brokerController.getBrokerConfig().isEnablePopLog()) {
POP_LOGGER.info("reviveQueueId={}, find batch ack, offset:{}, raw : {}", messageExt.getQueueId(), messageExt.getQueueOffset(), raw);
}
diff --git a/broker/src/main/java/org/apache/rocketmq/broker/subscription/RocksDBSubscriptionGroupManager.java b/broker/src/main/java/org/apache/rocketmq/broker/subscription/RocksDBSubscriptionGroupManager.java
index 6503970af..e9a81a8d6 100644
--- a/broker/src/main/java/org/apache/rocketmq/broker/subscription/RocksDBSubscriptionGroupManager.java
+++ b/broker/src/main/java/org/apache/rocketmq/broker/subscription/RocksDBSubscriptionGroupManager.java
@@ -30,7 +30,7 @@ public class RocksDBSubscriptionGroupManager extends SubscriptionGroupManager {
public RocksDBSubscriptionGroupManager(BrokerController brokerController) {
super(brokerController, false);
- this.rocksDBConfigManager = new RocksDBConfigManager(this.brokerController.getMessageStoreConfig().getMemTableFlushInterval());
+ this.rocksDBConfigManager = new RocksDBConfigManager(brokerController.getMessageStoreConfig().getMemTableFlushIntervalMs());
}
@Override
@@ -53,7 +53,7 @@ public class RocksDBSubscriptionGroupManager extends SubscriptionGroupManager {
SubscriptionGroupConfig oldConfig = this.subscriptionGroupTable.put(groupName, subscriptionGroupConfig);
try {
- byte[] keyBytes = groupName.getBytes(DataConverter.charset);
+ byte[] keyBytes = groupName.getBytes(DataConverter.CHARSET_UTF8);
byte[] valueBytes = JSON.toJSONBytes(subscriptionGroupConfig, SerializerFeature.BrowserCompatible);
this.rocksDBConfigManager.put(keyBytes, keyBytes.length, valueBytes);
} catch (Exception e) {
@@ -68,7 +68,7 @@ public class RocksDBSubscriptionGroupManager extends SubscriptionGroupManager {
SubscriptionGroupConfig oldConfig = this.subscriptionGroupTable.putIfAbsent(groupName, subscriptionGroupConfig);
if (oldConfig == null) {
try {
- byte[] keyBytes = groupName.getBytes(DataConverter.charset);
+ byte[] keyBytes = groupName.getBytes(DataConverter.CHARSET_UTF8);
byte[] valueBytes = JSON.toJSONBytes(subscriptionGroupConfig, SerializerFeature.BrowserCompatible);
this.rocksDBConfigManager.put(keyBytes, keyBytes.length, valueBytes);
} catch (Exception e) {
@@ -82,7 +82,7 @@ public class RocksDBSubscriptionGroupManager extends SubscriptionGroupManager {
protected SubscriptionGroupConfig removeSubscriptionGroupConfig(String groupName) {
SubscriptionGroupConfig subscriptionGroupConfig = this.subscriptionGroupTable.remove(groupName);
try {
- this.rocksDBConfigManager.delete(groupName.getBytes(DataConverter.charset));
+ this.rocksDBConfigManager.delete(groupName.getBytes(DataConverter.CHARSET_UTF8));
} catch (Exception e) {
log.error("kv delete sub Failed, {}", subscriptionGroupConfig.toString());
}
@@ -91,7 +91,7 @@ public class RocksDBSubscriptionGroupManager extends SubscriptionGroupManager {
@Override
protected void decode0(byte[] key, byte[] body) {
- String groupName = new String(key, DataConverter.charset);
+ String groupName = new String(key, DataConverter.CHARSET_UTF8);
SubscriptionGroupConfig subscriptionGroupConfig = JSON.parseObject(body, SubscriptionGroupConfig.class);
this.subscriptionGroupTable.put(groupName, subscriptionGroupConfig);
diff --git a/broker/src/main/java/org/apache/rocketmq/broker/topic/RocksDBTopicConfigManager.java b/broker/src/main/java/org/apache/rocketmq/broker/topic/RocksDBTopicConfigManager.java
index 7da0d7c8a..fddecf2d9 100644
--- a/broker/src/main/java/org/apache/rocketmq/broker/topic/RocksDBTopicConfigManager.java
+++ b/broker/src/main/java/org/apache/rocketmq/broker/topic/RocksDBTopicConfigManager.java
@@ -30,7 +30,7 @@ public class RocksDBTopicConfigManager extends TopicConfigManager {
public RocksDBTopicConfigManager(BrokerController brokerController) {
super(brokerController, false);
- this.rocksDBConfigManager = new RocksDBConfigManager(this.brokerController.getMessageStoreConfig().getMemTableFlushInterval());
+ this.rocksDBConfigManager = new RocksDBConfigManager(brokerController.getMessageStoreConfig().getMemTableFlushIntervalMs());
}
@Override
@@ -49,7 +49,7 @@ public class RocksDBTopicConfigManager extends TopicConfigManager {
@Override
protected void decode0(byte[] key, byte[] body) {
- String topicName = new String(key, DataConverter.charset);
+ String topicName = new String(key, DataConverter.CHARSET_UTF8);
TopicConfig topicConfig = JSON.parseObject(body, TopicConfig.class);
this.topicConfigTable.put(topicName, topicConfig);
@@ -66,7 +66,7 @@ public class RocksDBTopicConfigManager extends TopicConfigManager {
String topicName = topicConfig.getTopicName();
TopicConfig oldTopicConfig = this.topicConfigTable.put(topicName, topicConfig);
try {
- byte[] keyBytes = topicName.getBytes(DataConverter.charset);
+ byte[] keyBytes = topicName.getBytes(DataConverter.CHARSET_UTF8);
byte[] valueBytes = JSON.toJSONBytes(topicConfig, SerializerFeature.BrowserCompatible);
this.rocksDBConfigManager.put(keyBytes, keyBytes.length, valueBytes);
} catch (Exception e) {
@@ -79,7 +79,7 @@ public class RocksDBTopicConfigManager extends TopicConfigManager {
protected TopicConfig removeTopicConfig(String topicName) {
TopicConfig topicConfig = this.topicConfigTable.remove(topicName);
try {
- this.rocksDBConfigManager.delete(topicName.getBytes(DataConverter.charset));
+ this.rocksDBConfigManager.delete(topicName.getBytes(DataConverter.CHARSET_UTF8));
} catch (Exception e) {
log.error("kv remove topic Failed, {}", topicConfig.toString());
}
diff --git a/broker/src/test/java/org/apache/rocketmq/broker/processor/PopReviveServiceTest.java b/broker/src/test/java/org/apache/rocketmq/broker/processor/PopReviveServiceTest.java
index 1c3a0cd45..78b76264f 100644
--- a/broker/src/test/java/org/apache/rocketmq/broker/processor/PopReviveServiceTest.java
+++ b/broker/src/test/java/org/apache/rocketmq/broker/processor/PopReviveServiceTest.java
@@ -234,7 +234,7 @@ public class PopReviveServiceTest {
MessageExtBrokerInner msgInner = new MessageExtBrokerInner();
msgInner.setTopic(REVIVE_TOPIC);
- msgInner.setBody(JSON.toJSONString(ck).getBytes(DataConverter.charset));
+ msgInner.setBody(JSON.toJSONString(ck).getBytes(DataConverter.CHARSET_UTF8));
msgInner.setQueueId(REVIVE_QUEUE_ID);
msgInner.setTags(PopAckConstants.CK_TAG);
msgInner.setBornTimestamp(System.currentTimeMillis());
@@ -269,7 +269,7 @@ public class PopReviveServiceTest {
SocketAddress host, long deliverMs, String ackUniqueId) {
MessageExtBrokerInner msgInner = new MessageExtBrokerInner();
msgInner.setTopic(reviveTopic);
- msgInner.setBody(JSON.toJSONString(ackMsg).getBytes(DataConverter.charset));
+ msgInner.setBody(JSON.toJSONString(ackMsg).getBytes(DataConverter.CHARSET_UTF8));
msgInner.setQueueId(reviveQid);
msgInner.setTags(PopAckConstants.ACK_TAG);
msgInner.setBornTimestamp(System.currentTimeMillis());
diff --git a/common/pom.xml b/common/pom.xml
index 6104c3ac6..a28ed228f 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -109,7 +109,7 @@
<artifactId>rocketmq-logback-classic</artifactId>
</dependency>
<dependency>
- <groupId>io.github.aliyunmq</groupId>
+ <groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-rocksdb</artifactId>
</dependency>
</dependencies>
diff --git a/common/src/main/java/org/apache/rocketmq/common/MixAll.java b/common/src/main/java/org/apache/rocketmq/common/MixAll.java
index 1233a5422..407ef2842 100644
--- a/common/src/main/java/org/apache/rocketmq/common/MixAll.java
+++ b/common/src/main/java/org/apache/rocketmq/common/MixAll.java
@@ -492,6 +492,7 @@ public class MixAll {
public static int compareLong(long x, long y) {
return Long.compare(x, y);
}
+
public static boolean isLmq(String lmqMetaData) {
return lmqMetaData != null && lmqMetaData.startsWith(LMQ_PREFIX);
}
diff --git a/common/src/main/java/org/apache/rocketmq/common/attribute/CQType.java b/common/src/main/java/org/apache/rocketmq/common/attribute/CQType.java
index 73ef21880..9148d5a18 100644
--- a/common/src/main/java/org/apache/rocketmq/common/attribute/CQType.java
+++ b/common/src/main/java/org/apache/rocketmq/common/attribute/CQType.java
@@ -19,5 +19,6 @@ package org.apache.rocketmq.common.attribute;
public enum CQType {
SimpleCQ,
- BatchCQ
+ BatchCQ,
+ RocksDBCQ
}
diff --git a/common/src/main/java/org/apache/rocketmq/common/config/AbstractRocksDBStorage.java b/common/src/main/java/org/apache/rocketmq/common/config/AbstractRocksDBStorage.java
index 6f19a9815..20319abba 100644
--- a/common/src/main/java/org/apache/rocketmq/common/config/AbstractRocksDBStorage.java
+++ b/common/src/main/java/org/apache/rocketmq/common/config/AbstractRocksDBStorage.java
@@ -17,7 +17,6 @@
package org.apache.rocketmq.common.config;
import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -27,11 +26,11 @@ import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
-import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.rocketmq.common.ThreadFactoryImpl;
import org.apache.rocketmq.common.constant.LoggerName;
+import org.apache.rocketmq.common.utils.DataConverter;
import org.apache.rocketmq.common.utils.ThreadUtils;
import org.apache.rocketmq.logging.org.slf4j.Logger;
import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
@@ -47,7 +46,6 @@ import org.rocksdb.Priority;
import org.rocksdb.ReadOptions;
import org.rocksdb.RocksDB;
import org.rocksdb.RocksDBException;
-import org.rocksdb.RocksIterator;
import org.rocksdb.Statistics;
import org.rocksdb.Status;
import org.rocksdb.WriteBatch;
@@ -58,7 +56,6 @@ import static org.rocksdb.RocksDB.NOT_FOUND;
public abstract class AbstractRocksDBStorage {
protected static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.ROCKSDB_LOGGER_NAME);
- private static final Charset CHARSET_UTF8 = Charset.forName("UTF-8");
private static final String SPACE = " | ";
protected String dbPath;
@@ -223,10 +220,6 @@ public abstract class AbstractRocksDBStorage {
}
}
- protected WrappedRocksIterator newIterator(ColumnFamilyHandle cfHandle, ReadOptions readOptions) {
- return new WrappedRocksIterator(this.db.newIterator(cfHandle, readOptions));
- }
-
protected void rangeDelete(ColumnFamilyHandle cfHandle, WriteOptions writeOptions,
final byte[] startKey, final byte[] endKey) throws RocksDBException {
if (!hold()) {
@@ -243,46 +236,6 @@ public abstract class AbstractRocksDBStorage {
}
}
- protected void manualCompactionDefaultCfMaxLevel(final CompactionOptions compactionOptions) throws Exception {
- final ColumnFamilyHandle defaultCFHandle = this.defaultCFHandle;
- final byte[] defaultCFName = defaultCFHandle.getName();
- List<LiveFileMetaData> fileMetaDataList = this.db.getLiveFilesMetaData();
- if (fileMetaDataList == null || fileMetaDataList.isEmpty()) {
- return;
- }
-
- List<LiveFileMetaData> defaultLiveFileDataList = Lists.newArrayList();
- List<String> inputFileNames = Lists.newArrayList();
- int maxLevel = 0;
- for (LiveFileMetaData fileMetaData : fileMetaDataList) {
- if (compareTo(fileMetaData.columnFamilyName(), defaultCFName) != 0) {
- continue;
- }
- defaultLiveFileDataList.add(fileMetaData);
- if (fileMetaData.level() > maxLevel) {
- maxLevel = fileMetaData.level();
- }
- }
- if (maxLevel == 0) {
- LOGGER.info("manualCompactionDefaultCfFiles skip level 0.");
- return;
- }
-
- for (LiveFileMetaData fileMetaData : defaultLiveFileDataList) {
- if (fileMetaData.level() != maxLevel || fileMetaData.beingCompacted()) {
- continue;
- }
- inputFileNames.add(fileMetaData.path() + fileMetaData.fileName());
- }
- if (!inputFileNames.isEmpty()) {
- List<String> outputLists = this.db.compactFiles(compactionOptions, defaultCFHandle,
- inputFileNames, maxLevel, -1, null);
- LOGGER.info("manualCompactionDefaultCfFiles OK. src: {}, dst: {}", inputFileNames, outputLists);
- } else {
- LOGGER.info("manualCompactionDefaultCfFiles Empty.");
- }
- }
-
protected void manualCompactionDefaultCfRange(CompactRangeOptions compactRangeOptions) {
if (!hold()) {
return;
@@ -494,50 +447,6 @@ public abstract class AbstractRocksDBStorage {
this.db.flushWal(true);
}
- protected class WrappedRocksIterator {
- private final RocksIterator iterator;
-
- public WrappedRocksIterator(final RocksIterator iterator) {
- this.iterator = iterator;
- }
-
- public byte[] key() {
- return iterator.key();
- }
-
- public byte[] value() {
- return iterator.value();
- }
-
- public void next() {
- iterator.next();
- }
-
- public void prev() {
- iterator.prev();
- }
-
- public void seek(byte[] target) {
- iterator.seek(target);
- }
-
- public void seekForPrev(byte[] target) {
- iterator.seekForPrev(target);
- }
-
- public void seekToFirst() {
- iterator.seekToFirst();
- }
-
- public boolean isValid() {
- return iterator.isValid();
- }
-
- public void close() {
- iterator.close();
- }
- }
-
private String getStatusError(RocksDBException e) {
if (e == null || e.getStatus() == null) {
return "null";
@@ -574,7 +483,7 @@ public abstract class AbstractRocksDBStorage {
sb = new StringBuilder(256);
map.put(metaData.level(), sb);
}
- sb.append(new String(metaData.columnFamilyName(), CHARSET_UTF8)).append(SPACE).
+ sb.append(new String(metaData.columnFamilyName(), DataConverter.CHARSET_UTF8)).append(SPACE).
append(metaData.fileName()).append(SPACE).
append("s: ").append(metaData.size()).append(SPACE).
append("a: ").append(metaData.numEntries()).append(SPACE).
@@ -595,21 +504,4 @@ public abstract class AbstractRocksDBStorage {
} catch (Exception ignored) {
}
}
-
- public int compareTo(byte[] v1, byte[] v2) {
- int len1 = v1.length;
- int len2 = v2.length;
- int lim = Math.min(len1, len2);
-
- int k = 0;
- while (k < lim) {
- byte c1 = v1[k];
- byte c2 = v2[k];
- if (c1 != c2) {
- return c1 - c2;
- }
- k++;
- }
- return len1 - len2;
- }
}
\ No newline at end of file
diff --git a/common/src/main/java/org/apache/rocketmq/common/config/ConfigRocksDBStorage.java b/common/src/main/java/org/apache/rocketmq/common/config/ConfigRocksDBStorage.java
index 463bd8fed..b40f8046e 100644
--- a/common/src/main/java/org/apache/rocketmq/common/config/ConfigRocksDBStorage.java
+++ b/common/src/main/java/org/apache/rocketmq/common/config/ConfigRocksDBStorage.java
@@ -203,7 +203,7 @@ public class ConfigRocksDBStorage extends AbstractRocksDBStorage {
setUseDirectReads(true);
}
- private static String getDBLogDir() {
+ public static String getDBLogDir() {
String rootPath = System.getProperty("user.home");
if (StringUtils.isEmpty(rootPath)) {
return "";
diff --git a/common/src/main/java/org/apache/rocketmq/common/message/MessageExtBrokerInner.java b/common/src/main/java/org/apache/rocketmq/common/message/MessageExtBrokerInner.java
index 0c72ebb7b..91599653c 100644
--- a/common/src/main/java/org/apache/rocketmq/common/message/MessageExtBrokerInner.java
+++ b/common/src/main/java/org/apache/rocketmq/common/message/MessageExtBrokerInner.java
@@ -70,4 +70,17 @@ public class MessageExtBrokerInner extends MessageExt {
public void setVersion(MessageVersion version) {
this.version = version;
}
+
+ public void removeWaitStorePropertyString() {
+ if (this.getProperties().containsKey(MessageConst.PROPERTY_WAIT_STORE_MSG_OK)) {
+ // There is no need to store "WAIT=true", remove it from propertiesString to save 9 bytes for each message.
+ // It works for most case. In some cases msgInner.setPropertiesString invoked later and replace it.
+ String waitStoreMsgOKValue = this.getProperties().remove(MessageConst.PROPERTY_WAIT_STORE_MSG_OK);
+ this.setPropertiesString(MessageDecoder.messageProperties2String(this.getProperties()));
+ // Reput to properties, since msgInner.isWaitStoreMsgOK() will be invoked later
+ this.getProperties().put(MessageConst.PROPERTY_WAIT_STORE_MSG_OK, waitStoreMsgOKValue);
+ } else {
+ this.setPropertiesString(MessageDecoder.messageProperties2String(this.getProperties()));
+ }
+ }
}
diff --git a/common/src/main/java/org/apache/rocketmq/common/topic/TopicValidator.java b/common/src/main/java/org/apache/rocketmq/common/topic/TopicValidator.java
index 61265c05d..c19592a44 100644
--- a/common/src/main/java/org/apache/rocketmq/common/topic/TopicValidator.java
+++ b/common/src/main/java/org/apache/rocketmq/common/topic/TopicValidator.java
@@ -31,6 +31,7 @@ public class TopicValidator {
public static final String RMQ_SYS_TRANS_CHECK_MAX_TIME_TOPIC = "TRANS_CHECK_MAX_TIME_TOPIC";
public static final String RMQ_SYS_SELF_TEST_TOPIC = "SELF_TEST_TOPIC";
public static final String RMQ_SYS_OFFSET_MOVED_EVENT = "OFFSET_MOVED_EVENT";
+ public static final String RMQ_SYS_ROCKSDB_OFFSET_TOPIC = "CHECKPOINT_TOPIC";
public static final String SYSTEM_TOPIC_PREFIX = "rmq_sys_";
public static final String SYNC_BROKER_MEMBER_GROUP_PREFIX = SYSTEM_TOPIC_PREFIX + "SYNC_BROKER_MEMBER_";
@@ -55,6 +56,7 @@ public class TopicValidator {
SYSTEM_TOPIC_SET.add(RMQ_SYS_TRANS_CHECK_MAX_TIME_TOPIC);
SYSTEM_TOPIC_SET.add(RMQ_SYS_SELF_TEST_TOPIC);
SYSTEM_TOPIC_SET.add(RMQ_SYS_OFFSET_MOVED_EVENT);
+ SYSTEM_TOPIC_SET.add(RMQ_SYS_ROCKSDB_OFFSET_TOPIC);
NOT_ALLOWED_SEND_TOPIC_SET.add(RMQ_SYS_SCHEDULE_TOPIC);
NOT_ALLOWED_SEND_TOPIC_SET.add(RMQ_SYS_TRANS_HALF_TOPIC);
diff --git a/common/src/main/java/org/apache/rocketmq/common/utils/DataConverter.java b/common/src/main/java/org/apache/rocketmq/common/utils/DataConverter.java
index 8b50de12b..cc96770b2 100644
--- a/common/src/main/java/org/apache/rocketmq/common/utils/DataConverter.java
+++ b/common/src/main/java/org/apache/rocketmq/common/utils/DataConverter.java
@@ -20,7 +20,7 @@ import java.nio.ByteBuffer;
import java.nio.charset.Charset;
public class DataConverter {
- public static Charset charset = Charset.forName("UTF-8");
+ public static final Charset CHARSET_UTF8 = Charset.forName("UTF-8");
public static byte[] Long2Byte(Long v) {
ByteBuffer tmp = ByteBuffer.allocate(8);
diff --git a/example/src/main/java/org/apache/rocketmq/example/quickstart/Producer.java b/example/src/main/java/org/apache/rocketmq/example/quickstart/Producer.java
index aac295030..8662328ea 100644
--- a/example/src/main/java/org/apache/rocketmq/example/quickstart/Producer.java
+++ b/example/src/main/java/org/apache/rocketmq/example/quickstart/Producer.java
@@ -71,7 +71,7 @@ public class Producer {
TAG /* Tag */,
("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
);
-
+ msg.setDelayTimeLevel(1);
/*
* Call send message to deliver message to one of brokers.
*/
diff --git a/metrics b/metrics
new file mode 100644
index 000000000..146e90fae
--- /dev/null
+++ b/metrics
@@ -0,0 +1,241 @@
+# TYPE target info
+# HELP target Target metadata
+target_info{} 1
+# TYPE otel_scope_info info
+# HELP otel_scope_info Scope metadata
+otel_scope_info{otel_scope_name="broker-meter"} 1
+# TYPE rocketmq_storage_message_reserve_time gauge
+# HELP rocketmq_storage_message_reserve_time default view for gauge.
+rocketmq_storage_message_reserve_time_milliseconds{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",storage_medium="disk",storage_type="local"} 1.9491132515E10 1696905287361
+# TYPE rocketmq_timer_enqueue_latency gauge
+# HELP rocketmq_timer_enqueue_latency default view for gauge.
+rocketmq_timer_enqueue_latency_milliseconds{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",storage_medium="disk",storage_type="local"} 0.0 1696905287361
+# TYPE rocketmq_timer_enqueue_lag gauge
+# HELP rocketmq_timer_enqueue_lag default view for gauge.
+rocketmq_timer_enqueue_lag{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",storage_medium="disk",storage_type="local"} 0.0 1696905287361
+# TYPE rocketmq_timing_messages gauge
+# HELP rocketmq_timing_messages default view for gauge.
+rocketmq_timing_messages{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",storage_medium="disk",storage_type="local",topic="BenchmarkTest"} 0.0 1696905287361
+# TYPE rocketmq_storage_flush_behind_bytes gauge
+# HELP rocketmq_storage_flush_behind_bytes default view for gauge.
+rocketmq_storage_flush_behind_bytes{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",storage_medium="disk",storage_type="local"} 1230.0 1696905287361
+# TYPE rocketmq_processor_watermark gauge
+# HELP rocketmq_processor_watermark default view for gauge.
+rocketmq_processor_watermark{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",processor="reply"} 0.0 1696905287361
+rocketmq_processor_watermark{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",processor="transaction"} 0.0 1696905287361
+rocketmq_processor_watermark{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",processor="send"} 0.0 1696905287361
+rocketmq_processor_watermark{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",processor="async_put"} 0.0 1696905287361
+rocketmq_processor_watermark{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",processor="lite_pull"} 0.0 1696905287361
+rocketmq_processor_watermark{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",processor="pull"} 0.0 1696905287361
+rocketmq_processor_watermark{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",processor="heartbeat"} 0.0 1696905287361
+rocketmq_processor_watermark{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",processor="consumer_manager"} 0.0 1696905287361
+rocketmq_processor_watermark{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",processor="admin"} 0.0 1696905287361
+rocketmq_processor_watermark{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",processor="client_manager"} 0.0 1696905287361
+rocketmq_processor_watermark{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",processor="query_message"} 0.0 1696905287361
+rocketmq_processor_watermark{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",processor="ack"} 0.0 1696905287361
+# TYPE rocketmq_pop_buffer_scan_time_consume histogram
+# HELP rocketmq_pop_buffer_scan_time_consume Time consuming of pop buffer scan
+rocketmq_pop_buffer_scan_time_consume_milliseconds_count{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker"} 5267005.0 1696905287361
+rocketmq_pop_buffer_scan_time_consume_milliseconds_sum{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker"} 39651.0 1696905287361
+rocketmq_pop_buffer_scan_time_consume_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",le="1.0"} 5266935.0 1696905287361
+rocketmq_pop_buffer_scan_time_consume_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",le="10.0"} 5266998.0 1696905287361
+rocketmq_pop_buffer_scan_time_consume_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",le="100.0"} 5267001.0 1696905287361
+rocketmq_pop_buffer_scan_time_consume_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",le="1000.0"} 5267005.0 1696905287361
+rocketmq_pop_buffer_scan_time_consume_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",le="2000.0"} 5267005.0 1696905287361
+rocketmq_pop_buffer_scan_time_consume_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",le="3000.0"} 5267005.0 1696905287361
+rocketmq_pop_buffer_scan_time_consume_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",le="+Inf"} 5267005.0 1696905287361
+# TYPE rocketmq_pop_revive_lag gauge
+# HELP rocketmq_pop_revive_lag default view for gauge.
+rocketmq_pop_revive_lag_milliseconds{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",queue_id="1"} 1.0 1696905287361
+rocketmq_pop_revive_lag_milliseconds{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",queue_id="2"} 1.0 1696905287361
+rocketmq_pop_revive_lag_milliseconds{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",queue_id="3"} 1.0 1696905287361
+rocketmq_pop_revive_lag_milliseconds{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",queue_id="4"} 1.0 1696905287361
+rocketmq_pop_revive_lag_milliseconds{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",queue_id="5"} 1.0 1696905287361
+rocketmq_pop_revive_lag_milliseconds{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",queue_id="6"} 1.0 1696905287361
+rocketmq_pop_revive_lag_milliseconds{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",queue_id="7"} 1.0 1696905287361
+rocketmq_pop_revive_lag_milliseconds{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",queue_id="0"} 1.0 1696905287361
+# TYPE rocketmq_pop_revive_latency gauge
+# HELP rocketmq_pop_revive_latency default view for gauge.
+rocketmq_pop_revive_latency_milliseconds{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",queue_id="1"} 0.0 1696905287361
+rocketmq_pop_revive_latency_milliseconds{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",queue_id="2"} 0.0 1696905287361
+rocketmq_pop_revive_latency_milliseconds{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",queue_id="3"} 0.0 1696905287361
+rocketmq_pop_revive_latency_milliseconds{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",queue_id="4"} 0.0 1696905287361
+rocketmq_pop_revive_latency_milliseconds{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",queue_id="5"} 0.0 1696905287361
+rocketmq_pop_revive_latency_milliseconds{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",queue_id="6"} 0.0 1696905287361
+rocketmq_pop_revive_latency_milliseconds{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",queue_id="7"} 0.0 1696905287361
+rocketmq_pop_revive_latency_milliseconds{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",queue_id="0"} 0.0 1696905287361
+# TYPE rocketmq_storage_size gauge
+# HELP rocketmq_storage_size default view for gauge.
+rocketmq_storage_size_bytes{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",storage_medium="disk",storage_type="local"} 1.5905103872E10 1696905287361
+# TYPE rocketmq_pop_offset_buffer_size gauge
+# HELP rocketmq_pop_offset_buffer_size default view for gauge.
+rocketmq_pop_offset_buffer_size{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker"} 0.0 1696905287361
+# TYPE rocketmq_timer_dequeue_latency gauge
+# HELP rocketmq_timer_dequeue_latency default view for gauge.
+rocketmq_timer_dequeue_latency_milliseconds{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",storage_medium="disk",storage_type="local"} 0.0 1696905287361
+# TYPE rocketmq_broker_permission gauge
+# HELP rocketmq_broker_permission default view for gauge.
+rocketmq_broker_permission{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker"} 6.0 1696905287361
+# TYPE rocketmq_consumer_lag_messages gauge
+# HELP rocketmq_consumer_lag_messages default view for gauge.
+rocketmq_consumer_lag_messages{otel_scope_name="broker-meter",cluster="DefaultCluster",consumer_group="MQ_INST_test%GID_222",is_retry="false",is_system="false",node_id="broker-a",node_type="broker",topic="MQ_INST_test%zzl"} 0.0 1696905287361
+# TYPE rocketmq_throughput_out_total counter
+# HELP rocketmq_throughput_out_total default view for counter.
+rocketmq_throughput_out_total{otel_scope_name="broker-meter",cluster="DefaultCluster",consumer_group="MQ_INST_test%GID_222",is_system="false",node_id="broker-a",node_type="broker",topic="MQ_INST_test%zzl"} 1230.0 1696905287361
+# TYPE rocketmq_timer_dequeue_lag gauge
+# HELP rocketmq_timer_dequeue_lag default view for gauge.
+rocketmq_timer_dequeue_lag{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",storage_medium="disk",storage_type="local"} 0.0 1696905287361
+# TYPE rocketmq_messages_out_total counter
+# HELP rocketmq_messages_out_total default view for counter.
+rocketmq_messages_out_total{otel_scope_name="broker-meter",cluster="DefaultCluster",consumer_group="MQ_INST_test%GID_222",is_system="false",node_id="broker-a",node_type="broker",topic="MQ_INST_test%zzl"} 5.0 1696905287361
+# TYPE rocketmq_messages_in_total counter
+# HELP rocketmq_messages_in_total default view for counter.
+rocketmq_messages_in_total{otel_scope_name="broker-meter",cluster="DefaultCluster",is_system="false",message_type="normal",node_id="broker-a",node_type="broker",topic="MQ_INST_test%zzl"} 5.0 1696905287361
+# TYPE rocketmq_consumer_queueing_latency gauge
+# HELP rocketmq_consumer_queueing_latency default view for gauge.
+rocketmq_consumer_queueing_latency_milliseconds{otel_scope_name="broker-meter",cluster="DefaultCluster",consumer_group="MQ_INST_test%GID_222",is_retry="false",is_system="false",node_id="broker-a",node_type="broker",topic="MQ_INST_test%zzl"} 0.0 1696905287361
+# TYPE rocketmq_consumer_connections gauge
+# HELP rocketmq_consumer_connections default view for gauge.
+rocketmq_consumer_connections{otel_scope_name="broker-meter",cluster="DefaultCluster",consume_mode="push",consumer_group="MQ_INST_test%GID_222",is_system="false",language="java",node_id="broker-a",node_type="broker",protocol_type="remoting",version="v4_8_0"} 1.0 1696905287361
+# TYPE rocketmq_rpc_latency histogram
+# HELP rocketmq_rpc_latency Rpc latency
+rocketmq_rpc_latency_milliseconds_count{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="heart_beat",response_code="success",result="success"} 24.0 1696905287361
+rocketmq_rpc_latency_milliseconds_sum{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="heart_beat",response_code="success",result="success"} 128.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="heart_beat",response_code="success",result="success",le="1.0"} 17.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="heart_beat",response_code="success",result="success",le="3.0"} 18.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="heart_beat",response_code="success",result="success",le="5.0"} 19.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="heart_beat",response_code="success",result="success",le="7.0"} 19.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="heart_beat",response_code="success",result="success",le="10.0"} 20.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="heart_beat",response_code="success",result="success",le="100.0"} 24.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="heart_beat",response_code="success",result="success",le="1000.0"} 24.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="heart_beat",response_code="success",result="success",le="2000.0"} 24.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="heart_beat",response_code="success",result="success",le="3000.0"} 24.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="heart_beat",response_code="success",result="success",le="+Inf"} 24.0 1696905287361
+rocketmq_rpc_latency_milliseconds_count{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="query_consumer_offset",response_code="success",result="success"} 5.0 1696905287361
+rocketmq_rpc_latency_milliseconds_sum{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="query_consumer_offset",response_code="success",result="success"} 8.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="query_consumer_offset",response_code="success",result="success",le="1.0"} 3.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="query_consumer_offset",response_code="success",result="success",le="3.0"} 5.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="query_consumer_offset",response_code="success",result="success",le="5.0"} 5.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="query_consumer_offset",response_code="success",result="success",le="7.0"} 5.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="query_consumer_offset",response_code="success",result="success",le="10.0"} 5.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="query_consumer_offset",response_code="success",result="success",le="100.0"} 5.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="query_consumer_offset",response_code="success",result="success",le="1000.0"} 5.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="query_consumer_offset",response_code="success",result="success",le="2000.0"} 5.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="query_consumer_offset",response_code="success",result="success",le="3000.0"} 5.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="query_consumer_offset",response_code="success",result="success",le="+Inf"} 5.0 1696905287361
+rocketmq_rpc_latency_milliseconds_count{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="subscription_not_latest",result="success"} 1.0 1696905287361
+rocketmq_rpc_latency_milliseconds_sum{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="subscription_not_latest",result="success"} 54.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="subscription_not_latest",result="success",le="1.0"} 0.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="subscription_not_latest",result="success",le="3.0"} 0.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="subscription_not_latest",result="success",le="5.0"} 0.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="subscription_not_latest",result="success",le="7.0"} 0.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="subscription_not_latest",result="success",le="10.0"} 0.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="subscription_not_latest",result="success",le="100.0"} 1.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="subscription_not_latest",result="success",le="1000.0"} 1.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="subscription_not_latest",result="success",le="2000.0"} 1.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="subscription_not_latest",result="success",le="3000.0"} 1.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="subscription_not_latest",result="success",le="+Inf"} 1.0 1696905287361
+rocketmq_rpc_latency_milliseconds_count{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="get_consumer_list_by_group",response_code="success",result="success"} 60.0 1696905287361
+rocketmq_rpc_latency_milliseconds_sum{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="get_consumer_list_by_group",response_code="success",result="success"} 108.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="get_consumer_list_by_group",response_code="success",result="success",le="1.0"} 54.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="get_consumer_list_by_group",response_code="success",result="success",le="3.0"} 54.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="get_consumer_list_by_group",response_code="success",result="success",le="5.0"} 54.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="get_consumer_list_by_group",response_code="success",result="success",le="7.0"} 54.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="get_consumer_list_by_group",response_code="success",result="success",le="10.0"} 55.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="get_consumer_list_by_group",response_code="success",result="success",le="100.0"} 60.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="get_consumer_list_by_group",response_code="success",result="success",le="1000.0"} 60.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="get_consumer_list_by_group",response_code="success",result="success",le="2000.0"} 60.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="get_consumer_list_by_group",response_code="success",result="success",le="3000.0"} 60.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="get_consumer_list_by_group",response_code="success",result="success",le="+Inf"} 60.0 1696905287361
+rocketmq_rpc_latency_milliseconds_count{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="success",result="success"} 4.0 1696905287361
+rocketmq_rpc_latency_milliseconds_sum{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="success",result="success"} 210.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="success",result="success",le="1.0"} 0.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="success",result="success",le="3.0"} 1.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="success",result="success",le="5.0"} 1.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="success",result="success",le="7.0"} 1.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="success",result="success",le="10.0"} 1.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="success",result="success",le="100.0"} 4.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="success",result="success",le="1000.0"} 4.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="success",result="success",le="2000.0"} 4.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="success",result="success",le="3000.0"} 4.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="success",result="success",le="+Inf"} 4.0 1696905287361
+rocketmq_rpc_latency_milliseconds_count{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="true",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="pull_not_found",result="success"} 155.0 1696905287361
+rocketmq_rpc_latency_milliseconds_sum{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="true",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="pull_not_found",result="success"} 2811756.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="true",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="pull_not_found",result="success",le="1.0"} 0.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="true",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="pull_not_found",result="success",le="3.0"} 0.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="true",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="pull_not_found",result="success",le="5.0"} 0.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="true",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="pull_not_found",result="success",le="7.0"} 0.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="true",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="pull_not_found",result="success",le="10.0"} 0.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="true",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="pull_not_found",result="success",le="100.0"} 0.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="true",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="pull_not_found",result="success",le="1000.0"} 0.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="true",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="pull_not_found",result="success",le="2000.0"} 0.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="true",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="pull_not_found",result="success",le="3000.0"} 0.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="true",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="pull_message",response_code="pull_not_found",result="success",le="+Inf"} 155.0 1696905287361
+rocketmq_rpc_latency_milliseconds_count{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="send_message_v2",response_code="success",result="success"} 5.0 1696905287361
+rocketmq_rpc_latency_milliseconds_sum{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="send_message_v2",response_code="success",result="success"} 225.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="send_message_v2",response_code="success",result="success",le="1.0"} 1.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="send_message_v2",response_code="success",result="success",le="3.0"} 2.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="send_message_v2",response_code="success",result="success",le="5.0"} 2.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="send_message_v2",response_code="success",result="success",le="7.0"} 2.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="send_message_v2",response_code="success",result="success",le="10.0"} 4.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="send_message_v2",response_code="success",result="success",le="100.0"} 4.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="send_message_v2",response_code="success",result="success",le="1000.0"} 5.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="send_message_v2",response_code="success",result="success",le="2000.0"} 5.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="send_message_v2",response_code="success",result="success",le="3000.0"} 5.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="send_message_v2",response_code="success",result="success",le="+Inf"} 5.0 1696905287361
+rocketmq_rpc_latency_milliseconds_count{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="update_consumer_offset",response_code="success",result="oneway"} 565.0 1696905287361
+rocketmq_rpc_latency_milliseconds_sum{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="update_consumer_offset",response_code="success",result="oneway"} 84.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="update_consumer_offset",response_code="success",result="oneway",le="1.0"} 551.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="update_consumer_offset",response_code="success",result="oneway",le="3.0"} 557.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="update_consumer_offset",response_code="success",result="oneway",le="5.0"} 558.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="update_consumer_offset",response_code="success",result="oneway",le="7.0"} 560.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="update_consumer_offset",response_code="success",result="oneway",le="10.0"} 565.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="update_consumer_offset",response_code="success",result="oneway",le="100.0"} 565.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="update_consumer_offset",response_code="success",result="oneway",le="1000.0"} 565.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="update_consumer_offset",response_code="success",result="oneway",le="2000.0"} 565.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="update_consumer_offset",response_code="success",result="oneway",le="3000.0"} 565.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="update_consumer_offset",response_code="success",result="oneway",le="+Inf"} 565.0 1696905287361
+rocketmq_rpc_latency_milliseconds_count{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="unregister_client",response_code="success",result="success"} 2.0 1696905287361
+rocketmq_rpc_latency_milliseconds_sum{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="unregister_client",response_code="success",result="success"} 6.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="unregister_client",response_code="success",result="success",le="1.0"} 1.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="unregister_client",response_code="success",result="success",le="3.0"} 1.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="unregister_client",response_code="success",result="success",le="5.0"} 2.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="unregister_client",response_code="success",result="success",le="7.0"} 2.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="unregister_client",response_code="success",result="success",le="10.0"} 2.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="unregister_client",response_code="success",result="success",le="100.0"} 2.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="unregister_client",response_code="success",result="success",le="1000.0"} 2.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="unregister_client",response_code="success",result="success",le="2000.0"} 2.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="unregister_client",response_code="success",result="success",le="3000.0"} 2.0 1696905287361
+rocketmq_rpc_latency_milliseconds_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_long_polling="false",node_id="broker-a",node_type="broker",protocol_type="remoting",request_code="unregister_client",response_code="success",result="success",le="+Inf"} 2.0 1696905287361
+# TYPE rocketmq_storage_dispatch_behind_bytes gauge
+# HELP rocketmq_storage_dispatch_behind_bytes default view for gauge.
+rocketmq_storage_dispatch_behind_bytes{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker",storage_medium="disk",storage_type="local"} 0.0 1696905287361
+# TYPE rocketmq_consumer_ready_messages gauge
+# HELP rocketmq_consumer_ready_messages default view for gauge.
+rocketmq_consumer_ready_messages{otel_scope_name="broker-meter",cluster="DefaultCluster",consumer_group="MQ_INST_test%GID_222",is_retry="false",is_system="false",node_id="broker-a",node_type="broker",topic="MQ_INST_test%zzl"} 0.0 1696905287361
+# TYPE rocketmq_consumer_lag_latency gauge
+# HELP rocketmq_consumer_lag_latency default view for gauge.
+rocketmq_consumer_lag_latency_milliseconds{otel_scope_name="broker-meter",cluster="DefaultCluster",consumer_group="MQ_INST_test%GID_222",is_retry="false",is_system="false",node_id="broker-a",node_type="broker",topic="MQ_INST_test%zzl"} 0.0 1696905287361
+# TYPE rocketmq_throughput_in_total counter
+# HELP rocketmq_throughput_in_total default view for counter.
+rocketmq_throughput_in_total{otel_scope_name="broker-meter",cluster="DefaultCluster",is_system="false",message_type="normal",node_id="broker-a",node_type="broker",topic="MQ_INST_test%zzl"} 1230.0 1696905287361
+# TYPE rocketmq_pop_checkpoint_buffer_size gauge
+# HELP rocketmq_pop_checkpoint_buffer_size default view for gauge.
+rocketmq_pop_checkpoint_buffer_size{otel_scope_name="broker-meter",cluster="DefaultCluster",node_id="broker-a",node_type="broker"} 0.0 1696905287361
+# TYPE rocketmq_message_size histogram
+# HELP rocketmq_message_size Incoming messages size
+rocketmq_message_size_count{otel_scope_name="broker-meter",cluster="DefaultCluster",is_system="false",message_type="normal",node_id="broker-a",node_type="broker",topic="MQ_INST_test%zzl"} 5.0 1696905287361
+rocketmq_message_size_sum{otel_scope_name="broker-meter",cluster="DefaultCluster",is_system="false",message_type="normal",node_id="broker-a",node_type="broker",topic="MQ_INST_test%zzl"} 1230.0 1696905287361
+rocketmq_message_size_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_system="false",message_type="normal",node_id="broker-a",node_type="broker",topic="MQ_INST_test%zzl",le="1024.0"} 5.0 1696905287361
+rocketmq_message_size_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_system="false",message_type="normal",node_id="broker-a",node_type="broker",topic="MQ_INST_test%zzl",le="4096.0"} 5.0 1696905287361
+rocketmq_message_size_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_system="false",message_type="normal",node_id="broker-a",node_type="broker",topic="MQ_INST_test%zzl",le="524288.0"} 5.0 1696905287361
+rocketmq_message_size_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_system="false",message_type="normal",node_id="broker-a",node_type="broker",topic="MQ_INST_test%zzl",le="1048576.0"} 5.0 1696905287361
+rocketmq_message_size_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_system="false",message_type="normal",node_id="broker-a",node_type="broker",topic="MQ_INST_test%zzl",le="2097152.0"} 5.0 1696905287361
+rocketmq_message_size_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_system="false",message_type="normal",node_id="broker-a",node_type="broker",topic="MQ_INST_test%zzl",le="4194304.0"} 5.0 1696905287361
+rocketmq_message_size_bucket{otel_scope_name="broker-meter",cluster="DefaultCluster",is_system="false",message_type="normal",node_id="broker-a",node_type="broker",topic="MQ_INST_test%zzl",le="+Inf"} 5.0 1696905287361
+# TYPE rocketmq_consumer_inflight_messages gauge
+# HELP rocketmq_consumer_inflight_messages default view for gauge.
+rocketmq_consumer_inflight_messages{otel_scope_name="broker-meter",cluster="DefaultCluster",consumer_group="MQ_INST_test%GID_222",is_retry="false",is_system="false",node_id="broker-a",node_type="broker",topic="MQ_INST_test%zzl"} 0.0 1696905287361
+# TYPE rocketmq_producer_connections gauge
+# HELP rocketmq_producer_connections default view for gauge.
+rocketmq_producer_connections{otel_scope_name="broker-meter",cluster="DefaultCluster",language="java",node_id="broker-a",node_type="broker",protocol_type="remoting",version="v4_8_0"} 1.0 1696905287361
diff --git a/patch009-modify-CRLF-to-LF.patch b/patch009-modify-CRLF-to-LF.patch
new file mode 100644
index 000000000..c9f1f1843
--- /dev/null
+++ b/patch009-modify-CRLF-to-LF.patch
@@ -0,0 +1,127 @@
+From 81cd734ee33586544694675bd1081f2c81024d22 Mon Sep 17 00:00:00 2001
+From: zhiliatom <zhiliatox@163.com>
+Date: Thu, 28 Sep 2023 09:58:13 +0800
+Subject: [PATCH] modify line separater from CRLF to LF
+
+---
+ .../rocketmq/common/constant/LoggerName.java | 110 +++++++++---------
+ 1 file changed, 55 insertions(+), 55 deletions(-)
+
+diff --git a/common/src/main/java/org/apache/rocketmq/common/constant/LoggerName.java b/common/src/main/java/org/apache/rocketmq/common/constant/LoggerName.java
+index cb04b00b3..5c7afab9f 100644
+--- a/common/src/main/java/org/apache/rocketmq/common/constant/LoggerName.java
++++ b/common/src/main/java/org/apache/rocketmq/common/constant/LoggerName.java
+@@ -1,55 +1,55 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.rocketmq.common.constant;
+-
+-public class LoggerName {
+- public static final String FILTERSRV_LOGGER_NAME = "RocketmqFiltersrv";
+- public static final String NAMESRV_LOGGER_NAME = "RocketmqNamesrv";
+- public static final String NAMESRV_CONSOLE_LOGGER_NAME = "RocketmqNamesrvConsole";
+- public static final String CONTROLLER_LOGGER_NAME = "RocketmqController";
+- public static final String NAMESRV_WATER_MARK_LOGGER_NAME = "RocketmqNamesrvWaterMark";
+- public static final String BROKER_LOGGER_NAME = "RocketmqBroker";
+- public static final String BROKER_CONSOLE_NAME = "RocketmqConsole";
+- public static final String CLIENT_LOGGER_NAME = "RocketmqClient";
+- public static final String ROCKETMQ_TRAFFIC_NAME = "RocketmqTraffic";
+- public static final String ROCKETMQ_REMOTING_NAME = "RocketmqRemoting";
+- public static final String TOOLS_LOGGER_NAME = "RocketmqTools";
+- public static final String COMMON_LOGGER_NAME = "RocketmqCommon";
+- public static final String STORE_LOGGER_NAME = "RocketmqStore";
+- public static final String STORE_ERROR_LOGGER_NAME = "RocketmqStoreError";
+- public static final String TRANSACTION_LOGGER_NAME = "RocketmqTransaction";
+- public static final String REBALANCE_LOCK_LOGGER_NAME = "RocketmqRebalanceLock";
+- public static final String ROCKETMQ_STATS_LOGGER_NAME = "RocketmqStats";
+- public static final String DLQ_STATS_LOGGER_NAME = "RocketmqDLQStats";
+- public static final String DLQ_LOGGER_NAME = "RocketmqDLQ";
+- public static final String CONSUMER_STATS_LOGGER_NAME = "RocketmqConsumerStats";
+- public static final String COMMERCIAL_LOGGER_NAME = "RocketmqCommercial";
+- public static final String ACCOUNT_LOGGER_NAME = "RocketmqAccount";
+- public static final String FLOW_CONTROL_LOGGER_NAME = "RocketmqFlowControl";
+- public static final String ROCKETMQ_AUTHORIZE_LOGGER_NAME = "RocketmqAuthorize";
+- public static final String DUPLICATION_LOGGER_NAME = "RocketmqDuplication";
+- public static final String PROTECTION_LOGGER_NAME = "RocketmqProtection";
+- public static final String WATER_MARK_LOGGER_NAME = "RocketmqWaterMark";
+- public static final String FILTER_LOGGER_NAME = "RocketmqFilter";
+- public static final String ROCKETMQ_POP_LOGGER_NAME = "RocketmqPop";
+- public static final String FAILOVER_LOGGER_NAME = "RocketmqFailover";
+- public static final String STDOUT_LOGGER_NAME = "STDOUT";
+- public static final String PROXY_LOGGER_NAME = "RocketmqProxy";
+- public static final String PROXY_WATER_MARK_LOGGER_NAME = "RocketmqProxyWatermark";
+- public static final String ROCKETMQ_COLDCTR_LOGGER_NAME = "RocketmqColdCtr";
+- public static final String ROCKSDB_LOGGER_NAME = "RocketmqRocksDB";
+-}
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one or more
++ * contributor license agreements. See the NOTICE file distributed with
++ * this work for additional information regarding copyright ownership.
++ * The ASF licenses this file to You under the Apache License, Version 2.0
++ * (the "License"); you may not use this file except in compliance with
++ * the License. You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++package org.apache.rocketmq.common.constant;
++
++public class LoggerName {
++ public static final String FILTERSRV_LOGGER_NAME = "RocketmqFiltersrv";
++ public static final String NAMESRV_LOGGER_NAME = "RocketmqNamesrv";
++ public static final String NAMESRV_CONSOLE_LOGGER_NAME = "RocketmqNamesrvConsole";
++ public static final String CONTROLLER_LOGGER_NAME = "RocketmqController";
++ public static final String NAMESRV_WATER_MARK_LOGGER_NAME = "RocketmqNamesrvWaterMark";
++ public static final String BROKER_LOGGER_NAME = "RocketmqBroker";
++ public static final String BROKER_CONSOLE_NAME = "RocketmqConsole";
++ public static final String CLIENT_LOGGER_NAME = "RocketmqClient";
++ public static final String ROCKETMQ_TRAFFIC_NAME = "RocketmqTraffic";
++ public static final String ROCKETMQ_REMOTING_NAME = "RocketmqRemoting";
++ public static final String TOOLS_LOGGER_NAME = "RocketmqTools";
++ public static final String COMMON_LOGGER_NAME = "RocketmqCommon";
++ public static final String STORE_LOGGER_NAME = "RocketmqStore";
++ public static final String STORE_ERROR_LOGGER_NAME = "RocketmqStoreError";
++ public static final String TRANSACTION_LOGGER_NAME = "RocketmqTransaction";
++ public static final String REBALANCE_LOCK_LOGGER_NAME = "RocketmqRebalanceLock";
++ public static final String ROCKETMQ_STATS_LOGGER_NAME = "RocketmqStats";
++ public static final String DLQ_STATS_LOGGER_NAME = "RocketmqDLQStats";
++ public static final String DLQ_LOGGER_NAME = "RocketmqDLQ";
++ public static final String CONSUMER_STATS_LOGGER_NAME = "RocketmqConsumerStats";
++ public static final String COMMERCIAL_LOGGER_NAME = "RocketmqCommercial";
++ public static final String ACCOUNT_LOGGER_NAME = "RocketmqAccount";
++ public static final String FLOW_CONTROL_LOGGER_NAME = "RocketmqFlowControl";
++ public static final String ROCKETMQ_AUTHORIZE_LOGGER_NAME = "RocketmqAuthorize";
++ public static final String DUPLICATION_LOGGER_NAME = "RocketmqDuplication";
++ public static final String PROTECTION_LOGGER_NAME = "RocketmqProtection";
++ public static final String WATER_MARK_LOGGER_NAME = "RocketmqWaterMark";
++ public static final String FILTER_LOGGER_NAME = "RocketmqFilter";
++ public static final String ROCKETMQ_POP_LOGGER_NAME = "RocketmqPop";
++ public static final String FAILOVER_LOGGER_NAME = "RocketmqFailover";
++ public static final String STDOUT_LOGGER_NAME = "STDOUT";
++ public static final String PROXY_LOGGER_NAME = "RocketmqProxy";
++ public static final String PROXY_WATER_MARK_LOGGER_NAME = "RocketmqProxyWatermark";
++ public static final String ROCKETMQ_COLDCTR_LOGGER_NAME = "RocketmqColdCtr";
++ public static final String ROCKSDB_LOGGER_NAME = "RocketmqRocksDB";
++}
+--
+2.32.0.windows.2
+
diff --git a/patch010-backport-add-some-fixes b/patch010-backport-add-some-fixes
new file mode 100644
index 000000000..36d5e4a1f
--- /dev/null
+++ b/patch010-backport-add-some-fixes
@@ -0,0 +1,1286 @@
+From b2deef179dbc6a9eb1a2b6dd7b652d95cb768295 Mon Sep 17 00:00:00 2001
+From: lizhimins <707364882@qq.com>
+Date: Thu, 10 Aug 2023 10:38:47 +0800
+Subject: [PATCH 01/12] [ISSUE #7144] Accelerate the recovery speed of the
+ tiered storage module (#7145)
+
+---
+ .../tieredstore/TieredDispatcher.java | 3 +
+ .../tieredstore/TieredMessageStore.java | 2 +-
+ .../common/TieredStoreExecutor.java | 25 ++--
+ .../tieredstore/file/CompositeFlatFile.java | 15 +-
+ .../file/CompositeQueueFlatFile.java | 20 ++-
+ .../tieredstore/file/TieredCommitLog.java | 24 +++-
+ .../tieredstore/file/TieredFlatFile.java | 42 +++---
+ .../file/TieredFlatFileManager.java | 135 ++++++++++--------
+ .../metadata/FileSegmentMetadata.java | 26 +++-
+ .../tieredstore/TieredDispatcherTest.java | 15 +-
+ .../tieredstore/TieredMessageFetcherTest.java | 2 +-
+ .../file/CompositeQueueFlatFileTest.java | 2 +-
+ .../file/TieredFlatFileManagerTest.java | 7 +-
+ 13 files changed, 194 insertions(+), 124 deletions(-)
+
+diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredDispatcher.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredDispatcher.java
+index bb58ea7dd..1746190cd 100644
+--- a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredDispatcher.java
++++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredDispatcher.java
+@@ -279,6 +279,9 @@ public class TieredDispatcher extends ServiceThread implements CommitLogDispatch
+ long upperBound = Math.min(dispatchOffset + maxCount, maxOffsetInQueue);
+ ConsumeQueue consumeQueue = (ConsumeQueue) defaultStore.getConsumeQueue(topic, queueId);
+
++ logger.debug("DispatchFlatFile race, topic={}, queueId={}, cq range={}-{}, dispatch offset={}-{}",
++ topic, queueId, minOffsetInQueue, maxOffsetInQueue, dispatchOffset, upperBound - 1);
++
+ for (; dispatchOffset < upperBound; dispatchOffset++) {
+ // get consume queue
+ SelectMappedBufferResult cqItem = consumeQueue.getIndexBuffer(dispatchOffset);
+diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredMessageStore.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredMessageStore.java
+index 1f12410f2..ced1fb818 100644
+--- a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredMessageStore.java
++++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredMessageStore.java
+@@ -147,7 +147,7 @@ public class TieredMessageStore extends AbstractPluginMessageStore {
+ int queueId, long offset, int maxMsgNums, MessageFilter messageFilter) {
+
+ if (!viaTieredStorage(topic, queueId, offset, maxMsgNums)) {
+- logger.debug("GetMessageAsync from next store topic: {}, queue: {}, offset: {}", topic, queueId, offset);
++ logger.trace("GetMessageAsync from next store topic: {}, queue: {}, offset: {}", topic, queueId, offset);
+ return next.getMessageAsync(group, topic, queueId, offset, maxMsgNums, messageFilter);
+ }
+
+diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/common/TieredStoreExecutor.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/common/TieredStoreExecutor.java
+index 6eb3478b3..6dd0e8846 100644
+--- a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/common/TieredStoreExecutor.java
++++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/common/TieredStoreExecutor.java
+@@ -43,18 +43,9 @@ public class TieredStoreExecutor {
+ public static ExecutorService compactIndexFileExecutor;
+
+ public static void init() {
+- dispatchThreadPoolQueue = new LinkedBlockingQueue<>(QUEUE_CAPACITY);
+- dispatchExecutor = new ThreadPoolExecutor(
+- Math.max(2, Runtime.getRuntime().availableProcessors()),
+- Math.max(16, Runtime.getRuntime().availableProcessors() * 4),
+- 1000 * 60,
+- TimeUnit.MILLISECONDS,
+- dispatchThreadPoolQueue,
+- new ThreadFactoryImpl("TieredCommonExecutor_"));
+-
+ commonScheduledExecutor = new ScheduledThreadPoolExecutor(
+ Math.max(4, Runtime.getRuntime().availableProcessors()),
+- new ThreadFactoryImpl("TieredCommonScheduledExecutor_"));
++ new ThreadFactoryImpl("TieredCommonExecutor_"));
+
+ commitExecutor = new ScheduledThreadPoolExecutor(
+ Math.max(16, Runtime.getRuntime().availableProcessors() * 4),
+@@ -62,7 +53,17 @@ public class TieredStoreExecutor {
+
+ cleanExpiredFileExecutor = new ScheduledThreadPoolExecutor(
+ Math.max(4, Runtime.getRuntime().availableProcessors()),
+- new ThreadFactoryImpl("TieredCleanExpiredFileExecutor_"));
++ new ThreadFactoryImpl("TieredCleanFileExecutor_"));
++
++ dispatchThreadPoolQueue = new LinkedBlockingQueue<>(QUEUE_CAPACITY);
++ dispatchExecutor = new ThreadPoolExecutor(
++ Math.max(2, Runtime.getRuntime().availableProcessors()),
++ Math.max(16, Runtime.getRuntime().availableProcessors() * 4),
++ 1000 * 60,
++ TimeUnit.MILLISECONDS,
++ dispatchThreadPoolQueue,
++ new ThreadFactoryImpl("TieredDispatchExecutor_"),
++ new ThreadPoolExecutor.DiscardOldestPolicy());
+
+ fetchDataThreadPoolQueue = new LinkedBlockingQueue<>(QUEUE_CAPACITY);
+ fetchDataExecutor = new ThreadPoolExecutor(
+@@ -71,7 +72,7 @@ public class TieredStoreExecutor {
+ 1000 * 60,
+ TimeUnit.MILLISECONDS,
+ fetchDataThreadPoolQueue,
+- new ThreadFactoryImpl("TieredFetchDataExecutor_"));
++ new ThreadFactoryImpl("TieredFetchExecutor_"));
+
+ compactIndexFileThreadPoolQueue = new LinkedBlockingQueue<>(QUEUE_CAPACITY);
+ compactIndexFileExecutor = new ThreadPoolExecutor(
+diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/CompositeFlatFile.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/CompositeFlatFile.java
+index df4baf33f..5ad3a6ff3 100644
+--- a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/CompositeFlatFile.java
++++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/CompositeFlatFile.java
+@@ -76,20 +76,15 @@ public class CompositeFlatFile implements CompositeAccess {
+ this.storeConfig = fileQueueFactory.getStoreConfig();
+ this.readAheadFactor = this.storeConfig.getReadAheadMinFactor();
+ this.metadataStore = TieredStoreUtil.getMetadataStore(this.storeConfig);
+- this.dispatchOffset = new AtomicLong();
+ this.compositeFlatFileLock = new ReentrantLock();
+ this.inFlightRequestMap = new ConcurrentHashMap<>();
+ this.commitLog = new TieredCommitLog(fileQueueFactory, filePath);
+ this.consumeQueue = new TieredConsumeQueue(fileQueueFactory, filePath);
++ this.dispatchOffset = new AtomicLong(
++ this.consumeQueue.isInitialized() ? this.getConsumeQueueCommitOffset() : -1L);
+ this.groupOffsetCache = this.initOffsetCache();
+ }
+
+- protected void recoverMetadata() {
+- if (!consumeQueue.isInitialized() && this.dispatchOffset.get() != -1) {
+- consumeQueue.setBaseOffset(this.dispatchOffset.get() * TieredConsumeQueue.CONSUME_QUEUE_STORE_UNIT_SIZE);
+- }
+- }
+-
+ private Cache<String, Long> initOffsetCache() {
+ return Caffeine.newBuilder()
+ .expireAfterWrite(2, TimeUnit.MINUTES)
+@@ -310,10 +305,12 @@ public class CompositeFlatFile implements CompositeAccess {
+
+ @Override
+ public void initOffset(long offset) {
+- if (!consumeQueue.isInitialized()) {
++ if (consumeQueue.isInitialized()) {
++ dispatchOffset.set(this.getConsumeQueueCommitOffset());
++ } else {
+ consumeQueue.setBaseOffset(offset * TieredConsumeQueue.CONSUME_QUEUE_STORE_UNIT_SIZE);
++ dispatchOffset.set(offset);
+ }
+- dispatchOffset.set(offset);
+ }
+
+ @Override
+diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/CompositeQueueFlatFile.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/CompositeQueueFlatFile.java
+index f6c0afed0..0a797f465 100644
+--- a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/CompositeQueueFlatFile.java
++++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/CompositeQueueFlatFile.java
+@@ -36,8 +36,7 @@ public class CompositeQueueFlatFile extends CompositeFlatFile {
+ public CompositeQueueFlatFile(TieredFileAllocator fileQueueFactory, MessageQueue messageQueue) {
+ super(fileQueueFactory, TieredStoreUtil.toPath(messageQueue));
+ this.messageQueue = messageQueue;
+- this.recoverTopicMetadata();
+- super.recoverMetadata();
++ this.recoverQueueMetadata();
+ this.indexFile = TieredFlatFileManager.getIndexFile(storeConfig);
+ }
+
+@@ -46,11 +45,12 @@ public class CompositeQueueFlatFile extends CompositeFlatFile {
+ if (!consumeQueue.isInitialized()) {
+ queueMetadata.setMinOffset(offset);
+ queueMetadata.setMaxOffset(offset);
++ metadataStore.updateQueue(queueMetadata);
+ }
+ super.initOffset(offset);
+ }
+
+- public void recoverTopicMetadata() {
++ public void recoverQueueMetadata() {
+ TopicMetadata topicMetadata = this.metadataStore.getTopic(messageQueue.getTopic());
+ if (topicMetadata == null) {
+ topicMetadata = this.metadataStore.addTopic(messageQueue.getTopic(), -1L);
+@@ -64,18 +64,16 @@ public class CompositeQueueFlatFile extends CompositeFlatFile {
+ if (queueMetadata.getMaxOffset() < queueMetadata.getMinOffset()) {
+ queueMetadata.setMaxOffset(queueMetadata.getMinOffset());
+ }
+- this.dispatchOffset.set(queueMetadata.getMaxOffset());
+ }
+
+- public void persistMetadata() {
++ public void flushMetadata() {
+ try {
+- if (consumeQueue.getCommitOffset() < queueMetadata.getMinOffset()) {
+- return;
+- }
+- queueMetadata.setMaxOffset(consumeQueue.getCommitOffset() / TieredConsumeQueue.CONSUME_QUEUE_STORE_UNIT_SIZE);
++ queueMetadata.setMinOffset(super.getConsumeQueueMinOffset());
++ queueMetadata.setMaxOffset(super.getConsumeQueueMaxOffset());
+ metadataStore.updateQueue(queueMetadata);
+ } catch (Exception e) {
+- LOGGER.error("CompositeFlatFile#flushMetadata: update queue metadata failed: topic: {}, queue: {}", messageQueue.getTopic(), messageQueue.getQueueId(), e);
++ LOGGER.error("CompositeFlatFile#flushMetadata error, topic: {}, queue: {}",
++ messageQueue.getTopic(), messageQueue.getQueueId(), e);
+ }
+ }
+
+@@ -114,7 +112,7 @@ public class CompositeQueueFlatFile extends CompositeFlatFile {
+ @Override
+ public void shutdown() {
+ super.shutdown();
+- metadataStore.updateQueue(queueMetadata);
++ this.flushMetadata();
+ }
+
+ @Override
+diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredCommitLog.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredCommitLog.java
+index 80e1bce50..0e5f79132 100644
+--- a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredCommitLog.java
++++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredCommitLog.java
+@@ -50,7 +50,7 @@ public class TieredCommitLog {
+ this.storeConfig = fileQueueFactory.getStoreConfig();
+ this.flatFile = fileQueueFactory.createFlatFileForCommitLog(filePath);
+ this.minConsumeQueueOffset = new AtomicLong(NOT_EXIST_MIN_OFFSET);
+- this.correctMinOffset();
++ this.correctMinOffsetAsync();
+ }
+
+ @VisibleForTesting
+@@ -91,17 +91,26 @@ public class TieredCommitLog {
+ return flatFile.getFileToWrite().getMaxTimestamp();
+ }
+
+- public synchronized long correctMinOffset() {
++ public long correctMinOffset() {
++ try {
++ return correctMinOffsetAsync().get();
++ } catch (Exception e) {
++ log.error("Correct min offset failed in clean expired file", e);
++ }
++ return NOT_EXIST_MIN_OFFSET;
++ }
++
++ public synchronized CompletableFuture<Long> correctMinOffsetAsync() {
+ if (flatFile.getFileSegmentCount() == 0) {
+ this.minConsumeQueueOffset.set(NOT_EXIST_MIN_OFFSET);
+- return NOT_EXIST_MIN_OFFSET;
++ return CompletableFuture.completedFuture(NOT_EXIST_MIN_OFFSET);
+ }
+
+ // queue offset field length is 8
+ int length = MessageBufferUtil.QUEUE_OFFSET_POSITION + 8;
+ if (flatFile.getCommitOffset() - flatFile.getMinOffset() < length) {
+ this.minConsumeQueueOffset.set(NOT_EXIST_MIN_OFFSET);
+- return NOT_EXIST_MIN_OFFSET;
++ return CompletableFuture.completedFuture(NOT_EXIST_MIN_OFFSET);
+ }
+
+ try {
+@@ -109,7 +118,8 @@ public class TieredCommitLog {
+ .thenApply(buffer -> {
+ long offset = MessageBufferUtil.getQueueOffset(buffer);
+ minConsumeQueueOffset.set(offset);
+- log.info("Correct commitlog min cq offset success, filePath={}, min cq offset={}, range={}-{}",
++ log.debug("Correct commitlog min cq offset success, " +
++ "filePath={}, min cq offset={}, commitlog range={}-{}",
+ flatFile.getFilePath(), offset, flatFile.getMinOffset(), flatFile.getCommitOffset());
+ return offset;
+ })
+@@ -117,11 +127,11 @@ public class TieredCommitLog {
+ log.warn("Correct commitlog min cq offset error, filePath={}, range={}-{}",
+ flatFile.getFilePath(), flatFile.getMinOffset(), flatFile.getCommitOffset(), throwable);
+ return minConsumeQueueOffset.get();
+- }).get();
++ });
+ } catch (Exception e) {
+ log.error("Correct commitlog min cq offset error, filePath={}", flatFile.getFilePath(), e);
+ }
+- return minConsumeQueueOffset.get();
++ return CompletableFuture.completedFuture(minConsumeQueueOffset.get());
+ }
+
+ public AppendResult append(ByteBuffer byteBuf) {
+diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredFlatFile.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredFlatFile.java
+index 75ce8d89f..426c4e09d 100644
+--- a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredFlatFile.java
++++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredFlatFile.java
+@@ -16,6 +16,7 @@
+ */
+ package org.apache.rocketmq.tieredstore.file;
+
++import com.alibaba.fastjson.JSON;
+ import com.google.common.annotations.VisibleForTesting;
+ import java.nio.ByteBuffer;
+ import java.util.ArrayList;
+@@ -24,6 +25,7 @@ import java.util.Comparator;
+ import java.util.HashSet;
+ import java.util.LinkedList;
+ import java.util.List;
++import java.util.Objects;
+ import java.util.Set;
+ import java.util.concurrent.CompletableFuture;
+ import java.util.concurrent.CopyOnWriteArrayList;
+@@ -178,32 +180,26 @@ public class TieredFlatFile {
+ private FileSegmentMetadata getOrCreateFileSegmentMetadata(TieredFileSegment fileSegment) {
+
+ FileSegmentMetadata metadata = tieredMetadataStore.getFileSegment(
+- fileSegment.getPath(), fileSegment.getFileType(), fileSegment.getBaseOffset());
+-
+- if (metadata != null) {
+- return metadata;
+- }
++ this.filePath, fileSegment.getFileType(), fileSegment.getBaseOffset());
+
+ // Note: file segment path may not the same as file base path, use base path here.
+- metadata = new FileSegmentMetadata(
+- this.filePath, fileSegment.getBaseOffset(), fileSegment.getFileType().getType());
+-
+- if (fileSegment.isClosed()) {
+- metadata.setStatus(FileSegmentMetadata.STATUS_DELETED);
++ if (metadata == null) {
++ metadata = new FileSegmentMetadata(
++ this.filePath, fileSegment.getBaseOffset(), fileSegment.getFileType().getType());
++ metadata.setCreateTimestamp(fileSegment.getMinTimestamp());
++ metadata.setBeginTimestamp(fileSegment.getMinTimestamp());
++ metadata.setEndTimestamp(fileSegment.getMaxTimestamp());
++ if (fileSegment.isClosed()) {
++ metadata.setStatus(FileSegmentMetadata.STATUS_DELETED);
++ }
++ this.tieredMetadataStore.updateFileSegment(metadata);
+ }
+-
+- metadata.setBeginTimestamp(fileSegment.getMinTimestamp());
+- metadata.setEndTimestamp(fileSegment.getMaxTimestamp());
+-
+- // Submit to persist
+- this.tieredMetadataStore.updateFileSegment(metadata);
+ return metadata;
+ }
+
+ /**
+ * FileQueue Status: Sealed | Sealed | Sealed | Not sealed, Allow appended && Not Full
+ */
+- @VisibleForTesting
+ public void updateFileSegment(TieredFileSegment fileSegment) {
+ FileSegmentMetadata segmentMetadata = getOrCreateFileSegmentMetadata(fileSegment);
+
+@@ -219,9 +215,16 @@ public class TieredFlatFile {
+ }
+
+ segmentMetadata.setSize(fileSegment.getCommitPosition());
+- segmentMetadata.setBeginTimestamp(fileSegment.getMinTimestamp());
+ segmentMetadata.setEndTimestamp(fileSegment.getMaxTimestamp());
+- this.tieredMetadataStore.updateFileSegment(segmentMetadata);
++
++ FileSegmentMetadata metadata = tieredMetadataStore.getFileSegment(
++ this.filePath, fileSegment.getFileType(), fileSegment.getBaseOffset());
++
++ if (!Objects.equals(metadata, segmentMetadata)) {
++ this.tieredMetadataStore.updateFileSegment(segmentMetadata);
++ logger.debug("TieredFlatFile#UpdateSegmentMetadata, filePath: {}, content: {}",
++ segmentMetadata.getPath(), JSON.toJSONString(segmentMetadata));
++ }
+ }
+
+ private void checkAndFixFileSize() {
+@@ -257,6 +260,7 @@ public class TieredFlatFile {
+ logger.warn("TieredFlatFile#checkAndFixFileSize: fix last file {} size: origin: {}, actual: {}",
+ lastFile.getPath(), lastFile.getCommitOffset() - lastFile.getBaseOffset(), lastFileSize);
+ lastFile.initPosition(lastFileSize);
++ this.updateFileSegment(lastFile);
+ }
+ }
+ }
+diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredFlatFileManager.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredFlatFileManager.java
+index aeca44b8c..e9ae4a5a5 100644
+--- a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredFlatFileManager.java
++++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredFlatFileManager.java
+@@ -16,16 +16,19 @@
+ */
+ package org.apache.rocketmq.tieredstore.file;
+
++import com.google.common.base.Stopwatch;
+ import com.google.common.collect.ImmutableList;
+ import java.util.ArrayList;
+ import java.util.List;
+ import java.util.Random;
++import java.util.concurrent.CompletableFuture;
+ import java.util.concurrent.ConcurrentHashMap;
+ import java.util.concurrent.ConcurrentMap;
+-import java.util.concurrent.Future;
++import java.util.concurrent.Semaphore;
+ import java.util.concurrent.TimeUnit;
+ import java.util.concurrent.atomic.AtomicLong;
+ import javax.annotation.Nullable;
++import org.apache.rocketmq.common.constant.LoggerName;
+ import org.apache.rocketmq.common.message.MessageQueue;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+@@ -36,6 +39,7 @@ import org.apache.rocketmq.tieredstore.util.TieredStoreUtil;
+
+ public class TieredFlatFileManager {
+
++ private static final Logger BROKER_LOG = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME);
+ private static final Logger logger = LoggerFactory.getLogger(TieredStoreUtil.TIERED_STORE_LOGGER_NAME);
+
+ private static volatile TieredFlatFileManager instance;
+@@ -44,7 +48,7 @@ public class TieredFlatFileManager {
+ private final TieredMetadataStore metadataStore;
+ private final TieredMessageStoreConfig storeConfig;
+ private final TieredFileAllocator tieredFileAllocator;
+- private final ConcurrentMap<MessageQueue, CompositeQueueFlatFile> queueFlatFileMap;
++ private final ConcurrentMap<MessageQueue, CompositeQueueFlatFile> flatFileConcurrentMap;
+
+ public TieredFlatFileManager(TieredMessageStoreConfig storeConfig)
+ throws ClassNotFoundException, NoSuchMethodException {
+@@ -52,23 +56,20 @@ public class TieredFlatFileManager {
+ this.storeConfig = storeConfig;
+ this.metadataStore = TieredStoreUtil.getMetadataStore(storeConfig);
+ this.tieredFileAllocator = new TieredFileAllocator(storeConfig);
+- this.queueFlatFileMap = new ConcurrentHashMap<>();
++ this.flatFileConcurrentMap = new ConcurrentHashMap<>();
+ this.doScheduleTask();
+ }
+
+ public static TieredFlatFileManager getInstance(TieredMessageStoreConfig storeConfig) {
+- if (storeConfig == null) {
++ if (storeConfig == null || instance != null) {
+ return instance;
+ }
+-
+- if (instance == null) {
+- synchronized (TieredFlatFileManager.class) {
+- if (instance == null) {
+- try {
+- instance = new TieredFlatFileManager(storeConfig);
+- } catch (Exception e) {
+- logger.error("TieredFlatFileManager#getInstance: create flat file manager failed", e);
+- }
++ synchronized (TieredFlatFileManager.class) {
++ if (instance == null) {
++ try {
++ instance = new TieredFlatFileManager(storeConfig);
++ } catch (Exception e) {
++ logger.error("Construct FlatFileManager instance error", e);
+ }
+ }
+ }
+@@ -88,7 +89,7 @@ public class TieredFlatFileManager {
+ TieredStoreUtil.RMQ_SYS_TIERED_STORE_INDEX_TOPIC, storeConfig.getBrokerName(), 0));
+ indexFile = new TieredIndexFile(new TieredFileAllocator(storeConfig), filePath);
+ } catch (Exception e) {
+- logger.error("TieredFlatFileManager#getIndexFile: create index file failed", e);
++ logger.error("Construct FlatFileManager indexFile error", e);
+ }
+ }
+ }
+@@ -105,7 +106,7 @@ public class TieredFlatFileManager {
+ flatFile.commitCommitLog();
+ } catch (Throwable e) {
+ MessageQueue mq = flatFile.getMessageQueue();
+- logger.error("commit commitLog periodically failed: topic: {}, queue: {}",
++ logger.error("Commit commitLog periodically failed: topic: {}, queue: {}",
+ mq.getTopic(), mq.getQueueId(), e);
+ }
+ }, delay, TimeUnit.MILLISECONDS);
+@@ -114,7 +115,7 @@ public class TieredFlatFileManager {
+ flatFile.commitConsumeQueue();
+ } catch (Throwable e) {
+ MessageQueue mq = flatFile.getMessageQueue();
+- logger.error("commit consumeQueue periodically failed: topic: {}, queue: {}",
++ logger.error("Commit consumeQueue periodically failed: topic: {}, queue: {}",
+ mq.getTopic(), mq.getQueueId(), e);
+ }
+ }, delay, TimeUnit.MILLISECONDS);
+@@ -125,7 +126,7 @@ public class TieredFlatFileManager {
+ indexFile.commit(true);
+ }
+ } catch (Throwable e) {
+- logger.error("commit indexFile periodically failed", e);
++ logger.error("Commit indexFile periodically failed", e);
+ }
+ }, 0, TimeUnit.MILLISECONDS);
+ }
+@@ -160,7 +161,7 @@ public class TieredFlatFileManager {
+ try {
+ doCommit();
+ } catch (Throwable e) {
+- logger.error("commit flat file periodically failed: ", e);
++ logger.error("Commit flat file periodically failed: ", e);
+ }
+ }, 60, 60, TimeUnit.SECONDS);
+
+@@ -168,49 +169,73 @@ public class TieredFlatFileManager {
+ try {
+ doCleanExpiredFile();
+ } catch (Throwable e) {
+- logger.error("clean expired flat file failed: ", e);
++ logger.error("Clean expired flat file failed: ", e);
+ }
+ }, 30, 30, TimeUnit.SECONDS);
+ }
+
+ public boolean load() {
++ Stopwatch stopwatch = Stopwatch.createStarted();
+ try {
+- AtomicLong topicSequenceNumber = new AtomicLong();
+- List<Future<?>> futureList = new ArrayList<>();
+- queueFlatFileMap.clear();
+- metadataStore.iterateTopic(topicMetadata -> {
++ flatFileConcurrentMap.clear();
++ this.recoverSequenceNumber();
++ this.recoverTieredFlatFile();
++ logger.info("Message store recover end, total cost={}ms", stopwatch.elapsed(TimeUnit.MILLISECONDS));
++ } catch (Exception e) {
++ long costTime = stopwatch.elapsed(TimeUnit.MILLISECONDS);
++ logger.info("Message store recover error, total cost={}ms", costTime);
++ BROKER_LOG.error("Message store recover error, total cost={}ms", costTime, e);
++ return false;
++ }
++ return true;
++ }
++
++ public void recoverSequenceNumber() {
++ AtomicLong topicSequenceNumber = new AtomicLong();
++ metadataStore.iterateTopic(topicMetadata -> {
++ if (topicMetadata != null && topicMetadata.getTopicId() > 0) {
+ topicSequenceNumber.set(Math.max(topicSequenceNumber.get(), topicMetadata.getTopicId()));
+- Future<?> future = TieredStoreExecutor.dispatchExecutor.submit(() -> {
+- if (topicMetadata.getStatus() != 0) {
+- return;
+- }
++ }
++ });
++ metadataStore.setTopicSequenceNumber(topicSequenceNumber.incrementAndGet());
++ }
++
++ public void recoverTieredFlatFile() {
++ Semaphore semaphore = new Semaphore((int) (TieredStoreExecutor.QUEUE_CAPACITY * 0.75));
++ List<CompletableFuture<Void>> futures = new ArrayList<>();
++ metadataStore.iterateTopic(topicMetadata -> {
++ try {
++ semaphore.acquire();
++ CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
+ try {
+- metadataStore.iterateQueue(topicMetadata.getTopic(),
+- queueMetadata -> getOrCreateFlatFileIfAbsent(
+- new MessageQueue(topicMetadata.getTopic(),
+- storeConfig.getBrokerName(),
+- queueMetadata.getQueue().getQueueId())));
++ Stopwatch subWatch = Stopwatch.createStarted();
++ if (topicMetadata.getStatus() != 0) {
++ return;
++ }
++ AtomicLong queueCount = new AtomicLong();
++ metadataStore.iterateQueue(topicMetadata.getTopic(), queueMetadata -> {
++ this.getOrCreateFlatFileIfAbsent(new MessageQueue(topicMetadata.getTopic(),
++ storeConfig.getBrokerName(), queueMetadata.getQueue().getQueueId()));
++ queueCount.incrementAndGet();
++ });
++ logger.info("Recover TopicFlatFile, topic: {}, queueCount: {}, cost: {}ms",
++ topicMetadata.getTopic(), queueCount.get(), subWatch.elapsed(TimeUnit.MILLISECONDS));
+ } catch (Exception e) {
+- logger.error("load mq composite flat file from metadata failed", e);
++ logger.error("Recover TopicFlatFile error, topic: {}", topicMetadata.getTopic(), e);
++ } finally {
++ semaphore.release();
+ }
+- });
+- futureList.add(future);
+- });
+-
+- // Wait for load all metadata done
+- for (Future<?> future : futureList) {
+- future.get();
++ }, TieredStoreExecutor.commitExecutor);
++ futures.add(future);
++ } catch (Exception e) {
++ throw new RuntimeException(e);
+ }
+- metadataStore.setTopicSequenceNumber(topicSequenceNumber.incrementAndGet());
+- } catch (Exception e) {
+- logger.error("load mq composite flat file from metadata failed", e);
+- return false;
+- }
+- return true;
++ });
++ CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
+ }
+
+ public void cleanup() {
+- queueFlatFileMap.clear();
++ flatFileConcurrentMap.clear();
+ cleanStaticReference();
+ }
+
+@@ -221,27 +246,25 @@ public class TieredFlatFileManager {
+
+ @Nullable
+ public CompositeQueueFlatFile getOrCreateFlatFileIfAbsent(MessageQueue messageQueue) {
+- return queueFlatFileMap.computeIfAbsent(messageQueue, mq -> {
++ return flatFileConcurrentMap.computeIfAbsent(messageQueue, mq -> {
+ try {
+- logger.debug("TieredFlatFileManager#getOrCreateFlatFileIfAbsent: " +
+- "try to create new flat file: topic: {}, queueId: {}",
++ logger.debug("Create new TopicFlatFile, topic: {}, queueId: {}",
+ messageQueue.getTopic(), messageQueue.getQueueId());
+ return new CompositeQueueFlatFile(tieredFileAllocator, mq);
+ } catch (Exception e) {
+- logger.error("TieredFlatFileManager#getOrCreateFlatFileIfAbsent: " +
+- "create new flat file: topic: {}, queueId: {}",
++ logger.debug("Create new TopicFlatFile failed, topic: {}, queueId: {}",
+ messageQueue.getTopic(), messageQueue.getQueueId(), e);
+- return null;
+ }
++ return null;
+ });
+ }
+
+ public CompositeQueueFlatFile getFlatFile(MessageQueue messageQueue) {
+- return queueFlatFileMap.get(messageQueue);
++ return flatFileConcurrentMap.get(messageQueue);
+ }
+
+ public ImmutableList<CompositeQueueFlatFile> deepCopyFlatFileToList() {
+- return ImmutableList.copyOf(queueFlatFileMap.values());
++ return ImmutableList.copyOf(flatFileConcurrentMap.values());
+ }
+
+ public void shutdown() {
+@@ -270,7 +293,7 @@ public class TieredFlatFileManager {
+ }
+
+ // delete memory reference
+- CompositeQueueFlatFile flatFile = queueFlatFileMap.remove(mq);
++ CompositeQueueFlatFile flatFile = flatFileConcurrentMap.remove(mq);
+ if (flatFile != null) {
+ MessageQueue messageQueue = flatFile.getMessageQueue();
+ logger.info("TieredFlatFileManager#destroyCompositeFile: " +
+diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/metadata/FileSegmentMetadata.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/metadata/FileSegmentMetadata.java
+index 1b232fc75..2f0fd71de 100644
+--- a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/metadata/FileSegmentMetadata.java
++++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/metadata/FileSegmentMetadata.java
+@@ -16,6 +16,8 @@
+ */
+ package org.apache.rocketmq.tieredstore.metadata;
+
++import java.util.Objects;
++
+ public class FileSegmentMetadata {
+
+ public static final int STATUS_NEW = 0;
+@@ -43,7 +45,6 @@ public class FileSegmentMetadata {
+ this.baseOffset = baseOffset;
+ this.type = type;
+ this.status = STATUS_NEW;
+- this.createTimestamp = System.currentTimeMillis();
+ }
+
+ public void markSealed() {
+@@ -122,4 +123,27 @@ public class FileSegmentMetadata {
+ public void setSealTimestamp(long sealTimestamp) {
+ this.sealTimestamp = sealTimestamp;
+ }
++
++ @Override
++ public boolean equals(Object o) {
++ if (this == o)
++ return true;
++ if (o == null || getClass() != o.getClass())
++ return false;
++ FileSegmentMetadata metadata = (FileSegmentMetadata) o;
++ return size == metadata.size
++ && baseOffset == metadata.baseOffset
++ && status == metadata.status
++ && path.equals(metadata.path)
++ && type == metadata.type
++ && createTimestamp == metadata.createTimestamp
++ && beginTimestamp == metadata.beginTimestamp
++ && endTimestamp == metadata.endTimestamp
++ && sealTimestamp == metadata.sealTimestamp;
++ }
++
++ @Override
++ public int hashCode() {
++ return Objects.hash(type, path, baseOffset, status, size, createTimestamp, beginTimestamp, endTimestamp, sealTimestamp);
++ }
+ }
+diff --git a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/TieredDispatcherTest.java b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/TieredDispatcherTest.java
+index e6adef1d1..5791dc9a4 100644
+--- a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/TieredDispatcherTest.java
++++ b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/TieredDispatcherTest.java
+@@ -116,19 +116,20 @@ public class TieredDispatcherTest {
+ buffer3.putLong(MessageBufferUtil.QUEUE_OFFSET_POSITION, 9);
+ flatFile.appendCommitLog(buffer3);
+ flatFile.commitCommitLog();
+- Assert.assertEquals(10, flatFile.getDispatchOffset());
++ Assert.assertEquals(9 + 1, flatFile.getDispatchOffset());
++ Assert.assertEquals(9, flatFile.getCommitLogDispatchCommitOffset());
+
+ dispatcher.doRedispatchRequestToWriteMap(AppendResult.SUCCESS, flatFile, 8, 8, 0, 0, buffer1);
+ dispatcher.doRedispatchRequestToWriteMap(AppendResult.SUCCESS, flatFile, 9, 9, 0, 0, buffer2);
+ dispatcher.buildConsumeQueueAndIndexFile();
+ Assert.assertEquals(7, flatFile.getConsumeQueueMaxOffset());
+- Assert.assertEquals(7, flatFile.getDispatchOffset());
+
+ dispatcher.doRedispatchRequestToWriteMap(AppendResult.SUCCESS, flatFile, 7, 7, 0, 0, buffer1);
+ dispatcher.doRedispatchRequestToWriteMap(AppendResult.SUCCESS, flatFile, 8, 8, 0, 0, buffer2);
+ dispatcher.doRedispatchRequestToWriteMap(AppendResult.SUCCESS, flatFile, 9, 9, 0, 0, buffer3);
+ dispatcher.buildConsumeQueueAndIndexFile();
+- Assert.assertEquals(10, flatFile.getConsumeQueueMaxOffset());
++ Assert.assertEquals(6, flatFile.getConsumeQueueMinOffset());
++ Assert.assertEquals(9 + 1, flatFile.getConsumeQueueMaxOffset());
+ }
+
+ @Test
+@@ -142,6 +143,7 @@ public class TieredDispatcherTest {
+ Mockito.when(defaultStore.getMinOffsetInQueue(mq.getTopic(), mq.getQueueId())).thenReturn(0L);
+ Mockito.when(defaultStore.getMaxOffsetInQueue(mq.getTopic(), mq.getQueueId())).thenReturn(9L);
+
++ // mock cq item, position = 7
+ ByteBuffer cqItem = ByteBuffer.allocate(ConsumeQueue.CQ_STORE_UNIT_SIZE);
+ cqItem.putLong(7);
+ cqItem.putInt(MessageBufferUtilTest.MSG_LEN);
+@@ -150,13 +152,13 @@ public class TieredDispatcherTest {
+ SelectMappedBufferResult mockResult = new SelectMappedBufferResult(0, cqItem, ConsumeQueue.CQ_STORE_UNIT_SIZE, null);
+ Mockito.when(((ConsumeQueue) defaultStore.getConsumeQueue(mq.getTopic(), mq.getQueueId())).getIndexBuffer(6)).thenReturn(mockResult);
+
++ // mock cq item, position = 8
+ cqItem = ByteBuffer.allocate(ConsumeQueue.CQ_STORE_UNIT_SIZE);
+ cqItem.putLong(8);
+ cqItem.putInt(MessageBufferUtilTest.MSG_LEN);
+ cqItem.putLong(1);
+ cqItem.flip();
+ mockResult = new SelectMappedBufferResult(0, cqItem, ConsumeQueue.CQ_STORE_UNIT_SIZE, null);
+-
+ Mockito.when(((ConsumeQueue) defaultStore.getConsumeQueue(mq.getTopic(), mq.getQueueId())).getIndexBuffer(7)).thenReturn(mockResult);
+
+ mockResult = new SelectMappedBufferResult(0, MessageBufferUtilTest.buildMockedMessageBuffer(), MessageBufferUtilTest.MSG_LEN, null);
+@@ -167,7 +169,10 @@ public class TieredDispatcherTest {
+ mockResult = new SelectMappedBufferResult(0, msg, MessageBufferUtilTest.MSG_LEN, null);
+ Mockito.when(defaultStore.selectOneMessageByOffset(8, MessageBufferUtilTest.MSG_LEN)).thenReturn(mockResult);
+
+- dispatcher.dispatchFlatFile(flatFileManager.getOrCreateFlatFileIfAbsent(mq));
++ CompositeQueueFlatFile flatFile = flatFileManager.getOrCreateFlatFileIfAbsent(mq);
++ Assert.assertNotNull(flatFile);
++ flatFile.initOffset(7);
++ dispatcher.dispatchFlatFile(flatFile);
+ Assert.assertEquals(8, flatFileManager.getFlatFile(mq).getDispatchOffset());
+ }
+ }
+diff --git a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/TieredMessageFetcherTest.java b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/TieredMessageFetcherTest.java
+index d75b2f916..774c6cf64 100644
+--- a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/TieredMessageFetcherTest.java
++++ b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/TieredMessageFetcherTest.java
+@@ -23,6 +23,7 @@ import java.util.Objects;
+ import java.util.concurrent.TimeUnit;
+ import org.apache.commons.lang3.SystemUtils;
+ import org.apache.commons.lang3.tuple.Triple;
++import org.apache.rocketmq.common.BoundaryType;
+ import org.apache.rocketmq.common.message.MessageQueue;
+ import org.apache.rocketmq.store.DispatchRequest;
+ import org.apache.rocketmq.store.GetMessageResult;
+@@ -40,7 +41,6 @@ import org.apache.rocketmq.tieredstore.file.TieredIndexFile;
+ import org.apache.rocketmq.tieredstore.util.MessageBufferUtil;
+ import org.apache.rocketmq.tieredstore.util.MessageBufferUtilTest;
+ import org.apache.rocketmq.tieredstore.util.TieredStoreUtil;
+-import org.apache.rocketmq.common.BoundaryType;
+ import org.awaitility.Awaitility;
+ import org.junit.After;
+ import org.junit.Assert;
+diff --git a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/file/CompositeQueueFlatFileTest.java b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/file/CompositeQueueFlatFileTest.java
+index 27efe111e..2e028ada3 100644
+--- a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/file/CompositeQueueFlatFileTest.java
++++ b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/file/CompositeQueueFlatFileTest.java
+@@ -119,7 +119,7 @@ public class CompositeQueueFlatFileTest {
+ Assert.assertEquals(AppendResult.SUCCESS, result);
+
+ file.commit(true);
+- file.persistMetadata();
++ file.flushMetadata();
+
+ QueueMetadata queueMetadata = metadataStore.getQueue(mq);
+ Assert.assertEquals(53, queueMetadata.getMaxOffset());
+diff --git a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/file/TieredFlatFileManagerTest.java b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/file/TieredFlatFileManagerTest.java
+index b7528c5e4..20fe4dd70 100644
+--- a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/file/TieredFlatFileManagerTest.java
++++ b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/file/TieredFlatFileManagerTest.java
+@@ -72,10 +72,15 @@ public class TieredFlatFileManagerTest {
+
+ CompositeFlatFile flatFile = flatFileManager.getFlatFile(mq);
+ Assert.assertNotNull(flatFile);
+- Assert.assertEquals(100, flatFile.getDispatchOffset());
++ Assert.assertEquals(-1L, flatFile.getDispatchOffset());
++ flatFile.initOffset(100L);
++ Assert.assertEquals(100L, flatFile.getDispatchOffset());
++ flatFile.initOffset(200L);
++ Assert.assertEquals(100L, flatFile.getDispatchOffset());
+
+ CompositeFlatFile flatFile1 = flatFileManager.getFlatFile(mq1);
+ Assert.assertNotNull(flatFile1);
++ flatFile1.initOffset(200L);
+ Assert.assertEquals(200, flatFile1.getDispatchOffset());
+
+ flatFileManager.destroyCompositeFile(mq);
+--
+2.32.0.windows.2
+
+
+From 99b39a35f29e491862296d56b7938a995d153974 Mon Sep 17 00:00:00 2001
+From: ShuangxiDing <dingshuangxi888@gmail.com>
+Date: Thu, 10 Aug 2023 11:28:39 +0800
+Subject: [PATCH 02/12] [ISSUE #7115] Fix grpc response message NPE (#7116)
+
+---
+ .../apache/rocketmq/proxy/grpc/v2/common/ResponseBuilder.java | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/ResponseBuilder.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/ResponseBuilder.java
+index 0b3c85ea6..efa879a9c 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/ResponseBuilder.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/ResponseBuilder.java
+@@ -92,7 +92,7 @@ public class ResponseBuilder {
+ public Status buildStatus(Code code, String message) {
+ return Status.newBuilder()
+ .setCode(code)
+- .setMessage(message)
++ .setMessage(message != null ? message : code.name())
+ .build();
+ }
+
+--
+2.32.0.windows.2
+
+
+From c0ba453f38183266cf9a69be754e620311e1923b Mon Sep 17 00:00:00 2001
+From: caigy <csgytsai@163.com>
+Date: Thu, 10 Aug 2023 14:08:17 +0800
+Subject: [PATCH 03/12] [ISSUE #7129] Fix resource collisions in acl tests
+ (#7130)
+
+* run acl tests sequencially to avoid collision
+
+* disable reuseForks for acl like broker
+
+* Revert "[ISSUE #7135] Temporarily ignoring plainAccessValidator test (#7135)"
+
+This reverts commit 6bc2c8474a0ce1e2833c82dffea7b1d8f718fcd7.
+---
+ acl/pom.xml | 13 +++++++++++++
+ .../acl/plain/PlainAccessControlFlowTest.java | 5 -----
+ .../acl/plain/PlainAccessValidatorTest.java | 3 ---
+ .../acl/plain/PlainPermissionManagerTest.java | 3 ---
+ 4 files changed, 13 insertions(+), 11 deletions(-)
+
+diff --git a/acl/pom.xml b/acl/pom.xml
+index 67bfcb8d2..989c0cf77 100644
+--- a/acl/pom.xml
++++ b/acl/pom.xml
+@@ -74,4 +74,17 @@
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
++
++ <build>
++ <plugins>
++ <plugin>
++ <artifactId>maven-surefire-plugin</artifactId>
++ <version>${maven-surefire-plugin.version}</version>
++ <configuration>
++ <forkCount>1</forkCount>
++ <reuseForks>false</reuseForks>
++ </configuration>
++ </plugin>
++ </plugins>
++ </build>
+ </project>
+diff --git a/acl/src/test/java/org/apache/rocketmq/acl/plain/PlainAccessControlFlowTest.java b/acl/src/test/java/org/apache/rocketmq/acl/plain/PlainAccessControlFlowTest.java
+index e7fd0932f..519345714 100644
+--- a/acl/src/test/java/org/apache/rocketmq/acl/plain/PlainAccessControlFlowTest.java
++++ b/acl/src/test/java/org/apache/rocketmq/acl/plain/PlainAccessControlFlowTest.java
+@@ -31,7 +31,6 @@ import org.apache.rocketmq.remoting.protocol.header.PullMessageRequestHeader;
+ import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader;
+ import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeaderV2;
+ import org.junit.Assert;
+-import org.junit.Ignore;
+ import org.junit.Test;
+
+ import java.io.File;
+@@ -44,7 +43,6 @@ import java.util.Collections;
+ import java.util.LinkedList;
+ import java.util.List;
+
+-
+ /**
+ * <p> In this class, we'll test the following scenarios, each containing several consecutive operations on ACL,
+ * <p> like updating and deleting ACL, changing config files and checking validations.
+@@ -52,9 +50,6 @@ import java.util.List;
+ * <p> Case 2: Only conf/acl/plain_acl.yml exists;
+ * <p> Case 3: Both conf/plain_acl.yml and conf/acl/plain_acl.yml exists.
+ */
+-
+-// Ignore this test case as it is currently unable to pass on ubuntu workflow
+-@Ignore
+ public class PlainAccessControlFlowTest {
+ public static final String DEFAULT_TOPIC = "topic-acl";
+
+diff --git a/acl/src/test/java/org/apache/rocketmq/acl/plain/PlainAccessValidatorTest.java b/acl/src/test/java/org/apache/rocketmq/acl/plain/PlainAccessValidatorTest.java
+index a3a925758..ef0cffbdc 100644
+--- a/acl/src/test/java/org/apache/rocketmq/acl/plain/PlainAccessValidatorTest.java
++++ b/acl/src/test/java/org/apache/rocketmq/acl/plain/PlainAccessValidatorTest.java
+@@ -56,11 +56,8 @@ import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData;
+ import org.junit.After;
+ import org.junit.Assert;
+ import org.junit.Before;
+-import org.junit.Ignore;
+ import org.junit.Test;
+
+-// Ignore this test case as it is currently unable to pass on ubuntu workflow
+-@Ignore
+ public class PlainAccessValidatorTest {
+
+ private PlainAccessValidator plainAccessValidator;
+diff --git a/acl/src/test/java/org/apache/rocketmq/acl/plain/PlainPermissionManagerTest.java b/acl/src/test/java/org/apache/rocketmq/acl/plain/PlainPermissionManagerTest.java
+index aa7539f3a..941d8c779 100644
+--- a/acl/src/test/java/org/apache/rocketmq/acl/plain/PlainPermissionManagerTest.java
++++ b/acl/src/test/java/org/apache/rocketmq/acl/plain/PlainPermissionManagerTest.java
+@@ -29,7 +29,6 @@ import org.assertj.core.api.Assertions;
+ import org.assertj.core.util.Lists;
+ import org.junit.Assert;
+ import org.junit.Before;
+-import org.junit.Ignore;
+ import org.junit.Test;
+
+ import java.io.File;
+@@ -42,8 +41,6 @@ import java.util.List;
+ import java.util.Map;
+ import java.util.Set;
+
+-// Ignore this test case as it is currently unable to pass on ubuntu workflow
+-@Ignore
+ public class PlainPermissionManagerTest {
+
+ PlainPermissionManager plainPermissionManager;
+--
+2.32.0.windows.2
+
+
+From 8741ff8c9b3bdbfc97976285affa7ea35c81243c Mon Sep 17 00:00:00 2001
+From: ShuangxiDing <dingshuangxi888@gmail.com>
+Date: Thu, 10 Aug 2023 17:41:15 +0800
+Subject: [PATCH 04/12] [ISSUE #7153] Add switch for MIXED message type (#7154)
+
+Add a switch for MIXED message type when creating a Topic in the Broker.
+---
+ .../broker/processor/AdminBrokerProcessor.java | 8 ++++++++
+ .../java/org/apache/rocketmq/common/BrokerConfig.java | 10 ++++++++++
+ 2 files changed, 18 insertions(+)
+
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessor.java
+index a6ce03dc2..bbddcec2d 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessor.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessor.java
+@@ -59,6 +59,7 @@ import org.apache.rocketmq.common.TopicConfig;
+ import org.apache.rocketmq.common.UnlockCallback;
+ import org.apache.rocketmq.common.UtilAll;
+ import org.apache.rocketmq.common.attribute.AttributeParser;
++import org.apache.rocketmq.common.attribute.TopicMessageType;
+ import org.apache.rocketmq.common.constant.ConsumeInitMode;
+ import org.apache.rocketmq.common.constant.FIleReadaheadMode;
+ import org.apache.rocketmq.common.constant.LoggerName;
+@@ -439,6 +440,13 @@ public class AdminBrokerProcessor implements NettyRequestProcessor {
+ String attributesModification = requestHeader.getAttributes();
+ topicConfig.setAttributes(AttributeParser.parseToMap(attributesModification));
+
++ if (topicConfig.getTopicMessageType() == TopicMessageType.MIXED
++ && !brokerController.getBrokerConfig().isEnableMixedMessageType()) {
++ response.setCode(ResponseCode.SYSTEM_ERROR);
++ response.setRemark("MIXED message type is not supported.");
++ return response;
++ }
++
+ try {
+ this.brokerController.getTopicConfigManager().updateTopicConfig(topicConfig);
+ if (brokerController.getBrokerConfig().isEnableSingleTopicRegister()) {
+diff --git a/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java b/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java
+index a815636b1..99a5db5ad 100644
+--- a/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java
++++ b/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java
+@@ -393,6 +393,8 @@ public class BrokerConfig extends BrokerIdentity {
+ */
+ private boolean enableSingleTopicRegister = false;
+
++ private boolean enableMixedMessageType = false;
++
+ public long getMaxPopPollingSize() {
+ return maxPopPollingSize;
+ }
+@@ -1712,4 +1714,12 @@ public class BrokerConfig extends BrokerIdentity {
+ public void setEnableSingleTopicRegister(boolean enableSingleTopicRegister) {
+ this.enableSingleTopicRegister = enableSingleTopicRegister;
+ }
++
++ public boolean isEnableMixedMessageType() {
++ return enableMixedMessageType;
++ }
++
++ public void setEnableMixedMessageType(boolean enableMixedMessageType) {
++ this.enableMixedMessageType = enableMixedMessageType;
++ }
+ }
+--
+2.32.0.windows.2
+
+
+From f534501855f8edbcb58f5b856973bf1027b5cf3a Mon Sep 17 00:00:00 2001
+From: Steven <shirenchuang@users.noreply.github.com>
+Date: Fri, 11 Aug 2023 10:25:48 +0800
+Subject: [PATCH 05/12] [Feature 7155] add errlog when cmd err (#7157)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Co-authored-by: 十真 <shirenchuang.src@cainiao.com>
+---
+ .../src/main/java/org/apache/rocketmq/srvutil/ServerUtil.java | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/srvutil/src/main/java/org/apache/rocketmq/srvutil/ServerUtil.java b/srvutil/src/main/java/org/apache/rocketmq/srvutil/ServerUtil.java
+index b00bad3c5..5a8a7cd54 100644
+--- a/srvutil/src/main/java/org/apache/rocketmq/srvutil/ServerUtil.java
++++ b/srvutil/src/main/java/org/apache/rocketmq/srvutil/ServerUtil.java
+@@ -52,6 +52,7 @@ public class ServerUtil {
+ System.exit(0);
+ }
+ } catch (ParseException e) {
++ System.err.println(e.getMessage());
+ hf.printHelp(appName, options, true);
+ System.exit(1);
+ }
+--
+2.32.0.windows.2
+
+
+From db58f00c0fe0f129611d654291f2177de55dc9ff Mon Sep 17 00:00:00 2001
+From: Zhouxiang Zhan <zhouxzhan@apache.org>
+Date: Fri, 11 Aug 2023 19:18:30 +0800
+Subject: [PATCH 06/12] [ISSUE #7169] Change metadataThreadPoolQueueCapacity to
+ 100000 (#7170)
+
+---
+ .../main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java b/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java
+index 4f57a7052..39caaa0d9 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java
+@@ -165,7 +165,7 @@ public class ProxyConfig implements ConfigFile {
+ private int subscriptionGroupConfigCacheExpiredInSeconds = 20;
+ private int subscriptionGroupConfigCacheMaxNum = 20000;
+ private int metadataThreadPoolNums = 3;
+- private int metadataThreadPoolQueueCapacity = 1000;
++ private int metadataThreadPoolQueueCapacity = 100000;
+
+ private int transactionHeartbeatThreadPoolNums = 20;
+ private int transactionHeartbeatThreadPoolQueueCapacity = 200;
+--
+2.32.0.windows.2
+
+
+From 1f04e68a2e331ab035b791280c5a91b60fe0c85f Mon Sep 17 00:00:00 2001
+From: yx9o <yangx_soft@163.com>
+Date: Sat, 12 Aug 2023 21:12:22 +0800
+Subject: [PATCH 07/12] [ISSUE #7172] Unified Chinese for Name Server (#7173)
+
+---
+ docs/cn/concept.md | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/docs/cn/concept.md b/docs/cn/concept.md
+index cb2c863bd..3d67e9371 100644
+--- a/docs/cn/concept.md
++++ b/docs/cn/concept.md
+@@ -17,7 +17,7 @@ RocketMQ主要由 Producer、Broker、Consumer 三部分组成其中Producer
+ 消息中转角色负责存储消息、转发消息。代理服务器在RocketMQ系统中负责接收从生产者发送来的消息并存储、同时为消费者的拉取请求作准备。代理服务器也存储消息相关的元数据包括消费者组、消费进度偏移和主题和队列消息等。
+
+ ## 6 名字服务Name Server
+- 名称服务充当路由消息的提供者。生产者或消费者能够通过名字服务查找各主题相应的Broker IP列表。多个Namesrv实例组成集群但相互独立没有信息交换。
++名字服务充当路由消息的提供者。生产者或消费者能够通过名字服务查找各主题相应的Broker IP列表。多个Namesrv实例组成集群但相互独立没有信息交换。
+
+ ## 7 拉取式消费Pull Consumer
+ Consumer消费的一种类型应用通常主动调用Consumer的拉消息方法从Broker服务器拉消息、主动权由应用控制。一旦获取了批量消息应用就会启动消费过程。
+--
+2.32.0.windows.2
+
+
+From 25005060bbace477eeaaf4c0142cece5213efbbf Mon Sep 17 00:00:00 2001
+From: yx9o <yangx_soft@163.com>
+Date: Sun, 13 Aug 2023 20:52:17 +0800
+Subject: [PATCH 08/12] [ISSUE #7176] Correct mismatched logs (#7177)
+
+---
+ .../org/apache/rocketmq/namesrv/routeinfo/RouteInfoManager.java | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/namesrv/src/main/java/org/apache/rocketmq/namesrv/routeinfo/RouteInfoManager.java b/namesrv/src/main/java/org/apache/rocketmq/namesrv/routeinfo/RouteInfoManager.java
+index 0055a1cc8..f7a95f0a6 100644
+--- a/namesrv/src/main/java/org/apache/rocketmq/namesrv/routeinfo/RouteInfoManager.java
++++ b/namesrv/src/main/java/org/apache/rocketmq/namesrv/routeinfo/RouteInfoManager.java
+@@ -522,7 +522,7 @@ public class RouteInfoManager {
+ this.lock.writeLock().unlock();
+ }
+ } catch (Exception e) {
+- log.error("wipeWritePermOfBrokerByLock Exception", e);
++ log.error("addWritePermOfBrokerByLock Exception", e);
+ }
+ return 0;
+ }
+--
+2.32.0.windows.2
+
+
+From ac411daa27117e9115a8fc5e2d5753085f009ed9 Mon Sep 17 00:00:00 2001
+From: yx9o <yangx_soft@163.com>
+Date: Tue, 15 Aug 2023 08:31:00 +0800
+Subject: [PATCH 09/12] [ISSUE #7183] Correct mismatched commandDesc (#7184)
+
+---
+ .../tools/command/topic/RemappingStaticTopicSubCommand.java | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/topic/RemappingStaticTopicSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/topic/RemappingStaticTopicSubCommand.java
+index 849f680d0..2a08fdb5b 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/topic/RemappingStaticTopicSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/topic/RemappingStaticTopicSubCommand.java
+@@ -47,7 +47,7 @@ public class RemappingStaticTopicSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Update or create static topic, which has fixed number of queues";
++ return "Remapping static topic.";
+ }
+
+ @Override
+--
+2.32.0.windows.2
+
+
+From 55e0cdb2af3ab75a6d892f919d60797f17a99fda Mon Sep 17 00:00:00 2001
+From: redlsz <szliu0927@gmail.com>
+Date: Tue, 15 Aug 2023 19:19:45 +0800
+Subject: [PATCH 10/12] fix: IndexOutOfBoundsException when process pop
+ response (#7003)
+
+---
+ .../org/apache/rocketmq/client/impl/MQClientAPIImpl.java | 5 ++++-
+ .../rocketmq/proxy/service/message/LocalMessageService.java | 5 ++++-
+ .../rocketmq/remoting/protocol/header/ExtraInfoUtil.java | 4 ++++
+ 3 files changed, 12 insertions(+), 2 deletions(-)
+
+diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java
+index 708a6acd1..5101ffc8e 100644
+--- a/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java
++++ b/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java
+@@ -1174,7 +1174,10 @@ public class MQClientAPIImpl implements NameServerUpdateCallback {
+ Long.parseLong(messageExt.getProperty(MessageConst.PROPERTY_INNER_MULTI_QUEUE_OFFSET)));
+ continue;
+ }
+- key = ExtraInfoUtil.getStartOffsetInfoMapKey(messageExt.getTopic(), messageExt.getQueueId());
++ // Value of POP_CK is used to determine whether it is a pop retry,
++ // cause topic could be rewritten by broker.
++ key = ExtraInfoUtil.getStartOffsetInfoMapKey(messageExt.getTopic(),
++ messageExt.getProperty(MessageConst.PROPERTY_POP_CK), messageExt.getQueueId());
+ if (!sortMap.containsKey(key)) {
+ sortMap.put(key, new ArrayList<>(4));
+ }
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/LocalMessageService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/LocalMessageService.java
+index 115c140ff..eb2c4d9ee 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/LocalMessageService.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/LocalMessageService.java
+@@ -249,7 +249,10 @@ public class LocalMessageService implements MessageService {
+ // <topicMark@queueId, msg queueOffset>
+ Map<String, List<Long>> sortMap = new HashMap<>(16);
+ for (MessageExt messageExt : messageExtList) {
+- String key = ExtraInfoUtil.getStartOffsetInfoMapKey(messageExt.getTopic(), messageExt.getQueueId());
++ // Value of POP_CK is used to determine whether it is a pop retry,
++ // cause topic could be rewritten by broker.
++ String key = ExtraInfoUtil.getStartOffsetInfoMapKey(messageExt.getTopic(),
++ messageExt.getProperty(MessageConst.PROPERTY_POP_CK), messageExt.getQueueId());
+ if (!sortMap.containsKey(key)) {
+ sortMap.put(key, new ArrayList<>(4));
+ }
+diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ExtraInfoUtil.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ExtraInfoUtil.java
+index 9a5fa89ab..13094331e 100644
+--- a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ExtraInfoUtil.java
++++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/header/ExtraInfoUtil.java
+@@ -282,6 +282,10 @@ public class ExtraInfoUtil {
+ return (topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX) ? RETRY_TOPIC : NORMAL_TOPIC) + "@" + key;
+ }
+
++ public static String getStartOffsetInfoMapKey(String topic, String popCk, long key) {
++ return ((topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX) || popCk != null) ? RETRY_TOPIC : NORMAL_TOPIC) + "@" + key;
++ }
++
+ public static String getQueueOffsetKeyValueKey(long queueId, long queueOffset) {
+ return QUEUE_OFFSET + queueId + "%" + queueOffset;
+ }
+--
+2.32.0.windows.2
+
+
+From a9c0b43f7f6ce5acfc4f2f3069553071fa93dfee Mon Sep 17 00:00:00 2001
+From: yx9o <yangx_soft@163.com>
+Date: Wed, 16 Aug 2023 18:45:00 +0800
+Subject: [PATCH 11/12] [ISSUE #7192] Correct typos (#7193)
+
+---
+ .../tools/command/consumer/ConsumerProgressSubCommand.java | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/consumer/ConsumerProgressSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/consumer/ConsumerProgressSubCommand.java
+index f51a24673..97125b854 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/consumer/ConsumerProgressSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/consumer/ConsumerProgressSubCommand.java
+@@ -54,7 +54,7 @@ public class ConsumerProgressSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Query consumers's progress, speed";
++ return "Query consumer's progress, speed.";
+ }
+
+ @Override
+--
+2.32.0.windows.2
+
+
+From 5a3de926b816db5a121c1d788430072a3bc942ae Mon Sep 17 00:00:00 2001
+From: Zhouxiang Zhan <zhouxzhan@apache.org>
+Date: Wed, 16 Aug 2023 20:52:53 +0800
+Subject: [PATCH 12/12] Optimize updateSubscription check exist loop (#7190)
+
+---
+ .../broker/client/ConsumerGroupInfo.java | 17 ++++++-----------
+ 1 file changed, 6 insertions(+), 11 deletions(-)
+
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/client/ConsumerGroupInfo.java b/broker/src/main/java/org/apache/rocketmq/broker/client/ConsumerGroupInfo.java
+index 867b9c720..1ea58c125 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/client/ConsumerGroupInfo.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/client/ConsumerGroupInfo.java
+@@ -18,6 +18,7 @@ package org.apache.rocketmq.broker.client;
+
+ import io.netty.channel.Channel;
+ import java.util.ArrayList;
++import java.util.HashSet;
+ import java.util.Iterator;
+ import java.util.List;
+ import java.util.Map.Entry;
+@@ -172,7 +173,7 @@ public class ConsumerGroupInfo {
+ */
+ public boolean updateSubscription(final Set<SubscriptionData> subList) {
+ boolean updated = false;
+-
++ Set<String> topicSet = new HashSet<>();
+ for (SubscriptionData sub : subList) {
+ SubscriptionData old = this.subscriptionTable.get(sub.getTopic());
+ if (old == null) {
+@@ -194,22 +195,16 @@ public class ConsumerGroupInfo {
+
+ this.subscriptionTable.put(sub.getTopic(), sub);
+ }
++ // Add all new topics to the HashSet
++ topicSet.add(sub.getTopic());
+ }
+
+ Iterator<Entry<String, SubscriptionData>> it = this.subscriptionTable.entrySet().iterator();
+ while (it.hasNext()) {
+ Entry<String, SubscriptionData> next = it.next();
+ String oldTopic = next.getKey();
+-
+- boolean exist = false;
+- for (SubscriptionData sub : subList) {
+- if (sub.getTopic().equals(oldTopic)) {
+- exist = true;
+- break;
+- }
+- }
+-
+- if (!exist) {
++ // Check HashSet with O(1) time complexity
++ if (!topicSet.contains(oldTopic)) {
+ log.warn("subscription changed, group: {} remove topic {} {}",
+ this.groupName,
+ oldTopic,
+--
+2.32.0.windows.2
+
diff --git a/patch011-backport-optimize-config b/patch011-backport-optimize-config
new file mode 100644
index 000000000..fc8e2eceb
--- /dev/null
+++ b/patch011-backport-optimize-config
@@ -0,0 +1,1390 @@
+From 50d1050437ed8748f86ee50261b50a1e1f63162e Mon Sep 17 00:00:00 2001
+From: Jixiang Jin <lollipop@apache.org>
+Date: Wed, 16 Aug 2023 21:15:00 +0800
+Subject: [PATCH 1/7] To config the cardinalityLimit for openTelemetry metrics
+ exporting and fix logging config for metrics (#7196)
+
+---
+ WORKSPACE | 14 +++---
+ .../broker/metrics/BrokerMetricsManager.java | 47 ++++++++++++++-----
+ .../broker/metrics/PopMetricsManager.java | 11 +++--
+ .../src/main/resources/rmq.broker.logback.xml | 17 ++++---
+ .../apache/rocketmq/common/BrokerConfig.java | 9 ++++
+ .../metrics/ControllerMetricsManager.java | 6 +--
+ pom.xml | 4 +-
+ .../metrics/RemotingMetricsManager.java | 10 ++--
+ .../rocketmq/store/DefaultMessageStore.java | 24 +++++-----
+ .../apache/rocketmq/store/MessageStore.java | 6 +--
+ .../metrics/DefaultStoreMetricsManager.java | 4 +-
+ .../plugin/AbstractPluginMessageStore.java | 6 +--
+ .../tieredstore/TieredMessageStore.java | 6 +--
+ .../metrics/TieredStoreMetricsManager.java | 23 +++++----
+ 14 files changed, 110 insertions(+), 77 deletions(-)
+
+diff --git a/WORKSPACE b/WORKSPACE
+index a8a0aafe9..3126f2d1d 100644
+--- a/WORKSPACE
++++ b/WORKSPACE
+@@ -88,14 +88,14 @@ maven_install(
+ "io.grpc:grpc-api:1.47.0",
+ "io.grpc:grpc-testing:1.47.0",
+ "org.springframework:spring-core:5.3.26",
+- "io.opentelemetry:opentelemetry-exporter-otlp:1.19.0",
+- "io.opentelemetry:opentelemetry-exporter-prometheus:1.19.0-alpha",
+- "io.opentelemetry:opentelemetry-exporter-logging:1.19.0",
+- "io.opentelemetry:opentelemetry-sdk:1.19.0",
++ "io.opentelemetry:opentelemetry-exporter-otlp:1.29.0",
++ "io.opentelemetry:opentelemetry-exporter-prometheus:1.29.0-alpha",
++ "io.opentelemetry:opentelemetry-exporter-logging:1.29.0",
++ "io.opentelemetry:opentelemetry-sdk:1.29.0",
+ "com.squareup.okio:okio-jvm:3.0.0",
+- "io.opentelemetry:opentelemetry-api:1.19.0",
+- "io.opentelemetry:opentelemetry-sdk-metrics:1.19.0",
+- "io.opentelemetry:opentelemetry-sdk-common:1.19.0",
++ "io.opentelemetry:opentelemetry-api:1.29.0",
++ "io.opentelemetry:opentelemetry-sdk-metrics:1.29.0",
++ "io.opentelemetry:opentelemetry-sdk-common:1.29.0",
+ "io.github.aliyunmq:rocketmq-slf4j-api:1.0.0",
+ "io.github.aliyunmq:rocketmq-logback-classic:1.0.0",
+ "org.slf4j:jul-to-slf4j:2.0.6",
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/metrics/BrokerMetricsManager.java b/broker/src/main/java/org/apache/rocketmq/broker/metrics/BrokerMetricsManager.java
+index f0b76107e..6af5afc14 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/metrics/BrokerMetricsManager.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/metrics/BrokerMetricsManager.java
+@@ -34,8 +34,10 @@ import io.opentelemetry.sdk.metrics.InstrumentType;
+ import io.opentelemetry.sdk.metrics.SdkMeterProvider;
+ import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder;
+ import io.opentelemetry.sdk.metrics.View;
++import io.opentelemetry.sdk.metrics.ViewBuilder;
+ import io.opentelemetry.sdk.metrics.data.AggregationTemporality;
+ import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader;
++import io.opentelemetry.sdk.metrics.internal.SdkMeterProviderUtil;
+ import io.opentelemetry.sdk.resources.Resource;
+ import java.util.ArrayList;
+ import java.util.Arrays;
+@@ -361,22 +363,45 @@ public class BrokerMetricsManager {
+ .setType(InstrumentType.HISTOGRAM)
+ .setName(HISTOGRAM_MESSAGE_SIZE)
+ .build();
+- View messageSizeView = View.builder()
+- .setAggregation(Aggregation.explicitBucketHistogram(messageSizeBuckets))
+- .build();
+- providerBuilder.registerView(messageSizeSelector, messageSizeView);
+-
+- for (Pair<InstrumentSelector, View> selectorViewPair : RemotingMetricsManager.getMetricsView()) {
+- providerBuilder.registerView(selectorViewPair.getObject1(), selectorViewPair.getObject2());
++ ViewBuilder messageSizeViewBuilder = View.builder()
++ .setAggregation(Aggregation.explicitBucketHistogram(messageSizeBuckets));
++ // To config the cardinalityLimit for openTelemetry metrics exporting.
++ SdkMeterProviderUtil.setCardinalityLimit(messageSizeViewBuilder, brokerConfig.getMetricsOtelCardinalityLimit());
++ providerBuilder.registerView(messageSizeSelector, messageSizeViewBuilder.build());
++
++ for (Pair<InstrumentSelector, ViewBuilder> selectorViewPair : RemotingMetricsManager.getMetricsView()) {
++ ViewBuilder viewBuilder = selectorViewPair.getObject2();
++ SdkMeterProviderUtil.setCardinalityLimit(viewBuilder, brokerConfig.getMetricsOtelCardinalityLimit());
++ providerBuilder.registerView(selectorViewPair.getObject1(), viewBuilder.build());
+ }
+
+- for (Pair<InstrumentSelector, View> selectorViewPair : messageStore.getMetricsView()) {
+- providerBuilder.registerView(selectorViewPair.getObject1(), selectorViewPair.getObject2());
++ for (Pair<InstrumentSelector, ViewBuilder> selectorViewPair : messageStore.getMetricsView()) {
++ ViewBuilder viewBuilder = selectorViewPair.getObject2();
++ SdkMeterProviderUtil.setCardinalityLimit(viewBuilder, brokerConfig.getMetricsOtelCardinalityLimit());
++ providerBuilder.registerView(selectorViewPair.getObject1(), viewBuilder.build());
+ }
+
+- for (Pair<InstrumentSelector, View> selectorViewPair : PopMetricsManager.getMetricsView()) {
+- providerBuilder.registerView(selectorViewPair.getObject1(), selectorViewPair.getObject2());
++ for (Pair<InstrumentSelector, ViewBuilder> selectorViewPair : PopMetricsManager.getMetricsView()) {
++ ViewBuilder viewBuilder = selectorViewPair.getObject2();
++ SdkMeterProviderUtil.setCardinalityLimit(viewBuilder, brokerConfig.getMetricsOtelCardinalityLimit());
++ providerBuilder.registerView(selectorViewPair.getObject1(), viewBuilder.build());
+ }
++
++ // default view builder for all counter.
++ InstrumentSelector defaultCounterSelector = InstrumentSelector.builder()
++ .setType(InstrumentType.COUNTER)
++ .build();
++ ViewBuilder defaultCounterViewBuilder = View.builder().setDescription("default view for counter.");
++ SdkMeterProviderUtil.setCardinalityLimit(defaultCounterViewBuilder, brokerConfig.getMetricsOtelCardinalityLimit());
++ providerBuilder.registerView(defaultCounterSelector, defaultCounterViewBuilder.build());
++
++ //default view builder for all observable gauge.
++ InstrumentSelector defaultGaugeSelector = InstrumentSelector.builder()
++ .setType(InstrumentType.OBSERVABLE_GAUGE)
++ .build();
++ ViewBuilder defaultGaugeViewBuilder = View.builder().setDescription("default view for gauge.");
++ SdkMeterProviderUtil.setCardinalityLimit(defaultGaugeViewBuilder, brokerConfig.getMetricsOtelCardinalityLimit());
++ providerBuilder.registerView(defaultGaugeSelector, defaultGaugeViewBuilder.build());
+ }
+
+ private void initStatsMetrics() {
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/metrics/PopMetricsManager.java b/broker/src/main/java/org/apache/rocketmq/broker/metrics/PopMetricsManager.java
+index 463371d7e..2de220da1 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/metrics/PopMetricsManager.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/metrics/PopMetricsManager.java
+@@ -27,6 +27,7 @@ import io.opentelemetry.sdk.metrics.Aggregation;
+ import io.opentelemetry.sdk.metrics.InstrumentSelector;
+ import io.opentelemetry.sdk.metrics.InstrumentType;
+ import io.opentelemetry.sdk.metrics.View;
++import io.opentelemetry.sdk.metrics.ViewBuilder;
+ import java.time.Duration;
+ import java.util.Arrays;
+ import java.util.List;
+@@ -63,7 +64,7 @@ public class PopMetricsManager {
+ private static LongCounter popReviveGetTotal = new NopLongCounter();
+ private static LongCounter popReviveRetryMessageTotal = new NopLongCounter();
+
+- public static List<Pair<InstrumentSelector, View>> getMetricsView() {
++ public static List<Pair<InstrumentSelector, ViewBuilder>> getMetricsView() {
+ List<Double> rpcCostTimeBuckets = Arrays.asList(
+ (double) Duration.ofMillis(1).toMillis(),
+ (double) Duration.ofMillis(10).toMillis(),
+@@ -76,10 +77,10 @@ public class PopMetricsManager {
+ .setType(InstrumentType.HISTOGRAM)
+ .setName(HISTOGRAM_POP_BUFFER_SCAN_TIME_CONSUME)
+ .build();
+- View popBufferScanTimeConsumeView = View.builder()
+- .setAggregation(Aggregation.explicitBucketHistogram(rpcCostTimeBuckets))
+- .build();
+- return Lists.newArrayList(new Pair<>(popBufferScanTimeConsumeSelector, popBufferScanTimeConsumeView));
++ ViewBuilder popBufferScanTimeConsumeViewBuilder = View.builder()
++ .setAggregation(Aggregation.explicitBucketHistogram(rpcCostTimeBuckets));
++
++ return Lists.newArrayList(new Pair<>(popBufferScanTimeConsumeSelector, popBufferScanTimeConsumeViewBuilder));
+ }
+
+ public static void initMetrics(Meter meter, BrokerController brokerController,
+diff --git a/broker/src/main/resources/rmq.broker.logback.xml b/broker/src/main/resources/rmq.broker.logback.xml
+index 7d49f6664..3c51e59d4 100644
+--- a/broker/src/main/resources/rmq.broker.logback.xml
++++ b/broker/src/main/resources/rmq.broker.logback.xml
+@@ -559,27 +559,27 @@
+ </sift>
+ </appender>
+
+- <appender name="RocketmqBrokerMetricsAppender" class="ch.qos.logback.classic.sift.SiftingAppender">
++ <appender name="RocketmqBrokerMetricsSiftingAppender_inner" class="ch.qos.logback.classic.sift.SiftingAppender">
+ <discriminator>
+ <key>brokerContainerLogDir</key>
+ <defaultValue>${file.separator}</defaultValue>
+ </discriminator>
+ <sift>
+- <appender name="RocketmqCommercialAppender"
++ <appender name="RocketmqBrokerMetricsAppender"
+ class="ch.qos.logback.core.rolling.RollingFileAppender">
+ <file>
+- ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}broker_metric.log
++ ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}broker_metrics.log
+ </file>
+ <append>true</append>
+ <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
+ <fileNamePattern>
+- ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}otherdays${file.separator}broker_metric.%i.log.gz
++ ${user.home}${file.separator}logs${file.separator}rocketmqlogs${brokerLogDir:-${file.separator}}${brokerContainerLogDir}${file.separator}otherdays${file.separator}broker_metrics.%i.log.gz
+ </fileNamePattern>
+ <minIndex>1</minIndex>
+- <maxIndex>10</maxIndex>
++ <maxIndex>3</maxIndex>
+ </rollingPolicy>
+ <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
+- <maxFileSize>500MB</maxFileSize>
++ <maxFileSize>512MB</maxFileSize>
+ </triggeringPolicy>
+ <encoder>
+ <pattern>%d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n</pattern>
+@@ -588,6 +588,9 @@
+ </appender>
+ </sift>
+ </appender>
++ <appender name="RocketmqBrokerMetricsSiftingAppender" class="ch.qos.logback.classic.AsyncAppender">
++ <appender-ref ref="RocketmqBrokerMetricsSiftingAppender_inner"/>
++ </appender>
+
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+ <encoder>
+@@ -670,7 +673,7 @@
+ </logger>
+
+ <logger name="io.opentelemetry.exporter.logging.LoggingMetricExporter" additivity="false" level="INFO">
+- <appender-ref ref="RocketmqBrokerMetricsAppender"/>
++ <appender-ref ref="RocketmqBrokerMetricsSiftingAppender"/>
+ </logger>
+
+ <root level="INFO">
+diff --git a/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java b/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java
+index 99a5db5ad..45d26b29c 100644
+--- a/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java
++++ b/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java
+@@ -350,6 +350,7 @@ public class BrokerConfig extends BrokerIdentity {
+
+ private MetricsExporterType metricsExporterType = MetricsExporterType.DISABLE;
+
++ private int metricsOtelCardinalityLimit = 50 * 1000;
+ private String metricsGrpcExporterTarget = "";
+ private String metricsGrpcExporterHeader = "";
+ private long metricGrpcExporterTimeOutInMills = 3 * 1000;
+@@ -1531,6 +1532,14 @@ public class BrokerConfig extends BrokerIdentity {
+ this.metricsExporterType = MetricsExporterType.valueOf(metricsExporterType);
+ }
+
++ public int getMetricsOtelCardinalityLimit() {
++ return metricsOtelCardinalityLimit;
++ }
++
++ public void setMetricsOtelCardinalityLimit(int metricsOtelCardinalityLimit) {
++ this.metricsOtelCardinalityLimit = metricsOtelCardinalityLimit;
++ }
++
+ public String getMetricsGrpcExporterTarget() {
+ return metricsGrpcExporterTarget;
+ }
+diff --git a/controller/src/main/java/org/apache/rocketmq/controller/metrics/ControllerMetricsManager.java b/controller/src/main/java/org/apache/rocketmq/controller/metrics/ControllerMetricsManager.java
+index 9b30a3b43..650740bcc 100644
+--- a/controller/src/main/java/org/apache/rocketmq/controller/metrics/ControllerMetricsManager.java
++++ b/controller/src/main/java/org/apache/rocketmq/controller/metrics/ControllerMetricsManager.java
+@@ -203,7 +203,7 @@ public class ControllerMetricsManager {
+ 10 * s
+ );
+
+- View latecyView = View.builder()
++ View latencyView = View.builder()
+ .setAggregation(Aggregation.explicitBucketHistogram(latencyBuckets))
+ .build();
+
+@@ -217,8 +217,8 @@ public class ControllerMetricsManager {
+ .setName(HISTOGRAM_DLEDGER_OP_LATENCY)
+ .build();
+
+- providerBuilder.registerView(requestLatencySelector, latecyView);
+- providerBuilder.registerView(dLedgerOpLatencySelector, latecyView);
++ providerBuilder.registerView(requestLatencySelector, latencyView);
++ providerBuilder.registerView(dLedgerOpLatencySelector, latencyView);
+ }
+
+ private void initMetric(Meter meter) {
+diff --git a/pom.xml b/pom.xml
+index 3a08d75f2..9f0b3eb96 100644
+--- a/pom.xml
++++ b/pom.xml
+@@ -133,8 +133,8 @@
+ <caffeine.version>2.9.3</caffeine.version>
+ <spring.version>5.3.27</spring.version>
+ <okio-jvm.version>3.0.0</okio-jvm.version>
+- <opentelemetry.version>1.26.0</opentelemetry.version>
+- <opentelemetry-exporter-prometheus.version>1.26.0-alpha</opentelemetry-exporter-prometheus.version>
++ <opentelemetry.version>1.29.0</opentelemetry.version>
++ <opentelemetry-exporter-prometheus.version>1.29.0-alpha</opentelemetry-exporter-prometheus.version>
+ <jul-to-slf4j.version>2.0.6</jul-to-slf4j.version>
+ <s3.version>2.20.29</s3.version>
+ <rocksdb.version>1.0.3</rocksdb.version>
+diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/metrics/RemotingMetricsManager.java b/remoting/src/main/java/org/apache/rocketmq/remoting/metrics/RemotingMetricsManager.java
+index 34136f94f..2e0d70856 100644
+--- a/remoting/src/main/java/org/apache/rocketmq/remoting/metrics/RemotingMetricsManager.java
++++ b/remoting/src/main/java/org/apache/rocketmq/remoting/metrics/RemotingMetricsManager.java
+@@ -26,6 +26,7 @@ import io.opentelemetry.sdk.metrics.Aggregation;
+ import io.opentelemetry.sdk.metrics.InstrumentSelector;
+ import io.opentelemetry.sdk.metrics.InstrumentType;
+ import io.opentelemetry.sdk.metrics.View;
++import io.opentelemetry.sdk.metrics.ViewBuilder;
+ import java.time.Duration;
+ import java.util.Arrays;
+ import java.util.List;
+@@ -61,7 +62,7 @@ public class RemotingMetricsManager {
+ .build();
+ }
+
+- public static List<Pair<InstrumentSelector, View>> getMetricsView() {
++ public static List<Pair<InstrumentSelector, ViewBuilder>> getMetricsView() {
+ List<Double> rpcCostTimeBuckets = Arrays.asList(
+ (double) Duration.ofMillis(1).toMillis(),
+ (double) Duration.ofMillis(3).toMillis(),
+@@ -77,10 +78,9 @@ public class RemotingMetricsManager {
+ .setType(InstrumentType.HISTOGRAM)
+ .setName(HISTOGRAM_RPC_LATENCY)
+ .build();
+- View view = View.builder()
+- .setAggregation(Aggregation.explicitBucketHistogram(rpcCostTimeBuckets))
+- .build();
+- return Lists.newArrayList(new Pair<>(selector, view));
++ ViewBuilder viewBuilder = View.builder()
++ .setAggregation(Aggregation.explicitBucketHistogram(rpcCostTimeBuckets));
++ return Lists.newArrayList(new Pair<>(selector, viewBuilder));
+ }
+
+ public static String getWriteAndFlushResult(Future<?> future) {
+diff --git a/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java b/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java
+index 25e4a166f..6115ead59 100644
+--- a/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java
++++ b/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java
+@@ -22,7 +22,7 @@ import io.openmessaging.storage.dledger.entry.DLedgerEntry;
+ import io.opentelemetry.api.common.AttributesBuilder;
+ import io.opentelemetry.api.metrics.Meter;
+ import io.opentelemetry.sdk.metrics.InstrumentSelector;
+-import io.opentelemetry.sdk.metrics.View;
++import io.opentelemetry.sdk.metrics.ViewBuilder;
+ import java.io.File;
+ import java.io.IOException;
+ import java.io.RandomAccessFile;
+@@ -42,23 +42,24 @@ import java.util.Map;
+ import java.util.Objects;
+ import java.util.Optional;
+ import java.util.Set;
+-import java.util.concurrent.LinkedBlockingQueue;
+-import java.util.concurrent.TimeUnit;
+-import java.util.concurrent.TimeoutException;
+-import java.util.concurrent.ExecutionException;
+-import java.util.concurrent.Executors;
+-import java.util.concurrent.ExecutorService;
+-import java.util.concurrent.ThreadPoolExecutor;
+-import java.util.concurrent.ScheduledExecutorService;
+ import java.util.concurrent.CompletableFuture;
+-import java.util.concurrent.ConcurrentMap;
+ import java.util.concurrent.ConcurrentHashMap;
+ import java.util.concurrent.ConcurrentLinkedQueue;
++import java.util.concurrent.ConcurrentMap;
++import java.util.concurrent.ExecutionException;
++import java.util.concurrent.ExecutorService;
++import java.util.concurrent.Executors;
++import java.util.concurrent.LinkedBlockingQueue;
++import java.util.concurrent.ScheduledExecutorService;
++import java.util.concurrent.ThreadPoolExecutor;
++import java.util.concurrent.TimeUnit;
++import java.util.concurrent.TimeoutException;
+ import java.util.concurrent.atomic.AtomicInteger;
+ import java.util.concurrent.atomic.AtomicLong;
+ import java.util.function.Supplier;
+ import org.apache.commons.lang3.StringUtils;
+ import org.apache.rocketmq.common.AbstractBrokerRunnable;
++import org.apache.rocketmq.common.BoundaryType;
+ import org.apache.rocketmq.common.BrokerConfig;
+ import org.apache.rocketmq.common.BrokerIdentity;
+ import org.apache.rocketmq.common.MixAll;
+@@ -82,7 +83,6 @@ import org.apache.rocketmq.common.topic.TopicValidator;
+ import org.apache.rocketmq.common.utils.CleanupPolicyUtils;
+ import org.apache.rocketmq.common.utils.QueueTypeUtils;
+ import org.apache.rocketmq.common.utils.ServiceProvider;
+-import org.apache.rocketmq.common.BoundaryType;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+ import org.apache.rocketmq.remoting.protocol.body.HARuntimeInfo;
+@@ -3268,7 +3268,7 @@ public class DefaultMessageStore implements MessageStore {
+ }
+
+ @Override
+- public List<Pair<InstrumentSelector, View>> getMetricsView() {
++ public List<Pair<InstrumentSelector, ViewBuilder>> getMetricsView() {
+ return DefaultStoreMetricsManager.getMetricsView();
+ }
+
+diff --git a/store/src/main/java/org/apache/rocketmq/store/MessageStore.java b/store/src/main/java/org/apache/rocketmq/store/MessageStore.java
+index 31bbb907f..989cbbe31 100644
+--- a/store/src/main/java/org/apache/rocketmq/store/MessageStore.java
++++ b/store/src/main/java/org/apache/rocketmq/store/MessageStore.java
+@@ -19,8 +19,7 @@ package org.apache.rocketmq.store;
+ import io.opentelemetry.api.common.AttributesBuilder;
+ import io.opentelemetry.api.metrics.Meter;
+ import io.opentelemetry.sdk.metrics.InstrumentSelector;
+-import io.opentelemetry.sdk.metrics.View;
+-
++import io.opentelemetry.sdk.metrics.ViewBuilder;
+ import java.nio.ByteBuffer;
+ import java.util.HashMap;
+ import java.util.LinkedList;
+@@ -28,7 +27,6 @@ import java.util.List;
+ import java.util.Set;
+ import java.util.concurrent.CompletableFuture;
+ import java.util.function.Supplier;
+-
+ import org.apache.rocketmq.common.BoundaryType;
+ import org.apache.rocketmq.common.Pair;
+ import org.apache.rocketmq.common.SystemClock;
+@@ -964,7 +962,7 @@ public interface MessageStore {
+ *
+ * @return List of metrics selector and view pair
+ */
+- List<Pair<InstrumentSelector, View>> getMetricsView();
++ List<Pair<InstrumentSelector, ViewBuilder>> getMetricsView();
+
+ /**
+ * Init store metrics
+diff --git a/store/src/main/java/org/apache/rocketmq/store/metrics/DefaultStoreMetricsManager.java b/store/src/main/java/org/apache/rocketmq/store/metrics/DefaultStoreMetricsManager.java
+index ff87f6369..45a6bbc68 100644
+--- a/store/src/main/java/org/apache/rocketmq/store/metrics/DefaultStoreMetricsManager.java
++++ b/store/src/main/java/org/apache/rocketmq/store/metrics/DefaultStoreMetricsManager.java
+@@ -23,7 +23,7 @@ import io.opentelemetry.api.metrics.LongCounter;
+ import io.opentelemetry.api.metrics.Meter;
+ import io.opentelemetry.api.metrics.ObservableLongGauge;
+ import io.opentelemetry.sdk.metrics.InstrumentSelector;
+-import io.opentelemetry.sdk.metrics.View;
++import io.opentelemetry.sdk.metrics.ViewBuilder;
+ import java.io.File;
+ import java.util.List;
+ import java.util.function.Supplier;
+@@ -69,7 +69,7 @@ public class DefaultStoreMetricsManager {
+ public static LongCounter timerDequeueTotal = new NopLongCounter();
+ public static LongCounter timerEnqueueTotal = new NopLongCounter();
+
+- public static List<Pair<InstrumentSelector, View>> getMetricsView() {
++ public static List<Pair<InstrumentSelector, ViewBuilder>> getMetricsView() {
+ return Lists.newArrayList();
+ }
+
+diff --git a/store/src/main/java/org/apache/rocketmq/store/plugin/AbstractPluginMessageStore.java b/store/src/main/java/org/apache/rocketmq/store/plugin/AbstractPluginMessageStore.java
+index 25e947512..ab9fc6da7 100644
+--- a/store/src/main/java/org/apache/rocketmq/store/plugin/AbstractPluginMessageStore.java
++++ b/store/src/main/java/org/apache/rocketmq/store/plugin/AbstractPluginMessageStore.java
+@@ -20,8 +20,7 @@ package org.apache.rocketmq.store.plugin;
+ import io.opentelemetry.api.common.AttributesBuilder;
+ import io.opentelemetry.api.metrics.Meter;
+ import io.opentelemetry.sdk.metrics.InstrumentSelector;
+-import io.opentelemetry.sdk.metrics.View;
+-
++import io.opentelemetry.sdk.metrics.ViewBuilder;
+ import java.nio.ByteBuffer;
+ import java.util.HashMap;
+ import java.util.LinkedList;
+@@ -29,7 +28,6 @@ import java.util.List;
+ import java.util.Set;
+ import java.util.concurrent.CompletableFuture;
+ import java.util.function.Supplier;
+-
+ import org.apache.rocketmq.common.Pair;
+ import org.apache.rocketmq.common.SystemClock;
+ import org.apache.rocketmq.common.message.MessageExt;
+@@ -643,7 +641,7 @@ public abstract class AbstractPluginMessageStore implements MessageStore {
+ }
+
+ @Override
+- public List<Pair<InstrumentSelector, View>> getMetricsView() {
++ public List<Pair<InstrumentSelector, ViewBuilder>> getMetricsView() {
+ return next.getMetricsView();
+ }
+
+diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredMessageStore.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredMessageStore.java
+index ced1fb818..5240ac8e9 100644
+--- a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredMessageStore.java
++++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredMessageStore.java
+@@ -21,7 +21,7 @@ import io.opentelemetry.api.common.Attributes;
+ import io.opentelemetry.api.common.AttributesBuilder;
+ import io.opentelemetry.api.metrics.Meter;
+ import io.opentelemetry.sdk.metrics.InstrumentSelector;
+-import io.opentelemetry.sdk.metrics.View;
++import io.opentelemetry.sdk.metrics.ViewBuilder;
+ import java.util.List;
+ import java.util.Set;
+ import java.util.concurrent.CompletableFuture;
+@@ -352,8 +352,8 @@ public class TieredMessageStore extends AbstractPluginMessageStore {
+ }
+
+ @Override
+- public List<Pair<InstrumentSelector, View>> getMetricsView() {
+- List<Pair<InstrumentSelector, View>> res = super.getMetricsView();
++ public List<Pair<InstrumentSelector, ViewBuilder>> getMetricsView() {
++ List<Pair<InstrumentSelector, ViewBuilder>> res = super.getMetricsView();
+ res.addAll(TieredStoreMetricsManager.getMetricsView());
+ return res;
+ }
+diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/metrics/TieredStoreMetricsManager.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/metrics/TieredStoreMetricsManager.java
+index 3ca0fb614..d8a07f0a7 100644
+--- a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/metrics/TieredStoreMetricsManager.java
++++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/metrics/TieredStoreMetricsManager.java
+@@ -27,6 +27,7 @@ import io.opentelemetry.sdk.metrics.Aggregation;
+ import io.opentelemetry.sdk.metrics.InstrumentSelector;
+ import io.opentelemetry.sdk.metrics.InstrumentType;
+ import io.opentelemetry.sdk.metrics.View;
++import io.opentelemetry.sdk.metrics.ViewBuilder;
+ import java.util.ArrayList;
+ import java.util.Arrays;
+ import java.util.HashMap;
+@@ -101,8 +102,8 @@ public class TieredStoreMetricsManager {
+ public static ObservableLongGauge storageSize = new NopObservableLongGauge();
+ public static ObservableLongGauge storageMessageReserveTime = new NopObservableLongGauge();
+
+- public static List<Pair<InstrumentSelector, View>> getMetricsView() {
+- ArrayList<Pair<InstrumentSelector, View>> res = new ArrayList<>();
++ public static List<Pair<InstrumentSelector, ViewBuilder>> getMetricsView() {
++ ArrayList<Pair<InstrumentSelector, ViewBuilder>> res = new ArrayList<>();
+
+ InstrumentSelector providerRpcLatencySelector = InstrumentSelector.builder()
+ .setType(InstrumentType.HISTOGRAM)
+@@ -114,10 +115,9 @@ public class TieredStoreMetricsManager {
+ .setName(HISTOGRAM_API_LATENCY)
+ .build();
+
+- View rpcLatencyView = View.builder()
++ ViewBuilder rpcLatencyViewBuilder = View.builder()
+ .setAggregation(Aggregation.explicitBucketHistogram(Arrays.asList(1d, 3d, 5d, 7d, 10d, 100d, 200d, 400d, 600d, 800d, 1d * 1000, 1d * 1500, 1d * 3000)))
+- .setDescription("tiered_store_rpc_latency_view")
+- .build();
++ .setDescription("tiered_store_rpc_latency_view");
+
+ InstrumentSelector uploadBufferSizeSelector = InstrumentSelector.builder()
+ .setType(InstrumentType.HISTOGRAM)
+@@ -129,15 +129,14 @@ public class TieredStoreMetricsManager {
+ .setName(HISTOGRAM_DOWNLOAD_BYTES)
+ .build();
+
+- View bufferSizeView = View.builder()
++ ViewBuilder bufferSizeViewBuilder = View.builder()
+ .setAggregation(Aggregation.explicitBucketHistogram(Arrays.asList(1d * TieredStoreUtil.KB, 10d * TieredStoreUtil.KB, 100d * TieredStoreUtil.KB, 1d * TieredStoreUtil.MB, 10d * TieredStoreUtil.MB, 32d * TieredStoreUtil.MB, 50d * TieredStoreUtil.MB, 100d * TieredStoreUtil.MB)))
+- .setDescription("tiered_store_buffer_size_view")
+- .build();
++ .setDescription("tiered_store_buffer_size_view");
+
+- res.add(new Pair<>(rpcLatencySelector, rpcLatencyView));
+- res.add(new Pair<>(providerRpcLatencySelector, rpcLatencyView));
+- res.add(new Pair<>(uploadBufferSizeSelector, bufferSizeView));
+- res.add(new Pair<>(downloadBufferSizeSelector, bufferSizeView));
++ res.add(new Pair<>(rpcLatencySelector, rpcLatencyViewBuilder));
++ res.add(new Pair<>(providerRpcLatencySelector, rpcLatencyViewBuilder));
++ res.add(new Pair<>(uploadBufferSizeSelector, bufferSizeViewBuilder));
++ res.add(new Pair<>(downloadBufferSizeSelector, bufferSizeViewBuilder));
+ return res;
+ }
+
+--
+2.32.0.windows.2
+
+
+From a4bcc2a74d8bec9c9d34565536e87df06e0b11c1 Mon Sep 17 00:00:00 2001
+From: Ziyi Tan <tanziyi0925@gmail.com>
+Date: Thu, 17 Aug 2023 13:53:48 +0800
+Subject: [PATCH 2/7] [ISSUE #7178] refresh metadata after broker startup
+
+Signed-off-by: Ziy1-Tan <ajb459684460@gmail.com>
+---
+ .../rocketmq/broker/BrokerController.java | 24 +++++++++----------
+ 1 file changed, 12 insertions(+), 12 deletions(-)
+
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java
+index 30b1d2299..13f9d002b 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java
+@@ -663,7 +663,7 @@ public class BrokerController {
+ BrokerController.this.getSlaveSynchronize().syncAll();
+ lastSyncTimeMs = System.currentTimeMillis();
+ }
+-
++
+ //timer checkpoint, latency-sensitive, so sync it more frequently
+ if (messageStoreConfig.isTimerWheelEnable()) {
+ BrokerController.this.getSlaveSynchronize().syncTimerCheckPoint();
+@@ -698,17 +698,6 @@ public class BrokerController {
+
+ initializeBrokerScheduledTasks();
+
+- this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
+- @Override
+- public void run() {
+- try {
+- BrokerController.this.brokerOuterAPI.refreshMetadata();
+- } catch (Exception e) {
+- LOG.error("ScheduledTask refresh metadata exception", e);
+- }
+- }
+- }, 10, 5, TimeUnit.SECONDS);
+-
+ if (this.brokerConfig.getNamesrvAddr() != null) {
+ this.updateNamesrvAddr();
+ LOG.info("Set user specified name server address: {}", this.brokerConfig.getNamesrvAddr());
+@@ -1682,6 +1671,17 @@ public class BrokerController {
+ if (brokerConfig.isSkipPreOnline()) {
+ startServiceWithoutCondition();
+ }
++
++ this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
++ @Override
++ public void run() {
++ try {
++ BrokerController.this.brokerOuterAPI.refreshMetadata();
++ } catch (Exception e) {
++ LOG.error("ScheduledTask refresh metadata exception", e);
++ }
++ }
++ }, 10, 5, TimeUnit.SECONDS);
+ }
+
+ protected void scheduleSendHeartbeat() {
+--
+2.32.0.windows.2
+
+
+From 3df1b9232af99944cb3d4d4d2d00c5a85cd3b57d Mon Sep 17 00:00:00 2001
+From: guyinyou <36399867+guyinyou@users.noreply.github.com>
+Date: Thu, 17 Aug 2023 13:59:04 +0800
+Subject: [PATCH 3/7] [ISSUE #7201] Remove the DefaultMessageStore.class
+ dependency in TransientStorePool
+
+Co-authored-by: guyinyou <guyinyou.gyy@alibaba-inc.com>
+---
+ .../rocketmq/store/AllocateMappedFileService.java | 6 +++---
+ .../apache/rocketmq/store/DefaultMessageStore.java | 7 +++++--
+ .../apache/rocketmq/store/TransientStorePool.java | 13 ++++---------
+ 3 files changed, 12 insertions(+), 14 deletions(-)
+
+diff --git a/store/src/main/java/org/apache/rocketmq/store/AllocateMappedFileService.java b/store/src/main/java/org/apache/rocketmq/store/AllocateMappedFileService.java
+index dca7d5325..c8420fea1 100644
+--- a/store/src/main/java/org/apache/rocketmq/store/AllocateMappedFileService.java
++++ b/store/src/main/java/org/apache/rocketmq/store/AllocateMappedFileService.java
+@@ -55,7 +55,7 @@ public class AllocateMappedFileService extends ServiceThread {
+ if (this.messageStore.isTransientStorePoolEnable()) {
+ if (this.messageStore.getMessageStoreConfig().isFastFailIfNoBufferInStorePool()
+ && BrokerRole.SLAVE != this.messageStore.getMessageStoreConfig().getBrokerRole()) { //if broker is slave, don't fast fail even no buffer in pool
+- canSubmitRequests = this.messageStore.getTransientStorePool().availableBufferNums() - this.requestQueue.size();
++ canSubmitRequests = this.messageStore.remainTransientStoreBufferNumbs() - this.requestQueue.size();
+ }
+ }
+
+@@ -65,7 +65,7 @@ public class AllocateMappedFileService extends ServiceThread {
+ if (nextPutOK) {
+ if (canSubmitRequests <= 0) {
+ log.warn("[NOTIFYME]TransientStorePool is not enough, so create mapped file error, " +
+- "RequestQueueSize : {}, StorePoolSize: {}", this.requestQueue.size(), this.messageStore.getTransientStorePool().availableBufferNums());
++ "RequestQueueSize : {}, StorePoolSize: {}", this.requestQueue.size(), this.messageStore.remainTransientStoreBufferNumbs());
+ this.requestTable.remove(nextFilePath);
+ return null;
+ }
+@@ -81,7 +81,7 @@ public class AllocateMappedFileService extends ServiceThread {
+ if (nextNextPutOK) {
+ if (canSubmitRequests <= 0) {
+ log.warn("[NOTIFYME]TransientStorePool is not enough, so skip preallocate mapped file, " +
+- "RequestQueueSize : {}, StorePoolSize: {}", this.requestQueue.size(), this.messageStore.getTransientStorePool().availableBufferNums());
++ "RequestQueueSize : {}, StorePoolSize: {}", this.requestQueue.size(), this.messageStore.remainTransientStoreBufferNumbs());
+ this.requestTable.remove(nextNextFilePath);
+ } else {
+ boolean offerOK = this.requestQueue.offer(nextNextReq);
+diff --git a/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java b/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java
+index 6115ead59..f2a54ddf6 100644
+--- a/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java
++++ b/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java
+@@ -250,7 +250,7 @@ public class DefaultMessageStore implements MessageStore {
+ this.reputMessageService = new ConcurrentReputMessageService();
+ }
+
+- this.transientStorePool = new TransientStorePool(this);
++ this.transientStorePool = new TransientStorePool(messageStoreConfig.getTransientStorePoolSize(), messageStoreConfig.getMappedFileSizeCommitLog());
+
+ this.scheduledExecutorService =
+ Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("StoreScheduledThread", getBrokerIdentity()));
+@@ -1983,7 +1983,10 @@ public class DefaultMessageStore implements MessageStore {
+ }
+
+ public int remainTransientStoreBufferNumbs() {
+- return this.transientStorePool.availableBufferNums();
++ if (this.isTransientStorePoolEnable()) {
++ return this.transientStorePool.availableBufferNums();
++ }
++ return Integer.MAX_VALUE;
+ }
+
+ @Override
+diff --git a/store/src/main/java/org/apache/rocketmq/store/TransientStorePool.java b/store/src/main/java/org/apache/rocketmq/store/TransientStorePool.java
+index 8c1a5338b..0d42ee69e 100644
+--- a/store/src/main/java/org/apache/rocketmq/store/TransientStorePool.java
++++ b/store/src/main/java/org/apache/rocketmq/store/TransientStorePool.java
+@@ -33,13 +33,11 @@ public class TransientStorePool {
+ private final int poolSize;
+ private final int fileSize;
+ private final Deque<ByteBuffer> availableBuffers;
+- private final DefaultMessageStore messageStore;
+ private volatile boolean isRealCommit = true;
+
+- public TransientStorePool(final DefaultMessageStore messageStore) {
+- this.messageStore = messageStore;
+- this.poolSize = messageStore.getMessageStoreConfig().getTransientStorePoolSize();
+- this.fileSize = messageStore.getMessageStoreConfig().getMappedFileSizeCommitLog();
++ public TransientStorePool(final int poolSize, final int fileSize) {
++ this.poolSize = poolSize;
++ this.fileSize = fileSize;
+ this.availableBuffers = new ConcurrentLinkedDeque<>();
+ }
+
+@@ -81,10 +79,7 @@ public class TransientStorePool {
+ }
+
+ public int availableBufferNums() {
+- if (messageStore.isTransientStorePoolEnable()) {
+- return availableBuffers.size();
+- }
+- return Integer.MAX_VALUE;
++ return availableBuffers.size();
+ }
+
+ public boolean isRealCommit() {
+--
+2.32.0.windows.2
+
+
+From 2b93e1e32fd458d9df2091e89ea259ddd4d54061 Mon Sep 17 00:00:00 2001
+From: iamgd67 <iamgd67@sina.com>
+Date: Thu, 17 Aug 2023 15:31:14 +0800
+Subject: [PATCH 4/7] Update mqbroker to use runbroker.sh instead of
+ runserver.sh when use --enable-proxy (#7150)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Update mqbroker to use runbroker.sh instead of runserver.sh when enabling `--enable-proxy`
+this allow JVM `heap` and `gc` configuration using broker's settings instead of other common serverices'(proxy,namenode, etc).
+our main purpose, like the filename `mqbroker` suggest, is to start broker (which embeds a proxy), so use broker's config is reasonable
+
+chinese version
+mqbroker的--enable-proxy选项是启动内嵌了proxy的broker而不是内嵌broker的proxy而且broker的工作量和重要程度大于proxy所以使用broker的gc和heap配置更合适
+---
+ distribution/bin/mqbroker | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/distribution/bin/mqbroker b/distribution/bin/mqbroker
+index 3758ed597..35eb93c44 100644
+--- a/distribution/bin/mqbroker
++++ b/distribution/bin/mqbroker
+@@ -68,11 +68,11 @@ if [ "$enable_proxy" = true ]; then
+ if [ "$broker_config" != "" ]; then
+ args_for_proxy=${args_for_proxy}" -bc "${broker_config}
+ fi
+- sh ${ROCKETMQ_HOME}/bin/runserver.sh -Drmq.logback.configurationFile=$ROCKETMQ_HOME/conf/rmq.proxy.logback.xml org.apache.rocketmq.proxy.ProxyStartup ${args_for_proxy}
++ sh ${ROCKETMQ_HOME}/bin/runbroker.sh -Drmq.logback.configurationFile=$ROCKETMQ_HOME/conf/rmq.proxy.logback.xml org.apache.rocketmq.proxy.ProxyStartup ${args_for_proxy}
+ else
+ args_for_broker=$other_args
+ if [ "$broker_config" != "" ]; then
+ args_for_broker=${args_for_broker}" -c "${broker_config}
+ fi
+ sh ${ROCKETMQ_HOME}/bin/runbroker.sh -Drmq.logback.configurationFile=$ROCKETMQ_HOME/conf/rmq.broker.logback.xml org.apache.rocketmq.broker.BrokerStartup ${args_for_broker}
+-fi
+\ No newline at end of file
++fi
+--
+2.32.0.windows.2
+
+
+From 05e7cde610255ed9410fffb0f153efe7c2c8a326 Mon Sep 17 00:00:00 2001
+From: yao-wenbin <ywb992134@163.com>
+Date: Fri, 18 Aug 2023 09:49:59 +0800
+Subject: [PATCH 5/7] [ISSUE #7042] maven-compile job failed, Because TlsTest's
+ serverRejectsSSLClient test case will throw TooLongFrameException (#7179)
+
+---
+ .../remoting/netty/NettyRemotingServer.java | 2 +-
+ .../java/org/apache/rocketmq/remoting/TlsTest.java | 14 ++++++++++++--
+ 2 files changed, 13 insertions(+), 3 deletions(-)
+
+diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java
+index 90e358ce3..17f138f86 100644
+--- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java
++++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java
+@@ -502,7 +502,7 @@ public class NettyRemotingServer extends NettyRemotingAbstract implements Remoti
+ case DISABLED:
+ ctx.close();
+ log.warn("Clients intend to establish an SSL connection while this server is running in SSL disabled mode");
+- break;
++ throw new UnsupportedOperationException("The NettyRemotingServer in SSL disabled mode doesn't support ssl client");
+ case PERMISSIVE:
+ case ENFORCING:
+ if (null != sslContext) {
+diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/TlsTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/TlsTest.java
+index de7edbbfb..a4890d73d 100644
+--- a/remoting/src/test/java/org/apache/rocketmq/remoting/TlsTest.java
++++ b/remoting/src/test/java/org/apache/rocketmq/remoting/TlsTest.java
+@@ -144,8 +144,13 @@ public class TlsTest {
+ tlsClientKeyPath = "";
+ tlsClientCertPath = "";
+ clientConfig.setUseTLS(false);
+- } else if ("serverRejectsSSLClient".equals(name.getMethodName())) {
++ } else if ("disabledServerRejectsSSLClient".equals(name.getMethodName())) {
+ tlsMode = TlsMode.DISABLED;
++ } else if ("disabledServerAcceptUnAuthClient".equals(name.getMethodName())) {
++ tlsMode = TlsMode.DISABLED;
++ tlsClientKeyPath = "";
++ tlsClientCertPath = "";
++ clientConfig.setUseTLS(false);
+ } else if ("reloadSslContextForServer".equals(name.getMethodName())) {
+ tlsClientAuthServer = false;
+ tlsServerNeedClientAuth = "none";
+@@ -211,7 +216,7 @@ public class TlsTest {
+ }
+
+ @Test
+- public void serverRejectsSSLClient() throws Exception {
++ public void disabledServerRejectsSSLClient() throws Exception {
+ try {
+ RemotingCommand response = remotingClient.invokeSync(getServerAddress(), createRequest(), 1000 * 5);
+ failBecauseExceptionWasNotThrown(RemotingSendRequestException.class);
+@@ -219,6 +224,11 @@ public class TlsTest {
+ }
+ }
+
++ @Test
++ public void disabledServerAcceptUnAuthClient() throws Exception {
++ requestThenAssertResponse();
++ }
++
+ /**
+ * Tests that a server configured to require client authentication refuses to accept connections
+ * from a client that has an untrusted certificate.
+--
+2.32.0.windows.2
+
+
+From 72d796f2b20b3ec6aebca8c004d9275d7c749a95 Mon Sep 17 00:00:00 2001
+From: lk <xdkxlk@outlook.com>
+Date: Fri, 18 Aug 2023 11:55:39 +0800
+Subject: [PATCH 6/7] [ISSUE #7205] support batch ack for pop orderly (#7206)
+
+---
+ .../broker/processor/AckMessageProcessor.java | 99 ++++++-----
+ .../rocketmq/client/impl/MQClientAPIImpl.java | 91 ++++++++--
+ .../test/client/rmq/RMQPopClient.java | 22 +++
+ .../client/consumer/pop/BasePopNormally.java | 6 +
+ .../test/client/consumer/pop/BatchAckIT.java | 159 ++++++++++++++++++
+ 5 files changed, 322 insertions(+), 55 deletions(-)
+ create mode 100644 test/src/test/java/org/apache/rocketmq/test/client/consumer/pop/BatchAckIT.java
+
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java
+index 687811409..244b459d6 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java
+@@ -19,6 +19,7 @@ package org.apache.rocketmq.broker.processor;
+ import com.alibaba.fastjson.JSON;
+ import io.netty.channel.Channel;
+ import io.netty.channel.ChannelHandlerContext;
++import java.util.BitSet;
+ import org.apache.rocketmq.broker.BrokerController;
+ import org.apache.rocketmq.broker.metrics.PopMetricsManager;
+ import org.apache.rocketmq.common.KeyBuilder;
+@@ -186,46 +187,7 @@ public class AckMessageProcessor implements NettyRequestProcessor {
+ invisibleTime = ExtraInfoUtil.getInvisibleTime(extraInfo);
+
+ if (rqId == KeyBuilder.POP_ORDER_REVIVE_QUEUE) {
+- // order
+- String lockKey = topic + PopAckConstants.SPLIT + consumeGroup + PopAckConstants.SPLIT + qId;
+- long oldOffset = this.brokerController.getConsumerOffsetManager().queryOffset(consumeGroup, topic, qId);
+- if (ackOffset < oldOffset) {
+- return;
+- }
+- while (!this.brokerController.getPopMessageProcessor().getQueueLockManager().tryLock(lockKey)) {
+- }
+- try {
+- oldOffset = this.brokerController.getConsumerOffsetManager().queryOffset(consumeGroup, topic, qId);
+- if (ackOffset < oldOffset) {
+- return;
+- }
+- long nextOffset = brokerController.getConsumerOrderInfoManager().commitAndNext(
+- topic, consumeGroup,
+- qId, ackOffset,
+- popTime);
+- if (nextOffset > -1) {
+- if (!this.brokerController.getConsumerOffsetManager().hasOffsetReset(
+- topic, consumeGroup, qId)) {
+- this.brokerController.getConsumerOffsetManager().commitOffset(channel.remoteAddress().toString(),
+- consumeGroup, topic, qId, nextOffset);
+- }
+- if (!this.brokerController.getConsumerOrderInfoManager().checkBlock(null, topic,
+- consumeGroup, qId, invisibleTime)) {
+- this.brokerController.getPopMessageProcessor().notifyMessageArriving(
+- topic, consumeGroup, qId);
+- }
+- } else if (nextOffset == -1) {
+- String errorInfo = String.format("offset is illegal, key:%s, old:%d, commit:%d, next:%d, %s",
+- lockKey, oldOffset, ackOffset, nextOffset, channel.remoteAddress());
+- POP_LOGGER.warn(errorInfo);
+- response.setCode(ResponseCode.MESSAGE_ILLEGAL);
+- response.setRemark(errorInfo);
+- return;
+- }
+- } finally {
+- this.brokerController.getPopMessageProcessor().getQueueLockManager().unLock(lockKey);
+- }
+- brokerController.getPopInflightMessageCounter().decrementInFlightMessageNum(topic, consumeGroup, popTime, qId, ackCount);
++ ackOrderly(topic, consumeGroup, qId, ackOffset, popTime, invisibleTime, channel, response);
+ return;
+ }
+
+@@ -250,17 +212,22 @@ public class AckMessageProcessor implements NettyRequestProcessor {
+ }
+
+ BatchAckMsg batchAckMsg = new BatchAckMsg();
+- for (int i = 0; batchAck.getBitSet() != null && i < batchAck.getBitSet().length(); i++) {
+- if (!batchAck.getBitSet().get(i)) {
+- continue;
++ BitSet bitSet = batchAck.getBitSet();
++ for (int i = bitSet.nextSetBit(0); i >= 0; i = bitSet.nextSetBit(i + 1)) {
++ if (i == Integer.MAX_VALUE) {
++ break;
+ }
+ long offset = startOffset + i;
+ if (offset < minOffset || offset > maxOffset) {
+ continue;
+ }
+- batchAckMsg.getAckOffsetList().add(offset);
++ if (rqId == KeyBuilder.POP_ORDER_REVIVE_QUEUE) {
++ ackOrderly(topic, consumeGroup, qId, offset, popTime, invisibleTime, channel, response);
++ } else {
++ batchAckMsg.getAckOffsetList().add(offset);
++ }
+ }
+- if (batchAckMsg.getAckOffsetList().isEmpty()) {
++ if (rqId == KeyBuilder.POP_ORDER_REVIVE_QUEUE || batchAckMsg.getAckOffsetList().isEmpty()) {
+ return;
+ }
+
+@@ -311,4 +278,46 @@ public class AckMessageProcessor implements NettyRequestProcessor {
+ PopMetricsManager.incPopReviveAckPutCount(ackMsg, putMessageResult.getPutMessageStatus());
+ brokerController.getPopInflightMessageCounter().decrementInFlightMessageNum(topic, consumeGroup, popTime, qId, ackCount);
+ }
++
++ protected void ackOrderly(String topic, String consumeGroup, int qId, long ackOffset, long popTime, long invisibleTime, Channel channel, RemotingCommand response) {
++ String lockKey = topic + PopAckConstants.SPLIT + consumeGroup + PopAckConstants.SPLIT + qId;
++ long oldOffset = this.brokerController.getConsumerOffsetManager().queryOffset(consumeGroup, topic, qId);
++ if (ackOffset < oldOffset) {
++ return;
++ }
++ while (!this.brokerController.getPopMessageProcessor().getQueueLockManager().tryLock(lockKey)) {
++ }
++ try {
++ oldOffset = this.brokerController.getConsumerOffsetManager().queryOffset(consumeGroup, topic, qId);
++ if (ackOffset < oldOffset) {
++ return;
++ }
++ long nextOffset = brokerController.getConsumerOrderInfoManager().commitAndNext(
++ topic, consumeGroup,
++ qId, ackOffset,
++ popTime);
++ if (nextOffset > -1) {
++ if (!this.brokerController.getConsumerOffsetManager().hasOffsetReset(
++ topic, consumeGroup, qId)) {
++ this.brokerController.getConsumerOffsetManager().commitOffset(channel.remoteAddress().toString(),
++ consumeGroup, topic, qId, nextOffset);
++ }
++ if (!this.brokerController.getConsumerOrderInfoManager().checkBlock(null, topic,
++ consumeGroup, qId, invisibleTime)) {
++ this.brokerController.getPopMessageProcessor().notifyMessageArriving(
++ topic, consumeGroup, qId);
++ }
++ } else if (nextOffset == -1) {
++ String errorInfo = String.format("offset is illegal, key:%s, old:%d, commit:%d, next:%d, %s",
++ lockKey, oldOffset, ackOffset, nextOffset, channel.remoteAddress());
++ POP_LOGGER.warn(errorInfo);
++ response.setCode(ResponseCode.MESSAGE_ILLEGAL);
++ response.setRemark(errorInfo);
++ return;
++ }
++ } finally {
++ this.brokerController.getPopMessageProcessor().getQueueLockManager().unLock(lockKey);
++ }
++ brokerController.getPopInflightMessageCounter().decrementInFlightMessageNum(topic, consumeGroup, popTime, qId, 1);
++ }
+ }
+diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java
+index 5101ffc8e..213c26fd6 100644
+--- a/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java
++++ b/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java
+@@ -21,6 +21,7 @@ import java.io.UnsupportedEncodingException;
+ import java.nio.ByteBuffer;
+ import java.util.ArrayList;
+ import java.util.Arrays;
++import java.util.BitSet;
+ import java.util.Collections;
+ import java.util.HashMap;
+ import java.util.Iterator;
+@@ -54,6 +55,7 @@ import org.apache.rocketmq.client.producer.SendCallback;
+ import org.apache.rocketmq.client.producer.SendResult;
+ import org.apache.rocketmq.client.producer.SendStatus;
+ import org.apache.rocketmq.common.AclConfig;
++import org.apache.rocketmq.common.BoundaryType;
+ import org.apache.rocketmq.common.MQVersion;
+ import org.apache.rocketmq.common.MixAll;
+ import org.apache.rocketmq.common.Pair;
+@@ -76,7 +78,8 @@ import org.apache.rocketmq.common.namesrv.NameServerUpdateCallback;
+ import org.apache.rocketmq.common.namesrv.TopAddressing;
+ import org.apache.rocketmq.common.sysflag.PullSysFlag;
+ import org.apache.rocketmq.common.topic.TopicValidator;
+-import org.apache.rocketmq.common.BoundaryType;
++import org.apache.rocketmq.logging.org.slf4j.Logger;
++import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+ import org.apache.rocketmq.remoting.CommandCustomHeader;
+ import org.apache.rocketmq.remoting.InvokeCallback;
+ import org.apache.rocketmq.remoting.RPCHook;
+@@ -101,7 +104,10 @@ import org.apache.rocketmq.remoting.protocol.RequestCode;
+ import org.apache.rocketmq.remoting.protocol.ResponseCode;
+ import org.apache.rocketmq.remoting.protocol.admin.ConsumeStats;
+ import org.apache.rocketmq.remoting.protocol.admin.TopicStatsTable;
++import org.apache.rocketmq.remoting.protocol.body.BatchAck;
++import org.apache.rocketmq.remoting.protocol.body.BatchAckMessageRequestBody;
+ import org.apache.rocketmq.remoting.protocol.body.BrokerMemberGroup;
++import org.apache.rocketmq.remoting.protocol.body.BrokerReplicasInfo;
+ import org.apache.rocketmq.remoting.protocol.body.BrokerStatsData;
+ import org.apache.rocketmq.remoting.protocol.body.CheckClientRequestBody;
+ import org.apache.rocketmq.remoting.protocol.body.ClusterAclVersionInfo;
+@@ -114,7 +120,6 @@ import org.apache.rocketmq.remoting.protocol.body.EpochEntryCache;
+ import org.apache.rocketmq.remoting.protocol.body.GetConsumerStatusBody;
+ import org.apache.rocketmq.remoting.protocol.body.GroupList;
+ import org.apache.rocketmq.remoting.protocol.body.HARuntimeInfo;
+-import org.apache.rocketmq.remoting.protocol.body.BrokerReplicasInfo;
+ import org.apache.rocketmq.remoting.protocol.body.KVTable;
+ import org.apache.rocketmq.remoting.protocol.body.LockBatchRequestBody;
+ import org.apache.rocketmq.remoting.protocol.body.LockBatchResponseBody;
+@@ -196,6 +201,10 @@ import org.apache.rocketmq.remoting.protocol.header.UpdateGlobalWhiteAddrsConfig
+ import org.apache.rocketmq.remoting.protocol.header.UpdateGroupForbiddenRequestHeader;
+ import org.apache.rocketmq.remoting.protocol.header.ViewBrokerStatsDataRequestHeader;
+ import org.apache.rocketmq.remoting.protocol.header.ViewMessageRequestHeader;
++import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterRequestHeader;
++import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterResponseHeader;
++import org.apache.rocketmq.remoting.protocol.header.controller.GetMetaDataResponseHeader;
++import org.apache.rocketmq.remoting.protocol.header.controller.admin.CleanControllerBrokerDataRequestHeader;
+ import org.apache.rocketmq.remoting.protocol.header.namesrv.AddWritePermOfBrokerRequestHeader;
+ import org.apache.rocketmq.remoting.protocol.header.namesrv.AddWritePermOfBrokerResponseHeader;
+ import org.apache.rocketmq.remoting.protocol.header.namesrv.DeleteKVConfigRequestHeader;
+@@ -207,10 +216,6 @@ import org.apache.rocketmq.remoting.protocol.header.namesrv.GetRouteInfoRequestH
+ import org.apache.rocketmq.remoting.protocol.header.namesrv.PutKVConfigRequestHeader;
+ import org.apache.rocketmq.remoting.protocol.header.namesrv.WipeWritePermOfBrokerRequestHeader;
+ import org.apache.rocketmq.remoting.protocol.header.namesrv.WipeWritePermOfBrokerResponseHeader;
+-import org.apache.rocketmq.remoting.protocol.header.controller.admin.CleanControllerBrokerDataRequestHeader;
+-import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterRequestHeader;
+-import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterResponseHeader;
+-import org.apache.rocketmq.remoting.protocol.header.controller.GetMetaDataResponseHeader;
+ import org.apache.rocketmq.remoting.protocol.heartbeat.HeartbeatData;
+ import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel;
+ import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData;
+@@ -221,8 +226,6 @@ import org.apache.rocketmq.remoting.protocol.subscription.GroupForbidden;
+ import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig;
+ import org.apache.rocketmq.remoting.rpchook.DynamicalExtFieldRPCHook;
+ import org.apache.rocketmq.remoting.rpchook.StreamTypeRPCHook;
+-import org.apache.rocketmq.logging.org.slf4j.Logger;
+-import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+
+ import static org.apache.rocketmq.remoting.protocol.RemotingSysResponseCode.SUCCESS;
+
+@@ -885,9 +888,77 @@ public class MQClientAPIImpl implements NameServerUpdateCallback {
+ final String addr,
+ final long timeOut,
+ final AckCallback ackCallback,
+- final AckMessageRequestHeader requestHeader //
++ final AckMessageRequestHeader requestHeader
++ ) throws RemotingException, MQBrokerException, InterruptedException {
++ ackMessageAsync(addr, timeOut, ackCallback, requestHeader, null);
++ }
++
++ public void batchAckMessageAsync(
++ final String addr,
++ final long timeOut,
++ final AckCallback ackCallback,
++ final String topic,
++ final String consumerGroup,
++ final List<String> extraInfoList
++ ) throws RemotingException, MQBrokerException, InterruptedException {
++ String brokerName = null;
++ Map<String, BatchAck> batchAckMap = new HashMap<>();
++ for (String extraInfo : extraInfoList) {
++ String[] extraInfoData = ExtraInfoUtil.split(extraInfo);
++ if (brokerName == null) {
++ brokerName = ExtraInfoUtil.getBrokerName(extraInfoData);
++ }
++ String mergeKey = ExtraInfoUtil.getRetry(extraInfoData) + "@" +
++ ExtraInfoUtil.getQueueId(extraInfoData) + "@" +
++ ExtraInfoUtil.getCkQueueOffset(extraInfoData) + "@" +
++ ExtraInfoUtil.getPopTime(extraInfoData);
++ BatchAck bAck = batchAckMap.computeIfAbsent(mergeKey, k -> {
++ BatchAck newBatchAck = new BatchAck();
++ newBatchAck.setConsumerGroup(consumerGroup);
++ newBatchAck.setTopic(topic);
++ newBatchAck.setRetry(ExtraInfoUtil.getRetry(extraInfoData));
++ newBatchAck.setStartOffset(ExtraInfoUtil.getCkQueueOffset(extraInfoData));
++ newBatchAck.setQueueId(ExtraInfoUtil.getQueueId(extraInfoData));
++ newBatchAck.setReviveQueueId(ExtraInfoUtil.getReviveQid(extraInfoData));
++ newBatchAck.setPopTime(ExtraInfoUtil.getPopTime(extraInfoData));
++ newBatchAck.setInvisibleTime(ExtraInfoUtil.getInvisibleTime(extraInfoData));
++ newBatchAck.setBitSet(new BitSet());
++ return newBatchAck;
++ });
++ bAck.getBitSet().set((int) (ExtraInfoUtil.getQueueOffset(extraInfoData) - ExtraInfoUtil.getCkQueueOffset(extraInfoData)));
++ }
++
++ BatchAckMessageRequestBody requestBody = new BatchAckMessageRequestBody();
++ requestBody.setBrokerName(brokerName);
++ requestBody.setAcks(new ArrayList<>(batchAckMap.values()));
++ batchAckMessageAsync(addr, timeOut, ackCallback, requestBody);
++ }
++
++ public void batchAckMessageAsync(
++ final String addr,
++ final long timeOut,
++ final AckCallback ackCallback,
++ final BatchAckMessageRequestBody requestBody
+ ) throws RemotingException, MQBrokerException, InterruptedException {
+- final RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.ACK_MESSAGE, requestHeader);
++ ackMessageAsync(addr, timeOut, ackCallback, null, requestBody);
++ }
++
++ protected void ackMessageAsync(
++ final String addr,
++ final long timeOut,
++ final AckCallback ackCallback,
++ final AckMessageRequestHeader requestHeader,
++ final BatchAckMessageRequestBody requestBody
++ ) throws RemotingException, MQBrokerException, InterruptedException {
++ RemotingCommand request;
++ if (requestHeader != null) {
++ request = RemotingCommand.createRequestCommand(RequestCode.ACK_MESSAGE, requestHeader);
++ } else {
++ request = RemotingCommand.createRequestCommand(RequestCode.BATCH_ACK_MESSAGE, null);
++ if (requestBody != null) {
++ request.setBody(requestBody.encode());
++ }
++ }
+ this.remotingClient.invokeAsync(addr, request, timeOut, new BaseInvokeCallback(MQClientAPIImpl.this) {
+
+ @Override
+diff --git a/test/src/main/java/org/apache/rocketmq/test/client/rmq/RMQPopClient.java b/test/src/main/java/org/apache/rocketmq/test/client/rmq/RMQPopClient.java
+index 496bd6da4..09c60c0b4 100644
+--- a/test/src/main/java/org/apache/rocketmq/test/client/rmq/RMQPopClient.java
++++ b/test/src/main/java/org/apache/rocketmq/test/client/rmq/RMQPopClient.java
+@@ -17,6 +17,7 @@
+
+ package org.apache.rocketmq.test.client.rmq;
+
++import java.util.List;
+ import java.util.concurrent.CompletableFuture;
+ import org.apache.rocketmq.client.ClientConfig;
+ import org.apache.rocketmq.client.consumer.AckCallback;
+@@ -140,6 +141,27 @@ public class RMQPopClient implements MQConsumer {
+ return future;
+ }
+
++ public CompletableFuture<AckResult> batchAckMessageAsync(String brokerAddr, String topic, String consumerGroup,
++ List<String> extraInfoList) {
++ CompletableFuture<AckResult> future = new CompletableFuture<>();
++ try {
++ this.mqClientAPI.batchAckMessageAsync(brokerAddr, DEFAULT_TIMEOUT, new AckCallback() {
++ @Override
++ public void onSuccess(AckResult ackResult) {
++ future.complete(ackResult);
++ }
++
++ @Override
++ public void onException(Throwable e) {
++ future.completeExceptionally(e);
++ }
++ }, topic, consumerGroup, extraInfoList);
++ } catch (Throwable t) {
++ future.completeExceptionally(t);
++ }
++ return future;
++ }
++
+ public CompletableFuture<AckResult> changeInvisibleTimeAsync(String brokerAddr, String brokerName, String topic,
+ String consumerGroup, String extraInfo, long invisibleTime) {
+ String[] extraInfoStrs = ExtraInfoUtil.split(extraInfo);
+diff --git a/test/src/test/java/org/apache/rocketmq/test/client/consumer/pop/BasePopNormally.java b/test/src/test/java/org/apache/rocketmq/test/client/consumer/pop/BasePopNormally.java
+index 952fbe3f5..2e29b95a5 100644
+--- a/test/src/test/java/org/apache/rocketmq/test/client/consumer/pop/BasePopNormally.java
++++ b/test/src/test/java/org/apache/rocketmq/test/client/consumer/pop/BasePopNormally.java
+@@ -63,4 +63,10 @@ public class BasePopNormally extends BasePop {
+ brokerAddr, messageQueue, invisibleTime, maxNums, group, timeout, true,
+ ConsumeInitMode.MIN, false, ExpressionType.TAG, "*");
+ }
++
++ protected CompletableFuture<PopResult> popMessageAsync(long invisibleTime, int maxNums) {
++ return client.popMessageAsync(
++ brokerAddr, messageQueue, invisibleTime, maxNums, group, 3000, false,
++ ConsumeInitMode.MIN, false, ExpressionType.TAG, "*");
++ }
+ }
+diff --git a/test/src/test/java/org/apache/rocketmq/test/client/consumer/pop/BatchAckIT.java b/test/src/test/java/org/apache/rocketmq/test/client/consumer/pop/BatchAckIT.java
+new file mode 100644
+index 000000000..ec9153ccc
+--- /dev/null
++++ b/test/src/test/java/org/apache/rocketmq/test/client/consumer/pop/BatchAckIT.java
+@@ -0,0 +1,159 @@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one or more
++ * contributor license agreements. See the NOTICE file distributed with
++ * this work for additional information regarding copyright ownership.
++ * The ASF licenses this file to You under the Apache License, Version 2.0
++ * (the "License"); you may not use this file except in compliance with
++ * the License. You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package org.apache.rocketmq.test.client.consumer.pop;
++
++import java.time.Duration;
++import java.util.ArrayList;
++import java.util.List;
++import java.util.concurrent.CompletableFuture;
++import java.util.concurrent.TimeUnit;
++import java.util.concurrent.atomic.AtomicInteger;
++import java.util.function.Supplier;
++import org.apache.rocketmq.client.consumer.AckResult;
++import org.apache.rocketmq.client.consumer.AckStatus;
++import org.apache.rocketmq.client.consumer.PopResult;
++import org.apache.rocketmq.client.consumer.PopStatus;
++import org.apache.rocketmq.common.attribute.CQType;
++import org.apache.rocketmq.common.attribute.TopicMessageType;
++import org.apache.rocketmq.common.constant.ConsumeInitMode;
++import org.apache.rocketmq.common.filter.ExpressionType;
++import org.apache.rocketmq.common.message.MessageConst;
++import org.apache.rocketmq.common.message.MessageExt;
++import org.apache.rocketmq.common.message.MessageQueue;
++import org.apache.rocketmq.test.base.IntegrationTestBase;
++import org.apache.rocketmq.test.client.rmq.RMQNormalProducer;
++import org.apache.rocketmq.test.client.rmq.RMQPopClient;
++import org.apache.rocketmq.test.util.MQRandomUtils;
++import org.junit.After;
++import org.junit.Before;
++import org.junit.Test;
++
++import static org.awaitility.Awaitility.await;
++import static org.junit.Assert.assertEquals;
++
++public class BatchAckIT extends BasePop {
++
++ protected String topic;
++ protected String group;
++ protected RMQNormalProducer producer = null;
++ protected RMQPopClient client = null;
++ protected String brokerAddr;
++ protected MessageQueue messageQueue;
++
++ @Before
++ public void setUp() {
++ brokerAddr = brokerController1.getBrokerAddr();
++ topic = MQRandomUtils.getRandomTopic();
++ group = initConsumerGroup();
++ IntegrationTestBase.initTopic(topic, NAMESRV_ADDR, BROKER1_NAME, 8, CQType.SimpleCQ, TopicMessageType.NORMAL);
++ producer = getProducer(NAMESRV_ADDR, topic);
++ client = getRMQPopClient();
++ messageQueue = new MessageQueue(topic, BROKER1_NAME, -1);
++ }
++
++ @After
++ public void tearDown() {
++ shutdown();
++ }
++
++ @Test
++ public void testBatchAckNormallyWithPopBuffer() throws Throwable {
++ brokerController1.getBrokerConfig().setEnablePopBufferMerge(true);
++ brokerController2.getBrokerConfig().setEnablePopBufferMerge(true);
++
++ testBatchAck(() -> {
++ try {
++ return popMessageAsync().get();
++ } catch (Exception e) {
++ throw new RuntimeException(e);
++ }
++ });
++ }
++
++ @Test
++ public void testBatchAckNormallyWithOutPopBuffer() throws Throwable {
++ brokerController1.getBrokerConfig().setEnablePopBufferMerge(false);
++ brokerController2.getBrokerConfig().setEnablePopBufferMerge(false);
++
++ testBatchAck(() -> {
++ try {
++ return popMessageAsync().get();
++ } catch (Exception e) {
++ throw new RuntimeException(e);
++ }
++ });
++ }
++
++ @Test
++ public void testBatchAckOrderly() throws Throwable {
++ testBatchAck(() -> {
++ try {
++ return popMessageOrderlyAsync().get();
++ } catch (Exception e) {
++ throw new RuntimeException(e);
++ }
++ });
++ }
++
++ public void testBatchAck(Supplier<PopResult> popResultSupplier) throws Throwable {
++ // Send 10 messages but do not ack, let them enter the retry topic
++ producer.send(10);
++ AtomicInteger firstMsgRcvNum = new AtomicInteger();
++ await().atMost(Duration.ofSeconds(3)).untilAsserted(() -> {
++ PopResult popResult = popResultSupplier.get();
++ if (popResult.getPopStatus().equals(PopStatus.FOUND)) {
++ firstMsgRcvNum.addAndGet(popResult.getMsgFoundList().size());
++ }
++ assertEquals(10, firstMsgRcvNum.get());
++ });
++ // sleep 6s, expect messages to enter the retry topic
++ TimeUnit.SECONDS.sleep(6);
++
++ producer.send(20);
++ List<String> extraInfoList = new ArrayList<>();
++ await().atMost(Duration.ofSeconds(3)).untilAsserted(() -> {
++ PopResult popResult = popResultSupplier.get();
++ if (popResult.getPopStatus().equals(PopStatus.FOUND)) {
++ for (MessageExt messageExt : popResult.getMsgFoundList()) {
++ extraInfoList.add(messageExt.getProperty(MessageConst.PROPERTY_POP_CK));
++ }
++ }
++ assertEquals(30, extraInfoList.size());
++ });
++
++ AckResult ackResult = client.batchAckMessageAsync(brokerAddr, topic, group, extraInfoList).get();
++ assertEquals(AckStatus.OK, ackResult.getStatus());
++
++ // sleep 6s, expected that messages that have been acked will not be re-consumed
++ TimeUnit.SECONDS.sleep(6);
++ PopResult popResult = popResultSupplier.get();
++ assertEquals(PopStatus.POLLING_NOT_FOUND, popResult.getPopStatus());
++ }
++
++ private CompletableFuture<PopResult> popMessageAsync() {
++ return client.popMessageAsync(
++ brokerAddr, messageQueue, Duration.ofSeconds(3).toMillis(), 30, group, 3000, false,
++ ConsumeInitMode.MIN, false, ExpressionType.TAG, "*");
++ }
++
++ private CompletableFuture<PopResult> popMessageOrderlyAsync() {
++ return client.popMessageAsync(
++ brokerAddr, messageQueue, Duration.ofSeconds(3).toMillis(), 30, group, 3000, false,
++ ConsumeInitMode.MIN, true, ExpressionType.TAG, "*", null);
++ }
++}
+--
+2.32.0.windows.2
+
+
+From cc16a1b51216e1e80c22011b8b01e060bb4af8b3 Mon Sep 17 00:00:00 2001
+From: rongtong <jinrongtong5@163.com>
+Date: Tue, 22 Aug 2023 10:42:25 +0800
+Subject: [PATCH 7/7] Set table reference the same object for
+ setSubscriptionGroupTable method (#7204)
+
+---
+ .../broker/subscription/SubscriptionGroupManager.java | 5 +----
+ 1 file changed, 1 insertion(+), 4 deletions(-)
+
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/subscription/SubscriptionGroupManager.java b/broker/src/main/java/org/apache/rocketmq/broker/subscription/SubscriptionGroupManager.java
+index 74e39c0fe..e63b93058 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/subscription/SubscriptionGroupManager.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/subscription/SubscriptionGroupManager.java
+@@ -341,10 +341,7 @@ public class SubscriptionGroupManager extends ConfigManager {
+
+
+ public void setSubscriptionGroupTable(ConcurrentMap<String, SubscriptionGroupConfig> subscriptionGroupTable) {
+- this.subscriptionGroupTable.clear();
+- for (String key : subscriptionGroupTable.keySet()) {
+- putSubscriptionGroupConfig(subscriptionGroupTable.get(key));
+- }
++ this.subscriptionGroupTable = subscriptionGroupTable;
+ }
+
+ public boolean containsSubscriptionGroup(String group) {
+--
+2.32.0.windows.2
+
diff --git a/patch011-backport-optimize-opentelemetry-metric-config.patch b/patch011-backport-optimize-opentelemetry-metric-config.patch
new file mode 100644
index 000000000..b59d7e2c7
--- /dev/null
+++ b/patch011-backport-optimize-opentelemetry-metric-config.patch
@@ -0,0 +1,2081 @@
+From 744167bd01fab6821b4d5ae1794dc845153d5156 Mon Sep 17 00:00:00 2001
+From: Ziyi Tan <tanziyi0925@gmail.com>
+Date: Wed, 23 Aug 2023 08:32:17 +0800
+Subject: [PATCH 1/7] [ISSUE #7142] Add command `RocksDBConfigToJson` to
+ inspect rocksdb content (#7180)
+
+* feat: add command `RocksDBConfigToJson` to inspect rocksdb content
+
+Signed-off-by: Ziy1-Tan <ajb4596984460@gmail.com>
+
+* refactor: fix style
+
+---------
+
+Signed-off-by: Ziy1-Tan <ajb4596984460@gmail.com>
+Co-authored-by: Ziy1-Tan <ajb4596984460@gmail.com>
+---
+ .../tools/command/MQAdminStartup.java | 2 +
+ .../metadata/RocksDBConfigToJsonCommand.java | 118 ++++++++++++++++++
+ .../metadata/KvConfigToJsonCommandTest.java | 65 ++++++++++
+ 3 files changed, 185 insertions(+)
+ create mode 100644 tools/src/main/java/org/apache/rocketmq/tools/command/metadata/RocksDBConfigToJsonCommand.java
+ create mode 100644 tools/src/test/java/org/apache/rocketmq/tools/command/metadata/KvConfigToJsonCommandTest.java
+
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/MQAdminStartup.java b/tools/src/main/java/org/apache/rocketmq/tools/command/MQAdminStartup.java
+index 890125ca0..324aa1856 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/MQAdminStartup.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/MQAdminStartup.java
+@@ -80,6 +80,7 @@ import org.apache.rocketmq.tools.command.message.QueryMsgByOffsetSubCommand;
+ import org.apache.rocketmq.tools.command.message.QueryMsgByUniqueKeySubCommand;
+ import org.apache.rocketmq.tools.command.message.QueryMsgTraceByIdSubCommand;
+ import org.apache.rocketmq.tools.command.message.SendMessageCommand;
++import org.apache.rocketmq.tools.command.metadata.RocksDBConfigToJsonCommand;
+ import org.apache.rocketmq.tools.command.namesrv.AddWritePermSubCommand;
+ import org.apache.rocketmq.tools.command.namesrv.DeleteKvConfigCommand;
+ import org.apache.rocketmq.tools.command.namesrv.GetNamesrvConfigCommand;
+@@ -211,6 +212,7 @@ public class MQAdminStartup {
+
+ initCommand(new ClusterListSubCommand());
+ initCommand(new TopicListSubCommand());
++ initCommand(new RocksDBConfigToJsonCommand());
+
+ initCommand(new UpdateKvConfigCommand());
+ initCommand(new DeleteKvConfigCommand());
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/metadata/RocksDBConfigToJsonCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/metadata/RocksDBConfigToJsonCommand.java
+new file mode 100644
+index 000000000..3053f4684
+--- /dev/null
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/metadata/RocksDBConfigToJsonCommand.java
+@@ -0,0 +1,118 @@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one or more
++ * contributor license agreements. See the NOTICE file distributed with
++ * this work for additional information regarding copyright ownership.
++ * The ASF licenses this file to You under the Apache License, Version 2.0
++ * (the "License"); you may not use this file except in compliance with
++ * the License. You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++package org.apache.rocketmq.tools.command.metadata;
++
++import com.alibaba.fastjson.JSONObject;
++import org.apache.commons.cli.CommandLine;
++import org.apache.commons.cli.Option;
++import org.apache.commons.cli.Options;
++import org.apache.commons.lang3.StringUtils;
++import org.apache.rocketmq.common.config.RocksDBConfigManager;
++import org.apache.rocketmq.common.utils.DataConverter;
++import org.apache.rocketmq.remoting.RPCHook;
++import org.apache.rocketmq.tools.command.SubCommand;
++import org.apache.rocketmq.tools.command.SubCommandException;
++
++import java.io.File;
++import java.util.HashMap;
++import java.util.Map;
++
++public class RocksDBConfigToJsonCommand implements SubCommand {
++ private static final String TOPICS_JSON_CONFIG = "topics";
++ private static final String SUBSCRIPTION_GROUP_JSON_CONFIG = "subscriptionGroups";
++
++ @Override
++ public String commandName() {
++ return "rocksDBConfigToJson";
++ }
++
++ @Override
++ public String commandDesc() {
++ return "Convert RocksDB kv config (topics/subscriptionGroups) to json";
++ }
++
++ @Override
++ public Options buildCommandlineOptions(Options options) {
++ Option pathOption = new Option("p", "path", true,
++ "Absolute path to the metadata directory");
++ pathOption.setRequired(true);
++ options.addOption(pathOption);
++
++ Option configTypeOption = new Option("t", "configType", true, "Name of kv config, e.g. " +
++ "topics/subscriptionGroups");
++ configTypeOption.setRequired(true);
++ options.addOption(configTypeOption);
++
++ return options;
++ }
++
++ @Override
++ public void execute(CommandLine commandLine, Options options, RPCHook rpcHook) throws SubCommandException {
++ String path = commandLine.getOptionValue("path").trim();
++ if (StringUtils.isEmpty(path) || !new File(path).exists()) {
++ System.out.print("Rocksdb path is invalid.\n");
++ return;
++ }
++
++ String configType = commandLine.getOptionValue("configType").trim().toLowerCase();
++
++ final long memTableFlushInterval = 60 * 60 * 1000L;
++ RocksDBConfigManager kvConfigManager = new RocksDBConfigManager(memTableFlushInterval);
++ try {
++ if (TOPICS_JSON_CONFIG.toLowerCase().equals(configType)) {
++ // for topics.json
++ final Map<String, JSONObject> topicsJsonConfig = new HashMap<>();
++ final Map<String, JSONObject> topicConfigTable = new HashMap<>();
++ boolean isLoad = kvConfigManager.load(path, (key, value) -> {
++ final String topic = new String(key, DataConverter.charset);
++ final String topicConfig = new String(value, DataConverter.charset);
++ final JSONObject jsonObject = JSONObject.parseObject(topicConfig);
++ topicConfigTable.put(topic, jsonObject);
++ });
++
++ if (isLoad) {
++ topicsJsonConfig.put("topicConfigTable", (JSONObject) JSONObject.toJSON(topicConfigTable));
++ final String topicsJsonStr = JSONObject.toJSONString(topicsJsonConfig, true);
++ System.out.print(topicsJsonStr + "\n");
++ return;
++ }
++ }
++ if (SUBSCRIPTION_GROUP_JSON_CONFIG.toLowerCase().equals(configType)) {
++ // for subscriptionGroup.json
++ final Map<String, JSONObject> subscriptionGroupJsonConfig = new HashMap<>();
++ final Map<String, JSONObject> subscriptionGroupTable = new HashMap<>();
++ boolean isLoad = kvConfigManager.load(path, (key, value) -> {
++ final String subscriptionGroup = new String(key, DataConverter.charset);
++ final String subscriptionGroupConfig = new String(value, DataConverter.charset);
++ final JSONObject jsonObject = JSONObject.parseObject(subscriptionGroupConfig);
++ subscriptionGroupTable.put(subscriptionGroup, jsonObject);
++ });
++
++ if (isLoad) {
++ subscriptionGroupJsonConfig.put("subscriptionGroupTable",
++ (JSONObject) JSONObject.toJSON(subscriptionGroupTable));
++ final String subscriptionGroupJsonStr = JSONObject.toJSONString(subscriptionGroupJsonConfig, true);
++ System.out.print(subscriptionGroupJsonStr + "\n");
++ return;
++ }
++ }
++ System.out.print("Config type was not recognized, configType=" + configType + "\n");
++ } finally {
++ kvConfigManager.stop();
++ }
++ }
++}
+diff --git a/tools/src/test/java/org/apache/rocketmq/tools/command/metadata/KvConfigToJsonCommandTest.java b/tools/src/test/java/org/apache/rocketmq/tools/command/metadata/KvConfigToJsonCommandTest.java
+new file mode 100644
+index 000000000..b2f66c7b0
+--- /dev/null
++++ b/tools/src/test/java/org/apache/rocketmq/tools/command/metadata/KvConfigToJsonCommandTest.java
+@@ -0,0 +1,65 @@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one or more
++ * contributor license agreements. See the NOTICE file distributed with
++ * this work for additional information regarding copyright ownership.
++ * The ASF licenses this file to You under the Apache License, Version 2.0
++ * (the "License"); you may not use this file except in compliance with
++ * the License. You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++package org.apache.rocketmq.tools.command.metadata;
++
++import org.apache.commons.cli.CommandLine;
++import org.apache.commons.cli.DefaultParser;
++import org.apache.commons.cli.Options;
++import org.apache.rocketmq.srvutil.ServerUtil;
++import org.apache.rocketmq.tools.command.SubCommandException;
++import org.junit.Test;
++
++import java.io.File;
++
++import static org.assertj.core.api.Assertions.assertThat;
++
++public class KvConfigToJsonCommandTest {
++ private static final String BASE_PATH = System.getProperty("user.home") + File.separator + "store/config/";
++
++ @Test
++ public void testExecute() throws SubCommandException {
++ {
++ String[] cases = new String[]{"topics", "subscriptionGroups"};
++ for (String c : cases) {
++ RocksDBConfigToJsonCommand cmd = new RocksDBConfigToJsonCommand();
++ Options options = ServerUtil.buildCommandlineOptions(new Options());
++ String[] subargs = new String[]{"-p " + BASE_PATH + c, "-t " + c};
++ final CommandLine commandLine = ServerUtil.parseCmdLine("mqadmin " + cmd.commandName(), subargs,
++ cmd.buildCommandlineOptions(options), new DefaultParser());
++ cmd.execute(commandLine, options, null);
++ assertThat(commandLine.getOptionValue("p").trim()).isEqualTo(BASE_PATH + c);
++ assertThat(commandLine.getOptionValue("t").trim()).isEqualTo(c);
++ }
++ }
++ // invalid cases
++ {
++ String[][] cases = new String[][]{
++ {"-p " + BASE_PATH + "tmpPath", "-t topics"},
++ {"-p ", "-t topics"},
++ {"-p " + BASE_PATH + "topics", "-t invalid_type"}
++ };
++
++ for (String[] c : cases) {
++ RocksDBConfigToJsonCommand cmd = new RocksDBConfigToJsonCommand();
++ Options options = ServerUtil.buildCommandlineOptions(new Options());
++ final CommandLine commandLine = ServerUtil.parseCmdLine("mqadmin " + cmd.commandName(), c,
++ cmd.buildCommandlineOptions(options), new DefaultParser());
++ cmd.execute(commandLine, options, null);
++ }
++ }
++ }
++}
+--
+2.32.0.windows.2
+
+
+From bdede35db365a49b211cdc249c68b0f60a3df46d Mon Sep 17 00:00:00 2001
+From: mxsm <ljbmxsm@gmail.com>
+Date: Wed, 23 Aug 2023 08:34:56 +0800
+Subject: [PATCH 2/7] [ISSUE #7124] Fix the typos in the code comments (#7125)
+
+---
+ .../apache/rocketmq/broker/processor/ReplyMessageProcessor.java | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/ReplyMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/ReplyMessageProcessor.java
+index b2db356c8..d3bb048f7 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/processor/ReplyMessageProcessor.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/ReplyMessageProcessor.java
+@@ -234,7 +234,7 @@ public class ReplyMessageProcessor extends AbstractSendMessageProcessor {
+ } else {
+ response.setCode(ResponseCode.SUCCESS);
+ response.setRemark(null);
+- //set to zore to avoid client decoding exception
++ //set to zero to avoid client decoding exception
+ responseHeader.setMsgId("0");
+ responseHeader.setQueueId(queueIdInt);
+ responseHeader.setQueueOffset(0L);
+--
+2.32.0.windows.2
+
+
+From 9bb73b9a38548b99ac5126c40380c3c2e7fc586e Mon Sep 17 00:00:00 2001
+From: lizhimins <707364882@qq.com>
+Date: Wed, 23 Aug 2023 09:46:27 +0800
+Subject: [PATCH 3/7] [#ISSUE 7222] Bug fix and refactoring of the Indexfile in
+ tiered storage (#7224)
+
+---
+ .../tieredstore/file/TieredIndexFile.java | 38 +++++++--
+ .../tieredstore/file/TieredIndexFileTest.java | 84 +++++--------------
+ 2 files changed, 52 insertions(+), 70 deletions(-)
+
+diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredIndexFile.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredIndexFile.java
+index 50beb01ae..eda5e0106 100644
+--- a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredIndexFile.java
++++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredIndexFile.java
+@@ -16,6 +16,7 @@
+ */
+ package org.apache.rocketmq.tieredstore.file;
+
++import com.google.common.annotations.VisibleForTesting;
+ import java.io.File;
+ import java.io.IOException;
+ import java.nio.ByteBuffer;
+@@ -99,7 +100,7 @@ public class TieredIndexFile {
+ this::doScheduleTask, 10, 10, TimeUnit.SECONDS);
+ }
+
+- private void doScheduleTask() {
++ protected void doScheduleTask() {
+ try {
+ curFileLock.lock();
+ try {
+@@ -145,6 +146,11 @@ public class TieredIndexFile {
+ }
+ }
+
++ @VisibleForTesting
++ public MappedFile getPreMappedFile() {
++ return preMappedFile;
++ }
++
+ private void initFile() throws IOException {
+ curMappedFile = new DefaultMappedFile(curFilePath, fileMaxSize);
+ initIndexFileHeader(curMappedFile);
+@@ -156,19 +162,26 @@ public class TieredIndexFile {
+
+ if (isFileSealed(curMappedFile)) {
+ if (preFileExists) {
+- preFile.delete();
++ if (preFile.delete()) {
++ logger.info("Pre IndexFile deleted success", preFilepath);
++ } else {
++ logger.error("Pre IndexFile deleted failed", preFilepath);
++ }
+ }
+ boolean rename = curMappedFile.renameTo(preFilepath);
+ if (rename) {
+ preMappedFile = curMappedFile;
+ curMappedFile = new DefaultMappedFile(curFilePath, fileMaxSize);
++ initIndexFileHeader(curMappedFile);
+ preFileExists = true;
+ }
+ }
++
+ if (preFileExists) {
+ synchronized (TieredIndexFile.class) {
+ if (inflightCompactFuture.isDone()) {
+- inflightCompactFuture = TieredStoreExecutor.compactIndexFileExecutor.submit(new CompactTask(storeConfig, preMappedFile, flatFile), null);
++ inflightCompactFuture = TieredStoreExecutor.compactIndexFileExecutor.submit(
++ new CompactTask(storeConfig, preMappedFile, flatFile), null);
+ }
+ }
+ }
+@@ -261,7 +274,8 @@ public class TieredIndexFile {
+ }
+ }
+
+- public CompletableFuture<List<Pair<Long, ByteBuffer>>> queryAsync(String topic, String key, long beginTime, long endTime) {
++ public CompletableFuture<List<Pair<Long, ByteBuffer>>> queryAsync(String topic, String key, long beginTime,
++ long endTime) {
+ int hashCode = indexKeyHashMethod(buildKey(topic, key));
+ int slotPosition = hashCode % maxHashSlotNum;
+ List<TieredFileSegment> fileSegmentList = flatFile.getFileListByTime(beginTime, endTime);
+@@ -355,7 +369,7 @@ public class TieredIndexFile {
+ private final int fileMaxSize;
+ private MappedFile originFile;
+ private TieredFlatFile fileQueue;
+- private final MappedFile compactFile;
++ private MappedFile compactFile;
+
+ public CompactTask(TieredMessageStoreConfig storeConfig, MappedFile originFile,
+ TieredFlatFile fileQueue) throws IOException {
+@@ -381,6 +395,17 @@ public class TieredIndexFile {
+ } catch (Throwable throwable) {
+ logger.error("TieredIndexFile#compactTask: compact index file failed:", throwable);
+ }
++
++ try {
++ if (originFile != null) {
++ originFile.destroy(-1);
++ }
++ if (compactFile != null) {
++ compactFile.destroy(-1);
++ }
++ } catch (Throwable throwable) {
++ logger.error("TieredIndexFile#compactTask: destroy index file failed:", throwable);
++ }
+ }
+
+ public void compact() {
+@@ -396,6 +421,8 @@ public class TieredIndexFile {
+ fileQueue.commit(true);
+ compactFile.destroy(-1);
+ originFile.destroy(-1);
++ compactFile = null;
++ originFile = null;
+ }
+
+ private void buildCompactFile() {
+@@ -414,6 +441,7 @@ public class TieredIndexFile {
+ if (slotValue != -1) {
+ int indexTotalSize = 0;
+ int indexPosition = slotValue;
++
+ while (indexPosition >= 0 && indexPosition < maxIndexNum) {
+ int indexOffset = INDEX_FILE_HEADER_SIZE + maxHashSlotNum * INDEX_FILE_HASH_SLOT_SIZE
+ + indexPosition * INDEX_FILE_HASH_ORIGIN_INDEX_SIZE;
+diff --git a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/file/TieredIndexFileTest.java b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/file/TieredIndexFileTest.java
+index 7ef49578d..262d6645b 100644
+--- a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/file/TieredIndexFileTest.java
++++ b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/file/TieredIndexFileTest.java
+@@ -19,9 +19,8 @@ package org.apache.rocketmq.tieredstore.file;
+ import com.sun.jna.Platform;
+ import java.io.IOException;
+ import java.nio.ByteBuffer;
++import java.time.Duration;
+ import java.util.List;
+-import java.util.concurrent.TimeUnit;
+-import org.apache.commons.lang3.SystemUtils;
+ import org.apache.commons.lang3.tuple.Pair;
+ import org.apache.rocketmq.common.message.MessageQueue;
+ import org.apache.rocketmq.tieredstore.TieredStoreTestUtil;
+@@ -31,9 +30,7 @@ import org.apache.rocketmq.tieredstore.util.TieredStoreUtil;
+ import org.awaitility.Awaitility;
+ import org.junit.After;
+ import org.junit.Assert;
+-import org.junit.Assume;
+ import org.junit.Before;
+-import org.junit.Ignore;
+ import org.junit.Test;
+
+ public class TieredIndexFileTest {
+@@ -45,11 +42,12 @@ public class TieredIndexFileTest {
+ @Before
+ public void setUp() {
+ storeConfig = new TieredMessageStoreConfig();
++ storeConfig.setBrokerName("IndexFileBroker");
+ storeConfig.setStorePathRootDir(storePath);
+- storeConfig.setTieredBackendServiceProvider("org.apache.rocketmq.tieredstore.provider.memory.MemoryFileSegment");
+- storeConfig.setTieredStoreIndexFileMaxHashSlotNum(2);
+- storeConfig.setTieredStoreIndexFileMaxIndexNum(3);
+- mq = new MessageQueue("TieredIndexFileTest", storeConfig.getBrokerName(), 1);
++ storeConfig.setTieredBackendServiceProvider("org.apache.rocketmq.tieredstore.provider.posix.PosixFileSegment");
++ storeConfig.setTieredStoreIndexFileMaxHashSlotNum(5);
++ storeConfig.setTieredStoreIndexFileMaxIndexNum(20);
++ mq = new MessageQueue("IndexFileTest", storeConfig.getBrokerName(), 1);
+ TieredStoreUtil.getMetadataStore(storeConfig);
+ TieredStoreExecutor.init();
+ }
+@@ -61,77 +59,33 @@ public class TieredIndexFileTest {
+ TieredStoreExecutor.shutdown();
+ }
+
+- @Ignore
+ @Test
+ public void testAppendAndQuery() throws IOException, ClassNotFoundException, NoSuchMethodException {
+ if (Platform.isWindows()) {
+ return;
+ }
+
+- // skip this test on windows
+- Assume.assumeFalse(SystemUtils.IS_OS_WINDOWS);
+-
+ TieredFileAllocator fileQueueFactory = new TieredFileAllocator(storeConfig);
+ TieredIndexFile indexFile = new TieredIndexFile(fileQueueFactory, storePath);
++
+ indexFile.append(mq, 0, "key3", 3, 300, 1000);
+ indexFile.append(mq, 0, "key2", 2, 200, 1100);
+ indexFile.append(mq, 0, "key1", 1, 100, 1200);
+
+- Awaitility.waitAtMost(5, TimeUnit.SECONDS)
+- .until(() -> {
+- List<Pair<Long, ByteBuffer>> indexList = indexFile.queryAsync(mq.getTopic(), "key1", 1000, 1200).join();
+- if (indexList.size() != 1) {
+- return false;
+- }
+-
+- ByteBuffer indexBuffer = indexList.get(0).getValue();
+- Assert.assertEquals(TieredIndexFile.INDEX_FILE_HASH_COMPACT_INDEX_SIZE * 2, indexBuffer.remaining());
+-
+- Assert.assertEquals(1, indexBuffer.getLong(4 + 4 + 4));
+- Assert.assertEquals(100, indexBuffer.getInt(4 + 4 + 4 + 8));
+- Assert.assertEquals(200, indexBuffer.getInt(4 + 4 + 4 + 8 + 4));
+-
+- Assert.assertEquals(3, indexBuffer.getLong(TieredIndexFile.INDEX_FILE_HASH_COMPACT_INDEX_SIZE + 4 + 4 + 4));
+- Assert.assertEquals(300, indexBuffer.getInt(TieredIndexFile.INDEX_FILE_HASH_COMPACT_INDEX_SIZE + 4 + 4 + 4 + 8));
+- Assert.assertEquals(0, indexBuffer.getInt(TieredIndexFile.INDEX_FILE_HASH_COMPACT_INDEX_SIZE + 4 + 4 + 4 + 8 + 4));
+- return true;
+- });
+-
+- indexFile.append(mq, 0, "key4", 4, 400, 1300);
+- indexFile.append(mq, 0, "key4", 4, 400, 1300);
+- indexFile.append(mq, 0, "key4", 4, 400, 1300);
+-
+- Awaitility.waitAtMost(5, TimeUnit.SECONDS)
+- .until(() -> {
+- List<Pair<Long, ByteBuffer>> indexList = indexFile.queryAsync(mq.getTopic(), "key4", 1300, 1300).join();
+- if (indexList.size() != 1) {
+- return false;
+- }
+-
+- ByteBuffer indexBuffer = indexList.get(0).getValue();
+- Assert.assertEquals(TieredIndexFile.INDEX_FILE_HASH_COMPACT_INDEX_SIZE * 3, indexBuffer.remaining());
+- Assert.assertEquals(4, indexBuffer.getLong(4 + 4 + 4));
+- Assert.assertEquals(400, indexBuffer.getInt(4 + 4 + 4 + 8));
+- Assert.assertEquals(0, indexBuffer.getInt(4 + 4 + 4 + 8 + 4));
+- return true;
+- });
+-
+- List<Pair<Long, ByteBuffer>> indexList = indexFile.queryAsync(mq.getTopic(), "key1", 1300, 1300).join();
++ // do not do schedule task here
++ TieredStoreExecutor.shutdown();
++ List<Pair<Long, ByteBuffer>> indexList =
++ indexFile.queryAsync(mq.getTopic(), "key1", 1000, 1200).join();
+ Assert.assertEquals(0, indexList.size());
+
+- indexList = indexFile.queryAsync(mq.getTopic(), "key4", 1200, 1300).join();
+- Assert.assertEquals(2, indexList.size());
+-
+- ByteBuffer indexBuffer = indexList.get(0).getValue();
+- Assert.assertEquals(TieredIndexFile.INDEX_FILE_HASH_COMPACT_INDEX_SIZE * 3, indexBuffer.remaining());
+- Assert.assertEquals(4, indexBuffer.getLong(4 + 4 + 4));
+- Assert.assertEquals(400, indexBuffer.getInt(4 + 4 + 4 + 8));
+- Assert.assertEquals(0, indexBuffer.getInt(4 + 4 + 4 + 8 + 4));
++ // do compaction once
++ TieredStoreExecutor.init();
++ storeConfig.setTieredStoreIndexFileRollingIdleInterval(0);
++ indexFile.doScheduleTask();
++ Awaitility.await().atMost(Duration.ofSeconds(10))
++ .until(() -> !indexFile.getPreMappedFile().getFile().exists());
+
+- indexBuffer = indexList.get(1).getValue();
+- Assert.assertEquals(TieredIndexFile.INDEX_FILE_HASH_COMPACT_INDEX_SIZE, indexBuffer.remaining());
+- Assert.assertEquals(2, indexBuffer.getLong(4 + 4 + 4));
+- Assert.assertEquals(200, indexBuffer.getInt(4 + 4 + 4 + 8));
+- Assert.assertEquals(100, indexBuffer.getInt(4 + 4 + 4 + 8 + 4));
++ indexList = indexFile.queryAsync(mq.getTopic(), "key1", 1000, 1200).join();
++ Assert.assertEquals(1, indexList.size());
+ }
+ }
+--
+2.32.0.windows.2
+
+
+From 69c26d3d29cde7b4484ecd112ab9224f9f42bf45 Mon Sep 17 00:00:00 2001
+From: guyinyou <36399867+guyinyou@users.noreply.github.com>
+Date: Wed, 23 Aug 2023 10:27:52 +0800
+Subject: [PATCH 4/7] [ISSUE #7228] Converge the use of some important
+ variables for some class
+
+---
+ .../apache/rocketmq/store/ConsumeQueue.java | 16 ++++++------
+ .../rocketmq/store/MappedFileQueue.java | 26 +++++++++++--------
+ .../store/MultiPathMappedFileQueue.java | 4 +--
+ 3 files changed, 24 insertions(+), 22 deletions(-)
+
+diff --git a/store/src/main/java/org/apache/rocketmq/store/ConsumeQueue.java b/store/src/main/java/org/apache/rocketmq/store/ConsumeQueue.java
+index a0b886eb0..56bee2af3 100644
+--- a/store/src/main/java/org/apache/rocketmq/store/ConsumeQueue.java
++++ b/store/src/main/java/org/apache/rocketmq/store/ConsumeQueue.java
+@@ -145,7 +145,7 @@ public class ConsumeQueue implements ConsumeQueueInterface, FileQueueLifeCycle {
+
+ if (offset >= 0 && size > 0) {
+ mappedFileOffset = i + CQ_STORE_UNIT_SIZE;
+- this.maxPhysicOffset = offset + size;
++ this.setMaxPhysicOffset(offset + size);
+ if (isExtAddr(tagsCode)) {
+ maxExtAddr = tagsCode;
+ }
+@@ -409,7 +409,7 @@ public class ConsumeQueue implements ConsumeQueueInterface, FileQueueLifeCycle {
+
+ int logicFileSize = this.mappedFileSize;
+
+- this.maxPhysicOffset = phyOffset;
++ this.setMaxPhysicOffset(phyOffset);
+ long maxExtAddr = 1;
+ boolean shouldDeleteFile = false;
+ while (true) {
+@@ -435,7 +435,7 @@ public class ConsumeQueue implements ConsumeQueueInterface, FileQueueLifeCycle {
+ mappedFile.setWrotePosition(pos);
+ mappedFile.setCommittedPosition(pos);
+ mappedFile.setFlushedPosition(pos);
+- this.maxPhysicOffset = offset + size;
++ this.setMaxPhysicOffset(offset + size);
+ // This maybe not take effect, when not every consume queue has extend file.
+ if (isExtAddr(tagsCode)) {
+ maxExtAddr = tagsCode;
+@@ -453,7 +453,7 @@ public class ConsumeQueue implements ConsumeQueueInterface, FileQueueLifeCycle {
+ mappedFile.setWrotePosition(pos);
+ mappedFile.setCommittedPosition(pos);
+ mappedFile.setFlushedPosition(pos);
+- this.maxPhysicOffset = offset + size;
++ this.setMaxPhysicOffset(offset + size);
+ if (isExtAddr(tagsCode)) {
+ maxExtAddr = tagsCode;
+ }
+@@ -881,8 +881,8 @@ public class ConsumeQueue implements ConsumeQueueInterface, FileQueueLifeCycle {
+ private boolean putMessagePositionInfo(final long offset, final int size, final long tagsCode,
+ final long cqOffset) {
+
+- if (offset + size <= this.maxPhysicOffset) {
+- log.warn("Maybe try to build consume queue repeatedly maxPhysicOffset={} phyOffset={}", maxPhysicOffset, offset);
++ if (offset + size <= this.getMaxPhysicOffset()) {
++ log.warn("Maybe try to build consume queue repeatedly maxPhysicOffset={} phyOffset={}", this.getMaxPhysicOffset(), offset);
+ return true;
+ }
+
+@@ -926,7 +926,7 @@ public class ConsumeQueue implements ConsumeQueueInterface, FileQueueLifeCycle {
+ );
+ }
+ }
+- this.maxPhysicOffset = offset + size;
++ this.setMaxPhysicOffset(offset + size);
+ return mappedFile.appendMessage(this.byteBufferIndex.array());
+ }
+ return false;
+@@ -1130,7 +1130,7 @@ public class ConsumeQueue implements ConsumeQueueInterface, FileQueueLifeCycle {
+
+ @Override
+ public void destroy() {
+- this.maxPhysicOffset = -1;
++ this.setMaxPhysicOffset(-1);
+ this.minLogicOffset = 0;
+ this.mappedFileQueue.destroy();
+ if (isExtReadEnable()) {
+diff --git a/store/src/main/java/org/apache/rocketmq/store/MappedFileQueue.java b/store/src/main/java/org/apache/rocketmq/store/MappedFileQueue.java
+index 0bc70642f..32b90d14f 100644
+--- a/store/src/main/java/org/apache/rocketmq/store/MappedFileQueue.java
++++ b/store/src/main/java/org/apache/rocketmq/store/MappedFileQueue.java
+@@ -285,7 +285,7 @@ public class MappedFileQueue implements Swappable {
+ if (this.mappedFiles.isEmpty())
+ return 0;
+
+- long committed = this.flushedWhere;
++ long committed = this.getFlushedWhere();
+ if (committed != 0) {
+ MappedFile mappedFile = this.getLastMappedFile(0, false);
+ if (mappedFile != null) {
+@@ -442,11 +442,11 @@ public class MappedFileQueue implements Swappable {
+ }
+
+ public long remainHowManyDataToCommit() {
+- return getMaxWrotePosition() - committedWhere;
++ return getMaxWrotePosition() - getCommittedWhere();
+ }
+
+ public long remainHowManyDataToFlush() {
+- return getMaxOffset() - flushedWhere;
++ return getMaxOffset() - this.getFlushedWhere();
+ }
+
+ public void deleteLastMappedFile() {
+@@ -616,15 +616,15 @@ public class MappedFileQueue implements Swappable {
+
+ public boolean flush(final int flushLeastPages) {
+ boolean result = true;
+- MappedFile mappedFile = this.findMappedFileByOffset(this.flushedWhere, this.flushedWhere == 0);
++ MappedFile mappedFile = this.findMappedFileByOffset(this.getFlushedWhere(), this.getFlushedWhere() == 0);
+ if (mappedFile != null) {
+ long tmpTimeStamp = mappedFile.getStoreTimestamp();
+ int offset = mappedFile.flush(flushLeastPages);
+ long where = mappedFile.getFileFromOffset() + offset;
+- result = where == this.flushedWhere;
+- this.flushedWhere = where;
++ result = where == this.getFlushedWhere();
++ this.setFlushedWhere(where);
+ if (0 == flushLeastPages) {
+- this.storeTimestamp = tmpTimeStamp;
++ this.setStoreTimestamp(tmpTimeStamp);
+ }
+ }
+
+@@ -633,12 +633,12 @@ public class MappedFileQueue implements Swappable {
+
+ public synchronized boolean commit(final int commitLeastPages) {
+ boolean result = true;
+- MappedFile mappedFile = this.findMappedFileByOffset(this.committedWhere, this.committedWhere == 0);
++ MappedFile mappedFile = this.findMappedFileByOffset(this.getCommittedWhere(), this.getCommittedWhere() == 0);
+ if (mappedFile != null) {
+ int offset = mappedFile.commit(commitLeastPages);
+ long where = mappedFile.getFileFromOffset() + offset;
+- result = where == this.committedWhere;
+- this.committedWhere = where;
++ result = where == this.getCommittedWhere();
++ this.setCommittedWhere(where);
+ }
+
+ return result;
+@@ -763,7 +763,7 @@ public class MappedFileQueue implements Swappable {
+ mf.destroy(1000 * 3);
+ }
+ this.mappedFiles.clear();
+- this.flushedWhere = 0;
++ this.setFlushedWhere(0);
+
+ // delete parent directory
+ File file = new File(storePath);
+@@ -848,6 +848,10 @@ public class MappedFileQueue implements Swappable {
+ return storeTimestamp;
+ }
+
++ public void setStoreTimestamp(long storeTimestamp) {
++ this.storeTimestamp = storeTimestamp;
++ }
++
+ public List<MappedFile> getMappedFiles() {
+ return mappedFiles;
+ }
+diff --git a/store/src/main/java/org/apache/rocketmq/store/MultiPathMappedFileQueue.java b/store/src/main/java/org/apache/rocketmq/store/MultiPathMappedFileQueue.java
+index 8f5af9438..8ff050dfe 100644
+--- a/store/src/main/java/org/apache/rocketmq/store/MultiPathMappedFileQueue.java
++++ b/store/src/main/java/org/apache/rocketmq/store/MultiPathMappedFileQueue.java
+@@ -16,7 +16,6 @@
+ */
+ package org.apache.rocketmq.store;
+
+-
+ import java.util.Arrays;
+ import java.util.HashSet;
+ import java.util.Set;
+@@ -113,8 +112,7 @@ public class MultiPathMappedFileQueue extends MappedFileQueue {
+ mf.destroy(1000 * 3);
+ }
+ this.mappedFiles.clear();
+- this.flushedWhere = 0;
+-
++ this.setFlushedWhere(0);
+
+ Set<String> storePathSet = getPaths();
+ storePathSet.addAll(getReadonlyPaths());
+--
+2.32.0.windows.2
+
+
+From 3884f595949462044c5cb3c236199bc1d7ad2341 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?=E7=9F=B3=E8=87=BB=E8=87=BB=28Steven=20shi=29?=
+ <shirenchuang@users.noreply.github.com>
+Date: Wed, 23 Aug 2023 11:10:30 +0800
+Subject: [PATCH 5/7] [ISSUE #7149] When creating and updating Topic, there
+ will be problems with permission settings (#7151)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+* [ISSUE #7149] fix bug : When creating and updating Topic, there will be problems with permission settings
+
+* [ISSUE #7149] fix bug : When creating and updating Topic, there will be problems with permission settings
+
+* [issue#7249]
+
+---------
+
+Co-authored-by: 十真 <shirenchuang.src@cainiao.com>
+---
+ .../main/java/org/apache/rocketmq/broker/BrokerController.java | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java
+index 13f9d002b..e8f943702 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java
+@@ -1733,7 +1733,8 @@ public class BrokerController {
+ new TopicConfig(topicConfig.getTopicName(),
+ topicConfig.getReadQueueNums(),
+ topicConfig.getWriteQueueNums(),
+- this.brokerConfig.getBrokerPermission(), topicConfig.getTopicSysFlag());
++ topicConfig.getPerm()
++ & this.brokerConfig.getBrokerPermission(), topicConfig.getTopicSysFlag());
+ } else {
+ registerTopicConfig = new TopicConfig(topicConfig);
+ }
+--
+2.32.0.windows.2
+
+
+From 017ad110475e8024585327b44f47e5e97aabc63b Mon Sep 17 00:00:00 2001
+From: echooymxq <echooy.mxq@gmail.com>
+Date: Wed, 23 Aug 2023 11:11:42 +0800
+Subject: [PATCH 6/7] [ISSUE #7219] Fix Concurrent modify syncStateSet and Mark
+ synchronizing frequently when shrink. (#7220)
+
+---
+ .../broker/controller/ReplicasManager.java | 29 ++++++++++---------
+ .../ha/autoswitch/AutoSwitchHAService.java | 21 ++++++++------
+ 2 files changed, 28 insertions(+), 22 deletions(-)
+
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java b/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java
+index abae7cdb0..37c82e434 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java
+@@ -542,7 +542,7 @@ public class ReplicasManager {
+ this.brokerMetadata.updateAndPersist(brokerConfig.getBrokerClusterName(), brokerConfig.getBrokerName(), tempBrokerMetadata.getBrokerId());
+ this.tempBrokerMetadata.clear();
+ this.brokerControllerId = this.brokerMetadata.getBrokerId();
+- this.haService.setBrokerControllerId(this.brokerControllerId);
++ this.haService.setLocalBrokerId(this.brokerControllerId);
+ return true;
+ } catch (Exception e) {
+ LOGGER.error("fail to create metadata file", e);
+@@ -594,7 +594,7 @@ public class ReplicasManager {
+ if (this.brokerMetadata.isLoaded()) {
+ this.registerState = RegisterState.CREATE_METADATA_FILE_DONE;
+ this.brokerControllerId = brokerMetadata.getBrokerId();
+- this.haService.setBrokerControllerId(this.brokerControllerId);
++ this.haService.setLocalBrokerId(this.brokerControllerId);
+ return;
+ }
+ // 2. check if temp metadata exist
+@@ -735,23 +735,26 @@ public class ReplicasManager {
+ if (this.checkSyncStateSetTaskFuture != null) {
+ this.checkSyncStateSetTaskFuture.cancel(false);
+ }
+- this.checkSyncStateSetTaskFuture = this.scheduledService.scheduleAtFixedRate(() -> {
+- checkSyncStateSetAndDoReport();
+- }, 3 * 1000, this.brokerConfig.getCheckSyncStateSetPeriod(), TimeUnit.MILLISECONDS);
++ this.checkSyncStateSetTaskFuture = this.scheduledService.scheduleAtFixedRate(this::checkSyncStateSetAndDoReport, 3 * 1000,
++ this.brokerConfig.getCheckSyncStateSetPeriod(), TimeUnit.MILLISECONDS);
+ }
+
+ private void checkSyncStateSetAndDoReport() {
+- final Set<Long> newSyncStateSet = this.haService.maybeShrinkSyncStateSet();
+- newSyncStateSet.add(this.brokerControllerId);
+- synchronized (this) {
+- if (this.syncStateSet != null) {
+- // Check if syncStateSet changed
+- if (this.syncStateSet.size() == newSyncStateSet.size() && this.syncStateSet.containsAll(newSyncStateSet)) {
+- return;
++ try {
++ final Set<Long> newSyncStateSet = this.haService.maybeShrinkSyncStateSet();
++ newSyncStateSet.add(this.brokerControllerId);
++ synchronized (this) {
++ if (this.syncStateSet != null) {
++ // Check if syncStateSet changed
++ if (this.syncStateSet.size() == newSyncStateSet.size() && this.syncStateSet.containsAll(newSyncStateSet)) {
++ return;
++ }
+ }
+ }
++ doReportSyncStateSetChanged(newSyncStateSet);
++ } catch (Exception e) {
++ LOGGER.error("Check syncStateSet error", e);
+ }
+- doReportSyncStateSetChanged(newSyncStateSet);
+ }
+
+ private void doReportSyncStateSetChanged(Set<Long> newSyncStateSet) {
+diff --git a/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAService.java b/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAService.java
+index 6dc734e0c..d5393fdca 100644
+--- a/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAService.java
++++ b/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAService.java
+@@ -41,6 +41,7 @@ import java.nio.channels.SocketChannel;
+ import java.util.ArrayList;
+ import java.util.HashSet;
+ import java.util.List;
++import java.util.Iterator;
+ import java.util.Map;
+ import java.util.Objects;
+ import java.util.Set;
+@@ -73,7 +74,7 @@ public class AutoSwitchHAService extends DefaultHAService {
+ private EpochFileCache epochCache;
+ private AutoSwitchHAClient haClient;
+
+- private Long brokerControllerId = null;
++ private Long localBrokerId = null;
+
+ public AutoSwitchHAService() {
+ }
+@@ -287,9 +288,11 @@ public class AutoSwitchHAService extends DefaultHAService {
+
+ // If the slaveBrokerId is in syncStateSet but not in connectionCaughtUpTimeTable,
+ // it means that the broker has not connected.
+- for (Long slaveBrokerId : newSyncStateSet) {
+- if (!this.connectionCaughtUpTimeTable.containsKey(slaveBrokerId)) {
+- newSyncStateSet.remove(slaveBrokerId);
++ Iterator<Long> iterator = newSyncStateSet.iterator();
++ while (iterator.hasNext()) {
++ Long slaveBrokerId = iterator.next();
++ if (!Objects.equals(slaveBrokerId, this.localBrokerId) && !this.connectionCaughtUpTimeTable.containsKey(slaveBrokerId)) {
++ iterator.remove();
+ isSyncStateSetChanged = true;
+ }
+ }
+@@ -419,7 +422,7 @@ public class AutoSwitchHAService extends DefaultHAService {
+ // To avoid the syncStateSet is not consistent with connectionList.
+ // Fix issue: https://github.com/apache/rocketmq/issues/6662
+ for (Long syncId : currentSyncStateSet) {
+- if (!idList.contains(syncId) && this.brokerControllerId != null && !Objects.equals(syncId, this.brokerControllerId)) {
++ if (!idList.contains(syncId) && this.localBrokerId != null && !Objects.equals(syncId, this.localBrokerId)) {
+ LOGGER.warn("Slave {} is still in syncStateSet, but has lost its connection. So new offset can't be compute.", syncId);
+ // Without check and re-compute, return the confirmOffset's value directly.
+ return this.defaultMessageStore.getConfirmOffsetDirectly();
+@@ -545,12 +548,12 @@ public class AutoSwitchHAService extends DefaultHAService {
+ return this.epochCache.getAllEntries();
+ }
+
+- public Long getBrokerControllerId() {
+- return brokerControllerId;
++ public Long getLocalBrokerId() {
++ return localBrokerId;
+ }
+
+- public void setBrokerControllerId(Long brokerControllerId) {
+- this.brokerControllerId = brokerControllerId;
++ public void setLocalBrokerId(Long localBrokerId) {
++ this.localBrokerId = localBrokerId;
+ }
+
+ class AutoSwitchAcceptSocketService extends AcceptSocketService {
+--
+2.32.0.windows.2
+
+
+From 77e8e54b37c3fc3ea0beffc1ace6f5bf20af10d9 Mon Sep 17 00:00:00 2001
+From: lk <xdkxlk@outlook.com>
+Date: Wed, 23 Aug 2023 15:56:39 +0800
+Subject: [PATCH 7/7] [ISSUE #7223] Support batch ack for grpc client in proxy
+ (#7225)
+
+---
+ .../client/impl/mqclient/MQClientAPIExt.java | 26 +++
+ .../rocketmq/proxy/config/ProxyConfig.java | 10 +
+ .../grpc/v2/consumer/AckMessageActivity.java | 136 ++++++++---
+ .../proxy/processor/AbstractProcessor.java | 4 +-
+ .../proxy/processor/BatchAckResult.java | 53 +++++
+ .../proxy/processor/ConsumerProcessor.java | 64 +++++
+ .../processor/DefaultMessagingProcessor.java | 7 +
+ .../proxy/processor/MessagingProcessor.java | 18 ++
+ .../message/ClusterMessageService.java | 16 +-
+ .../service/message/LocalMessageService.java | 58 +++++
+ .../proxy/service/message/MessageService.java | 8 +
+ .../service/message/ReceiptHandleMessage.java | 39 ++++
+ .../v2/consumer/AckMessageActivityTest.java | 221 +++++++++++++++---
+ .../proxy/processor/BaseProcessorTest.java | 18 +-
+ .../processor/ConsumerProcessorTest.java | 115 +++++++++
+ .../service/mqclient/MQClientAPIExtTest.java | 12 +
+ 16 files changed, 728 insertions(+), 77 deletions(-)
+ create mode 100644 proxy/src/main/java/org/apache/rocketmq/proxy/processor/BatchAckResult.java
+ create mode 100644 proxy/src/main/java/org/apache/rocketmq/proxy/service/message/ReceiptHandleMessage.java
+
+diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/mqclient/MQClientAPIExt.java b/client/src/main/java/org/apache/rocketmq/client/impl/mqclient/MQClientAPIExt.java
+index fb8f8d11f..d7c8ef8d9 100644
+--- a/client/src/main/java/org/apache/rocketmq/client/impl/mqclient/MQClientAPIExt.java
++++ b/client/src/main/java/org/apache/rocketmq/client/impl/mqclient/MQClientAPIExt.java
+@@ -306,6 +306,32 @@ public class MQClientAPIExt extends MQClientAPIImpl {
+ return future;
+ }
+
++ public CompletableFuture<AckResult> batchAckMessageAsync(
++ String brokerAddr,
++ String topic,
++ String consumerGroup,
++ List<String> extraInfoList,
++ long timeoutMillis
++ ) {
++ CompletableFuture<AckResult> future = new CompletableFuture<>();
++ try {
++ this.batchAckMessageAsync(brokerAddr, timeoutMillis, new AckCallback() {
++ @Override
++ public void onSuccess(AckResult ackResult) {
++ future.complete(ackResult);
++ }
++
++ @Override
++ public void onException(Throwable t) {
++ future.completeExceptionally(t);
++ }
++ }, topic, consumerGroup, extraInfoList);
++ } catch (Throwable t) {
++ future.completeExceptionally(t);
++ }
++ return future;
++ }
++
+ public CompletableFuture<AckResult> changeInvisibleTimeAsync(
+ String brokerAddr,
+ String brokerName,
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java b/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java
+index 39caaa0d9..76a243919 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java
+@@ -250,6 +250,8 @@ public class ProxyConfig implements ConfigFile {
+ private long remotingWaitTimeMillsInTopicRouteQueue = 3 * 1000;
+ private long remotingWaitTimeMillsInDefaultQueue = 3 * 1000;
+
++ private boolean enableBatchAck = false;
++
+ @Override
+ public void initData() {
+ parseDelayLevel();
+@@ -1379,4 +1381,12 @@ public class ProxyConfig implements ConfigFile {
+ public void setRemotingWaitTimeMillsInDefaultQueue(long remotingWaitTimeMillsInDefaultQueue) {
+ this.remotingWaitTimeMillsInDefaultQueue = remotingWaitTimeMillsInDefaultQueue;
+ }
++
++ public boolean isEnableBatchAck() {
++ return enableBatchAck;
++ }
++
++ public void setEnableBatchAck(boolean enableBatchAck) {
++ this.enableBatchAck = enableBatchAck;
++ }
+ }
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/AckMessageActivity.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/AckMessageActivity.java
+index 9a3a77201..97c716c8f 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/AckMessageActivity.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/AckMessageActivity.java
+@@ -31,12 +31,15 @@ import org.apache.rocketmq.client.consumer.AckStatus;
+ import org.apache.rocketmq.common.consumer.ReceiptHandle;
+ import org.apache.rocketmq.proxy.common.MessageReceiptHandle;
+ import org.apache.rocketmq.proxy.common.ProxyContext;
++import org.apache.rocketmq.proxy.config.ConfigurationManager;
+ import org.apache.rocketmq.proxy.grpc.v2.AbstractMessingActivity;
+ import org.apache.rocketmq.proxy.grpc.v2.channel.GrpcChannelManager;
+ import org.apache.rocketmq.proxy.grpc.v2.common.GrpcClientSettingsManager;
+ import org.apache.rocketmq.proxy.grpc.v2.common.GrpcConverter;
+ import org.apache.rocketmq.proxy.grpc.v2.common.ResponseBuilder;
++import org.apache.rocketmq.proxy.processor.BatchAckResult;
+ import org.apache.rocketmq.proxy.processor.MessagingProcessor;
++import org.apache.rocketmq.proxy.service.message.ReceiptHandleMessage;
+
+ public class AckMessageActivity extends AbstractMessingActivity {
+
+@@ -50,60 +53,98 @@ public class AckMessageActivity extends AbstractMessingActivity {
+
+ try {
+ validateTopicAndConsumerGroup(request.getTopic(), request.getGroup());
+-
+- CompletableFuture<AckMessageResultEntry>[] futures = new CompletableFuture[request.getEntriesCount()];
+- for (int i = 0; i < request.getEntriesCount(); i++) {
+- futures[i] = processAckMessage(ctx, request, request.getEntries(i));
++ String group = GrpcConverter.getInstance().wrapResourceWithNamespace(request.getGroup());
++ String topic = GrpcConverter.getInstance().wrapResourceWithNamespace(request.getTopic());
++ if (ConfigurationManager.getProxyConfig().isEnableBatchAck()) {
++ future = ackMessageInBatch(ctx, group, topic, request);
++ } else {
++ future = ackMessageOneByOne(ctx, group, topic, request);
+ }
+- CompletableFuture.allOf(futures).whenComplete((val, throwable) -> {
+- if (throwable != null) {
+- future.completeExceptionally(throwable);
+- return;
+- }
++ } catch (Throwable t) {
++ future.completeExceptionally(t);
++ }
++ return future;
++ }
++
++ protected CompletableFuture<AckMessageResponse> ackMessageInBatch(ProxyContext ctx, String group, String topic, AckMessageRequest request) {
++ List<ReceiptHandleMessage> handleMessageList = new ArrayList<>(request.getEntriesCount());
+
++ for (AckMessageEntry ackMessageEntry : request.getEntriesList()) {
++ String handleString = getHandleString(ctx, group, request, ackMessageEntry);
++ handleMessageList.add(new ReceiptHandleMessage(ReceiptHandle.decode(handleString), ackMessageEntry.getMessageId()));
++ }
++ return this.messagingProcessor.batchAckMessage(ctx, handleMessageList, group, topic)
++ .thenApply(batchAckResultList -> {
++ AckMessageResponse.Builder responseBuilder = AckMessageResponse.newBuilder();
+ Set<Code> responseCodes = new HashSet<>();
+- List<AckMessageResultEntry> entryList = new ArrayList<>();
+- for (CompletableFuture<AckMessageResultEntry> entryFuture : futures) {
+- AckMessageResultEntry entryResult = entryFuture.join();
+- responseCodes.add(entryResult.getStatus().getCode());
+- entryList.add(entryResult);
+- }
+- AckMessageResponse.Builder responseBuilder = AckMessageResponse.newBuilder()
+- .addAllEntries(entryList);
+- if (responseCodes.size() > 1) {
+- responseBuilder.setStatus(ResponseBuilder.getInstance().buildStatus(Code.MULTIPLE_RESULTS, Code.MULTIPLE_RESULTS.name()));
+- } else if (responseCodes.size() == 1) {
+- Code code = responseCodes.stream().findAny().get();
+- responseBuilder.setStatus(ResponseBuilder.getInstance().buildStatus(code, code.name()));
+- } else {
+- responseBuilder.setStatus(ResponseBuilder.getInstance().buildStatus(Code.INTERNAL_SERVER_ERROR, "ack message result is empty"));
++ for (BatchAckResult batchAckResult : batchAckResultList) {
++ AckMessageResultEntry entry = convertToAckMessageResultEntry(batchAckResult);
++ responseBuilder.addEntries(entry);
++ responseCodes.add(entry.getStatus().getCode());
+ }
+- future.complete(responseBuilder.build());
++ setAckResponseStatus(responseBuilder, responseCodes);
++ return responseBuilder.build();
+ });
+- } catch (Throwable t) {
+- future.completeExceptionally(t);
++ }
++
++ protected AckMessageResultEntry convertToAckMessageResultEntry(BatchAckResult batchAckResult) {
++ ReceiptHandleMessage handleMessage = batchAckResult.getReceiptHandleMessage();
++ AckMessageResultEntry.Builder resultBuilder = AckMessageResultEntry.newBuilder()
++ .setMessageId(handleMessage.getMessageId())
++ .setReceiptHandle(handleMessage.getReceiptHandle().getReceiptHandle());
++ if (batchAckResult.getProxyException() != null) {
++ resultBuilder.setStatus(ResponseBuilder.getInstance().buildStatus(batchAckResult.getProxyException()));
++ } else {
++ AckResult ackResult = batchAckResult.getAckResult();
++ if (AckStatus.OK.equals(ackResult.getStatus())) {
++ resultBuilder.setStatus(ResponseBuilder.getInstance().buildStatus(Code.OK, Code.OK.name()));
++ } else {
++ resultBuilder.setStatus(ResponseBuilder.getInstance().buildStatus(Code.INTERNAL_SERVER_ERROR, "ack failed: status is abnormal"));
++ }
+ }
+- return future;
++ return resultBuilder.build();
+ }
+
+- protected CompletableFuture<AckMessageResultEntry> processAckMessage(ProxyContext ctx, AckMessageRequest request,
++ protected CompletableFuture<AckMessageResponse> ackMessageOneByOne(ProxyContext ctx, String group, String topic, AckMessageRequest request) {
++ CompletableFuture<AckMessageResponse> resultFuture = new CompletableFuture<>();
++ CompletableFuture<AckMessageResultEntry>[] futures = new CompletableFuture[request.getEntriesCount()];
++ for (int i = 0; i < request.getEntriesCount(); i++) {
++ futures[i] = processAckMessage(ctx, group, topic, request, request.getEntries(i));
++ }
++ CompletableFuture.allOf(futures).whenComplete((val, throwable) -> {
++ if (throwable != null) {
++ resultFuture.completeExceptionally(throwable);
++ return;
++ }
++
++ Set<Code> responseCodes = new HashSet<>();
++ List<AckMessageResultEntry> entryList = new ArrayList<>();
++ for (CompletableFuture<AckMessageResultEntry> entryFuture : futures) {
++ AckMessageResultEntry entryResult = entryFuture.join();
++ responseCodes.add(entryResult.getStatus().getCode());
++ entryList.add(entryResult);
++ }
++ AckMessageResponse.Builder responseBuilder = AckMessageResponse.newBuilder()
++ .addAllEntries(entryList);
++ setAckResponseStatus(responseBuilder, responseCodes);
++ resultFuture.complete(responseBuilder.build());
++ });
++ return resultFuture;
++ }
++
++ protected CompletableFuture<AckMessageResultEntry> processAckMessage(ProxyContext ctx, String group, String topic, AckMessageRequest request,
+ AckMessageEntry ackMessageEntry) {
+ CompletableFuture<AckMessageResultEntry> future = new CompletableFuture<>();
+
+ try {
+- String handleString = ackMessageEntry.getReceiptHandle();
+-
+- String group = GrpcConverter.getInstance().wrapResourceWithNamespace(request.getGroup());
+- MessageReceiptHandle messageReceiptHandle = messagingProcessor.removeReceiptHandle(ctx, grpcChannelManager.getChannel(ctx.getClientID()), group, ackMessageEntry.getMessageId(), ackMessageEntry.getReceiptHandle());
+- if (messageReceiptHandle != null) {
+- handleString = messageReceiptHandle.getReceiptHandleStr();
+- }
++ String handleString = this.getHandleString(ctx, group, request, ackMessageEntry);
+ CompletableFuture<AckResult> ackResultFuture = this.messagingProcessor.ackMessage(
+ ctx,
+ ReceiptHandle.decode(handleString),
+ ackMessageEntry.getMessageId(),
+ group,
+- GrpcConverter.getInstance().wrapResourceWithNamespace(request.getTopic()));
++ topic
++ );
+ ackResultFuture.thenAccept(result -> {
+ future.complete(convertToAckMessageResultEntry(ctx, ackMessageEntry, result));
+ }).exceptionally(t -> {
+@@ -139,4 +180,25 @@ public class AckMessageActivity extends AbstractMessingActivity {
+ .setStatus(ResponseBuilder.getInstance().buildStatus(Code.INTERNAL_SERVER_ERROR, "ack failed: status is abnormal"))
+ .build();
+ }
++
++ protected void setAckResponseStatus(AckMessageResponse.Builder responseBuilder, Set<Code> responseCodes) {
++ if (responseCodes.size() > 1) {
++ responseBuilder.setStatus(ResponseBuilder.getInstance().buildStatus(Code.MULTIPLE_RESULTS, Code.MULTIPLE_RESULTS.name()));
++ } else if (responseCodes.size() == 1) {
++ Code code = responseCodes.stream().findAny().get();
++ responseBuilder.setStatus(ResponseBuilder.getInstance().buildStatus(code, code.name()));
++ } else {
++ responseBuilder.setStatus(ResponseBuilder.getInstance().buildStatus(Code.INTERNAL_SERVER_ERROR, "ack message result is empty"));
++ }
++ }
++
++ protected String getHandleString(ProxyContext ctx, String group, AckMessageRequest request, AckMessageEntry ackMessageEntry) {
++ String handleString = ackMessageEntry.getReceiptHandle();
++
++ MessageReceiptHandle messageReceiptHandle = messagingProcessor.removeReceiptHandle(ctx, grpcChannelManager.getChannel(ctx.getClientID()), group, ackMessageEntry.getMessageId(), ackMessageEntry.getReceiptHandle());
++ if (messageReceiptHandle != null) {
++ handleString = messageReceiptHandle.getReceiptHandleStr();
++ }
++ return handleString;
++ }
+ }
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/AbstractProcessor.java b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/AbstractProcessor.java
+index b61c3df9e..c63212c23 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/AbstractProcessor.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/AbstractProcessor.java
+@@ -27,6 +27,8 @@ public abstract class AbstractProcessor extends AbstractStartAndShutdown {
+ protected MessagingProcessor messagingProcessor;
+ protected ServiceManager serviceManager;
+
++ protected static final ProxyException EXPIRED_HANDLE_PROXY_EXCEPTION = new ProxyException(ProxyExceptionCode.INVALID_RECEIPT_HANDLE, "receipt handle is expired");
++
+ public AbstractProcessor(MessagingProcessor messagingProcessor,
+ ServiceManager serviceManager) {
+ this.messagingProcessor = messagingProcessor;
+@@ -35,7 +37,7 @@ public abstract class AbstractProcessor extends AbstractStartAndShutdown {
+
+ protected void validateReceiptHandle(ReceiptHandle handle) {
+ if (handle.isExpired()) {
+- throw new ProxyException(ProxyExceptionCode.INVALID_RECEIPT_HANDLE, "receipt handle is expired");
++ throw EXPIRED_HANDLE_PROXY_EXCEPTION;
+ }
+ }
+ }
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/BatchAckResult.java b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/BatchAckResult.java
+new file mode 100644
+index 000000000..dfb9c9b9e
+--- /dev/null
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/BatchAckResult.java
+@@ -0,0 +1,53 @@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one or more
++ * contributor license agreements. See the NOTICE file distributed with
++ * this work for additional information regarding copyright ownership.
++ * The ASF licenses this file to You under the Apache License, Version 2.0
++ * (the "License"); you may not use this file except in compliance with
++ * the License. You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package org.apache.rocketmq.proxy.processor;
++
++import org.apache.rocketmq.client.consumer.AckResult;
++import org.apache.rocketmq.proxy.common.ProxyException;
++import org.apache.rocketmq.proxy.service.message.ReceiptHandleMessage;
++
++public class BatchAckResult {
++
++ private final ReceiptHandleMessage receiptHandleMessage;
++ private AckResult ackResult;
++ private ProxyException proxyException;
++
++ public BatchAckResult(ReceiptHandleMessage receiptHandleMessage,
++ AckResult ackResult) {
++ this.receiptHandleMessage = receiptHandleMessage;
++ this.ackResult = ackResult;
++ }
++
++ public BatchAckResult(ReceiptHandleMessage receiptHandleMessage,
++ ProxyException proxyException) {
++ this.receiptHandleMessage = receiptHandleMessage;
++ this.proxyException = proxyException;
++ }
++
++ public ReceiptHandleMessage getReceiptHandleMessage() {
++ return receiptHandleMessage;
++ }
++
++ public AckResult getAckResult() {
++ return ackResult;
++ }
++
++ public ProxyException getProxyException() {
++ return proxyException;
++ }
++}
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/ConsumerProcessor.java b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/ConsumerProcessor.java
+index 656a6339d..f3522b374 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/ConsumerProcessor.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/ConsumerProcessor.java
+@@ -48,6 +48,7 @@ import org.apache.rocketmq.proxy.common.ProxyExceptionCode;
+ import org.apache.rocketmq.proxy.common.utils.FutureUtils;
+ import org.apache.rocketmq.proxy.common.utils.ProxyUtils;
+ import org.apache.rocketmq.proxy.service.ServiceManager;
++import org.apache.rocketmq.proxy.service.message.ReceiptHandleMessage;
+ import org.apache.rocketmq.proxy.service.route.AddressableMessageQueue;
+ import org.apache.rocketmq.remoting.protocol.body.LockBatchRequestBody;
+ import org.apache.rocketmq.remoting.protocol.body.UnlockBatchRequestBody;
+@@ -241,6 +242,69 @@ public class ConsumerProcessor extends AbstractProcessor {
+ return FutureUtils.addExecutor(future, this.executor);
+ }
+
++ public CompletableFuture<List<BatchAckResult>> batchAckMessage(
++ ProxyContext ctx,
++ List<ReceiptHandleMessage> handleMessageList,
++ String consumerGroup,
++ String topic,
++ long timeoutMillis
++ ) {
++ CompletableFuture<List<BatchAckResult>> future = new CompletableFuture<>();
++ try {
++ List<BatchAckResult> batchAckResultList = new ArrayList<>(handleMessageList.size());
++ Map<String, List<ReceiptHandleMessage>> brokerHandleListMap = new HashMap<>();
++
++ for (ReceiptHandleMessage handleMessage : handleMessageList) {
++ if (handleMessage.getReceiptHandle().isExpired()) {
++ batchAckResultList.add(new BatchAckResult(handleMessage, EXPIRED_HANDLE_PROXY_EXCEPTION));
++ continue;
++ }
++ List<ReceiptHandleMessage> brokerHandleList = brokerHandleListMap.computeIfAbsent(handleMessage.getReceiptHandle().getBrokerName(), key -> new ArrayList<>());
++ brokerHandleList.add(handleMessage);
++ }
++
++ if (brokerHandleListMap.isEmpty()) {
++ return FutureUtils.addExecutor(CompletableFuture.completedFuture(batchAckResultList), this.executor);
++ }
++ Set<Map.Entry<String, List<ReceiptHandleMessage>>> brokerHandleListMapEntrySet = brokerHandleListMap.entrySet();
++ CompletableFuture<List<BatchAckResult>>[] futures = new CompletableFuture[brokerHandleListMapEntrySet.size()];
++ int futureIndex = 0;
++ for (Map.Entry<String, List<ReceiptHandleMessage>> entry : brokerHandleListMapEntrySet) {
++ futures[futureIndex++] = processBrokerHandle(ctx, consumerGroup, topic, entry.getValue(), timeoutMillis);
++ }
++ CompletableFuture.allOf(futures).whenComplete((val, throwable) -> {
++ if (throwable != null) {
++ future.completeExceptionally(throwable);
++ }
++ for (CompletableFuture<List<BatchAckResult>> resultFuture : futures) {
++ batchAckResultList.addAll(resultFuture.join());
++ }
++ future.complete(batchAckResultList);
++ });
++ } catch (Throwable t) {
++ future.completeExceptionally(t);
++ }
++ return FutureUtils.addExecutor(future, this.executor);
++ }
++
++ protected CompletableFuture<List<BatchAckResult>> processBrokerHandle(ProxyContext ctx, String consumerGroup, String topic, List<ReceiptHandleMessage> handleMessageList, long timeoutMillis) {
++ return this.serviceManager.getMessageService().batchAckMessage(ctx, handleMessageList, consumerGroup, topic, timeoutMillis)
++ .thenApply(result -> {
++ List<BatchAckResult> results = new ArrayList<>();
++ for (ReceiptHandleMessage handleMessage : handleMessageList) {
++ results.add(new BatchAckResult(handleMessage, result));
++ }
++ return results;
++ })
++ .exceptionally(throwable -> {
++ List<BatchAckResult> results = new ArrayList<>();
++ for (ReceiptHandleMessage handleMessage : handleMessageList) {
++ results.add(new BatchAckResult(handleMessage, new ProxyException(ProxyExceptionCode.INTERNAL_SERVER_ERROR, throwable.getMessage(), throwable)));
++ }
++ return results;
++ });
++ }
++
+ public CompletableFuture<AckResult> changeInvisibleTime(ProxyContext ctx, ReceiptHandle handle,
+ String messageId, String groupName, String topicName, long invisibleTime, long timeoutMillis) {
+ CompletableFuture<AckResult> future = new CompletableFuture<>();
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/DefaultMessagingProcessor.java b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/DefaultMessagingProcessor.java
+index 188cb7b9b..ba150051b 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/DefaultMessagingProcessor.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/DefaultMessagingProcessor.java
+@@ -46,6 +46,7 @@ import org.apache.rocketmq.proxy.config.ConfigurationManager;
+ import org.apache.rocketmq.proxy.config.ProxyConfig;
+ import org.apache.rocketmq.proxy.service.ServiceManager;
+ import org.apache.rocketmq.proxy.service.ServiceManagerFactory;
++import org.apache.rocketmq.proxy.service.message.ReceiptHandleMessage;
+ import org.apache.rocketmq.proxy.service.metadata.MetadataService;
+ import org.apache.rocketmq.proxy.service.relay.ProxyRelayService;
+ import org.apache.rocketmq.proxy.service.route.ProxyTopicRouteData;
+@@ -183,6 +184,12 @@ public class DefaultMessagingProcessor extends AbstractStartAndShutdown implemen
+ return this.consumerProcessor.ackMessage(ctx, handle, messageId, consumerGroup, topic, timeoutMillis);
+ }
+
++ @Override
++ public CompletableFuture<List<BatchAckResult>> batchAckMessage(ProxyContext ctx,
++ List<ReceiptHandleMessage> handleMessageList, String consumerGroup, String topic, long timeoutMillis) {
++ return this.consumerProcessor.batchAckMessage(ctx, handleMessageList, consumerGroup, topic, timeoutMillis);
++ }
++
+ @Override
+ public CompletableFuture<AckResult> changeInvisibleTime(ProxyContext ctx, ReceiptHandle handle, String messageId,
+ String groupName, String topicName, long invisibleTime, long timeoutMillis) {
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/MessagingProcessor.java b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/MessagingProcessor.java
+index d86be0bd8..2ae7418ba 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/MessagingProcessor.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/MessagingProcessor.java
+@@ -37,6 +37,7 @@ import org.apache.rocketmq.proxy.common.Address;
+ import org.apache.rocketmq.proxy.common.MessageReceiptHandle;
+ import org.apache.rocketmq.proxy.common.ProxyContext;
+ import org.apache.rocketmq.common.utils.StartAndShutdown;
++import org.apache.rocketmq.proxy.service.message.ReceiptHandleMessage;
+ import org.apache.rocketmq.proxy.service.metadata.MetadataService;
+ import org.apache.rocketmq.proxy.service.relay.ProxyRelayService;
+ import org.apache.rocketmq.proxy.service.route.ProxyTopicRouteData;
+@@ -155,6 +156,23 @@ public interface MessagingProcessor extends StartAndShutdown {
+ long timeoutMillis
+ );
+
++ default CompletableFuture<List<BatchAckResult>> batchAckMessage(
++ ProxyContext ctx,
++ List<ReceiptHandleMessage> handleMessageList,
++ String consumerGroup,
++ String topic
++ ) {
++ return batchAckMessage(ctx, handleMessageList, consumerGroup, topic, DEFAULT_TIMEOUT_MILLS);
++ }
++
++ CompletableFuture<List<BatchAckResult>> batchAckMessage(
++ ProxyContext ctx,
++ List<ReceiptHandleMessage> handleMessageList,
++ String consumerGroup,
++ String topic,
++ long timeoutMillis
++ );
++
+ default CompletableFuture<AckResult> changeInvisibleTime(
+ ProxyContext ctx,
+ ReceiptHandle handle,
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/ClusterMessageService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/ClusterMessageService.java
+index 9f163f1b9..70b72deae 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/ClusterMessageService.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/ClusterMessageService.java
+@@ -20,9 +20,11 @@ import com.google.common.collect.Lists;
+ import java.util.List;
+ import java.util.Set;
+ import java.util.concurrent.CompletableFuture;
++import java.util.stream.Collectors;
+ import org.apache.rocketmq.client.consumer.AckResult;
+ import org.apache.rocketmq.client.consumer.PopResult;
+ import org.apache.rocketmq.client.consumer.PullResult;
++import org.apache.rocketmq.client.impl.mqclient.MQClientAPIFactory;
+ import org.apache.rocketmq.client.producer.SendResult;
+ import org.apache.rocketmq.common.consumer.ReceiptHandle;
+ import org.apache.rocketmq.common.message.Message;
+@@ -31,7 +33,6 @@ import org.apache.rocketmq.proxy.common.ProxyContext;
+ import org.apache.rocketmq.proxy.common.ProxyException;
+ import org.apache.rocketmq.proxy.common.ProxyExceptionCode;
+ import org.apache.rocketmq.proxy.common.utils.FutureUtils;
+-import org.apache.rocketmq.client.impl.mqclient.MQClientAPIFactory;
+ import org.apache.rocketmq.proxy.service.route.AddressableMessageQueue;
+ import org.apache.rocketmq.proxy.service.route.TopicRouteService;
+ import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+@@ -137,6 +138,19 @@ public class ClusterMessageService implements MessageService {
+ );
+ }
+
++ @Override
++ public CompletableFuture<AckResult> batchAckMessage(ProxyContext ctx, List<ReceiptHandleMessage> handleList, String consumerGroup,
++ String topic, long timeoutMillis) {
++ List<String> extraInfoList = handleList.stream().map(message -> message.getReceiptHandle().getReceiptHandle()).collect(Collectors.toList());
++ return this.mqClientAPIFactory.getClient().batchAckMessageAsync(
++ this.resolveBrokerAddrInReceiptHandle(ctx, handleList.get(0).getReceiptHandle()),
++ topic,
++ consumerGroup,
++ extraInfoList,
++ timeoutMillis
++ );
++ }
++
+ @Override
+ public CompletableFuture<PullResult> pullMessage(ProxyContext ctx, AddressableMessageQueue messageQueue,
+ PullMessageRequestHeader requestHeader, long timeoutMillis) {
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/LocalMessageService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/LocalMessageService.java
+index eb2c4d9ee..ca7dcc9eb 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/LocalMessageService.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/LocalMessageService.java
+@@ -19,6 +19,7 @@ package org.apache.rocketmq.proxy.service.message;
+ import io.netty.channel.ChannelHandlerContext;
+ import java.nio.ByteBuffer;
+ import java.util.ArrayList;
++import java.util.BitSet;
+ import java.util.Collections;
+ import java.util.HashMap;
+ import java.util.List;
+@@ -54,6 +55,8 @@ import org.apache.rocketmq.remoting.RPCHook;
+ import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+ import org.apache.rocketmq.remoting.protocol.RequestCode;
+ import org.apache.rocketmq.remoting.protocol.ResponseCode;
++import org.apache.rocketmq.remoting.protocol.body.BatchAck;
++import org.apache.rocketmq.remoting.protocol.body.BatchAckMessageRequestBody;
+ import org.apache.rocketmq.remoting.protocol.body.LockBatchRequestBody;
+ import org.apache.rocketmq.remoting.protocol.body.UnlockBatchRequestBody;
+ import org.apache.rocketmq.remoting.protocol.header.AckMessageRequestHeader;
+@@ -364,6 +367,61 @@ public class LocalMessageService implements MessageService {
+ });
+ }
+
++ @Override
++ public CompletableFuture<AckResult> batchAckMessage(ProxyContext ctx, List<ReceiptHandleMessage> handleList,
++ String consumerGroup, String topic, long timeoutMillis) {
++ SimpleChannel channel = channelManager.createChannel(ctx);
++ ChannelHandlerContext channelHandlerContext = channel.getChannelHandlerContext();
++ RemotingCommand command = LocalRemotingCommand.createRequestCommand(RequestCode.BATCH_ACK_MESSAGE, null);
++
++ Map<String, BatchAck> batchAckMap = new HashMap<>();
++ for (ReceiptHandleMessage receiptHandleMessage : handleList) {
++ String extraInfo = receiptHandleMessage.getReceiptHandle().getReceiptHandle();
++ String[] extraInfoData = ExtraInfoUtil.split(extraInfo);
++ String mergeKey = ExtraInfoUtil.getRetry(extraInfoData) + "@" +
++ ExtraInfoUtil.getQueueId(extraInfoData) + "@" +
++ ExtraInfoUtil.getCkQueueOffset(extraInfoData) + "@" +
++ ExtraInfoUtil.getPopTime(extraInfoData);
++ BatchAck bAck = batchAckMap.computeIfAbsent(mergeKey, k -> {
++ BatchAck newBatchAck = new BatchAck();
++ newBatchAck.setConsumerGroup(consumerGroup);
++ newBatchAck.setTopic(topic);
++ newBatchAck.setRetry(ExtraInfoUtil.getRetry(extraInfoData));
++ newBatchAck.setStartOffset(ExtraInfoUtil.getCkQueueOffset(extraInfoData));
++ newBatchAck.setQueueId(ExtraInfoUtil.getQueueId(extraInfoData));
++ newBatchAck.setReviveQueueId(ExtraInfoUtil.getReviveQid(extraInfoData));
++ newBatchAck.setPopTime(ExtraInfoUtil.getPopTime(extraInfoData));
++ newBatchAck.setInvisibleTime(ExtraInfoUtil.getInvisibleTime(extraInfoData));
++ newBatchAck.setBitSet(new BitSet());
++ return newBatchAck;
++ });
++ bAck.getBitSet().set((int) (ExtraInfoUtil.getQueueOffset(extraInfoData) - ExtraInfoUtil.getCkQueueOffset(extraInfoData)));
++ }
++ BatchAckMessageRequestBody requestBody = new BatchAckMessageRequestBody();
++ requestBody.setBrokerName(brokerController.getBrokerConfig().getBrokerName());
++ requestBody.setAcks(new ArrayList<>(batchAckMap.values()));
++
++ command.setBody(requestBody.encode());
++ CompletableFuture<RemotingCommand> future = new CompletableFuture<>();
++ try {
++ RemotingCommand response = brokerController.getAckMessageProcessor()
++ .processRequest(channelHandlerContext, command);
++ future.complete(response);
++ } catch (Exception e) {
++ log.error("Fail to process batchAckMessage command", e);
++ future.completeExceptionally(e);
++ }
++ return future.thenApply(r -> {
++ AckResult ackResult = new AckResult();
++ if (ResponseCode.SUCCESS == r.getCode()) {
++ ackResult.setStatus(AckStatus.OK);
++ } else {
++ ackResult.setStatus(AckStatus.NO_EXIST);
++ }
++ return ackResult;
++ });
++ }
++
+ @Override
+ public CompletableFuture<PullResult> pullMessage(ProxyContext ctx, AddressableMessageQueue messageQueue,
+ PullMessageRequestHeader requestHeader, long timeoutMillis) {
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/MessageService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/MessageService.java
+index 15da17154..58a835adb 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/MessageService.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/MessageService.java
+@@ -91,6 +91,14 @@ public interface MessageService {
+ long timeoutMillis
+ );
+
++ CompletableFuture<AckResult> batchAckMessage(
++ ProxyContext ctx,
++ List<ReceiptHandleMessage> handleList,
++ String consumerGroup,
++ String topic,
++ long timeoutMillis
++ );
++
+ CompletableFuture<PullResult> pullMessage(
+ ProxyContext ctx,
+ AddressableMessageQueue messageQueue,
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/ReceiptHandleMessage.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/ReceiptHandleMessage.java
+new file mode 100644
+index 000000000..ae63fed49
+--- /dev/null
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/ReceiptHandleMessage.java
+@@ -0,0 +1,39 @@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one or more
++ * contributor license agreements. See the NOTICE file distributed with
++ * this work for additional information regarding copyright ownership.
++ * The ASF licenses this file to You under the Apache License, Version 2.0
++ * (the "License"); you may not use this file except in compliance with
++ * the License. You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package org.apache.rocketmq.proxy.service.message;
++
++import org.apache.rocketmq.common.consumer.ReceiptHandle;
++
++public class ReceiptHandleMessage {
++
++ private final ReceiptHandle receiptHandle;
++ private final String messageId;
++
++ public ReceiptHandleMessage(ReceiptHandle receiptHandle, String messageId) {
++ this.receiptHandle = receiptHandle;
++ this.messageId = messageId;
++ }
++
++ public ReceiptHandle getReceiptHandle() {
++ return receiptHandle;
++ }
++
++ public String getMessageId() {
++ return messageId;
++ }
++}
+diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/consumer/AckMessageActivityTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/consumer/AckMessageActivityTest.java
+index 49fdfc6a8..3c4746105 100644
+--- a/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/consumer/AckMessageActivityTest.java
++++ b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/consumer/AckMessageActivityTest.java
+@@ -20,21 +20,32 @@ package org.apache.rocketmq.proxy.grpc.v2.consumer;
+ import apache.rocketmq.v2.AckMessageEntry;
+ import apache.rocketmq.v2.AckMessageRequest;
+ import apache.rocketmq.v2.AckMessageResponse;
++import apache.rocketmq.v2.AckMessageResultEntry;
+ import apache.rocketmq.v2.Code;
+ import apache.rocketmq.v2.Resource;
++import java.util.ArrayList;
++import java.util.HashMap;
++import java.util.List;
++import java.util.Map;
+ import java.util.concurrent.CompletableFuture;
+ import org.apache.rocketmq.client.consumer.AckResult;
+ import org.apache.rocketmq.client.consumer.AckStatus;
+ import org.apache.rocketmq.proxy.common.ProxyException;
+ import org.apache.rocketmq.proxy.common.ProxyExceptionCode;
++import org.apache.rocketmq.proxy.config.ConfigurationManager;
+ import org.apache.rocketmq.proxy.grpc.v2.BaseActivityTest;
++import org.apache.rocketmq.proxy.processor.BatchAckResult;
++import org.apache.rocketmq.proxy.service.message.ReceiptHandleMessage;
+ import org.junit.Before;
+ import org.junit.Test;
++import org.mockito.stubbing.Answer;
+
+ import static org.junit.Assert.assertEquals;
+ import static org.mockito.ArgumentMatchers.any;
++import static org.mockito.ArgumentMatchers.anyList;
+ import static org.mockito.ArgumentMatchers.anyString;
+ import static org.mockito.ArgumentMatchers.eq;
++import static org.mockito.Mockito.doAnswer;
+ import static org.mockito.Mockito.when;
+
+ public class AckMessageActivityTest extends BaseActivityTest {
+@@ -52,43 +63,197 @@ public class AckMessageActivityTest extends BaseActivityTest {
+
+ @Test
+ public void testAckMessage() throws Throwable {
+- when(this.messagingProcessor.ackMessage(any(), any(), eq("msg1"), anyString(), anyString()))
++ ConfigurationManager.getProxyConfig().setEnableBatchAck(false);
++
++ String msg1 = "msg1";
++ String msg2 = "msg2";
++ String msg3 = "msg3";
++
++ when(this.messagingProcessor.ackMessage(any(), any(), eq(msg1), anyString(), anyString()))
+ .thenThrow(new ProxyException(ProxyExceptionCode.INVALID_RECEIPT_HANDLE, "receipt handle is expired"));
+
+ AckResult msg2AckResult = new AckResult();
+ msg2AckResult.setStatus(AckStatus.OK);
+- when(this.messagingProcessor.ackMessage(any(), any(), eq("msg2"), anyString(), anyString()))
++ when(this.messagingProcessor.ackMessage(any(), any(), eq(msg2), anyString(), anyString()))
+ .thenReturn(CompletableFuture.completedFuture(msg2AckResult));
+
+ AckResult msg3AckResult = new AckResult();
+ msg3AckResult.setStatus(AckStatus.NO_EXIST);
+- when(this.messagingProcessor.ackMessage(any(), any(), eq("msg3"), anyString(), anyString()))
++ when(this.messagingProcessor.ackMessage(any(), any(), eq(msg3), anyString(), anyString()))
+ .thenReturn(CompletableFuture.completedFuture(msg3AckResult));
+
+- AckMessageResponse response = this.ackMessageActivity.ackMessage(
+- createContext(),
+- AckMessageRequest.newBuilder()
+- .setTopic(Resource.newBuilder().setName(TOPIC).build())
+- .setGroup(Resource.newBuilder().setName(GROUP).build())
+- .addEntries(AckMessageEntry.newBuilder()
+- .setMessageId("msg1")
+- .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis() - 10000, 1000))
+- .build())
+- .addEntries(AckMessageEntry.newBuilder()
+- .setMessageId("msg2")
+- .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis(), 3000))
+- .build())
+- .addEntries(AckMessageEntry.newBuilder()
+- .setMessageId("msg3")
+- .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis(), 3000))
+- .build())
+- .build()
+- ).get();
+-
+- assertEquals(Code.MULTIPLE_RESULTS, response.getStatus().getCode());
+- assertEquals(3, response.getEntriesCount());
+- assertEquals(Code.INVALID_RECEIPT_HANDLE, response.getEntries(0).getStatus().getCode());
+- assertEquals(Code.OK, response.getEntries(1).getStatus().getCode());
+- assertEquals(Code.INTERNAL_SERVER_ERROR, response.getEntries(2).getStatus().getCode());
++ {
++ AckMessageResponse response = this.ackMessageActivity.ackMessage(
++ createContext(),
++ AckMessageRequest.newBuilder()
++ .setTopic(Resource.newBuilder().setName(TOPIC).build())
++ .setGroup(Resource.newBuilder().setName(GROUP).build())
++ .addEntries(AckMessageEntry.newBuilder()
++ .setMessageId(msg1)
++ .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis() - 10000, 1000))
++ .build())
++ .build()
++ ).get();
++ assertEquals(Code.INVALID_RECEIPT_HANDLE, response.getStatus().getCode());
++ }
++ {
++ AckMessageResponse response = this.ackMessageActivity.ackMessage(
++ createContext(),
++ AckMessageRequest.newBuilder()
++ .setTopic(Resource.newBuilder().setName(TOPIC).build())
++ .setGroup(Resource.newBuilder().setName(GROUP).build())
++ .addEntries(AckMessageEntry.newBuilder()
++ .setMessageId(msg2)
++ .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis() - 10000, 1000))
++ .build())
++ .build()
++ ).get();
++ assertEquals(Code.OK, response.getStatus().getCode());
++ }
++ {
++ AckMessageResponse response = this.ackMessageActivity.ackMessage(
++ createContext(),
++ AckMessageRequest.newBuilder()
++ .setTopic(Resource.newBuilder().setName(TOPIC).build())
++ .setGroup(Resource.newBuilder().setName(GROUP).build())
++ .addEntries(AckMessageEntry.newBuilder()
++ .setMessageId(msg3)
++ .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis() - 10000, 1000))
++ .build())
++ .build()
++ ).get();
++ assertEquals(Code.INTERNAL_SERVER_ERROR, response.getStatus().getCode());
++ }
++ {
++ AckMessageResponse response = this.ackMessageActivity.ackMessage(
++ createContext(),
++ AckMessageRequest.newBuilder()
++ .setTopic(Resource.newBuilder().setName(TOPIC).build())
++ .setGroup(Resource.newBuilder().setName(GROUP).build())
++ .addEntries(AckMessageEntry.newBuilder()
++ .setMessageId(msg1)
++ .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis() - 10000, 1000))
++ .build())
++ .addEntries(AckMessageEntry.newBuilder()
++ .setMessageId(msg2)
++ .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis(), 3000))
++ .build())
++ .addEntries(AckMessageEntry.newBuilder()
++ .setMessageId(msg3)
++ .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis(), 3000))
++ .build())
++ .build()
++ ).get();
++
++ assertEquals(Code.MULTIPLE_RESULTS, response.getStatus().getCode());
++ assertEquals(3, response.getEntriesCount());
++ assertEquals(Code.INVALID_RECEIPT_HANDLE, response.getEntries(0).getStatus().getCode());
++ assertEquals(Code.OK, response.getEntries(1).getStatus().getCode());
++ assertEquals(Code.INTERNAL_SERVER_ERROR, response.getEntries(2).getStatus().getCode());
++ }
++ }
++
++ @Test
++ public void testAckMessageInBatch() throws Throwable {
++ ConfigurationManager.getProxyConfig().setEnableBatchAck(true);
++
++ String successMessageId = "msg1";
++ String notOkMessageId = "msg2";
++ String exceptionMessageId = "msg3";
++
++ doAnswer((Answer<CompletableFuture<List<BatchAckResult>>>) invocation -> {
++ List<ReceiptHandleMessage> receiptHandleMessageList = invocation.getArgument(1, List.class);
++ List<BatchAckResult> batchAckResultList = new ArrayList<>();
++ for (ReceiptHandleMessage receiptHandleMessage : receiptHandleMessageList) {
++ BatchAckResult batchAckResult;
++ if (receiptHandleMessage.getMessageId().equals(successMessageId)) {
++ AckResult ackResult = new AckResult();
++ ackResult.setStatus(AckStatus.OK);
++ batchAckResult = new BatchAckResult(receiptHandleMessage, ackResult);
++ } else if (receiptHandleMessage.getMessageId().equals(notOkMessageId)) {
++ AckResult ackResult = new AckResult();
++ ackResult.setStatus(AckStatus.NO_EXIST);
++ batchAckResult = new BatchAckResult(receiptHandleMessage, ackResult);
++ } else {
++ batchAckResult = new BatchAckResult(receiptHandleMessage, new ProxyException(ProxyExceptionCode.INVALID_RECEIPT_HANDLE, ""));
++ }
++ batchAckResultList.add(batchAckResult);
++ }
++ return CompletableFuture.completedFuture(batchAckResultList);
++ }).when(this.messagingProcessor).batchAckMessage(any(), anyList(), anyString(), anyString());
++
++ {
++ AckMessageResponse response = this.ackMessageActivity.ackMessage(
++ createContext(),
++ AckMessageRequest.newBuilder()
++ .setTopic(Resource.newBuilder().setName(TOPIC).build())
++ .setGroup(Resource.newBuilder().setName(GROUP).build())
++ .addEntries(AckMessageEntry.newBuilder()
++ .setMessageId(successMessageId)
++ .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis(), 3000))
++ .build())
++ .build()
++ ).get();
++ assertEquals(Code.OK, response.getStatus().getCode());
++ }
++ {
++ AckMessageResponse response = this.ackMessageActivity.ackMessage(
++ createContext(),
++ AckMessageRequest.newBuilder()
++ .setTopic(Resource.newBuilder().setName(TOPIC).build())
++ .setGroup(Resource.newBuilder().setName(GROUP).build())
++ .addEntries(AckMessageEntry.newBuilder()
++ .setMessageId(notOkMessageId)
++ .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis(), 3000))
++ .build())
++ .build()
++ ).get();
++ assertEquals(Code.INTERNAL_SERVER_ERROR, response.getStatus().getCode());
++ }
++ {
++ AckMessageResponse response = this.ackMessageActivity.ackMessage(
++ createContext(),
++ AckMessageRequest.newBuilder()
++ .setTopic(Resource.newBuilder().setName(TOPIC).build())
++ .setGroup(Resource.newBuilder().setName(GROUP).build())
++ .addEntries(AckMessageEntry.newBuilder()
++ .setMessageId(exceptionMessageId)
++ .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis(), 3000))
++ .build())
++ .build()
++ ).get();
++ assertEquals(Code.INVALID_RECEIPT_HANDLE, response.getStatus().getCode());
++ }
++ {
++ AckMessageResponse response = this.ackMessageActivity.ackMessage(
++ createContext(),
++ AckMessageRequest.newBuilder()
++ .setTopic(Resource.newBuilder().setName(TOPIC).build())
++ .setGroup(Resource.newBuilder().setName(GROUP).build())
++ .addEntries(AckMessageEntry.newBuilder()
++ .setMessageId(successMessageId)
++ .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis(), 3000))
++ .build())
++ .addEntries(AckMessageEntry.newBuilder()
++ .setMessageId(notOkMessageId)
++ .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis(), 3000))
++ .build())
++ .addEntries(AckMessageEntry.newBuilder()
++ .setMessageId(exceptionMessageId)
++ .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis(), 3000))
++ .build())
++ .build()
++ ).get();
++
++ assertEquals(Code.MULTIPLE_RESULTS, response.getStatus().getCode());
++ assertEquals(3, response.getEntriesCount());
++ Map<String, Code> msgCode = new HashMap<>();
++ for (AckMessageResultEntry entry : response.getEntriesList()) {
++ msgCode.put(entry.getMessageId(), entry.getStatus().getCode());
++ }
++ assertEquals(Code.OK, msgCode.get(successMessageId));
++ assertEquals(Code.INTERNAL_SERVER_ERROR, msgCode.get(notOkMessageId));
++ assertEquals(Code.INVALID_RECEIPT_HANDLE, msgCode.get(exceptionMessageId));
++ }
+ }
+ }
+\ No newline at end of file
+diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/processor/BaseProcessorTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/processor/BaseProcessorTest.java
+index 5c1ea9627..072630e39 100644
+--- a/proxy/src/test/java/org/apache/rocketmq/proxy/processor/BaseProcessorTest.java
++++ b/proxy/src/test/java/org/apache/rocketmq/proxy/processor/BaseProcessorTest.java
+@@ -66,14 +66,6 @@ public class BaseProcessorTest extends InitConfigTest {
+ protected ProxyRelayService proxyRelayService;
+ @Mock
+ protected MetadataService metadataService;
+- @Mock
+- protected ProducerProcessor producerProcessor;
+- @Mock
+- protected ConsumerProcessor consumerProcessor;
+- @Mock
+- protected TransactionProcessor transactionProcessor;
+- @Mock
+- protected ClientProcessor clientProcessor;
+
+ public void before() throws Throwable {
+ super.before();
+@@ -92,6 +84,13 @@ public class BaseProcessorTest extends InitConfigTest {
+ }
+
+ protected static MessageExt createMessageExt(String topic, String tags, int reconsumeTimes, long invisibleTime) {
++ return createMessageExt(topic, tags, reconsumeTimes, invisibleTime, System.currentTimeMillis(),
++ RANDOM.nextInt(Integer.MAX_VALUE), RANDOM.nextInt(Integer.MAX_VALUE), RANDOM.nextInt(Integer.MAX_VALUE),
++ RANDOM.nextInt(Integer.MAX_VALUE), "mockBroker");
++ }
++
++ protected static MessageExt createMessageExt(String topic, String tags, int reconsumeTimes, long invisibleTime, long popTime,
++ long startOffset, int reviveQid, int queueId, long queueOffset, String brokerName) {
+ MessageExt messageExt = new MessageExt();
+ messageExt.setTopic(topic);
+ messageExt.setTags(tags);
+@@ -100,8 +99,7 @@ public class BaseProcessorTest extends InitConfigTest {
+ messageExt.setMsgId(MessageClientIDSetter.createUniqID());
+ messageExt.setCommitLogOffset(RANDOM.nextInt(Integer.MAX_VALUE));
+ MessageAccessor.putProperty(messageExt, MessageConst.PROPERTY_POP_CK,
+- ExtraInfoUtil.buildExtraInfo(RANDOM.nextInt(Integer.MAX_VALUE), System.currentTimeMillis(), invisibleTime,
+- RANDOM.nextInt(Integer.MAX_VALUE), topic, "mockBroker", RANDOM.nextInt(Integer.MAX_VALUE), RANDOM.nextInt(Integer.MAX_VALUE)));
++ ExtraInfoUtil.buildExtraInfo(startOffset, popTime, invisibleTime, reviveQid, topic, brokerName, queueId, queueOffset));
+ return messageExt;
+ }
+
+diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/processor/ConsumerProcessorTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/processor/ConsumerProcessorTest.java
+index 717e86fc0..db268a06e 100644
+--- a/proxy/src/test/java/org/apache/rocketmq/proxy/processor/ConsumerProcessorTest.java
++++ b/proxy/src/test/java/org/apache/rocketmq/proxy/processor/ConsumerProcessorTest.java
+@@ -20,8 +20,11 @@ package org.apache.rocketmq.proxy.processor;
+ import com.google.common.collect.Sets;
+ import java.time.Duration;
+ import java.util.ArrayList;
++import java.util.Collections;
++import java.util.HashMap;
+ import java.util.HashSet;
+ import java.util.List;
++import java.util.Map;
+ import java.util.Set;
+ import java.util.concurrent.CompletableFuture;
+ import java.util.concurrent.Executors;
+@@ -39,7 +42,10 @@ import org.apache.rocketmq.common.message.MessageClientIDSetter;
+ import org.apache.rocketmq.common.message.MessageExt;
+ import org.apache.rocketmq.common.message.MessageQueue;
+ import org.apache.rocketmq.proxy.common.ProxyContext;
++import org.apache.rocketmq.proxy.common.ProxyExceptionCode;
++import org.apache.rocketmq.proxy.common.utils.FutureUtils;
+ import org.apache.rocketmq.proxy.common.utils.ProxyUtils;
++import org.apache.rocketmq.proxy.service.message.ReceiptHandleMessage;
+ import org.apache.rocketmq.proxy.service.route.AddressableMessageQueue;
+ import org.apache.rocketmq.proxy.service.route.MessageQueueView;
+ import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+@@ -50,16 +56,22 @@ import org.apache.rocketmq.remoting.protocol.header.PopMessageRequestHeader;
+ import org.junit.Before;
+ import org.junit.Test;
+ import org.mockito.ArgumentCaptor;
++import org.mockito.stubbing.Answer;
+
+ import static org.assertj.core.api.Assertions.assertThat;
+ import static org.junit.Assert.assertEquals;
+ import static org.junit.Assert.assertNotNull;
++import static org.junit.Assert.assertNull;
+ import static org.junit.Assert.assertSame;
+ import static org.mockito.ArgumentMatchers.any;
++import static org.mockito.ArgumentMatchers.anyList;
+ import static org.mockito.ArgumentMatchers.anyLong;
+ import static org.mockito.ArgumentMatchers.anyString;
+ import static org.mockito.ArgumentMatchers.eq;
++import static org.mockito.Mockito.doAnswer;
+ import static org.mockito.Mockito.mock;
++import static org.mockito.Mockito.never;
++import static org.mockito.Mockito.verify;
+ import static org.mockito.Mockito.when;
+
+ public class ConsumerProcessorTest extends BaseProcessorTest {
+@@ -162,6 +174,109 @@ public class ConsumerProcessorTest extends BaseProcessorTest {
+ assertEquals(handle.getReceiptHandle(), requestHeaderArgumentCaptor.getValue().getExtraInfo());
+ }
+
++ @Test
++ public void testBatchAckExpireMessage() throws Throwable {
++ String brokerName1 = "brokerName1";
++
++ List<ReceiptHandleMessage> receiptHandleMessageList = new ArrayList<>();
++ for (int i = 0; i < 3; i++) {
++ MessageExt expireMessage = createMessageExt(TOPIC, "", 0, 3000, System.currentTimeMillis() - 10000,
++ 0, 0, 0, i, brokerName1);
++ ReceiptHandle expireHandle = create(expireMessage);
++ receiptHandleMessageList.add(new ReceiptHandleMessage(expireHandle, expireMessage.getMsgId()));
++ }
++
++ List<BatchAckResult> batchAckResultList = this.consumerProcessor.batchAckMessage(createContext(), receiptHandleMessageList, CONSUMER_GROUP, TOPIC, 3000).get();
++
++ verify(this.messageService, never()).batchAckMessage(any(), anyList(), anyString(), anyString(), anyLong());
++ assertEquals(receiptHandleMessageList.size(), batchAckResultList.size());
++ for (BatchAckResult batchAckResult : batchAckResultList) {
++ assertNull(batchAckResult.getAckResult());
++ assertNotNull(batchAckResult.getProxyException());
++ assertNotNull(batchAckResult.getReceiptHandleMessage());
++ }
++
++ }
++
++ @Test
++ public void testBatchAckMessage() throws Throwable {
++ String brokerName1 = "brokerName1";
++ String brokerName2 = "brokerName2";
++ String errThrowBrokerName = "errThrowBrokerName";
++ MessageExt expireMessage = createMessageExt(TOPIC, "", 0, 3000, System.currentTimeMillis() - 10000,
++ 0, 0, 0, 0, brokerName1);
++ ReceiptHandle expireHandle = create(expireMessage);
++
++ List<ReceiptHandleMessage> receiptHandleMessageList = new ArrayList<>();
++ receiptHandleMessageList.add(new ReceiptHandleMessage(expireHandle, expireMessage.getMsgId()));
++ List<String> broker1Msg = new ArrayList<>();
++ List<String> broker2Msg = new ArrayList<>();
++
++ long now = System.currentTimeMillis();
++ int msgNum = 3;
++ for (int i = 0; i < msgNum; i++) {
++ MessageExt brokerMessage = createMessageExt(TOPIC, "", 0, 3000, now,
++ 0, 0, 0, i + 1, brokerName1);
++ ReceiptHandle brokerHandle = create(brokerMessage);
++ receiptHandleMessageList.add(new ReceiptHandleMessage(brokerHandle, brokerMessage.getMsgId()));
++ broker1Msg.add(brokerMessage.getMsgId());
++ }
++ for (int i = 0; i < msgNum; i++) {
++ MessageExt brokerMessage = createMessageExt(TOPIC, "", 0, 3000, now,
++ 0, 0, 0, i + 1, brokerName2);
++ ReceiptHandle brokerHandle = create(brokerMessage);
++ receiptHandleMessageList.add(new ReceiptHandleMessage(brokerHandle, brokerMessage.getMsgId()));
++ broker2Msg.add(brokerMessage.getMsgId());
++ }
++
++ // for this message, will throw exception in batchAckMessage
++ MessageExt errThrowMessage = createMessageExt(TOPIC, "", 0, 3000, now,
++ 0, 0, 0, 0, errThrowBrokerName);
++ ReceiptHandle errThrowHandle = create(errThrowMessage);
++ receiptHandleMessageList.add(new ReceiptHandleMessage(errThrowHandle, errThrowMessage.getMsgId()));
++
++ Collections.shuffle(receiptHandleMessageList);
++
++ doAnswer((Answer<CompletableFuture<AckResult>>) invocation -> {
++ List<ReceiptHandleMessage> handleMessageList = invocation.getArgument(1, List.class);
++ AckResult ackResult = new AckResult();
++ String brokerName = handleMessageList.get(0).getReceiptHandle().getBrokerName();
++ if (brokerName.equals(brokerName1)) {
++ ackResult.setStatus(AckStatus.OK);
++ } else if (brokerName.equals(brokerName2)) {
++ ackResult.setStatus(AckStatus.NO_EXIST);
++ } else {
++ return FutureUtils.completeExceptionally(new RuntimeException());
++ }
++
++ return CompletableFuture.completedFuture(ackResult);
++ }).when(this.messageService).batchAckMessage(any(), anyList(), anyString(), anyString(), anyLong());
++
++ List<BatchAckResult> batchAckResultList = this.consumerProcessor.batchAckMessage(createContext(), receiptHandleMessageList, CONSUMER_GROUP, TOPIC, 3000).get();
++ assertEquals(receiptHandleMessageList.size(), batchAckResultList.size());
++
++ // check ackResult for each msg
++ Map<String, BatchAckResult> msgBatchAckResult = new HashMap<>();
++ for (BatchAckResult batchAckResult : batchAckResultList) {
++ msgBatchAckResult.put(batchAckResult.getReceiptHandleMessage().getMessageId(), batchAckResult);
++ }
++ for (String msgId : broker1Msg) {
++ assertEquals(AckStatus.OK, msgBatchAckResult.get(msgId).getAckResult().getStatus());
++ assertNull(msgBatchAckResult.get(msgId).getProxyException());
++ }
++ for (String msgId : broker2Msg) {
++ assertEquals(AckStatus.NO_EXIST, msgBatchAckResult.get(msgId).getAckResult().getStatus());
++ assertNull(msgBatchAckResult.get(msgId).getProxyException());
++ }
++ assertNotNull(msgBatchAckResult.get(expireMessage.getMsgId()).getProxyException());
++ assertEquals(ProxyExceptionCode.INVALID_RECEIPT_HANDLE, msgBatchAckResult.get(expireMessage.getMsgId()).getProxyException().getCode());
++ assertNull(msgBatchAckResult.get(expireMessage.getMsgId()).getAckResult());
++
++ assertNotNull(msgBatchAckResult.get(errThrowMessage.getMsgId()).getProxyException());
++ assertEquals(ProxyExceptionCode.INTERNAL_SERVER_ERROR, msgBatchAckResult.get(errThrowMessage.getMsgId()).getProxyException().getCode());
++ assertNull(msgBatchAckResult.get(errThrowMessage.getMsgId()).getAckResult());
++ }
++
+ @Test
+ public void testChangeInvisibleTime() throws Throwable {
+ ReceiptHandle handle = create(createMessageExt(MixAll.RETRY_GROUP_TOPIC_PREFIX + TOPIC, "", 0, 3000));
+diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/service/mqclient/MQClientAPIExtTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/service/mqclient/MQClientAPIExtTest.java
+index 77a119a29..3f3a4ae40 100644
+--- a/proxy/src/test/java/org/apache/rocketmq/proxy/service/mqclient/MQClientAPIExtTest.java
++++ b/proxy/src/test/java/org/apache/rocketmq/proxy/service/mqclient/MQClientAPIExtTest.java
+@@ -220,6 +220,18 @@ public class MQClientAPIExtTest {
+ assertSame(ackResult, mqClientAPI.ackMessageAsync(BROKER_ADDR, new AckMessageRequestHeader(), TIMEOUT).get());
+ }
+
++ @Test
++ public void testBatchAckMessageAsync() throws Exception {
++ AckResult ackResult = new AckResult();
++ doAnswer((Answer<Void>) mock -> {
++ AckCallback ackCallback = mock.getArgument(2);
++ ackCallback.onSuccess(ackResult);
++ return null;
++ }).when(mqClientAPI).batchAckMessageAsync(anyString(), anyLong(), any(AckCallback.class), any());
++
++ assertSame(ackResult, mqClientAPI.batchAckMessageAsync(BROKER_ADDR, TOPIC, CONSUMER_GROUP, new ArrayList<>(), TIMEOUT).get());
++ }
++
+ @Test
+ public void testChangeInvisibleTimeAsync() throws Exception {
+ AckResult ackResult = new AckResult();
+--
+2.32.0.windows.2
+
diff --git a/patch012-backport-enhance-rockdbconfigtojson.patch b/patch012-backport-enhance-rockdbconfigtojson.patch
new file mode 100644
index 000000000..cf6128b24
--- /dev/null
+++ b/patch012-backport-enhance-rockdbconfigtojson.patch
@@ -0,0 +1,2920 @@
+From fec141481496c53a0db398367006c34264662d18 Mon Sep 17 00:00:00 2001
+From: yx9o <yangx_soft@163.com>
+Date: Wed, 23 Aug 2023 08:22:34 +0800
+Subject: [PATCH 1/8] [ISSUE #7166] Optimize the display format of admin
+ (#7210)
+
+---
+ .../java/org/apache/rocketmq/tools/command/MQAdminStartup.java | 2 +-
+ .../command/acl/ClusterAclConfigVersionListSubCommand.java | 2 +-
+ .../tools/command/acl/DeleteAccessConfigSubCommand.java | 2 +-
+ .../rocketmq/tools/command/acl/GetAccessConfigSubCommand.java | 2 +-
+ .../tools/command/acl/UpdateAccessConfigSubCommand.java | 2 +-
+ .../tools/command/acl/UpdateGlobalWhiteAddrSubCommand.java | 2 +-
+ .../tools/command/broker/BrokerConsumeStatsSubCommad.java | 2 +-
+ .../rocketmq/tools/command/broker/BrokerStatusSubCommand.java | 2 +-
+ .../tools/command/broker/CommitLogSetReadAheadSubCommand.java | 2 +-
+ .../tools/command/broker/DeleteExpiredCommitLogSubCommand.java | 2 +-
+ .../rocketmq/tools/command/broker/GetBrokerConfigCommand.java | 2 +-
+ .../rocketmq/tools/command/broker/GetBrokerEpochSubCommand.java | 2 +-
+ .../tools/command/broker/GetColdDataFlowCtrInfoSubCommand.java | 2 +-
+ .../broker/RemoveColdDataFlowCtrGroupConfigSubCommand.java | 2 +-
+ .../tools/command/broker/ResetMasterFlushOffsetSubCommand.java | 2 +-
+ .../tools/command/broker/UpdateBrokerConfigSubCommand.java | 2 +-
+ .../broker/UpdateColdDataFlowCtrGroupConfigSubCommand.java | 2 +-
+ .../rocketmq/tools/command/cluster/CLusterSendMsgRTCommand.java | 2 +-
+ .../rocketmq/tools/command/cluster/ClusterListSubCommand.java | 2 +-
+ .../tools/command/connection/ConsumerConnectionSubCommand.java | 2 +-
+ .../tools/command/connection/ProducerConnectionSubCommand.java | 2 +-
+ .../tools/command/consumer/ConsumerStatusSubCommand.java | 2 +-
+ .../tools/command/consumer/GetConsumerConfigSubCommand.java | 2 +-
+ .../tools/command/consumer/StartMonitoringSubCommand.java | 2 +-
+ .../tools/command/consumer/UpdateSubGroupSubCommand.java | 2 +-
+ .../rocketmq/tools/command/container/AddBrokerSubCommand.java | 2 +-
+ .../tools/command/container/RemoveBrokerSubCommand.java | 2 +-
+ .../command/controller/CleanControllerBrokerMetaSubCommand.java | 2 +-
+ .../command/controller/GetControllerMetaDataSubCommand.java | 2 +-
+ .../tools/command/controller/ReElectMasterSubCommand.java | 2 +-
+ .../rocketmq/tools/command/export/ExportConfigsCommand.java | 2 +-
+ .../rocketmq/tools/command/export/ExportMetadataCommand.java | 2 +-
+ .../rocketmq/tools/command/export/ExportMetricsCommand.java | 2 +-
+ .../rocketmq/tools/command/ha/GetSyncStateSetSubCommand.java | 2 +-
+ .../apache/rocketmq/tools/command/ha/HAStatusSubCommand.java | 2 +-
+ .../rocketmq/tools/command/message/CheckMsgSendRTCommand.java | 2 +-
+ .../rocketmq/tools/command/message/ConsumeMessageCommand.java | 2 +-
+ .../tools/command/message/DumpCompactionLogCommand.java | 2 +-
+ .../tools/command/message/PrintMessageByQueueCommand.java | 2 +-
+ .../rocketmq/tools/command/message/PrintMessageSubCommand.java | 2 +-
+ .../rocketmq/tools/command/message/QueryMsgByIdSubCommand.java | 2 +-
+ .../rocketmq/tools/command/message/QueryMsgByKeySubCommand.java | 2 +-
+ .../tools/command/message/QueryMsgByOffsetSubCommand.java | 2 +-
+ .../tools/command/message/QueryMsgByUniqueKeySubCommand.java | 2 +-
+ .../tools/command/message/QueryMsgTraceByIdSubCommand.java | 2 +-
+ .../rocketmq/tools/command/message/SendMessageCommand.java | 2 +-
+ .../rocketmq/tools/command/namesrv/AddWritePermSubCommand.java | 2 +-
+ .../rocketmq/tools/command/namesrv/WipeWritePermSubCommand.java | 2 +-
+ .../tools/command/offset/SkipAccumulationSubCommand.java | 2 +-
+ .../apache/rocketmq/tools/command/stats/StatsAllSubCommand.java | 2 +-
+ .../rocketmq/tools/command/topic/AllocateMQSubCommand.java | 2 +-
+ .../rocketmq/tools/command/topic/TopicClusterSubCommand.java | 2 +-
+ .../rocketmq/tools/command/topic/TopicListSubCommand.java | 2 +-
+ .../rocketmq/tools/command/topic/TopicRouteSubCommand.java | 2 +-
+ .../rocketmq/tools/command/topic/TopicStatusSubCommand.java | 2 +-
+ .../rocketmq/tools/command/topic/UpdateOrderConfCommand.java | 2 +-
+ .../tools/command/topic/UpdateStaticTopicSubCommand.java | 2 +-
+ .../rocketmq/tools/command/topic/UpdateTopicPermSubCommand.java | 2 +-
+ .../rocketmq/tools/command/topic/UpdateTopicSubCommand.java | 2 +-
+ 59 files changed, 59 insertions(+), 59 deletions(-)
+
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/MQAdminStartup.java b/tools/src/main/java/org/apache/rocketmq/tools/command/MQAdminStartup.java
+index 0c2618e91..890125ca0 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/MQAdminStartup.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/MQAdminStartup.java
+@@ -278,7 +278,7 @@ public class MQAdminStartup {
+ System.out.printf("The most commonly used mqadmin commands are:%n");
+
+ for (SubCommand cmd : SUB_COMMANDS) {
+- System.out.printf(" %-25s %s%n", cmd.commandName(), cmd.commandDesc());
++ System.out.printf(" %-35s %s%n", cmd.commandName(), cmd.commandDesc());
+ }
+
+ System.out.printf("%nSee 'mqadmin help <command>' for more information on a specific command.%n");
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/acl/ClusterAclConfigVersionListSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/acl/ClusterAclConfigVersionListSubCommand.java
+index f8a00b1e0..26ed028fb 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/acl/ClusterAclConfigVersionListSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/acl/ClusterAclConfigVersionListSubCommand.java
+@@ -47,7 +47,7 @@ public class ClusterAclConfigVersionListSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "List all of acl config version information in cluster";
++ return "List all of acl config version information in cluster.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/acl/DeleteAccessConfigSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/acl/DeleteAccessConfigSubCommand.java
+index fd3a92fff..a7f3d295a 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/acl/DeleteAccessConfigSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/acl/DeleteAccessConfigSubCommand.java
+@@ -42,7 +42,7 @@ public class DeleteAccessConfigSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Delete Acl Config Account in broker";
++ return "Delete Acl Config Account in broker.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/acl/GetAccessConfigSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/acl/GetAccessConfigSubCommand.java
+index 25844d6a1..f1c9a1496 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/acl/GetAccessConfigSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/acl/GetAccessConfigSubCommand.java
+@@ -49,7 +49,7 @@ public class GetAccessConfigSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "List all of acl config information in cluster";
++ return "List all of acl config information in cluster.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/acl/UpdateAccessConfigSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/acl/UpdateAccessConfigSubCommand.java
+index 3be40daa1..d8a06f92d 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/acl/UpdateAccessConfigSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/acl/UpdateAccessConfigSubCommand.java
+@@ -40,7 +40,7 @@ public class UpdateAccessConfigSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Update acl config yaml file in broker";
++ return "Update acl config yaml file in broker.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/acl/UpdateGlobalWhiteAddrSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/acl/UpdateGlobalWhiteAddrSubCommand.java
+index ff662b506..9dacf1fae 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/acl/UpdateGlobalWhiteAddrSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/acl/UpdateGlobalWhiteAddrSubCommand.java
+@@ -37,7 +37,7 @@ public class UpdateGlobalWhiteAddrSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Update global white address for acl Config File in broker";
++ return "Update global white address for acl Config File in broker.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/broker/BrokerConsumeStatsSubCommad.java b/tools/src/main/java/org/apache/rocketmq/tools/command/broker/BrokerConsumeStatsSubCommad.java
+index 3f2f90673..7658a2139 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/broker/BrokerConsumeStatsSubCommad.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/broker/BrokerConsumeStatsSubCommad.java
+@@ -61,7 +61,7 @@ public class BrokerConsumeStatsSubCommad implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Fetch broker consume stats data";
++ return "Fetch broker consume stats data.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/broker/BrokerStatusSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/broker/BrokerStatusSubCommand.java
+index 830ff3425..ce934f547 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/broker/BrokerStatusSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/broker/BrokerStatusSubCommand.java
+@@ -44,7 +44,7 @@ public class BrokerStatusSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Fetch broker runtime status data";
++ return "Fetch broker runtime status data.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/broker/CommitLogSetReadAheadSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/broker/CommitLogSetReadAheadSubCommand.java
+index b00c7f5f5..4fdabfdf8 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/broker/CommitLogSetReadAheadSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/broker/CommitLogSetReadAheadSubCommand.java
+@@ -44,7 +44,7 @@ public class CommitLogSetReadAheadSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "set read ahead mode for all commitlog files";
++ return "Set read ahead mode for all commitlog files.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/broker/DeleteExpiredCommitLogSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/broker/DeleteExpiredCommitLogSubCommand.java
+index a4b2a51ad..142bb7b3c 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/broker/DeleteExpiredCommitLogSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/broker/DeleteExpiredCommitLogSubCommand.java
+@@ -37,7 +37,7 @@ public class DeleteExpiredCommitLogSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Delete expired CommitLog files";
++ return "Delete expired CommitLog files.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/broker/GetBrokerConfigCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/broker/GetBrokerConfigCommand.java
+index 5d86c10e4..c4762a296 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/broker/GetBrokerConfigCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/broker/GetBrokerConfigCommand.java
+@@ -45,7 +45,7 @@ public class GetBrokerConfigCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Get broker config by cluster or special broker";
++ return "Get broker config by cluster or special broker.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/broker/GetBrokerEpochSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/broker/GetBrokerEpochSubCommand.java
+index abe8fc622..1a8961e04 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/broker/GetBrokerEpochSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/broker/GetBrokerEpochSubCommand.java
+@@ -38,7 +38,7 @@ public class GetBrokerEpochSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Fetch broker epoch entries";
++ return "Fetch broker epoch entries.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/broker/GetColdDataFlowCtrInfoSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/broker/GetColdDataFlowCtrInfoSubCommand.java
+index 7c54e650c..34b3ba7d3 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/broker/GetColdDataFlowCtrInfoSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/broker/GetColdDataFlowCtrInfoSubCommand.java
+@@ -47,7 +47,7 @@ public class GetColdDataFlowCtrInfoSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "get cold data flow ctr info";
++ return "Get cold data flow ctr info.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/broker/RemoveColdDataFlowCtrGroupConfigSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/broker/RemoveColdDataFlowCtrGroupConfigSubCommand.java
+index b0477924f..f20407480 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/broker/RemoveColdDataFlowCtrGroupConfigSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/broker/RemoveColdDataFlowCtrGroupConfigSubCommand.java
+@@ -36,7 +36,7 @@ public class RemoveColdDataFlowCtrGroupConfigSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "remove consumer from cold ctr config";
++ return "Remove consumer from cold ctr config.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/broker/ResetMasterFlushOffsetSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/broker/ResetMasterFlushOffsetSubCommand.java
+index b2ac48c84..90451b51f 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/broker/ResetMasterFlushOffsetSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/broker/ResetMasterFlushOffsetSubCommand.java
+@@ -33,7 +33,7 @@ public class ResetMasterFlushOffsetSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Reset master flush offset in slave";
++ return "Reset master flush offset in slave.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/broker/UpdateBrokerConfigSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/broker/UpdateBrokerConfigSubCommand.java
+index 98abeb6ae..62816ef03 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/broker/UpdateBrokerConfigSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/broker/UpdateBrokerConfigSubCommand.java
+@@ -37,7 +37,7 @@ public class UpdateBrokerConfigSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Update broker's config";
++ return "Update broker's config.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/broker/UpdateColdDataFlowCtrGroupConfigSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/broker/UpdateColdDataFlowCtrGroupConfigSubCommand.java
+index d06a24b57..8d1a00077 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/broker/UpdateColdDataFlowCtrGroupConfigSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/broker/UpdateColdDataFlowCtrGroupConfigSubCommand.java
+@@ -39,7 +39,7 @@ public class UpdateColdDataFlowCtrGroupConfigSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "addOrUpdate cold data flow ctr group config";
++ return "Add or update cold data flow ctr group config.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/cluster/CLusterSendMsgRTCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/cluster/CLusterSendMsgRTCommand.java
+index 7253970bd..d755e9e5d 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/cluster/CLusterSendMsgRTCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/cluster/CLusterSendMsgRTCommand.java
+@@ -48,7 +48,7 @@ public class CLusterSendMsgRTCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "List All clusters Message Send RT";
++ return "List All clusters Message Send RT.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/cluster/ClusterListSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/cluster/ClusterListSubCommand.java
+index a7a840a44..ede0fa5cf 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/cluster/ClusterListSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/cluster/ClusterListSubCommand.java
+@@ -41,7 +41,7 @@ public class ClusterListSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "List cluster infos";
++ return "List cluster infos.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/connection/ConsumerConnectionSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/connection/ConsumerConnectionSubCommand.java
+index 630961e31..35f73d8a0 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/connection/ConsumerConnectionSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/connection/ConsumerConnectionSubCommand.java
+@@ -39,7 +39,7 @@ public class ConsumerConnectionSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Query consumer's socket connection, client version and subscription";
++ return "Query consumer's socket connection, client version and subscription.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/connection/ProducerConnectionSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/connection/ProducerConnectionSubCommand.java
+index 2533982c8..bde674ab2 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/connection/ProducerConnectionSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/connection/ProducerConnectionSubCommand.java
+@@ -36,7 +36,7 @@ public class ProducerConnectionSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Query producer's socket connection and client version";
++ return "Query producer's socket connection and client version.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/consumer/ConsumerStatusSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/consumer/ConsumerStatusSubCommand.java
+index 72b9c975e..d8f6f9aa9 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/consumer/ConsumerStatusSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/consumer/ConsumerStatusSubCommand.java
+@@ -47,7 +47,7 @@ public class ConsumerStatusSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Query consumer's internal data structure";
++ return "Query consumer's internal data structure.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/consumer/GetConsumerConfigSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/consumer/GetConsumerConfigSubCommand.java
+index 6095e7668..4a8253a02 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/consumer/GetConsumerConfigSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/consumer/GetConsumerConfigSubCommand.java
+@@ -43,7 +43,7 @@ public class GetConsumerConfigSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Get consumer config by subscription group name";
++ return "Get consumer config by subscription group name.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/consumer/StartMonitoringSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/consumer/StartMonitoringSubCommand.java
+index 2d08d0bd0..f5e140433 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/consumer/StartMonitoringSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/consumer/StartMonitoringSubCommand.java
+@@ -34,7 +34,7 @@ public class StartMonitoringSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Start Monitoring";
++ return "Start Monitoring.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/consumer/UpdateSubGroupSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/consumer/UpdateSubGroupSubCommand.java
+index f87bafc93..b17da4de4 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/consumer/UpdateSubGroupSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/consumer/UpdateSubGroupSubCommand.java
+@@ -41,7 +41,7 @@ public class UpdateSubGroupSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Update or create subscription group";
++ return "Update or create subscription group.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/container/AddBrokerSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/container/AddBrokerSubCommand.java
+index e9e5be4a5..007d42ae6 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/container/AddBrokerSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/container/AddBrokerSubCommand.java
+@@ -33,7 +33,7 @@ public class AddBrokerSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Add a broker to specified container";
++ return "Add a broker to specified container.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/container/RemoveBrokerSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/container/RemoveBrokerSubCommand.java
+index 7c455f858..ab25d8ebe 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/container/RemoveBrokerSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/container/RemoveBrokerSubCommand.java
+@@ -33,7 +33,7 @@ public class RemoveBrokerSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Remove a broker from specified container";
++ return "Remove a broker from specified container.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/controller/CleanControllerBrokerMetaSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/controller/CleanControllerBrokerMetaSubCommand.java
+index 856e4b426..24ed02566 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/controller/CleanControllerBrokerMetaSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/controller/CleanControllerBrokerMetaSubCommand.java
+@@ -37,7 +37,7 @@ public class CleanControllerBrokerMetaSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Clean metadata of broker on controller";
++ return "Clean metadata of broker on controller.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/controller/GetControllerMetaDataSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/controller/GetControllerMetaDataSubCommand.java
+index 70bd7f8e9..966443127 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/controller/GetControllerMetaDataSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/controller/GetControllerMetaDataSubCommand.java
+@@ -34,7 +34,7 @@ public class GetControllerMetaDataSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Get controller cluster's metadata";
++ return "Get controller cluster's metadata.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/controller/ReElectMasterSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/controller/ReElectMasterSubCommand.java
+index 1affe81f9..a522a903d 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/controller/ReElectMasterSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/controller/ReElectMasterSubCommand.java
+@@ -37,7 +37,7 @@ public class ReElectMasterSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Re-elect the specified broker as master";
++ return "Re-elect the specified broker as master.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/export/ExportConfigsCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/export/ExportConfigsCommand.java
+index b8191296d..03613b29c 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/export/ExportConfigsCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/export/ExportConfigsCommand.java
+@@ -42,7 +42,7 @@ public class ExportConfigsCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Export configs";
++ return "Export configs.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/export/ExportMetadataCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/export/ExportMetadataCommand.java
+index 1f9cf7d96..748f7b16e 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/export/ExportMetadataCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/export/ExportMetadataCommand.java
+@@ -46,7 +46,7 @@ public class ExportMetadataCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Export metadata";
++ return "Export metadata.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/export/ExportMetricsCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/export/ExportMetricsCommand.java
+index a793b4b84..5d8bb37ba 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/export/ExportMetricsCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/export/ExportMetricsCommand.java
+@@ -56,7 +56,7 @@ public class ExportMetricsCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Export metrics";
++ return "Export metrics.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/ha/GetSyncStateSetSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/ha/GetSyncStateSetSubCommand.java
+index 44b3ec3e1..b6231e4f9 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/ha/GetSyncStateSetSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/ha/GetSyncStateSetSubCommand.java
+@@ -40,7 +40,7 @@ public class GetSyncStateSetSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Fetch syncStateSet for target brokers";
++ return "Fetch syncStateSet for target brokers.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/ha/HAStatusSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/ha/HAStatusSubCommand.java
+index b1795e046..931658a08 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/ha/HAStatusSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/ha/HAStatusSubCommand.java
+@@ -41,7 +41,7 @@ public class HAStatusSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Fetch ha runtime status data";
++ return "Fetch ha runtime status data.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/message/CheckMsgSendRTCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/message/CheckMsgSendRTCommand.java
+index 4c6d5ffb6..b15b59d50 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/message/CheckMsgSendRTCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/message/CheckMsgSendRTCommand.java
+@@ -40,7 +40,7 @@ public class CheckMsgSendRTCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Check message send response time";
++ return "Check message send response time.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/message/ConsumeMessageCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/message/ConsumeMessageCommand.java
+index 8aed59ea4..02ff53269 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/message/ConsumeMessageCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/message/ConsumeMessageCommand.java
+@@ -70,7 +70,7 @@ public class ConsumeMessageCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Consume message";
++ return "Consume message.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/message/DumpCompactionLogCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/message/DumpCompactionLogCommand.java
+index ae6d9bdcf..eee8f3d4b 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/message/DumpCompactionLogCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/message/DumpCompactionLogCommand.java
+@@ -38,7 +38,7 @@ import java.nio.file.Paths;
+ public class DumpCompactionLogCommand implements SubCommand {
+ @Override
+ public String commandDesc() {
+- return "parse compaction log to message";
++ return "Parse compaction log to message.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/message/PrintMessageByQueueCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/message/PrintMessageByQueueCommand.java
+index 654560167..0418e88a7 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/message/PrintMessageByQueueCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/message/PrintMessageByQueueCommand.java
+@@ -108,7 +108,7 @@ public class PrintMessageByQueueCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Print Message Detail";
++ return "Print Message Detail by queueId.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/message/PrintMessageSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/message/PrintMessageSubCommand.java
+index d01c36d42..bb82f5079 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/message/PrintMessageSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/message/PrintMessageSubCommand.java
+@@ -62,7 +62,7 @@ public class PrintMessageSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Print Message Detail";
++ return "Print Message Detail.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgByIdSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgByIdSubCommand.java
+index 2880477f1..b42612150 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgByIdSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgByIdSubCommand.java
+@@ -186,7 +186,7 @@ public class QueryMsgByIdSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Query Message by Id";
++ return "Query Message by Id.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgByKeySubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgByKeySubCommand.java
+index ba7b00c3b..64627fd19 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgByKeySubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgByKeySubCommand.java
+@@ -36,7 +36,7 @@ public class QueryMsgByKeySubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Query Message by Key";
++ return "Query Message by Key.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgByOffsetSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgByOffsetSubCommand.java
+index d27313af1..14d0625fd 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgByOffsetSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgByOffsetSubCommand.java
+@@ -39,7 +39,7 @@ public class QueryMsgByOffsetSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Query Message by offset";
++ return "Query Message by offset.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgByUniqueKeySubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgByUniqueKeySubCommand.java
+index 1b28f8be1..b71cee901 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgByUniqueKeySubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgByUniqueKeySubCommand.java
+@@ -141,7 +141,7 @@ public class QueryMsgByUniqueKeySubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Query Message by Unique key";
++ return "Query Message by Unique key.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgTraceByIdSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgTraceByIdSubCommand.java
+index 2b982efef..2c546ec56 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgTraceByIdSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgTraceByIdSubCommand.java
+@@ -65,7 +65,7 @@ public class QueryMsgTraceByIdSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Query a message trace";
++ return "Query a message trace.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/message/SendMessageCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/message/SendMessageCommand.java
+index 836ee192b..970da6b16 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/message/SendMessageCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/message/SendMessageCommand.java
+@@ -41,7 +41,7 @@ public class SendMessageCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Send a message";
++ return "Send a message.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/namesrv/AddWritePermSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/namesrv/AddWritePermSubCommand.java
+index 98542d065..0b0a075bd 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/namesrv/AddWritePermSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/namesrv/AddWritePermSubCommand.java
+@@ -34,7 +34,7 @@ public class AddWritePermSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Add write perm of broker in all name server you defined in the -n param";
++ return "Add write perm of broker in all name server you defined in the -n param.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/namesrv/WipeWritePermSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/namesrv/WipeWritePermSubCommand.java
+index 213931ed8..637dd52c8 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/namesrv/WipeWritePermSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/namesrv/WipeWritePermSubCommand.java
+@@ -34,7 +34,7 @@ public class WipeWritePermSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Wipe write perm of broker in all name server you defined in the -n param";
++ return "Wipe write perm of broker in all name server you defined in the -n param.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/offset/SkipAccumulationSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/offset/SkipAccumulationSubCommand.java
+index 139821f9c..b22491a59 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/offset/SkipAccumulationSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/offset/SkipAccumulationSubCommand.java
+@@ -41,7 +41,7 @@ public class SkipAccumulationSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Skip all messages that are accumulated (not consumed) currently";
++ return "Skip all messages that are accumulated (not consumed) currently.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/stats/StatsAllSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/stats/StatsAllSubCommand.java
+index 1d49bbe11..96097a93e 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/stats/StatsAllSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/stats/StatsAllSubCommand.java
+@@ -144,7 +144,7 @@ public class StatsAllSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Topic and Consumer tps stats";
++ return "Topic and Consumer tps stats.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/topic/AllocateMQSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/topic/AllocateMQSubCommand.java
+index 3fa42f297..6a9b81eb8 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/topic/AllocateMQSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/topic/AllocateMQSubCommand.java
+@@ -41,7 +41,7 @@ public class AllocateMQSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Allocate MQ";
++ return "Allocate MQ.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicClusterSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicClusterSubCommand.java
+index 1dab693d9..098f34ff0 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicClusterSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicClusterSubCommand.java
+@@ -34,7 +34,7 @@ public class TopicClusterSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Get cluster info for topic";
++ return "Get cluster info for topic.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicListSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicListSubCommand.java
+index 346bac704..d9a279f80 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicListSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicListSubCommand.java
+@@ -45,7 +45,7 @@ public class TopicListSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Fetch all topic list from name server";
++ return "Fetch all topic list from name server.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicRouteSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicRouteSubCommand.java
+index f2dabec4e..70949d388 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicRouteSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicRouteSubCommand.java
+@@ -42,7 +42,7 @@ public class TopicRouteSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Examine topic route info";
++ return "Examine topic route info.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicStatusSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicStatusSubCommand.java
+index fdb249fab..a1619eced 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicStatusSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicStatusSubCommand.java
+@@ -40,7 +40,7 @@ public class TopicStatusSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Examine topic Status info";
++ return "Examine topic Status info.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/topic/UpdateOrderConfCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/topic/UpdateOrderConfCommand.java
+index bebc646b4..3040d04c2 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/topic/UpdateOrderConfCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/topic/UpdateOrderConfCommand.java
+@@ -36,7 +36,7 @@ public class UpdateOrderConfCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Create or update or delete order conf";
++ return "Create or update or delete order conf.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/topic/UpdateStaticTopicSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/topic/UpdateStaticTopicSubCommand.java
+index 85a18c654..3daeee86c 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/topic/UpdateStaticTopicSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/topic/UpdateStaticTopicSubCommand.java
+@@ -48,7 +48,7 @@ public class UpdateStaticTopicSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Update or create static topic, which has fixed number of queues";
++ return "Update or create static topic, which has fixed number of queues.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/topic/UpdateTopicPermSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/topic/UpdateTopicPermSubCommand.java
+index aaa881538..d27cd1861 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/topic/UpdateTopicPermSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/topic/UpdateTopicPermSubCommand.java
+@@ -44,7 +44,7 @@ public class UpdateTopicPermSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Update topic perm";
++ return "Update topic perm.";
+ }
+
+ @Override
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/topic/UpdateTopicSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/topic/UpdateTopicSubCommand.java
+index b68463396..298914175 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/topic/UpdateTopicSubCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/topic/UpdateTopicSubCommand.java
+@@ -42,7 +42,7 @@ public class UpdateTopicSubCommand implements SubCommand {
+
+ @Override
+ public String commandDesc() {
+- return "Update or create topic";
++ return "Update or create topic.";
+ }
+
+ @Override
+--
+2.32.0.windows.2
+
+
+From 744167bd01fab6821b4d5ae1794dc845153d5156 Mon Sep 17 00:00:00 2001
+From: Ziyi Tan <tanziyi0925@gmail.com>
+Date: Wed, 23 Aug 2023 08:32:17 +0800
+Subject: [PATCH 2/8] [ISSUE #7142] Add command `RocksDBConfigToJson` to
+ inspect rocksdb content (#7180)
+
+* feat: add command `RocksDBConfigToJson` to inspect rocksdb content
+
+Signed-off-by: Ziy1-Tan <ajb4596984460@gmail.com>
+
+* refactor: fix style
+
+---------
+
+Signed-off-by: Ziy1-Tan <ajb4596984460@gmail.com>
+Co-authored-by: Ziy1-Tan <ajb4596984460@gmail.com>
+---
+ .../tools/command/MQAdminStartup.java | 2 +
+ .../metadata/RocksDBConfigToJsonCommand.java | 118 ++++++++++++++++++
+ .../metadata/KvConfigToJsonCommandTest.java | 65 ++++++++++
+ 3 files changed, 185 insertions(+)
+ create mode 100644 tools/src/main/java/org/apache/rocketmq/tools/command/metadata/RocksDBConfigToJsonCommand.java
+ create mode 100644 tools/src/test/java/org/apache/rocketmq/tools/command/metadata/KvConfigToJsonCommandTest.java
+
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/MQAdminStartup.java b/tools/src/main/java/org/apache/rocketmq/tools/command/MQAdminStartup.java
+index 890125ca0..324aa1856 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/MQAdminStartup.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/MQAdminStartup.java
+@@ -80,6 +80,7 @@ import org.apache.rocketmq.tools.command.message.QueryMsgByOffsetSubCommand;
+ import org.apache.rocketmq.tools.command.message.QueryMsgByUniqueKeySubCommand;
+ import org.apache.rocketmq.tools.command.message.QueryMsgTraceByIdSubCommand;
+ import org.apache.rocketmq.tools.command.message.SendMessageCommand;
++import org.apache.rocketmq.tools.command.metadata.RocksDBConfigToJsonCommand;
+ import org.apache.rocketmq.tools.command.namesrv.AddWritePermSubCommand;
+ import org.apache.rocketmq.tools.command.namesrv.DeleteKvConfigCommand;
+ import org.apache.rocketmq.tools.command.namesrv.GetNamesrvConfigCommand;
+@@ -211,6 +212,7 @@ public class MQAdminStartup {
+
+ initCommand(new ClusterListSubCommand());
+ initCommand(new TopicListSubCommand());
++ initCommand(new RocksDBConfigToJsonCommand());
+
+ initCommand(new UpdateKvConfigCommand());
+ initCommand(new DeleteKvConfigCommand());
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/metadata/RocksDBConfigToJsonCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/metadata/RocksDBConfigToJsonCommand.java
+new file mode 100644
+index 000000000..3053f4684
+--- /dev/null
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/metadata/RocksDBConfigToJsonCommand.java
+@@ -0,0 +1,118 @@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one or more
++ * contributor license agreements. See the NOTICE file distributed with
++ * this work for additional information regarding copyright ownership.
++ * The ASF licenses this file to You under the Apache License, Version 2.0
++ * (the "License"); you may not use this file except in compliance with
++ * the License. You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++package org.apache.rocketmq.tools.command.metadata;
++
++import com.alibaba.fastjson.JSONObject;
++import org.apache.commons.cli.CommandLine;
++import org.apache.commons.cli.Option;
++import org.apache.commons.cli.Options;
++import org.apache.commons.lang3.StringUtils;
++import org.apache.rocketmq.common.config.RocksDBConfigManager;
++import org.apache.rocketmq.common.utils.DataConverter;
++import org.apache.rocketmq.remoting.RPCHook;
++import org.apache.rocketmq.tools.command.SubCommand;
++import org.apache.rocketmq.tools.command.SubCommandException;
++
++import java.io.File;
++import java.util.HashMap;
++import java.util.Map;
++
++public class RocksDBConfigToJsonCommand implements SubCommand {
++ private static final String TOPICS_JSON_CONFIG = "topics";
++ private static final String SUBSCRIPTION_GROUP_JSON_CONFIG = "subscriptionGroups";
++
++ @Override
++ public String commandName() {
++ return "rocksDBConfigToJson";
++ }
++
++ @Override
++ public String commandDesc() {
++ return "Convert RocksDB kv config (topics/subscriptionGroups) to json";
++ }
++
++ @Override
++ public Options buildCommandlineOptions(Options options) {
++ Option pathOption = new Option("p", "path", true,
++ "Absolute path to the metadata directory");
++ pathOption.setRequired(true);
++ options.addOption(pathOption);
++
++ Option configTypeOption = new Option("t", "configType", true, "Name of kv config, e.g. " +
++ "topics/subscriptionGroups");
++ configTypeOption.setRequired(true);
++ options.addOption(configTypeOption);
++
++ return options;
++ }
++
++ @Override
++ public void execute(CommandLine commandLine, Options options, RPCHook rpcHook) throws SubCommandException {
++ String path = commandLine.getOptionValue("path").trim();
++ if (StringUtils.isEmpty(path) || !new File(path).exists()) {
++ System.out.print("Rocksdb path is invalid.\n");
++ return;
++ }
++
++ String configType = commandLine.getOptionValue("configType").trim().toLowerCase();
++
++ final long memTableFlushInterval = 60 * 60 * 1000L;
++ RocksDBConfigManager kvConfigManager = new RocksDBConfigManager(memTableFlushInterval);
++ try {
++ if (TOPICS_JSON_CONFIG.toLowerCase().equals(configType)) {
++ // for topics.json
++ final Map<String, JSONObject> topicsJsonConfig = new HashMap<>();
++ final Map<String, JSONObject> topicConfigTable = new HashMap<>();
++ boolean isLoad = kvConfigManager.load(path, (key, value) -> {
++ final String topic = new String(key, DataConverter.charset);
++ final String topicConfig = new String(value, DataConverter.charset);
++ final JSONObject jsonObject = JSONObject.parseObject(topicConfig);
++ topicConfigTable.put(topic, jsonObject);
++ });
++
++ if (isLoad) {
++ topicsJsonConfig.put("topicConfigTable", (JSONObject) JSONObject.toJSON(topicConfigTable));
++ final String topicsJsonStr = JSONObject.toJSONString(topicsJsonConfig, true);
++ System.out.print(topicsJsonStr + "\n");
++ return;
++ }
++ }
++ if (SUBSCRIPTION_GROUP_JSON_CONFIG.toLowerCase().equals(configType)) {
++ // for subscriptionGroup.json
++ final Map<String, JSONObject> subscriptionGroupJsonConfig = new HashMap<>();
++ final Map<String, JSONObject> subscriptionGroupTable = new HashMap<>();
++ boolean isLoad = kvConfigManager.load(path, (key, value) -> {
++ final String subscriptionGroup = new String(key, DataConverter.charset);
++ final String subscriptionGroupConfig = new String(value, DataConverter.charset);
++ final JSONObject jsonObject = JSONObject.parseObject(subscriptionGroupConfig);
++ subscriptionGroupTable.put(subscriptionGroup, jsonObject);
++ });
++
++ if (isLoad) {
++ subscriptionGroupJsonConfig.put("subscriptionGroupTable",
++ (JSONObject) JSONObject.toJSON(subscriptionGroupTable));
++ final String subscriptionGroupJsonStr = JSONObject.toJSONString(subscriptionGroupJsonConfig, true);
++ System.out.print(subscriptionGroupJsonStr + "\n");
++ return;
++ }
++ }
++ System.out.print("Config type was not recognized, configType=" + configType + "\n");
++ } finally {
++ kvConfigManager.stop();
++ }
++ }
++}
+diff --git a/tools/src/test/java/org/apache/rocketmq/tools/command/metadata/KvConfigToJsonCommandTest.java b/tools/src/test/java/org/apache/rocketmq/tools/command/metadata/KvConfigToJsonCommandTest.java
+new file mode 100644
+index 000000000..b2f66c7b0
+--- /dev/null
++++ b/tools/src/test/java/org/apache/rocketmq/tools/command/metadata/KvConfigToJsonCommandTest.java
+@@ -0,0 +1,65 @@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one or more
++ * contributor license agreements. See the NOTICE file distributed with
++ * this work for additional information regarding copyright ownership.
++ * The ASF licenses this file to You under the Apache License, Version 2.0
++ * (the "License"); you may not use this file except in compliance with
++ * the License. You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++package org.apache.rocketmq.tools.command.metadata;
++
++import org.apache.commons.cli.CommandLine;
++import org.apache.commons.cli.DefaultParser;
++import org.apache.commons.cli.Options;
++import org.apache.rocketmq.srvutil.ServerUtil;
++import org.apache.rocketmq.tools.command.SubCommandException;
++import org.junit.Test;
++
++import java.io.File;
++
++import static org.assertj.core.api.Assertions.assertThat;
++
++public class KvConfigToJsonCommandTest {
++ private static final String BASE_PATH = System.getProperty("user.home") + File.separator + "store/config/";
++
++ @Test
++ public void testExecute() throws SubCommandException {
++ {
++ String[] cases = new String[]{"topics", "subscriptionGroups"};
++ for (String c : cases) {
++ RocksDBConfigToJsonCommand cmd = new RocksDBConfigToJsonCommand();
++ Options options = ServerUtil.buildCommandlineOptions(new Options());
++ String[] subargs = new String[]{"-p " + BASE_PATH + c, "-t " + c};
++ final CommandLine commandLine = ServerUtil.parseCmdLine("mqadmin " + cmd.commandName(), subargs,
++ cmd.buildCommandlineOptions(options), new DefaultParser());
++ cmd.execute(commandLine, options, null);
++ assertThat(commandLine.getOptionValue("p").trim()).isEqualTo(BASE_PATH + c);
++ assertThat(commandLine.getOptionValue("t").trim()).isEqualTo(c);
++ }
++ }
++ // invalid cases
++ {
++ String[][] cases = new String[][]{
++ {"-p " + BASE_PATH + "tmpPath", "-t topics"},
++ {"-p ", "-t topics"},
++ {"-p " + BASE_PATH + "topics", "-t invalid_type"}
++ };
++
++ for (String[] c : cases) {
++ RocksDBConfigToJsonCommand cmd = new RocksDBConfigToJsonCommand();
++ Options options = ServerUtil.buildCommandlineOptions(new Options());
++ final CommandLine commandLine = ServerUtil.parseCmdLine("mqadmin " + cmd.commandName(), c,
++ cmd.buildCommandlineOptions(options), new DefaultParser());
++ cmd.execute(commandLine, options, null);
++ }
++ }
++ }
++}
+--
+2.32.0.windows.2
+
+
+From bdede35db365a49b211cdc249c68b0f60a3df46d Mon Sep 17 00:00:00 2001
+From: mxsm <ljbmxsm@gmail.com>
+Date: Wed, 23 Aug 2023 08:34:56 +0800
+Subject: [PATCH 3/8] [ISSUE #7124] Fix the typos in the code comments (#7125)
+
+---
+ .../apache/rocketmq/broker/processor/ReplyMessageProcessor.java | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/ReplyMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/ReplyMessageProcessor.java
+index b2db356c8..d3bb048f7 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/processor/ReplyMessageProcessor.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/ReplyMessageProcessor.java
+@@ -234,7 +234,7 @@ public class ReplyMessageProcessor extends AbstractSendMessageProcessor {
+ } else {
+ response.setCode(ResponseCode.SUCCESS);
+ response.setRemark(null);
+- //set to zore to avoid client decoding exception
++ //set to zero to avoid client decoding exception
+ responseHeader.setMsgId("0");
+ responseHeader.setQueueId(queueIdInt);
+ responseHeader.setQueueOffset(0L);
+--
+2.32.0.windows.2
+
+
+From 9bb73b9a38548b99ac5126c40380c3c2e7fc586e Mon Sep 17 00:00:00 2001
+From: lizhimins <707364882@qq.com>
+Date: Wed, 23 Aug 2023 09:46:27 +0800
+Subject: [PATCH 4/8] [#ISSUE 7222] Bug fix and refactoring of the Indexfile in
+ tiered storage (#7224)
+
+---
+ .../tieredstore/file/TieredIndexFile.java | 38 +++++++--
+ .../tieredstore/file/TieredIndexFileTest.java | 84 +++++--------------
+ 2 files changed, 52 insertions(+), 70 deletions(-)
+
+diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredIndexFile.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredIndexFile.java
+index 50beb01ae..eda5e0106 100644
+--- a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredIndexFile.java
++++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredIndexFile.java
+@@ -16,6 +16,7 @@
+ */
+ package org.apache.rocketmq.tieredstore.file;
+
++import com.google.common.annotations.VisibleForTesting;
+ import java.io.File;
+ import java.io.IOException;
+ import java.nio.ByteBuffer;
+@@ -99,7 +100,7 @@ public class TieredIndexFile {
+ this::doScheduleTask, 10, 10, TimeUnit.SECONDS);
+ }
+
+- private void doScheduleTask() {
++ protected void doScheduleTask() {
+ try {
+ curFileLock.lock();
+ try {
+@@ -145,6 +146,11 @@ public class TieredIndexFile {
+ }
+ }
+
++ @VisibleForTesting
++ public MappedFile getPreMappedFile() {
++ return preMappedFile;
++ }
++
+ private void initFile() throws IOException {
+ curMappedFile = new DefaultMappedFile(curFilePath, fileMaxSize);
+ initIndexFileHeader(curMappedFile);
+@@ -156,19 +162,26 @@ public class TieredIndexFile {
+
+ if (isFileSealed(curMappedFile)) {
+ if (preFileExists) {
+- preFile.delete();
++ if (preFile.delete()) {
++ logger.info("Pre IndexFile deleted success", preFilepath);
++ } else {
++ logger.error("Pre IndexFile deleted failed", preFilepath);
++ }
+ }
+ boolean rename = curMappedFile.renameTo(preFilepath);
+ if (rename) {
+ preMappedFile = curMappedFile;
+ curMappedFile = new DefaultMappedFile(curFilePath, fileMaxSize);
++ initIndexFileHeader(curMappedFile);
+ preFileExists = true;
+ }
+ }
++
+ if (preFileExists) {
+ synchronized (TieredIndexFile.class) {
+ if (inflightCompactFuture.isDone()) {
+- inflightCompactFuture = TieredStoreExecutor.compactIndexFileExecutor.submit(new CompactTask(storeConfig, preMappedFile, flatFile), null);
++ inflightCompactFuture = TieredStoreExecutor.compactIndexFileExecutor.submit(
++ new CompactTask(storeConfig, preMappedFile, flatFile), null);
+ }
+ }
+ }
+@@ -261,7 +274,8 @@ public class TieredIndexFile {
+ }
+ }
+
+- public CompletableFuture<List<Pair<Long, ByteBuffer>>> queryAsync(String topic, String key, long beginTime, long endTime) {
++ public CompletableFuture<List<Pair<Long, ByteBuffer>>> queryAsync(String topic, String key, long beginTime,
++ long endTime) {
+ int hashCode = indexKeyHashMethod(buildKey(topic, key));
+ int slotPosition = hashCode % maxHashSlotNum;
+ List<TieredFileSegment> fileSegmentList = flatFile.getFileListByTime(beginTime, endTime);
+@@ -355,7 +369,7 @@ public class TieredIndexFile {
+ private final int fileMaxSize;
+ private MappedFile originFile;
+ private TieredFlatFile fileQueue;
+- private final MappedFile compactFile;
++ private MappedFile compactFile;
+
+ public CompactTask(TieredMessageStoreConfig storeConfig, MappedFile originFile,
+ TieredFlatFile fileQueue) throws IOException {
+@@ -381,6 +395,17 @@ public class TieredIndexFile {
+ } catch (Throwable throwable) {
+ logger.error("TieredIndexFile#compactTask: compact index file failed:", throwable);
+ }
++
++ try {
++ if (originFile != null) {
++ originFile.destroy(-1);
++ }
++ if (compactFile != null) {
++ compactFile.destroy(-1);
++ }
++ } catch (Throwable throwable) {
++ logger.error("TieredIndexFile#compactTask: destroy index file failed:", throwable);
++ }
+ }
+
+ public void compact() {
+@@ -396,6 +421,8 @@ public class TieredIndexFile {
+ fileQueue.commit(true);
+ compactFile.destroy(-1);
+ originFile.destroy(-1);
++ compactFile = null;
++ originFile = null;
+ }
+
+ private void buildCompactFile() {
+@@ -414,6 +441,7 @@ public class TieredIndexFile {
+ if (slotValue != -1) {
+ int indexTotalSize = 0;
+ int indexPosition = slotValue;
++
+ while (indexPosition >= 0 && indexPosition < maxIndexNum) {
+ int indexOffset = INDEX_FILE_HEADER_SIZE + maxHashSlotNum * INDEX_FILE_HASH_SLOT_SIZE
+ + indexPosition * INDEX_FILE_HASH_ORIGIN_INDEX_SIZE;
+diff --git a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/file/TieredIndexFileTest.java b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/file/TieredIndexFileTest.java
+index 7ef49578d..262d6645b 100644
+--- a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/file/TieredIndexFileTest.java
++++ b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/file/TieredIndexFileTest.java
+@@ -19,9 +19,8 @@ package org.apache.rocketmq.tieredstore.file;
+ import com.sun.jna.Platform;
+ import java.io.IOException;
+ import java.nio.ByteBuffer;
++import java.time.Duration;
+ import java.util.List;
+-import java.util.concurrent.TimeUnit;
+-import org.apache.commons.lang3.SystemUtils;
+ import org.apache.commons.lang3.tuple.Pair;
+ import org.apache.rocketmq.common.message.MessageQueue;
+ import org.apache.rocketmq.tieredstore.TieredStoreTestUtil;
+@@ -31,9 +30,7 @@ import org.apache.rocketmq.tieredstore.util.TieredStoreUtil;
+ import org.awaitility.Awaitility;
+ import org.junit.After;
+ import org.junit.Assert;
+-import org.junit.Assume;
+ import org.junit.Before;
+-import org.junit.Ignore;
+ import org.junit.Test;
+
+ public class TieredIndexFileTest {
+@@ -45,11 +42,12 @@ public class TieredIndexFileTest {
+ @Before
+ public void setUp() {
+ storeConfig = new TieredMessageStoreConfig();
++ storeConfig.setBrokerName("IndexFileBroker");
+ storeConfig.setStorePathRootDir(storePath);
+- storeConfig.setTieredBackendServiceProvider("org.apache.rocketmq.tieredstore.provider.memory.MemoryFileSegment");
+- storeConfig.setTieredStoreIndexFileMaxHashSlotNum(2);
+- storeConfig.setTieredStoreIndexFileMaxIndexNum(3);
+- mq = new MessageQueue("TieredIndexFileTest", storeConfig.getBrokerName(), 1);
++ storeConfig.setTieredBackendServiceProvider("org.apache.rocketmq.tieredstore.provider.posix.PosixFileSegment");
++ storeConfig.setTieredStoreIndexFileMaxHashSlotNum(5);
++ storeConfig.setTieredStoreIndexFileMaxIndexNum(20);
++ mq = new MessageQueue("IndexFileTest", storeConfig.getBrokerName(), 1);
+ TieredStoreUtil.getMetadataStore(storeConfig);
+ TieredStoreExecutor.init();
+ }
+@@ -61,77 +59,33 @@ public class TieredIndexFileTest {
+ TieredStoreExecutor.shutdown();
+ }
+
+- @Ignore
+ @Test
+ public void testAppendAndQuery() throws IOException, ClassNotFoundException, NoSuchMethodException {
+ if (Platform.isWindows()) {
+ return;
+ }
+
+- // skip this test on windows
+- Assume.assumeFalse(SystemUtils.IS_OS_WINDOWS);
+-
+ TieredFileAllocator fileQueueFactory = new TieredFileAllocator(storeConfig);
+ TieredIndexFile indexFile = new TieredIndexFile(fileQueueFactory, storePath);
++
+ indexFile.append(mq, 0, "key3", 3, 300, 1000);
+ indexFile.append(mq, 0, "key2", 2, 200, 1100);
+ indexFile.append(mq, 0, "key1", 1, 100, 1200);
+
+- Awaitility.waitAtMost(5, TimeUnit.SECONDS)
+- .until(() -> {
+- List<Pair<Long, ByteBuffer>> indexList = indexFile.queryAsync(mq.getTopic(), "key1", 1000, 1200).join();
+- if (indexList.size() != 1) {
+- return false;
+- }
+-
+- ByteBuffer indexBuffer = indexList.get(0).getValue();
+- Assert.assertEquals(TieredIndexFile.INDEX_FILE_HASH_COMPACT_INDEX_SIZE * 2, indexBuffer.remaining());
+-
+- Assert.assertEquals(1, indexBuffer.getLong(4 + 4 + 4));
+- Assert.assertEquals(100, indexBuffer.getInt(4 + 4 + 4 + 8));
+- Assert.assertEquals(200, indexBuffer.getInt(4 + 4 + 4 + 8 + 4));
+-
+- Assert.assertEquals(3, indexBuffer.getLong(TieredIndexFile.INDEX_FILE_HASH_COMPACT_INDEX_SIZE + 4 + 4 + 4));
+- Assert.assertEquals(300, indexBuffer.getInt(TieredIndexFile.INDEX_FILE_HASH_COMPACT_INDEX_SIZE + 4 + 4 + 4 + 8));
+- Assert.assertEquals(0, indexBuffer.getInt(TieredIndexFile.INDEX_FILE_HASH_COMPACT_INDEX_SIZE + 4 + 4 + 4 + 8 + 4));
+- return true;
+- });
+-
+- indexFile.append(mq, 0, "key4", 4, 400, 1300);
+- indexFile.append(mq, 0, "key4", 4, 400, 1300);
+- indexFile.append(mq, 0, "key4", 4, 400, 1300);
+-
+- Awaitility.waitAtMost(5, TimeUnit.SECONDS)
+- .until(() -> {
+- List<Pair<Long, ByteBuffer>> indexList = indexFile.queryAsync(mq.getTopic(), "key4", 1300, 1300).join();
+- if (indexList.size() != 1) {
+- return false;
+- }
+-
+- ByteBuffer indexBuffer = indexList.get(0).getValue();
+- Assert.assertEquals(TieredIndexFile.INDEX_FILE_HASH_COMPACT_INDEX_SIZE * 3, indexBuffer.remaining());
+- Assert.assertEquals(4, indexBuffer.getLong(4 + 4 + 4));
+- Assert.assertEquals(400, indexBuffer.getInt(4 + 4 + 4 + 8));
+- Assert.assertEquals(0, indexBuffer.getInt(4 + 4 + 4 + 8 + 4));
+- return true;
+- });
+-
+- List<Pair<Long, ByteBuffer>> indexList = indexFile.queryAsync(mq.getTopic(), "key1", 1300, 1300).join();
++ // do not do schedule task here
++ TieredStoreExecutor.shutdown();
++ List<Pair<Long, ByteBuffer>> indexList =
++ indexFile.queryAsync(mq.getTopic(), "key1", 1000, 1200).join();
+ Assert.assertEquals(0, indexList.size());
+
+- indexList = indexFile.queryAsync(mq.getTopic(), "key4", 1200, 1300).join();
+- Assert.assertEquals(2, indexList.size());
+-
+- ByteBuffer indexBuffer = indexList.get(0).getValue();
+- Assert.assertEquals(TieredIndexFile.INDEX_FILE_HASH_COMPACT_INDEX_SIZE * 3, indexBuffer.remaining());
+- Assert.assertEquals(4, indexBuffer.getLong(4 + 4 + 4));
+- Assert.assertEquals(400, indexBuffer.getInt(4 + 4 + 4 + 8));
+- Assert.assertEquals(0, indexBuffer.getInt(4 + 4 + 4 + 8 + 4));
++ // do compaction once
++ TieredStoreExecutor.init();
++ storeConfig.setTieredStoreIndexFileRollingIdleInterval(0);
++ indexFile.doScheduleTask();
++ Awaitility.await().atMost(Duration.ofSeconds(10))
++ .until(() -> !indexFile.getPreMappedFile().getFile().exists());
+
+- indexBuffer = indexList.get(1).getValue();
+- Assert.assertEquals(TieredIndexFile.INDEX_FILE_HASH_COMPACT_INDEX_SIZE, indexBuffer.remaining());
+- Assert.assertEquals(2, indexBuffer.getLong(4 + 4 + 4));
+- Assert.assertEquals(200, indexBuffer.getInt(4 + 4 + 4 + 8));
+- Assert.assertEquals(100, indexBuffer.getInt(4 + 4 + 4 + 8 + 4));
++ indexList = indexFile.queryAsync(mq.getTopic(), "key1", 1000, 1200).join();
++ Assert.assertEquals(1, indexList.size());
+ }
+ }
+--
+2.32.0.windows.2
+
+
+From 69c26d3d29cde7b4484ecd112ab9224f9f42bf45 Mon Sep 17 00:00:00 2001
+From: guyinyou <36399867+guyinyou@users.noreply.github.com>
+Date: Wed, 23 Aug 2023 10:27:52 +0800
+Subject: [PATCH 5/8] [ISSUE #7228] Converge the use of some important
+ variables for some class
+
+---
+ .../apache/rocketmq/store/ConsumeQueue.java | 16 ++++++------
+ .../rocketmq/store/MappedFileQueue.java | 26 +++++++++++--------
+ .../store/MultiPathMappedFileQueue.java | 4 +--
+ 3 files changed, 24 insertions(+), 22 deletions(-)
+
+diff --git a/store/src/main/java/org/apache/rocketmq/store/ConsumeQueue.java b/store/src/main/java/org/apache/rocketmq/store/ConsumeQueue.java
+index a0b886eb0..56bee2af3 100644
+--- a/store/src/main/java/org/apache/rocketmq/store/ConsumeQueue.java
++++ b/store/src/main/java/org/apache/rocketmq/store/ConsumeQueue.java
+@@ -145,7 +145,7 @@ public class ConsumeQueue implements ConsumeQueueInterface, FileQueueLifeCycle {
+
+ if (offset >= 0 && size > 0) {
+ mappedFileOffset = i + CQ_STORE_UNIT_SIZE;
+- this.maxPhysicOffset = offset + size;
++ this.setMaxPhysicOffset(offset + size);
+ if (isExtAddr(tagsCode)) {
+ maxExtAddr = tagsCode;
+ }
+@@ -409,7 +409,7 @@ public class ConsumeQueue implements ConsumeQueueInterface, FileQueueLifeCycle {
+
+ int logicFileSize = this.mappedFileSize;
+
+- this.maxPhysicOffset = phyOffset;
++ this.setMaxPhysicOffset(phyOffset);
+ long maxExtAddr = 1;
+ boolean shouldDeleteFile = false;
+ while (true) {
+@@ -435,7 +435,7 @@ public class ConsumeQueue implements ConsumeQueueInterface, FileQueueLifeCycle {
+ mappedFile.setWrotePosition(pos);
+ mappedFile.setCommittedPosition(pos);
+ mappedFile.setFlushedPosition(pos);
+- this.maxPhysicOffset = offset + size;
++ this.setMaxPhysicOffset(offset + size);
+ // This maybe not take effect, when not every consume queue has extend file.
+ if (isExtAddr(tagsCode)) {
+ maxExtAddr = tagsCode;
+@@ -453,7 +453,7 @@ public class ConsumeQueue implements ConsumeQueueInterface, FileQueueLifeCycle {
+ mappedFile.setWrotePosition(pos);
+ mappedFile.setCommittedPosition(pos);
+ mappedFile.setFlushedPosition(pos);
+- this.maxPhysicOffset = offset + size;
++ this.setMaxPhysicOffset(offset + size);
+ if (isExtAddr(tagsCode)) {
+ maxExtAddr = tagsCode;
+ }
+@@ -881,8 +881,8 @@ public class ConsumeQueue implements ConsumeQueueInterface, FileQueueLifeCycle {
+ private boolean putMessagePositionInfo(final long offset, final int size, final long tagsCode,
+ final long cqOffset) {
+
+- if (offset + size <= this.maxPhysicOffset) {
+- log.warn("Maybe try to build consume queue repeatedly maxPhysicOffset={} phyOffset={}", maxPhysicOffset, offset);
++ if (offset + size <= this.getMaxPhysicOffset()) {
++ log.warn("Maybe try to build consume queue repeatedly maxPhysicOffset={} phyOffset={}", this.getMaxPhysicOffset(), offset);
+ return true;
+ }
+
+@@ -926,7 +926,7 @@ public class ConsumeQueue implements ConsumeQueueInterface, FileQueueLifeCycle {
+ );
+ }
+ }
+- this.maxPhysicOffset = offset + size;
++ this.setMaxPhysicOffset(offset + size);
+ return mappedFile.appendMessage(this.byteBufferIndex.array());
+ }
+ return false;
+@@ -1130,7 +1130,7 @@ public class ConsumeQueue implements ConsumeQueueInterface, FileQueueLifeCycle {
+
+ @Override
+ public void destroy() {
+- this.maxPhysicOffset = -1;
++ this.setMaxPhysicOffset(-1);
+ this.minLogicOffset = 0;
+ this.mappedFileQueue.destroy();
+ if (isExtReadEnable()) {
+diff --git a/store/src/main/java/org/apache/rocketmq/store/MappedFileQueue.java b/store/src/main/java/org/apache/rocketmq/store/MappedFileQueue.java
+index 0bc70642f..32b90d14f 100644
+--- a/store/src/main/java/org/apache/rocketmq/store/MappedFileQueue.java
++++ b/store/src/main/java/org/apache/rocketmq/store/MappedFileQueue.java
+@@ -285,7 +285,7 @@ public class MappedFileQueue implements Swappable {
+ if (this.mappedFiles.isEmpty())
+ return 0;
+
+- long committed = this.flushedWhere;
++ long committed = this.getFlushedWhere();
+ if (committed != 0) {
+ MappedFile mappedFile = this.getLastMappedFile(0, false);
+ if (mappedFile != null) {
+@@ -442,11 +442,11 @@ public class MappedFileQueue implements Swappable {
+ }
+
+ public long remainHowManyDataToCommit() {
+- return getMaxWrotePosition() - committedWhere;
++ return getMaxWrotePosition() - getCommittedWhere();
+ }
+
+ public long remainHowManyDataToFlush() {
+- return getMaxOffset() - flushedWhere;
++ return getMaxOffset() - this.getFlushedWhere();
+ }
+
+ public void deleteLastMappedFile() {
+@@ -616,15 +616,15 @@ public class MappedFileQueue implements Swappable {
+
+ public boolean flush(final int flushLeastPages) {
+ boolean result = true;
+- MappedFile mappedFile = this.findMappedFileByOffset(this.flushedWhere, this.flushedWhere == 0);
++ MappedFile mappedFile = this.findMappedFileByOffset(this.getFlushedWhere(), this.getFlushedWhere() == 0);
+ if (mappedFile != null) {
+ long tmpTimeStamp = mappedFile.getStoreTimestamp();
+ int offset = mappedFile.flush(flushLeastPages);
+ long where = mappedFile.getFileFromOffset() + offset;
+- result = where == this.flushedWhere;
+- this.flushedWhere = where;
++ result = where == this.getFlushedWhere();
++ this.setFlushedWhere(where);
+ if (0 == flushLeastPages) {
+- this.storeTimestamp = tmpTimeStamp;
++ this.setStoreTimestamp(tmpTimeStamp);
+ }
+ }
+
+@@ -633,12 +633,12 @@ public class MappedFileQueue implements Swappable {
+
+ public synchronized boolean commit(final int commitLeastPages) {
+ boolean result = true;
+- MappedFile mappedFile = this.findMappedFileByOffset(this.committedWhere, this.committedWhere == 0);
++ MappedFile mappedFile = this.findMappedFileByOffset(this.getCommittedWhere(), this.getCommittedWhere() == 0);
+ if (mappedFile != null) {
+ int offset = mappedFile.commit(commitLeastPages);
+ long where = mappedFile.getFileFromOffset() + offset;
+- result = where == this.committedWhere;
+- this.committedWhere = where;
++ result = where == this.getCommittedWhere();
++ this.setCommittedWhere(where);
+ }
+
+ return result;
+@@ -763,7 +763,7 @@ public class MappedFileQueue implements Swappable {
+ mf.destroy(1000 * 3);
+ }
+ this.mappedFiles.clear();
+- this.flushedWhere = 0;
++ this.setFlushedWhere(0);
+
+ // delete parent directory
+ File file = new File(storePath);
+@@ -848,6 +848,10 @@ public class MappedFileQueue implements Swappable {
+ return storeTimestamp;
+ }
+
++ public void setStoreTimestamp(long storeTimestamp) {
++ this.storeTimestamp = storeTimestamp;
++ }
++
+ public List<MappedFile> getMappedFiles() {
+ return mappedFiles;
+ }
+diff --git a/store/src/main/java/org/apache/rocketmq/store/MultiPathMappedFileQueue.java b/store/src/main/java/org/apache/rocketmq/store/MultiPathMappedFileQueue.java
+index 8f5af9438..8ff050dfe 100644
+--- a/store/src/main/java/org/apache/rocketmq/store/MultiPathMappedFileQueue.java
++++ b/store/src/main/java/org/apache/rocketmq/store/MultiPathMappedFileQueue.java
+@@ -16,7 +16,6 @@
+ */
+ package org.apache.rocketmq.store;
+
+-
+ import java.util.Arrays;
+ import java.util.HashSet;
+ import java.util.Set;
+@@ -113,8 +112,7 @@ public class MultiPathMappedFileQueue extends MappedFileQueue {
+ mf.destroy(1000 * 3);
+ }
+ this.mappedFiles.clear();
+- this.flushedWhere = 0;
+-
++ this.setFlushedWhere(0);
+
+ Set<String> storePathSet = getPaths();
+ storePathSet.addAll(getReadonlyPaths());
+--
+2.32.0.windows.2
+
+
+From 3884f595949462044c5cb3c236199bc1d7ad2341 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?=E7=9F=B3=E8=87=BB=E8=87=BB=28Steven=20shi=29?=
+ <shirenchuang@users.noreply.github.com>
+Date: Wed, 23 Aug 2023 11:10:30 +0800
+Subject: [PATCH 6/8] [ISSUE #7149] When creating and updating Topic, there
+ will be problems with permission settings (#7151)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+* [ISSUE #7149] fix bug : When creating and updating Topic, there will be problems with permission settings
+
+* [ISSUE #7149] fix bug : When creating and updating Topic, there will be problems with permission settings
+
+* [issue#7249]
+
+---------
+
+Co-authored-by: 十真 <shirenchuang.src@cainiao.com>
+---
+ .../main/java/org/apache/rocketmq/broker/BrokerController.java | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java
+index 13f9d002b..e8f943702 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java
+@@ -1733,7 +1733,8 @@ public class BrokerController {
+ new TopicConfig(topicConfig.getTopicName(),
+ topicConfig.getReadQueueNums(),
+ topicConfig.getWriteQueueNums(),
+- this.brokerConfig.getBrokerPermission(), topicConfig.getTopicSysFlag());
++ topicConfig.getPerm()
++ & this.brokerConfig.getBrokerPermission(), topicConfig.getTopicSysFlag());
+ } else {
+ registerTopicConfig = new TopicConfig(topicConfig);
+ }
+--
+2.32.0.windows.2
+
+
+From 017ad110475e8024585327b44f47e5e97aabc63b Mon Sep 17 00:00:00 2001
+From: echooymxq <echooy.mxq@gmail.com>
+Date: Wed, 23 Aug 2023 11:11:42 +0800
+Subject: [PATCH 7/8] [ISSUE #7219] Fix Concurrent modify syncStateSet and Mark
+ synchronizing frequently when shrink. (#7220)
+
+---
+ .../broker/controller/ReplicasManager.java | 29 ++++++++++---------
+ .../ha/autoswitch/AutoSwitchHAService.java | 21 ++++++++------
+ 2 files changed, 28 insertions(+), 22 deletions(-)
+
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java b/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java
+index abae7cdb0..37c82e434 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java
+@@ -542,7 +542,7 @@ public class ReplicasManager {
+ this.brokerMetadata.updateAndPersist(brokerConfig.getBrokerClusterName(), brokerConfig.getBrokerName(), tempBrokerMetadata.getBrokerId());
+ this.tempBrokerMetadata.clear();
+ this.brokerControllerId = this.brokerMetadata.getBrokerId();
+- this.haService.setBrokerControllerId(this.brokerControllerId);
++ this.haService.setLocalBrokerId(this.brokerControllerId);
+ return true;
+ } catch (Exception e) {
+ LOGGER.error("fail to create metadata file", e);
+@@ -594,7 +594,7 @@ public class ReplicasManager {
+ if (this.brokerMetadata.isLoaded()) {
+ this.registerState = RegisterState.CREATE_METADATA_FILE_DONE;
+ this.brokerControllerId = brokerMetadata.getBrokerId();
+- this.haService.setBrokerControllerId(this.brokerControllerId);
++ this.haService.setLocalBrokerId(this.brokerControllerId);
+ return;
+ }
+ // 2. check if temp metadata exist
+@@ -735,23 +735,26 @@ public class ReplicasManager {
+ if (this.checkSyncStateSetTaskFuture != null) {
+ this.checkSyncStateSetTaskFuture.cancel(false);
+ }
+- this.checkSyncStateSetTaskFuture = this.scheduledService.scheduleAtFixedRate(() -> {
+- checkSyncStateSetAndDoReport();
+- }, 3 * 1000, this.brokerConfig.getCheckSyncStateSetPeriod(), TimeUnit.MILLISECONDS);
++ this.checkSyncStateSetTaskFuture = this.scheduledService.scheduleAtFixedRate(this::checkSyncStateSetAndDoReport, 3 * 1000,
++ this.brokerConfig.getCheckSyncStateSetPeriod(), TimeUnit.MILLISECONDS);
+ }
+
+ private void checkSyncStateSetAndDoReport() {
+- final Set<Long> newSyncStateSet = this.haService.maybeShrinkSyncStateSet();
+- newSyncStateSet.add(this.brokerControllerId);
+- synchronized (this) {
+- if (this.syncStateSet != null) {
+- // Check if syncStateSet changed
+- if (this.syncStateSet.size() == newSyncStateSet.size() && this.syncStateSet.containsAll(newSyncStateSet)) {
+- return;
++ try {
++ final Set<Long> newSyncStateSet = this.haService.maybeShrinkSyncStateSet();
++ newSyncStateSet.add(this.brokerControllerId);
++ synchronized (this) {
++ if (this.syncStateSet != null) {
++ // Check if syncStateSet changed
++ if (this.syncStateSet.size() == newSyncStateSet.size() && this.syncStateSet.containsAll(newSyncStateSet)) {
++ return;
++ }
+ }
+ }
++ doReportSyncStateSetChanged(newSyncStateSet);
++ } catch (Exception e) {
++ LOGGER.error("Check syncStateSet error", e);
+ }
+- doReportSyncStateSetChanged(newSyncStateSet);
+ }
+
+ private void doReportSyncStateSetChanged(Set<Long> newSyncStateSet) {
+diff --git a/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAService.java b/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAService.java
+index 6dc734e0c..d5393fdca 100644
+--- a/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAService.java
++++ b/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAService.java
+@@ -41,6 +41,7 @@ import java.nio.channels.SocketChannel;
+ import java.util.ArrayList;
+ import java.util.HashSet;
+ import java.util.List;
++import java.util.Iterator;
+ import java.util.Map;
+ import java.util.Objects;
+ import java.util.Set;
+@@ -73,7 +74,7 @@ public class AutoSwitchHAService extends DefaultHAService {
+ private EpochFileCache epochCache;
+ private AutoSwitchHAClient haClient;
+
+- private Long brokerControllerId = null;
++ private Long localBrokerId = null;
+
+ public AutoSwitchHAService() {
+ }
+@@ -287,9 +288,11 @@ public class AutoSwitchHAService extends DefaultHAService {
+
+ // If the slaveBrokerId is in syncStateSet but not in connectionCaughtUpTimeTable,
+ // it means that the broker has not connected.
+- for (Long slaveBrokerId : newSyncStateSet) {
+- if (!this.connectionCaughtUpTimeTable.containsKey(slaveBrokerId)) {
+- newSyncStateSet.remove(slaveBrokerId);
++ Iterator<Long> iterator = newSyncStateSet.iterator();
++ while (iterator.hasNext()) {
++ Long slaveBrokerId = iterator.next();
++ if (!Objects.equals(slaveBrokerId, this.localBrokerId) && !this.connectionCaughtUpTimeTable.containsKey(slaveBrokerId)) {
++ iterator.remove();
+ isSyncStateSetChanged = true;
+ }
+ }
+@@ -419,7 +422,7 @@ public class AutoSwitchHAService extends DefaultHAService {
+ // To avoid the syncStateSet is not consistent with connectionList.
+ // Fix issue: https://github.com/apache/rocketmq/issues/6662
+ for (Long syncId : currentSyncStateSet) {
+- if (!idList.contains(syncId) && this.brokerControllerId != null && !Objects.equals(syncId, this.brokerControllerId)) {
++ if (!idList.contains(syncId) && this.localBrokerId != null && !Objects.equals(syncId, this.localBrokerId)) {
+ LOGGER.warn("Slave {} is still in syncStateSet, but has lost its connection. So new offset can't be compute.", syncId);
+ // Without check and re-compute, return the confirmOffset's value directly.
+ return this.defaultMessageStore.getConfirmOffsetDirectly();
+@@ -545,12 +548,12 @@ public class AutoSwitchHAService extends DefaultHAService {
+ return this.epochCache.getAllEntries();
+ }
+
+- public Long getBrokerControllerId() {
+- return brokerControllerId;
++ public Long getLocalBrokerId() {
++ return localBrokerId;
+ }
+
+- public void setBrokerControllerId(Long brokerControllerId) {
+- this.brokerControllerId = brokerControllerId;
++ public void setLocalBrokerId(Long localBrokerId) {
++ this.localBrokerId = localBrokerId;
+ }
+
+ class AutoSwitchAcceptSocketService extends AcceptSocketService {
+--
+2.32.0.windows.2
+
+
+From 77e8e54b37c3fc3ea0beffc1ace6f5bf20af10d9 Mon Sep 17 00:00:00 2001
+From: lk <xdkxlk@outlook.com>
+Date: Wed, 23 Aug 2023 15:56:39 +0800
+Subject: [PATCH 8/8] [ISSUE #7223] Support batch ack for grpc client in proxy
+ (#7225)
+
+---
+ .../client/impl/mqclient/MQClientAPIExt.java | 26 +++
+ .../rocketmq/proxy/config/ProxyConfig.java | 10 +
+ .../grpc/v2/consumer/AckMessageActivity.java | 136 ++++++++---
+ .../proxy/processor/AbstractProcessor.java | 4 +-
+ .../proxy/processor/BatchAckResult.java | 53 +++++
+ .../proxy/processor/ConsumerProcessor.java | 64 +++++
+ .../processor/DefaultMessagingProcessor.java | 7 +
+ .../proxy/processor/MessagingProcessor.java | 18 ++
+ .../message/ClusterMessageService.java | 16 +-
+ .../service/message/LocalMessageService.java | 58 +++++
+ .../proxy/service/message/MessageService.java | 8 +
+ .../service/message/ReceiptHandleMessage.java | 39 ++++
+ .../v2/consumer/AckMessageActivityTest.java | 221 +++++++++++++++---
+ .../proxy/processor/BaseProcessorTest.java | 18 +-
+ .../processor/ConsumerProcessorTest.java | 115 +++++++++
+ .../service/mqclient/MQClientAPIExtTest.java | 12 +
+ 16 files changed, 728 insertions(+), 77 deletions(-)
+ create mode 100644 proxy/src/main/java/org/apache/rocketmq/proxy/processor/BatchAckResult.java
+ create mode 100644 proxy/src/main/java/org/apache/rocketmq/proxy/service/message/ReceiptHandleMessage.java
+
+diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/mqclient/MQClientAPIExt.java b/client/src/main/java/org/apache/rocketmq/client/impl/mqclient/MQClientAPIExt.java
+index fb8f8d11f..d7c8ef8d9 100644
+--- a/client/src/main/java/org/apache/rocketmq/client/impl/mqclient/MQClientAPIExt.java
++++ b/client/src/main/java/org/apache/rocketmq/client/impl/mqclient/MQClientAPIExt.java
+@@ -306,6 +306,32 @@ public class MQClientAPIExt extends MQClientAPIImpl {
+ return future;
+ }
+
++ public CompletableFuture<AckResult> batchAckMessageAsync(
++ String brokerAddr,
++ String topic,
++ String consumerGroup,
++ List<String> extraInfoList,
++ long timeoutMillis
++ ) {
++ CompletableFuture<AckResult> future = new CompletableFuture<>();
++ try {
++ this.batchAckMessageAsync(brokerAddr, timeoutMillis, new AckCallback() {
++ @Override
++ public void onSuccess(AckResult ackResult) {
++ future.complete(ackResult);
++ }
++
++ @Override
++ public void onException(Throwable t) {
++ future.completeExceptionally(t);
++ }
++ }, topic, consumerGroup, extraInfoList);
++ } catch (Throwable t) {
++ future.completeExceptionally(t);
++ }
++ return future;
++ }
++
+ public CompletableFuture<AckResult> changeInvisibleTimeAsync(
+ String brokerAddr,
+ String brokerName,
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java b/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java
+index 39caaa0d9..76a243919 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java
+@@ -250,6 +250,8 @@ public class ProxyConfig implements ConfigFile {
+ private long remotingWaitTimeMillsInTopicRouteQueue = 3 * 1000;
+ private long remotingWaitTimeMillsInDefaultQueue = 3 * 1000;
+
++ private boolean enableBatchAck = false;
++
+ @Override
+ public void initData() {
+ parseDelayLevel();
+@@ -1379,4 +1381,12 @@ public class ProxyConfig implements ConfigFile {
+ public void setRemotingWaitTimeMillsInDefaultQueue(long remotingWaitTimeMillsInDefaultQueue) {
+ this.remotingWaitTimeMillsInDefaultQueue = remotingWaitTimeMillsInDefaultQueue;
+ }
++
++ public boolean isEnableBatchAck() {
++ return enableBatchAck;
++ }
++
++ public void setEnableBatchAck(boolean enableBatchAck) {
++ this.enableBatchAck = enableBatchAck;
++ }
+ }
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/AckMessageActivity.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/AckMessageActivity.java
+index 9a3a77201..97c716c8f 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/AckMessageActivity.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/consumer/AckMessageActivity.java
+@@ -31,12 +31,15 @@ import org.apache.rocketmq.client.consumer.AckStatus;
+ import org.apache.rocketmq.common.consumer.ReceiptHandle;
+ import org.apache.rocketmq.proxy.common.MessageReceiptHandle;
+ import org.apache.rocketmq.proxy.common.ProxyContext;
++import org.apache.rocketmq.proxy.config.ConfigurationManager;
+ import org.apache.rocketmq.proxy.grpc.v2.AbstractMessingActivity;
+ import org.apache.rocketmq.proxy.grpc.v2.channel.GrpcChannelManager;
+ import org.apache.rocketmq.proxy.grpc.v2.common.GrpcClientSettingsManager;
+ import org.apache.rocketmq.proxy.grpc.v2.common.GrpcConverter;
+ import org.apache.rocketmq.proxy.grpc.v2.common.ResponseBuilder;
++import org.apache.rocketmq.proxy.processor.BatchAckResult;
+ import org.apache.rocketmq.proxy.processor.MessagingProcessor;
++import org.apache.rocketmq.proxy.service.message.ReceiptHandleMessage;
+
+ public class AckMessageActivity extends AbstractMessingActivity {
+
+@@ -50,60 +53,98 @@ public class AckMessageActivity extends AbstractMessingActivity {
+
+ try {
+ validateTopicAndConsumerGroup(request.getTopic(), request.getGroup());
+-
+- CompletableFuture<AckMessageResultEntry>[] futures = new CompletableFuture[request.getEntriesCount()];
+- for (int i = 0; i < request.getEntriesCount(); i++) {
+- futures[i] = processAckMessage(ctx, request, request.getEntries(i));
++ String group = GrpcConverter.getInstance().wrapResourceWithNamespace(request.getGroup());
++ String topic = GrpcConverter.getInstance().wrapResourceWithNamespace(request.getTopic());
++ if (ConfigurationManager.getProxyConfig().isEnableBatchAck()) {
++ future = ackMessageInBatch(ctx, group, topic, request);
++ } else {
++ future = ackMessageOneByOne(ctx, group, topic, request);
+ }
+- CompletableFuture.allOf(futures).whenComplete((val, throwable) -> {
+- if (throwable != null) {
+- future.completeExceptionally(throwable);
+- return;
+- }
++ } catch (Throwable t) {
++ future.completeExceptionally(t);
++ }
++ return future;
++ }
++
++ protected CompletableFuture<AckMessageResponse> ackMessageInBatch(ProxyContext ctx, String group, String topic, AckMessageRequest request) {
++ List<ReceiptHandleMessage> handleMessageList = new ArrayList<>(request.getEntriesCount());
+
++ for (AckMessageEntry ackMessageEntry : request.getEntriesList()) {
++ String handleString = getHandleString(ctx, group, request, ackMessageEntry);
++ handleMessageList.add(new ReceiptHandleMessage(ReceiptHandle.decode(handleString), ackMessageEntry.getMessageId()));
++ }
++ return this.messagingProcessor.batchAckMessage(ctx, handleMessageList, group, topic)
++ .thenApply(batchAckResultList -> {
++ AckMessageResponse.Builder responseBuilder = AckMessageResponse.newBuilder();
+ Set<Code> responseCodes = new HashSet<>();
+- List<AckMessageResultEntry> entryList = new ArrayList<>();
+- for (CompletableFuture<AckMessageResultEntry> entryFuture : futures) {
+- AckMessageResultEntry entryResult = entryFuture.join();
+- responseCodes.add(entryResult.getStatus().getCode());
+- entryList.add(entryResult);
+- }
+- AckMessageResponse.Builder responseBuilder = AckMessageResponse.newBuilder()
+- .addAllEntries(entryList);
+- if (responseCodes.size() > 1) {
+- responseBuilder.setStatus(ResponseBuilder.getInstance().buildStatus(Code.MULTIPLE_RESULTS, Code.MULTIPLE_RESULTS.name()));
+- } else if (responseCodes.size() == 1) {
+- Code code = responseCodes.stream().findAny().get();
+- responseBuilder.setStatus(ResponseBuilder.getInstance().buildStatus(code, code.name()));
+- } else {
+- responseBuilder.setStatus(ResponseBuilder.getInstance().buildStatus(Code.INTERNAL_SERVER_ERROR, "ack message result is empty"));
++ for (BatchAckResult batchAckResult : batchAckResultList) {
++ AckMessageResultEntry entry = convertToAckMessageResultEntry(batchAckResult);
++ responseBuilder.addEntries(entry);
++ responseCodes.add(entry.getStatus().getCode());
+ }
+- future.complete(responseBuilder.build());
++ setAckResponseStatus(responseBuilder, responseCodes);
++ return responseBuilder.build();
+ });
+- } catch (Throwable t) {
+- future.completeExceptionally(t);
++ }
++
++ protected AckMessageResultEntry convertToAckMessageResultEntry(BatchAckResult batchAckResult) {
++ ReceiptHandleMessage handleMessage = batchAckResult.getReceiptHandleMessage();
++ AckMessageResultEntry.Builder resultBuilder = AckMessageResultEntry.newBuilder()
++ .setMessageId(handleMessage.getMessageId())
++ .setReceiptHandle(handleMessage.getReceiptHandle().getReceiptHandle());
++ if (batchAckResult.getProxyException() != null) {
++ resultBuilder.setStatus(ResponseBuilder.getInstance().buildStatus(batchAckResult.getProxyException()));
++ } else {
++ AckResult ackResult = batchAckResult.getAckResult();
++ if (AckStatus.OK.equals(ackResult.getStatus())) {
++ resultBuilder.setStatus(ResponseBuilder.getInstance().buildStatus(Code.OK, Code.OK.name()));
++ } else {
++ resultBuilder.setStatus(ResponseBuilder.getInstance().buildStatus(Code.INTERNAL_SERVER_ERROR, "ack failed: status is abnormal"));
++ }
+ }
+- return future;
++ return resultBuilder.build();
+ }
+
+- protected CompletableFuture<AckMessageResultEntry> processAckMessage(ProxyContext ctx, AckMessageRequest request,
++ protected CompletableFuture<AckMessageResponse> ackMessageOneByOne(ProxyContext ctx, String group, String topic, AckMessageRequest request) {
++ CompletableFuture<AckMessageResponse> resultFuture = new CompletableFuture<>();
++ CompletableFuture<AckMessageResultEntry>[] futures = new CompletableFuture[request.getEntriesCount()];
++ for (int i = 0; i < request.getEntriesCount(); i++) {
++ futures[i] = processAckMessage(ctx, group, topic, request, request.getEntries(i));
++ }
++ CompletableFuture.allOf(futures).whenComplete((val, throwable) -> {
++ if (throwable != null) {
++ resultFuture.completeExceptionally(throwable);
++ return;
++ }
++
++ Set<Code> responseCodes = new HashSet<>();
++ List<AckMessageResultEntry> entryList = new ArrayList<>();
++ for (CompletableFuture<AckMessageResultEntry> entryFuture : futures) {
++ AckMessageResultEntry entryResult = entryFuture.join();
++ responseCodes.add(entryResult.getStatus().getCode());
++ entryList.add(entryResult);
++ }
++ AckMessageResponse.Builder responseBuilder = AckMessageResponse.newBuilder()
++ .addAllEntries(entryList);
++ setAckResponseStatus(responseBuilder, responseCodes);
++ resultFuture.complete(responseBuilder.build());
++ });
++ return resultFuture;
++ }
++
++ protected CompletableFuture<AckMessageResultEntry> processAckMessage(ProxyContext ctx, String group, String topic, AckMessageRequest request,
+ AckMessageEntry ackMessageEntry) {
+ CompletableFuture<AckMessageResultEntry> future = new CompletableFuture<>();
+
+ try {
+- String handleString = ackMessageEntry.getReceiptHandle();
+-
+- String group = GrpcConverter.getInstance().wrapResourceWithNamespace(request.getGroup());
+- MessageReceiptHandle messageReceiptHandle = messagingProcessor.removeReceiptHandle(ctx, grpcChannelManager.getChannel(ctx.getClientID()), group, ackMessageEntry.getMessageId(), ackMessageEntry.getReceiptHandle());
+- if (messageReceiptHandle != null) {
+- handleString = messageReceiptHandle.getReceiptHandleStr();
+- }
++ String handleString = this.getHandleString(ctx, group, request, ackMessageEntry);
+ CompletableFuture<AckResult> ackResultFuture = this.messagingProcessor.ackMessage(
+ ctx,
+ ReceiptHandle.decode(handleString),
+ ackMessageEntry.getMessageId(),
+ group,
+- GrpcConverter.getInstance().wrapResourceWithNamespace(request.getTopic()));
++ topic
++ );
+ ackResultFuture.thenAccept(result -> {
+ future.complete(convertToAckMessageResultEntry(ctx, ackMessageEntry, result));
+ }).exceptionally(t -> {
+@@ -139,4 +180,25 @@ public class AckMessageActivity extends AbstractMessingActivity {
+ .setStatus(ResponseBuilder.getInstance().buildStatus(Code.INTERNAL_SERVER_ERROR, "ack failed: status is abnormal"))
+ .build();
+ }
++
++ protected void setAckResponseStatus(AckMessageResponse.Builder responseBuilder, Set<Code> responseCodes) {
++ if (responseCodes.size() > 1) {
++ responseBuilder.setStatus(ResponseBuilder.getInstance().buildStatus(Code.MULTIPLE_RESULTS, Code.MULTIPLE_RESULTS.name()));
++ } else if (responseCodes.size() == 1) {
++ Code code = responseCodes.stream().findAny().get();
++ responseBuilder.setStatus(ResponseBuilder.getInstance().buildStatus(code, code.name()));
++ } else {
++ responseBuilder.setStatus(ResponseBuilder.getInstance().buildStatus(Code.INTERNAL_SERVER_ERROR, "ack message result is empty"));
++ }
++ }
++
++ protected String getHandleString(ProxyContext ctx, String group, AckMessageRequest request, AckMessageEntry ackMessageEntry) {
++ String handleString = ackMessageEntry.getReceiptHandle();
++
++ MessageReceiptHandle messageReceiptHandle = messagingProcessor.removeReceiptHandle(ctx, grpcChannelManager.getChannel(ctx.getClientID()), group, ackMessageEntry.getMessageId(), ackMessageEntry.getReceiptHandle());
++ if (messageReceiptHandle != null) {
++ handleString = messageReceiptHandle.getReceiptHandleStr();
++ }
++ return handleString;
++ }
+ }
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/AbstractProcessor.java b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/AbstractProcessor.java
+index b61c3df9e..c63212c23 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/AbstractProcessor.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/AbstractProcessor.java
+@@ -27,6 +27,8 @@ public abstract class AbstractProcessor extends AbstractStartAndShutdown {
+ protected MessagingProcessor messagingProcessor;
+ protected ServiceManager serviceManager;
+
++ protected static final ProxyException EXPIRED_HANDLE_PROXY_EXCEPTION = new ProxyException(ProxyExceptionCode.INVALID_RECEIPT_HANDLE, "receipt handle is expired");
++
+ public AbstractProcessor(MessagingProcessor messagingProcessor,
+ ServiceManager serviceManager) {
+ this.messagingProcessor = messagingProcessor;
+@@ -35,7 +37,7 @@ public abstract class AbstractProcessor extends AbstractStartAndShutdown {
+
+ protected void validateReceiptHandle(ReceiptHandle handle) {
+ if (handle.isExpired()) {
+- throw new ProxyException(ProxyExceptionCode.INVALID_RECEIPT_HANDLE, "receipt handle is expired");
++ throw EXPIRED_HANDLE_PROXY_EXCEPTION;
+ }
+ }
+ }
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/BatchAckResult.java b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/BatchAckResult.java
+new file mode 100644
+index 000000000..dfb9c9b9e
+--- /dev/null
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/BatchAckResult.java
+@@ -0,0 +1,53 @@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one or more
++ * contributor license agreements. See the NOTICE file distributed with
++ * this work for additional information regarding copyright ownership.
++ * The ASF licenses this file to You under the Apache License, Version 2.0
++ * (the "License"); you may not use this file except in compliance with
++ * the License. You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package org.apache.rocketmq.proxy.processor;
++
++import org.apache.rocketmq.client.consumer.AckResult;
++import org.apache.rocketmq.proxy.common.ProxyException;
++import org.apache.rocketmq.proxy.service.message.ReceiptHandleMessage;
++
++public class BatchAckResult {
++
++ private final ReceiptHandleMessage receiptHandleMessage;
++ private AckResult ackResult;
++ private ProxyException proxyException;
++
++ public BatchAckResult(ReceiptHandleMessage receiptHandleMessage,
++ AckResult ackResult) {
++ this.receiptHandleMessage = receiptHandleMessage;
++ this.ackResult = ackResult;
++ }
++
++ public BatchAckResult(ReceiptHandleMessage receiptHandleMessage,
++ ProxyException proxyException) {
++ this.receiptHandleMessage = receiptHandleMessage;
++ this.proxyException = proxyException;
++ }
++
++ public ReceiptHandleMessage getReceiptHandleMessage() {
++ return receiptHandleMessage;
++ }
++
++ public AckResult getAckResult() {
++ return ackResult;
++ }
++
++ public ProxyException getProxyException() {
++ return proxyException;
++ }
++}
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/ConsumerProcessor.java b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/ConsumerProcessor.java
+index 656a6339d..f3522b374 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/ConsumerProcessor.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/ConsumerProcessor.java
+@@ -48,6 +48,7 @@ import org.apache.rocketmq.proxy.common.ProxyExceptionCode;
+ import org.apache.rocketmq.proxy.common.utils.FutureUtils;
+ import org.apache.rocketmq.proxy.common.utils.ProxyUtils;
+ import org.apache.rocketmq.proxy.service.ServiceManager;
++import org.apache.rocketmq.proxy.service.message.ReceiptHandleMessage;
+ import org.apache.rocketmq.proxy.service.route.AddressableMessageQueue;
+ import org.apache.rocketmq.remoting.protocol.body.LockBatchRequestBody;
+ import org.apache.rocketmq.remoting.protocol.body.UnlockBatchRequestBody;
+@@ -241,6 +242,69 @@ public class ConsumerProcessor extends AbstractProcessor {
+ return FutureUtils.addExecutor(future, this.executor);
+ }
+
++ public CompletableFuture<List<BatchAckResult>> batchAckMessage(
++ ProxyContext ctx,
++ List<ReceiptHandleMessage> handleMessageList,
++ String consumerGroup,
++ String topic,
++ long timeoutMillis
++ ) {
++ CompletableFuture<List<BatchAckResult>> future = new CompletableFuture<>();
++ try {
++ List<BatchAckResult> batchAckResultList = new ArrayList<>(handleMessageList.size());
++ Map<String, List<ReceiptHandleMessage>> brokerHandleListMap = new HashMap<>();
++
++ for (ReceiptHandleMessage handleMessage : handleMessageList) {
++ if (handleMessage.getReceiptHandle().isExpired()) {
++ batchAckResultList.add(new BatchAckResult(handleMessage, EXPIRED_HANDLE_PROXY_EXCEPTION));
++ continue;
++ }
++ List<ReceiptHandleMessage> brokerHandleList = brokerHandleListMap.computeIfAbsent(handleMessage.getReceiptHandle().getBrokerName(), key -> new ArrayList<>());
++ brokerHandleList.add(handleMessage);
++ }
++
++ if (brokerHandleListMap.isEmpty()) {
++ return FutureUtils.addExecutor(CompletableFuture.completedFuture(batchAckResultList), this.executor);
++ }
++ Set<Map.Entry<String, List<ReceiptHandleMessage>>> brokerHandleListMapEntrySet = brokerHandleListMap.entrySet();
++ CompletableFuture<List<BatchAckResult>>[] futures = new CompletableFuture[brokerHandleListMapEntrySet.size()];
++ int futureIndex = 0;
++ for (Map.Entry<String, List<ReceiptHandleMessage>> entry : brokerHandleListMapEntrySet) {
++ futures[futureIndex++] = processBrokerHandle(ctx, consumerGroup, topic, entry.getValue(), timeoutMillis);
++ }
++ CompletableFuture.allOf(futures).whenComplete((val, throwable) -> {
++ if (throwable != null) {
++ future.completeExceptionally(throwable);
++ }
++ for (CompletableFuture<List<BatchAckResult>> resultFuture : futures) {
++ batchAckResultList.addAll(resultFuture.join());
++ }
++ future.complete(batchAckResultList);
++ });
++ } catch (Throwable t) {
++ future.completeExceptionally(t);
++ }
++ return FutureUtils.addExecutor(future, this.executor);
++ }
++
++ protected CompletableFuture<List<BatchAckResult>> processBrokerHandle(ProxyContext ctx, String consumerGroup, String topic, List<ReceiptHandleMessage> handleMessageList, long timeoutMillis) {
++ return this.serviceManager.getMessageService().batchAckMessage(ctx, handleMessageList, consumerGroup, topic, timeoutMillis)
++ .thenApply(result -> {
++ List<BatchAckResult> results = new ArrayList<>();
++ for (ReceiptHandleMessage handleMessage : handleMessageList) {
++ results.add(new BatchAckResult(handleMessage, result));
++ }
++ return results;
++ })
++ .exceptionally(throwable -> {
++ List<BatchAckResult> results = new ArrayList<>();
++ for (ReceiptHandleMessage handleMessage : handleMessageList) {
++ results.add(new BatchAckResult(handleMessage, new ProxyException(ProxyExceptionCode.INTERNAL_SERVER_ERROR, throwable.getMessage(), throwable)));
++ }
++ return results;
++ });
++ }
++
+ public CompletableFuture<AckResult> changeInvisibleTime(ProxyContext ctx, ReceiptHandle handle,
+ String messageId, String groupName, String topicName, long invisibleTime, long timeoutMillis) {
+ CompletableFuture<AckResult> future = new CompletableFuture<>();
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/DefaultMessagingProcessor.java b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/DefaultMessagingProcessor.java
+index 188cb7b9b..ba150051b 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/DefaultMessagingProcessor.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/DefaultMessagingProcessor.java
+@@ -46,6 +46,7 @@ import org.apache.rocketmq.proxy.config.ConfigurationManager;
+ import org.apache.rocketmq.proxy.config.ProxyConfig;
+ import org.apache.rocketmq.proxy.service.ServiceManager;
+ import org.apache.rocketmq.proxy.service.ServiceManagerFactory;
++import org.apache.rocketmq.proxy.service.message.ReceiptHandleMessage;
+ import org.apache.rocketmq.proxy.service.metadata.MetadataService;
+ import org.apache.rocketmq.proxy.service.relay.ProxyRelayService;
+ import org.apache.rocketmq.proxy.service.route.ProxyTopicRouteData;
+@@ -183,6 +184,12 @@ public class DefaultMessagingProcessor extends AbstractStartAndShutdown implemen
+ return this.consumerProcessor.ackMessage(ctx, handle, messageId, consumerGroup, topic, timeoutMillis);
+ }
+
++ @Override
++ public CompletableFuture<List<BatchAckResult>> batchAckMessage(ProxyContext ctx,
++ List<ReceiptHandleMessage> handleMessageList, String consumerGroup, String topic, long timeoutMillis) {
++ return this.consumerProcessor.batchAckMessage(ctx, handleMessageList, consumerGroup, topic, timeoutMillis);
++ }
++
+ @Override
+ public CompletableFuture<AckResult> changeInvisibleTime(ProxyContext ctx, ReceiptHandle handle, String messageId,
+ String groupName, String topicName, long invisibleTime, long timeoutMillis) {
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/MessagingProcessor.java b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/MessagingProcessor.java
+index d86be0bd8..2ae7418ba 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/MessagingProcessor.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/MessagingProcessor.java
+@@ -37,6 +37,7 @@ import org.apache.rocketmq.proxy.common.Address;
+ import org.apache.rocketmq.proxy.common.MessageReceiptHandle;
+ import org.apache.rocketmq.proxy.common.ProxyContext;
+ import org.apache.rocketmq.common.utils.StartAndShutdown;
++import org.apache.rocketmq.proxy.service.message.ReceiptHandleMessage;
+ import org.apache.rocketmq.proxy.service.metadata.MetadataService;
+ import org.apache.rocketmq.proxy.service.relay.ProxyRelayService;
+ import org.apache.rocketmq.proxy.service.route.ProxyTopicRouteData;
+@@ -155,6 +156,23 @@ public interface MessagingProcessor extends StartAndShutdown {
+ long timeoutMillis
+ );
+
++ default CompletableFuture<List<BatchAckResult>> batchAckMessage(
++ ProxyContext ctx,
++ List<ReceiptHandleMessage> handleMessageList,
++ String consumerGroup,
++ String topic
++ ) {
++ return batchAckMessage(ctx, handleMessageList, consumerGroup, topic, DEFAULT_TIMEOUT_MILLS);
++ }
++
++ CompletableFuture<List<BatchAckResult>> batchAckMessage(
++ ProxyContext ctx,
++ List<ReceiptHandleMessage> handleMessageList,
++ String consumerGroup,
++ String topic,
++ long timeoutMillis
++ );
++
+ default CompletableFuture<AckResult> changeInvisibleTime(
+ ProxyContext ctx,
+ ReceiptHandle handle,
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/ClusterMessageService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/ClusterMessageService.java
+index 9f163f1b9..70b72deae 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/ClusterMessageService.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/ClusterMessageService.java
+@@ -20,9 +20,11 @@ import com.google.common.collect.Lists;
+ import java.util.List;
+ import java.util.Set;
+ import java.util.concurrent.CompletableFuture;
++import java.util.stream.Collectors;
+ import org.apache.rocketmq.client.consumer.AckResult;
+ import org.apache.rocketmq.client.consumer.PopResult;
+ import org.apache.rocketmq.client.consumer.PullResult;
++import org.apache.rocketmq.client.impl.mqclient.MQClientAPIFactory;
+ import org.apache.rocketmq.client.producer.SendResult;
+ import org.apache.rocketmq.common.consumer.ReceiptHandle;
+ import org.apache.rocketmq.common.message.Message;
+@@ -31,7 +33,6 @@ import org.apache.rocketmq.proxy.common.ProxyContext;
+ import org.apache.rocketmq.proxy.common.ProxyException;
+ import org.apache.rocketmq.proxy.common.ProxyExceptionCode;
+ import org.apache.rocketmq.proxy.common.utils.FutureUtils;
+-import org.apache.rocketmq.client.impl.mqclient.MQClientAPIFactory;
+ import org.apache.rocketmq.proxy.service.route.AddressableMessageQueue;
+ import org.apache.rocketmq.proxy.service.route.TopicRouteService;
+ import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+@@ -137,6 +138,19 @@ public class ClusterMessageService implements MessageService {
+ );
+ }
+
++ @Override
++ public CompletableFuture<AckResult> batchAckMessage(ProxyContext ctx, List<ReceiptHandleMessage> handleList, String consumerGroup,
++ String topic, long timeoutMillis) {
++ List<String> extraInfoList = handleList.stream().map(message -> message.getReceiptHandle().getReceiptHandle()).collect(Collectors.toList());
++ return this.mqClientAPIFactory.getClient().batchAckMessageAsync(
++ this.resolveBrokerAddrInReceiptHandle(ctx, handleList.get(0).getReceiptHandle()),
++ topic,
++ consumerGroup,
++ extraInfoList,
++ timeoutMillis
++ );
++ }
++
+ @Override
+ public CompletableFuture<PullResult> pullMessage(ProxyContext ctx, AddressableMessageQueue messageQueue,
+ PullMessageRequestHeader requestHeader, long timeoutMillis) {
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/LocalMessageService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/LocalMessageService.java
+index eb2c4d9ee..ca7dcc9eb 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/LocalMessageService.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/LocalMessageService.java
+@@ -19,6 +19,7 @@ package org.apache.rocketmq.proxy.service.message;
+ import io.netty.channel.ChannelHandlerContext;
+ import java.nio.ByteBuffer;
+ import java.util.ArrayList;
++import java.util.BitSet;
+ import java.util.Collections;
+ import java.util.HashMap;
+ import java.util.List;
+@@ -54,6 +55,8 @@ import org.apache.rocketmq.remoting.RPCHook;
+ import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+ import org.apache.rocketmq.remoting.protocol.RequestCode;
+ import org.apache.rocketmq.remoting.protocol.ResponseCode;
++import org.apache.rocketmq.remoting.protocol.body.BatchAck;
++import org.apache.rocketmq.remoting.protocol.body.BatchAckMessageRequestBody;
+ import org.apache.rocketmq.remoting.protocol.body.LockBatchRequestBody;
+ import org.apache.rocketmq.remoting.protocol.body.UnlockBatchRequestBody;
+ import org.apache.rocketmq.remoting.protocol.header.AckMessageRequestHeader;
+@@ -364,6 +367,61 @@ public class LocalMessageService implements MessageService {
+ });
+ }
+
++ @Override
++ public CompletableFuture<AckResult> batchAckMessage(ProxyContext ctx, List<ReceiptHandleMessage> handleList,
++ String consumerGroup, String topic, long timeoutMillis) {
++ SimpleChannel channel = channelManager.createChannel(ctx);
++ ChannelHandlerContext channelHandlerContext = channel.getChannelHandlerContext();
++ RemotingCommand command = LocalRemotingCommand.createRequestCommand(RequestCode.BATCH_ACK_MESSAGE, null);
++
++ Map<String, BatchAck> batchAckMap = new HashMap<>();
++ for (ReceiptHandleMessage receiptHandleMessage : handleList) {
++ String extraInfo = receiptHandleMessage.getReceiptHandle().getReceiptHandle();
++ String[] extraInfoData = ExtraInfoUtil.split(extraInfo);
++ String mergeKey = ExtraInfoUtil.getRetry(extraInfoData) + "@" +
++ ExtraInfoUtil.getQueueId(extraInfoData) + "@" +
++ ExtraInfoUtil.getCkQueueOffset(extraInfoData) + "@" +
++ ExtraInfoUtil.getPopTime(extraInfoData);
++ BatchAck bAck = batchAckMap.computeIfAbsent(mergeKey, k -> {
++ BatchAck newBatchAck = new BatchAck();
++ newBatchAck.setConsumerGroup(consumerGroup);
++ newBatchAck.setTopic(topic);
++ newBatchAck.setRetry(ExtraInfoUtil.getRetry(extraInfoData));
++ newBatchAck.setStartOffset(ExtraInfoUtil.getCkQueueOffset(extraInfoData));
++ newBatchAck.setQueueId(ExtraInfoUtil.getQueueId(extraInfoData));
++ newBatchAck.setReviveQueueId(ExtraInfoUtil.getReviveQid(extraInfoData));
++ newBatchAck.setPopTime(ExtraInfoUtil.getPopTime(extraInfoData));
++ newBatchAck.setInvisibleTime(ExtraInfoUtil.getInvisibleTime(extraInfoData));
++ newBatchAck.setBitSet(new BitSet());
++ return newBatchAck;
++ });
++ bAck.getBitSet().set((int) (ExtraInfoUtil.getQueueOffset(extraInfoData) - ExtraInfoUtil.getCkQueueOffset(extraInfoData)));
++ }
++ BatchAckMessageRequestBody requestBody = new BatchAckMessageRequestBody();
++ requestBody.setBrokerName(brokerController.getBrokerConfig().getBrokerName());
++ requestBody.setAcks(new ArrayList<>(batchAckMap.values()));
++
++ command.setBody(requestBody.encode());
++ CompletableFuture<RemotingCommand> future = new CompletableFuture<>();
++ try {
++ RemotingCommand response = brokerController.getAckMessageProcessor()
++ .processRequest(channelHandlerContext, command);
++ future.complete(response);
++ } catch (Exception e) {
++ log.error("Fail to process batchAckMessage command", e);
++ future.completeExceptionally(e);
++ }
++ return future.thenApply(r -> {
++ AckResult ackResult = new AckResult();
++ if (ResponseCode.SUCCESS == r.getCode()) {
++ ackResult.setStatus(AckStatus.OK);
++ } else {
++ ackResult.setStatus(AckStatus.NO_EXIST);
++ }
++ return ackResult;
++ });
++ }
++
+ @Override
+ public CompletableFuture<PullResult> pullMessage(ProxyContext ctx, AddressableMessageQueue messageQueue,
+ PullMessageRequestHeader requestHeader, long timeoutMillis) {
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/MessageService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/MessageService.java
+index 15da17154..58a835adb 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/MessageService.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/MessageService.java
+@@ -91,6 +91,14 @@ public interface MessageService {
+ long timeoutMillis
+ );
+
++ CompletableFuture<AckResult> batchAckMessage(
++ ProxyContext ctx,
++ List<ReceiptHandleMessage> handleList,
++ String consumerGroup,
++ String topic,
++ long timeoutMillis
++ );
++
+ CompletableFuture<PullResult> pullMessage(
+ ProxyContext ctx,
+ AddressableMessageQueue messageQueue,
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/ReceiptHandleMessage.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/ReceiptHandleMessage.java
+new file mode 100644
+index 000000000..ae63fed49
+--- /dev/null
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/message/ReceiptHandleMessage.java
+@@ -0,0 +1,39 @@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one or more
++ * contributor license agreements. See the NOTICE file distributed with
++ * this work for additional information regarding copyright ownership.
++ * The ASF licenses this file to You under the Apache License, Version 2.0
++ * (the "License"); you may not use this file except in compliance with
++ * the License. You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package org.apache.rocketmq.proxy.service.message;
++
++import org.apache.rocketmq.common.consumer.ReceiptHandle;
++
++public class ReceiptHandleMessage {
++
++ private final ReceiptHandle receiptHandle;
++ private final String messageId;
++
++ public ReceiptHandleMessage(ReceiptHandle receiptHandle, String messageId) {
++ this.receiptHandle = receiptHandle;
++ this.messageId = messageId;
++ }
++
++ public ReceiptHandle getReceiptHandle() {
++ return receiptHandle;
++ }
++
++ public String getMessageId() {
++ return messageId;
++ }
++}
+diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/consumer/AckMessageActivityTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/consumer/AckMessageActivityTest.java
+index 49fdfc6a8..3c4746105 100644
+--- a/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/consumer/AckMessageActivityTest.java
++++ b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/consumer/AckMessageActivityTest.java
+@@ -20,21 +20,32 @@ package org.apache.rocketmq.proxy.grpc.v2.consumer;
+ import apache.rocketmq.v2.AckMessageEntry;
+ import apache.rocketmq.v2.AckMessageRequest;
+ import apache.rocketmq.v2.AckMessageResponse;
++import apache.rocketmq.v2.AckMessageResultEntry;
+ import apache.rocketmq.v2.Code;
+ import apache.rocketmq.v2.Resource;
++import java.util.ArrayList;
++import java.util.HashMap;
++import java.util.List;
++import java.util.Map;
+ import java.util.concurrent.CompletableFuture;
+ import org.apache.rocketmq.client.consumer.AckResult;
+ import org.apache.rocketmq.client.consumer.AckStatus;
+ import org.apache.rocketmq.proxy.common.ProxyException;
+ import org.apache.rocketmq.proxy.common.ProxyExceptionCode;
++import org.apache.rocketmq.proxy.config.ConfigurationManager;
+ import org.apache.rocketmq.proxy.grpc.v2.BaseActivityTest;
++import org.apache.rocketmq.proxy.processor.BatchAckResult;
++import org.apache.rocketmq.proxy.service.message.ReceiptHandleMessage;
+ import org.junit.Before;
+ import org.junit.Test;
++import org.mockito.stubbing.Answer;
+
+ import static org.junit.Assert.assertEquals;
+ import static org.mockito.ArgumentMatchers.any;
++import static org.mockito.ArgumentMatchers.anyList;
+ import static org.mockito.ArgumentMatchers.anyString;
+ import static org.mockito.ArgumentMatchers.eq;
++import static org.mockito.Mockito.doAnswer;
+ import static org.mockito.Mockito.when;
+
+ public class AckMessageActivityTest extends BaseActivityTest {
+@@ -52,43 +63,197 @@ public class AckMessageActivityTest extends BaseActivityTest {
+
+ @Test
+ public void testAckMessage() throws Throwable {
+- when(this.messagingProcessor.ackMessage(any(), any(), eq("msg1"), anyString(), anyString()))
++ ConfigurationManager.getProxyConfig().setEnableBatchAck(false);
++
++ String msg1 = "msg1";
++ String msg2 = "msg2";
++ String msg3 = "msg3";
++
++ when(this.messagingProcessor.ackMessage(any(), any(), eq(msg1), anyString(), anyString()))
+ .thenThrow(new ProxyException(ProxyExceptionCode.INVALID_RECEIPT_HANDLE, "receipt handle is expired"));
+
+ AckResult msg2AckResult = new AckResult();
+ msg2AckResult.setStatus(AckStatus.OK);
+- when(this.messagingProcessor.ackMessage(any(), any(), eq("msg2"), anyString(), anyString()))
++ when(this.messagingProcessor.ackMessage(any(), any(), eq(msg2), anyString(), anyString()))
+ .thenReturn(CompletableFuture.completedFuture(msg2AckResult));
+
+ AckResult msg3AckResult = new AckResult();
+ msg3AckResult.setStatus(AckStatus.NO_EXIST);
+- when(this.messagingProcessor.ackMessage(any(), any(), eq("msg3"), anyString(), anyString()))
++ when(this.messagingProcessor.ackMessage(any(), any(), eq(msg3), anyString(), anyString()))
+ .thenReturn(CompletableFuture.completedFuture(msg3AckResult));
+
+- AckMessageResponse response = this.ackMessageActivity.ackMessage(
+- createContext(),
+- AckMessageRequest.newBuilder()
+- .setTopic(Resource.newBuilder().setName(TOPIC).build())
+- .setGroup(Resource.newBuilder().setName(GROUP).build())
+- .addEntries(AckMessageEntry.newBuilder()
+- .setMessageId("msg1")
+- .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis() - 10000, 1000))
+- .build())
+- .addEntries(AckMessageEntry.newBuilder()
+- .setMessageId("msg2")
+- .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis(), 3000))
+- .build())
+- .addEntries(AckMessageEntry.newBuilder()
+- .setMessageId("msg3")
+- .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis(), 3000))
+- .build())
+- .build()
+- ).get();
+-
+- assertEquals(Code.MULTIPLE_RESULTS, response.getStatus().getCode());
+- assertEquals(3, response.getEntriesCount());
+- assertEquals(Code.INVALID_RECEIPT_HANDLE, response.getEntries(0).getStatus().getCode());
+- assertEquals(Code.OK, response.getEntries(1).getStatus().getCode());
+- assertEquals(Code.INTERNAL_SERVER_ERROR, response.getEntries(2).getStatus().getCode());
++ {
++ AckMessageResponse response = this.ackMessageActivity.ackMessage(
++ createContext(),
++ AckMessageRequest.newBuilder()
++ .setTopic(Resource.newBuilder().setName(TOPIC).build())
++ .setGroup(Resource.newBuilder().setName(GROUP).build())
++ .addEntries(AckMessageEntry.newBuilder()
++ .setMessageId(msg1)
++ .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis() - 10000, 1000))
++ .build())
++ .build()
++ ).get();
++ assertEquals(Code.INVALID_RECEIPT_HANDLE, response.getStatus().getCode());
++ }
++ {
++ AckMessageResponse response = this.ackMessageActivity.ackMessage(
++ createContext(),
++ AckMessageRequest.newBuilder()
++ .setTopic(Resource.newBuilder().setName(TOPIC).build())
++ .setGroup(Resource.newBuilder().setName(GROUP).build())
++ .addEntries(AckMessageEntry.newBuilder()
++ .setMessageId(msg2)
++ .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis() - 10000, 1000))
++ .build())
++ .build()
++ ).get();
++ assertEquals(Code.OK, response.getStatus().getCode());
++ }
++ {
++ AckMessageResponse response = this.ackMessageActivity.ackMessage(
++ createContext(),
++ AckMessageRequest.newBuilder()
++ .setTopic(Resource.newBuilder().setName(TOPIC).build())
++ .setGroup(Resource.newBuilder().setName(GROUP).build())
++ .addEntries(AckMessageEntry.newBuilder()
++ .setMessageId(msg3)
++ .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis() - 10000, 1000))
++ .build())
++ .build()
++ ).get();
++ assertEquals(Code.INTERNAL_SERVER_ERROR, response.getStatus().getCode());
++ }
++ {
++ AckMessageResponse response = this.ackMessageActivity.ackMessage(
++ createContext(),
++ AckMessageRequest.newBuilder()
++ .setTopic(Resource.newBuilder().setName(TOPIC).build())
++ .setGroup(Resource.newBuilder().setName(GROUP).build())
++ .addEntries(AckMessageEntry.newBuilder()
++ .setMessageId(msg1)
++ .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis() - 10000, 1000))
++ .build())
++ .addEntries(AckMessageEntry.newBuilder()
++ .setMessageId(msg2)
++ .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis(), 3000))
++ .build())
++ .addEntries(AckMessageEntry.newBuilder()
++ .setMessageId(msg3)
++ .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis(), 3000))
++ .build())
++ .build()
++ ).get();
++
++ assertEquals(Code.MULTIPLE_RESULTS, response.getStatus().getCode());
++ assertEquals(3, response.getEntriesCount());
++ assertEquals(Code.INVALID_RECEIPT_HANDLE, response.getEntries(0).getStatus().getCode());
++ assertEquals(Code.OK, response.getEntries(1).getStatus().getCode());
++ assertEquals(Code.INTERNAL_SERVER_ERROR, response.getEntries(2).getStatus().getCode());
++ }
++ }
++
++ @Test
++ public void testAckMessageInBatch() throws Throwable {
++ ConfigurationManager.getProxyConfig().setEnableBatchAck(true);
++
++ String successMessageId = "msg1";
++ String notOkMessageId = "msg2";
++ String exceptionMessageId = "msg3";
++
++ doAnswer((Answer<CompletableFuture<List<BatchAckResult>>>) invocation -> {
++ List<ReceiptHandleMessage> receiptHandleMessageList = invocation.getArgument(1, List.class);
++ List<BatchAckResult> batchAckResultList = new ArrayList<>();
++ for (ReceiptHandleMessage receiptHandleMessage : receiptHandleMessageList) {
++ BatchAckResult batchAckResult;
++ if (receiptHandleMessage.getMessageId().equals(successMessageId)) {
++ AckResult ackResult = new AckResult();
++ ackResult.setStatus(AckStatus.OK);
++ batchAckResult = new BatchAckResult(receiptHandleMessage, ackResult);
++ } else if (receiptHandleMessage.getMessageId().equals(notOkMessageId)) {
++ AckResult ackResult = new AckResult();
++ ackResult.setStatus(AckStatus.NO_EXIST);
++ batchAckResult = new BatchAckResult(receiptHandleMessage, ackResult);
++ } else {
++ batchAckResult = new BatchAckResult(receiptHandleMessage, new ProxyException(ProxyExceptionCode.INVALID_RECEIPT_HANDLE, ""));
++ }
++ batchAckResultList.add(batchAckResult);
++ }
++ return CompletableFuture.completedFuture(batchAckResultList);
++ }).when(this.messagingProcessor).batchAckMessage(any(), anyList(), anyString(), anyString());
++
++ {
++ AckMessageResponse response = this.ackMessageActivity.ackMessage(
++ createContext(),
++ AckMessageRequest.newBuilder()
++ .setTopic(Resource.newBuilder().setName(TOPIC).build())
++ .setGroup(Resource.newBuilder().setName(GROUP).build())
++ .addEntries(AckMessageEntry.newBuilder()
++ .setMessageId(successMessageId)
++ .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis(), 3000))
++ .build())
++ .build()
++ ).get();
++ assertEquals(Code.OK, response.getStatus().getCode());
++ }
++ {
++ AckMessageResponse response = this.ackMessageActivity.ackMessage(
++ createContext(),
++ AckMessageRequest.newBuilder()
++ .setTopic(Resource.newBuilder().setName(TOPIC).build())
++ .setGroup(Resource.newBuilder().setName(GROUP).build())
++ .addEntries(AckMessageEntry.newBuilder()
++ .setMessageId(notOkMessageId)
++ .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis(), 3000))
++ .build())
++ .build()
++ ).get();
++ assertEquals(Code.INTERNAL_SERVER_ERROR, response.getStatus().getCode());
++ }
++ {
++ AckMessageResponse response = this.ackMessageActivity.ackMessage(
++ createContext(),
++ AckMessageRequest.newBuilder()
++ .setTopic(Resource.newBuilder().setName(TOPIC).build())
++ .setGroup(Resource.newBuilder().setName(GROUP).build())
++ .addEntries(AckMessageEntry.newBuilder()
++ .setMessageId(exceptionMessageId)
++ .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis(), 3000))
++ .build())
++ .build()
++ ).get();
++ assertEquals(Code.INVALID_RECEIPT_HANDLE, response.getStatus().getCode());
++ }
++ {
++ AckMessageResponse response = this.ackMessageActivity.ackMessage(
++ createContext(),
++ AckMessageRequest.newBuilder()
++ .setTopic(Resource.newBuilder().setName(TOPIC).build())
++ .setGroup(Resource.newBuilder().setName(GROUP).build())
++ .addEntries(AckMessageEntry.newBuilder()
++ .setMessageId(successMessageId)
++ .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis(), 3000))
++ .build())
++ .addEntries(AckMessageEntry.newBuilder()
++ .setMessageId(notOkMessageId)
++ .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis(), 3000))
++ .build())
++ .addEntries(AckMessageEntry.newBuilder()
++ .setMessageId(exceptionMessageId)
++ .setReceiptHandle(buildReceiptHandle(TOPIC, System.currentTimeMillis(), 3000))
++ .build())
++ .build()
++ ).get();
++
++ assertEquals(Code.MULTIPLE_RESULTS, response.getStatus().getCode());
++ assertEquals(3, response.getEntriesCount());
++ Map<String, Code> msgCode = new HashMap<>();
++ for (AckMessageResultEntry entry : response.getEntriesList()) {
++ msgCode.put(entry.getMessageId(), entry.getStatus().getCode());
++ }
++ assertEquals(Code.OK, msgCode.get(successMessageId));
++ assertEquals(Code.INTERNAL_SERVER_ERROR, msgCode.get(notOkMessageId));
++ assertEquals(Code.INVALID_RECEIPT_HANDLE, msgCode.get(exceptionMessageId));
++ }
+ }
+ }
+\ No newline at end of file
+diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/processor/BaseProcessorTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/processor/BaseProcessorTest.java
+index 5c1ea9627..072630e39 100644
+--- a/proxy/src/test/java/org/apache/rocketmq/proxy/processor/BaseProcessorTest.java
++++ b/proxy/src/test/java/org/apache/rocketmq/proxy/processor/BaseProcessorTest.java
+@@ -66,14 +66,6 @@ public class BaseProcessorTest extends InitConfigTest {
+ protected ProxyRelayService proxyRelayService;
+ @Mock
+ protected MetadataService metadataService;
+- @Mock
+- protected ProducerProcessor producerProcessor;
+- @Mock
+- protected ConsumerProcessor consumerProcessor;
+- @Mock
+- protected TransactionProcessor transactionProcessor;
+- @Mock
+- protected ClientProcessor clientProcessor;
+
+ public void before() throws Throwable {
+ super.before();
+@@ -92,6 +84,13 @@ public class BaseProcessorTest extends InitConfigTest {
+ }
+
+ protected static MessageExt createMessageExt(String topic, String tags, int reconsumeTimes, long invisibleTime) {
++ return createMessageExt(topic, tags, reconsumeTimes, invisibleTime, System.currentTimeMillis(),
++ RANDOM.nextInt(Integer.MAX_VALUE), RANDOM.nextInt(Integer.MAX_VALUE), RANDOM.nextInt(Integer.MAX_VALUE),
++ RANDOM.nextInt(Integer.MAX_VALUE), "mockBroker");
++ }
++
++ protected static MessageExt createMessageExt(String topic, String tags, int reconsumeTimes, long invisibleTime, long popTime,
++ long startOffset, int reviveQid, int queueId, long queueOffset, String brokerName) {
+ MessageExt messageExt = new MessageExt();
+ messageExt.setTopic(topic);
+ messageExt.setTags(tags);
+@@ -100,8 +99,7 @@ public class BaseProcessorTest extends InitConfigTest {
+ messageExt.setMsgId(MessageClientIDSetter.createUniqID());
+ messageExt.setCommitLogOffset(RANDOM.nextInt(Integer.MAX_VALUE));
+ MessageAccessor.putProperty(messageExt, MessageConst.PROPERTY_POP_CK,
+- ExtraInfoUtil.buildExtraInfo(RANDOM.nextInt(Integer.MAX_VALUE), System.currentTimeMillis(), invisibleTime,
+- RANDOM.nextInt(Integer.MAX_VALUE), topic, "mockBroker", RANDOM.nextInt(Integer.MAX_VALUE), RANDOM.nextInt(Integer.MAX_VALUE)));
++ ExtraInfoUtil.buildExtraInfo(startOffset, popTime, invisibleTime, reviveQid, topic, brokerName, queueId, queueOffset));
+ return messageExt;
+ }
+
+diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/processor/ConsumerProcessorTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/processor/ConsumerProcessorTest.java
+index 717e86fc0..db268a06e 100644
+--- a/proxy/src/test/java/org/apache/rocketmq/proxy/processor/ConsumerProcessorTest.java
++++ b/proxy/src/test/java/org/apache/rocketmq/proxy/processor/ConsumerProcessorTest.java
+@@ -20,8 +20,11 @@ package org.apache.rocketmq.proxy.processor;
+ import com.google.common.collect.Sets;
+ import java.time.Duration;
+ import java.util.ArrayList;
++import java.util.Collections;
++import java.util.HashMap;
+ import java.util.HashSet;
+ import java.util.List;
++import java.util.Map;
+ import java.util.Set;
+ import java.util.concurrent.CompletableFuture;
+ import java.util.concurrent.Executors;
+@@ -39,7 +42,10 @@ import org.apache.rocketmq.common.message.MessageClientIDSetter;
+ import org.apache.rocketmq.common.message.MessageExt;
+ import org.apache.rocketmq.common.message.MessageQueue;
+ import org.apache.rocketmq.proxy.common.ProxyContext;
++import org.apache.rocketmq.proxy.common.ProxyExceptionCode;
++import org.apache.rocketmq.proxy.common.utils.FutureUtils;
+ import org.apache.rocketmq.proxy.common.utils.ProxyUtils;
++import org.apache.rocketmq.proxy.service.message.ReceiptHandleMessage;
+ import org.apache.rocketmq.proxy.service.route.AddressableMessageQueue;
+ import org.apache.rocketmq.proxy.service.route.MessageQueueView;
+ import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+@@ -50,16 +56,22 @@ import org.apache.rocketmq.remoting.protocol.header.PopMessageRequestHeader;
+ import org.junit.Before;
+ import org.junit.Test;
+ import org.mockito.ArgumentCaptor;
++import org.mockito.stubbing.Answer;
+
+ import static org.assertj.core.api.Assertions.assertThat;
+ import static org.junit.Assert.assertEquals;
+ import static org.junit.Assert.assertNotNull;
++import static org.junit.Assert.assertNull;
+ import static org.junit.Assert.assertSame;
+ import static org.mockito.ArgumentMatchers.any;
++import static org.mockito.ArgumentMatchers.anyList;
+ import static org.mockito.ArgumentMatchers.anyLong;
+ import static org.mockito.ArgumentMatchers.anyString;
+ import static org.mockito.ArgumentMatchers.eq;
++import static org.mockito.Mockito.doAnswer;
+ import static org.mockito.Mockito.mock;
++import static org.mockito.Mockito.never;
++import static org.mockito.Mockito.verify;
+ import static org.mockito.Mockito.when;
+
+ public class ConsumerProcessorTest extends BaseProcessorTest {
+@@ -162,6 +174,109 @@ public class ConsumerProcessorTest extends BaseProcessorTest {
+ assertEquals(handle.getReceiptHandle(), requestHeaderArgumentCaptor.getValue().getExtraInfo());
+ }
+
++ @Test
++ public void testBatchAckExpireMessage() throws Throwable {
++ String brokerName1 = "brokerName1";
++
++ List<ReceiptHandleMessage> receiptHandleMessageList = new ArrayList<>();
++ for (int i = 0; i < 3; i++) {
++ MessageExt expireMessage = createMessageExt(TOPIC, "", 0, 3000, System.currentTimeMillis() - 10000,
++ 0, 0, 0, i, brokerName1);
++ ReceiptHandle expireHandle = create(expireMessage);
++ receiptHandleMessageList.add(new ReceiptHandleMessage(expireHandle, expireMessage.getMsgId()));
++ }
++
++ List<BatchAckResult> batchAckResultList = this.consumerProcessor.batchAckMessage(createContext(), receiptHandleMessageList, CONSUMER_GROUP, TOPIC, 3000).get();
++
++ verify(this.messageService, never()).batchAckMessage(any(), anyList(), anyString(), anyString(), anyLong());
++ assertEquals(receiptHandleMessageList.size(), batchAckResultList.size());
++ for (BatchAckResult batchAckResult : batchAckResultList) {
++ assertNull(batchAckResult.getAckResult());
++ assertNotNull(batchAckResult.getProxyException());
++ assertNotNull(batchAckResult.getReceiptHandleMessage());
++ }
++
++ }
++
++ @Test
++ public void testBatchAckMessage() throws Throwable {
++ String brokerName1 = "brokerName1";
++ String brokerName2 = "brokerName2";
++ String errThrowBrokerName = "errThrowBrokerName";
++ MessageExt expireMessage = createMessageExt(TOPIC, "", 0, 3000, System.currentTimeMillis() - 10000,
++ 0, 0, 0, 0, brokerName1);
++ ReceiptHandle expireHandle = create(expireMessage);
++
++ List<ReceiptHandleMessage> receiptHandleMessageList = new ArrayList<>();
++ receiptHandleMessageList.add(new ReceiptHandleMessage(expireHandle, expireMessage.getMsgId()));
++ List<String> broker1Msg = new ArrayList<>();
++ List<String> broker2Msg = new ArrayList<>();
++
++ long now = System.currentTimeMillis();
++ int msgNum = 3;
++ for (int i = 0; i < msgNum; i++) {
++ MessageExt brokerMessage = createMessageExt(TOPIC, "", 0, 3000, now,
++ 0, 0, 0, i + 1, brokerName1);
++ ReceiptHandle brokerHandle = create(brokerMessage);
++ receiptHandleMessageList.add(new ReceiptHandleMessage(brokerHandle, brokerMessage.getMsgId()));
++ broker1Msg.add(brokerMessage.getMsgId());
++ }
++ for (int i = 0; i < msgNum; i++) {
++ MessageExt brokerMessage = createMessageExt(TOPIC, "", 0, 3000, now,
++ 0, 0, 0, i + 1, brokerName2);
++ ReceiptHandle brokerHandle = create(brokerMessage);
++ receiptHandleMessageList.add(new ReceiptHandleMessage(brokerHandle, brokerMessage.getMsgId()));
++ broker2Msg.add(brokerMessage.getMsgId());
++ }
++
++ // for this message, will throw exception in batchAckMessage
++ MessageExt errThrowMessage = createMessageExt(TOPIC, "", 0, 3000, now,
++ 0, 0, 0, 0, errThrowBrokerName);
++ ReceiptHandle errThrowHandle = create(errThrowMessage);
++ receiptHandleMessageList.add(new ReceiptHandleMessage(errThrowHandle, errThrowMessage.getMsgId()));
++
++ Collections.shuffle(receiptHandleMessageList);
++
++ doAnswer((Answer<CompletableFuture<AckResult>>) invocation -> {
++ List<ReceiptHandleMessage> handleMessageList = invocation.getArgument(1, List.class);
++ AckResult ackResult = new AckResult();
++ String brokerName = handleMessageList.get(0).getReceiptHandle().getBrokerName();
++ if (brokerName.equals(brokerName1)) {
++ ackResult.setStatus(AckStatus.OK);
++ } else if (brokerName.equals(brokerName2)) {
++ ackResult.setStatus(AckStatus.NO_EXIST);
++ } else {
++ return FutureUtils.completeExceptionally(new RuntimeException());
++ }
++
++ return CompletableFuture.completedFuture(ackResult);
++ }).when(this.messageService).batchAckMessage(any(), anyList(), anyString(), anyString(), anyLong());
++
++ List<BatchAckResult> batchAckResultList = this.consumerProcessor.batchAckMessage(createContext(), receiptHandleMessageList, CONSUMER_GROUP, TOPIC, 3000).get();
++ assertEquals(receiptHandleMessageList.size(), batchAckResultList.size());
++
++ // check ackResult for each msg
++ Map<String, BatchAckResult> msgBatchAckResult = new HashMap<>();
++ for (BatchAckResult batchAckResult : batchAckResultList) {
++ msgBatchAckResult.put(batchAckResult.getReceiptHandleMessage().getMessageId(), batchAckResult);
++ }
++ for (String msgId : broker1Msg) {
++ assertEquals(AckStatus.OK, msgBatchAckResult.get(msgId).getAckResult().getStatus());
++ assertNull(msgBatchAckResult.get(msgId).getProxyException());
++ }
++ for (String msgId : broker2Msg) {
++ assertEquals(AckStatus.NO_EXIST, msgBatchAckResult.get(msgId).getAckResult().getStatus());
++ assertNull(msgBatchAckResult.get(msgId).getProxyException());
++ }
++ assertNotNull(msgBatchAckResult.get(expireMessage.getMsgId()).getProxyException());
++ assertEquals(ProxyExceptionCode.INVALID_RECEIPT_HANDLE, msgBatchAckResult.get(expireMessage.getMsgId()).getProxyException().getCode());
++ assertNull(msgBatchAckResult.get(expireMessage.getMsgId()).getAckResult());
++
++ assertNotNull(msgBatchAckResult.get(errThrowMessage.getMsgId()).getProxyException());
++ assertEquals(ProxyExceptionCode.INTERNAL_SERVER_ERROR, msgBatchAckResult.get(errThrowMessage.getMsgId()).getProxyException().getCode());
++ assertNull(msgBatchAckResult.get(errThrowMessage.getMsgId()).getAckResult());
++ }
++
+ @Test
+ public void testChangeInvisibleTime() throws Throwable {
+ ReceiptHandle handle = create(createMessageExt(MixAll.RETRY_GROUP_TOPIC_PREFIX + TOPIC, "", 0, 3000));
+diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/service/mqclient/MQClientAPIExtTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/service/mqclient/MQClientAPIExtTest.java
+index 77a119a29..3f3a4ae40 100644
+--- a/proxy/src/test/java/org/apache/rocketmq/proxy/service/mqclient/MQClientAPIExtTest.java
++++ b/proxy/src/test/java/org/apache/rocketmq/proxy/service/mqclient/MQClientAPIExtTest.java
+@@ -220,6 +220,18 @@ public class MQClientAPIExtTest {
+ assertSame(ackResult, mqClientAPI.ackMessageAsync(BROKER_ADDR, new AckMessageRequestHeader(), TIMEOUT).get());
+ }
+
++ @Test
++ public void testBatchAckMessageAsync() throws Exception {
++ AckResult ackResult = new AckResult();
++ doAnswer((Answer<Void>) mock -> {
++ AckCallback ackCallback = mock.getArgument(2);
++ ackCallback.onSuccess(ackResult);
++ return null;
++ }).when(mqClientAPI).batchAckMessageAsync(anyString(), anyLong(), any(AckCallback.class), any());
++
++ assertSame(ackResult, mqClientAPI.batchAckMessageAsync(BROKER_ADDR, TOPIC, CONSUMER_GROUP, new ArrayList<>(), TIMEOUT).get());
++ }
++
+ @Test
+ public void testChangeInvisibleTimeAsync() throws Exception {
+ AckResult ackResult = new AckResult();
+--
+2.32.0.windows.2
+
diff --git a/patch013-backport-enhance-admin-output.patch b/patch013-backport-enhance-admin-output.patch
new file mode 100644
index 000000000..3fa60916f
--- /dev/null
+++ b/patch013-backport-enhance-admin-output.patch
@@ -0,0 +1,892 @@
+From 7e018520ef707a841c66c55d621f6560d03b631b Mon Sep 17 00:00:00 2001
+From: Zhouxiang Zhan <zhouxzhan@apache.org>
+Date: Fri, 25 Aug 2023 09:49:22 +0800
+Subject: [PATCH 1/6] Add expireAfterAccess for cache (#7247)
+
+Add expireAfterAccess for cache
+---
+ .../rocketmq/proxy/config/ProxyConfig.java | 59 ++++++++++++++-----
+ .../metadata/ClusterMetadataService.java | 6 +-
+ .../service/route/TopicRouteService.java | 14 +++--
+ 3 files changed, 56 insertions(+), 23 deletions(-)
+
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java b/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java
+index 76a243919..2994893d7 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java
+@@ -155,14 +155,17 @@ public class ProxyConfig implements ConfigFile {
+ private int consumerProcessorThreadPoolQueueCapacity = 10000;
+
+ private boolean useEndpointPortFromRequest = false;
+- private int topicRouteServiceCacheExpiredInSeconds = 20;
++
++ private int topicRouteServiceCacheExpiredSeconds = 300;
++ private int topicRouteServiceCacheRefreshSeconds = 20;
+ private int topicRouteServiceCacheMaxNum = 20000;
+ private int topicRouteServiceThreadPoolNums = PROCESSOR_NUMBER;
+ private int topicRouteServiceThreadPoolQueueCapacity = 5000;
+-
+- private int topicConfigCacheExpiredInSeconds = 20;
++ private int topicConfigCacheExpiredSeconds = 300;
++ private int topicConfigCacheRefreshSeconds = 20;
+ private int topicConfigCacheMaxNum = 20000;
+- private int subscriptionGroupConfigCacheExpiredInSeconds = 20;
++ private int subscriptionGroupConfigCacheExpiredSeconds = 300;
++ private int subscriptionGroupConfigCacheRefreshSeconds = 20;
+ private int subscriptionGroupConfigCacheMaxNum = 20000;
+ private int metadataThreadPoolNums = 3;
+ private int metadataThreadPoolQueueCapacity = 100000;
+@@ -794,12 +797,20 @@ public class ProxyConfig implements ConfigFile {
+ this.consumerProcessorThreadPoolQueueCapacity = consumerProcessorThreadPoolQueueCapacity;
+ }
+
+- public int getTopicRouteServiceCacheExpiredInSeconds() {
+- return topicRouteServiceCacheExpiredInSeconds;
++ public int getTopicRouteServiceCacheExpiredSeconds() {
++ return topicRouteServiceCacheExpiredSeconds;
++ }
++
++ public void setTopicRouteServiceCacheExpiredSeconds(int topicRouteServiceCacheExpiredSeconds) {
++ this.topicRouteServiceCacheExpiredSeconds = topicRouteServiceCacheExpiredSeconds;
+ }
+
+- public void setTopicRouteServiceCacheExpiredInSeconds(int topicRouteServiceCacheExpiredInSeconds) {
+- this.topicRouteServiceCacheExpiredInSeconds = topicRouteServiceCacheExpiredInSeconds;
++ public int getTopicRouteServiceCacheRefreshSeconds() {
++ return topicRouteServiceCacheRefreshSeconds;
++ }
++
++ public void setTopicRouteServiceCacheRefreshSeconds(int topicRouteServiceCacheRefreshSeconds) {
++ this.topicRouteServiceCacheRefreshSeconds = topicRouteServiceCacheRefreshSeconds;
+ }
+
+ public int getTopicRouteServiceCacheMaxNum() {
+@@ -826,12 +837,20 @@ public class ProxyConfig implements ConfigFile {
+ this.topicRouteServiceThreadPoolQueueCapacity = topicRouteServiceThreadPoolQueueCapacity;
+ }
+
+- public int getTopicConfigCacheExpiredInSeconds() {
+- return topicConfigCacheExpiredInSeconds;
++ public int getTopicConfigCacheRefreshSeconds() {
++ return topicConfigCacheRefreshSeconds;
++ }
++
++ public void setTopicConfigCacheRefreshSeconds(int topicConfigCacheRefreshSeconds) {
++ this.topicConfigCacheRefreshSeconds = topicConfigCacheRefreshSeconds;
++ }
++
++ public int getTopicConfigCacheExpiredSeconds() {
++ return topicConfigCacheExpiredSeconds;
+ }
+
+- public void setTopicConfigCacheExpiredInSeconds(int topicConfigCacheExpiredInSeconds) {
+- this.topicConfigCacheExpiredInSeconds = topicConfigCacheExpiredInSeconds;
++ public void setTopicConfigCacheExpiredSeconds(int topicConfigCacheExpiredSeconds) {
++ this.topicConfigCacheExpiredSeconds = topicConfigCacheExpiredSeconds;
+ }
+
+ public int getTopicConfigCacheMaxNum() {
+@@ -842,12 +861,20 @@ public class ProxyConfig implements ConfigFile {
+ this.topicConfigCacheMaxNum = topicConfigCacheMaxNum;
+ }
+
+- public int getSubscriptionGroupConfigCacheExpiredInSeconds() {
+- return subscriptionGroupConfigCacheExpiredInSeconds;
++ public int getSubscriptionGroupConfigCacheRefreshSeconds() {
++ return subscriptionGroupConfigCacheRefreshSeconds;
++ }
++
++ public void setSubscriptionGroupConfigCacheRefreshSeconds(int subscriptionGroupConfigCacheRefreshSeconds) {
++ this.subscriptionGroupConfigCacheRefreshSeconds = subscriptionGroupConfigCacheRefreshSeconds;
++ }
++
++ public int getSubscriptionGroupConfigCacheExpiredSeconds() {
++ return subscriptionGroupConfigCacheExpiredSeconds;
+ }
+
+- public void setSubscriptionGroupConfigCacheExpiredInSeconds(int subscriptionGroupConfigCacheExpiredInSeconds) {
+- this.subscriptionGroupConfigCacheExpiredInSeconds = subscriptionGroupConfigCacheExpiredInSeconds;
++ public void setSubscriptionGroupConfigCacheExpiredSeconds(int subscriptionGroupConfigCacheExpiredSeconds) {
++ this.subscriptionGroupConfigCacheExpiredSeconds = subscriptionGroupConfigCacheExpiredSeconds;
+ }
+
+ public int getSubscriptionGroupConfigCacheMaxNum() {
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/metadata/ClusterMetadataService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/metadata/ClusterMetadataService.java
+index bc9582ad8..d34a0efd9 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/metadata/ClusterMetadataService.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/metadata/ClusterMetadataService.java
+@@ -69,11 +69,13 @@ public class ClusterMetadataService extends AbstractStartAndShutdown implements
+ );
+ this.topicConfigCache = CacheBuilder.newBuilder()
+ .maximumSize(config.getTopicConfigCacheMaxNum())
+- .refreshAfterWrite(config.getTopicConfigCacheExpiredInSeconds(), TimeUnit.SECONDS)
++ .expireAfterAccess(config.getTopicConfigCacheExpiredSeconds(), TimeUnit.SECONDS)
++ .refreshAfterWrite(config.getTopicConfigCacheRefreshSeconds(), TimeUnit.SECONDS)
+ .build(new ClusterTopicConfigCacheLoader());
+ this.subscriptionGroupConfigCache = CacheBuilder.newBuilder()
+ .maximumSize(config.getSubscriptionGroupConfigCacheMaxNum())
+- .refreshAfterWrite(config.getSubscriptionGroupConfigCacheExpiredInSeconds(), TimeUnit.SECONDS)
++ .expireAfterAccess(config.getSubscriptionGroupConfigCacheExpiredSeconds(), TimeUnit.SECONDS)
++ .refreshAfterWrite(config.getSubscriptionGroupConfigCacheRefreshSeconds(), TimeUnit.SECONDS)
+ .build(new ClusterSubscriptionGroupConfigCacheLoader());
+
+ this.init();
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java
+index e012a5465..84348adc3 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java
+@@ -68,10 +68,13 @@ public abstract class TopicRouteService extends AbstractStartAndShutdown {
+ );
+ this.mqClientAPIFactory = mqClientAPIFactory;
+
+- this.topicCache = Caffeine.newBuilder().maximumSize(config.getTopicRouteServiceCacheMaxNum()).
+- refreshAfterWrite(config.getTopicRouteServiceCacheExpiredInSeconds(), TimeUnit.SECONDS).
+- executor(cacheRefreshExecutor).build(new CacheLoader<String, MessageQueueView>() {
+- @Override public @Nullable MessageQueueView load(String topic) throws Exception {
++ this.topicCache = Caffeine.newBuilder().maximumSize(config.getTopicRouteServiceCacheMaxNum())
++ .expireAfterAccess(config.getTopicRouteServiceCacheExpiredSeconds(), TimeUnit.SECONDS)
++ .refreshAfterWrite(config.getTopicRouteServiceCacheRefreshSeconds(), TimeUnit.SECONDS)
++ .executor(cacheRefreshExecutor)
++ .build(new CacheLoader<String, MessageQueueView>() {
++ @Override
++ public @Nullable MessageQueueView load(String topic) throws Exception {
+ try {
+ TopicRouteData topicRouteData = mqClientAPIFactory.getClient().getTopicRouteInfoFromNameServer(topic, Duration.ofSeconds(3).toMillis());
+ return buildMessageQueueView(topic, topicRouteData);
+@@ -83,7 +86,8 @@ public abstract class TopicRouteService extends AbstractStartAndShutdown {
+ }
+ }
+
+- @Override public @Nullable MessageQueueView reload(@NonNull String key,
++ @Override
++ public @Nullable MessageQueueView reload(@NonNull String key,
+ @NonNull MessageQueueView oldValue) throws Exception {
+ try {
+ return load(key);
+--
+2.32.0.windows.2
+
+
+From 5f6dc90f9dab35809fcb0407d4d5cc2737d2335e Mon Sep 17 00:00:00 2001
+From: Ziyi Tan <tanziyi0925@gmail.com>
+Date: Fri, 25 Aug 2023 11:17:23 +0800
+Subject: [PATCH 2/6] [ISSUE #7250] Beautify command rocksDBConfigToJson output
+
+Co-authored-by: Ziy1-Tan <ajb4596984460@gmail.com>
+---
+ .../metadata/RocksDBConfigToJsonCommand.java | 32 +++++++++++--------
+ 1 file changed, 18 insertions(+), 14 deletions(-)
+
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/metadata/RocksDBConfigToJsonCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/metadata/RocksDBConfigToJsonCommand.java
+index 3053f4684..3fc63e4dd 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/metadata/RocksDBConfigToJsonCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/metadata/RocksDBConfigToJsonCommand.java
+@@ -21,13 +21,13 @@ import org.apache.commons.cli.CommandLine;
+ import org.apache.commons.cli.Option;
+ import org.apache.commons.cli.Options;
+ import org.apache.commons.lang3.StringUtils;
++import org.apache.rocketmq.common.UtilAll;
+ import org.apache.rocketmq.common.config.RocksDBConfigManager;
+ import org.apache.rocketmq.common.utils.DataConverter;
+ import org.apache.rocketmq.remoting.RPCHook;
+ import org.apache.rocketmq.tools.command.SubCommand;
+ import org.apache.rocketmq.tools.command.SubCommandException;
+
+-import java.io.File;
+ import java.util.HashMap;
+ import java.util.Map;
+
+@@ -48,7 +48,7 @@ public class RocksDBConfigToJsonCommand implements SubCommand {
+ @Override
+ public Options buildCommandlineOptions(Options options) {
+ Option pathOption = new Option("p", "path", true,
+- "Absolute path to the metadata directory");
++ "Absolute path for the metadata directory");
+ pathOption.setRequired(true);
+ options.addOption(pathOption);
+
+@@ -63,15 +63,14 @@ public class RocksDBConfigToJsonCommand implements SubCommand {
+ @Override
+ public void execute(CommandLine commandLine, Options options, RPCHook rpcHook) throws SubCommandException {
+ String path = commandLine.getOptionValue("path").trim();
+- if (StringUtils.isEmpty(path) || !new File(path).exists()) {
++ if (StringUtils.isEmpty(path) || !UtilAll.isPathExists(path)) {
+ System.out.print("Rocksdb path is invalid.\n");
+ return;
+ }
+
+ String configType = commandLine.getOptionValue("configType").trim().toLowerCase();
+
+- final long memTableFlushInterval = 60 * 60 * 1000L;
+- RocksDBConfigManager kvConfigManager = new RocksDBConfigManager(memTableFlushInterval);
++ RocksDBConfigManager kvConfigManager = new RocksDBConfigManager(60 * 60 * 1000L);
+ try {
+ if (TOPICS_JSON_CONFIG.toLowerCase().equals(configType)) {
+ // for topics.json
+@@ -84,13 +83,16 @@ public class RocksDBConfigToJsonCommand implements SubCommand {
+ topicConfigTable.put(topic, jsonObject);
+ });
+
+- if (isLoad) {
+- topicsJsonConfig.put("topicConfigTable", (JSONObject) JSONObject.toJSON(topicConfigTable));
+- final String topicsJsonStr = JSONObject.toJSONString(topicsJsonConfig, true);
+- System.out.print(topicsJsonStr + "\n");
++ if (!isLoad) {
++ System.out.print("RocksDB load error, path=" + path);
+ return;
+ }
++ topicsJsonConfig.put("topicConfigTable", (JSONObject) JSONObject.toJSON(topicConfigTable));
++ final String topicsJsonStr = JSONObject.toJSONString(topicsJsonConfig, true);
++ System.out.print(topicsJsonStr + "\n");
++ return;
+ }
++
+ if (SUBSCRIPTION_GROUP_JSON_CONFIG.toLowerCase().equals(configType)) {
+ // for subscriptionGroup.json
+ final Map<String, JSONObject> subscriptionGroupJsonConfig = new HashMap<>();
+@@ -102,13 +104,15 @@ public class RocksDBConfigToJsonCommand implements SubCommand {
+ subscriptionGroupTable.put(subscriptionGroup, jsonObject);
+ });
+
+- if (isLoad) {
+- subscriptionGroupJsonConfig.put("subscriptionGroupTable",
+- (JSONObject) JSONObject.toJSON(subscriptionGroupTable));
+- final String subscriptionGroupJsonStr = JSONObject.toJSONString(subscriptionGroupJsonConfig, true);
+- System.out.print(subscriptionGroupJsonStr + "\n");
++ if (!isLoad) {
++ System.out.print("RocksDB load error, path=" + path);
+ return;
+ }
++ subscriptionGroupJsonConfig.put("subscriptionGroupTable",
++ (JSONObject) JSONObject.toJSON(subscriptionGroupTable));
++ final String subscriptionGroupJsonStr = JSONObject.toJSONString(subscriptionGroupJsonConfig, true);
++ System.out.print(subscriptionGroupJsonStr + "\n");
++ return;
+ }
+ System.out.print("Config type was not recognized, configType=" + configType + "\n");
+ } finally {
+--
+2.32.0.windows.2
+
+
+From b4f73e2aabc1b141cec98431899e4090340adf0f Mon Sep 17 00:00:00 2001
+From: mxsm <ljbmxsm@gmail.com>
+Date: Sun, 27 Aug 2023 20:58:58 +0800
+Subject: [PATCH 3/6] [ISSUE #7271] Optimize the configuration for setting the
+ quantity of TimerDequeuePutMessageService (#7272)
+
+---
+ .../java/org/apache/rocketmq/store/timer/TimerMessageStore.java | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/store/src/main/java/org/apache/rocketmq/store/timer/TimerMessageStore.java b/store/src/main/java/org/apache/rocketmq/store/timer/TimerMessageStore.java
+index 690f4863e..181f7087a 100644
+--- a/store/src/main/java/org/apache/rocketmq/store/timer/TimerMessageStore.java
++++ b/store/src/main/java/org/apache/rocketmq/store/timer/TimerMessageStore.java
+@@ -222,7 +222,7 @@ public class TimerMessageStore {
+ dequeueGetMessageServices[i] = new TimerDequeueGetMessageService();
+ }
+
+- int putThreadNum = Math.max(storeConfig.getTimerGetMessageThreadNum(), 1);
++ int putThreadNum = Math.max(storeConfig.getTimerPutMessageThreadNum(), 1);
+ dequeuePutMessageServices = new TimerDequeuePutMessageService[putThreadNum];
+ for (int i = 0; i < dequeuePutMessageServices.length; i++) {
+ dequeuePutMessageServices[i] = new TimerDequeuePutMessageService();
+--
+2.32.0.windows.2
+
+
+From 3e100103af68588528bf32f3752a85e8023f46f8 Mon Sep 17 00:00:00 2001
+From: Ziyi Tan <tanziyi0925@gmail.com>
+Date: Tue, 29 Aug 2023 13:48:51 +0800
+Subject: [PATCH 4/6] [ISSUE #7277] Enhance rocksDBConfigToJson to support
+ metadata counting (#7276)
+
+---
+ .../common/config/AbstractRocksDBStorage.java | 4 +-
+ .../common/config/ConfigRocksDBStorage.java | 6 +
+ .../tools/command/MQAdminStartup.java | 4 +-
+ .../ExportMetadataInRocksDBCommand.java | 138 ++++++++++++++++++
+ .../metadata/RocksDBConfigToJsonCommand.java | 122 ----------------
+ ...> ExportMetadataInRocksDBCommandTest.java} | 38 +++--
+ 6 files changed, 173 insertions(+), 139 deletions(-)
+ create mode 100644 tools/src/main/java/org/apache/rocketmq/tools/command/export/ExportMetadataInRocksDBCommand.java
+ delete mode 100644 tools/src/main/java/org/apache/rocketmq/tools/command/metadata/RocksDBConfigToJsonCommand.java
+ rename tools/src/test/java/org/apache/rocketmq/tools/command/metadata/{KvConfigToJsonCommandTest.java => ExportMetadataInRocksDBCommandTest.java} (62%)
+
+diff --git a/common/src/main/java/org/apache/rocketmq/common/config/AbstractRocksDBStorage.java b/common/src/main/java/org/apache/rocketmq/common/config/AbstractRocksDBStorage.java
+index e3673baad..a720a5be3 100644
+--- a/common/src/main/java/org/apache/rocketmq/common/config/AbstractRocksDBStorage.java
++++ b/common/src/main/java/org/apache/rocketmq/common/config/AbstractRocksDBStorage.java
+@@ -385,8 +385,10 @@ public abstract class AbstractRocksDBStorage {
+ this.options.close();
+ }
+ //4. close db.
+- if (db != null) {
++ if (db != null && !this.readOnly) {
+ this.db.syncWal();
++ }
++ if (db != null) {
+ this.db.closeE();
+ }
+ //5. help gc.
+diff --git a/common/src/main/java/org/apache/rocketmq/common/config/ConfigRocksDBStorage.java b/common/src/main/java/org/apache/rocketmq/common/config/ConfigRocksDBStorage.java
+index 9d05ed282..463bd8fed 100644
+--- a/common/src/main/java/org/apache/rocketmq/common/config/ConfigRocksDBStorage.java
++++ b/common/src/main/java/org/apache/rocketmq/common/config/ConfigRocksDBStorage.java
+@@ -60,6 +60,12 @@ public class ConfigRocksDBStorage extends AbstractRocksDBStorage {
+ this.readOnly = false;
+ }
+
++ public ConfigRocksDBStorage(final String dbPath, boolean readOnly) {
++ super();
++ this.dbPath = dbPath;
++ this.readOnly = readOnly;
++ }
++
+ private void initOptions() {
+ this.options = createConfigDBOptions();
+
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/MQAdminStartup.java b/tools/src/main/java/org/apache/rocketmq/tools/command/MQAdminStartup.java
+index 324aa1856..788fa83c2 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/MQAdminStartup.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/MQAdminStartup.java
+@@ -80,7 +80,7 @@ import org.apache.rocketmq.tools.command.message.QueryMsgByOffsetSubCommand;
+ import org.apache.rocketmq.tools.command.message.QueryMsgByUniqueKeySubCommand;
+ import org.apache.rocketmq.tools.command.message.QueryMsgTraceByIdSubCommand;
+ import org.apache.rocketmq.tools.command.message.SendMessageCommand;
+-import org.apache.rocketmq.tools.command.metadata.RocksDBConfigToJsonCommand;
++import org.apache.rocketmq.tools.command.export.ExportMetadataInRocksDBCommand;
+ import org.apache.rocketmq.tools.command.namesrv.AddWritePermSubCommand;
+ import org.apache.rocketmq.tools.command.namesrv.DeleteKvConfigCommand;
+ import org.apache.rocketmq.tools.command.namesrv.GetNamesrvConfigCommand;
+@@ -212,7 +212,6 @@ public class MQAdminStartup {
+
+ initCommand(new ClusterListSubCommand());
+ initCommand(new TopicListSubCommand());
+- initCommand(new RocksDBConfigToJsonCommand());
+
+ initCommand(new UpdateKvConfigCommand());
+ initCommand(new DeleteKvConfigCommand());
+@@ -257,6 +256,7 @@ public class MQAdminStartup {
+ initCommand(new ExportMetadataCommand());
+ initCommand(new ExportConfigsCommand());
+ initCommand(new ExportMetricsCommand());
++ initCommand(new ExportMetadataInRocksDBCommand());
+
+ initCommand(new HAStatusSubCommand());
+
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/export/ExportMetadataInRocksDBCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/export/ExportMetadataInRocksDBCommand.java
+new file mode 100644
+index 000000000..2a7d3fba4
+--- /dev/null
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/export/ExportMetadataInRocksDBCommand.java
+@@ -0,0 +1,138 @@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one or more
++ * contributor license agreements. See the NOTICE file distributed with
++ * this work for additional information regarding copyright ownership.
++ * The ASF licenses this file to You under the Apache License, Version 2.0
++ * (the "License"); you may not use this file except in compliance with
++ * the License. You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++package org.apache.rocketmq.tools.command.export;
++
++import com.alibaba.fastjson.JSONObject;
++import org.apache.commons.cli.CommandLine;
++import org.apache.commons.cli.Option;
++import org.apache.commons.cli.Options;
++import org.apache.commons.lang3.StringUtils;
++import org.apache.rocketmq.common.UtilAll;
++import org.apache.rocketmq.common.config.ConfigRocksDBStorage;
++import org.apache.rocketmq.common.utils.DataConverter;
++import org.apache.rocketmq.remoting.RPCHook;
++import org.apache.rocketmq.tools.command.SubCommand;
++import org.apache.rocketmq.tools.command.SubCommandException;
++import org.rocksdb.RocksIterator;
++
++import java.util.HashMap;
++import java.util.Map;
++import java.util.concurrent.atomic.AtomicLong;
++import java.util.function.BiConsumer;
++
++public class ExportMetadataInRocksDBCommand implements SubCommand {
++ private static final String TOPICS_JSON_CONFIG = "topics";
++ private static final String SUBSCRIPTION_GROUP_JSON_CONFIG = "subscriptionGroups";
++
++ @Override
++ public String commandName() {
++ return "exportMetadataInRocksDB";
++ }
++
++ @Override
++ public String commandDesc() {
++ return "export RocksDB kv config (topics/subscriptionGroups)";
++ }
++
++ @Override
++ public Options buildCommandlineOptions(Options options) {
++ Option pathOption = new Option("p", "path", true,
++ "Absolute path for the metadata directory");
++ pathOption.setRequired(true);
++ options.addOption(pathOption);
++
++ Option configTypeOption = new Option("t", "configType", true, "Name of kv config, e.g. " +
++ "topics/subscriptionGroups");
++ configTypeOption.setRequired(true);
++ options.addOption(configTypeOption);
++
++ Option jsonEnableOption = new Option("j", "jsonEnable", true,
++ "Json format enable, Default: false");
++ jsonEnableOption.setRequired(false);
++ options.addOption(jsonEnableOption);
++
++ return options;
++ }
++
++ @Override
++ public void execute(CommandLine commandLine, Options options, RPCHook rpcHook) throws SubCommandException {
++ String path = commandLine.getOptionValue("path").trim();
++ if (StringUtils.isEmpty(path) || !UtilAll.isPathExists(path)) {
++ System.out.print("RocksDB path is invalid.\n");
++ return;
++ }
++
++ String configType = commandLine.getOptionValue("configType").trim().toLowerCase();
++
++ boolean jsonEnable = false;
++ if (commandLine.hasOption("jsonEnable")) {
++ jsonEnable = Boolean.parseBoolean(commandLine.getOptionValue("jsonEnable").trim());
++ }
++
++
++ ConfigRocksDBStorage kvStore = new ConfigRocksDBStorage(path, true /* readOnly */);
++ if (!kvStore.start()) {
++ System.out.print("RocksDB load error, path=" + path + "\n");
++ return;
++ }
++
++ try {
++ if (TOPICS_JSON_CONFIG.equalsIgnoreCase(configType) || SUBSCRIPTION_GROUP_JSON_CONFIG.equalsIgnoreCase(configType)) {
++ handleExportMetadata(kvStore, configType, jsonEnable);
++ } else {
++ System.out.printf("Invalid config type=%s, Options: topics,subscriptionGroups\n", configType);
++ }
++ } finally {
++ kvStore.shutdown();
++ }
++ }
++
++ private static void handleExportMetadata(ConfigRocksDBStorage kvStore, String configType, boolean jsonEnable) {
++ if (jsonEnable) {
++ final Map<String, JSONObject> jsonConfig = new HashMap<>();
++ final Map<String, JSONObject> configTable = new HashMap<>();
++ iterateKvStore(kvStore, (key, value) -> {
++ final String configKey = new String(key, DataConverter.charset);
++ final String configValue = new String(value, DataConverter.charset);
++ final JSONObject jsonObject = JSONObject.parseObject(configValue);
++ configTable.put(configKey, jsonObject);
++ }
++ );
++
++ jsonConfig.put(configType.equalsIgnoreCase(TOPICS_JSON_CONFIG) ? "topicConfigTable" : "subscriptionGroupTable",
++ (JSONObject) JSONObject.toJSON(configTable));
++ final String jsonConfigStr = JSONObject.toJSONString(jsonConfig, true);
++ System.out.print(jsonConfigStr + "\n");
++ } else {
++ AtomicLong count = new AtomicLong(0);
++ iterateKvStore(kvStore, (key, value) -> {
++ final String configKey = new String(key, DataConverter.charset);
++ final String configValue = new String(value, DataConverter.charset);
++ System.out.printf("%d, Key: %s, Value: %s%n", count.incrementAndGet(), configKey, configValue);
++ });
++ }
++ }
++
++ private static void iterateKvStore(ConfigRocksDBStorage kvStore, BiConsumer<byte[], byte[]> biConsumer) {
++ try (RocksIterator iterator = kvStore.iterator()) {
++ iterator.seekToFirst();
++ for (iterator.seekToFirst(); iterator.isValid(); iterator.next()) {
++ biConsumer.accept(iterator.key(), iterator.value());
++ }
++ }
++ }
++}
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/metadata/RocksDBConfigToJsonCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/metadata/RocksDBConfigToJsonCommand.java
+deleted file mode 100644
+index 3fc63e4dd..000000000
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/metadata/RocksDBConfigToJsonCommand.java
++++ /dev/null
+@@ -1,122 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-package org.apache.rocketmq.tools.command.metadata;
+-
+-import com.alibaba.fastjson.JSONObject;
+-import org.apache.commons.cli.CommandLine;
+-import org.apache.commons.cli.Option;
+-import org.apache.commons.cli.Options;
+-import org.apache.commons.lang3.StringUtils;
+-import org.apache.rocketmq.common.UtilAll;
+-import org.apache.rocketmq.common.config.RocksDBConfigManager;
+-import org.apache.rocketmq.common.utils.DataConverter;
+-import org.apache.rocketmq.remoting.RPCHook;
+-import org.apache.rocketmq.tools.command.SubCommand;
+-import org.apache.rocketmq.tools.command.SubCommandException;
+-
+-import java.util.HashMap;
+-import java.util.Map;
+-
+-public class RocksDBConfigToJsonCommand implements SubCommand {
+- private static final String TOPICS_JSON_CONFIG = "topics";
+- private static final String SUBSCRIPTION_GROUP_JSON_CONFIG = "subscriptionGroups";
+-
+- @Override
+- public String commandName() {
+- return "rocksDBConfigToJson";
+- }
+-
+- @Override
+- public String commandDesc() {
+- return "Convert RocksDB kv config (topics/subscriptionGroups) to json";
+- }
+-
+- @Override
+- public Options buildCommandlineOptions(Options options) {
+- Option pathOption = new Option("p", "path", true,
+- "Absolute path for the metadata directory");
+- pathOption.setRequired(true);
+- options.addOption(pathOption);
+-
+- Option configTypeOption = new Option("t", "configType", true, "Name of kv config, e.g. " +
+- "topics/subscriptionGroups");
+- configTypeOption.setRequired(true);
+- options.addOption(configTypeOption);
+-
+- return options;
+- }
+-
+- @Override
+- public void execute(CommandLine commandLine, Options options, RPCHook rpcHook) throws SubCommandException {
+- String path = commandLine.getOptionValue("path").trim();
+- if (StringUtils.isEmpty(path) || !UtilAll.isPathExists(path)) {
+- System.out.print("Rocksdb path is invalid.\n");
+- return;
+- }
+-
+- String configType = commandLine.getOptionValue("configType").trim().toLowerCase();
+-
+- RocksDBConfigManager kvConfigManager = new RocksDBConfigManager(60 * 60 * 1000L);
+- try {
+- if (TOPICS_JSON_CONFIG.toLowerCase().equals(configType)) {
+- // for topics.json
+- final Map<String, JSONObject> topicsJsonConfig = new HashMap<>();
+- final Map<String, JSONObject> topicConfigTable = new HashMap<>();
+- boolean isLoad = kvConfigManager.load(path, (key, value) -> {
+- final String topic = new String(key, DataConverter.charset);
+- final String topicConfig = new String(value, DataConverter.charset);
+- final JSONObject jsonObject = JSONObject.parseObject(topicConfig);
+- topicConfigTable.put(topic, jsonObject);
+- });
+-
+- if (!isLoad) {
+- System.out.print("RocksDB load error, path=" + path);
+- return;
+- }
+- topicsJsonConfig.put("topicConfigTable", (JSONObject) JSONObject.toJSON(topicConfigTable));
+- final String topicsJsonStr = JSONObject.toJSONString(topicsJsonConfig, true);
+- System.out.print(topicsJsonStr + "\n");
+- return;
+- }
+-
+- if (SUBSCRIPTION_GROUP_JSON_CONFIG.toLowerCase().equals(configType)) {
+- // for subscriptionGroup.json
+- final Map<String, JSONObject> subscriptionGroupJsonConfig = new HashMap<>();
+- final Map<String, JSONObject> subscriptionGroupTable = new HashMap<>();
+- boolean isLoad = kvConfigManager.load(path, (key, value) -> {
+- final String subscriptionGroup = new String(key, DataConverter.charset);
+- final String subscriptionGroupConfig = new String(value, DataConverter.charset);
+- final JSONObject jsonObject = JSONObject.parseObject(subscriptionGroupConfig);
+- subscriptionGroupTable.put(subscriptionGroup, jsonObject);
+- });
+-
+- if (!isLoad) {
+- System.out.print("RocksDB load error, path=" + path);
+- return;
+- }
+- subscriptionGroupJsonConfig.put("subscriptionGroupTable",
+- (JSONObject) JSONObject.toJSON(subscriptionGroupTable));
+- final String subscriptionGroupJsonStr = JSONObject.toJSONString(subscriptionGroupJsonConfig, true);
+- System.out.print(subscriptionGroupJsonStr + "\n");
+- return;
+- }
+- System.out.print("Config type was not recognized, configType=" + configType + "\n");
+- } finally {
+- kvConfigManager.stop();
+- }
+- }
+-}
+diff --git a/tools/src/test/java/org/apache/rocketmq/tools/command/metadata/KvConfigToJsonCommandTest.java b/tools/src/test/java/org/apache/rocketmq/tools/command/metadata/ExportMetadataInRocksDBCommandTest.java
+similarity index 62%
+rename from tools/src/test/java/org/apache/rocketmq/tools/command/metadata/KvConfigToJsonCommandTest.java
+rename to tools/src/test/java/org/apache/rocketmq/tools/command/metadata/ExportMetadataInRocksDBCommandTest.java
+index b2f66c7b0..2b938c90f 100644
+--- a/tools/src/test/java/org/apache/rocketmq/tools/command/metadata/KvConfigToJsonCommandTest.java
++++ b/tools/src/test/java/org/apache/rocketmq/tools/command/metadata/ExportMetadataInRocksDBCommandTest.java
+@@ -21,43 +21,53 @@ import org.apache.commons.cli.DefaultParser;
+ import org.apache.commons.cli.Options;
+ import org.apache.rocketmq.srvutil.ServerUtil;
+ import org.apache.rocketmq.tools.command.SubCommandException;
++import org.apache.rocketmq.tools.command.export.ExportMetadataInRocksDBCommand;
+ import org.junit.Test;
+
+ import java.io.File;
+
+ import static org.assertj.core.api.Assertions.assertThat;
+
+-public class KvConfigToJsonCommandTest {
++public class ExportMetadataInRocksDBCommandTest {
+ private static final String BASE_PATH = System.getProperty("user.home") + File.separator + "store/config/";
+
+ @Test
+ public void testExecute() throws SubCommandException {
+ {
+- String[] cases = new String[]{"topics", "subscriptionGroups"};
+- for (String c : cases) {
+- RocksDBConfigToJsonCommand cmd = new RocksDBConfigToJsonCommand();
++ String[][] cases = new String[][] {
++ {"topics", "false"},
++ {"topics", "false1"},
++ {"topics", "true"},
++ {"subscriptionGroups", "false"},
++ {"subscriptionGroups", "false2"},
++ {"subscriptionGroups", "true"}
++ };
++
++ for (String[] c : cases) {
++ ExportMetadataInRocksDBCommand cmd = new ExportMetadataInRocksDBCommand();
+ Options options = ServerUtil.buildCommandlineOptions(new Options());
+- String[] subargs = new String[]{"-p " + BASE_PATH + c, "-t " + c};
++ String[] subargs = new String[] {"-p " + BASE_PATH + c[0], "-t " + c[0], "-j " + c[1]};
+ final CommandLine commandLine = ServerUtil.parseCmdLine("mqadmin " + cmd.commandName(), subargs,
+- cmd.buildCommandlineOptions(options), new DefaultParser());
++ cmd.buildCommandlineOptions(options), new DefaultParser());
+ cmd.execute(commandLine, options, null);
+- assertThat(commandLine.getOptionValue("p").trim()).isEqualTo(BASE_PATH + c);
+- assertThat(commandLine.getOptionValue("t").trim()).isEqualTo(c);
++ assertThat(commandLine.getOptionValue("p").trim()).isEqualTo(BASE_PATH + c[0]);
++ assertThat(commandLine.getOptionValue("t").trim()).isEqualTo(c[0]);
++ assertThat(commandLine.getOptionValue("j").trim()).isEqualTo(c[1]);
+ }
+ }
+ // invalid cases
+ {
+- String[][] cases = new String[][]{
+- {"-p " + BASE_PATH + "tmpPath", "-t topics"},
+- {"-p ", "-t topics"},
+- {"-p " + BASE_PATH + "topics", "-t invalid_type"}
++ String[][] cases = new String[][] {
++ {"-p " + BASE_PATH + "tmpPath", "-t topics", "-j true"},
++ {"-p ", "-t topics", "-j true"},
++ {"-p " + BASE_PATH + "topics", "-t invalid_type", "-j true"}
+ };
+
+ for (String[] c : cases) {
+- RocksDBConfigToJsonCommand cmd = new RocksDBConfigToJsonCommand();
++ ExportMetadataInRocksDBCommand cmd = new ExportMetadataInRocksDBCommand();
+ Options options = ServerUtil.buildCommandlineOptions(new Options());
+ final CommandLine commandLine = ServerUtil.parseCmdLine("mqadmin " + cmd.commandName(), c,
+- cmd.buildCommandlineOptions(options), new DefaultParser());
++ cmd.buildCommandlineOptions(options), new DefaultParser());
+ cmd.execute(commandLine, options, null);
+ }
+ }
+--
+2.32.0.windows.2
+
+
+From fa549154370cb866a90e37c13a90d2c598d6b1f6 Mon Sep 17 00:00:00 2001
+From: yuz10 <845238369@qq.com>
+Date: Tue, 29 Aug 2023 15:22:09 +0800
+Subject: [PATCH 5/6] [ISSUE #7261] Slave high CPU usage when
+ enableScheduleAsyncDeliver=true (#7262)
+
+* [ISSUE #6390] Add break to the exception of WHEEL_TIMER_NOT_ENABLE.
+
+* fix broker start fail if mapped file size is 0
+
+* log
+
+* only delete the last empty file
+
+* change dataReadAheadEnable default to true
+
+* fix endless loop when master change to slave.
+---
+ .../rocketmq/broker/schedule/ScheduleMessageService.java | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/schedule/ScheduleMessageService.java b/broker/src/main/java/org/apache/rocketmq/broker/schedule/ScheduleMessageService.java
+index aed0ee19f..297b14207 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/schedule/ScheduleMessageService.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/schedule/ScheduleMessageService.java
+@@ -566,7 +566,8 @@ public class ScheduleMessageService extends ConfigManager {
+ pendingQueue.remove();
+ break;
+ case RUNNING:
+- break;
++ scheduleNextTask();
++ return;
+ case EXCEPTION:
+ if (!isStarted()) {
+ log.warn("HandlePutResultTask shutdown, info={}", putResultProcess.toString());
+@@ -586,6 +587,10 @@ public class ScheduleMessageService extends ConfigManager {
+ }
+ }
+
++ scheduleNextTask();
++ }
++
++ private void scheduleNextTask() {
+ if (isStarted()) {
+ ScheduleMessageService.this.handleExecutorService
+ .schedule(new HandlePutResultTask(this.delayLevel), DELAY_FOR_A_SLEEP, TimeUnit.MILLISECONDS);
+--
+2.32.0.windows.2
+
+
+From 9f34f55e1dac495730c9cd5469f2ab3225b8f0b9 Mon Sep 17 00:00:00 2001
+From: ShuangxiDing <dingshuangxi888@gmail.com>
+Date: Tue, 29 Aug 2023 15:48:46 +0800
+Subject: [PATCH 6/6] [ISSUE #7226] Filter tlvs in ppv2 which contents not are
+ spec-compliant ASCII characters and space (#7227)
+
+Filter tlvs in ppv2 which not are spec-compliant ASCII characters and space
+---
+ .../rocketmq/common/utils/BinaryUtil.java | 17 +++++++++++++++++
+ .../grpc/ProxyAndTlsProtocolNegotiator.java | 8 +++++++-
+ .../remoting/netty/NettyRemotingServer.java | 8 +++++++-
+ 3 files changed, 31 insertions(+), 2 deletions(-)
+
+diff --git a/common/src/main/java/org/apache/rocketmq/common/utils/BinaryUtil.java b/common/src/main/java/org/apache/rocketmq/common/utils/BinaryUtil.java
+index 421adaca4..7b4b24819 100644
+--- a/common/src/main/java/org/apache/rocketmq/common/utils/BinaryUtil.java
++++ b/common/src/main/java/org/apache/rocketmq/common/utils/BinaryUtil.java
+@@ -43,4 +43,21 @@ public class BinaryUtil {
+ byte[] bytes = calculateMd5(content);
+ return Hex.encodeHexString(bytes, false);
+ }
++
++ /**
++ * Returns true if subject contains only bytes that are spec-compliant ASCII characters.
++ * @param subject
++ * @return
++ */
++ public static boolean isAscii(byte[] subject) {
++ if (subject == null) {
++ return false;
++ }
++ for (byte b : subject) {
++ if ((b & 0x80) != 0) {
++ return false;
++ }
++ }
++ return true;
++ }
+ }
+\ No newline at end of file
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/ProxyAndTlsProtocolNegotiator.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/ProxyAndTlsProtocolNegotiator.java
+index ee167bd7b..b584ddfbd 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/ProxyAndTlsProtocolNegotiator.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/ProxyAndTlsProtocolNegotiator.java
+@@ -24,6 +24,7 @@ import io.grpc.netty.shaded.io.grpc.netty.InternalProtocolNegotiator;
+ import io.grpc.netty.shaded.io.grpc.netty.InternalProtocolNegotiators;
+ import io.grpc.netty.shaded.io.grpc.netty.ProtocolNegotiationEvent;
+ import io.grpc.netty.shaded.io.netty.buffer.ByteBuf;
++import io.grpc.netty.shaded.io.netty.buffer.ByteBufUtil;
+ import io.grpc.netty.shaded.io.netty.channel.ChannelHandler;
+ import io.grpc.netty.shaded.io.netty.channel.ChannelHandlerContext;
+ import io.grpc.netty.shaded.io.netty.channel.ChannelInboundHandlerAdapter;
+@@ -44,6 +45,7 @@ import org.apache.commons.collections.CollectionUtils;
+ import org.apache.commons.lang3.StringUtils;
+ import org.apache.rocketmq.common.constant.HAProxyConstants;
+ import org.apache.rocketmq.common.constant.LoggerName;
++import org.apache.rocketmq.common.utils.BinaryUtil;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+ import org.apache.rocketmq.proxy.config.ConfigurationManager;
+@@ -191,9 +193,13 @@ public class ProxyAndTlsProtocolNegotiator implements InternalProtocolNegotiator
+ }
+ if (CollectionUtils.isNotEmpty(msg.tlvs())) {
+ msg.tlvs().forEach(tlv -> {
++ byte[] valueBytes = ByteBufUtil.getBytes(tlv.content());
++ if (!BinaryUtil.isAscii(valueBytes)) {
++ return;
++ }
+ Attributes.Key<String> key = AttributeKeys.valueOf(
+ HAProxyConstants.PROXY_PROTOCOL_TLV_PREFIX + String.format("%02x", tlv.typeByteValue()));
+- String value = StringUtils.trim(tlv.content().toString(CharsetUtil.UTF_8));
++ String value = StringUtils.trim(new String(valueBytes, CharsetUtil.UTF_8));
+ builder.set(key, value);
+ });
+ }
+diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java
+index 17f138f86..e626260c9 100644
+--- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java
++++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java
+@@ -18,6 +18,7 @@ package org.apache.rocketmq.remoting.netty;
+
+ import io.netty.bootstrap.ServerBootstrap;
+ import io.netty.buffer.ByteBuf;
++import io.netty.buffer.ByteBufUtil;
+ import io.netty.buffer.PooledByteBufAllocator;
+ import io.netty.channel.Channel;
+ import io.netty.channel.ChannelDuplexHandler;
+@@ -58,6 +59,7 @@ import org.apache.rocketmq.common.Pair;
+ import org.apache.rocketmq.common.ThreadFactoryImpl;
+ import org.apache.rocketmq.common.constant.HAProxyConstants;
+ import org.apache.rocketmq.common.constant.LoggerName;
++import org.apache.rocketmq.common.utils.BinaryUtil;
+ import org.apache.rocketmq.common.utils.NetworkUtil;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+@@ -787,9 +789,13 @@ public class NettyRemotingServer extends NettyRemotingAbstract implements Remoti
+ }
+ if (CollectionUtils.isNotEmpty(msg.tlvs())) {
+ msg.tlvs().forEach(tlv -> {
++ byte[] valueBytes = ByteBufUtil.getBytes(tlv.content());
++ if (!BinaryUtil.isAscii(valueBytes)) {
++ return;
++ }
+ AttributeKey<String> key = AttributeKeys.valueOf(
+ HAProxyConstants.PROXY_PROTOCOL_TLV_PREFIX + String.format("%02x", tlv.typeByteValue()));
+- String value = StringUtils.trim(tlv.content().toString(CharsetUtil.UTF_8));
++ String value = StringUtils.trim(new String(valueBytes, CharsetUtil.UTF_8));
+ channel.attr(key).set(value);
+ });
+ }
+--
+2.32.0.windows.2
+
diff --git a/patch014-backport-Queue-Selection-Strategy-Optimization.patch b/patch014-backport-Queue-Selection-Strategy-Optimization.patch
new file mode 100644
index 000000000..90d3553aa
--- /dev/null
+++ b/patch014-backport-Queue-Selection-Strategy-Optimization.patch
@@ -0,0 +1,2023 @@
+From b028277018946868838a82a08211071bc231a175 Mon Sep 17 00:00:00 2001
+From: Ji Juntao <juntao.jjt@alibaba-inc.com>
+Date: Tue, 29 Aug 2023 16:13:38 +0800
+Subject: [PATCH] [ISSUE #6567] [RIP-63] Queue Selection Strategy Optimization
+ (#6568)
+
+Optimize the proxy's and client's selection strategy for brokers when sending messages, and use multiple selection strategies as a pipeline to filter suitable queues.
+---
+ .../apache/rocketmq/client/ClientConfig.java | 54 +++++
+ .../client/common/ThreadLocalIndex.java | 8 +
+ .../rocketmq/client/impl/MQClientAPIImpl.java | 12 +-
+ .../client/impl/factory/MQClientInstance.java | 7 +
+ .../impl/producer/DefaultMQProducerImpl.java | 87 ++++++--
+ .../impl/producer/TopicPublishInfo.java | 40 ++++
+ .../client/latency/LatencyFaultTolerance.java | 66 +++++-
+ .../latency/LatencyFaultToleranceImpl.java | 189 ++++++++++++++----
+ .../client/latency/MQFaultStrategy.java | 155 ++++++++++----
+ .../rocketmq/client/latency/Resolver.java | 17 +-
+ .../client/latency/ServiceDetector.java | 30 +++
+ .../LatencyFaultToleranceImplTest.java | 36 +++-
+ .../processor/DefaultRequestProcessor.java | 24 ---
+ .../rocketmq/proxy/config/ProxyConfig.java | 46 +++++
+ .../grpc/v2/producer/SendMessageActivity.java | 2 +-
+ .../proxy/processor/ProducerProcessor.java | 18 +-
+ .../service/route/LocalTopicRouteService.java | 2 +-
+ .../service/route/MessageQueueSelector.java | 95 ++++++++-
+ .../proxy/service/route/MessageQueueView.java | 18 +-
+ .../service/route/TopicRouteService.java | 80 +++++++-
+ .../consumer/ReceiveMessageActivityTest.java | 5 +-
+ .../v2/producer/SendMessageActivityTest.java | 82 +++++++-
+ .../proxy/service/BaseServiceTest.java | 4 +-
+ .../route/MessageQueueSelectorTest.java | 8 +-
+ .../sysmessage/HeartbeatSyncerTest.java | 2 +-
+ .../ClusterTransactionServiceTest.java | 8 +-
+ 26 files changed, 919 insertions(+), 176 deletions(-)
+ rename remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/GetRemoteClientConfigBody.java => client/src/main/java/org/apache/rocketmq/client/latency/Resolver.java (65%)
+ create mode 100644 client/src/main/java/org/apache/rocketmq/client/latency/ServiceDetector.java
+
+diff --git a/client/src/main/java/org/apache/rocketmq/client/ClientConfig.java b/client/src/main/java/org/apache/rocketmq/client/ClientConfig.java
+index f87450f66..bb0fe3522 100644
+--- a/client/src/main/java/org/apache/rocketmq/client/ClientConfig.java
++++ b/client/src/main/java/org/apache/rocketmq/client/ClientConfig.java
+@@ -38,6 +38,8 @@ public class ClientConfig {
+ public static final String SOCKS_PROXY_CONFIG = "com.rocketmq.socks.proxy.config";
+ public static final String DECODE_READ_BODY = "com.rocketmq.read.body";
+ public static final String DECODE_DECOMPRESS_BODY = "com.rocketmq.decompress.body";
++ public static final String SEND_LATENCY_ENABLE = "com.rocketmq.sendLatencyEnable";
++ public static final String START_DETECTOR_ENABLE = "com.rocketmq.startDetectorEnable";
+ public static final String HEART_BEAT_V2 = "com.rocketmq.heartbeat.v2";
+ private String namesrvAddr = NameServerAddressUtils.getNameServerAddresses();
+ private String clientIP = NetworkUtil.getLocalAddress();
+@@ -72,6 +74,8 @@ public class ClientConfig {
+ private String socksProxyConfig = System.getProperty(SOCKS_PROXY_CONFIG, "{}");
+
+ private int mqClientApiTimeout = 3 * 1000;
++ private int detectTimeout = 200;
++ private int detectInterval = 2 * 1000;
+
+ private LanguageCode language = LanguageCode.JAVA;
+
+@@ -81,6 +85,15 @@ public class ClientConfig {
+ */
+ protected boolean enableStreamRequestType = false;
+
++ /**
++ * Enable the fault tolerance mechanism of the client sending process.
++ * DO NOT OPEN when ORDER messages are required.
++ * Turning on will interfere with the queue selection functionality,
++ * possibly conflicting with the order message.
++ */
++ private boolean sendLatencyEnable = Boolean.parseBoolean(System.getProperty(SEND_LATENCY_ENABLE, "false"));
++ private boolean startDetectorEnable = Boolean.parseBoolean(System.getProperty(START_DETECTOR_ENABLE, "false"));
++
+ public String buildMQClientId() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(this.getClientIP());
+@@ -186,6 +199,10 @@ public class ClientConfig {
+ this.decodeDecompressBody = cc.decodeDecompressBody;
+ this.enableStreamRequestType = cc.enableStreamRequestType;
+ this.useHeartbeatV2 = cc.useHeartbeatV2;
++ this.startDetectorEnable = cc.startDetectorEnable;
++ this.sendLatencyEnable = cc.sendLatencyEnable;
++ this.detectInterval = cc.detectInterval;
++ this.detectTimeout = cc.detectTimeout;
+ }
+
+ public ClientConfig cloneClientConfig() {
+@@ -210,6 +227,10 @@ public class ClientConfig {
+ cc.decodeDecompressBody = decodeDecompressBody;
+ cc.enableStreamRequestType = enableStreamRequestType;
+ cc.useHeartbeatV2 = useHeartbeatV2;
++ cc.startDetectorEnable = startDetectorEnable;
++ cc.sendLatencyEnable = sendLatencyEnable;
++ cc.detectInterval = detectInterval;
++ cc.detectTimeout = detectTimeout;
+ return cc;
+ }
+
+@@ -381,6 +402,38 @@ public class ClientConfig {
+ this.enableStreamRequestType = enableStreamRequestType;
+ }
+
++ public boolean isSendLatencyEnable() {
++ return sendLatencyEnable;
++ }
++
++ public void setSendLatencyEnable(boolean sendLatencyEnable) {
++ this.sendLatencyEnable = sendLatencyEnable;
++ }
++
++ public boolean isStartDetectorEnable() {
++ return startDetectorEnable;
++ }
++
++ public void setStartDetectorEnable(boolean startDetectorEnable) {
++ this.startDetectorEnable = startDetectorEnable;
++ }
++
++ public int getDetectTimeout() {
++ return this.detectTimeout;
++ }
++
++ public void setDetectTimeout(int detectTimeout) {
++ this.detectTimeout = detectTimeout;
++ }
++
++ public int getDetectInterval() {
++ return this.detectInterval;
++ }
++
++ public void setDetectInterval(int detectInterval) {
++ this.detectInterval = detectInterval;
++ }
++
+ public boolean isUseHeartbeatV2() {
+ return useHeartbeatV2;
+ }
+@@ -403,6 +456,7 @@ public class ClientConfig {
+ + ", socksProxyConfig=" + socksProxyConfig + ", language=" + language.name()
+ + ", namespace=" + namespace + ", mqClientApiTimeout=" + mqClientApiTimeout
+ + ", decodeReadBody=" + decodeReadBody + ", decodeDecompressBody=" + decodeDecompressBody
++ + ", sendLatencyEnable=" + sendLatencyEnable + ", startDetectorEnable=" + startDetectorEnable
+ + ", enableStreamRequestType=" + enableStreamRequestType + ", useHeartbeatV2=" + useHeartbeatV2 + "]";
+ }
+ }
+diff --git a/client/src/main/java/org/apache/rocketmq/client/common/ThreadLocalIndex.java b/client/src/main/java/org/apache/rocketmq/client/common/ThreadLocalIndex.java
+index 4a3d90135..3a086c13d 100644
+--- a/client/src/main/java/org/apache/rocketmq/client/common/ThreadLocalIndex.java
++++ b/client/src/main/java/org/apache/rocketmq/client/common/ThreadLocalIndex.java
+@@ -33,6 +33,14 @@ public class ThreadLocalIndex {
+ return index & POSITIVE_MASK;
+ }
+
++ public void reset() {
++ int index = Math.abs(random.nextInt(Integer.MAX_VALUE));
++ if (index < 0) {
++ index = 0;
++ }
++ this.threadLocalIndex.set(index);
++ }
++
+ @Override
+ public String toString() {
+ return "ThreadLocalIndex{" +
+diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java
+index 213c26fd6..3201a493f 100644
+--- a/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java
++++ b/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java
+@@ -666,7 +666,7 @@ public class MQClientAPIImpl implements NameServerUpdateCallback {
+ } catch (Throwable e) {
+ }
+
+- producer.updateFaultItem(brokerName, System.currentTimeMillis() - responseFuture.getBeginTimestamp(), false);
++ producer.updateFaultItem(brokerName, System.currentTimeMillis() - responseFuture.getBeginTimestamp(), false, true);
+ return;
+ }
+
+@@ -684,14 +684,14 @@ public class MQClientAPIImpl implements NameServerUpdateCallback {
+ } catch (Throwable e) {
+ }
+
+- producer.updateFaultItem(brokerName, System.currentTimeMillis() - responseFuture.getBeginTimestamp(), false);
++ producer.updateFaultItem(brokerName, System.currentTimeMillis() - responseFuture.getBeginTimestamp(), false, true);
+ } catch (Exception e) {
+- producer.updateFaultItem(brokerName, System.currentTimeMillis() - responseFuture.getBeginTimestamp(), true);
++ producer.updateFaultItem(brokerName, System.currentTimeMillis() - responseFuture.getBeginTimestamp(), true, true);
+ onExceptionImpl(brokerName, msg, timeoutMillis - cost, request, sendCallback, topicPublishInfo, instance,
+ retryTimesWhenSendFailed, times, e, context, false, producer);
+ }
+ } else {
+- producer.updateFaultItem(brokerName, System.currentTimeMillis() - responseFuture.getBeginTimestamp(), true);
++ producer.updateFaultItem(brokerName, System.currentTimeMillis() - responseFuture.getBeginTimestamp(), true, true);
+ if (!responseFuture.isSendRequestOK()) {
+ MQClientException ex = new MQClientException("send request failed", responseFuture.getCause());
+ onExceptionImpl(brokerName, msg, timeoutMillis - cost, request, sendCallback, topicPublishInfo, instance,
+@@ -711,7 +711,7 @@ public class MQClientAPIImpl implements NameServerUpdateCallback {
+ });
+ } catch (Exception ex) {
+ long cost = System.currentTimeMillis() - beginStartTime;
+- producer.updateFaultItem(brokerName, cost, true);
++ producer.updateFaultItem(brokerName, cost, true, false);
+ onExceptionImpl(brokerName, msg, timeoutMillis - cost, request, sendCallback, topicPublishInfo, instance,
+ retryTimesWhenSendFailed, times, ex, context, true, producer);
+ }
+@@ -735,7 +735,7 @@ public class MQClientAPIImpl implements NameServerUpdateCallback {
+ if (needRetry && tmp <= timesTotal) {
+ String retryBrokerName = brokerName;//by default, it will send to the same broker
+ if (topicPublishInfo != null) { //select one message queue accordingly, in order to determine which broker to send
+- MessageQueue mqChosen = producer.selectOneMessageQueue(topicPublishInfo, brokerName);
++ MessageQueue mqChosen = producer.selectOneMessageQueue(topicPublishInfo, brokerName, false);
+ retryBrokerName = instance.getBrokerNameFromMessageQueue(mqChosen);
+ }
+ String addr = instance.findBrokerAddressInPublish(retryBrokerName);
+diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/factory/MQClientInstance.java b/client/src/main/java/org/apache/rocketmq/client/impl/factory/MQClientInstance.java
+index 8851bc815..9484b26f8 100644
+--- a/client/src/main/java/org/apache/rocketmq/client/impl/factory/MQClientInstance.java
++++ b/client/src/main/java/org/apache/rocketmq/client/impl/factory/MQClientInstance.java
+@@ -29,6 +29,7 @@ import java.util.concurrent.ConcurrentHashMap;
+ import java.util.concurrent.ConcurrentMap;
+ import java.util.concurrent.Executors;
+ import java.util.concurrent.ScheduledExecutorService;
++import java.util.concurrent.ThreadFactory;
+ import java.util.concurrent.TimeUnit;
+ import java.util.concurrent.atomic.AtomicLong;
+ import java.util.concurrent.locks.Lock;
+@@ -125,6 +126,12 @@ public class MQClientInstance {
+ private final Set<String/* Broker address */> brokerSupportV2HeartbeatSet = new HashSet();
+ private final ConcurrentMap<String, Integer> brokerAddrHeartbeatFingerprintTable = new ConcurrentHashMap();
+ private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(r -> new Thread(r, "MQClientFactoryScheduledThread"));
++ private final ScheduledExecutorService fetchRemoteConfigExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
++ @Override
++ public Thread newThread(Runnable r) {
++ return new Thread(r, "MQClientFactoryFetchRemoteConfigScheduledThread");
++ }
++ });
+ private final PullMessageService pullMessageService;
+ private final RebalanceService rebalanceService;
+ private final DefaultMQProducer defaultMQProducer;
+diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java
+index 3f4c6e5f7..bbbb17b07 100644
+--- a/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java
++++ b/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java
+@@ -33,6 +33,8 @@ import java.util.concurrent.RejectedExecutionException;
+ import java.util.concurrent.Semaphore;
+ import java.util.concurrent.ThreadPoolExecutor;
+ import java.util.concurrent.TimeUnit;
++
++import com.google.common.base.Optional;
+ import org.apache.rocketmq.client.QueryResult;
+ import org.apache.rocketmq.client.Validators;
+ import org.apache.rocketmq.client.common.ClientErrorCode;
+@@ -49,6 +51,8 @@ import org.apache.rocketmq.client.impl.CommunicationMode;
+ import org.apache.rocketmq.client.impl.MQClientManager;
+ import org.apache.rocketmq.client.impl.factory.MQClientInstance;
+ import org.apache.rocketmq.client.latency.MQFaultStrategy;
++import org.apache.rocketmq.client.latency.Resolver;
++import org.apache.rocketmq.client.latency.ServiceDetector;
+ import org.apache.rocketmq.client.producer.DefaultMQProducer;
+ import org.apache.rocketmq.client.producer.LocalTransactionExecuter;
+ import org.apache.rocketmq.client.producer.LocalTransactionState;
+@@ -112,7 +116,7 @@ public class DefaultMQProducerImpl implements MQProducerInner {
+ private ServiceState serviceState = ServiceState.CREATE_JUST;
+ private MQClientInstance mQClientFactory;
+ private ArrayList<CheckForbiddenHook> checkForbiddenHookList = new ArrayList<>();
+- private MQFaultStrategy mqFaultStrategy = new MQFaultStrategy();
++ private MQFaultStrategy mqFaultStrategy;
+ private ExecutorService asyncSenderExecutor;
+
+ // compression related
+@@ -153,8 +157,38 @@ public class DefaultMQProducerImpl implements MQProducerInner {
+ semaphoreAsyncSendSize = new Semaphore(1024 * 1024, true);
+ log.info("semaphoreAsyncSendSize can not be smaller than 1M.");
+ }
+- }
+
++ ServiceDetector serviceDetector = new ServiceDetector() {
++ @Override
++ public boolean detect(String endpoint, long timeoutMillis) {
++ Optional<String> candidateTopic = pickTopic();
++ if (!candidateTopic.isPresent()) {
++ return false;
++ }
++ try {
++ MessageQueue mq = new MessageQueue(candidateTopic.get(), null, 0);
++ mQClientFactory.getMQClientAPIImpl()
++ .getMaxOffset(endpoint, mq, timeoutMillis);
++ return true;
++ } catch (Exception e) {
++ return false;
++ }
++ }
++ };
++
++ this.mqFaultStrategy = new MQFaultStrategy(defaultMQProducer.cloneClientConfig(), new Resolver() {
++ @Override
++ public String resolve(String name) {
++ return DefaultMQProducerImpl.this.mQClientFactory.findBrokerAddressInPublish(name);
++ }
++ }, serviceDetector);
++ }
++ private Optional<String> pickTopic() {
++ if (topicPublishInfoTable.isEmpty()) {
++ return Optional.absent();
++ }
++ return Optional.of(topicPublishInfoTable.keySet().iterator().next());
++ }
+ public void registerCheckForbiddenHook(CheckForbiddenHook checkForbiddenHook) {
+ this.checkForbiddenHookList.add(checkForbiddenHook);
+ log.info("register a new checkForbiddenHook. hookName={}, allHookSize={}", checkForbiddenHook.hookName(),
+@@ -229,6 +263,10 @@ public class DefaultMQProducerImpl implements MQProducerInner {
+ mQClientFactory.start();
+ }
+
++ if (this.mqFaultStrategy.isStartDetectorEnable()) {
++ this.mqFaultStrategy.startDetector();
++ }
++
+ log.info("the producer [{}] start OK. sendMessageWithVIPChannel={}", this.defaultMQProducer.getProducerGroup(),
+ this.defaultMQProducer.isSendMessageWithVIPChannel());
+ this.serviceState = ServiceState.RUNNING;
+@@ -273,6 +311,9 @@ public class DefaultMQProducerImpl implements MQProducerInner {
+ if (shutdownFactory) {
+ this.mQClientFactory.shutdown();
+ }
++ if (this.mqFaultStrategy.isStartDetectorEnable()) {
++ this.mqFaultStrategy.shutdown();
++ }
+ RequestFutureHolder.getInstance().shutdown(this);
+ log.info("the producer [{}] shutdown OK", this.defaultMQProducer.getProducerGroup());
+ this.serviceState = ServiceState.SHUTDOWN_ALREADY;
+@@ -574,7 +615,7 @@ public class DefaultMQProducerImpl implements MQProducerInner {
+ }
+
+ public MessageQueue invokeMessageQueueSelector(Message msg, MessageQueueSelector selector, Object arg,
+- final long timeout) throws MQClientException, RemotingTooMuchRequestException {
++ final long timeout) throws MQClientException, RemotingTooMuchRequestException {
+ long beginStartTime = System.currentTimeMillis();
+ this.makeSureStateOK();
+ Validators.checkMessage(msg, this.defaultMQProducer);
+@@ -584,7 +625,7 @@ public class DefaultMQProducerImpl implements MQProducerInner {
+ MessageQueue mq = null;
+ try {
+ List<MessageQueue> messageQueueList =
+- mQClientFactory.getMQAdminImpl().parsePublishMessageQueues(topicPublishInfo.getMessageQueueList());
++ mQClientFactory.getMQAdminImpl().parsePublishMessageQueues(topicPublishInfo.getMessageQueueList());
+ Message userMessage = MessageAccessor.cloneMessage(msg);
+ String userTopic = NamespaceUtil.withoutNamespace(userMessage.getTopic(), mQClientFactory.getClientConfig().getNamespace());
+ userMessage.setTopic(userTopic);
+@@ -609,12 +650,13 @@ public class DefaultMQProducerImpl implements MQProducerInner {
+ throw new MQClientException("No route info for this topic, " + msg.getTopic(), null);
+ }
+
+- public MessageQueue selectOneMessageQueue(final TopicPublishInfo tpInfo, final String lastBrokerName) {
+- return this.mqFaultStrategy.selectOneMessageQueue(tpInfo, lastBrokerName);
++ public MessageQueue selectOneMessageQueue(final TopicPublishInfo tpInfo, final String lastBrokerName, final boolean resetIndex) {
++ return this.mqFaultStrategy.selectOneMessageQueue(tpInfo, lastBrokerName, resetIndex);
+ }
+
+- public void updateFaultItem(final String brokerName, final long currentLatency, boolean isolation) {
+- this.mqFaultStrategy.updateFaultItem(brokerName, currentLatency, isolation);
++ public void updateFaultItem(final String brokerName, final long currentLatency, boolean isolation,
++ boolean reachable) {
++ this.mqFaultStrategy.updateFaultItem(brokerName, currentLatency, isolation, reachable);
+ }
+
+ private void validateNameServerSetting() throws MQClientException {
+@@ -647,9 +689,13 @@ public class DefaultMQProducerImpl implements MQProducerInner {
+ int timesTotal = communicationMode == CommunicationMode.SYNC ? 1 + this.defaultMQProducer.getRetryTimesWhenSendFailed() : 1;
+ int times = 0;
+ String[] brokersSent = new String[timesTotal];
++ boolean resetIndex = false;
+ for (; times < timesTotal; times++) {
+ String lastBrokerName = null == mq ? null : mq.getBrokerName();
+- MessageQueue mqSelected = this.selectOneMessageQueue(topicPublishInfo, lastBrokerName);
++ if (times > 0) {
++ resetIndex = true;
++ }
++ MessageQueue mqSelected = this.selectOneMessageQueue(topicPublishInfo, lastBrokerName, resetIndex);
+ if (mqSelected != null) {
+ mq = mqSelected;
+ brokersSent[times] = mq.getBrokerName();
+@@ -667,7 +713,7 @@ public class DefaultMQProducerImpl implements MQProducerInner {
+
+ sendResult = this.sendKernelImpl(msg, mq, communicationMode, sendCallback, topicPublishInfo, timeout - costTime);
+ endTimestamp = System.currentTimeMillis();
+- this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, false);
++ this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, false, true);
+ switch (communicationMode) {
+ case ASYNC:
+ return null;
+@@ -684,9 +730,22 @@ public class DefaultMQProducerImpl implements MQProducerInner {
+ default:
+ break;
+ }
+- } catch (RemotingException | MQClientException e) {
++ } catch (MQClientException e) {
+ endTimestamp = System.currentTimeMillis();
+- this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, true);
++ this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, false, true);
++ log.warn("sendKernelImpl exception, resend at once, InvokeID: %s, RT: %sms, Broker: %s", invokeID, endTimestamp - beginTimestampPrev, mq, e);
++ log.warn(msg.toString());
++ exception = e;
++ continue;
++ } catch (RemotingException e) {
++ endTimestamp = System.currentTimeMillis();
++ if (this.mqFaultStrategy.isStartDetectorEnable()) {
++ // Set this broker unreachable when detecting schedule task is running for RemotingException.
++ this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, true, false);
++ } else {
++ // Otherwise, isolate this broker.
++ this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, true, true);
++ }
+ log.warn("sendKernelImpl exception, resend at once, InvokeID: %s, RT: %sms, Broker: %s", invokeID, endTimestamp - beginTimestampPrev, mq, e);
+ if (log.isDebugEnabled()) {
+ log.debug(msg.toString());
+@@ -695,7 +754,7 @@ public class DefaultMQProducerImpl implements MQProducerInner {
+ continue;
+ } catch (MQBrokerException e) {
+ endTimestamp = System.currentTimeMillis();
+- this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, true);
++ this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, true, false);
+ log.warn("sendKernelImpl exception, resend at once, InvokeID: %s, RT: %sms, Broker: %s", invokeID, endTimestamp - beginTimestampPrev, mq, e);
+ if (log.isDebugEnabled()) {
+ log.debug(msg.toString());
+@@ -712,7 +771,7 @@ public class DefaultMQProducerImpl implements MQProducerInner {
+ }
+ } catch (InterruptedException e) {
+ endTimestamp = System.currentTimeMillis();
+- this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, false);
++ this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, false, true);
+ log.warn("sendKernelImpl exception, throw exception, InvokeID: %s, RT: %sms, Broker: %s", invokeID, endTimestamp - beginTimestampPrev, mq, e);
+ if (log.isDebugEnabled()) {
+ log.debug(msg.toString());
+diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/producer/TopicPublishInfo.java b/client/src/main/java/org/apache/rocketmq/client/impl/producer/TopicPublishInfo.java
+index 275ada7ac..37b1f3252 100644
+--- a/client/src/main/java/org/apache/rocketmq/client/impl/producer/TopicPublishInfo.java
++++ b/client/src/main/java/org/apache/rocketmq/client/impl/producer/TopicPublishInfo.java
+@@ -18,6 +18,8 @@ package org.apache.rocketmq.client.impl.producer;
+
+ import java.util.ArrayList;
+ import java.util.List;
++
++import com.google.common.base.Preconditions;
+ import org.apache.rocketmq.client.common.ThreadLocalIndex;
+ import org.apache.rocketmq.common.message.MessageQueue;
+ import org.apache.rocketmq.remoting.protocol.route.QueueData;
+@@ -30,6 +32,10 @@ public class TopicPublishInfo {
+ private volatile ThreadLocalIndex sendWhichQueue = new ThreadLocalIndex();
+ private TopicRouteData topicRouteData;
+
++ public interface QueueFilter {
++ boolean filter(MessageQueue mq);
++ }
++
+ public boolean isOrderTopic() {
+ return orderTopic;
+ }
+@@ -66,6 +72,40 @@ public class TopicPublishInfo {
+ this.haveTopicRouterInfo = haveTopicRouterInfo;
+ }
+
++ public MessageQueue selectOneMessageQueue(QueueFilter ...filter) {
++ return selectOneMessageQueue(this.messageQueueList, this.sendWhichQueue, filter);
++ }
++
++ private MessageQueue selectOneMessageQueue(List<MessageQueue> messageQueueList, ThreadLocalIndex sendQueue, QueueFilter ...filter) {
++ if (messageQueueList == null || messageQueueList.isEmpty()) {
++ return null;
++ }
++
++ if (filter != null && filter.length != 0) {
++ for (int i = 0; i < messageQueueList.size(); i++) {
++ int index = Math.abs(sendQueue.incrementAndGet() % messageQueueList.size());
++ MessageQueue mq = messageQueueList.get(index);
++ boolean filterResult = true;
++ for (QueueFilter f: filter) {
++ Preconditions.checkNotNull(f);
++ filterResult &= f.filter(mq);
++ }
++ if (filterResult) {
++ return mq;
++ }
++ }
++
++ return null;
++ }
++
++ int index = Math.abs(sendQueue.incrementAndGet() % messageQueueList.size());
++ return messageQueueList.get(index);
++ }
++
++ public void resetIndex() {
++ this.sendWhichQueue.reset();
++ }
++
+ public MessageQueue selectOneMessageQueue(final String lastBrokerName) {
+ if (lastBrokerName == null) {
+ return selectOneMessageQueue();
+diff --git a/client/src/main/java/org/apache/rocketmq/client/latency/LatencyFaultTolerance.java b/client/src/main/java/org/apache/rocketmq/client/latency/LatencyFaultTolerance.java
+index 09a8aa461..72d2f3450 100644
+--- a/client/src/main/java/org/apache/rocketmq/client/latency/LatencyFaultTolerance.java
++++ b/client/src/main/java/org/apache/rocketmq/client/latency/LatencyFaultTolerance.java
+@@ -18,11 +18,75 @@
+ package org.apache.rocketmq.client.latency;
+
+ public interface LatencyFaultTolerance<T> {
+- void updateFaultItem(final T name, final long currentLatency, final long notAvailableDuration);
++ /**
++ * Update brokers' states, to decide if they are good or not.
++ *
++ * @param name Broker's name.
++ * @param currentLatency Current message sending process's latency.
++ * @param notAvailableDuration Corresponding not available time, ms. The broker will be not available until it
++ * spends such time.
++ * @param reachable To decide if this broker is reachable or not.
++ */
++ void updateFaultItem(final T name, final long currentLatency, final long notAvailableDuration,
++ final boolean reachable);
+
++ /**
++ * To check if this broker is available.
++ *
++ * @param name Broker's name.
++ * @return boolean variable, if this is true, then the broker is available.
++ */
+ boolean isAvailable(final T name);
+
++ /**
++ * To check if this broker is reachable.
++ *
++ * @param name Broker's name.
++ * @return boolean variable, if this is true, then the broker is reachable.
++ */
++ boolean isReachable(final T name);
++
++ /**
++ * Remove the broker in this fault item table.
++ *
++ * @param name broker's name.
++ */
+ void remove(final T name);
+
++ /**
++ * The worst situation, no broker can be available. Then choose random one.
++ *
++ * @return A random mq will be returned.
++ */
+ T pickOneAtLeast();
++
++ /**
++ * Start a new thread, to detect the broker's reachable tag.
++ */
++ void startDetector();
++
++ /**
++ * Shutdown threads that started by LatencyFaultTolerance.
++ */
++ void shutdown();
++
++ /**
++ * A function reserved, just detect by once, won't create a new thread.
++ */
++ void detectByOneRound();
++
++ /**
++ * Use it to set the detect timeout bound.
++ *
++ * @param detectTimeout timeout bound
++ */
++ void setDetectTimeout(final int detectTimeout);
++
++ /**
++ * Use it to set the detector's detector interval for each broker (each broker will be detected once during this
++ * time)
++ *
++ * @param detectInterval each broker's detecting interval
++ */
++ void setDetectInterval(final int detectInterval);
+ }
+diff --git a/client/src/main/java/org/apache/rocketmq/client/latency/LatencyFaultToleranceImpl.java b/client/src/main/java/org/apache/rocketmq/client/latency/LatencyFaultToleranceImpl.java
+index 93795d957..8af629574 100644
+--- a/client/src/main/java/org/apache/rocketmq/client/latency/LatencyFaultToleranceImpl.java
++++ b/client/src/main/java/org/apache/rocketmq/client/latency/LatencyFaultToleranceImpl.java
+@@ -21,30 +21,97 @@ import java.util.Collections;
+ import java.util.Enumeration;
+ import java.util.LinkedList;
+ import java.util.List;
++import java.util.Map;
+ import java.util.concurrent.ConcurrentHashMap;
++import java.util.concurrent.Executors;
++import java.util.concurrent.ScheduledExecutorService;
++import java.util.concurrent.ThreadFactory;
++import java.util.concurrent.TimeUnit;
+ import org.apache.rocketmq.client.common.ThreadLocalIndex;
++import org.apache.rocketmq.logging.org.slf4j.Logger;
++import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+
+ public class LatencyFaultToleranceImpl implements LatencyFaultTolerance<String> {
+- private final ConcurrentHashMap<String, FaultItem> faultItemTable = new ConcurrentHashMap<>(16);
++ private final static Logger log = LoggerFactory.getLogger(MQFaultStrategy.class);
++ private final ConcurrentHashMap<String, FaultItem> faultItemTable = new ConcurrentHashMap<String, FaultItem>(16);
++ private int detectTimeout = 200;
++ private int detectInterval = 2000;
++ private final ThreadLocalIndex whichItemWorst = new ThreadLocalIndex();
++ private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
++ @Override
++ public Thread newThread(Runnable r) {
++ return new Thread(r, "LatencyFaultToleranceScheduledThread");
++ }
++ });
+
+- private final ThreadLocalIndex randomItem = new ThreadLocalIndex();
++ private final Resolver resolver;
++
++ private final ServiceDetector serviceDetector;
++
++ public LatencyFaultToleranceImpl(Resolver resolver, ServiceDetector serviceDetector) {
++ this.resolver = resolver;
++ this.serviceDetector = serviceDetector;
++ }
++
++ public void detectByOneRound() {
++ for (Map.Entry<String, FaultItem> item : this.faultItemTable.entrySet()) {
++ FaultItem brokerItem = item.getValue();
++ if (System.currentTimeMillis() - brokerItem.checkStamp >= 0) {
++ brokerItem.checkStamp = System.currentTimeMillis() + this.detectInterval;
++ String brokerAddr = resolver.resolve(brokerItem.getName());
++ if (brokerAddr == null) {
++ faultItemTable.remove(item.getKey());
++ continue;
++ }
++ if (null == serviceDetector) {
++ continue;
++ }
++ boolean serviceOK = serviceDetector.detect(brokerAddr, detectTimeout);
++ if (serviceOK && !brokerItem.reachableFlag) {
++ log.info(brokerItem.name + " is reachable now, then it can be used.");
++ brokerItem.reachableFlag = true;
++ }
++ }
++ }
++ }
++
++ public void startDetector() {
++ this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
++ @Override
++ public void run() {
++ try {
++ detectByOneRound();
++ } catch (Exception e) {
++ log.warn("Unexpected exception raised while detecting service reachability", e);
++ }
++ }
++ }, 3, 3, TimeUnit.SECONDS);
++ }
++
++ public void shutdown() {
++ this.scheduledExecutorService.shutdown();
++ }
+
+ @Override
+- public void updateFaultItem(final String name, final long currentLatency, final long notAvailableDuration) {
++ public void updateFaultItem(final String name, final long currentLatency, final long notAvailableDuration,
++ final boolean reachable) {
+ FaultItem old = this.faultItemTable.get(name);
+ if (null == old) {
+ final FaultItem faultItem = new FaultItem(name);
+ faultItem.setCurrentLatency(currentLatency);
+- faultItem.setStartTimestamp(System.currentTimeMillis() + notAvailableDuration);
+-
++ faultItem.updateNotAvailableDuration(notAvailableDuration);
++ faultItem.setReachable(reachable);
+ old = this.faultItemTable.putIfAbsent(name, faultItem);
+- if (old != null) {
+- old.setCurrentLatency(currentLatency);
+- old.setStartTimestamp(System.currentTimeMillis() + notAvailableDuration);
+- }
+- } else {
++ }
++
++ if (null != old) {
+ old.setCurrentLatency(currentLatency);
+- old.setStartTimestamp(System.currentTimeMillis() + notAvailableDuration);
++ old.updateNotAvailableDuration(notAvailableDuration);
++ old.setReachable(reachable);
++ }
++
++ if (!reachable) {
++ log.info(name + " is unreachable, it will not be used until it's reachable");
+ }
+ }
+
+@@ -57,6 +124,14 @@ public class LatencyFaultToleranceImpl implements LatencyFaultTolerance<String>
+ return true;
+ }
+
++ public boolean isReachable(final String name) {
++ final FaultItem faultItem = this.faultItemTable.get(name);
++ if (faultItem != null) {
++ return faultItem.isReachable();
++ }
++ return true;
++ }
++
+ @Override
+ public void remove(final String name) {
+ this.faultItemTable.remove(name);
+@@ -65,68 +140,98 @@ public class LatencyFaultToleranceImpl implements LatencyFaultTolerance<String>
+ @Override
+ public String pickOneAtLeast() {
+ final Enumeration<FaultItem> elements = this.faultItemTable.elements();
+- List<FaultItem> tmpList = new LinkedList<>();
++ List<FaultItem> tmpList = new LinkedList<FaultItem>();
+ while (elements.hasMoreElements()) {
+ final FaultItem faultItem = elements.nextElement();
+ tmpList.add(faultItem);
+ }
++
+ if (!tmpList.isEmpty()) {
+- Collections.sort(tmpList);
+- final int half = tmpList.size() / 2;
+- if (half <= 0) {
+- return tmpList.get(0).getName();
+- } else {
+- final int i = this.randomItem.incrementAndGet() % half;
+- return tmpList.get(i).getName();
++ Collections.shuffle(tmpList);
++ for (FaultItem faultItem : tmpList) {
++ if (faultItem.reachableFlag) {
++ return faultItem.name;
++ }
+ }
+ }
++
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return "LatencyFaultToleranceImpl{" +
+- "faultItemTable=" + faultItemTable +
+- ", whichItemWorst=" + randomItem +
+- '}';
++ "faultItemTable=" + faultItemTable +
++ ", whichItemWorst=" + whichItemWorst +
++ '}';
++ }
++
++ public void setDetectTimeout(final int detectTimeout) {
++ this.detectTimeout = detectTimeout;
+ }
+
+- class FaultItem implements Comparable<FaultItem> {
++ public void setDetectInterval(final int detectInterval) {
++ this.detectInterval = detectInterval;
++ }
++
++ public class FaultItem implements Comparable<FaultItem> {
+ private final String name;
+ private volatile long currentLatency;
+ private volatile long startTimestamp;
++ private volatile long checkStamp;
++ private volatile boolean reachableFlag;
+
+ public FaultItem(final String name) {
+ this.name = name;
+ }
+
++ public void updateNotAvailableDuration(long notAvailableDuration) {
++ if (notAvailableDuration > 0 && System.currentTimeMillis() + notAvailableDuration > this.startTimestamp) {
++ this.startTimestamp = System.currentTimeMillis() + notAvailableDuration;
++ log.info(name + " will be isolated for " + notAvailableDuration + " ms.");
++ }
++ }
++
+ @Override
+ public int compareTo(final FaultItem other) {
+ if (this.isAvailable() != other.isAvailable()) {
+- if (this.isAvailable())
++ if (this.isAvailable()) {
+ return -1;
++ }
+
+- if (other.isAvailable())
++ if (other.isAvailable()) {
+ return 1;
++ }
+ }
+
+- if (this.currentLatency < other.currentLatency)
++ if (this.currentLatency < other.currentLatency) {
+ return -1;
+- else if (this.currentLatency > other.currentLatency) {
++ } else if (this.currentLatency > other.currentLatency) {
+ return 1;
+ }
+
+- if (this.startTimestamp < other.startTimestamp)
++ if (this.startTimestamp < other.startTimestamp) {
+ return -1;
+- else if (this.startTimestamp > other.startTimestamp) {
++ } else if (this.startTimestamp > other.startTimestamp) {
+ return 1;
+ }
+-
+ return 0;
+ }
+
++ public void setReachable(boolean reachableFlag) {
++ this.reachableFlag = reachableFlag;
++ }
++
++ public void setCheckStamp(long checkStamp) {
++ this.checkStamp = checkStamp;
++ }
++
+ public boolean isAvailable() {
+- return (System.currentTimeMillis() - startTimestamp) >= 0;
++ return reachableFlag && System.currentTimeMillis() >= startTimestamp;
++ }
++
++ public boolean isReachable() {
++ return reachableFlag;
+ }
+
+ @Override
+@@ -139,28 +244,32 @@ public class LatencyFaultToleranceImpl implements LatencyFaultTolerance<String>
+
+ @Override
+ public boolean equals(final Object o) {
+- if (this == o)
++ if (this == o) {
+ return true;
+- if (!(o instanceof FaultItem))
++ }
++ if (!(o instanceof FaultItem)) {
+ return false;
++ }
+
+ final FaultItem faultItem = (FaultItem) o;
+
+- if (getCurrentLatency() != faultItem.getCurrentLatency())
++ if (getCurrentLatency() != faultItem.getCurrentLatency()) {
+ return false;
+- if (getStartTimestamp() != faultItem.getStartTimestamp())
++ }
++ if (getStartTimestamp() != faultItem.getStartTimestamp()) {
+ return false;
++ }
+ return getName() != null ? getName().equals(faultItem.getName()) : faultItem.getName() == null;
+-
+ }
+
+ @Override
+ public String toString() {
+ return "FaultItem{" +
+- "name='" + name + '\'' +
+- ", currentLatency=" + currentLatency +
+- ", startTimestamp=" + startTimestamp +
+- '}';
++ "name='" + name + '\'' +
++ ", currentLatency=" + currentLatency +
++ ", startTimestamp=" + startTimestamp +
++ ", reachableFlag=" + reachableFlag +
++ '}';
+ }
+
+ public String getName() {
+diff --git a/client/src/main/java/org/apache/rocketmq/client/latency/MQFaultStrategy.java b/client/src/main/java/org/apache/rocketmq/client/latency/MQFaultStrategy.java
+index 1e1953fad..c01490784 100644
+--- a/client/src/main/java/org/apache/rocketmq/client/latency/MQFaultStrategy.java
++++ b/client/src/main/java/org/apache/rocketmq/client/latency/MQFaultStrategy.java
+@@ -17,25 +17,86 @@
+
+ package org.apache.rocketmq.client.latency;
+
+-import org.apache.commons.lang3.StringUtils;
++import org.apache.rocketmq.client.ClientConfig;
+ import org.apache.rocketmq.client.impl.producer.TopicPublishInfo;
++import org.apache.rocketmq.client.impl.producer.TopicPublishInfo.QueueFilter;
+ import org.apache.rocketmq.common.message.MessageQueue;
+-import org.apache.rocketmq.logging.org.slf4j.Logger;
+-import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+
+ public class MQFaultStrategy {
+- private final static Logger log = LoggerFactory.getLogger(MQFaultStrategy.class);
+- private final LatencyFaultTolerance<String> latencyFaultTolerance = new LatencyFaultToleranceImpl();
++ private LatencyFaultTolerance<String> latencyFaultTolerance;
++ private boolean sendLatencyFaultEnable;
++ private boolean startDetectorEnable;
++ private long[] latencyMax = {50L, 100L, 550L, 1800L, 3000L, 5000L, 15000L};
++ private long[] notAvailableDuration = {0L, 0L, 2000L, 5000L, 6000L, 10000L, 30000L};
+
+- private boolean sendLatencyFaultEnable = false;
++ public static class BrokerFilter implements QueueFilter {
++ private String lastBrokerName;
++
++ public void setLastBrokerName(String lastBrokerName) {
++ this.lastBrokerName = lastBrokerName;
++ }
++
++ @Override public boolean filter(MessageQueue mq) {
++ if (lastBrokerName != null) {
++ return !mq.getBrokerName().equals(lastBrokerName);
++ }
++ return true;
++ }
++ }
++
++ private ThreadLocal<BrokerFilter> threadBrokerFilter = new ThreadLocal<BrokerFilter>() {
++ @Override protected BrokerFilter initialValue() {
++ return new BrokerFilter();
++ }
++ };
++
++ private QueueFilter reachableFilter = new QueueFilter() {
++ @Override public boolean filter(MessageQueue mq) {
++ return latencyFaultTolerance.isReachable(mq.getBrokerName());
++ }
++ };
++
++ private QueueFilter availableFilter = new QueueFilter() {
++ @Override public boolean filter(MessageQueue mq) {
++ return latencyFaultTolerance.isAvailable(mq.getBrokerName());
++ }
++ };
++
++
++ public MQFaultStrategy(ClientConfig cc, Resolver fetcher, ServiceDetector serviceDetector) {
++ this.setStartDetectorEnable(cc.isStartDetectorEnable());
++ this.setSendLatencyFaultEnable(cc.isSendLatencyEnable());
++ this.latencyFaultTolerance = new LatencyFaultToleranceImpl(fetcher, serviceDetector);
++ this.latencyFaultTolerance.setDetectInterval(cc.getDetectInterval());
++ this.latencyFaultTolerance.setDetectTimeout(cc.getDetectTimeout());
++ }
++
++ // For unit test.
++ public MQFaultStrategy(ClientConfig cc, LatencyFaultTolerance<String> tolerance) {
++ this.setStartDetectorEnable(cc.isStartDetectorEnable());
++ this.setSendLatencyFaultEnable(cc.isSendLatencyEnable());
++ this.latencyFaultTolerance = tolerance;
++ this.latencyFaultTolerance.setDetectInterval(cc.getDetectInterval());
++ this.latencyFaultTolerance.setDetectTimeout(cc.getDetectTimeout());
++ }
+
+- private long[] latencyMax = {50L, 100L, 550L, 1000L, 2000L, 3000L, 15000L};
+- private long[] notAvailableDuration = {0L, 0L, 30000L, 60000L, 120000L, 180000L, 600000L};
+
+ public long[] getNotAvailableDuration() {
+ return notAvailableDuration;
+ }
+
++ public QueueFilter getAvailableFilter() {
++ return availableFilter;
++ }
++
++ public QueueFilter getReachableFilter() {
++ return reachableFilter;
++ }
++
++ public ThreadLocal<BrokerFilter> getThreadBrokerFilter() {
++ return threadBrokerFilter;
++ }
++
+ public void setNotAvailableDuration(final long[] notAvailableDuration) {
+ this.notAvailableDuration = notAvailableDuration;
+ }
+@@ -56,51 +117,69 @@ public class MQFaultStrategy {
+ this.sendLatencyFaultEnable = sendLatencyFaultEnable;
+ }
+
+- public MessageQueue selectOneMessageQueue(final TopicPublishInfo tpInfo, final String lastBrokerName) {
++ public boolean isStartDetectorEnable() {
++ return startDetectorEnable;
++ }
++
++ public void setStartDetectorEnable(boolean startDetectorEnable) {
++ this.startDetectorEnable = startDetectorEnable;
++ }
++
++ public void startDetector() {
++ // user should start the detector
++ // and the thread should not be in running state.
++ if (this.sendLatencyFaultEnable && this.startDetectorEnable) {
++ // start the detector.
++ this.latencyFaultTolerance.startDetector();
++ }
++ }
++
++ public void shutdown() {
++ if (this.sendLatencyFaultEnable && this.startDetectorEnable) {
++ this.latencyFaultTolerance.shutdown();
++ }
++ }
++
++ public MessageQueue selectOneMessageQueue(final TopicPublishInfo tpInfo, final String lastBrokerName, final boolean resetIndex) {
++ BrokerFilter brokerFilter = threadBrokerFilter.get();
++ brokerFilter.setLastBrokerName(lastBrokerName);
+ if (this.sendLatencyFaultEnable) {
+- try {
+- int index = tpInfo.getSendWhichQueue().incrementAndGet();
+- for (int i = 0; i < tpInfo.getMessageQueueList().size(); i++) {
+- int pos = index++ % tpInfo.getMessageQueueList().size();
+- MessageQueue mq = tpInfo.getMessageQueueList().get(pos);
+- if (!StringUtils.equals(lastBrokerName, mq.getBrokerName()) && latencyFaultTolerance.isAvailable(mq.getBrokerName())) {
+- return mq;
+- }
+- }
+-
+- final String notBestBroker = latencyFaultTolerance.pickOneAtLeast();
+- int writeQueueNums = tpInfo.getWriteQueueNumsByBroker(notBestBroker);
+- if (writeQueueNums > 0) {
+- final MessageQueue mq = tpInfo.selectOneMessageQueue();
+- if (notBestBroker != null) {
+- mq.setBrokerName(notBestBroker);
+- mq.setQueueId(tpInfo.getSendWhichQueue().incrementAndGet() % writeQueueNums);
+- }
+- return mq;
+- } else {
+- latencyFaultTolerance.remove(notBestBroker);
+- }
+- } catch (Exception e) {
+- log.error("Error occurred when selecting message queue", e);
++ if (resetIndex) {
++ tpInfo.resetIndex();
++ }
++ MessageQueue mq = tpInfo.selectOneMessageQueue(availableFilter, brokerFilter);
++ if (mq != null) {
++ return mq;
++ }
++
++ mq = tpInfo.selectOneMessageQueue(reachableFilter, brokerFilter);
++ if (mq != null) {
++ return mq;
+ }
+
+ return tpInfo.selectOneMessageQueue();
+ }
+
+- return tpInfo.selectOneMessageQueue(lastBrokerName);
++ MessageQueue mq = tpInfo.selectOneMessageQueue(brokerFilter);
++ if (mq != null) {
++ return mq;
++ }
++ return tpInfo.selectOneMessageQueue();
+ }
+
+- public void updateFaultItem(final String brokerName, final long currentLatency, boolean isolation) {
++ public void updateFaultItem(final String brokerName, final long currentLatency, boolean isolation,
++ final boolean reachable) {
+ if (this.sendLatencyFaultEnable) {
+- long duration = computeNotAvailableDuration(isolation ? 30000 : currentLatency);
+- this.latencyFaultTolerance.updateFaultItem(brokerName, currentLatency, duration);
++ long duration = computeNotAvailableDuration(isolation ? 10000 : currentLatency);
++ this.latencyFaultTolerance.updateFaultItem(brokerName, currentLatency, duration, reachable);
+ }
+ }
+
+ private long computeNotAvailableDuration(final long currentLatency) {
+ for (int i = latencyMax.length - 1; i >= 0; i--) {
+- if (currentLatency >= latencyMax[i])
++ if (currentLatency >= latencyMax[i]) {
+ return this.notAvailableDuration[i];
++ }
+ }
+
+ return 0;
+diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/GetRemoteClientConfigBody.java b/client/src/main/java/org/apache/rocketmq/client/latency/Resolver.java
+similarity index 65%
+rename from remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/GetRemoteClientConfigBody.java
+rename to client/src/main/java/org/apache/rocketmq/client/latency/Resolver.java
+index 6aa547047..1c29ba334 100644
+--- a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/body/GetRemoteClientConfigBody.java
++++ b/client/src/main/java/org/apache/rocketmq/client/latency/Resolver.java
+@@ -14,20 +14,9 @@
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-package org.apache.rocketmq.remoting.protocol.body;
++package org.apache.rocketmq.client.latency;
+
+-import java.util.ArrayList;
+-import java.util.List;
+-import org.apache.rocketmq.remoting.protocol.RemotingSerializable;
++public interface Resolver {
+
+-public class GetRemoteClientConfigBody extends RemotingSerializable {
+- private List<String> keys = new ArrayList<>();
+-
+- public List<String> getKeys() {
+- return keys;
+- }
+-
+- public void setKeys(List<String> keys) {
+- this.keys = keys;
+- }
++ String resolve(String name);
+ }
+diff --git a/client/src/main/java/org/apache/rocketmq/client/latency/ServiceDetector.java b/client/src/main/java/org/apache/rocketmq/client/latency/ServiceDetector.java
+new file mode 100644
+index 000000000..c6ffbad1c
+--- /dev/null
++++ b/client/src/main/java/org/apache/rocketmq/client/latency/ServiceDetector.java
+@@ -0,0 +1,30 @@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one or more
++ * contributor license agreements. See the NOTICE file distributed with
++ * this work for additional information regarding copyright ownership.
++ * The ASF licenses this file to You under the Apache License, Version 2.0
++ * (the "License"); you may not use this file except in compliance with
++ * the License. You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++package org.apache.rocketmq.client.latency;
++
++/**
++ * Detect whether the remote service state is normal.
++ */
++public interface ServiceDetector {
++
++ /**
++ * Check if the remote service is normal.
++ * @param endpoint Service endpoint to check against
++ * @return true if the service is back to normal; false otherwise.
++ */
++ boolean detect(String endpoint, long timeoutMillis);
++}
+diff --git a/client/src/test/java/org/apache/rocketmq/client/latency/LatencyFaultToleranceImplTest.java b/client/src/test/java/org/apache/rocketmq/client/latency/LatencyFaultToleranceImplTest.java
+index 86690e40b..42ccdae5a 100644
+--- a/client/src/test/java/org/apache/rocketmq/client/latency/LatencyFaultToleranceImplTest.java
++++ b/client/src/test/java/org/apache/rocketmq/client/latency/LatencyFaultToleranceImplTest.java
+@@ -16,11 +16,14 @@
+ */
+ package org.apache.rocketmq.client.latency;
+
+-import java.util.concurrent.TimeUnit;
++import org.awaitility.core.ThrowingRunnable;
+ import org.junit.Before;
+ import org.junit.Test;
+
++import java.util.concurrent.TimeUnit;
++
+ import static org.assertj.core.api.Assertions.assertThat;
++import static org.awaitility.Awaitility.await;
+
+ public class LatencyFaultToleranceImplTest {
+ private LatencyFaultTolerance<String> latencyFaultTolerance;
+@@ -29,28 +32,31 @@ public class LatencyFaultToleranceImplTest {
+
+ @Before
+ public void init() {
+- latencyFaultTolerance = new LatencyFaultToleranceImpl();
++ latencyFaultTolerance = new LatencyFaultToleranceImpl(null, null);
+ }
+
+ @Test
+ public void testUpdateFaultItem() throws Exception {
+- latencyFaultTolerance.updateFaultItem(brokerName, 3000, 3000);
++ latencyFaultTolerance.updateFaultItem(brokerName, 3000, 3000, true);
+ assertThat(latencyFaultTolerance.isAvailable(brokerName)).isFalse();
+ assertThat(latencyFaultTolerance.isAvailable(anotherBrokerName)).isTrue();
+ }
+
+ @Test
+ public void testIsAvailable() throws Exception {
+- latencyFaultTolerance.updateFaultItem(brokerName, 3000, 50);
++ latencyFaultTolerance.updateFaultItem(brokerName, 3000, 50, true);
+ assertThat(latencyFaultTolerance.isAvailable(brokerName)).isFalse();
+
+- TimeUnit.MILLISECONDS.sleep(70);
+- assertThat(latencyFaultTolerance.isAvailable(brokerName)).isTrue();
++ await().atMost(500, TimeUnit.MILLISECONDS).untilAsserted(new ThrowingRunnable() {
++ @Override public void run() throws Throwable {
++ assertThat(latencyFaultTolerance.isAvailable(brokerName)).isTrue();
++ }
++ });
+ }
+
+ @Test
+ public void testRemove() throws Exception {
+- latencyFaultTolerance.updateFaultItem(brokerName, 3000, 3000);
++ latencyFaultTolerance.updateFaultItem(brokerName, 3000, 3000, true);
+ assertThat(latencyFaultTolerance.isAvailable(brokerName)).isFalse();
+ latencyFaultTolerance.remove(brokerName);
+ assertThat(latencyFaultTolerance.isAvailable(brokerName)).isTrue();
+@@ -58,10 +64,20 @@ public class LatencyFaultToleranceImplTest {
+
+ @Test
+ public void testPickOneAtLeast() throws Exception {
+- latencyFaultTolerance.updateFaultItem(brokerName, 1000, 3000);
++ latencyFaultTolerance.updateFaultItem(brokerName, 1000, 3000, true);
+ assertThat(latencyFaultTolerance.pickOneAtLeast()).isEqualTo(brokerName);
+
+- latencyFaultTolerance.updateFaultItem(anotherBrokerName, 1001, 3000);
+- assertThat(latencyFaultTolerance.pickOneAtLeast()).isEqualTo(brokerName);
++ // Bad case, since pickOneAtLeast's behavior becomes random
++ // latencyFaultTolerance.updateFaultItem(anotherBrokerName, 1001, 3000, "127.0.0.1:12011", true);
++ // assertThat(latencyFaultTolerance.pickOneAtLeast()).isEqualTo(brokerName);
++ }
++
++ @Test
++ public void testIsReachable() throws Exception {
++ latencyFaultTolerance.updateFaultItem(brokerName, 1000, 3000, true);
++ assertThat(latencyFaultTolerance.isReachable(brokerName)).isEqualTo(true);
++
++ latencyFaultTolerance.updateFaultItem(anotherBrokerName, 1001, 3000, false);
++ assertThat(latencyFaultTolerance.isReachable(anotherBrokerName)).isEqualTo(false);
+ }
+ }
+\ No newline at end of file
+diff --git a/namesrv/src/main/java/org/apache/rocketmq/namesrv/processor/DefaultRequestProcessor.java b/namesrv/src/main/java/org/apache/rocketmq/namesrv/processor/DefaultRequestProcessor.java
+index fada0efd7..485b95c42 100644
+--- a/namesrv/src/main/java/org/apache/rocketmq/namesrv/processor/DefaultRequestProcessor.java
++++ b/namesrv/src/main/java/org/apache/rocketmq/namesrv/processor/DefaultRequestProcessor.java
+@@ -41,7 +41,6 @@ import org.apache.rocketmq.remoting.protocol.RequestCode;
+ import org.apache.rocketmq.remoting.protocol.ResponseCode;
+ import org.apache.rocketmq.remoting.protocol.body.BrokerMemberGroup;
+ import org.apache.rocketmq.remoting.protocol.body.GetBrokerMemberGroupResponseBody;
+-import org.apache.rocketmq.remoting.protocol.body.GetRemoteClientConfigBody;
+ import org.apache.rocketmq.remoting.protocol.body.RegisterBrokerBody;
+ import org.apache.rocketmq.remoting.protocol.body.TopicConfigSerializeWrapper;
+ import org.apache.rocketmq.remoting.protocol.body.TopicList;
+@@ -132,8 +131,6 @@ public class DefaultRequestProcessor implements NettyRequestProcessor {
+ return this.updateConfig(ctx, request);
+ case RequestCode.GET_NAMESRV_CONFIG:
+ return this.getConfig(ctx, request);
+- case RequestCode.GET_CLIENT_CONFIG:
+- return this.getClientConfigs(ctx, request);
+ default:
+ String error = " request type " + request.getCode() + " not supported";
+ return RemotingCommand.createResponseCommand(RemotingSysResponseCode.REQUEST_CODE_NOT_SUPPORTED, error);
+@@ -661,25 +658,4 @@ public class DefaultRequestProcessor implements NettyRequestProcessor {
+ return response;
+ }
+
+- private RemotingCommand getClientConfigs(ChannelHandlerContext ctx, RemotingCommand request) {
+- final RemotingCommand response = RemotingCommand.createResponseCommand(null);
+- final GetRemoteClientConfigBody body = GetRemoteClientConfigBody.decode(request.getBody(), GetRemoteClientConfigBody.class);
+-
+- String content = this.namesrvController.getConfiguration().getClientConfigsFormatString(body.getKeys());
+- if (StringUtils.isNotBlank(content)) {
+- try {
+- response.setBody(content.getBytes(MixAll.DEFAULT_CHARSET));
+- } catch (UnsupportedEncodingException e) {
+- log.error("getConfig error, ", e);
+- response.setCode(ResponseCode.SYSTEM_ERROR);
+- response.setRemark("UnsupportedEncodingException " + e);
+- return response;
+- }
+- }
+-
+- response.setCode(ResponseCode.SUCCESS);
+- response.setRemark(null);
+- return response;
+- }
+-
+ }
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java b/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java
+index 2994893d7..b2478fec3 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java
+@@ -232,6 +232,12 @@ public class ProxyConfig implements ConfigFile {
+ private String remotingAccessAddr = "";
+ private int remotingListenPort = 8080;
+
++ // related to proxy's send strategy in cluster mode.
++ private boolean sendLatencyEnable = false;
++ private boolean startDetectorEnable = false;
++ private int detectTimeout = 200;
++ private int detectInterval = 2 * 1000;
++
+ private int remotingHeartbeatThreadPoolNums = 2 * PROCESSOR_NUMBER;
+ private int remotingTopicRouteThreadPoolNums = 2 * PROCESSOR_NUMBER;
+ private int remotingSendMessageThreadPoolNums = 4 * PROCESSOR_NUMBER;
+@@ -1409,6 +1415,46 @@ public class ProxyConfig implements ConfigFile {
+ this.remotingWaitTimeMillsInDefaultQueue = remotingWaitTimeMillsInDefaultQueue;
+ }
+
++ public boolean isSendLatencyEnable() {
++ return sendLatencyEnable;
++ }
++
++ public boolean isStartDetectorEnable() {
++ return startDetectorEnable;
++ }
++
++ public void setStartDetectorEnable(boolean startDetectorEnable) {
++ this.startDetectorEnable = startDetectorEnable;
++ }
++
++ public void setSendLatencyEnable(boolean sendLatencyEnable) {
++ this.sendLatencyEnable = sendLatencyEnable;
++ }
++
++ public boolean getStartDetectorEnable() {
++ return this.startDetectorEnable;
++ }
++
++ public boolean getSendLatencyEnable() {
++ return this.sendLatencyEnable;
++ }
++
++ public int getDetectTimeout() {
++ return detectTimeout;
++ }
++
++ public void setDetectTimeout(int detectTimeout) {
++ this.detectTimeout = detectTimeout;
++ }
++
++ public int getDetectInterval() {
++ return detectInterval;
++ }
++
++ public void setDetectInterval(int detectInterval) {
++ this.detectInterval = detectInterval;
++ }
++
+ public boolean isEnableBatchAck() {
+ return enableBatchAck;
+ }
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/producer/SendMessageActivity.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/producer/SendMessageActivity.java
+index 6146c80cd..f670df205 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/producer/SendMessageActivity.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/producer/SendMessageActivity.java
+@@ -382,7 +382,7 @@ public class SendMessageActivity extends AbstractMessingActivity {
+ int bucket = Hashing.consistentHash(shardingKey.hashCode(), writeQueues.size());
+ targetMessageQueue = writeQueues.get(bucket);
+ } else {
+- targetMessageQueue = messageQueueView.getWriteSelector().selectOne(false);
++ targetMessageQueue = messageQueueView.getWriteSelector().selectOneByPipeline(false);
+ }
+ return targetMessageQueue;
+ } catch (Exception e) {
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/ProducerProcessor.java b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/ProducerProcessor.java
+index 0d0c62168..a80f6df0b 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/processor/ProducerProcessor.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/processor/ProducerProcessor.java
+@@ -19,6 +19,7 @@ package org.apache.rocketmq.proxy.processor;
+ import java.util.List;
+ import java.util.concurrent.CompletableFuture;
+ import java.util.concurrent.ExecutorService;
++
+ import org.apache.commons.lang3.StringUtils;
+ import org.apache.rocketmq.client.producer.SendResult;
+ import org.apache.rocketmq.client.producer.SendStatus;
+@@ -66,6 +67,8 @@ public class ProducerProcessor extends AbstractProcessor {
+ public CompletableFuture<List<SendResult>> sendMessage(ProxyContext ctx, QueueSelector queueSelector,
+ String producerGroup, int sysFlag, List<Message> messageList, long timeoutMillis) {
+ CompletableFuture<List<SendResult>> future = new CompletableFuture<>();
++ long beginTimestampFirst = System.currentTimeMillis();
++ AddressableMessageQueue messageQueue = null;
+ try {
+ Message message = messageList.get(0);
+ String topic = message.getTopic();
+@@ -79,7 +82,7 @@ public class ProducerProcessor extends AbstractProcessor {
+ }
+ }
+ }
+- AddressableMessageQueue messageQueue = queueSelector.select(ctx,
++ messageQueue = queueSelector.select(ctx,
+ this.serviceManager.getTopicRouteService().getCurrentMessageQueueView(ctx, topic));
+ if (messageQueue == null) {
+ throw new ProxyException(ProxyExceptionCode.FORBIDDEN, "no writable queue");
+@@ -90,6 +93,7 @@ public class ProducerProcessor extends AbstractProcessor {
+ }
+ SendMessageRequestHeader requestHeader = buildSendMessageRequestHeader(messageList, producerGroup, sysFlag, messageQueue.getQueueId());
+
++ AddressableMessageQueue finalMessageQueue = messageQueue;
+ future = this.serviceManager.getMessageService().sendMessage(
+ ctx,
+ messageQueue,
+@@ -102,11 +106,19 @@ public class ProducerProcessor extends AbstractProcessor {
+ if (SendStatus.SEND_OK.equals(sendResult.getSendStatus()) &&
+ tranType == MessageSysFlag.TRANSACTION_PREPARED_TYPE &&
+ StringUtils.isNotBlank(sendResult.getTransactionId())) {
+- fillTransactionData(ctx, producerGroup, messageQueue, sendResult, messageList);
++ fillTransactionData(ctx, producerGroup, finalMessageQueue, sendResult, messageList);
+ }
+ }
+ return sendResultList;
+- }, this.executor);
++ }, this.executor)
++ .whenComplete((result, exception) -> {
++ long endTimestamp = System.currentTimeMillis();
++ if (exception != null) {
++ this.serviceManager.getTopicRouteService().updateFaultItem(finalMessageQueue.getBrokerName(), endTimestamp - beginTimestampFirst, true, false);
++ } else {
++ this.serviceManager.getTopicRouteService().updateFaultItem(finalMessageQueue.getBrokerName(),endTimestamp - beginTimestampFirst, false, true);
++ }
++ });
+ } catch (Throwable t) {
+ future.completeExceptionally(t);
+ }
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/LocalTopicRouteService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/LocalTopicRouteService.java
+index d67b68f38..aced15cee 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/LocalTopicRouteService.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/LocalTopicRouteService.java
+@@ -54,7 +54,7 @@ public class LocalTopicRouteService extends TopicRouteService {
+ @Override
+ public MessageQueueView getCurrentMessageQueueView(ProxyContext ctx, String topic) throws Exception {
+ TopicConfig topicConfig = this.brokerController.getTopicConfigManager().getTopicConfigTable().get(topic);
+- return new MessageQueueView(topic, toTopicRouteData(topicConfig));
++ return new MessageQueueView(topic, toTopicRouteData(topicConfig), null);
+ }
+
+ @Override
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/MessageQueueSelector.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/MessageQueueSelector.java
+index 85cd18d45..f25fb907e 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/MessageQueueSelector.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/MessageQueueSelector.java
+@@ -17,6 +17,7 @@
+ package org.apache.rocketmq.proxy.service.route;
+
+ import com.google.common.base.MoreObjects;
++import com.google.common.base.Preconditions;
+ import com.google.common.math.IntMath;
+ import java.util.ArrayList;
+ import java.util.Collections;
+@@ -30,6 +31,8 @@ import java.util.concurrent.ConcurrentHashMap;
+ import java.util.concurrent.atomic.AtomicInteger;
+ import java.util.stream.Collectors;
+ import org.apache.commons.lang3.StringUtils;
++import org.apache.rocketmq.client.impl.producer.TopicPublishInfo;
++import org.apache.rocketmq.client.latency.MQFaultStrategy;
+ import org.apache.rocketmq.common.constant.PermName;
+ import org.apache.rocketmq.common.message.MessageQueue;
+ import org.apache.rocketmq.remoting.protocol.route.QueueData;
+@@ -44,8 +47,9 @@ public class MessageQueueSelector {
+ private final Map<String, AddressableMessageQueue> brokerNameQueueMap = new ConcurrentHashMap<>();
+ private final AtomicInteger queueIndex;
+ private final AtomicInteger brokerIndex;
++ private MQFaultStrategy mqFaultStrategy;
+
+- public MessageQueueSelector(TopicRouteWrapper topicRouteWrapper, boolean read) {
++ public MessageQueueSelector(TopicRouteWrapper topicRouteWrapper, MQFaultStrategy mqFaultStrategy, boolean read) {
+ if (read) {
+ this.queues.addAll(buildRead(topicRouteWrapper));
+ } else {
+@@ -55,6 +59,7 @@ public class MessageQueueSelector {
+ Random random = new Random();
+ this.queueIndex = new AtomicInteger(random.nextInt());
+ this.brokerIndex = new AtomicInteger(random.nextInt());
++ this.mqFaultStrategy = mqFaultStrategy;
+ }
+
+ private static List<AddressableMessageQueue> buildRead(TopicRouteWrapper topicRoute) {
+@@ -154,6 +159,86 @@ public class MessageQueueSelector {
+ return selectOneByIndex(nextIndex, onlyBroker);
+ }
+
++ public AddressableMessageQueue selectOneByPipeline(boolean onlyBroker) {
++ if (mqFaultStrategy != null && mqFaultStrategy.isSendLatencyFaultEnable()) {
++ List<MessageQueue> messageQueueList = null;
++ MessageQueue messageQueue = null;
++ if (onlyBroker) {
++ messageQueueList = transferAddressableQueues(brokerActingQueues);
++ } else {
++ messageQueueList = transferAddressableQueues(queues);
++ }
++ AddressableMessageQueue addressableMessageQueue = null;
++
++ // use both available filter.
++ messageQueue = selectOneMessageQueue(messageQueueList, onlyBroker ? brokerIndex : queueIndex,
++ mqFaultStrategy.getAvailableFilter(), mqFaultStrategy.getReachableFilter());
++ addressableMessageQueue = transferQueue2Addressable(messageQueue);
++ if (addressableMessageQueue != null) {
++ return addressableMessageQueue;
++ }
++
++ // use available filter.
++ messageQueue = selectOneMessageQueue(messageQueueList, onlyBroker ? brokerIndex : queueIndex,
++ mqFaultStrategy.getAvailableFilter());
++ addressableMessageQueue = transferQueue2Addressable(messageQueue);
++ if (addressableMessageQueue != null) {
++ return addressableMessageQueue;
++ }
++
++ // no available filter, then use reachable filter.
++ messageQueue = selectOneMessageQueue(messageQueueList, onlyBroker ? brokerIndex : queueIndex,
++ mqFaultStrategy.getReachableFilter());
++ addressableMessageQueue = transferQueue2Addressable(messageQueue);
++ if (addressableMessageQueue != null) {
++ return addressableMessageQueue;
++ }
++ }
++
++ // SendLatency is not enabled, or no queue is selected, then select by index.
++ return selectOne(onlyBroker);
++ }
++
++ private MessageQueue selectOneMessageQueue(List<MessageQueue> messageQueueList, AtomicInteger sendQueue, TopicPublishInfo.QueueFilter...filter) {
++ if (messageQueueList == null || messageQueueList.isEmpty()) {
++ return null;
++ }
++ if (filter != null && filter.length != 0) {
++ for (int i = 0; i < messageQueueList.size(); i++) {
++ int index = Math.abs(sendQueue.incrementAndGet() % messageQueueList.size());
++ MessageQueue mq = messageQueueList.get(index);
++ boolean filterResult = true;
++ for (TopicPublishInfo.QueueFilter f: filter) {
++ Preconditions.checkNotNull(f);
++ filterResult &= f.filter(mq);
++ }
++ if (filterResult) {
++ return mq;
++ }
++ }
++ }
++ return null;
++ }
++
++ public List<MessageQueue> transferAddressableQueues(List<AddressableMessageQueue> addressableMessageQueueList) {
++ if (addressableMessageQueueList == null) {
++ return null;
++ }
++
++ return addressableMessageQueueList.stream()
++ .map(AddressableMessageQueue::getMessageQueue)
++ .collect(Collectors.toList());
++ }
++
++ private AddressableMessageQueue transferQueue2Addressable(MessageQueue messageQueue) {
++ for (AddressableMessageQueue amq: queues) {
++ if (amq.getMessageQueue().equals(messageQueue)) {
++ return amq;
++ }
++ }
++ return null;
++ }
++
+ public AddressableMessageQueue selectNextOne(AddressableMessageQueue last) {
+ boolean onlyBroker = last.getQueueId() < 0;
+ AddressableMessageQueue newOne = last;
+@@ -190,6 +275,14 @@ public class MessageQueueSelector {
+ return brokerActingQueues;
+ }
+
++ public MQFaultStrategy getMQFaultStrategy() {
++ return mqFaultStrategy;
++ }
++
++ public void setMQFaultStrategy(MQFaultStrategy mqFaultStrategy) {
++ this.mqFaultStrategy = mqFaultStrategy;
++ }
++
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/MessageQueueView.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/MessageQueueView.java
+index fe5387cfd..8b3c2f7c8 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/MessageQueueView.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/MessageQueueView.java
+@@ -17,20 +17,22 @@
+ package org.apache.rocketmq.proxy.service.route;
+
+ import com.google.common.base.MoreObjects;
++import org.apache.rocketmq.client.latency.MQFaultStrategy;
+ import org.apache.rocketmq.remoting.protocol.route.TopicRouteData;
+
+ public class MessageQueueView {
+- public static final MessageQueueView WRAPPED_EMPTY_QUEUE = new MessageQueueView("", new TopicRouteData());
++ public static final MessageQueueView WRAPPED_EMPTY_QUEUE = new MessageQueueView("", new TopicRouteData(), null);
+
+ private final MessageQueueSelector readSelector;
+ private final MessageQueueSelector writeSelector;
+ private final TopicRouteWrapper topicRouteWrapper;
++ private MQFaultStrategy mqFaultStrategy;
+
+- public MessageQueueView(String topic, TopicRouteData topicRouteData) {
++ public MessageQueueView(String topic, TopicRouteData topicRouteData, MQFaultStrategy mqFaultStrategy) {
+ this.topicRouteWrapper = new TopicRouteWrapper(topicRouteData, topic);
+
+- this.readSelector = new MessageQueueSelector(topicRouteWrapper, true);
+- this.writeSelector = new MessageQueueSelector(topicRouteWrapper, false);
++ this.readSelector = new MessageQueueSelector(topicRouteWrapper, mqFaultStrategy, true);
++ this.writeSelector = new MessageQueueSelector(topicRouteWrapper, mqFaultStrategy, false);
+ }
+
+ public TopicRouteData getTopicRouteData() {
+@@ -65,4 +67,12 @@ public class MessageQueueView {
+ .add("topicRouteWrapper", topicRouteWrapper)
+ .toString();
+ }
++
++ public MQFaultStrategy getMQFaultStrategy() {
++ return mqFaultStrategy;
++ }
++
++ public void setMQFaultStrategy(MQFaultStrategy mqFaultStrategy) {
++ this.mqFaultStrategy = mqFaultStrategy;
++ }
+ }
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java
+index 84348adc3..74769a423 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java
+@@ -25,7 +25,13 @@ import java.util.concurrent.Executors;
+ import java.util.concurrent.ScheduledExecutorService;
+ import java.util.concurrent.ThreadPoolExecutor;
+ import java.util.concurrent.TimeUnit;
++
++import com.google.common.base.Optional;
++import org.apache.rocketmq.client.ClientConfig;
+ import org.apache.rocketmq.client.exception.MQClientException;
++import org.apache.rocketmq.client.latency.MQFaultStrategy;
++import org.apache.rocketmq.client.latency.Resolver;
++import org.apache.rocketmq.client.latency.ServiceDetector;
+ import org.apache.rocketmq.client.impl.mqclient.MQClientAPIFactory;
+ import org.apache.rocketmq.common.ThreadFactoryImpl;
+ import org.apache.rocketmq.common.constant.LoggerName;
+@@ -39,6 +45,7 @@ import org.apache.rocketmq.proxy.common.ProxyContext;
+ import org.apache.rocketmq.proxy.config.ConfigurationManager;
+ import org.apache.rocketmq.proxy.config.ProxyConfig;
+ import org.apache.rocketmq.remoting.protocol.ResponseCode;
++import org.apache.rocketmq.remoting.protocol.header.GetMaxOffsetRequestHeader;
+ import org.apache.rocketmq.remoting.protocol.route.TopicRouteData;
+ import org.checkerframework.checker.nullness.qual.NonNull;
+ import org.checkerframework.checker.nullness.qual.Nullable;
+@@ -47,6 +54,7 @@ public abstract class TopicRouteService extends AbstractStartAndShutdown {
+ private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME);
+
+ private final MQClientAPIFactory mqClientAPIFactory;
++ private MQFaultStrategy mqFaultStrategy;
+
+ protected final LoadingCache<String /* topicName */, MessageQueueView> topicCache;
+ protected final ScheduledExecutorService scheduledExecutorService;
+@@ -97,15 +105,83 @@ public abstract class TopicRouteService extends AbstractStartAndShutdown {
+ }
+ }
+ });
+-
++ ServiceDetector serviceDetector = new ServiceDetector() {
++ @Override
++ public boolean detect(String endpoint, long timeoutMillis) {
++ Optional<String> candidateTopic = pickTopic();
++ if (!candidateTopic.isPresent()) {
++ return false;
++ }
++ try {
++ GetMaxOffsetRequestHeader requestHeader = new GetMaxOffsetRequestHeader();
++ requestHeader.setTopic(candidateTopic.get());
++ requestHeader.setQueueId(0);
++ Long maxOffset = mqClientAPIFactory.getClient().getMaxOffset(endpoint, requestHeader, timeoutMillis).get();
++ return true;
++ } catch (Exception e) {
++ return false;
++ }
++ }
++ };
++ mqFaultStrategy = new MQFaultStrategy(extractClientConfigFromProxyConfig(config), new Resolver() {
++ @Override
++ public String resolve(String name) {
++ try {
++ String brokerAddr = getBrokerAddr(null, name);
++ return brokerAddr;
++ } catch (Exception e) {
++ return null;
++ }
++ }
++ }, serviceDetector);
+ this.init();
+ }
+
++ // pickup one topic in the topic cache
++ private Optional<String> pickTopic() {
++ if (topicCache.asMap().isEmpty()) {
++ return Optional.absent();
++ }
++ return Optional.of(topicCache.asMap().keySet().iterator().next());
++ }
++
+ protected void init() {
+ this.appendShutdown(this.scheduledExecutorService::shutdown);
+ this.appendStartAndShutdown(this.mqClientAPIFactory);
+ }
+
++ @Override
++ public void shutdown() throws Exception {
++ if (this.mqFaultStrategy.isStartDetectorEnable()) {
++ mqFaultStrategy.shutdown();
++ }
++ }
++
++ @Override
++ public void start() throws Exception {
++ if (this.mqFaultStrategy.isStartDetectorEnable()) {
++ this.mqFaultStrategy.startDetector();
++ }
++ }
++
++ public ClientConfig extractClientConfigFromProxyConfig(ProxyConfig proxyConfig) {
++ ClientConfig tempClientConfig = new ClientConfig();
++ tempClientConfig.setSendLatencyEnable(proxyConfig.getSendLatencyEnable());
++ tempClientConfig.setStartDetectorEnable(proxyConfig.getStartDetectorEnable());
++ tempClientConfig.setDetectTimeout(proxyConfig.getDetectTimeout());
++ tempClientConfig.setDetectInterval(proxyConfig.getDetectInterval());
++ return tempClientConfig;
++ }
++
++ public void updateFaultItem(final String brokerName, final long currentLatency, boolean isolation,
++ boolean reachable) {
++ this.mqFaultStrategy.updateFaultItem(brokerName, currentLatency, isolation, reachable);
++ }
++
++ public MQFaultStrategy getMqFaultStrategy() {
++ return this.mqFaultStrategy;
++ }
++
+ public MessageQueueView getAllMessageQueueView(ProxyContext ctx, String topicName) throws Exception {
+ return getCacheMessageQueueWrapper(this.topicCache, topicName);
+ }
+@@ -136,7 +212,7 @@ public abstract class TopicRouteService extends AbstractStartAndShutdown {
+
+ protected MessageQueueView buildMessageQueueView(String topic, TopicRouteData topicRouteData) {
+ if (isTopicRouteValid(topicRouteData)) {
+- MessageQueueView tmp = new MessageQueueView(topic, topicRouteData);
++ MessageQueueView tmp = new MessageQueueView(topic, topicRouteData, TopicRouteService.this.getMqFaultStrategy());
+ log.debug("load topic route from namesrv. topic: {}, queue: {}", topic, tmp);
+ return tmp;
+ }
+diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ReceiveMessageActivityTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ReceiveMessageActivityTest.java
+index 7fd9a9ffd..77ae5e4d1 100644
+--- a/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ReceiveMessageActivityTest.java
++++ b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/consumer/ReceiveMessageActivityTest.java
+@@ -93,7 +93,6 @@ public class ReceiveMessageActivityTest extends BaseActivityTest {
+ pollTimeCaptor.capture(), anyInt(), any(), anyBoolean(), any(), isNull(), anyLong()))
+ .thenReturn(CompletableFuture.completedFuture(new PopResult(PopStatus.NO_NEW_MSG, Collections.emptyList())));
+
+-
+ ProxyContext context = createContext();
+ context.setRemainingMs(1L);
+ this.receiveMessageActivity.receiveMessage(
+@@ -274,7 +273,7 @@ public class ReceiveMessageActivityTest extends BaseActivityTest {
+ }
+
+ @Test
+- public void testReceiveMessageQueueSelector() {
++ public void testReceiveMessageQueueSelector() throws Exception {
+ TopicRouteData topicRouteData = new TopicRouteData();
+ List<QueueData> queueDatas = new ArrayList<>();
+ for (int i = 0; i < 2; i++) {
+@@ -298,7 +297,7 @@ public class ReceiveMessageActivityTest extends BaseActivityTest {
+ }
+ topicRouteData.setBrokerDatas(brokerDatas);
+
+- MessageQueueView messageQueueView = new MessageQueueView(TOPIC, topicRouteData);
++ MessageQueueView messageQueueView = new MessageQueueView(TOPIC, topicRouteData, null);
+ ReceiveMessageActivity.ReceiveMessageQueueSelector selector = new ReceiveMessageActivity.ReceiveMessageQueueSelector("");
+
+ AddressableMessageQueue firstSelect = selector.select(ProxyContext.create(), messageQueueView);
+diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/producer/SendMessageActivityTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/producer/SendMessageActivityTest.java
+index 588423bb9..4882a5ed8 100644
+--- a/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/producer/SendMessageActivityTest.java
++++ b/proxy/src/test/java/org/apache/rocketmq/proxy/grpc/v2/producer/SendMessageActivityTest.java
+@@ -35,6 +35,8 @@ import java.util.concurrent.CompletableFuture;
+ import java.util.concurrent.ExecutionException;
+ import java.util.concurrent.TimeUnit;
+ import org.apache.commons.lang3.StringUtils;
++import org.apache.rocketmq.client.ClientConfig;
++import org.apache.rocketmq.client.latency.MQFaultStrategy;
+ import org.apache.rocketmq.client.producer.SendResult;
+ import org.apache.rocketmq.client.producer.SendStatus;
+ import org.apache.rocketmq.common.MixAll;
+@@ -49,6 +51,7 @@ import org.apache.rocketmq.proxy.grpc.v2.BaseActivityTest;
+ import org.apache.rocketmq.proxy.grpc.v2.common.GrpcProxyException;
+ import org.apache.rocketmq.proxy.service.route.AddressableMessageQueue;
+ import org.apache.rocketmq.proxy.service.route.MessageQueueView;
++import org.apache.rocketmq.proxy.service.route.TopicRouteService;
+ import org.apache.rocketmq.remoting.protocol.route.BrokerData;
+ import org.apache.rocketmq.remoting.protocol.route.QueueData;
+ import org.apache.rocketmq.remoting.protocol.route.TopicRouteData;
+@@ -62,15 +65,19 @@ import static org.junit.Assert.assertThrows;
+ import static org.mockito.ArgumentMatchers.any;
+ import static org.mockito.ArgumentMatchers.anyInt;
+ import static org.mockito.ArgumentMatchers.anyString;
++import static org.mockito.Mockito.mock;
+ import static org.mockito.Mockito.when;
+
+ public class SendMessageActivityTest extends BaseActivityTest {
+
+ protected static final String BROKER_NAME = "broker";
++ protected static final String BROKER_NAME2 = "broker2";
+ protected static final String CLUSTER_NAME = "cluster";
+ protected static final String BROKER_ADDR = "127.0.0.1:10911";
++ protected static final String BROKER_ADDR2 = "127.0.0.1:10912";
+ private static final String TOPIC = "topic";
+ private static final String CONSUMER_GROUP = "consumerGroup";
++ MQFaultStrategy mqFaultStrategy;
+
+ private SendMessageActivity sendMessageActivity;
+
+@@ -262,7 +269,7 @@ public class SendMessageActivityTest extends BaseActivityTest {
+ }
+
+ @Test
+- public void testSendOrderMessageQueueSelector() {
++ public void testSendOrderMessageQueueSelector() throws Exception {
+ TopicRouteData topicRouteData = new TopicRouteData();
+ QueueData queueData = new QueueData();
+ BrokerData brokerData = new BrokerData();
+@@ -277,7 +284,7 @@ public class SendMessageActivityTest extends BaseActivityTest {
+ brokerData.setBrokerAddrs(brokerAddrs);
+ topicRouteData.setBrokerDatas(Lists.newArrayList(brokerData));
+
+- MessageQueueView messageQueueView = new MessageQueueView(TOPIC, topicRouteData);
++ MessageQueueView messageQueueView = new MessageQueueView(TOPIC, topicRouteData, null);
+ SendMessageActivity.SendMessageQueueSelector selector1 = new SendMessageActivity.SendMessageQueueSelector(
+ SendMessageRequest.newBuilder()
+ .addMessages(Message.newBuilder()
+@@ -288,6 +295,12 @@ public class SendMessageActivityTest extends BaseActivityTest {
+ .build()
+ );
+
++ TopicRouteService topicRouteService = mock(TopicRouteService.class);
++ MQFaultStrategy mqFaultStrategy = mock(MQFaultStrategy.class);
++ when(topicRouteService.getAllMessageQueueView(any(), any())).thenReturn(messageQueueView);
++ when(topicRouteService.getMqFaultStrategy()).thenReturn(mqFaultStrategy);
++ when(mqFaultStrategy.isSendLatencyFaultEnable()).thenReturn(false);
++
+ SendMessageActivity.SendMessageQueueSelector selector2 = new SendMessageActivity.SendMessageQueueSelector(
+ SendMessageRequest.newBuilder()
+ .addMessages(Message.newBuilder()
+@@ -328,12 +341,17 @@ public class SendMessageActivityTest extends BaseActivityTest {
+ brokerData.setBrokerAddrs(brokerAddrs);
+ topicRouteData.setBrokerDatas(Lists.newArrayList(brokerData));
+
+- MessageQueueView messageQueueView = new MessageQueueView(TOPIC, topicRouteData);
++
+ SendMessageActivity.SendMessageQueueSelector selector = new SendMessageActivity.SendMessageQueueSelector(
+ SendMessageRequest.newBuilder()
+ .addMessages(Message.newBuilder().build())
+ .build()
+ );
++ TopicRouteService topicRouteService = mock(TopicRouteService.class);
++ MQFaultStrategy mqFaultStrategy = mock(MQFaultStrategy.class);
++ when(topicRouteService.getMqFaultStrategy()).thenReturn(mqFaultStrategy);
++ when(mqFaultStrategy.isSendLatencyFaultEnable()).thenReturn(false);
++ MessageQueueView messageQueueView = new MessageQueueView(TOPIC, topicRouteData, topicRouteService.getMqFaultStrategy());
+
+ AddressableMessageQueue firstSelect = selector.select(ProxyContext.create(), messageQueueView);
+ AddressableMessageQueue secondSelect = selector.select(ProxyContext.create(), messageQueueView);
+@@ -343,6 +361,45 @@ public class SendMessageActivityTest extends BaseActivityTest {
+ assertNotEquals(firstSelect, secondSelect);
+ }
+
++ @Test
++ public void testSendNormalMessageQueueSelectorPipeLine() throws Exception {
++ TopicRouteData topicRouteData = new TopicRouteData();
++ int queueNums = 2;
++
++ QueueData queueData = createQueueData(BROKER_NAME, queueNums);
++ QueueData queueData2 = createQueueData(BROKER_NAME2, queueNums);
++ topicRouteData.setQueueDatas(Lists.newArrayList(queueData,queueData2));
++
++
++ BrokerData brokerData = createBrokerData(CLUSTER_NAME, BROKER_NAME, BROKER_ADDR);
++ BrokerData brokerData2 = createBrokerData(CLUSTER_NAME, BROKER_NAME2, BROKER_ADDR2);
++ topicRouteData.setBrokerDatas(Lists.newArrayList(brokerData, brokerData2));
++
++ SendMessageActivity.SendMessageQueueSelector selector = new SendMessageActivity.SendMessageQueueSelector(
++ SendMessageRequest.newBuilder()
++ .addMessages(Message.newBuilder().build())
++ .build()
++ );
++
++ ClientConfig cc = new ClientConfig();
++ this.mqFaultStrategy = new MQFaultStrategy(cc, null, null);
++ mqFaultStrategy.setSendLatencyFaultEnable(true);
++ mqFaultStrategy.updateFaultItem(BROKER_NAME2, 1000, true, true);
++ mqFaultStrategy.updateFaultItem(BROKER_NAME, 1000, true, false);
++
++ TopicRouteService topicRouteService = mock(TopicRouteService.class);
++ when(topicRouteService.getMqFaultStrategy()).thenReturn(mqFaultStrategy);
++ MessageQueueView messageQueueView = new MessageQueueView(TOPIC, topicRouteData, topicRouteService.getMqFaultStrategy());
++
++
++ AddressableMessageQueue firstSelect = selector.select(ProxyContext.create(), messageQueueView);
++ assertEquals(firstSelect.getBrokerName(), BROKER_NAME2);
++
++ mqFaultStrategy.updateFaultItem(BROKER_NAME2, 1000, true, false);
++ mqFaultStrategy.updateFaultItem(BROKER_NAME, 1000, true, true);
++ AddressableMessageQueue secondSelect = selector.select(ProxyContext.create(), messageQueueView);
++ assertEquals(secondSelect.getBrokerName(), BROKER_NAME);
++ }
+ @Test
+ public void testParameterValidate() {
+ // too large message body
+@@ -850,4 +907,23 @@ public class SendMessageActivityTest extends BaseActivityTest {
+ }
+ return sb.toString();
+ }
++
++ private static QueueData createQueueData(String brokerName, int writeQueueNums) {
++ QueueData queueData = new QueueData();
++ queueData.setBrokerName(brokerName);
++ queueData.setWriteQueueNums(writeQueueNums);
++ queueData.setPerm(PermName.PERM_WRITE);
++ return queueData;
++ }
++
++ private static BrokerData createBrokerData(String clusterName, String brokerName, String brokerAddrs) {
++ BrokerData brokerData = new BrokerData();
++ brokerData.setCluster(clusterName);
++ brokerData.setBrokerName(brokerName);
++ HashMap<Long, String> brokerAddrsMap = new HashMap<>();
++ brokerAddrsMap.put(MixAll.MASTER_ID, brokerAddrs);
++ brokerData.setBrokerAddrs(brokerAddrsMap);
++
++ return brokerData;
++ }
+ }
+diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/service/BaseServiceTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/service/BaseServiceTest.java
+index c97bd5a72..ca6fe909e 100644
+--- a/proxy/src/test/java/org/apache/rocketmq/proxy/service/BaseServiceTest.java
++++ b/proxy/src/test/java/org/apache/rocketmq/proxy/service/BaseServiceTest.java
+@@ -78,7 +78,7 @@ public class BaseServiceTest extends InitConfigTest {
+ topicRouteData.setBrokerDatas(Lists.newArrayList(brokerData));
+
+ when(this.topicRouteService.getAllMessageQueueView(any(), eq(ERR_TOPIC))).thenThrow(new MQClientException(ResponseCode.TOPIC_NOT_EXIST, ""));
+- when(this.topicRouteService.getAllMessageQueueView(any(), eq(TOPIC))).thenReturn(new MessageQueueView(TOPIC, topicRouteData));
+- when(this.topicRouteService.getAllMessageQueueView(any(), eq(CLUSTER_NAME))).thenReturn(new MessageQueueView(CLUSTER_NAME, topicRouteData));
++ when(this.topicRouteService.getAllMessageQueueView(any(), eq(TOPIC))).thenReturn(new MessageQueueView(TOPIC, topicRouteData, null));
++ when(this.topicRouteService.getAllMessageQueueView(any(), eq(CLUSTER_NAME))).thenReturn(new MessageQueueView(CLUSTER_NAME, topicRouteData, null));
+ }
+ }
+diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/MessageQueueSelectorTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/MessageQueueSelectorTest.java
+index e44ed28f4..d150f87c4 100644
+--- a/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/MessageQueueSelectorTest.java
++++ b/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/MessageQueueSelectorTest.java
+@@ -30,12 +30,12 @@ public class MessageQueueSelectorTest extends BaseServiceTest {
+ public void testReadMessageQueue() {
+ queueData.setPerm(PermName.PERM_READ);
+ queueData.setReadQueueNums(0);
+- MessageQueueSelector messageQueueSelector = new MessageQueueSelector(new TopicRouteWrapper(topicRouteData, TOPIC), true);
++ MessageQueueSelector messageQueueSelector = new MessageQueueSelector(new TopicRouteWrapper(topicRouteData, TOPIC), null, true);
+ assertTrue(messageQueueSelector.getQueues().isEmpty());
+
+ queueData.setPerm(PermName.PERM_READ);
+ queueData.setReadQueueNums(3);
+- messageQueueSelector = new MessageQueueSelector(new TopicRouteWrapper(topicRouteData, TOPIC), true);
++ messageQueueSelector = new MessageQueueSelector(new TopicRouteWrapper(topicRouteData, TOPIC), null, true);
+ assertEquals(3, messageQueueSelector.getQueues().size());
+ assertEquals(1, messageQueueSelector.getBrokerActingQueues().size());
+ for (int i = 0; i < messageQueueSelector.getQueues().size(); i++) {
+@@ -58,12 +58,12 @@ public class MessageQueueSelectorTest extends BaseServiceTest {
+ public void testWriteMessageQueue() {
+ queueData.setPerm(PermName.PERM_WRITE);
+ queueData.setReadQueueNums(0);
+- MessageQueueSelector messageQueueSelector = new MessageQueueSelector(new TopicRouteWrapper(topicRouteData, TOPIC), false);
++ MessageQueueSelector messageQueueSelector = new MessageQueueSelector(new TopicRouteWrapper(topicRouteData, TOPIC), null, false);
+ assertTrue(messageQueueSelector.getQueues().isEmpty());
+
+ queueData.setPerm(PermName.PERM_WRITE);
+ queueData.setWriteQueueNums(3);
+- messageQueueSelector = new MessageQueueSelector(new TopicRouteWrapper(topicRouteData, TOPIC), false);
++ messageQueueSelector = new MessageQueueSelector(new TopicRouteWrapper(topicRouteData, TOPIC), null, false);
+ assertEquals(3, messageQueueSelector.getQueues().size());
+ assertEquals(1, messageQueueSelector.getBrokerActingQueues().size());
+ for (int i = 0; i < messageQueueSelector.getQueues().size(); i++) {
+diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatSyncerTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatSyncerTest.java
+index c67f4953d..43fba3d03 100644
+--- a/proxy/src/test/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatSyncerTest.java
++++ b/proxy/src/test/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatSyncerTest.java
+@@ -132,7 +132,7 @@ public class HeartbeatSyncerTest extends InitConfigTest {
+ brokerAddr.put(0L, "127.0.0.1:10911");
+ brokerData.setBrokerAddrs(brokerAddr);
+ topicRouteData.getBrokerDatas().add(brokerData);
+- MessageQueueView messageQueueView = new MessageQueueView("foo", topicRouteData);
++ MessageQueueView messageQueueView = new MessageQueueView("foo", topicRouteData, null);
+ when(this.topicRouteService.getAllMessageQueueView(any(), anyString())).thenReturn(messageQueueView);
+ }
+ }
+diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/service/transaction/ClusterTransactionServiceTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/service/transaction/ClusterTransactionServiceTest.java
+index a0063544e..91af74cbe 100644
+--- a/proxy/src/test/java/org/apache/rocketmq/proxy/service/transaction/ClusterTransactionServiceTest.java
++++ b/proxy/src/test/java/org/apache/rocketmq/proxy/service/transaction/ClusterTransactionServiceTest.java
+@@ -64,7 +64,7 @@ public class ClusterTransactionServiceTest extends BaseServiceTest {
+ this.clusterTransactionService = new ClusterTransactionService(this.topicRouteService, this.producerManager,
+ this.mqClientAPIFactory);
+
+- MessageQueueView messageQueueView = new MessageQueueView(TOPIC, topicRouteData);
++ MessageQueueView messageQueueView = new MessageQueueView(TOPIC, topicRouteData, null);
+ when(this.topicRouteService.getAllMessageQueueView(any(), anyString()))
+ .thenReturn(messageQueueView);
+
+@@ -127,7 +127,7 @@ public class ClusterTransactionServiceTest extends BaseServiceTest {
+ brokerData.setBrokerAddrs(brokerAddrs);
+ topicRouteData.getQueueDatas().add(queueData);
+ topicRouteData.getBrokerDatas().add(brokerData);
+- when(this.topicRouteService.getAllMessageQueueView(any(), eq(TOPIC))).thenReturn(new MessageQueueView(TOPIC, topicRouteData));
++ when(this.topicRouteService.getAllMessageQueueView(any(), eq(TOPIC))).thenReturn(new MessageQueueView(TOPIC, topicRouteData, null));
+
+ TopicRouteData clusterTopicRouteData = new TopicRouteData();
+ QueueData clusterQueueData = new QueueData();
+@@ -141,7 +141,7 @@ public class ClusterTransactionServiceTest extends BaseServiceTest {
+ brokerAddrs.put(MixAll.MASTER_ID, BROKER_ADDR);
+ clusterBrokerData.setBrokerAddrs(brokerAddrs);
+ clusterTopicRouteData.setBrokerDatas(Lists.newArrayList(clusterBrokerData));
+- when(this.topicRouteService.getAllMessageQueueView(any(), eq(CLUSTER_NAME))).thenReturn(new MessageQueueView(CLUSTER_NAME, clusterTopicRouteData));
++ when(this.topicRouteService.getAllMessageQueueView(any(), eq(CLUSTER_NAME))).thenReturn(new MessageQueueView(CLUSTER_NAME, clusterTopicRouteData, null));
+
+ TopicRouteData clusterTopicRouteData2 = new TopicRouteData();
+ QueueData clusterQueueData2 = new QueueData();
+@@ -155,7 +155,7 @@ public class ClusterTransactionServiceTest extends BaseServiceTest {
+ brokerAddrs.put(MixAll.MASTER_ID, brokerAddr2);
+ clusterBrokerData2.setBrokerAddrs(brokerAddrs);
+ clusterTopicRouteData2.setBrokerDatas(Lists.newArrayList(clusterBrokerData2));
+- when(this.topicRouteService.getAllMessageQueueView(any(), eq(clusterName2))).thenReturn(new MessageQueueView(clusterName2, clusterTopicRouteData2));
++ when(this.topicRouteService.getAllMessageQueueView(any(), eq(clusterName2))).thenReturn(new MessageQueueView(clusterName2, clusterTopicRouteData2, null));
+
+ ConfigurationManager.getProxyConfig().setTransactionHeartbeatBatchNum(2);
+ this.clusterTransactionService.start();
+--
+2.32.0.windows.2
+
diff --git a/patch015-backport-fix-some-bugs.patch b/patch015-backport-fix-some-bugs.patch
new file mode 100644
index 000000000..11c10a6bd
--- /dev/null
+++ b/patch015-backport-fix-some-bugs.patch
@@ -0,0 +1,1894 @@
+From bd0e9c09db9748f7f74a0c707579142dccf30afc Mon Sep 17 00:00:00 2001
+From: PiteXChen <44110731+RapperCL@users.noreply.github.com>
+Date: Tue, 29 Aug 2023 19:39:27 +0800
+Subject: [PATCH 1/7] [ISSUE #7111] Remove responseFuture from the
+ responseTable when exception occurs (#7112)
+
+* remove responseFuture when exception
+* Empty-Commit
+
+---------
+Co-authored-by: chenyong152 <chenyong152@midea.com>
+---
+ .../apache/rocketmq/remoting/netty/NettyRemotingAbstract.java | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingAbstract.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingAbstract.java
+index 44d6a3df4..fce2de267 100644
+--- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingAbstract.java
++++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingAbstract.java
+@@ -529,6 +529,7 @@ public abstract class NettyRemotingAbstract {
+ log.warn("send a request command to channel <{}> failed.", RemotingHelper.parseChannelRemoteAddr(channel));
+ });
+ } catch (Exception e) {
++ responseTable.remove(opaque);
+ responseFuture.release();
+ log.warn("send a request command to channel <" + RemotingHelper.parseChannelRemoteAddr(channel) + "> Exception", e);
+ throw new RemotingSendRequestException(RemotingHelper.parseChannelRemoteAddr(channel), e);
+--
+2.32.0.windows.2
+
+
+From c78061bf6ca5f35452510ec4107c46735c51c316 Mon Sep 17 00:00:00 2001
+From: lizhimins <707364882@qq.com>
+Date: Wed, 30 Aug 2023 22:29:51 +0800
+Subject: [PATCH 2/7] [ISSUE#7280] Fix and refactor handle commit exception in
+ tiered storage (#7281)
+
+* refactor handle commit exception
+
+* refactor handle commit exception
+
+* fix handle commit exception
+---
+ .../tieredstore/TieredDispatcher.java | 3 +-
+ .../tieredstore/TieredMessageFetcher.java | 57 ++--
+ .../tieredstore/TieredMessageStore.java | 26 +-
+ .../provider/TieredFileSegment.java | 291 ++++++++++--------
+ .../provider/TieredStoreProvider.java | 8 +-
+ .../provider/posix/PosixFileSegment.java | 4 +-
+ .../CommitLogInputStream.java} | 30 +-
+ .../FileSegmentInputStream.java} | 49 ++-
+ .../FileSegmentInputStreamFactory.java} | 26 +-
+ .../tieredstore/TieredMessageStoreTest.java | 14 +-
+ .../tieredstore/file/TieredFlatFileTest.java | 2 +
+ .../tieredstore/file/TieredIndexFileTest.java | 2 +
+ ...m.java => MockFileSegmentInputStream.java} | 8 +-
+ .../TieredFileSegmentInputStreamTest.java | 24 +-
+ .../provider/TieredFileSegmentTest.java | 89 +++++-
+ .../provider/memory/MemoryFileSegment.java | 27 +-
+ .../memory/MemoryFileSegmentWithoutCheck.java | 4 +-
+ 17 files changed, 427 insertions(+), 237 deletions(-)
+ rename tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/{inputstream/TieredCommitLogInputStream.java => stream/CommitLogInputStream.java} (88%)
+ rename tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/{inputstream/TieredFileSegmentInputStream.java => stream/FileSegmentInputStream.java} (77%)
+ rename tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/{inputstream/TieredFileSegmentInputStreamFactory.java => stream/FileSegmentInputStreamFactory.java} (54%)
+ rename tieredstore/src/test/java/org/apache/rocketmq/tieredstore/provider/{MockTieredFileSegmentInputStream.java => MockFileSegmentInputStream.java} (82%)
+
+diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredDispatcher.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredDispatcher.java
+index 1746190cd..430c2b62e 100644
+--- a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredDispatcher.java
++++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredDispatcher.java
+@@ -318,8 +318,7 @@ public class TieredDispatcher extends ServiceThread implements CommitLogDispatch
+ continue;
+ case FILE_CLOSED:
+ tieredFlatFileManager.destroyCompositeFile(flatFile.getMessageQueue());
+- logger.info("TieredDispatcher#dispatchFlatFile: file has been close and destroy, " +
+- "topic: {}, queueId: {}", topic, queueId);
++ logger.info("File has been closed and destroy, topic: {}, queueId: {}", topic, queueId);
+ return;
+ default:
+ dispatchOffset--;
+diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredMessageFetcher.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredMessageFetcher.java
+index 9a9a3e5a5..766ff64f6 100644
+--- a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredMessageFetcher.java
++++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredMessageFetcher.java
+@@ -273,15 +273,17 @@ public class TieredMessageFetcher implements MessageStoreFetcher {
+ TieredStoreMetricsManager.cacheHit.add(resultWrapperList.size(), attributes);
+ }
+
+- // if no cached message found and there is currently an inflight request, wait for the request to end before continuing
++ // If there are no messages in the cache and there are currently requests being pulled.
++ // We need to wait for the request to return before continuing.
+ if (resultWrapperList.isEmpty() && waitInflightRequest) {
+- CompletableFuture<Long> future = flatFile.getInflightRequest(group, queueOffset, maxCount)
+- .getFuture(queueOffset);
++ CompletableFuture<Long> future =
++ flatFile.getInflightRequest(group, queueOffset, maxCount).getFuture(queueOffset);
+ if (!future.isDone()) {
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ // to prevent starvation issues, only allow waiting for inflight request once
+ return future.thenCompose(v -> {
+- LOGGER.debug("TieredMessageFetcher#getMessageFromCacheAsync: wait for inflight request cost: {}ms", stopwatch.elapsed(TimeUnit.MILLISECONDS));
++ LOGGER.debug("MessageFetcher#getMessageFromCacheAsync: wait for response cost: {}ms",
++ stopwatch.elapsed(TimeUnit.MILLISECONDS));
+ return getMessageFromCacheAsync(flatFile, group, queueOffset, maxCount, false);
+ });
+ }
+@@ -302,7 +304,8 @@ public class TieredMessageFetcher implements MessageStoreFetcher {
+
+ // if cache hit, result will be returned immediately and asynchronously prefetch messages for later requests
+ if (!resultWrapperList.isEmpty()) {
+- LOGGER.debug("TieredMessageFetcher#getMessageFromCacheAsync: cache hit: topic: {}, queue: {}, queue offset: {}, max message num: {}, cache hit num: {}",
++ LOGGER.debug("MessageFetcher#getMessageFromCacheAsync: cache hit: " +
++ "topic: {}, queue: {}, queue offset: {}, max message num: {}, cache hit num: {}",
+ mq.getTopic(), mq.getQueueId(), queueOffset, maxCount, resultWrapperList.size());
+ prefetchMessage(flatFile, group, maxCount, lastGetOffset + 1);
+
+@@ -316,8 +319,10 @@ public class TieredMessageFetcher implements MessageStoreFetcher {
+ }
+
+ // if cache is miss, immediately pull messages
+- LOGGER.warn("TieredMessageFetcher#getMessageFromCacheAsync: cache miss: topic: {}, queue: {}, queue offset: {}, max message num: {}",
++ LOGGER.warn("TieredMessageFetcher#getMessageFromCacheAsync: cache miss: " +
++ "topic: {}, queue: {}, queue offset: {}, max message num: {}",
+ mq.getTopic(), mq.getQueueId(), queueOffset, maxCount);
++
+ CompletableFuture<GetMessageResult> resultFuture;
+ synchronized (flatFile) {
+ int batchSize = maxCount * storeConfig.getReadAheadMinFactor();
+@@ -453,42 +458,42 @@ public class TieredMessageFetcher implements MessageStoreFetcher {
+ public CompletableFuture<GetMessageResult> getMessageAsync(
+ String group, String topic, int queueId, long queueOffset, int maxCount, final MessageFilter messageFilter) {
+
++ GetMessageResult result = new GetMessageResult();
+ CompositeQueueFlatFile flatFile = flatFileManager.getFlatFile(new MessageQueue(topic, brokerName, queueId));
++
+ if (flatFile == null) {
+- GetMessageResult result = new GetMessageResult();
+ result.setNextBeginOffset(queueOffset);
+ result.setStatus(GetMessageStatus.NO_MATCHED_LOGIC_QUEUE);
+ return CompletableFuture.completedFuture(result);
+ }
+
+- GetMessageResult result = new GetMessageResult();
+- long minQueueOffset = flatFile.getConsumeQueueMinOffset();
+- long maxQueueOffset = flatFile.getConsumeQueueCommitOffset();
+- result.setMinOffset(minQueueOffset);
+- result.setMaxOffset(maxQueueOffset);
++ // Max queue offset means next message put position
++ result.setMinOffset(flatFile.getConsumeQueueMinOffset());
++ result.setMaxOffset(flatFile.getConsumeQueueCommitOffset());
++
++ // Fill result according file offset.
++ // Offset range | Result | Fix to
++ // (-oo, 0] | no message | current offset
++ // (0, min) | too small | min offset
++ // [min, max) | correct |
++ // [max, max] | overflow one | max offset
++ // (max, +oo) | overflow badly | max offset
+
+- if (flatFile.getConsumeQueueCommitOffset() <= 0) {
++ if (result.getMaxOffset() <= 0) {
+ result.setStatus(GetMessageStatus.NO_MESSAGE_IN_QUEUE);
+ result.setNextBeginOffset(queueOffset);
+ return CompletableFuture.completedFuture(result);
+- }
+-
+- // request range | result
+- // (0, min) | too small
+- // [min, max) | correct
+- // [max, max] | overflow one
+- // (max, +oo) | overflow badly
+- if (queueOffset < minQueueOffset) {
++ } else if (queueOffset < result.getMinOffset()) {
+ result.setStatus(GetMessageStatus.OFFSET_TOO_SMALL);
+- result.setNextBeginOffset(flatFile.getConsumeQueueMinOffset());
++ result.setNextBeginOffset(result.getMinOffset());
+ return CompletableFuture.completedFuture(result);
+- } else if (queueOffset == maxQueueOffset) {
++ } else if (queueOffset == result.getMaxOffset()) {
+ result.setStatus(GetMessageStatus.OFFSET_OVERFLOW_ONE);
+- result.setNextBeginOffset(flatFile.getConsumeQueueCommitOffset());
++ result.setNextBeginOffset(result.getMaxOffset());
+ return CompletableFuture.completedFuture(result);
+- } else if (queueOffset > maxQueueOffset) {
++ } else if (queueOffset > result.getMaxOffset()) {
+ result.setStatus(GetMessageStatus.OFFSET_OVERFLOW_BADLY);
+- result.setNextBeginOffset(flatFile.getConsumeQueueCommitOffset());
++ result.setNextBeginOffset(result.getMaxOffset());
+ return CompletableFuture.completedFuture(result);
+ }
+
+diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredMessageStore.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredMessageStore.java
+index 5240ac8e9..78e855f36 100644
+--- a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredMessageStore.java
++++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredMessageStore.java
+@@ -99,11 +99,11 @@ public class TieredMessageStore extends AbstractPluginMessageStore {
+ return storeConfig;
+ }
+
+- public boolean viaTieredStorage(String topic, int queueId, long offset) {
+- return viaTieredStorage(topic, queueId, offset, 1);
++ public boolean fetchFromCurrentStore(String topic, int queueId, long offset) {
++ return fetchFromCurrentStore(topic, queueId, offset, 1);
+ }
+
+- public boolean viaTieredStorage(String topic, int queueId, long offset, int batchSize) {
++ public boolean fetchFromCurrentStore(String topic, int queueId, long offset, int batchSize) {
+ TieredMessageStoreConfig.TieredStorageLevel deepStorageLevel = storeConfig.getTieredStorageLevel();
+
+ if (deepStorageLevel.check(TieredMessageStoreConfig.TieredStorageLevel.FORCE)) {
+@@ -146,8 +146,10 @@ public class TieredMessageStore extends AbstractPluginMessageStore {
+ public CompletableFuture<GetMessageResult> getMessageAsync(String group, String topic,
+ int queueId, long offset, int maxMsgNums, MessageFilter messageFilter) {
+
+- if (!viaTieredStorage(topic, queueId, offset, maxMsgNums)) {
+- logger.trace("GetMessageAsync from next store topic: {}, queue: {}, offset: {}", topic, queueId, offset);
++ if (fetchFromCurrentStore(topic, queueId, offset, maxMsgNums)) {
++ logger.trace("GetMessageAsync from current store, topic: {}, queue: {}, offset: {}", topic, queueId, offset);
++ } else {
++ logger.trace("GetMessageAsync from next store, topic: {}, queue: {}, offset: {}", topic, queueId, offset);
+ return next.getMessageAsync(group, topic, queueId, offset, maxMsgNums, messageFilter);
+ }
+
+@@ -168,14 +170,14 @@ public class TieredMessageStore extends AbstractPluginMessageStore {
+
+ if (next.checkInStoreByConsumeOffset(topic, queueId, offset)) {
+ TieredStoreMetricsManager.fallbackTotal.add(1, latencyAttributes);
+- logger.debug("GetMessageAsync not found then try back to next store, result: {}, " +
++ logger.debug("GetMessageAsync not found, then back to next store, result: {}, " +
+ "topic: {}, queue: {}, queue offset: {}, offset range: {}-{}",
+ result.getStatus(), topic, queueId, offset, result.getMinOffset(), result.getMaxOffset());
+ return next.getMessage(group, topic, queueId, offset, maxMsgNums, messageFilter);
+ }
+ }
+
+- // system topic
++ // Fetch system topic data from the broker when using the force level.
+ if (result.getStatus() == GetMessageStatus.NO_MATCHED_LOGIC_QUEUE) {
+ if (TieredStoreUtil.isSystemTopic(topic) || PopAckConstants.isStartWithRevivePrefix(topic)) {
+ return next.getMessage(group, topic, queueId, offset, maxMsgNums, messageFilter);
+@@ -198,7 +200,7 @@ public class TieredMessageStore extends AbstractPluginMessageStore {
+ TieredStoreMetricsManager.messagesOutTotal.add(result.getMessageCount(), messagesOutAttributes);
+ }
+
+- // fix min or max offset according next store
++ // Fix min or max offset according next store at last
+ long minOffsetInQueue = next.getMinOffsetInQueue(topic, queueId);
+ if (minOffsetInQueue >= 0 && minOffsetInQueue < result.getMinOffset()) {
+ result.setMinOffset(minOffsetInQueue);
+@@ -209,7 +211,7 @@ public class TieredMessageStore extends AbstractPluginMessageStore {
+ }
+ return result;
+ }).exceptionally(e -> {
+- logger.error("GetMessageAsync from tiered store failed: ", e);
++ logger.error("GetMessageAsync from tiered store failed", e);
+ return next.getMessage(group, topic, queueId, offset, maxMsgNums, messageFilter);
+ });
+ }
+@@ -251,7 +253,7 @@ public class TieredMessageStore extends AbstractPluginMessageStore {
+ .build();
+ TieredStoreMetricsManager.apiLatency.record(stopwatch.elapsed(TimeUnit.MILLISECONDS), latencyAttributes);
+ if (time < 0) {
+- logger.debug("TieredMessageStore#getEarliestMessageTimeAsync: get earliest message time failed, try to get earliest message time from next store: topic: {}, queue: {}",
++ logger.debug("GetEarliestMessageTimeAsync failed, try to get earliest message time from next store: topic: {}, queue: {}",
+ topic, queueId);
+ return finalNextEarliestMessageTime != Long.MAX_VALUE ? finalNextEarliestMessageTime : -1;
+ }
+@@ -262,7 +264,7 @@ public class TieredMessageStore extends AbstractPluginMessageStore {
+ @Override
+ public CompletableFuture<Long> getMessageStoreTimeStampAsync(String topic, int queueId,
+ long consumeQueueOffset) {
+- if (viaTieredStorage(topic, queueId, consumeQueueOffset)) {
++ if (fetchFromCurrentStore(topic, queueId, consumeQueueOffset)) {
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ return fetcher.getMessageStoreTimeStampAsync(topic, queueId, consumeQueueOffset)
+ .thenApply(time -> {
+@@ -272,7 +274,7 @@ public class TieredMessageStore extends AbstractPluginMessageStore {
+ .build();
+ TieredStoreMetricsManager.apiLatency.record(stopwatch.elapsed(TimeUnit.MILLISECONDS), latencyAttributes);
+ if (time == -1) {
+- logger.debug("TieredMessageStore#getMessageStoreTimeStampAsync: get message time failed, try to get message time from next store: topic: {}, queue: {}, queue offset: {}",
++ logger.debug("GetEarliestMessageTimeAsync failed, try to get message time from next store, topic: {}, queue: {}, queue offset: {}",
+ topic, queueId, consumeQueueOffset);
+ return next.getMessageStoreTimeStamp(topic, queueId, consumeQueueOffset);
+ }
+diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/TieredFileSegment.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/TieredFileSegment.java
+index 5062c7d9e..32911a6e8 100644
+--- a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/TieredFileSegment.java
++++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/TieredFileSegment.java
+@@ -16,14 +16,11 @@
+ */
+ package org.apache.rocketmq.tieredstore.provider;
+
+-import com.google.common.annotations.VisibleForTesting;
+-import com.google.common.base.Stopwatch;
+ import java.nio.ByteBuffer;
+ import java.util.ArrayList;
+ import java.util.List;
+ import java.util.concurrent.CompletableFuture;
+ import java.util.concurrent.Semaphore;
+-import java.util.concurrent.TimeUnit;
+ import java.util.concurrent.locks.ReentrantLock;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+@@ -35,8 +32,8 @@ import org.apache.rocketmq.tieredstore.exception.TieredStoreException;
+ import org.apache.rocketmq.tieredstore.file.TieredCommitLog;
+ import org.apache.rocketmq.tieredstore.file.TieredConsumeQueue;
+ import org.apache.rocketmq.tieredstore.file.TieredIndexFile;
+-import org.apache.rocketmq.tieredstore.provider.inputstream.TieredFileSegmentInputStream;
+-import org.apache.rocketmq.tieredstore.provider.inputstream.TieredFileSegmentInputStreamFactory;
++import org.apache.rocketmq.tieredstore.provider.stream.FileSegmentInputStream;
++import org.apache.rocketmq.tieredstore.provider.stream.FileSegmentInputStreamFactory;
+ import org.apache.rocketmq.tieredstore.util.MessageBufferUtil;
+ import org.apache.rocketmq.tieredstore.util.TieredStoreUtil;
+
+@@ -50,22 +47,23 @@ public abstract class TieredFileSegment implements Comparable<TieredFileSegment>
+ protected final TieredMessageStoreConfig storeConfig;
+
+ private final long maxSize;
+- private final ReentrantLock bufferLock;
+- private final Semaphore commitLock;
++ private final ReentrantLock bufferLock = new ReentrantLock();
++ private final Semaphore commitLock = new Semaphore(1);
+
+- private volatile boolean full;
+- private volatile boolean closed;
++ private volatile boolean full = false;
++ private volatile boolean closed = false;
+
+- private volatile long minTimestamp;
+- private volatile long maxTimestamp;
+- private volatile long commitPosition;
+- private volatile long appendPosition;
++ private volatile long minTimestamp = Long.MAX_VALUE;
++ private volatile long maxTimestamp = Long.MAX_VALUE;
++ private volatile long commitPosition = 0L;
++ private volatile long appendPosition = 0L;
+
+ // only used in commitLog
+- private volatile long dispatchCommitOffset = 0;
++ private volatile long dispatchCommitOffset = 0L;
+
+ private ByteBuffer codaBuffer;
+- private List<ByteBuffer> uploadBufferList = new ArrayList<>();
++ private List<ByteBuffer> bufferList = new ArrayList<>();
++ private FileSegmentInputStream fileSegmentInputStream;
+ private CompletableFuture<Boolean> flightCommitRequest = CompletableFuture.completedFuture(false);
+
+ public TieredFileSegment(TieredMessageStoreConfig storeConfig,
+@@ -75,21 +73,13 @@ public abstract class TieredFileSegment implements Comparable<TieredFileSegment>
+ this.fileType = fileType;
+ this.filePath = filePath;
+ this.baseOffset = baseOffset;
+-
+- this.closed = false;
+- this.bufferLock = new ReentrantLock();
+- this.commitLock = new Semaphore(1);
+-
+- this.commitPosition = 0L;
+- this.appendPosition = 0L;
+- this.minTimestamp = Long.MAX_VALUE;
+- this.maxTimestamp = Long.MAX_VALUE;
+-
+- // The max segment size of a file is determined by the file type
+- this.maxSize = getMaxSizeAccordingFileType(storeConfig);
++ this.maxSize = getMaxSizeByFileType();
+ }
+
+- private long getMaxSizeAccordingFileType(TieredMessageStoreConfig storeConfig) {
++ /**
++ * The max segment size of a file is determined by the file type
++ */
++ protected long getMaxSizeByFileType() {
+ switch (fileType) {
+ case COMMIT_LOG:
+ return storeConfig.getTieredStoreCommitLogMaxSize();
+@@ -184,39 +174,23 @@ public abstract class TieredFileSegment implements Comparable<TieredFileSegment>
+ this.appendPosition = pos;
+ }
+
+- private List<ByteBuffer> rollingUploadBuffer() {
++ private List<ByteBuffer> borrowBuffer() {
+ bufferLock.lock();
+ try {
+- List<ByteBuffer> tmp = uploadBufferList;
+- uploadBufferList = new ArrayList<>();
++ List<ByteBuffer> tmp = bufferList;
++ bufferList = new ArrayList<>();
+ return tmp;
+ } finally {
+ bufferLock.unlock();
+ }
+ }
+
+- private void sendBackBuffer(TieredFileSegmentInputStream inputStream) {
+- bufferLock.lock();
+- try {
+- List<ByteBuffer> tmpBufferList = inputStream.getUploadBufferList();
+- for (ByteBuffer buffer : tmpBufferList) {
+- buffer.rewind();
+- }
+- tmpBufferList.addAll(uploadBufferList);
+- uploadBufferList = tmpBufferList;
+- if (inputStream.getCodaBuffer() != null) {
+- codaBuffer.rewind();
+- }
+- } finally {
+- bufferLock.unlock();
+- }
+- }
+-
+ @SuppressWarnings("NonAtomicOperationOnVolatileField")
+- public AppendResult append(ByteBuffer byteBuf, long timeStamp) {
++ public AppendResult append(ByteBuffer byteBuf, long timestamp) {
+ if (closed) {
+ return AppendResult.FILE_CLOSED;
+ }
++
+ bufferLock.lock();
+ try {
+ if (full || codaBuffer != null) {
+@@ -227,7 +201,8 @@ public abstract class TieredFileSegment implements Comparable<TieredFileSegment>
+ minTimestamp = byteBuf.getLong(TieredIndexFile.INDEX_FILE_HEADER_BEGIN_TIME_STAMP_POSITION);
+ maxTimestamp = byteBuf.getLong(TieredIndexFile.INDEX_FILE_HEADER_END_TIME_STAMP_POSITION);
+ appendPosition += byteBuf.remaining();
+- uploadBufferList.add(byteBuf);
++ // IndexFile is large and not change after compaction, no need deep copy
++ bufferList.add(byteBuf);
+ setFull();
+ return AppendResult.SUCCESS;
+ }
+@@ -236,23 +211,34 @@ public abstract class TieredFileSegment implements Comparable<TieredFileSegment>
+ setFull();
+ return AppendResult.FILE_FULL;
+ }
+- if (uploadBufferList.size() > storeConfig.getTieredStoreGroupCommitCount()
++
++ if (bufferList.size() > storeConfig.getTieredStoreGroupCommitCount()
+ || appendPosition - commitPosition > storeConfig.getTieredStoreGroupCommitSize()) {
+ commitAsync();
+ }
+- if (uploadBufferList.size() > storeConfig.getTieredStoreMaxGroupCommitCount()) {
+- logger.debug("TieredFileSegment#append: buffer full: file: {}, upload buffer size: {}",
+- getPath(), uploadBufferList.size());
++
++ if (bufferList.size() > storeConfig.getTieredStoreMaxGroupCommitCount()) {
++ logger.debug("File segment append buffer full, file: {}, buffer size: {}, pending bytes: {}",
++ getPath(), bufferList.size(), appendPosition - commitPosition);
+ return AppendResult.BUFFER_FULL;
+ }
+- if (timeStamp != Long.MAX_VALUE) {
+- maxTimestamp = timeStamp;
++
++ if (timestamp != Long.MAX_VALUE) {
++ maxTimestamp = timestamp;
+ if (minTimestamp == Long.MAX_VALUE) {
+- minTimestamp = timeStamp;
++ minTimestamp = timestamp;
+ }
+ }
++
+ appendPosition += byteBuf.remaining();
+- uploadBufferList.add(byteBuf);
++
++ // deep copy buffer
++ ByteBuffer byteBuffer = ByteBuffer.allocateDirect(byteBuf.remaining());
++ byteBuffer.put(byteBuf);
++ byteBuffer.flip();
++ byteBuf.rewind();
++
++ bufferList.add(byteBuffer);
+ return AppendResult.SUCCESS;
+ } finally {
+ bufferLock.unlock();
+@@ -267,7 +253,6 @@ public abstract class TieredFileSegment implements Comparable<TieredFileSegment>
+ return appendPosition;
+ }
+
+- @VisibleForTesting
+ public void setAppendPosition(long appendPosition) {
+ this.appendPosition = appendPosition;
+ }
+@@ -333,6 +318,8 @@ public abstract class TieredFileSegment implements Comparable<TieredFileSegment>
+ if (closed) {
+ return false;
+ }
++ // result is false when we send real commit request
++ // use join for wait flight request done
+ Boolean result = commitAsync().join();
+ if (!result) {
+ result = flightCommitRequest.join();
+@@ -340,92 +327,156 @@ public abstract class TieredFileSegment implements Comparable<TieredFileSegment>
+ return result;
+ }
+
++ private void releaseCommitLock() {
++ if (commitLock.availablePermits() == 0) {
++ commitLock.release();
++ } else {
++ logger.error("[Bug] FileSegmentCommitAsync, lock is already released: available permits: {}",
++ commitLock.availablePermits());
++ }
++ }
++
++ private void updateDispatchCommitOffset(List<ByteBuffer> bufferList) {
++ if (fileType == FileSegmentType.COMMIT_LOG && bufferList.size() > 0) {
++ dispatchCommitOffset =
++ MessageBufferUtil.getQueueOffset(bufferList.get(bufferList.size() - 1));
++ }
++ }
++
++ /**
++ * @return false: commit, true: no commit operation
++ */
+ @SuppressWarnings("NonAtomicOperationOnVolatileField")
+ public CompletableFuture<Boolean> commitAsync() {
+ if (closed) {
+ return CompletableFuture.completedFuture(false);
+ }
+- Stopwatch stopwatch = Stopwatch.createStarted();
++
+ if (!needCommit()) {
+ return CompletableFuture.completedFuture(true);
+ }
+- try {
+- int permits = commitLock.drainPermits();
+- if (permits <= 0) {
+- return CompletableFuture.completedFuture(false);
+- }
+- } catch (Exception e) {
++
++ if (commitLock.drainPermits() <= 0) {
+ return CompletableFuture.completedFuture(false);
+ }
+- List<ByteBuffer> bufferList = rollingUploadBuffer();
+- int bufferSize = 0;
+- for (ByteBuffer buffer : bufferList) {
+- bufferSize += buffer.remaining();
+- }
+- if (codaBuffer != null) {
+- bufferSize += codaBuffer.remaining();
+- }
+- if (bufferSize == 0) {
+- return CompletableFuture.completedFuture(true);
+- }
+- TieredFileSegmentInputStream inputStream = TieredFileSegmentInputStreamFactory.build(
+- fileType, baseOffset + commitPosition, bufferList, codaBuffer, bufferSize);
+- int finalBufferSize = bufferSize;
++
+ try {
+- flightCommitRequest = commit0(inputStream, commitPosition, bufferSize, fileType != FileSegmentType.INDEX)
++ if (fileSegmentInputStream != null) {
++ long fileSize = this.getSize();
++ if (fileSize == -1L) {
++ logger.error("Get commit position error before commit, Commit: %d, Expect: %d, Current Max: %d, FileName: %s",
++ commitPosition, commitPosition + fileSegmentInputStream.getContentLength(), appendPosition, getPath());
++ releaseCommitLock();
++ return CompletableFuture.completedFuture(false);
++ } else {
++ if (correctPosition(fileSize, null)) {
++ updateDispatchCommitOffset(fileSegmentInputStream.getBufferList());
++ fileSegmentInputStream = null;
++ }
++ }
++ }
++
++ int bufferSize;
++ if (fileSegmentInputStream != null) {
++ bufferSize = fileSegmentInputStream.available();
++ } else {
++ List<ByteBuffer> bufferList = borrowBuffer();
++ bufferSize = bufferList.stream().mapToInt(ByteBuffer::remaining).sum()
++ + (codaBuffer != null ? codaBuffer.remaining() : 0);
++ if (bufferSize == 0) {
++ releaseCommitLock();
++ return CompletableFuture.completedFuture(true);
++ }
++ fileSegmentInputStream = FileSegmentInputStreamFactory.build(
++ fileType, baseOffset + commitPosition, bufferList, codaBuffer, bufferSize);
++ }
++
++ return flightCommitRequest = this
++ .commit0(fileSegmentInputStream, commitPosition, bufferSize, fileType != FileSegmentType.INDEX)
+ .thenApply(result -> {
+ if (result) {
+- if (fileType == FileSegmentType.COMMIT_LOG && bufferList.size() > 0) {
+- dispatchCommitOffset = MessageBufferUtil.getQueueOffset(bufferList.get(bufferList.size() - 1));
+- }
+- commitPosition += finalBufferSize;
++ updateDispatchCommitOffset(fileSegmentInputStream.getBufferList());
++ commitPosition += bufferSize;
++ fileSegmentInputStream = null;
+ return true;
+- }
+- sendBackBuffer(inputStream);
+- return false;
+- })
+- .exceptionally(e -> handleCommitException(inputStream, e))
+- .whenComplete((result, e) -> {
+- if (commitLock.availablePermits() == 0) {
+- logger.debug("TieredFileSegment#commitAsync: commit cost: {}ms, file: {}, item count: {}, buffer size: {}", stopwatch.elapsed(TimeUnit.MILLISECONDS), getPath(), bufferList.size(), finalBufferSize);
+- commitLock.release();
+ } else {
+- logger.error("[Bug]TieredFileSegment#commitAsync: commit lock is already released: available permits: {}", commitLock.availablePermits());
++ fileSegmentInputStream.rewind();
++ return false;
+ }
+- });
+- return flightCommitRequest;
++ })
++ .exceptionally(this::handleCommitException)
++ .whenComplete((result, e) -> releaseCommitLock());
++
+ } catch (Exception e) {
+- handleCommitException(inputStream, e);
+- if (commitLock.availablePermits() == 0) {
+- logger.debug("TieredFileSegment#commitAsync: commit cost: {}ms, file: {}, item count: {}, buffer size: {}", stopwatch.elapsed(TimeUnit.MILLISECONDS), getPath(), bufferList.size(), finalBufferSize);
+- commitLock.release();
+- } else {
+- logger.error("[Bug]TieredFileSegment#commitAsync: commit lock is already released: available permits: {}", commitLock.availablePermits());
+- }
++ handleCommitException(e);
++ releaseCommitLock();
+ }
+ return CompletableFuture.completedFuture(false);
+ }
+
+- private boolean handleCommitException(TieredFileSegmentInputStream inputStream, Throwable e) {
++ private long getCorrectFileSize(Throwable throwable) {
++ if (throwable instanceof TieredStoreException) {
++ long fileSize = ((TieredStoreException) throwable).getPosition();
++ if (fileSize > 0) {
++ return fileSize;
++ }
++ }
++ return getSize();
++ }
++
++ private boolean handleCommitException(Throwable e) {
++ // Get root cause here
+ Throwable cause = e.getCause() != null ? e.getCause() : e;
+- sendBackBuffer(inputStream);
+- long realSize = 0;
+- if (cause instanceof TieredStoreException && ((TieredStoreException) cause).getPosition() > 0) {
+- realSize = ((TieredStoreException) cause).getPosition();
++ long fileSize = this.getCorrectFileSize(cause);
++
++ if (fileSize == -1L) {
++ logger.error("Get commit position error, Commit: %d, Expect: %d, Current Max: %d, FileName: %s",
++ commitPosition, commitPosition + fileSegmentInputStream.getContentLength(), appendPosition, getPath());
++ fileSegmentInputStream.rewind();
++ return false;
++ }
++
++ if (correctPosition(fileSize, cause)) {
++ updateDispatchCommitOffset(fileSegmentInputStream.getBufferList());
++ fileSegmentInputStream = null;
++ return true;
++ } else {
++ fileSegmentInputStream.rewind();
++ return false;
+ }
+- if (realSize <= 0) {
+- realSize = getSize();
++ }
++
++ /**
++ * return true to clear buffer
++ */
++ private boolean correctPosition(long fileSize, Throwable throwable) {
++
++ // Current we have three offsets here: commit offset, expect offset, file size.
++ // We guarantee that the commit offset is less than or equal to the expect offset.
++ // Max offset will increase because we can continuously put in new buffers
++ String handleInfo = throwable == null ? "before commit" : "after commit";
++ long expectPosition = commitPosition + fileSegmentInputStream.getContentLength();
++
++ String offsetInfo = String.format("Correct Commit Position, %s, result=[{}], " +
++ "Commit: %d, Expect: %d, Current Max: %d, FileSize: %d, FileName: %s",
++ handleInfo, commitPosition, expectPosition, appendPosition, fileSize, this.getPath());
++
++ // We are believing that the file size returned by the server is correct,
++ // can reset the commit offset to the file size reported by the storage system.
++ if (fileSize == expectPosition) {
++ logger.info(offsetInfo, "Success", throwable);
++ commitPosition = fileSize;
++ return true;
+ }
+- if (realSize > 0 && realSize > commitPosition) {
+- logger.error("TieredFileSegment#handleCommitException: commit failed: file: {}, try to fix position: origin: {}, real: {}", getPath(), commitPosition, realSize, cause);
+- // TODO check if this diff part is uploaded to backend storage
+- long diff = appendPosition - commitPosition;
+- commitPosition = realSize;
+- appendPosition = realSize + diff;
+- // TODO check if appendPosition is large than maxOffset
+- } else if (realSize < commitPosition) {
+- logger.error("[Bug]TieredFileSegment#handleCommitException: commit failed: file: {}, can not fix position: origin: {}, real: {}", getPath(), commitPosition, realSize, cause);
++
++ if (fileSize < commitPosition) {
++ logger.error(offsetInfo, "FileSizeIncorrect", throwable);
++ } else if (fileSize == commitPosition) {
++ logger.warn(offsetInfo, "CommitFailed", throwable);
++ } else if (fileSize > commitPosition) {
++ logger.warn(offsetInfo, "PartialSuccess", throwable);
+ }
++ commitPosition = fileSize;
+ return false;
+ }
+ }
+diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/TieredStoreProvider.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/TieredStoreProvider.java
+index 5a0ca25f5..0db3eaf8f 100644
+--- a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/TieredStoreProvider.java
++++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/TieredStoreProvider.java
+@@ -18,7 +18,7 @@ package org.apache.rocketmq.tieredstore.provider;
+
+ import java.nio.ByteBuffer;
+ import java.util.concurrent.CompletableFuture;
+-import org.apache.rocketmq.tieredstore.provider.inputstream.TieredFileSegmentInputStream;
++import org.apache.rocketmq.tieredstore.provider.stream.FileSegmentInputStream;
+
+ public interface TieredStoreProvider {
+
+@@ -30,7 +30,9 @@ public interface TieredStoreProvider {
+ String getPath();
+
+ /**
+- * Get file size in backend file system
++ * Get the real length of the file.
++ * Return 0 if the file does not exist,
++ * Return -1 if system get size failed.
+ *
+ * @return file real size
+ */
+@@ -71,5 +73,5 @@ public interface TieredStoreProvider {
+ * @param append try to append or create a new file
+ * @return put result, <code>true</code> if data successfully write; <code>false</code> otherwise
+ */
+- CompletableFuture<Boolean> commit0(TieredFileSegmentInputStream inputStream,long position, int length, boolean append);
++ CompletableFuture<Boolean> commit0(FileSegmentInputStream inputStream,long position, int length, boolean append);
+ }
+diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/posix/PosixFileSegment.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/posix/PosixFileSegment.java
+index 52be90b1d..7e949cb28 100644
+--- a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/posix/PosixFileSegment.java
++++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/posix/PosixFileSegment.java
+@@ -36,7 +36,7 @@ import org.apache.rocketmq.tieredstore.common.TieredMessageStoreConfig;
+ import org.apache.rocketmq.tieredstore.common.TieredStoreExecutor;
+ import org.apache.rocketmq.tieredstore.metrics.TieredStoreMetricsManager;
+ import org.apache.rocketmq.tieredstore.provider.TieredFileSegment;
+-import org.apache.rocketmq.tieredstore.provider.inputstream.TieredFileSegmentInputStream;
++import org.apache.rocketmq.tieredstore.provider.stream.FileSegmentInputStream;
+ import org.apache.rocketmq.tieredstore.util.TieredStoreUtil;
+
+ import static org.apache.rocketmq.tieredstore.metrics.TieredStoreMetricsConstant.LABEL_FILE_TYPE;
+@@ -184,7 +184,7 @@ public class PosixFileSegment extends TieredFileSegment {
+
+ @Override
+ public CompletableFuture<Boolean> commit0(
+- TieredFileSegmentInputStream inputStream, long position, int length, boolean append) {
++ FileSegmentInputStream inputStream, long position, int length, boolean append) {
+
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ AttributesBuilder attributesBuilder = newAttributesBuilder()
+diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/inputstream/TieredCommitLogInputStream.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/stream/CommitLogInputStream.java
+similarity index 88%
+rename from tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/inputstream/TieredCommitLogInputStream.java
+rename to tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/stream/CommitLogInputStream.java
+index c70bb7656..13b6e0ef9 100644
+--- a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/inputstream/TieredCommitLogInputStream.java
++++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/stream/CommitLogInputStream.java
+@@ -15,7 +15,7 @@
+ * limitations under the License.
+ */
+
+-package org.apache.rocketmq.tieredstore.provider.inputstream;
++package org.apache.rocketmq.tieredstore.provider.stream;
+
+ import java.io.IOException;
+ import java.nio.ByteBuffer;
+@@ -23,20 +23,23 @@ import java.util.List;
+ import org.apache.rocketmq.tieredstore.common.FileSegmentType;
+ import org.apache.rocketmq.tieredstore.util.MessageBufferUtil;
+
+-public class TieredCommitLogInputStream extends TieredFileSegmentInputStream {
++public class CommitLogInputStream extends FileSegmentInputStream {
+
+ /**
+ * commitLogOffset is the real physical offset of the commitLog buffer which is being read
+ */
++ private final long startCommitLogOffset;
++
+ private long commitLogOffset;
+
+ private final ByteBuffer codaBuffer;
+
+ private long markCommitLogOffset = -1;
+
+- public TieredCommitLogInputStream(FileSegmentType fileType, long startOffset,
++ public CommitLogInputStream(FileSegmentType fileType, long startOffset,
+ List<ByteBuffer> uploadBufferList, ByteBuffer codaBuffer, int contentLength) {
+ super(fileType, uploadBufferList, contentLength);
++ this.startCommitLogOffset = startOffset;
+ this.commitLogOffset = startOffset;
+ this.codaBuffer = codaBuffer;
+ }
+@@ -53,6 +56,15 @@ public class TieredCommitLogInputStream extends TieredFileSegmentInputStream {
+ this.commitLogOffset = markCommitLogOffset;
+ }
+
++ @Override
++ public synchronized void rewind() {
++ super.rewind();
++ this.commitLogOffset = this.startCommitLogOffset;
++ if (this.codaBuffer != null) {
++ this.codaBuffer.rewind();
++ }
++ }
++
+ @Override
+ public ByteBuffer getCodaBuffer() {
+ return this.codaBuffer;
+@@ -64,17 +76,17 @@ public class TieredCommitLogInputStream extends TieredFileSegmentInputStream {
+ return -1;
+ }
+ readPosition++;
+- if (curReadBufferIndex >= uploadBufferList.size()) {
++ if (curReadBufferIndex >= bufferList.size()) {
+ return readCoda();
+ }
+ int res;
+ if (readPosInCurBuffer >= curBuffer.remaining()) {
+ curReadBufferIndex++;
+- if (curReadBufferIndex >= uploadBufferList.size()) {
++ if (curReadBufferIndex >= bufferList.size()) {
+ readPosInCurBuffer = 0;
+ return readCoda();
+ }
+- curBuffer = uploadBufferList.get(curReadBufferIndex);
++ curBuffer = bufferList.get(curReadBufferIndex);
+ commitLogOffset += readPosInCurBuffer;
+ readPosInCurBuffer = 0;
+ }
+@@ -119,9 +131,9 @@ public class TieredCommitLogInputStream extends TieredFileSegmentInputStream {
+ int posInCurBuffer = readPosInCurBuffer;
+ long curCommitLogOffset = commitLogOffset;
+ ByteBuffer curBuf = curBuffer;
+- while (needRead > 0 && bufIndex <= uploadBufferList.size()) {
++ while (needRead > 0 && bufIndex <= bufferList.size()) {
+ int readLen, remaining, realReadLen = 0;
+- if (bufIndex == uploadBufferList.size()) {
++ if (bufIndex == bufferList.size()) {
+ // read from coda buffer
+ remaining = codaBuffer.remaining() - posInCurBuffer;
+ readLen = Math.min(remaining, needRead);
+@@ -137,7 +149,7 @@ public class TieredCommitLogInputStream extends TieredFileSegmentInputStream {
+ }
+ remaining = curBuf.remaining() - posInCurBuffer;
+ readLen = Math.min(remaining, needRead);
+- curBuf = uploadBufferList.get(bufIndex);
++ curBuf = bufferList.get(bufIndex);
+ if (posInCurBuffer < MessageBufferUtil.PHYSICAL_OFFSET_POSITION) {
+ realReadLen = Math.min(MessageBufferUtil.PHYSICAL_OFFSET_POSITION - posInCurBuffer, readLen);
+ // read from commitLog buffer
+diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/inputstream/TieredFileSegmentInputStream.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/stream/FileSegmentInputStream.java
+similarity index 77%
+rename from tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/inputstream/TieredFileSegmentInputStream.java
+rename to tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/stream/FileSegmentInputStream.java
+index e1758ca93..9e9d5135c 100644
+--- a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/inputstream/TieredFileSegmentInputStream.java
++++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/stream/FileSegmentInputStream.java
+@@ -15,15 +15,16 @@
+ * limitations under the License.
+ */
+
+-package org.apache.rocketmq.tieredstore.provider.inputstream;
++package org.apache.rocketmq.tieredstore.provider.stream;
+
+ import java.io.IOException;
+ import java.io.InputStream;
+ import java.nio.ByteBuffer;
+ import java.util.List;
++import org.apache.commons.collections.CollectionUtils;
+ import org.apache.rocketmq.tieredstore.common.FileSegmentType;
+
+-public class TieredFileSegmentInputStream extends InputStream {
++public class FileSegmentInputStream extends InputStream {
+
+ /**
+ * file type, can be commitlog, consume queue or indexfile now
+@@ -33,7 +34,7 @@ public class TieredFileSegmentInputStream extends InputStream {
+ /**
+ * hold bytebuffer
+ */
+- protected final List<ByteBuffer> uploadBufferList;
++ protected final List<ByteBuffer> bufferList;
+
+ /**
+ * total remaining of bytebuffer list
+@@ -65,13 +66,13 @@ public class TieredFileSegmentInputStream extends InputStream {
+
+ private int markReadPosInCurBuffer = -1;
+
+- public TieredFileSegmentInputStream(FileSegmentType fileType, List<ByteBuffer> uploadBufferList,
+- int contentLength) {
++ public FileSegmentInputStream(
++ FileSegmentType fileType, List<ByteBuffer> bufferList, int contentLength) {
+ this.fileType = fileType;
+ this.contentLength = contentLength;
+- this.uploadBufferList = uploadBufferList;
+- if (uploadBufferList != null && uploadBufferList.size() > 0) {
+- this.curBuffer = uploadBufferList.get(curReadBufferIndex);
++ this.bufferList = bufferList;
++ if (bufferList != null && bufferList.size() > 0) {
++ this.curBuffer = bufferList.get(curReadBufferIndex);
+ }
+ }
+
+@@ -95,18 +96,34 @@ public class TieredFileSegmentInputStream extends InputStream {
+ this.readPosition = markReadPosition;
+ this.curReadBufferIndex = markCurReadBufferIndex;
+ this.readPosInCurBuffer = markReadPosInCurBuffer;
+- if (this.curReadBufferIndex < uploadBufferList.size()) {
+- this.curBuffer = uploadBufferList.get(curReadBufferIndex);
++ if (this.curReadBufferIndex < bufferList.size()) {
++ this.curBuffer = bufferList.get(curReadBufferIndex);
+ }
+ }
+
++ public synchronized void rewind() {
++ this.readPosition = 0;
++ this.curReadBufferIndex = 0;
++ this.readPosInCurBuffer = 0;
++ if (CollectionUtils.isNotEmpty(bufferList)) {
++ this.curBuffer = bufferList.get(0);
++ for (ByteBuffer buffer : bufferList) {
++ buffer.rewind();
++ }
++ }
++ }
++
++ public int getContentLength() {
++ return contentLength;
++ }
++
+ @Override
+ public int available() {
+ return contentLength - readPosition;
+ }
+
+- public List<ByteBuffer> getUploadBufferList() {
+- return uploadBufferList;
++ public List<ByteBuffer> getBufferList() {
++ return bufferList;
+ }
+
+ public ByteBuffer getCodaBuffer() {
+@@ -121,10 +138,10 @@ public class TieredFileSegmentInputStream extends InputStream {
+ readPosition++;
+ if (readPosInCurBuffer >= curBuffer.remaining()) {
+ curReadBufferIndex++;
+- if (curReadBufferIndex >= uploadBufferList.size()) {
++ if (curReadBufferIndex >= bufferList.size()) {
+ return -1;
+ }
+- curBuffer = uploadBufferList.get(curReadBufferIndex);
++ curBuffer = bufferList.get(curReadBufferIndex);
+ readPosInCurBuffer = 0;
+ }
+ return curBuffer.get(readPosInCurBuffer++) & 0xff;
+@@ -153,8 +170,8 @@ public class TieredFileSegmentInputStream extends InputStream {
+ int bufIndex = curReadBufferIndex;
+ int posInCurBuffer = readPosInCurBuffer;
+ ByteBuffer curBuf = curBuffer;
+- while (needRead > 0 && bufIndex < uploadBufferList.size()) {
+- curBuf = uploadBufferList.get(bufIndex);
++ while (needRead > 0 && bufIndex < bufferList.size()) {
++ curBuf = bufferList.get(bufIndex);
+ int remaining = curBuf.remaining() - posInCurBuffer;
+ int readLen = Math.min(remaining, needRead);
+ // read from curBuf
+diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/inputstream/TieredFileSegmentInputStreamFactory.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/stream/FileSegmentInputStreamFactory.java
+similarity index 54%
+rename from tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/inputstream/TieredFileSegmentInputStreamFactory.java
+rename to tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/stream/FileSegmentInputStreamFactory.java
+index d0c983fd4..a90baff3a 100644
+--- a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/inputstream/TieredFileSegmentInputStreamFactory.java
++++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/stream/FileSegmentInputStreamFactory.java
+@@ -15,30 +15,34 @@
+ * limitations under the License.
+ */
+
+-package org.apache.rocketmq.tieredstore.provider.inputstream;
++package org.apache.rocketmq.tieredstore.provider.stream;
+
+ import java.nio.ByteBuffer;
+ import java.util.List;
+ import org.apache.rocketmq.tieredstore.common.FileSegmentType;
+
+-public class TieredFileSegmentInputStreamFactory {
++public class FileSegmentInputStreamFactory {
+
+- public static TieredFileSegmentInputStream build(FileSegmentType fileType,
+- long startOffset, List<ByteBuffer> uploadBufferList, ByteBuffer codaBuffer, int contentLength) {
++ public static FileSegmentInputStream build(
++ FileSegmentType fileType, long offset, List<ByteBuffer> bufferList, ByteBuffer byteBuffer, int length) {
++
++ if (bufferList == null) {
++ throw new IllegalArgumentException("bufferList is null");
++ }
+
+ switch (fileType) {
+ case COMMIT_LOG:
+- return new TieredCommitLogInputStream(
+- fileType, startOffset, uploadBufferList, codaBuffer, contentLength);
++ return new CommitLogInputStream(
++ fileType, offset, bufferList, byteBuffer, length);
+ case CONSUME_QUEUE:
+- return new TieredFileSegmentInputStream(fileType, uploadBufferList, contentLength);
++ return new FileSegmentInputStream(fileType, bufferList, length);
+ case INDEX:
+- if (uploadBufferList.size() != 1) {
+- throw new IllegalArgumentException("uploadBufferList size in INDEX type input stream must be 1");
++ if (bufferList.size() != 1) {
++ throw new IllegalArgumentException("buffer block size must be 1 when file type is IndexFile");
+ }
+- return new TieredFileSegmentInputStream(fileType, uploadBufferList, contentLength);
++ return new FileSegmentInputStream(fileType, bufferList, length);
+ default:
+- throw new IllegalArgumentException("fileType is not supported");
++ throw new IllegalArgumentException("file type is not supported");
+ }
+ }
+ }
+diff --git a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/TieredMessageStoreTest.java b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/TieredMessageStoreTest.java
+index 8601392e7..2451199c2 100644
+--- a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/TieredMessageStoreTest.java
++++ b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/TieredMessageStoreTest.java
+@@ -130,36 +130,36 @@ public class TieredMessageStoreTest {
+ // TieredStorageLevel.DISABLE
+ properties.setProperty("tieredStorageLevel", "0");
+ configuration.update(properties);
+- Assert.assertFalse(store.viaTieredStorage(mq.getTopic(), mq.getQueueId(), 0));
++ Assert.assertFalse(store.fetchFromCurrentStore(mq.getTopic(), mq.getQueueId(), 0));
+
+ // TieredStorageLevel.NOT_IN_DISK
+ properties.setProperty("tieredStorageLevel", "1");
+ configuration.update(properties);
+ when(nextStore.checkInStoreByConsumeOffset(anyString(), anyInt(), anyLong())).thenReturn(false);
+- Assert.assertTrue(store.viaTieredStorage(mq.getTopic(), mq.getQueueId(), 0));
++ Assert.assertTrue(store.fetchFromCurrentStore(mq.getTopic(), mq.getQueueId(), 0));
+
+ when(nextStore.checkInStoreByConsumeOffset(anyString(), anyInt(), anyLong())).thenReturn(true);
+- Assert.assertFalse(store.viaTieredStorage(mq.getTopic(), mq.getQueueId(), 0));
++ Assert.assertFalse(store.fetchFromCurrentStore(mq.getTopic(), mq.getQueueId(), 0));
+
+ // TieredStorageLevel.NOT_IN_MEM
+ properties.setProperty("tieredStorageLevel", "2");
+ configuration.update(properties);
+ Mockito.when(nextStore.checkInStoreByConsumeOffset(anyString(), anyInt(), anyLong())).thenReturn(false);
+ Mockito.when(nextStore.checkInMemByConsumeOffset(anyString(), anyInt(), anyLong(), anyInt())).thenReturn(true);
+- Assert.assertTrue(store.viaTieredStorage(mq.getTopic(), mq.getQueueId(), 0));
++ Assert.assertTrue(store.fetchFromCurrentStore(mq.getTopic(), mq.getQueueId(), 0));
+
+ Mockito.when(nextStore.checkInStoreByConsumeOffset(anyString(), anyInt(), anyLong())).thenReturn(true);
+ Mockito.when(nextStore.checkInMemByConsumeOffset(anyString(), anyInt(), anyLong(), anyInt())).thenReturn(false);
+- Assert.assertTrue(store.viaTieredStorage(mq.getTopic(), mq.getQueueId(), 0));
++ Assert.assertTrue(store.fetchFromCurrentStore(mq.getTopic(), mq.getQueueId(), 0));
+
+ Mockito.when(nextStore.checkInStoreByConsumeOffset(anyString(), anyInt(), anyLong())).thenReturn(true);
+ Mockito.when(nextStore.checkInMemByConsumeOffset(anyString(), anyInt(), anyLong(), anyInt())).thenReturn(true);
+- Assert.assertFalse(store.viaTieredStorage(mq.getTopic(), mq.getQueueId(), 0));
++ Assert.assertFalse(store.fetchFromCurrentStore(mq.getTopic(), mq.getQueueId(), 0));
+
+ // TieredStorageLevel.FORCE
+ properties.setProperty("tieredStorageLevel", "3");
+ configuration.update(properties);
+- Assert.assertTrue(store.viaTieredStorage(mq.getTopic(), mq.getQueueId(), 0));
++ Assert.assertTrue(store.fetchFromCurrentStore(mq.getTopic(), mq.getQueueId(), 0));
+ }
+
+ @Test
+diff --git a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/file/TieredFlatFileTest.java b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/file/TieredFlatFileTest.java
+index cc39cfbfc..7a4d05969 100644
+--- a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/file/TieredFlatFileTest.java
++++ b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/file/TieredFlatFileTest.java
+@@ -24,6 +24,7 @@ import org.apache.rocketmq.common.message.MessageQueue;
+ import org.apache.rocketmq.tieredstore.TieredStoreTestUtil;
+ import org.apache.rocketmq.tieredstore.common.FileSegmentType;
+ import org.apache.rocketmq.tieredstore.common.TieredMessageStoreConfig;
++import org.apache.rocketmq.tieredstore.common.TieredStoreExecutor;
+ import org.apache.rocketmq.tieredstore.metadata.FileSegmentMetadata;
+ import org.apache.rocketmq.tieredstore.metadata.TieredMetadataStore;
+ import org.apache.rocketmq.tieredstore.provider.TieredFileSegment;
+@@ -55,6 +56,7 @@ public class TieredFlatFileTest {
+ public void tearDown() throws IOException {
+ TieredStoreTestUtil.destroyMetadataStore();
+ TieredStoreTestUtil.destroyTempDir(storePath);
++ TieredStoreExecutor.shutdown();
+ }
+
+ private List<FileSegmentMetadata> getSegmentMetadataList(TieredMetadataStore metadataStore) {
+diff --git a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/file/TieredIndexFileTest.java b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/file/TieredIndexFileTest.java
+index 262d6645b..2da72bc7a 100644
+--- a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/file/TieredIndexFileTest.java
++++ b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/file/TieredIndexFileTest.java
+@@ -87,5 +87,7 @@ public class TieredIndexFileTest {
+
+ indexList = indexFile.queryAsync(mq.getTopic(), "key1", 1000, 1200).join();
+ Assert.assertEquals(1, indexList.size());
++
++ indexFile.destroy();
+ }
+ }
+diff --git a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/provider/MockTieredFileSegmentInputStream.java b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/provider/MockFileSegmentInputStream.java
+similarity index 82%
+rename from tieredstore/src/test/java/org/apache/rocketmq/tieredstore/provider/MockTieredFileSegmentInputStream.java
+rename to tieredstore/src/test/java/org/apache/rocketmq/tieredstore/provider/MockFileSegmentInputStream.java
+index a6566b7de..3bbe41dd4 100644
+--- a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/provider/MockTieredFileSegmentInputStream.java
++++ b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/provider/MockFileSegmentInputStream.java
+@@ -20,13 +20,13 @@ package org.apache.rocketmq.tieredstore.provider;
+ import java.io.InputStream;
+ import java.nio.ByteBuffer;
+ import java.util.List;
+-import org.apache.rocketmq.tieredstore.provider.inputstream.TieredFileSegmentInputStream;
++import org.apache.rocketmq.tieredstore.provider.stream.FileSegmentInputStream;
+
+-public class MockTieredFileSegmentInputStream extends TieredFileSegmentInputStream {
++public class MockFileSegmentInputStream extends FileSegmentInputStream {
+
+ private final InputStream inputStream;
+
+- public MockTieredFileSegmentInputStream(InputStream inputStream) {
++ public MockFileSegmentInputStream(InputStream inputStream) {
+ super(null, null, Integer.MAX_VALUE);
+ this.inputStream = inputStream;
+ }
+@@ -43,7 +43,7 @@ public class MockTieredFileSegmentInputStream extends TieredFileSegmentInputStre
+ }
+
+ @Override
+- public List<ByteBuffer> getUploadBufferList() {
++ public List<ByteBuffer> getBufferList() {
+ return null;
+ }
+
+diff --git a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/provider/TieredFileSegmentInputStreamTest.java b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/provider/TieredFileSegmentInputStreamTest.java
+index a2554ba3d..743d9182c 100644
+--- a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/provider/TieredFileSegmentInputStreamTest.java
++++ b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/provider/TieredFileSegmentInputStreamTest.java
+@@ -28,8 +28,8 @@ import java.util.Random;
+ import org.apache.rocketmq.tieredstore.common.FileSegmentType;
+ import org.apache.rocketmq.tieredstore.file.TieredCommitLog;
+ import org.apache.rocketmq.tieredstore.file.TieredConsumeQueue;
+-import org.apache.rocketmq.tieredstore.provider.inputstream.TieredFileSegmentInputStream;
+-import org.apache.rocketmq.tieredstore.provider.inputstream.TieredFileSegmentInputStreamFactory;
++import org.apache.rocketmq.tieredstore.provider.stream.FileSegmentInputStream;
++import org.apache.rocketmq.tieredstore.provider.stream.FileSegmentInputStreamFactory;
+ import org.apache.rocketmq.tieredstore.util.MessageBufferUtil;
+ import org.apache.rocketmq.tieredstore.util.MessageBufferUtilTest;
+ import org.junit.Assert;
+@@ -57,7 +57,7 @@ public class TieredFileSegmentInputStreamTest {
+ bufferSize += byteBuffer.remaining();
+ }
+
+- // build expected byte buffer for verifying the TieredFileSegmentInputStream
++ // build expected byte buffer for verifying the FileSegmentInputStream
+ ByteBuffer expectedByteBuffer = ByteBuffer.allocate(bufferSize);
+ for (ByteBuffer byteBuffer : uploadBufferList) {
+ expectedByteBuffer.put(byteBuffer);
+@@ -74,7 +74,7 @@ public class TieredFileSegmentInputStreamTest {
+ int[] batchReadSizeTestSet = {
+ MessageBufferUtil.PHYSICAL_OFFSET_POSITION - 1, MessageBufferUtil.PHYSICAL_OFFSET_POSITION, MessageBufferUtil.PHYSICAL_OFFSET_POSITION + 1, MSG_LEN - 1, MSG_LEN, MSG_LEN + 1
+ };
+- verifyReadAndReset(expectedByteBuffer, () -> TieredFileSegmentInputStreamFactory.build(
++ verifyReadAndReset(expectedByteBuffer, () -> FileSegmentInputStreamFactory.build(
+ FileSegmentType.COMMIT_LOG, COMMIT_LOG_START_OFFSET, uploadBufferList, null, finalBufferSize), finalBufferSize, batchReadSizeTestSet);
+
+ }
+@@ -98,7 +98,7 @@ public class TieredFileSegmentInputStreamTest {
+ int codaBufferSize = codaBuffer.remaining();
+ bufferSize += codaBufferSize;
+
+- // build expected byte buffer for verifying the TieredFileSegmentInputStream
++ // build expected byte buffer for verifying the FileSegmentInputStream
+ ByteBuffer expectedByteBuffer = ByteBuffer.allocate(bufferSize);
+ for (ByteBuffer byteBuffer : uploadBufferList) {
+ expectedByteBuffer.put(byteBuffer);
+@@ -119,7 +119,7 @@ public class TieredFileSegmentInputStreamTest {
+ MSG_LEN - 1, MSG_LEN, MSG_LEN + 1,
+ bufferSize - 1, bufferSize, bufferSize + 1
+ };
+- verifyReadAndReset(expectedByteBuffer, () -> TieredFileSegmentInputStreamFactory.build(
++ verifyReadAndReset(expectedByteBuffer, () -> FileSegmentInputStreamFactory.build(
+ FileSegmentType.COMMIT_LOG, COMMIT_LOG_START_OFFSET, uploadBufferList, codaBuffer, finalBufferSize), finalBufferSize, batchReadSizeTestSet);
+
+ }
+@@ -134,7 +134,7 @@ public class TieredFileSegmentInputStreamTest {
+ bufferSize += byteBuffer.remaining();
+ }
+
+- // build expected byte buffer for verifying the TieredFileSegmentInputStream
++ // build expected byte buffer for verifying the FileSegmentInputStream
+ ByteBuffer expectedByteBuffer = ByteBuffer.allocate(bufferSize);
+ for (ByteBuffer byteBuffer : uploadBufferList) {
+ expectedByteBuffer.put(byteBuffer);
+@@ -143,7 +143,7 @@ public class TieredFileSegmentInputStreamTest {
+
+ int finalBufferSize = bufferSize;
+ int[] batchReadSizeTestSet = {TieredConsumeQueue.CONSUME_QUEUE_STORE_UNIT_SIZE - 1, TieredConsumeQueue.CONSUME_QUEUE_STORE_UNIT_SIZE, TieredConsumeQueue.CONSUME_QUEUE_STORE_UNIT_SIZE + 1};
+- verifyReadAndReset(expectedByteBuffer, () -> TieredFileSegmentInputStreamFactory.build(
++ verifyReadAndReset(expectedByteBuffer, () -> FileSegmentInputStreamFactory.build(
+ FileSegmentType.CONSUME_QUEUE, COMMIT_LOG_START_OFFSET, uploadBufferList, null, finalBufferSize), bufferSize, batchReadSizeTestSet);
+ }
+
+@@ -156,16 +156,16 @@ public class TieredFileSegmentInputStreamTest {
+ byteBuffer.flip();
+ List<ByteBuffer> uploadBufferList = Arrays.asList(byteBuffer);
+
+- // build expected byte buffer for verifying the TieredFileSegmentInputStream
++ // build expected byte buffer for verifying the FileSegmentInputStream
+ ByteBuffer expectedByteBuffer = byteBuffer.slice();
+
+- verifyReadAndReset(expectedByteBuffer, () -> TieredFileSegmentInputStreamFactory.build(
++ verifyReadAndReset(expectedByteBuffer, () -> FileSegmentInputStreamFactory.build(
+ FileSegmentType.INDEX, COMMIT_LOG_START_OFFSET, uploadBufferList, null, byteBuffer.limit()), byteBuffer.limit(), new int[] {23, 24, 25});
+ }
+
+- private void verifyReadAndReset(ByteBuffer expectedByteBuffer, Supplier<TieredFileSegmentInputStream> constructor,
++ private void verifyReadAndReset(ByteBuffer expectedByteBuffer, Supplier<FileSegmentInputStream> constructor,
+ int bufferSize, int[] readBatchSizeTestSet) {
+- TieredFileSegmentInputStream inputStream = constructor.get();
++ FileSegmentInputStream inputStream = constructor.get();
+
+ // verify
+ verifyInputStream(inputStream, expectedByteBuffer);
+diff --git a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/provider/TieredFileSegmentTest.java b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/provider/TieredFileSegmentTest.java
+index 4cd83e0d2..a655710a5 100644
+--- a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/provider/TieredFileSegmentTest.java
++++ b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/provider/TieredFileSegmentTest.java
+@@ -116,13 +116,22 @@ public class TieredFileSegmentTest {
+ }
+
+ @Test
+- public void testCommitFailed() {
++ public void testCommitFailedThenSuccess() {
+ long startTime = System.currentTimeMillis();
+ MemoryFileSegment segment = (MemoryFileSegment) createFileSegment(FileSegmentType.COMMIT_LOG);
+ long lastSize = segment.getSize();
+- segment.append(MessageBufferUtilTest.buildMockedMessageBuffer(), 0);
+- segment.append(MessageBufferUtilTest.buildMockedMessageBuffer(), 0);
+-
++ segment.setCheckSize(false);
++ segment.initPosition(lastSize);
++ segment.setSize((int) lastSize);
++
++ ByteBuffer buffer1 = MessageBufferUtilTest.buildMockedMessageBuffer().putLong(
++ MessageBufferUtil.PHYSICAL_OFFSET_POSITION, baseOffset + lastSize);
++ ByteBuffer buffer2 = MessageBufferUtilTest.buildMockedMessageBuffer().putLong(
++ MessageBufferUtil.PHYSICAL_OFFSET_POSITION, baseOffset + lastSize + MessageBufferUtilTest.MSG_LEN);
++ segment.append(buffer1, 0);
++ segment.append(buffer2, 0);
++
++ // Mock new message arrive
+ segment.blocker = new CompletableFuture<>();
+ new Thread(() -> {
+ try {
+@@ -131,20 +140,88 @@ public class TieredFileSegmentTest {
+ Assert.fail(e.getMessage());
+ }
+ ByteBuffer buffer = MessageBufferUtilTest.buildMockedMessageBuffer();
++ buffer.putLong(MessageBufferUtil.PHYSICAL_OFFSET_POSITION, MessageBufferUtilTest.MSG_LEN * 2);
+ buffer.putLong(MessageBufferUtil.STORE_TIMESTAMP_POSITION, startTime);
+ segment.append(buffer, 0);
+ segment.blocker.complete(false);
+ }).start();
+
++ // Commit failed
+ segment.commit();
+ segment.blocker.join();
++ segment.blocker = null;
++
++ // Copy data and assume commit success
++ segment.getMemStore().put(buffer1);
++ segment.getMemStore().put(buffer2);
++ segment.setSize((int) (lastSize + MessageBufferUtilTest.MSG_LEN * 2));
+
+- segment.blocker = new CompletableFuture<>();
+- segment.blocker.complete(true);
+ segment.commit();
++ Assert.assertEquals(lastSize + MessageBufferUtilTest.MSG_LEN * 3, segment.getCommitPosition());
++ Assert.assertEquals(baseOffset + lastSize + MessageBufferUtilTest.MSG_LEN * 3, segment.getCommitOffset());
++ Assert.assertEquals(baseOffset + lastSize + MessageBufferUtilTest.MSG_LEN * 3, segment.getMaxOffset());
++
++ ByteBuffer msg1 = segment.read(lastSize, MessageBufferUtilTest.MSG_LEN);
++ Assert.assertEquals(baseOffset + lastSize, MessageBufferUtil.getCommitLogOffset(msg1));
++
++ ByteBuffer msg2 = segment.read(lastSize + MessageBufferUtilTest.MSG_LEN, MessageBufferUtilTest.MSG_LEN);
++ Assert.assertEquals(baseOffset + lastSize + MessageBufferUtilTest.MSG_LEN, MessageBufferUtil.getCommitLogOffset(msg2));
++
++ ByteBuffer msg3 = segment.read(lastSize + MessageBufferUtilTest.MSG_LEN * 2, MessageBufferUtilTest.MSG_LEN);
++ Assert.assertEquals(baseOffset + lastSize + MessageBufferUtilTest.MSG_LEN * 2, MessageBufferUtil.getCommitLogOffset(msg3));
++ }
++
++ @Test
++ public void testCommitFailed3Times() {
++ long startTime = System.currentTimeMillis();
++ MemoryFileSegment segment = (MemoryFileSegment) createFileSegment(FileSegmentType.COMMIT_LOG);
++ long lastSize = segment.getSize();
++ segment.setCheckSize(false);
++ segment.initPosition(lastSize);
++ segment.setSize((int) lastSize);
++
++ ByteBuffer buffer1 = MessageBufferUtilTest.buildMockedMessageBuffer().putLong(
++ MessageBufferUtil.PHYSICAL_OFFSET_POSITION, baseOffset + lastSize);
++ ByteBuffer buffer2 = MessageBufferUtilTest.buildMockedMessageBuffer().putLong(
++ MessageBufferUtil.PHYSICAL_OFFSET_POSITION, baseOffset + lastSize + MessageBufferUtilTest.MSG_LEN);
++ segment.append(buffer1, 0);
++ segment.append(buffer2, 0);
++
++ // Mock new message arrive
++ segment.blocker = new CompletableFuture<>();
++ new Thread(() -> {
++ try {
++ Thread.sleep(3000);
++ } catch (InterruptedException e) {
++ Assert.fail(e.getMessage());
++ }
++ ByteBuffer buffer = MessageBufferUtilTest.buildMockedMessageBuffer();
++ buffer.putLong(MessageBufferUtil.PHYSICAL_OFFSET_POSITION, MessageBufferUtilTest.MSG_LEN * 2);
++ buffer.putLong(MessageBufferUtil.STORE_TIMESTAMP_POSITION, startTime);
++ segment.append(buffer, 0);
++ segment.blocker.complete(false);
++ }).start();
++
++ for (int i = 0; i < 3; i++) {
++ segment.commit();
++ }
++
++ Assert.assertEquals(lastSize, segment.getCommitPosition());
++ Assert.assertEquals(baseOffset + lastSize, segment.getCommitOffset());
++ Assert.assertEquals(baseOffset + lastSize + MessageBufferUtilTest.MSG_LEN * 3, segment.getMaxOffset());
++
++ segment.blocker.join();
++ segment.blocker = null;
+
++ segment.commit();
++ Assert.assertEquals(lastSize + MessageBufferUtilTest.MSG_LEN * 2, segment.getCommitPosition());
++ Assert.assertEquals(baseOffset + lastSize + MessageBufferUtilTest.MSG_LEN * 2, segment.getCommitOffset());
+ Assert.assertEquals(baseOffset + lastSize + MessageBufferUtilTest.MSG_LEN * 3, segment.getMaxOffset());
++
++ segment.commit();
++ Assert.assertEquals(lastSize + MessageBufferUtilTest.MSG_LEN * 3, segment.getCommitPosition());
+ Assert.assertEquals(baseOffset + lastSize + MessageBufferUtilTest.MSG_LEN * 3, segment.getCommitOffset());
++ Assert.assertEquals(baseOffset + lastSize + MessageBufferUtilTest.MSG_LEN * 3, segment.getMaxOffset());
+
+ ByteBuffer msg1 = segment.read(lastSize, MessageBufferUtilTest.MSG_LEN);
+ Assert.assertEquals(baseOffset + lastSize, MessageBufferUtil.getCommitLogOffset(msg1));
+diff --git a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/provider/memory/MemoryFileSegment.java b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/provider/memory/MemoryFileSegment.java
+index cb155cf8f..80ad41f68 100644
+--- a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/provider/memory/MemoryFileSegment.java
++++ b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/provider/memory/MemoryFileSegment.java
+@@ -23,7 +23,7 @@ import org.apache.rocketmq.common.message.MessageQueue;
+ import org.apache.rocketmq.tieredstore.common.FileSegmentType;
+ import org.apache.rocketmq.tieredstore.common.TieredMessageStoreConfig;
+ import org.apache.rocketmq.tieredstore.provider.TieredFileSegment;
+-import org.apache.rocketmq.tieredstore.provider.inputstream.TieredFileSegmentInputStream;
++import org.apache.rocketmq.tieredstore.provider.stream.FileSegmentInputStream;
+ import org.apache.rocketmq.tieredstore.util.TieredStoreUtil;
+ import org.junit.Assert;
+
+@@ -33,6 +33,8 @@ public class MemoryFileSegment extends TieredFileSegment {
+
+ public CompletableFuture<Boolean> blocker;
+
++ protected int size = 0;
++
+ protected boolean checkSize = true;
+
+ public MemoryFileSegment(FileSegmentType fileType, MessageQueue messageQueue, long baseOffset,
+@@ -56,6 +58,18 @@ public class MemoryFileSegment extends TieredFileSegment {
+ memStore.position((int) getSize());
+ }
+
++ public boolean isCheckSize() {
++ return checkSize;
++ }
++
++ public void setCheckSize(boolean checkSize) {
++ this.checkSize = checkSize;
++ }
++
++ public ByteBuffer getMemStore() {
++ return memStore;
++ }
++
+ @Override
+ public String getPath() {
+ return filePath;
+@@ -66,7 +80,11 @@ public class MemoryFileSegment extends TieredFileSegment {
+ if (checkSize) {
+ return 1000;
+ }
+- return 0;
++ return size;
++ }
++
++ public void setSize(int size) {
++ this.size = size;
+ }
+
+ @Override
+@@ -85,11 +103,11 @@ public class MemoryFileSegment extends TieredFileSegment {
+
+ @Override
+ public CompletableFuture<Boolean> commit0(
+- TieredFileSegmentInputStream inputStream, long position, int length, boolean append) {
++ FileSegmentInputStream inputStream, long position, int length, boolean append) {
+
+ try {
+ if (blocker != null && !blocker.get()) {
+- throw new IllegalStateException();
++ throw new IllegalStateException("Commit Exception for Memory Test");
+ }
+ } catch (InterruptedException | ExecutionException e) {
+ Assert.fail(e.getMessage());
+@@ -98,7 +116,6 @@ public class MemoryFileSegment extends TieredFileSegment {
+ Assert.assertTrue(!checkSize || position >= getSize());
+
+ byte[] buffer = new byte[1024];
+-
+ int startPos = memStore.position();
+ try {
+ int len;
+diff --git a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/provider/memory/MemoryFileSegmentWithoutCheck.java b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/provider/memory/MemoryFileSegmentWithoutCheck.java
+index 8ac330b37..630fd2223 100644
+--- a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/provider/memory/MemoryFileSegmentWithoutCheck.java
++++ b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/provider/memory/MemoryFileSegmentWithoutCheck.java
+@@ -22,7 +22,7 @@ import java.util.concurrent.ExecutionException;
+ import org.apache.rocketmq.common.message.MessageQueue;
+ import org.apache.rocketmq.tieredstore.common.FileSegmentType;
+ import org.apache.rocketmq.tieredstore.common.TieredMessageStoreConfig;
+-import org.apache.rocketmq.tieredstore.provider.inputstream.TieredFileSegmentInputStream;
++import org.apache.rocketmq.tieredstore.provider.stream.FileSegmentInputStream;
+ import org.apache.rocketmq.tieredstore.util.TieredStoreUtil;
+ import org.junit.Assert;
+
+@@ -46,7 +46,7 @@ public class MemoryFileSegmentWithoutCheck extends MemoryFileSegment {
+ }
+
+ @Override
+- public CompletableFuture<Boolean> commit0(TieredFileSegmentInputStream inputStream, long position, int length,
++ public CompletableFuture<Boolean> commit0(FileSegmentInputStream inputStream, long position, int length,
+ boolean append) {
+ try {
+ if (blocker != null && !blocker.get()) {
+--
+2.32.0.windows.2
+
+
+From d000ef947d7c99918ceba0fa451c1e29fd84ba07 Mon Sep 17 00:00:00 2001
+From: yuz10 <845238369@qq.com>
+Date: Thu, 31 Aug 2023 09:41:33 +0800
+Subject: [PATCH 3/7] [ISSUE #7283] Incorrect dledger commitlog min offset
+ after mappedFile re delete failed (#7284)
+
+---
+ .../apache/rocketmq/store/dledger/DLedgerCommitLog.java | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/store/src/main/java/org/apache/rocketmq/store/dledger/DLedgerCommitLog.java b/store/src/main/java/org/apache/rocketmq/store/dledger/DLedgerCommitLog.java
+index ec5e86d70..d5f6acdc0 100644
+--- a/store/src/main/java/org/apache/rocketmq/store/dledger/DLedgerCommitLog.java
++++ b/store/src/main/java/org/apache/rocketmq/store/dledger/DLedgerCommitLog.java
+@@ -162,7 +162,12 @@ public class DLedgerCommitLog extends CommitLog {
+ if (!mappedFileQueue.getMappedFiles().isEmpty()) {
+ return mappedFileQueue.getMinOffset();
+ }
+- return dLedgerFileList.getMinOffset();
++ for (MmapFile file : dLedgerFileList.getMappedFiles()) {
++ if (file.isAvailable()) {
++ return file.getFileFromOffset() + file.getStartPosition();
++ }
++ }
++ return 0;
+ }
+
+ @Override
+--
+2.32.0.windows.2
+
+
+From f82718ae3b77a16b553c03f672dc971a2d5d48fa Mon Sep 17 00:00:00 2001
+From: cnScarb <jjhfen00@163.com>
+Date: Thu, 31 Aug 2023 15:50:10 +0800
+Subject: [PATCH 4/7] [ISSUE #7208] fix: when deleting topic also delete its
+ pop retry topic (#7209)
+
+---
+ .../processor/AdminBrokerProcessor.java | 24 ++++++++++---
+ .../processor/AdminBrokerProcessorTest.java | 36 +++++++++++++++++++
+ 2 files changed, 55 insertions(+), 5 deletions(-)
+
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessor.java
+index bbddcec2d..8fbcd3c94 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessor.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessor.java
+@@ -51,6 +51,7 @@ import org.apache.rocketmq.broker.subscription.SubscriptionGroupManager;
+ import org.apache.rocketmq.broker.transaction.queue.TransactionalMessageUtil;
+ import org.apache.rocketmq.common.AclConfig;
+ import org.apache.rocketmq.common.BrokerConfig;
++import org.apache.rocketmq.common.KeyBuilder;
+ import org.apache.rocketmq.common.LockCallback;
+ import org.apache.rocketmq.common.MQVersion;
+ import org.apache.rocketmq.common.MixAll;
+@@ -542,16 +543,29 @@ public class AdminBrokerProcessor implements NettyRequestProcessor {
+ }
+ }
+
+- this.brokerController.getTopicConfigManager().deleteTopicConfig(requestHeader.getTopic());
+- this.brokerController.getTopicQueueMappingManager().delete(requestHeader.getTopic());
+- this.brokerController.getConsumerOffsetManager().cleanOffsetByTopic(requestHeader.getTopic());
+- this.brokerController.getPopInflightMessageCounter().clearInFlightMessageNumByTopicName(requestHeader.getTopic());
+- this.brokerController.getMessageStore().deleteTopics(Sets.newHashSet(requestHeader.getTopic()));
++ final Set<String> groups = this.brokerController.getConsumerOffsetManager().whichGroupByTopic(topic);
++ // delete pop retry topics first
++ for (String group : groups) {
++ final String popRetryTopic = KeyBuilder.buildPopRetryTopic(topic, group);
++ if (brokerController.getTopicConfigManager().selectTopicConfig(popRetryTopic) != null) {
++ deleteTopicInBroker(popRetryTopic);
++ }
++ }
++ // delete topic
++ deleteTopicInBroker(topic);
+ response.setCode(ResponseCode.SUCCESS);
+ response.setRemark(null);
+ return response;
+ }
+
++ private void deleteTopicInBroker(String topic) {
++ this.brokerController.getTopicConfigManager().deleteTopicConfig(topic);
++ this.brokerController.getTopicQueueMappingManager().delete(topic);
++ this.brokerController.getConsumerOffsetManager().cleanOffsetByTopic(topic);
++ this.brokerController.getPopInflightMessageCounter().clearInFlightMessageNumByTopicName(topic);
++ this.brokerController.getMessageStore().deleteTopics(Sets.newHashSet(topic));
++ }
++
+ private synchronized RemotingCommand updateAndCreateAccessConfig(ChannelHandlerContext ctx,
+ RemotingCommand request) throws RemotingCommandException {
+ final RemotingCommand response = RemotingCommand.createResponseCommand(null);
+diff --git a/broker/src/test/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessorTest.java b/broker/src/test/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessorTest.java
+index d33a217f7..9d17011b6 100644
+--- a/broker/src/test/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessorTest.java
++++ b/broker/src/test/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessorTest.java
+@@ -29,6 +29,7 @@ import java.util.HashMap;
+ import java.util.Map;
+ import java.util.Properties;
+ import java.util.Set;
++import java.util.concurrent.ConcurrentHashMap;
+ import java.util.concurrent.ConcurrentMap;
+ import java.util.concurrent.atomic.LongAdder;
+ import org.apache.rocketmq.broker.BrokerController;
+@@ -41,6 +42,7 @@ import org.apache.rocketmq.broker.topic.RocksDBTopicConfigManager;
+ import org.apache.rocketmq.broker.topic.TopicConfigManager;
+ import org.apache.rocketmq.common.BoundaryType;
+ import org.apache.rocketmq.common.BrokerConfig;
++import org.apache.rocketmq.common.KeyBuilder;
+ import org.apache.rocketmq.common.MixAll;
+ import org.apache.rocketmq.common.TopicConfig;
+ import org.apache.rocketmq.common.TopicFilterType;
+@@ -90,8 +92,11 @@ import static org.assertj.core.api.Assertions.assertThat;
+ import static org.mockito.ArgumentMatchers.any;
+ import static org.mockito.ArgumentMatchers.anyInt;
+ import static org.mockito.ArgumentMatchers.anyLong;
++import static org.mockito.ArgumentMatchers.anySet;
+ import static org.mockito.ArgumentMatchers.anyString;
+ import static org.mockito.Mockito.mock;
++import static org.mockito.Mockito.times;
++import static org.mockito.Mockito.verify;
+ import static org.mockito.Mockito.when;
+
+ @RunWith(MockitoJUnitRunner.class)
+@@ -321,6 +326,37 @@ public class AdminBrokerProcessorTest {
+ "please execute it from master broker.");
+ }
+
++ @Test
++ public void testDeleteWithPopRetryTopic() throws Exception {
++ String topic = "topicA";
++ String anotherTopic = "another_topicA";
++
++ topicConfigManager = mock(TopicConfigManager.class);
++ when(brokerController.getTopicConfigManager()).thenReturn(topicConfigManager);
++ final ConcurrentHashMap<String, TopicConfig> topicConfigTable = new ConcurrentHashMap<>();
++ topicConfigTable.put(topic, new TopicConfig());
++ topicConfigTable.put(KeyBuilder.buildPopRetryTopic(topic, "cid1"), new TopicConfig());
++
++ topicConfigTable.put(anotherTopic, new TopicConfig());
++ topicConfigTable.put(KeyBuilder.buildPopRetryTopic(anotherTopic, "cid2"), new TopicConfig());
++ when(topicConfigManager.getTopicConfigTable()).thenReturn(topicConfigTable);
++ when(topicConfigManager.selectTopicConfig(anyString())).thenAnswer(invocation -> {
++ final String selectTopic = invocation.getArgument(0);
++ return topicConfigManager.getTopicConfigTable().get(selectTopic);
++ });
++
++ when(brokerController.getConsumerOffsetManager()).thenReturn(consumerOffsetManager);
++ when(consumerOffsetManager.whichGroupByTopic(topic)).thenReturn(Sets.newHashSet("cid1"));
++
++ RemotingCommand request = buildDeleteTopicRequest(topic);
++ RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request);
++ assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS);
++
++ verify(topicConfigManager).deleteTopicConfig(topic);
++ verify(topicConfigManager).deleteTopicConfig(KeyBuilder.buildPopRetryTopic(topic, "cid1"));
++ verify(messageStore, times(2)).deleteTopics(anySet());
++ }
++
+ @Test
+ public void testGetAllTopicConfigInRocksdb() throws Exception {
+ if (notToBeExecuted()) {
+--
+2.32.0.windows.2
+
+
+From 31d10385d1616445478104ce9ef463a8c4852ba2 Mon Sep 17 00:00:00 2001
+From: guyinyou <36399867+guyinyou@users.noreply.github.com>
+Date: Mon, 4 Sep 2023 14:09:32 +0800
+Subject: [PATCH 5/7] [ISSUE #7289] Fixed asynchronous send backpressure
+ capability
+
+Co-authored-by: guyinyou <guyinyou.gyy@alibaba-inc.com>
+---
+ .../impl/producer/DefaultMQProducerImpl.java | 77 +++++++++++++------
+ 1 file changed, 53 insertions(+), 24 deletions(-)
+
+diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java
+index bbbb17b07..2d6b83ac2 100644
+--- a/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java
++++ b/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java
+@@ -547,6 +547,8 @@ public class DefaultMQProducerImpl implements MQProducerInner {
+ @Deprecated
+ public void send(final Message msg, final SendCallback sendCallback, final long timeout)
+ throws MQClientException, RemotingException, InterruptedException {
++ BackpressureSendCallBack newCallBack = new BackpressureSendCallBack(sendCallback);
++
+ final long beginStartTime = System.currentTimeMillis();
+ Runnable runnable = new Runnable() {
+ @Override
+@@ -554,20 +556,53 @@ public class DefaultMQProducerImpl implements MQProducerInner {
+ long costTime = System.currentTimeMillis() - beginStartTime;
+ if (timeout > costTime) {
+ try {
+- sendDefaultImpl(msg, CommunicationMode.ASYNC, sendCallback, timeout - costTime);
++ sendDefaultImpl(msg, CommunicationMode.ASYNC, newCallBack, timeout - costTime);
+ } catch (Exception e) {
+- sendCallback.onException(e);
++ newCallBack.onException(e);
+ }
+ } else {
+- sendCallback.onException(
++ newCallBack.onException(
+ new RemotingTooMuchRequestException("DEFAULT ASYNC send call timeout"));
+ }
+ }
+ };
+- executeAsyncMessageSend(runnable, msg, sendCallback, timeout, beginStartTime);
++ executeAsyncMessageSend(runnable, msg, newCallBack, timeout, beginStartTime);
+ }
+
+- public void executeAsyncMessageSend(Runnable runnable, final Message msg, final SendCallback sendCallback,
++ class BackpressureSendCallBack implements SendCallback {
++ public boolean isSemaphoreAsyncSizeAquired = false;
++ public boolean isSemaphoreAsyncNumAquired = false;
++ public int msgLen;
++ private final SendCallback sendCallback;
++
++ public BackpressureSendCallBack(final SendCallback sendCallback) {
++ this.sendCallback = sendCallback;
++ }
++
++ @Override
++ public void onSuccess(SendResult sendResult) {
++ if (isSemaphoreAsyncSizeAquired) {
++ semaphoreAsyncSendSize.release(msgLen);
++ }
++ if (isSemaphoreAsyncNumAquired) {
++ semaphoreAsyncSendNum.release();
++ }
++ sendCallback.onSuccess(sendResult);
++ }
++
++ @Override
++ public void onException(Throwable e) {
++ if (isSemaphoreAsyncSizeAquired) {
++ semaphoreAsyncSendSize.release(msgLen);
++ }
++ if (isSemaphoreAsyncNumAquired) {
++ semaphoreAsyncSendNum.release();
++ }
++ sendCallback.onException(e);
++ }
++ }
++
++ public void executeAsyncMessageSend(Runnable runnable, final Message msg, final BackpressureSendCallBack sendCallback,
+ final long timeout, final long beginStartTime)
+ throws MQClientException, InterruptedException {
+ ExecutorService executor = this.getAsyncSenderExecutor();
+@@ -595,7 +630,9 @@ public class DefaultMQProducerImpl implements MQProducerInner {
+ return;
+ }
+ }
+-
++ sendCallback.isSemaphoreAsyncSizeAquired = isSemaphoreAsyncSizeAquired;
++ sendCallback.isSemaphoreAsyncNumAquired = isSemaphoreAsyncNumAquired;
++ sendCallback.msgLen = msgLen;
+ executor.submit(runnable);
+ } catch (RejectedExecutionException e) {
+ if (isEnableBackpressureForAsyncMode) {
+@@ -603,15 +640,7 @@ public class DefaultMQProducerImpl implements MQProducerInner {
+ } else {
+ throw new MQClientException("executor rejected ", e);
+ }
+- } finally {
+- if (isSemaphoreAsyncSizeAquired) {
+- semaphoreAsyncSendSize.release(msgLen);
+- }
+- if (isSemaphoreAsyncNumAquired) {
+- semaphoreAsyncSendNum.release();
+- }
+ }
+-
+ }
+
+ public MessageQueue invokeMessageQueueSelector(Message msg, MessageQueueSelector selector, Object arg,
+@@ -1188,7 +1217,7 @@ public class DefaultMQProducerImpl implements MQProducerInner {
+ @Deprecated
+ public void send(final Message msg, final MessageQueue mq, final SendCallback sendCallback, final long timeout)
+ throws MQClientException, RemotingException, InterruptedException {
+-
++ BackpressureSendCallBack newCallBack = new BackpressureSendCallBack(sendCallback);
+ final long beginStartTime = System.currentTimeMillis();
+ Runnable runnable = new Runnable() {
+ @Override
+@@ -1203,22 +1232,22 @@ public class DefaultMQProducerImpl implements MQProducerInner {
+ long costTime = System.currentTimeMillis() - beginStartTime;
+ if (timeout > costTime) {
+ try {
+- sendKernelImpl(msg, mq, CommunicationMode.ASYNC, sendCallback, null,
++ sendKernelImpl(msg, mq, CommunicationMode.ASYNC, newCallBack, null,
+ timeout - costTime);
+ } catch (MQBrokerException e) {
+ throw new MQClientException("unknown exception", e);
+ }
+ } else {
+- sendCallback.onException(new RemotingTooMuchRequestException("call timeout"));
++ newCallBack.onException(new RemotingTooMuchRequestException("call timeout"));
+ }
+ } catch (Exception e) {
+- sendCallback.onException(e);
++ newCallBack.onException(e);
+ }
+ }
+
+ };
+
+- executeAsyncMessageSend(runnable, msg, sendCallback, timeout, beginStartTime);
++ executeAsyncMessageSend(runnable, msg, newCallBack, timeout, beginStartTime);
+ }
+
+ /**
+@@ -1315,7 +1344,7 @@ public class DefaultMQProducerImpl implements MQProducerInner {
+ public void send(final Message msg, final MessageQueueSelector selector, final Object arg,
+ final SendCallback sendCallback, final long timeout)
+ throws MQClientException, RemotingException, InterruptedException {
+-
++ BackpressureSendCallBack newCallBack = new BackpressureSendCallBack(sendCallback);
+ final long beginStartTime = System.currentTimeMillis();
+ Runnable runnable = new Runnable() {
+ @Override
+@@ -1324,21 +1353,21 @@ public class DefaultMQProducerImpl implements MQProducerInner {
+ if (timeout > costTime) {
+ try {
+ try {
+- sendSelectImpl(msg, selector, arg, CommunicationMode.ASYNC, sendCallback,
++ sendSelectImpl(msg, selector, arg, CommunicationMode.ASYNC, newCallBack,
+ timeout - costTime);
+ } catch (MQBrokerException e) {
+ throw new MQClientException("unknown exception", e);
+ }
+ } catch (Exception e) {
+- sendCallback.onException(e);
++ newCallBack.onException(e);
+ }
+ } else {
+- sendCallback.onException(new RemotingTooMuchRequestException("call timeout"));
++ newCallBack.onException(new RemotingTooMuchRequestException("call timeout"));
+ }
+ }
+
+ };
+- executeAsyncMessageSend(runnable, msg, sendCallback, timeout, beginStartTime);
++ executeAsyncMessageSend(runnable, msg, newCallBack, timeout, beginStartTime);
+ }
+
+ /**
+--
+2.32.0.windows.2
+
+
+From d67b9d64cbd53824798af57ba18770e0fcefa37a Mon Sep 17 00:00:00 2001
+From: yuz10 <845238369@qq.com>
+Date: Wed, 6 Sep 2023 14:07:23 +0800
+Subject: [PATCH 6/7] [ISSUE #7302] Fix singleTopicRegister code deleted in
+ merge
+
+---
+ .../apache/rocketmq/broker/topic/TopicConfigManager.java | 6 +++++-
+ 1 file changed, 5 insertions(+), 1 deletion(-)
+
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java b/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java
+index 1c3b9711f..4e3c1736c 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java
+@@ -330,7 +330,11 @@ public class TopicConfigManager extends ConfigManager {
+ log.error("createTopicIfAbsent ", e);
+ }
+ if (createNew && register) {
+- this.brokerController.registerIncrementBrokerData(topicConfig, dataVersion);
++ if (brokerController.getBrokerConfig().isEnableSingleTopicRegister()) {
++ this.brokerController.registerSingleTopicAll(topicConfig);
++ } else {
++ this.brokerController.registerIncrementBrokerData(topicConfig, dataVersion);
++ }
+ }
+ return getTopicConfig(topicConfig.getTopicName());
+ }
+--
+2.32.0.windows.2
+
+
+From 37017dbaec5c521fd529ef4aecf3658092884f84 Mon Sep 17 00:00:00 2001
+From: lizhimins <707364882@qq.com>
+Date: Wed, 6 Sep 2023 15:23:15 +0800
+Subject: [PATCH 7/7] [ISSUE #7305] Fix metrics and transactional module not
+ shutdown while broker offline cause coredump(#7307)
+
+---
+ .../java/org/apache/rocketmq/broker/BrokerController.java | 8 ++++++++
+ .../queue/TransactionalMessageServiceImpl.java | 4 +++-
+ 2 files changed, 11 insertions(+), 1 deletion(-)
+
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java
+index e8f943702..6aba70cb2 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java
+@@ -1302,6 +1302,10 @@ public class BrokerController {
+ this.fastRemotingServer.shutdown();
+ }
+
++ if (this.brokerMetricsManager != null) {
++ this.brokerMetricsManager.shutdown();
++ }
++
+ if (this.brokerStatsManager != null) {
+ this.brokerStatsManager.shutdown();
+ }
+@@ -1324,6 +1328,10 @@ public class BrokerController {
+ this.ackMessageProcessor.shutdownPopReviveService();
+ }
+
++ if (this.transactionalMessageService != null) {
++ this.transactionalMessageService.close();
++ }
++
+ if (this.notificationProcessor != null) {
+ this.notificationProcessor.getPopLongPollingService().shutdown();
+ }
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/transaction/queue/TransactionalMessageServiceImpl.java b/broker/src/main/java/org/apache/rocketmq/broker/transaction/queue/TransactionalMessageServiceImpl.java
+index 93fa725a9..48db828e0 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/transaction/queue/TransactionalMessageServiceImpl.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/transaction/queue/TransactionalMessageServiceImpl.java
+@@ -629,7 +629,9 @@ public class TransactionalMessageServiceImpl implements TransactionalMessageServ
+
+ @Override
+ public void close() {
+-
++ if (this.transactionalOpBatchService != null) {
++ this.transactionalOpBatchService.shutdown();
++ }
+ }
+
+ public Message getOpMessage(int queueId, String moreData) {
+--
+2.32.0.windows.2
+
diff --git a/patch016-backport-Optimize-fault-tolerant-mechanism.patch b/patch016-backport-Optimize-fault-tolerant-mechanism.patch
new file mode 100644
index 000000000..9d8ef20a1
--- /dev/null
+++ b/patch016-backport-Optimize-fault-tolerant-mechanism.patch
@@ -0,0 +1,520 @@
+From e11e29419f6e2d1d9673d0329e57b824ebf3da47 Mon Sep 17 00:00:00 2001
+From: lizhimins <707364882@qq.com>
+Date: Wed, 6 Sep 2023 20:42:24 +0800
+Subject: [PATCH 1/3] [ISSUE #7308] Adding topic blacklist and filter in tiered
+ storage module (#7310)
+
+---
+ .../tieredstore/TieredDispatcher.java | 21 +++++++--
+ .../tieredstore/TieredMessageStore.java | 1 +
+ .../file/TieredFlatFileManager.java | 17 ++++---
+ .../TieredStoreTopicBlackListFilter.java | 45 +++++++++++++++++++
+ .../provider/TieredStoreTopicFilter.java | 25 +++++++++++
+ .../TieredStoreTopicBlackListFilterTest.java | 36 +++++++++++++++
+ 6 files changed, 136 insertions(+), 9 deletions(-)
+ create mode 100644 tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/TieredStoreTopicBlackListFilter.java
+ create mode 100644 tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/TieredStoreTopicFilter.java
+ create mode 100644 tieredstore/src/test/java/org/apache/rocketmq/tieredstore/provider/TieredStoreTopicBlackListFilterTest.java
+
+diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredDispatcher.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredDispatcher.java
+index 430c2b62e..766c559e9 100644
+--- a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredDispatcher.java
++++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredDispatcher.java
+@@ -48,6 +48,8 @@ import org.apache.rocketmq.tieredstore.file.CompositeQueueFlatFile;
+ import org.apache.rocketmq.tieredstore.file.TieredFlatFileManager;
+ import org.apache.rocketmq.tieredstore.metrics.TieredStoreMetricsConstant;
+ import org.apache.rocketmq.tieredstore.metrics.TieredStoreMetricsManager;
++import org.apache.rocketmq.tieredstore.provider.TieredStoreTopicBlackListFilter;
++import org.apache.rocketmq.tieredstore.provider.TieredStoreTopicFilter;
+ import org.apache.rocketmq.tieredstore.util.CQItemBufferUtil;
+ import org.apache.rocketmq.tieredstore.util.MessageBufferUtil;
+ import org.apache.rocketmq.tieredstore.util.TieredStoreUtil;
+@@ -56,6 +58,7 @@ public class TieredDispatcher extends ServiceThread implements CommitLogDispatch
+
+ private static final Logger logger = LoggerFactory.getLogger(TieredStoreUtil.TIERED_STORE_LOGGER_NAME);
+
++ private TieredStoreTopicFilter topicFilter;
+ private final String brokerName;
+ private final MessageStore defaultStore;
+ private final TieredMessageStoreConfig storeConfig;
+@@ -70,15 +73,15 @@ public class TieredDispatcher extends ServiceThread implements CommitLogDispatch
+ this.defaultStore = defaultStore;
+ this.storeConfig = storeConfig;
+ this.brokerName = storeConfig.getBrokerName();
++ this.topicFilter = new TieredStoreTopicBlackListFilter();
+ this.tieredFlatFileManager = TieredFlatFileManager.getInstance(storeConfig);
+ this.dispatchRequestReadMap = new ConcurrentHashMap<>();
+ this.dispatchRequestWriteMap = new ConcurrentHashMap<>();
+ this.dispatchTaskLock = new ReentrantLock();
+ this.dispatchWriteLock = new ReentrantLock();
+- this.initScheduleTask();
+ }
+
+- private void initScheduleTask() {
++ protected void initScheduleTask() {
+ TieredStoreExecutor.commonScheduledExecutor.scheduleWithFixedDelay(() ->
+ tieredFlatFileManager.deepCopyFlatFileToList().forEach(flatFile -> {
+ if (!flatFile.getCompositeFlatFileLock().isLocked()) {
+@@ -87,6 +90,14 @@ public class TieredDispatcher extends ServiceThread implements CommitLogDispatch
+ }), 30, 10, TimeUnit.SECONDS);
+ }
+
++ public TieredStoreTopicFilter getTopicFilter() {
++ return topicFilter;
++ }
++
++ public void setTopicFilter(TieredStoreTopicFilter topicFilter) {
++ this.topicFilter = topicFilter;
++ }
++
+ @Override
+ public void dispatch(DispatchRequest request) {
+ if (stopped) {
+@@ -94,7 +105,7 @@ public class TieredDispatcher extends ServiceThread implements CommitLogDispatch
+ }
+
+ String topic = request.getTopic();
+- if (TieredStoreUtil.isSystemTopic(topic)) {
++ if (topicFilter != null && topicFilter.filterTopic(topic)) {
+ return;
+ }
+
+@@ -219,6 +230,10 @@ public class TieredDispatcher extends ServiceThread implements CommitLogDispatch
+ return;
+ }
+
++ if (topicFilter != null && topicFilter.filterTopic(flatFile.getMessageQueue().getTopic())) {
++ return;
++ }
++
+ if (flatFile.getDispatchOffset() == -1L) {
+ return;
+ }
+diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredMessageStore.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredMessageStore.java
+index 78e855f36..9fb1b2f01 100644
+--- a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredMessageStore.java
++++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredMessageStore.java
+@@ -90,6 +90,7 @@ public class TieredMessageStore extends AbstractPluginMessageStore {
+ boolean loadNextStore = next.load();
+ boolean result = loadFlatFile && loadNextStore;
+ if (result) {
++ dispatcher.initScheduleTask();
+ dispatcher.start();
+ }
+ return result;
+diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredFlatFileManager.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredFlatFileManager.java
+index e9ae4a5a5..7c744af3b 100644
+--- a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredFlatFileManager.java
++++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredFlatFileManager.java
+@@ -134,21 +134,21 @@ public class TieredFlatFileManager {
+ public void doCleanExpiredFile() {
+ long expiredTimeStamp = System.currentTimeMillis() -
+ TimeUnit.HOURS.toMillis(storeConfig.getTieredStoreFileReservedTime());
+- Random random = new Random();
+ for (CompositeQueueFlatFile flatFile : deepCopyFlatFileToList()) {
+- int delay = random.nextInt(storeConfig.getMaxCommitJitter());
+- TieredStoreExecutor.cleanExpiredFileExecutor.schedule(() -> {
++ TieredStoreExecutor.cleanExpiredFileExecutor.submit(() -> {
+ flatFile.getCompositeFlatFileLock().lock();
+ try {
+ flatFile.cleanExpiredFile(expiredTimeStamp);
+ flatFile.destroyExpiredFile();
+ if (flatFile.getConsumeQueueBaseOffset() == -1) {
++ logger.info("Clean flatFile because file not initialized, topic={}, queueId={}",
++ flatFile.getMessageQueue().getTopic(), flatFile.getMessageQueue().getQueueId());
+ destroyCompositeFile(flatFile.getMessageQueue());
+ }
+ } finally {
+ flatFile.getCompositeFlatFileLock().unlock();
+ }
+- }, delay, TimeUnit.MILLISECONDS);
++ });
+ }
+ if (indexFile != null) {
+ indexFile.cleanExpiredFile(expiredTimeStamp);
+@@ -218,8 +218,13 @@ public class TieredFlatFileManager {
+ storeConfig.getBrokerName(), queueMetadata.getQueue().getQueueId()));
+ queueCount.incrementAndGet();
+ });
+- logger.info("Recover TopicFlatFile, topic: {}, queueCount: {}, cost: {}ms",
+- topicMetadata.getTopic(), queueCount.get(), subWatch.elapsed(TimeUnit.MILLISECONDS));
++
++ if (queueCount.get() == 0L) {
++ metadataStore.deleteTopic(topicMetadata.getTopic());
++ } else {
++ logger.info("Recover TopicFlatFile, topic: {}, queueCount: {}, cost: {}ms",
++ topicMetadata.getTopic(), queueCount.get(), subWatch.elapsed(TimeUnit.MILLISECONDS));
++ }
+ } catch (Exception e) {
+ logger.error("Recover TopicFlatFile error, topic: {}", topicMetadata.getTopic(), e);
+ } finally {
+diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/TieredStoreTopicBlackListFilter.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/TieredStoreTopicBlackListFilter.java
+new file mode 100644
+index 000000000..50adbb713
+--- /dev/null
++++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/TieredStoreTopicBlackListFilter.java
+@@ -0,0 +1,45 @@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one or more
++ * contributor license agreements. See the NOTICE file distributed with
++ * this work for additional information regarding copyright ownership.
++ * The ASF licenses this file to You under the Apache License, Version 2.0
++ * (the "License"); you may not use this file except in compliance with
++ * the License. You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package org.apache.rocketmq.tieredstore.provider;
++
++import java.util.HashSet;
++import java.util.Set;
++import org.apache.commons.lang3.StringUtils;
++import org.apache.rocketmq.tieredstore.util.TieredStoreUtil;
++
++public class TieredStoreTopicBlackListFilter implements TieredStoreTopicFilter {
++
++ private final Set<String> topicBlackSet;
++
++ public TieredStoreTopicBlackListFilter() {
++ this.topicBlackSet = new HashSet<>();
++ }
++
++ @Override
++ public boolean filterTopic(String topicName) {
++ if (StringUtils.isBlank(topicName)) {
++ return true;
++ }
++ return TieredStoreUtil.isSystemTopic(topicName) || topicBlackSet.contains(topicName);
++ }
++
++ @Override
++ public void addTopicToWhiteList(String topicName) {
++ this.topicBlackSet.add(topicName);
++ }
++}
+diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/TieredStoreTopicFilter.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/TieredStoreTopicFilter.java
+new file mode 100644
+index 000000000..3f26b8b02
+--- /dev/null
++++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/TieredStoreTopicFilter.java
+@@ -0,0 +1,25 @@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one or more
++ * contributor license agreements. See the NOTICE file distributed with
++ * this work for additional information regarding copyright ownership.
++ * The ASF licenses this file to You under the Apache License, Version 2.0
++ * (the "License"); you may not use this file except in compliance with
++ * the License. You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package org.apache.rocketmq.tieredstore.provider;
++
++public interface TieredStoreTopicFilter {
++
++ boolean filterTopic(String topicName);
++
++ void addTopicToWhiteList(String topicName);
++}
+diff --git a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/provider/TieredStoreTopicBlackListFilterTest.java b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/provider/TieredStoreTopicBlackListFilterTest.java
+new file mode 100644
+index 000000000..2bf48173c
+--- /dev/null
++++ b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/provider/TieredStoreTopicBlackListFilterTest.java
+@@ -0,0 +1,36 @@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one or more
++ * contributor license agreements. See the NOTICE file distributed with
++ * this work for additional information regarding copyright ownership.
++ * The ASF licenses this file to You under the Apache License, Version 2.0
++ * (the "License"); you may not use this file except in compliance with
++ * the License. You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++package org.apache.rocketmq.tieredstore.provider;
++
++import org.apache.rocketmq.common.topic.TopicValidator;
++import org.junit.Assert;
++import org.junit.Test;
++
++public class TieredStoreTopicBlackListFilterTest {
++
++ @Test
++ public void filterTopicTest() {
++ TieredStoreTopicFilter topicFilter = new TieredStoreTopicBlackListFilter();
++ Assert.assertTrue(topicFilter.filterTopic(""));
++ Assert.assertTrue(topicFilter.filterTopic(TopicValidator.SYSTEM_TOPIC_PREFIX + "_Topic"));
++
++ String topicName = "WhiteTopic";
++ Assert.assertFalse(topicFilter.filterTopic(topicName));
++ topicFilter.addTopicToWhiteList(topicName);
++ Assert.assertTrue(topicFilter.filterTopic(topicName));
++ }
++}
+\ No newline at end of file
+--
+2.32.0.windows.2
+
+
+From 628020537fa7035226bc8dcde9fa33d9d5df30ff Mon Sep 17 00:00:00 2001
+From: rongtong <jinrongtong5@163.com>
+Date: Thu, 7 Sep 2023 16:17:47 +0800
+Subject: [PATCH 2/3] [ISSUE #7293] Fix NPE when alter sync state set
+
+---
+ .../rocketmq/controller/impl/manager/ReplicasInfoManager.java | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/controller/src/main/java/org/apache/rocketmq/controller/impl/manager/ReplicasInfoManager.java b/controller/src/main/java/org/apache/rocketmq/controller/impl/manager/ReplicasInfoManager.java
+index b0a67531d..d83a690f9 100644
+--- a/controller/src/main/java/org/apache/rocketmq/controller/impl/manager/ReplicasInfoManager.java
++++ b/controller/src/main/java/org/apache/rocketmq/controller/impl/manager/ReplicasInfoManager.java
+@@ -104,7 +104,7 @@ public class ReplicasInfoManager {
+ }
+
+ // Check master
+- if (!syncStateInfo.getMasterBrokerId().equals(request.getMasterBrokerId())) {
++ if (syncStateInfo.getMasterBrokerId() == null || !syncStateInfo.getMasterBrokerId().equals(request.getMasterBrokerId())) {
+ String err = String.format("Rejecting alter syncStateSet request because the current leader is:{%s}, not {%s}",
+ syncStateInfo.getMasterBrokerId(), request.getMasterBrokerId());
+ LOGGER.error("{}", err);
+--
+2.32.0.windows.2
+
+
+From 6fd0073d6475c539e8f4c30dc4f104a56a21d724 Mon Sep 17 00:00:00 2001
+From: Ji Juntao <juntao.jjt@alibaba-inc.com>
+Date: Thu, 7 Sep 2023 20:21:16 +0800
+Subject: [PATCH 3/3] [ISSUE #7319] Optimize fault-tolerant mechanism for
+ sending messages and hot update switch (#7320)
+
+---
+ .../impl/producer/DefaultMQProducerImpl.java | 8 ++------
+ .../client/latency/LatencyFaultTolerance.java | 14 +++++++++++++
+ .../latency/LatencyFaultToleranceImpl.java | 13 +++++++++++-
+ .../client/latency/MQFaultStrategy.java | 20 +++++++------------
+ .../proxy/service/route/MessageQueueView.java | 9 ---------
+ .../service/route/TopicRouteService.java | 10 +++++++++-
+ 6 files changed, 44 insertions(+), 30 deletions(-)
+
+diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java
+index 2d6b83ac2..b0c212e46 100644
+--- a/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java
++++ b/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java
+@@ -263,9 +263,7 @@ public class DefaultMQProducerImpl implements MQProducerInner {
+ mQClientFactory.start();
+ }
+
+- if (this.mqFaultStrategy.isStartDetectorEnable()) {
+- this.mqFaultStrategy.startDetector();
+- }
++ this.mqFaultStrategy.startDetector();
+
+ log.info("the producer [{}] start OK. sendMessageWithVIPChannel={}", this.defaultMQProducer.getProducerGroup(),
+ this.defaultMQProducer.isSendMessageWithVIPChannel());
+@@ -311,9 +309,7 @@ public class DefaultMQProducerImpl implements MQProducerInner {
+ if (shutdownFactory) {
+ this.mQClientFactory.shutdown();
+ }
+- if (this.mqFaultStrategy.isStartDetectorEnable()) {
+- this.mqFaultStrategy.shutdown();
+- }
++ this.mqFaultStrategy.shutdown();
+ RequestFutureHolder.getInstance().shutdown(this);
+ log.info("the producer [{}] shutdown OK", this.defaultMQProducer.getProducerGroup());
+ this.serviceState = ServiceState.SHUTDOWN_ALREADY;
+diff --git a/client/src/main/java/org/apache/rocketmq/client/latency/LatencyFaultTolerance.java b/client/src/main/java/org/apache/rocketmq/client/latency/LatencyFaultTolerance.java
+index 72d2f3450..17aaa266a 100644
+--- a/client/src/main/java/org/apache/rocketmq/client/latency/LatencyFaultTolerance.java
++++ b/client/src/main/java/org/apache/rocketmq/client/latency/LatencyFaultTolerance.java
+@@ -89,4 +89,18 @@ public interface LatencyFaultTolerance<T> {
+ * @param detectInterval each broker's detecting interval
+ */
+ void setDetectInterval(final int detectInterval);
++
++ /**
++ * Use it to set the detector work or not.
++ *
++ * @param startDetectorEnable set the detector's work status
++ */
++ void setStartDetectorEnable(final boolean startDetectorEnable);
++
++ /**
++ * Use it to judge if the detector enabled.
++ *
++ * @return is the detector should be started.
++ */
++ boolean isStartDetectorEnable();
+ }
+diff --git a/client/src/main/java/org/apache/rocketmq/client/latency/LatencyFaultToleranceImpl.java b/client/src/main/java/org/apache/rocketmq/client/latency/LatencyFaultToleranceImpl.java
+index 8af629574..d3ff7eb45 100644
+--- a/client/src/main/java/org/apache/rocketmq/client/latency/LatencyFaultToleranceImpl.java
++++ b/client/src/main/java/org/apache/rocketmq/client/latency/LatencyFaultToleranceImpl.java
+@@ -37,6 +37,8 @@ public class LatencyFaultToleranceImpl implements LatencyFaultTolerance<String>
+ private int detectTimeout = 200;
+ private int detectInterval = 2000;
+ private final ThreadLocalIndex whichItemWorst = new ThreadLocalIndex();
++
++ private volatile boolean startDetectorEnable = false;
+ private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
+ @Override
+ public Thread newThread(Runnable r) {
+@@ -80,7 +82,9 @@ public class LatencyFaultToleranceImpl implements LatencyFaultTolerance<String>
+ @Override
+ public void run() {
+ try {
+- detectByOneRound();
++ if (startDetectorEnable) {
++ detectByOneRound();
++ }
+ } catch (Exception e) {
+ log.warn("Unexpected exception raised while detecting service reachability", e);
+ }
+@@ -137,6 +141,13 @@ public class LatencyFaultToleranceImpl implements LatencyFaultTolerance<String>
+ this.faultItemTable.remove(name);
+ }
+
++ public boolean isStartDetectorEnable() {
++ return startDetectorEnable;
++ }
++
++ public void setStartDetectorEnable(boolean startDetectorEnable) {
++ this.startDetectorEnable = startDetectorEnable;
++ }
+ @Override
+ public String pickOneAtLeast() {
+ final Enumeration<FaultItem> elements = this.faultItemTable.elements();
+diff --git a/client/src/main/java/org/apache/rocketmq/client/latency/MQFaultStrategy.java b/client/src/main/java/org/apache/rocketmq/client/latency/MQFaultStrategy.java
+index c01490784..69fb533e5 100644
+--- a/client/src/main/java/org/apache/rocketmq/client/latency/MQFaultStrategy.java
++++ b/client/src/main/java/org/apache/rocketmq/client/latency/MQFaultStrategy.java
+@@ -24,8 +24,8 @@ import org.apache.rocketmq.common.message.MessageQueue;
+
+ public class MQFaultStrategy {
+ private LatencyFaultTolerance<String> latencyFaultTolerance;
+- private boolean sendLatencyFaultEnable;
+- private boolean startDetectorEnable;
++ private volatile boolean sendLatencyFaultEnable;
++ private volatile boolean startDetectorEnable;
+ private long[] latencyMax = {50L, 100L, 550L, 1800L, 3000L, 5000L, 15000L};
+ private long[] notAvailableDuration = {0L, 0L, 2000L, 5000L, 6000L, 10000L, 30000L};
+
+@@ -64,11 +64,11 @@ public class MQFaultStrategy {
+
+
+ public MQFaultStrategy(ClientConfig cc, Resolver fetcher, ServiceDetector serviceDetector) {
+- this.setStartDetectorEnable(cc.isStartDetectorEnable());
+- this.setSendLatencyFaultEnable(cc.isSendLatencyEnable());
+ this.latencyFaultTolerance = new LatencyFaultToleranceImpl(fetcher, serviceDetector);
+ this.latencyFaultTolerance.setDetectInterval(cc.getDetectInterval());
+ this.latencyFaultTolerance.setDetectTimeout(cc.getDetectTimeout());
++ this.setStartDetectorEnable(cc.isStartDetectorEnable());
++ this.setSendLatencyFaultEnable(cc.isSendLatencyEnable());
+ }
+
+ // For unit test.
+@@ -123,21 +123,15 @@ public class MQFaultStrategy {
+
+ public void setStartDetectorEnable(boolean startDetectorEnable) {
+ this.startDetectorEnable = startDetectorEnable;
++ this.latencyFaultTolerance.setStartDetectorEnable(startDetectorEnable);
+ }
+
+ public void startDetector() {
+- // user should start the detector
+- // and the thread should not be in running state.
+- if (this.sendLatencyFaultEnable && this.startDetectorEnable) {
+- // start the detector.
+- this.latencyFaultTolerance.startDetector();
+- }
++ this.latencyFaultTolerance.startDetector();
+ }
+
+ public void shutdown() {
+- if (this.sendLatencyFaultEnable && this.startDetectorEnable) {
+- this.latencyFaultTolerance.shutdown();
+- }
++ this.latencyFaultTolerance.shutdown();
+ }
+
+ public MessageQueue selectOneMessageQueue(final TopicPublishInfo tpInfo, final String lastBrokerName, final boolean resetIndex) {
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/MessageQueueView.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/MessageQueueView.java
+index 8b3c2f7c8..898e529f8 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/MessageQueueView.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/MessageQueueView.java
+@@ -26,7 +26,6 @@ public class MessageQueueView {
+ private final MessageQueueSelector readSelector;
+ private final MessageQueueSelector writeSelector;
+ private final TopicRouteWrapper topicRouteWrapper;
+- private MQFaultStrategy mqFaultStrategy;
+
+ public MessageQueueView(String topic, TopicRouteData topicRouteData, MQFaultStrategy mqFaultStrategy) {
+ this.topicRouteWrapper = new TopicRouteWrapper(topicRouteData, topic);
+@@ -67,12 +66,4 @@ public class MessageQueueView {
+ .add("topicRouteWrapper", topicRouteWrapper)
+ .toString();
+ }
+-
+- public MQFaultStrategy getMQFaultStrategy() {
+- return mqFaultStrategy;
+- }
+-
+- public void setMQFaultStrategy(MQFaultStrategy mqFaultStrategy) {
+- this.mqFaultStrategy = mqFaultStrategy;
+- }
+ }
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java
+index 74769a423..caf62a1e0 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java
+@@ -127,7 +127,7 @@ public abstract class TopicRouteService extends AbstractStartAndShutdown {
+ @Override
+ public String resolve(String name) {
+ try {
+- String brokerAddr = getBrokerAddr(null, name);
++ String brokerAddr = getBrokerAddr(ProxyContext.createForInner("MQFaultStrategy"), name);
+ return brokerAddr;
+ } catch (Exception e) {
+ return null;
+@@ -175,9 +175,17 @@ public abstract class TopicRouteService extends AbstractStartAndShutdown {
+
+ public void updateFaultItem(final String brokerName, final long currentLatency, boolean isolation,
+ boolean reachable) {
++ checkSendFaultToleranceEnable();
+ this.mqFaultStrategy.updateFaultItem(brokerName, currentLatency, isolation, reachable);
+ }
+
++ public void checkSendFaultToleranceEnable() {
++ boolean hotLatencySwitch = ConfigurationManager.getProxyConfig().isSendLatencyEnable();
++ boolean hotDetectorSwitch = ConfigurationManager.getProxyConfig().isStartDetectorEnable();
++ this.mqFaultStrategy.setSendLatencyFaultEnable(hotLatencySwitch);
++ this.mqFaultStrategy.setStartDetectorEnable(hotDetectorSwitch);
++ }
++
+ public MQFaultStrategy getMqFaultStrategy() {
+ return this.mqFaultStrategy;
+ }
+--
+2.32.0.windows.2
+
diff --git a/patch017-backport-Convergent-thread-pool-creation.patch b/patch017-backport-Convergent-thread-pool-creation.patch
new file mode 100644
index 000000000..92d0bd2e9
--- /dev/null
+++ b/patch017-backport-Convergent-thread-pool-creation.patch
@@ -0,0 +1,2243 @@
+From c100d815d754d7cb330bc63e145bafd2d9b59cb1 Mon Sep 17 00:00:00 2001
+From: guyinyou <36399867+guyinyou@users.noreply.github.com>
+Date: Mon, 11 Sep 2023 10:13:56 +0800
+Subject: [PATCH 1/6] [ISSUE #7328] Convergent thread pool creation (#7329)
+
+* Convergence thread pool creation to facilitate subsequent iteration management
+
+* Convergence thread pool creation in ThreadPoolMonitor.java
+
+* fix unit test
+
+* Convergence ThreadPool constructor
+
+* Convergence ScheduledThreadPool constructor
+
+* remove unused import
+
+* Convergence ScheduledThreadPool constructor
+
+* remove unused import
+
+---------
+---
+ .../rocketmq/broker/BrokerController.java | 39 +++++-----
+ .../client/ClientHousekeepingService.java | 4 +-
+ .../DefaultConsumerIdsChangeListener.java | 3 +-
+ .../broker/controller/ReplicasManager.java | 9 +--
+ .../dledger/DLedgerRoleChangeHandler.java | 4 +-
+ .../broker/failover/EscapeBridge.java | 4 +-
+ .../broker/latency/BrokerFastFailure.java | 5 +-
+ .../BrokerFixedThreadPoolExecutor.java | 57 --------------
+ .../broker/latency/FutureTaskExt.java | 39 ----------
+ .../rocketmq/broker/out/BrokerOuterAPI.java | 7 +-
+ .../schedule/ScheduleMessageService.java | 7 +-
+ .../broker/topic/TopicRouteInfoManager.java | 4 +-
+ ...ractTransactionalMessageCheckListener.java | 4 +-
+ .../rocketmq/broker/BrokerControllerTest.java | 2 +-
+ .../broker/latency/BrokerFastFailureTest.java | 1 +
+ .../common/config/AbstractRocksDBStorage.java | 6 +-
+ .../FutureTaskExtThreadPoolExecutor.java | 3 +-
+ .../common/thread/ThreadPoolMonitor.java | 6 +-
+ .../rocketmq/common/utils/ThreadUtils.java | 74 ++++++++++++++++---
+ .../rocketmq/container/BrokerContainer.java | 6 +-
+ .../controller/ControllerManager.java | 14 +---
+ .../controller/impl/DLedgerController.java | 10 +--
+ .../DefaultBrokerHeartbeatManager.java | 3 +-
+ .../rocketmq/namesrv/NamesrvController.java | 22 ++----
+ .../grpc/v2/channel/GrpcChannelManager.java | 6 +-
+ .../remoting/RemotingProtocolServer.java | 4 +-
+ .../proxy/service/ClusterServiceManager.java | 12 +--
+ .../proxy/service/LocalServiceManager.java | 4 +-
+ .../receipt/DefaultReceiptHandleManager.java | 8 +-
+ .../service/route/TopicRouteService.java | 9 +--
+ .../remoting/netty/NettyRemotingClient.java | 4 +-
+ .../remoting/netty/NettyRemotingServer.java | 4 +-
+ .../rocketmq/store/DefaultMessageStore.java | 8 +-
+ .../ha/autoswitch/AutoSwitchHAService.java | 38 +++++-----
+ .../rocketmq/store/kv/CompactionStore.java | 21 +++---
+ .../store/queue/ConsumeQueueStore.java | 4 +-
+ .../store/stats/BrokerStatsManager.java | 14 ++--
+ .../store/timer/TimerMessageStore.java | 6 +-
+ .../apache/rocketmq/test/util/StatUtil.java | 1 -
+ .../common/TieredStoreExecutor.java | 14 ++--
+ .../tools/admin/DefaultMQAdminExtImpl.java | 3 +-
+ 41 files changed, 215 insertions(+), 278 deletions(-)
+ delete mode 100644 broker/src/main/java/org/apache/rocketmq/broker/latency/BrokerFixedThreadPoolExecutor.java
+ delete mode 100644 broker/src/main/java/org/apache/rocketmq/broker/latency/FutureTaskExt.java
+
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java
+index 6aba70cb2..275b64b1a 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java
+@@ -34,7 +34,6 @@ import org.apache.rocketmq.broker.failover.EscapeBridge;
+ import org.apache.rocketmq.broker.filter.CommitLogDispatcherCalcBitMap;
+ import org.apache.rocketmq.broker.filter.ConsumerFilterManager;
+ import org.apache.rocketmq.broker.latency.BrokerFastFailure;
+-import org.apache.rocketmq.broker.latency.BrokerFixedThreadPoolExecutor;
+ import org.apache.rocketmq.broker.longpolling.LmqPullRequestHoldService;
+ import org.apache.rocketmq.broker.longpolling.NotifyMessageArrivingListener;
+ import org.apache.rocketmq.broker.longpolling.PullRequestHoldService;
+@@ -98,6 +97,7 @@ import org.apache.rocketmq.common.message.MessageExt;
+ import org.apache.rocketmq.common.message.MessageExtBrokerInner;
+ import org.apache.rocketmq.common.stats.MomentStatsItem;
+ import org.apache.rocketmq.common.utils.ServiceProvider;
++import org.apache.rocketmq.common.utils.ThreadUtils;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+ import org.apache.rocketmq.remoting.Configuration;
+@@ -160,7 +160,6 @@ import java.util.concurrent.ExecutorService;
+ import java.util.concurrent.LinkedBlockingQueue;
+ import java.util.concurrent.ScheduledExecutorService;
+ import java.util.concurrent.ScheduledFuture;
+-import java.util.concurrent.ScheduledThreadPoolExecutor;
+ import java.util.concurrent.TimeUnit;
+ import java.util.concurrent.locks.Lock;
+ import java.util.concurrent.locks.ReentrantLock;
+@@ -455,10 +454,10 @@ public class BrokerController {
+ * Initialize resources including remoting server and thread executors.
+ */
+ protected void initializeResources() {
+- this.scheduledExecutorService = new ScheduledThreadPoolExecutor(1,
++ this.scheduledExecutorService = ThreadUtils.newScheduledThreadPool(1,
+ new ThreadFactoryImpl("BrokerControllerScheduledThread", true, getBrokerIdentity()));
+
+- this.sendMessageExecutor = new BrokerFixedThreadPoolExecutor(
++ this.sendMessageExecutor = ThreadUtils.newThreadPoolExecutor(
+ this.brokerConfig.getSendMessageThreadPoolNums(),
+ this.brokerConfig.getSendMessageThreadPoolNums(),
+ 1000 * 60,
+@@ -466,7 +465,7 @@ public class BrokerController {
+ this.sendThreadPoolQueue,
+ new ThreadFactoryImpl("SendMessageThread_", getBrokerIdentity()));
+
+- this.pullMessageExecutor = new BrokerFixedThreadPoolExecutor(
++ this.pullMessageExecutor = ThreadUtils.newThreadPoolExecutor(
+ this.brokerConfig.getPullMessageThreadPoolNums(),
+ this.brokerConfig.getPullMessageThreadPoolNums(),
+ 1000 * 60,
+@@ -474,7 +473,7 @@ public class BrokerController {
+ this.pullThreadPoolQueue,
+ new ThreadFactoryImpl("PullMessageThread_", getBrokerIdentity()));
+
+- this.litePullMessageExecutor = new BrokerFixedThreadPoolExecutor(
++ this.litePullMessageExecutor = ThreadUtils.newThreadPoolExecutor(
+ this.brokerConfig.getLitePullMessageThreadPoolNums(),
+ this.brokerConfig.getLitePullMessageThreadPoolNums(),
+ 1000 * 60,
+@@ -482,7 +481,7 @@ public class BrokerController {
+ this.litePullThreadPoolQueue,
+ new ThreadFactoryImpl("LitePullMessageThread_", getBrokerIdentity()));
+
+- this.putMessageFutureExecutor = new BrokerFixedThreadPoolExecutor(
++ this.putMessageFutureExecutor = ThreadUtils.newThreadPoolExecutor(
+ this.brokerConfig.getPutMessageFutureThreadPoolNums(),
+ this.brokerConfig.getPutMessageFutureThreadPoolNums(),
+ 1000 * 60,
+@@ -490,7 +489,7 @@ public class BrokerController {
+ this.putThreadPoolQueue,
+ new ThreadFactoryImpl("SendMessageThread_", getBrokerIdentity()));
+
+- this.ackMessageExecutor = new BrokerFixedThreadPoolExecutor(
++ this.ackMessageExecutor = ThreadUtils.newThreadPoolExecutor(
+ this.brokerConfig.getAckMessageThreadPoolNums(),
+ this.brokerConfig.getAckMessageThreadPoolNums(),
+ 1000 * 60,
+@@ -498,7 +497,7 @@ public class BrokerController {
+ this.ackThreadPoolQueue,
+ new ThreadFactoryImpl("AckMessageThread_", getBrokerIdentity()));
+
+- this.queryMessageExecutor = new BrokerFixedThreadPoolExecutor(
++ this.queryMessageExecutor = ThreadUtils.newThreadPoolExecutor(
+ this.brokerConfig.getQueryMessageThreadPoolNums(),
+ this.brokerConfig.getQueryMessageThreadPoolNums(),
+ 1000 * 60,
+@@ -506,7 +505,7 @@ public class BrokerController {
+ this.queryThreadPoolQueue,
+ new ThreadFactoryImpl("QueryMessageThread_", getBrokerIdentity()));
+
+- this.adminBrokerExecutor = new BrokerFixedThreadPoolExecutor(
++ this.adminBrokerExecutor = ThreadUtils.newThreadPoolExecutor(
+ this.brokerConfig.getAdminBrokerThreadPoolNums(),
+ this.brokerConfig.getAdminBrokerThreadPoolNums(),
+ 1000 * 60,
+@@ -514,7 +513,7 @@ public class BrokerController {
+ this.adminBrokerThreadPoolQueue,
+ new ThreadFactoryImpl("AdminBrokerThread_", getBrokerIdentity()));
+
+- this.clientManageExecutor = new BrokerFixedThreadPoolExecutor(
++ this.clientManageExecutor = ThreadUtils.newThreadPoolExecutor(
+ this.brokerConfig.getClientManageThreadPoolNums(),
+ this.brokerConfig.getClientManageThreadPoolNums(),
+ 1000 * 60,
+@@ -522,7 +521,7 @@ public class BrokerController {
+ this.clientManagerThreadPoolQueue,
+ new ThreadFactoryImpl("ClientManageThread_", getBrokerIdentity()));
+
+- this.heartbeatExecutor = new BrokerFixedThreadPoolExecutor(
++ this.heartbeatExecutor = ThreadUtils.newThreadPoolExecutor(
+ this.brokerConfig.getHeartbeatThreadPoolNums(),
+ this.brokerConfig.getHeartbeatThreadPoolNums(),
+ 1000 * 60,
+@@ -530,7 +529,7 @@ public class BrokerController {
+ this.heartbeatThreadPoolQueue,
+ new ThreadFactoryImpl("HeartbeatThread_", true, getBrokerIdentity()));
+
+- this.consumerManageExecutor = new BrokerFixedThreadPoolExecutor(
++ this.consumerManageExecutor = ThreadUtils.newThreadPoolExecutor(
+ this.brokerConfig.getConsumerManageThreadPoolNums(),
+ this.brokerConfig.getConsumerManageThreadPoolNums(),
+ 1000 * 60,
+@@ -538,7 +537,7 @@ public class BrokerController {
+ this.consumerManagerThreadPoolQueue,
+ new ThreadFactoryImpl("ConsumerManageThread_", true, getBrokerIdentity()));
+
+- this.replyMessageExecutor = new BrokerFixedThreadPoolExecutor(
++ this.replyMessageExecutor = ThreadUtils.newThreadPoolExecutor(
+ this.brokerConfig.getProcessReplyMessageThreadPoolNums(),
+ this.brokerConfig.getProcessReplyMessageThreadPoolNums(),
+ 1000 * 60,
+@@ -546,7 +545,7 @@ public class BrokerController {
+ this.replyThreadPoolQueue,
+ new ThreadFactoryImpl("ProcessReplyMessageThread_", getBrokerIdentity()));
+
+- this.endTransactionExecutor = new BrokerFixedThreadPoolExecutor(
++ this.endTransactionExecutor = ThreadUtils.newThreadPoolExecutor(
+ this.brokerConfig.getEndTransactionThreadPoolNums(),
+ this.brokerConfig.getEndTransactionThreadPoolNums(),
+ 1000 * 60,
+@@ -554,7 +553,7 @@ public class BrokerController {
+ this.endTransactionThreadPoolQueue,
+ new ThreadFactoryImpl("EndTransactionThread_", getBrokerIdentity()));
+
+- this.loadBalanceExecutor = new BrokerFixedThreadPoolExecutor(
++ this.loadBalanceExecutor = ThreadUtils.newThreadPoolExecutor(
+ this.brokerConfig.getLoadBalanceProcessorThreadPoolNums(),
+ this.brokerConfig.getLoadBalanceProcessorThreadPoolNums(),
+ 1000 * 60,
+@@ -562,9 +561,9 @@ public class BrokerController {
+ this.loadBalanceThreadPoolQueue,
+ new ThreadFactoryImpl("LoadBalanceProcessorThread_", getBrokerIdentity()));
+
+- this.syncBrokerMemberGroupExecutorService = new ScheduledThreadPoolExecutor(1,
++ this.syncBrokerMemberGroupExecutorService = ThreadUtils.newScheduledThreadPool(1,
+ new ThreadFactoryImpl("BrokerControllerSyncBrokerScheduledThread", getBrokerIdentity()));
+- this.brokerHeartbeatExecutorService = new ScheduledThreadPoolExecutor(1,
++ this.brokerHeartbeatExecutorService = ThreadUtils.newScheduledThreadPool(1,
+ new ThreadFactoryImpl("BrokerControllerHeartbeatScheduledThread", getBrokerIdentity()));
+
+ this.topicQueueMappingCleanService = new TopicQueueMappingCleanService(this);
+@@ -828,8 +827,6 @@ public class BrokerController {
+
+ initializeResources();
+
+- registerProcessor();
+-
+ initializeScheduledTasks();
+
+ initialTransaction();
+@@ -1690,6 +1687,8 @@ public class BrokerController {
+ }
+ }
+ }, 10, 5, TimeUnit.SECONDS);
++
++ registerProcessor();
+ }
+
+ protected void scheduleSendHeartbeat() {
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/client/ClientHousekeepingService.java b/broker/src/main/java/org/apache/rocketmq/broker/client/ClientHousekeepingService.java
+index 98e5f450f..cbb81f632 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/client/ClientHousekeepingService.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/client/ClientHousekeepingService.java
+@@ -18,11 +18,11 @@ package org.apache.rocketmq.broker.client;
+
+ import io.netty.channel.Channel;
+ import java.util.concurrent.ScheduledExecutorService;
+-import java.util.concurrent.ScheduledThreadPoolExecutor;
+ import java.util.concurrent.TimeUnit;
+ import org.apache.rocketmq.broker.BrokerController;
+ import org.apache.rocketmq.common.ThreadFactoryImpl;
+ import org.apache.rocketmq.common.constant.LoggerName;
++import org.apache.rocketmq.common.utils.ThreadUtils;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+ import org.apache.rocketmq.remoting.ChannelEventListener;
+@@ -35,7 +35,7 @@ public class ClientHousekeepingService implements ChannelEventListener {
+
+ public ClientHousekeepingService(final BrokerController brokerController) {
+ this.brokerController = brokerController;
+- scheduledExecutorService = new ScheduledThreadPoolExecutor(1,
++ scheduledExecutorService = ThreadUtils.newScheduledThreadPool(1,
+ new ThreadFactoryImpl("ClientHousekeepingScheduledThread", brokerController.getBrokerIdentity()));
+ }
+
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/client/DefaultConsumerIdsChangeListener.java b/broker/src/main/java/org/apache/rocketmq/broker/client/DefaultConsumerIdsChangeListener.java
+index 2ce036a0f..d17a2a547 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/client/DefaultConsumerIdsChangeListener.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/client/DefaultConsumerIdsChangeListener.java
+@@ -22,7 +22,6 @@ import java.util.List;
+ import java.util.Map;
+ import java.util.concurrent.ConcurrentHashMap;
+ import java.util.concurrent.ScheduledExecutorService;
+-import java.util.concurrent.ScheduledThreadPoolExecutor;
+ import java.util.concurrent.TimeUnit;
+ import org.apache.rocketmq.broker.BrokerController;
+ import org.apache.rocketmq.common.AbstractBrokerRunnable;
+@@ -37,7 +36,7 @@ public class DefaultConsumerIdsChangeListener implements ConsumerIdsChangeListen
+ private final BrokerController brokerController;
+ private final int cacheSize = 8096;
+
+- private final ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1,
++ private final ScheduledExecutorService scheduledExecutorService = ThreadUtils.newScheduledThreadPool(1,
+ ThreadUtils.newGenericThreadFactory("DefaultConsumerIdsChangeListener", true));
+
+ private ConcurrentHashMap<String,List<Channel>> consumerChannelMap = new ConcurrentHashMap<>(cacheSize);
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java b/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java
+index 37c82e434..a989e6e68 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java
+@@ -27,10 +27,8 @@ import java.util.concurrent.ArrayBlockingQueue;
+ import java.util.concurrent.ConcurrentHashMap;
+ import java.util.concurrent.ConcurrentMap;
+ import java.util.concurrent.ExecutorService;
+-import java.util.concurrent.Executors;
+ import java.util.concurrent.ScheduledExecutorService;
+ import java.util.concurrent.ScheduledFuture;
+-import java.util.concurrent.ThreadPoolExecutor;
+ import java.util.concurrent.TimeUnit;
+
+ import org.apache.commons.lang3.StringUtils;
+@@ -42,6 +40,7 @@ import org.apache.rocketmq.common.MixAll;
+ import org.apache.rocketmq.common.Pair;
+ import org.apache.rocketmq.common.ThreadFactoryImpl;
+ import org.apache.rocketmq.common.constant.LoggerName;
++import org.apache.rocketmq.common.utils.ThreadUtils;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+ import org.apache.rocketmq.remoting.protocol.EpochEntry;
+@@ -107,9 +106,9 @@ public class ReplicasManager {
+ public ReplicasManager(final BrokerController brokerController) {
+ this.brokerController = brokerController;
+ this.brokerOuterAPI = brokerController.getBrokerOuterAPI();
+- this.scheduledService = Executors.newScheduledThreadPool(3, new ThreadFactoryImpl("ReplicasManager_ScheduledService_", brokerController.getBrokerIdentity()));
+- this.executorService = Executors.newFixedThreadPool(3, new ThreadFactoryImpl("ReplicasManager_ExecutorService_", brokerController.getBrokerIdentity()));
+- this.scanExecutor = new ThreadPoolExecutor(4, 10, 60, TimeUnit.SECONDS,
++ this.scheduledService = ThreadUtils.newScheduledThreadPool(3, new ThreadFactoryImpl("ReplicasManager_ScheduledService_", brokerController.getBrokerIdentity()));
++ this.executorService = ThreadUtils.newThreadPoolExecutor(3, new ThreadFactoryImpl("ReplicasManager_ExecutorService_", brokerController.getBrokerIdentity()));
++ this.scanExecutor = ThreadUtils.newThreadPoolExecutor(4, 10, 60, TimeUnit.SECONDS,
+ new ArrayBlockingQueue<>(32), new ThreadFactoryImpl("ReplicasManager_scan_thread_", brokerController.getBrokerIdentity()));
+ this.haService = (AutoSwitchHAService) brokerController.getMessageStore().getHaService();
+ this.brokerConfig = brokerController.getBrokerConfig();
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/dledger/DLedgerRoleChangeHandler.java b/broker/src/main/java/org/apache/rocketmq/broker/dledger/DLedgerRoleChangeHandler.java
+index 75023ee1b..e6cb97640 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/dledger/DLedgerRoleChangeHandler.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/dledger/DLedgerRoleChangeHandler.java
+@@ -21,12 +21,12 @@ import io.openmessaging.storage.dledger.DLedgerServer;
+ import io.openmessaging.storage.dledger.MemberState;
+ import io.openmessaging.storage.dledger.utils.DLedgerUtils;
+ import java.util.concurrent.ExecutorService;
+-import java.util.concurrent.Executors;
+ import java.util.concurrent.Future;
+ import java.util.concurrent.TimeUnit;
+ import org.apache.rocketmq.broker.BrokerController;
+ import org.apache.rocketmq.common.ThreadFactoryImpl;
+ import org.apache.rocketmq.common.constant.LoggerName;
++import org.apache.rocketmq.common.utils.ThreadUtils;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+ import org.apache.rocketmq.store.DefaultMessageStore;
+@@ -49,7 +49,7 @@ public class DLedgerRoleChangeHandler implements DLedgerLeaderElector.RoleChange
+ this.messageStore = messageStore;
+ this.dLedgerCommitLog = (DLedgerCommitLog) messageStore.getCommitLog();
+ this.dLegerServer = dLedgerCommitLog.getdLedgerServer();
+- this.executorService = Executors.newSingleThreadExecutor(
++ this.executorService = ThreadUtils.newSingleThreadExecutor(
+ new ThreadFactoryImpl("DLegerRoleChangeHandler_", brokerController.getBrokerIdentity()));
+ }
+
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/failover/EscapeBridge.java b/broker/src/main/java/org/apache/rocketmq/broker/failover/EscapeBridge.java
+index 7c350fc1d..6a0817480 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/failover/EscapeBridge.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/failover/EscapeBridge.java
+@@ -24,7 +24,6 @@ import java.util.concurrent.BlockingQueue;
+ import java.util.concurrent.CompletableFuture;
+ import java.util.concurrent.ExecutorService;
+ import java.util.concurrent.LinkedBlockingQueue;
+-import java.util.concurrent.ThreadPoolExecutor;
+ import java.util.concurrent.TimeUnit;
+ import org.apache.commons.lang3.StringUtils;
+ import org.apache.rocketmq.broker.BrokerController;
+@@ -43,6 +42,7 @@ import org.apache.rocketmq.common.message.MessageDecoder;
+ import org.apache.rocketmq.common.message.MessageExt;
+ import org.apache.rocketmq.common.message.MessageExtBrokerInner;
+ import org.apache.rocketmq.common.message.MessageQueue;
++import org.apache.rocketmq.common.utils.ThreadUtils;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+ import org.apache.rocketmq.remoting.exception.RemotingException;
+@@ -72,7 +72,7 @@ public class EscapeBridge {
+ public void start() throws Exception {
+ if (brokerController.getBrokerConfig().isEnableSlaveActingMaster() && brokerController.getBrokerConfig().isEnableRemoteEscape()) {
+ final BlockingQueue<Runnable> asyncSenderThreadPoolQueue = new LinkedBlockingQueue<>(50000);
+- this.defaultAsyncSenderExecutor = new ThreadPoolExecutor(
++ this.defaultAsyncSenderExecutor = ThreadUtils.newThreadPoolExecutor(
+ Runtime.getRuntime().availableProcessors(),
+ Runtime.getRuntime().availableProcessors(),
+ 1000 * 60,
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/latency/BrokerFastFailure.java b/broker/src/main/java/org/apache/rocketmq/broker/latency/BrokerFastFailure.java
+index d3d0bc8ba..3b6e9dc67 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/latency/BrokerFastFailure.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/latency/BrokerFastFailure.java
+@@ -18,13 +18,14 @@ package org.apache.rocketmq.broker.latency;
+
+ import java.util.concurrent.BlockingQueue;
+ import java.util.concurrent.ScheduledExecutorService;
+-import java.util.concurrent.ScheduledThreadPoolExecutor;
+ import java.util.concurrent.TimeUnit;
+ import org.apache.rocketmq.broker.BrokerController;
+ import org.apache.rocketmq.common.AbstractBrokerRunnable;
+ import org.apache.rocketmq.common.ThreadFactoryImpl;
+ import org.apache.rocketmq.common.UtilAll;
+ import org.apache.rocketmq.common.constant.LoggerName;
++import org.apache.rocketmq.common.future.FutureTaskExt;
++import org.apache.rocketmq.common.utils.ThreadUtils;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+ import org.apache.rocketmq.remoting.netty.RequestTask;
+@@ -43,7 +44,7 @@ public class BrokerFastFailure {
+
+ public BrokerFastFailure(final BrokerController brokerController) {
+ this.brokerController = brokerController;
+- this.scheduledExecutorService = new ScheduledThreadPoolExecutor(1,
++ this.scheduledExecutorService = ThreadUtils.newScheduledThreadPool(1,
+ new ThreadFactoryImpl("BrokerFastFailureScheduledThread", true,
+ brokerController == null ? null : brokerController.getBrokerConfig()));
+ }
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/latency/BrokerFixedThreadPoolExecutor.java b/broker/src/main/java/org/apache/rocketmq/broker/latency/BrokerFixedThreadPoolExecutor.java
+deleted file mode 100644
+index d2d1143a3..000000000
+--- a/broker/src/main/java/org/apache/rocketmq/broker/latency/BrokerFixedThreadPoolExecutor.java
++++ /dev/null
+@@ -1,57 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.rocketmq.broker.latency;
+-
+-import java.util.concurrent.BlockingQueue;
+-import java.util.concurrent.RejectedExecutionHandler;
+-import java.util.concurrent.RunnableFuture;
+-import java.util.concurrent.ThreadFactory;
+-import java.util.concurrent.ThreadPoolExecutor;
+-import java.util.concurrent.TimeUnit;
+-
+-public class BrokerFixedThreadPoolExecutor extends ThreadPoolExecutor {
+- public BrokerFixedThreadPoolExecutor(final int corePoolSize, final int maximumPoolSize, final long keepAliveTime,
+- final TimeUnit unit,
+- final BlockingQueue<Runnable> workQueue) {
+- super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
+- }
+-
+- public BrokerFixedThreadPoolExecutor(final int corePoolSize, final int maximumPoolSize, final long keepAliveTime,
+- final TimeUnit unit,
+- final BlockingQueue<Runnable> workQueue, final ThreadFactory threadFactory) {
+- super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
+- }
+-
+- public BrokerFixedThreadPoolExecutor(final int corePoolSize, final int maximumPoolSize, final long keepAliveTime,
+- final TimeUnit unit,
+- final BlockingQueue<Runnable> workQueue, final RejectedExecutionHandler handler) {
+- super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
+- }
+-
+- public BrokerFixedThreadPoolExecutor(final int corePoolSize, final int maximumPoolSize, final long keepAliveTime,
+- final TimeUnit unit,
+- final BlockingQueue<Runnable> workQueue, final ThreadFactory threadFactory,
+- final RejectedExecutionHandler handler) {
+- super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
+- }
+-
+- @Override
+- protected <T> RunnableFuture<T> newTaskFor(final Runnable runnable, final T value) {
+- return new FutureTaskExt<>(runnable, value);
+- }
+-}
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/latency/FutureTaskExt.java b/broker/src/main/java/org/apache/rocketmq/broker/latency/FutureTaskExt.java
+deleted file mode 100644
+index f132efaeb..000000000
+--- a/broker/src/main/java/org/apache/rocketmq/broker/latency/FutureTaskExt.java
++++ /dev/null
+@@ -1,39 +0,0 @@
+-/*
+- * Licensed to the Apache Software Foundation (ASF) under one or more
+- * contributor license agreements. See the NOTICE file distributed with
+- * this work for additional information regarding copyright ownership.
+- * The ASF licenses this file to You under the Apache License, Version 2.0
+- * (the "License"); you may not use this file except in compliance with
+- * the License. You may obtain a copy of the License at
+- *
+- * http://www.apache.org/licenses/LICENSE-2.0
+- *
+- * Unless required by applicable law or agreed to in writing, software
+- * distributed under the License is distributed on an "AS IS" BASIS,
+- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+- * See the License for the specific language governing permissions and
+- * limitations under the License.
+- */
+-
+-package org.apache.rocketmq.broker.latency;
+-
+-import java.util.concurrent.Callable;
+-import java.util.concurrent.FutureTask;
+-
+-public class FutureTaskExt<V> extends FutureTask<V> {
+- private final Runnable runnable;
+-
+- public FutureTaskExt(final Callable<V> callable) {
+- super(callable);
+- this.runnable = null;
+- }
+-
+- public FutureTaskExt(final Runnable runnable, final V result) {
+- super(runnable, result);
+- this.runnable = runnable;
+- }
+-
+- public Runnable getRunnable() {
+- return runnable;
+- }
+-}
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/out/BrokerOuterAPI.java b/broker/src/main/java/org/apache/rocketmq/broker/out/BrokerOuterAPI.java
+index ae81e8b11..9dfb8127d 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/out/BrokerOuterAPI.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/out/BrokerOuterAPI.java
+@@ -27,9 +27,9 @@ import java.util.concurrent.ArrayBlockingQueue;
+ import java.util.concurrent.CompletableFuture;
+ import java.util.concurrent.CopyOnWriteArrayList;
+ import java.util.concurrent.CountDownLatch;
++import java.util.concurrent.ExecutorService;
+ import java.util.concurrent.TimeUnit;
+ import org.apache.commons.lang3.StringUtils;
+-import org.apache.rocketmq.broker.latency.BrokerFixedThreadPoolExecutor;
+ import org.apache.rocketmq.client.consumer.PullResult;
+ import org.apache.rocketmq.client.consumer.PullStatus;
+ import org.apache.rocketmq.client.exception.MQBrokerException;
+@@ -59,6 +59,7 @@ import org.apache.rocketmq.common.namesrv.DefaultTopAddressing;
+ import org.apache.rocketmq.common.namesrv.TopAddressing;
+ import org.apache.rocketmq.common.sysflag.PullSysFlag;
+ import org.apache.rocketmq.common.topic.TopicValidator;
++import org.apache.rocketmq.common.utils.ThreadUtils;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+ import org.apache.rocketmq.remoting.InvokeCallback;
+@@ -144,7 +145,7 @@ public class BrokerOuterAPI {
+ private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME);
+ private final RemotingClient remotingClient;
+ private final TopAddressing topAddressing = new DefaultTopAddressing(MixAll.getWSAddr());
+- private final BrokerFixedThreadPoolExecutor brokerOuterExecutor = new BrokerFixedThreadPoolExecutor(4, 10, 1, TimeUnit.MINUTES,
++ private final ExecutorService brokerOuterExecutor = ThreadUtils.newThreadPoolExecutor(4, 10, 1, TimeUnit.MINUTES,
+ new ArrayBlockingQueue<>(32), new ThreadFactoryImpl("brokerOutApi_thread_", true));
+ private final ClientMetadata clientMetadata;
+ private final RpcClient rpcClient;
+@@ -1092,7 +1093,7 @@ public class BrokerOuterAPI {
+ throw new MQBrokerException(response.getCode(), response.getRemark());
+ }
+
+- public BrokerFixedThreadPoolExecutor getBrokerOuterExecutor() {
++ public ExecutorService getBrokerOuterExecutor() {
+ return brokerOuterExecutor;
+ }
+
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/schedule/ScheduleMessageService.java b/broker/src/main/java/org/apache/rocketmq/broker/schedule/ScheduleMessageService.java
+index 297b14207..0c2e6507b 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/schedule/ScheduleMessageService.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/schedule/ScheduleMessageService.java
+@@ -26,7 +26,6 @@ import java.util.concurrent.ConcurrentMap;
+ import java.util.concurrent.ExecutionException;
+ import java.util.concurrent.LinkedBlockingQueue;
+ import java.util.concurrent.ScheduledExecutorService;
+-import java.util.concurrent.ScheduledThreadPoolExecutor;
+ import java.util.concurrent.TimeUnit;
+ import java.util.concurrent.atomic.AtomicBoolean;
+ import java.util.concurrent.atomic.AtomicInteger;
+@@ -91,7 +90,7 @@ public class ScheduleMessageService extends ConfigManager {
+ public ScheduleMessageService(final BrokerController brokerController) {
+ this.brokerController = brokerController;
+ this.enableAsyncDeliver = brokerController.getMessageStoreConfig().isEnableScheduleAsyncDeliver();
+- scheduledPersistService = new ScheduledThreadPoolExecutor(1,
++ scheduledPersistService = ThreadUtils.newScheduledThreadPool(1,
+ new ThreadFactoryImpl("ScheduleMessageServicePersistThread", true, brokerController.getBrokerConfig()));
+ }
+
+@@ -134,9 +133,9 @@ public class ScheduleMessageService extends ConfigManager {
+ public void start() {
+ if (started.compareAndSet(false, true)) {
+ this.load();
+- this.deliverExecutorService = new ScheduledThreadPoolExecutor(this.maxDelayLevel, new ThreadFactoryImpl("ScheduleMessageTimerThread_"));
++ this.deliverExecutorService = ThreadUtils.newScheduledThreadPool(this.maxDelayLevel, new ThreadFactoryImpl("ScheduleMessageTimerThread_"));
+ if (this.enableAsyncDeliver) {
+- this.handleExecutorService = new ScheduledThreadPoolExecutor(this.maxDelayLevel, new ThreadFactoryImpl("ScheduleMessageExecutorHandleThread_"));
++ this.handleExecutorService = ThreadUtils.newScheduledThreadPool(this.maxDelayLevel, new ThreadFactoryImpl("ScheduleMessageExecutorHandleThread_"));
+ }
+ for (Map.Entry<Integer, Long> entry : this.delayLevelTable.entrySet()) {
+ Integer level = entry.getKey();
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicRouteInfoManager.java b/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicRouteInfoManager.java
+index b35564725..11bde5f5f 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicRouteInfoManager.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicRouteInfoManager.java
+@@ -23,7 +23,6 @@ import java.util.Objects;
+ import java.util.Set;
+ import java.util.concurrent.ConcurrentHashMap;
+ import java.util.concurrent.ConcurrentMap;
+-import java.util.concurrent.Executors;
+ import java.util.concurrent.ScheduledExecutorService;
+ import java.util.concurrent.TimeUnit;
+ import java.util.concurrent.locks.Lock;
+@@ -36,6 +35,7 @@ import org.apache.rocketmq.common.MixAll;
+ import org.apache.rocketmq.common.ThreadFactoryImpl;
+ import org.apache.rocketmq.common.constant.LoggerName;
+ import org.apache.rocketmq.common.message.MessageQueue;
++import org.apache.rocketmq.common.utils.ThreadUtils;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+ import org.apache.rocketmq.remoting.exception.RemotingException;
+@@ -66,7 +66,7 @@ public class TopicRouteInfoManager {
+ }
+
+ public void start() {
+- this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("TopicRouteInfoManagerScheduledThread"));
++ this.scheduledExecutorService = ThreadUtils.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("TopicRouteInfoManagerScheduledThread"));
+
+ this.scheduledExecutorService.scheduleAtFixedRate(() -> {
+ try {
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/transaction/AbstractTransactionalMessageCheckListener.java b/broker/src/main/java/org/apache/rocketmq/broker/transaction/AbstractTransactionalMessageCheckListener.java
+index 771d84300..982355d78 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/transaction/AbstractTransactionalMessageCheckListener.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/transaction/AbstractTransactionalMessageCheckListener.java
+@@ -19,7 +19,6 @@ package org.apache.rocketmq.broker.transaction;
+ import io.netty.channel.Channel;
+ import java.util.concurrent.ArrayBlockingQueue;
+ import java.util.concurrent.ExecutorService;
+-import java.util.concurrent.ThreadPoolExecutor;
+ import java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy;
+ import java.util.concurrent.TimeUnit;
+ import org.apache.rocketmq.broker.BrokerController;
+@@ -27,6 +26,7 @@ import org.apache.rocketmq.common.ThreadFactoryImpl;
+ import org.apache.rocketmq.common.constant.LoggerName;
+ import org.apache.rocketmq.common.message.MessageConst;
+ import org.apache.rocketmq.common.message.MessageExt;
++import org.apache.rocketmq.common.utils.ThreadUtils;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+ import org.apache.rocketmq.remoting.protocol.header.CheckTransactionStateRequestHeader;
+@@ -97,7 +97,7 @@ public abstract class AbstractTransactionalMessageCheckListener {
+
+ public synchronized void initExecutorService() {
+ if (executorService == null) {
+- executorService = new ThreadPoolExecutor(2, 5, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2000),
++ executorService = ThreadUtils.newThreadPoolExecutor(2, 5, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2000),
+ new ThreadFactoryImpl("Transaction-msg-check-thread", brokerController.getBrokerIdentity()), new CallerRunsPolicy());
+ }
+ }
+diff --git a/broker/src/test/java/org/apache/rocketmq/broker/BrokerControllerTest.java b/broker/src/test/java/org/apache/rocketmq/broker/BrokerControllerTest.java
+index 75ad961ce..6035a20ac 100644
+--- a/broker/src/test/java/org/apache/rocketmq/broker/BrokerControllerTest.java
++++ b/broker/src/test/java/org/apache/rocketmq/broker/BrokerControllerTest.java
+@@ -23,9 +23,9 @@ import java.util.concurrent.BlockingQueue;
+ import java.util.concurrent.LinkedBlockingQueue;
+ import java.util.concurrent.TimeUnit;
+
+-import org.apache.rocketmq.broker.latency.FutureTaskExt;
+ import org.apache.rocketmq.common.BrokerConfig;
+ import org.apache.rocketmq.common.UtilAll;
++import org.apache.rocketmq.common.future.FutureTaskExt;
+ import org.apache.rocketmq.remoting.netty.NettyClientConfig;
+ import org.apache.rocketmq.remoting.netty.NettyServerConfig;
+ import org.apache.rocketmq.remoting.netty.RequestTask;
+diff --git a/broker/src/test/java/org/apache/rocketmq/broker/latency/BrokerFastFailureTest.java b/broker/src/test/java/org/apache/rocketmq/broker/latency/BrokerFastFailureTest.java
+index 5d0f7f9d7..31b547cf1 100644
+--- a/broker/src/test/java/org/apache/rocketmq/broker/latency/BrokerFastFailureTest.java
++++ b/broker/src/test/java/org/apache/rocketmq/broker/latency/BrokerFastFailureTest.java
+@@ -19,6 +19,7 @@ package org.apache.rocketmq.broker.latency;
+ import java.util.concurrent.BlockingQueue;
+ import java.util.concurrent.LinkedBlockingQueue;
+ import java.util.concurrent.TimeUnit;
++import org.apache.rocketmq.common.future.FutureTaskExt;
+ import org.apache.rocketmq.remoting.netty.RequestTask;
+ import org.junit.Test;
+
+diff --git a/common/src/main/java/org/apache/rocketmq/common/config/AbstractRocksDBStorage.java b/common/src/main/java/org/apache/rocketmq/common/config/AbstractRocksDBStorage.java
+index a720a5be3..6f19a9815 100644
+--- a/common/src/main/java/org/apache/rocketmq/common/config/AbstractRocksDBStorage.java
++++ b/common/src/main/java/org/apache/rocketmq/common/config/AbstractRocksDBStorage.java
+@@ -23,7 +23,6 @@ import java.util.List;
+ import java.util.Map;
+ import java.util.concurrent.ArrayBlockingQueue;
+ import java.util.concurrent.ScheduledExecutorService;
+-import java.util.concurrent.ScheduledThreadPoolExecutor;
+ import java.util.concurrent.Semaphore;
+ import java.util.concurrent.ThreadPoolExecutor;
+ import java.util.concurrent.TimeUnit;
+@@ -33,6 +32,7 @@ import com.google.common.collect.Maps;
+
+ import org.apache.rocketmq.common.ThreadFactoryImpl;
+ import org.apache.rocketmq.common.constant.LoggerName;
++import org.apache.rocketmq.common.utils.ThreadUtils;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+ import org.rocksdb.ColumnFamilyDescriptor;
+@@ -82,8 +82,8 @@ public abstract class AbstractRocksDBStorage {
+ private volatile boolean closed;
+
+ private final Semaphore reloadPermit = new Semaphore(1);
+- private final ScheduledExecutorService reloadScheduler = new ScheduledThreadPoolExecutor(1, new ThreadFactoryImpl("RocksDBStorageReloadService_"));
+- private final ThreadPoolExecutor manualCompactionThread = new ThreadPoolExecutor(
++ private final ScheduledExecutorService reloadScheduler = ThreadUtils.newScheduledThreadPool(1, new ThreadFactoryImpl("RocksDBStorageReloadService_"));
++ private final ThreadPoolExecutor manualCompactionThread = (ThreadPoolExecutor) ThreadUtils.newThreadPoolExecutor(
+ 1, 1, 1000 * 60, TimeUnit.MILLISECONDS,
+ new ArrayBlockingQueue(1),
+ new ThreadFactoryImpl("RocksDBManualCompactionService_"),
+diff --git a/common/src/main/java/org/apache/rocketmq/common/thread/FutureTaskExtThreadPoolExecutor.java b/common/src/main/java/org/apache/rocketmq/common/thread/FutureTaskExtThreadPoolExecutor.java
+index 411da9221..7b68873a9 100644
+--- a/common/src/main/java/org/apache/rocketmq/common/thread/FutureTaskExtThreadPoolExecutor.java
++++ b/common/src/main/java/org/apache/rocketmq/common/thread/FutureTaskExtThreadPoolExecutor.java
+@@ -29,7 +29,8 @@ public class FutureTaskExtThreadPoolExecutor extends ThreadPoolExecutor {
+
+ public FutureTaskExtThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
+ TimeUnit unit,
+- BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
++ BlockingQueue<Runnable> workQueue,
++ ThreadFactory threadFactory,
+ RejectedExecutionHandler handler) {
+ super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
+ }
+diff --git a/common/src/main/java/org/apache/rocketmq/common/thread/ThreadPoolMonitor.java b/common/src/main/java/org/apache/rocketmq/common/thread/ThreadPoolMonitor.java
+index 49d97a5d7..1bfabbffe 100644
+--- a/common/src/main/java/org/apache/rocketmq/common/thread/ThreadPoolMonitor.java
++++ b/common/src/main/java/org/apache/rocketmq/common/thread/ThreadPoolMonitor.java
+@@ -22,12 +22,12 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder;
+ import java.util.Collections;
+ import java.util.List;
+ import java.util.concurrent.CopyOnWriteArrayList;
+-import java.util.concurrent.Executors;
+ import java.util.concurrent.LinkedBlockingQueue;
+ import java.util.concurrent.ScheduledExecutorService;
+ import java.util.concurrent.ThreadPoolExecutor;
+ import java.util.concurrent.TimeUnit;
+ import org.apache.rocketmq.common.UtilAll;
++import org.apache.rocketmq.common.utils.ThreadUtils;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+
+@@ -36,7 +36,7 @@ public class ThreadPoolMonitor {
+ private static Logger waterMarkLogger = LoggerFactory.getLogger(ThreadPoolMonitor.class);
+
+ private static final List<ThreadPoolWrapper> MONITOR_EXECUTOR = new CopyOnWriteArrayList<>();
+- private static final ScheduledExecutorService MONITOR_SCHEDULED = Executors.newSingleThreadScheduledExecutor(
++ private static final ScheduledExecutorService MONITOR_SCHEDULED = ThreadUtils.newSingleThreadScheduledExecutor(
+ new ThreadFactoryBuilder().setNameFormat("ThreadPoolMonitor-%d").build()
+ );
+
+@@ -81,7 +81,7 @@ public class ThreadPoolMonitor {
+ String name,
+ int queueCapacity,
+ List<ThreadPoolStatusMonitor> threadPoolStatusMonitors) {
+- ThreadPoolExecutor executor = new FutureTaskExtThreadPoolExecutor(
++ ThreadPoolExecutor executor = (ThreadPoolExecutor) ThreadUtils.newThreadPoolExecutor(
+ corePoolSize,
+ maximumPoolSize,
+ keepAliveTime,
+diff --git a/common/src/main/java/org/apache/rocketmq/common/utils/ThreadUtils.java b/common/src/main/java/org/apache/rocketmq/common/utils/ThreadUtils.java
+index 4b366d4e3..1644c6360 100644
+--- a/common/src/main/java/org/apache/rocketmq/common/utils/ThreadUtils.java
++++ b/common/src/main/java/org/apache/rocketmq/common/utils/ThreadUtils.java
+@@ -20,38 +20,94 @@ package org.apache.rocketmq.common.utils;
+ import java.util.concurrent.BlockingQueue;
+ import java.util.concurrent.ExecutorService;
+ import java.util.concurrent.Executors;
++import java.util.concurrent.LinkedBlockingQueue;
++import java.util.concurrent.RejectedExecutionHandler;
+ import java.util.concurrent.ScheduledExecutorService;
++import java.util.concurrent.ScheduledThreadPoolExecutor;
+ import java.util.concurrent.ThreadFactory;
+ import java.util.concurrent.ThreadPoolExecutor;
+ import java.util.concurrent.TimeUnit;
+ import org.apache.rocketmq.common.ThreadFactoryImpl;
+ import org.apache.rocketmq.common.constant.LoggerName;
++import org.apache.rocketmq.common.thread.FutureTaskExtThreadPoolExecutor;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+
+ public final class ThreadUtils {
+ private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.TOOLS_LOGGER_NAME);
+
+- public static ExecutorService newThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
+- TimeUnit unit, BlockingQueue<Runnable> workQueue, String processName, boolean isDaemon) {
+- return new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, newThreadFactory(processName, isDaemon));
++ public static ExecutorService newSingleThreadExecutor(String processName, boolean isDaemon) {
++ return ThreadUtils.newSingleThreadExecutor(newThreadFactory(processName, isDaemon));
+ }
+
+- public static ExecutorService newSingleThreadExecutor(String processName, boolean isDaemon) {
+- return Executors.newSingleThreadExecutor(newThreadFactory(processName, isDaemon));
++ public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
++ return ThreadUtils.newThreadPoolExecutor(1, threadFactory);
++ }
++
++ public static ExecutorService newThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory) {
++ return ThreadUtils.newThreadPoolExecutor(corePoolSize, corePoolSize,
++ 0L, TimeUnit.MILLISECONDS,
++ new LinkedBlockingQueue<>(),
++ threadFactory);
++ }
++
++ public static ExecutorService newThreadPoolExecutor(int corePoolSize,
++ int maximumPoolSize,
++ long keepAliveTime,
++ TimeUnit unit, BlockingQueue<Runnable> workQueue,
++ String processName,
++ boolean isDaemon) {
++ return ThreadUtils.newThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, newThreadFactory(processName, isDaemon));
++ }
++
++ public static ExecutorService newThreadPoolExecutor(final int corePoolSize,
++ final int maximumPoolSize,
++ final long keepAliveTime,
++ final TimeUnit unit,
++ final BlockingQueue<Runnable> workQueue,
++ final ThreadFactory threadFactory) {
++ return ThreadUtils.newThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, new ThreadPoolExecutor.AbortPolicy());
++ }
++
++ public static ExecutorService newThreadPoolExecutor(int corePoolSize,
++ int maximumPoolSize,
++ long keepAliveTime,
++ TimeUnit unit,
++ BlockingQueue<Runnable> workQueue,
++ ThreadFactory threadFactory,
++ RejectedExecutionHandler handler) {
++ return new FutureTaskExtThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
+ }
+
+ public static ScheduledExecutorService newSingleThreadScheduledExecutor(String processName, boolean isDaemon) {
+- return Executors.newSingleThreadScheduledExecutor(newThreadFactory(processName, isDaemon));
++ return ThreadUtils.newScheduledThreadPool(1, processName, isDaemon);
++ }
++
++ public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) {
++ return ThreadUtils.newScheduledThreadPool(1, threadFactory);
++ }
++
++ public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
++ return ThreadUtils.newScheduledThreadPool(corePoolSize, Executors.defaultThreadFactory());
+ }
+
+- public static ScheduledExecutorService newFixedThreadScheduledPool(int nThreads, String processName,
++ public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, String processName,
+ boolean isDaemon) {
+- return Executors.newScheduledThreadPool(nThreads, newThreadFactory(processName, isDaemon));
++ return ThreadUtils.newScheduledThreadPool(corePoolSize, newThreadFactory(processName, isDaemon));
++ }
++
++ public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory) {
++ return ThreadUtils.newScheduledThreadPool(corePoolSize, threadFactory, new ThreadPoolExecutor.AbortPolicy());
++ }
++
++ public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize,
++ ThreadFactory threadFactory,
++ RejectedExecutionHandler handler) {
++ return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory, handler);
+ }
+
+ public static ThreadFactory newThreadFactory(String processName, boolean isDaemon) {
+- return newGenericThreadFactory("Remoting-" + processName, isDaemon);
++ return newGenericThreadFactory("ThreadUtils-" + processName, isDaemon);
+ }
+
+ public static ThreadFactory newGenericThreadFactory(String processName) {
+diff --git a/container/src/main/java/org/apache/rocketmq/container/BrokerContainer.java b/container/src/main/java/org/apache/rocketmq/container/BrokerContainer.java
+index c6446f058..5b712bc30 100644
+--- a/container/src/main/java/org/apache/rocketmq/container/BrokerContainer.java
++++ b/container/src/main/java/org/apache/rocketmq/container/BrokerContainer.java
+@@ -47,14 +47,12 @@ import java.util.concurrent.ConcurrentMap;
+ import java.util.concurrent.ExecutorService;
+ import java.util.concurrent.LinkedBlockingQueue;
+ import java.util.concurrent.ScheduledExecutorService;
+-import java.util.concurrent.ScheduledThreadPoolExecutor;
+-import java.util.concurrent.ThreadPoolExecutor;
+ import java.util.concurrent.TimeUnit;
+
+ public class BrokerContainer implements IBrokerContainer {
+ private static final Logger LOG = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME);
+
+- private final ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1,
++ private final ScheduledExecutorService scheduledExecutorService = ThreadUtils.newScheduledThreadPool(1,
+ new BasicThreadFactory.Builder()
+ .namingPattern("BrokerContainerScheduledThread")
+ .daemon(true)
+@@ -143,7 +141,7 @@ public class BrokerContainer implements IBrokerContainer {
+ this.remotingServer = new NettyRemotingServer(this.nettyServerConfig, this.containerClientHouseKeepingService);
+ this.fastRemotingServer = this.remotingServer.newRemotingServer(this.nettyServerConfig.getListenPort() - 2);
+
+- this.brokerContainerExecutor = new ThreadPoolExecutor(
++ this.brokerContainerExecutor = ThreadUtils.newThreadPoolExecutor(
+ 1,
+ 1,
+ 1000 * 60,
+diff --git a/controller/src/main/java/org/apache/rocketmq/controller/ControllerManager.java b/controller/src/main/java/org/apache/rocketmq/controller/ControllerManager.java
+index 7c91e70da..3e6b0eba5 100644
+--- a/controller/src/main/java/org/apache/rocketmq/controller/ControllerManager.java
++++ b/controller/src/main/java/org/apache/rocketmq/controller/ControllerManager.java
+@@ -25,8 +25,6 @@ import java.util.concurrent.ExecutorService;
+ import java.util.concurrent.Executors;
+ import java.util.concurrent.Future;
+ import java.util.concurrent.LinkedBlockingQueue;
+-import java.util.concurrent.RunnableFuture;
+-import java.util.concurrent.ThreadPoolExecutor;
+ import java.util.concurrent.TimeUnit;
+
+ import org.apache.commons.lang3.StringUtils;
+@@ -34,8 +32,8 @@ import org.apache.rocketmq.common.ControllerConfig;
+ import org.apache.rocketmq.common.Pair;
+ import org.apache.rocketmq.common.ThreadFactoryImpl;
+ import org.apache.rocketmq.common.constant.LoggerName;
+-import org.apache.rocketmq.common.future.FutureTaskExt;
+
++import org.apache.rocketmq.common.utils.ThreadUtils;
+ import org.apache.rocketmq.controller.elect.impl.DefaultElectPolicy;
+ import org.apache.rocketmq.controller.impl.DLedgerController;
+ import org.apache.rocketmq.controller.impl.heartbeat.DefaultBrokerHeartbeatManager;
+@@ -93,18 +91,14 @@ public class ControllerManager {
+
+ public boolean initialize() {
+ this.controllerRequestThreadPoolQueue = new LinkedBlockingQueue<>(this.controllerConfig.getControllerRequestThreadPoolQueueCapacity());
+- this.controllerRequestExecutor = new ThreadPoolExecutor(
++ this.controllerRequestExecutor = ThreadUtils.newThreadPoolExecutor(
+ this.controllerConfig.getControllerThreadPoolNums(),
+ this.controllerConfig.getControllerThreadPoolNums(),
+ 1000 * 60,
+ TimeUnit.MILLISECONDS,
+ this.controllerRequestThreadPoolQueue,
+- new ThreadFactoryImpl("ControllerRequestExecutorThread_")) {
+- @Override
+- protected <T> RunnableFuture<T> newTaskFor(final Runnable runnable, final T value) {
+- return new FutureTaskExt<T>(runnable, value);
+- }
+- };
++ new ThreadFactoryImpl("ControllerRequestExecutorThread_"));
++
+ this.notifyService.initialize();
+ if (StringUtils.isEmpty(this.controllerConfig.getControllerDLegerPeers())) {
+ throw new IllegalArgumentException("Attribute value controllerDLegerPeers of ControllerConfig is null or empty");
+diff --git a/controller/src/main/java/org/apache/rocketmq/controller/impl/DLedgerController.java b/controller/src/main/java/org/apache/rocketmq/controller/impl/DLedgerController.java
+index fa91f288e..33e4406e4 100644
+--- a/controller/src/main/java/org/apache/rocketmq/controller/impl/DLedgerController.java
++++ b/controller/src/main/java/org/apache/rocketmq/controller/impl/DLedgerController.java
+@@ -32,7 +32,6 @@ import java.util.Map;
+ import java.util.concurrent.BlockingQueue;
+ import java.util.concurrent.CompletableFuture;
+ import java.util.concurrent.ExecutorService;
+-import java.util.concurrent.Executors;
+ import java.util.concurrent.LinkedBlockingQueue;
+ import java.util.concurrent.ScheduledExecutorService;
+ import java.util.concurrent.ScheduledFuture;
+@@ -44,6 +43,7 @@ import org.apache.rocketmq.common.ControllerConfig;
+ import org.apache.rocketmq.common.ServiceThread;
+ import org.apache.rocketmq.common.ThreadFactoryImpl;
+ import org.apache.rocketmq.common.constant.LoggerName;
++import org.apache.rocketmq.common.utils.ThreadUtils;
+ import org.apache.rocketmq.controller.Controller;
+ import org.apache.rocketmq.controller.elect.ElectPolicy;
+ import org.apache.rocketmq.controller.elect.impl.DefaultElectPolicy;
+@@ -66,11 +66,11 @@ import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+ import org.apache.rocketmq.remoting.protocol.ResponseCode;
+ import org.apache.rocketmq.remoting.protocol.body.SyncStateSet;
+ import org.apache.rocketmq.remoting.protocol.header.controller.AlterSyncStateSetRequestHeader;
+-import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterResponseHeader;
+-import org.apache.rocketmq.remoting.protocol.header.controller.admin.CleanControllerBrokerDataRequestHeader;
+ import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterRequestHeader;
++import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterResponseHeader;
+ import org.apache.rocketmq.remoting.protocol.header.controller.GetMetaDataResponseHeader;
+ import org.apache.rocketmq.remoting.protocol.header.controller.GetReplicaInfoRequestHeader;
++import org.apache.rocketmq.remoting.protocol.header.controller.admin.CleanControllerBrokerDataRequestHeader;
+ import org.apache.rocketmq.remoting.protocol.header.controller.register.ApplyBrokerIdRequestHeader;
+ import org.apache.rocketmq.remoting.protocol.header.controller.register.GetNextBrokerIdRequestHeader;
+ import org.apache.rocketmq.remoting.protocol.header.controller.register.RegisterBrokerToControllerRequestHeader;
+@@ -136,7 +136,7 @@ public class DLedgerController implements Controller {
+ this.dLedgerServer = new DLedgerServer(dLedgerConfig, nettyServerConfig, nettyClientConfig, channelEventListener);
+ this.dLedgerServer.registerStateMachine(this.statemachine);
+ this.dLedgerServer.getDLedgerLeaderElector().addRoleChangeHandler(this.roleHandler);
+- this.scanInactiveMasterService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("DLedgerController_scanInactiveService_"));
++ this.scanInactiveMasterService = ThreadUtils.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("DLedgerController_scanInactiveService_"));
+ this.brokerLifecycleListeners = new ArrayList<>();
+ }
+
+@@ -513,7 +513,7 @@ public class DLedgerController implements Controller {
+ class RoleChangeHandler implements DLedgerLeaderElector.RoleChangeHandler {
+
+ private final String selfId;
+- private final ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactoryImpl("DLedgerControllerRoleChangeHandler_"));
++ private final ExecutorService executorService = ThreadUtils.newSingleThreadExecutor(new ThreadFactoryImpl("DLedgerControllerRoleChangeHandler_"));
+ private volatile MemberState.Role currentRole = MemberState.Role.FOLLOWER;
+
+ public RoleChangeHandler(final String selfId) {
+diff --git a/controller/src/main/java/org/apache/rocketmq/controller/impl/heartbeat/DefaultBrokerHeartbeatManager.java b/controller/src/main/java/org/apache/rocketmq/controller/impl/heartbeat/DefaultBrokerHeartbeatManager.java
+index 2fbddb9cd..6ebb2c994 100644
+--- a/controller/src/main/java/org/apache/rocketmq/controller/impl/heartbeat/DefaultBrokerHeartbeatManager.java
++++ b/controller/src/main/java/org/apache/rocketmq/controller/impl/heartbeat/DefaultBrokerHeartbeatManager.java
+@@ -31,6 +31,7 @@ import java.util.concurrent.TimeUnit;
+ import org.apache.rocketmq.common.ControllerConfig;
+ import org.apache.rocketmq.common.ThreadFactoryImpl;
+ import org.apache.rocketmq.common.constant.LoggerName;
++import org.apache.rocketmq.common.utils.ThreadUtils;
+ import org.apache.rocketmq.controller.BrokerHeartbeatManager;
+ import org.apache.rocketmq.controller.helper.BrokerLifecycleListener;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+@@ -66,7 +67,7 @@ public class DefaultBrokerHeartbeatManager implements BrokerHeartbeatManager {
+
+ @Override
+ public void initialize() {
+- this.scheduledService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("DefaultBrokerHeartbeatManager_scheduledService_"));
++ this.scheduledService = ThreadUtils.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("DefaultBrokerHeartbeatManager_scheduledService_"));
+ this.executor = Executors.newFixedThreadPool(2, new ThreadFactoryImpl("DefaultBrokerHeartbeatManager_executorService_"));
+ }
+
+diff --git a/namesrv/src/main/java/org/apache/rocketmq/namesrv/NamesrvController.java b/namesrv/src/main/java/org/apache/rocketmq/namesrv/NamesrvController.java
+index 15c65ebec..be327cffa 100644
+--- a/namesrv/src/main/java/org/apache/rocketmq/namesrv/NamesrvController.java
++++ b/namesrv/src/main/java/org/apache/rocketmq/namesrv/NamesrvController.java
+@@ -20,10 +20,7 @@ import java.util.Collections;
+ import java.util.concurrent.BlockingQueue;
+ import java.util.concurrent.ExecutorService;
+ import java.util.concurrent.LinkedBlockingQueue;
+-import java.util.concurrent.RunnableFuture;
+ import java.util.concurrent.ScheduledExecutorService;
+-import java.util.concurrent.ScheduledThreadPoolExecutor;
+-import java.util.concurrent.ThreadPoolExecutor;
+ import java.util.concurrent.TimeUnit;
+ import org.apache.commons.lang3.concurrent.BasicThreadFactory;
+ import org.apache.rocketmq.common.ThreadFactoryImpl;
+@@ -31,6 +28,7 @@ import org.apache.rocketmq.common.constant.LoggerName;
+ import org.apache.rocketmq.common.future.FutureTaskExt;
+ import org.apache.rocketmq.common.namesrv.NamesrvConfig;
+ import org.apache.rocketmq.common.utils.NetworkUtil;
++import org.apache.rocketmq.common.utils.ThreadUtils;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+ import org.apache.rocketmq.namesrv.kvconfig.KVConfigManager;
+@@ -62,10 +60,10 @@ public class NamesrvController {
+ private final NettyServerConfig nettyServerConfig;
+ private final NettyClientConfig nettyClientConfig;
+
+- private final ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1,
++ private final ScheduledExecutorService scheduledExecutorService = ThreadUtils.newScheduledThreadPool(1,
+ new BasicThreadFactory.Builder().namingPattern("NSScheduledThread").daemon(true).build());
+
+- private final ScheduledExecutorService scanExecutorService = new ScheduledThreadPoolExecutor(1,
++ private final ScheduledExecutorService scanExecutorService = ThreadUtils.newScheduledThreadPool(1,
+ new BasicThreadFactory.Builder().namingPattern("NSScanScheduledThread").daemon(true).build());
+
+ private final KVConfigManager kvConfigManager;
+@@ -138,20 +136,10 @@ public class NamesrvController {
+
+ private void initiateThreadExecutors() {
+ this.defaultThreadPoolQueue = new LinkedBlockingQueue<>(this.namesrvConfig.getDefaultThreadPoolQueueCapacity());
+- this.defaultExecutor = new ThreadPoolExecutor(this.namesrvConfig.getDefaultThreadPoolNums(), this.namesrvConfig.getDefaultThreadPoolNums(), 1000 * 60, TimeUnit.MILLISECONDS, this.defaultThreadPoolQueue, new ThreadFactoryImpl("RemotingExecutorThread_")) {
+- @Override
+- protected <T> RunnableFuture<T> newTaskFor(final Runnable runnable, final T value) {
+- return new FutureTaskExt<>(runnable, value);
+- }
+- };
++ this.defaultExecutor = ThreadUtils.newThreadPoolExecutor(this.namesrvConfig.getDefaultThreadPoolNums(), this.namesrvConfig.getDefaultThreadPoolNums(), 1000 * 60, TimeUnit.MILLISECONDS, this.defaultThreadPoolQueue, new ThreadFactoryImpl("RemotingExecutorThread_"));
+
+ this.clientRequestThreadPoolQueue = new LinkedBlockingQueue<>(this.namesrvConfig.getClientRequestThreadPoolQueueCapacity());
+- this.clientRequestExecutor = new ThreadPoolExecutor(this.namesrvConfig.getClientRequestThreadPoolNums(), this.namesrvConfig.getClientRequestThreadPoolNums(), 1000 * 60, TimeUnit.MILLISECONDS, this.clientRequestThreadPoolQueue, new ThreadFactoryImpl("ClientRequestExecutorThread_")) {
+- @Override
+- protected <T> RunnableFuture<T> newTaskFor(final Runnable runnable, final T value) {
+- return new FutureTaskExt<>(runnable, value);
+- }
+- };
++ this.clientRequestExecutor = ThreadUtils.newThreadPoolExecutor(this.namesrvConfig.getClientRequestThreadPoolNums(), this.namesrvConfig.getClientRequestThreadPoolNums(), 1000 * 60, TimeUnit.MILLISECONDS, this.clientRequestThreadPoolQueue, new ThreadFactoryImpl("ClientRequestExecutorThread_"));
+ }
+
+ private void initiateSslContext() {
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/channel/GrpcChannelManager.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/channel/GrpcChannelManager.java
+index 14330dd8d..a18cf7600 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/channel/GrpcChannelManager.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/channel/GrpcChannelManager.java
+@@ -21,13 +21,13 @@ import java.util.Set;
+ import java.util.concurrent.CompletableFuture;
+ import java.util.concurrent.ConcurrentHashMap;
+ import java.util.concurrent.ConcurrentMap;
+-import java.util.concurrent.Executors;
+ import java.util.concurrent.ScheduledExecutorService;
+ import java.util.concurrent.TimeUnit;
+ import java.util.concurrent.atomic.AtomicLong;
+ import org.apache.rocketmq.common.ThreadFactoryImpl;
+-import org.apache.rocketmq.proxy.common.ProxyContext;
+ import org.apache.rocketmq.common.utils.StartAndShutdown;
++import org.apache.rocketmq.common.utils.ThreadUtils;
++import org.apache.rocketmq.proxy.common.ProxyContext;
+ import org.apache.rocketmq.proxy.config.ConfigurationManager;
+ import org.apache.rocketmq.proxy.config.ProxyConfig;
+ import org.apache.rocketmq.proxy.grpc.v2.common.GrpcClientSettingsManager;
+@@ -43,7 +43,7 @@ public class GrpcChannelManager implements StartAndShutdown {
+ protected final AtomicLong nonceIdGenerator = new AtomicLong(0);
+ protected final ConcurrentMap<String /* nonce */, ResultFuture> resultNonceFutureMap = new ConcurrentHashMap<>();
+
+- protected final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(
++ protected final ScheduledExecutorService scheduledExecutorService = ThreadUtils.newSingleThreadScheduledExecutor(
+ new ThreadFactoryImpl("GrpcChannelManager_")
+ );
+
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/RemotingProtocolServer.java b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/RemotingProtocolServer.java
+index bcc9edd09..fe07090d5 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/RemotingProtocolServer.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/RemotingProtocolServer.java
+@@ -22,7 +22,6 @@ import io.netty.channel.Channel;
+ import java.util.List;
+ import java.util.concurrent.BlockingQueue;
+ import java.util.concurrent.CompletableFuture;
+-import java.util.concurrent.Executors;
+ import java.util.concurrent.ScheduledExecutorService;
+ import java.util.concurrent.ThreadPoolExecutor;
+ import java.util.concurrent.TimeUnit;
+@@ -33,6 +32,7 @@ import org.apache.rocketmq.common.future.FutureTaskExt;
+ import org.apache.rocketmq.common.thread.ThreadPoolMonitor;
+ import org.apache.rocketmq.common.thread.ThreadPoolStatusMonitor;
+ import org.apache.rocketmq.common.utils.StartAndShutdown;
++import org.apache.rocketmq.common.utils.ThreadUtils;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+ import org.apache.rocketmq.proxy.config.ConfigurationManager;
+@@ -178,7 +178,7 @@ public class RemotingProtocolServer implements StartAndShutdown, RemotingProxyOu
+ new ThreadPoolHeadSlowTimeMillsMonitor(config.getRemotingWaitTimeMillsInDefaultQueue())
+ );
+
+- this.timerExecutor = Executors.newSingleThreadScheduledExecutor(
++ this.timerExecutor = ThreadUtils.newSingleThreadScheduledExecutor(
+ new ThreadFactoryBuilder().setNameFormat("RemotingServerScheduler-%d").build()
+ );
+ this.timerExecutor.scheduleAtFixedRate(this::cleanExpireRequest, 10, 10, TimeUnit.SECONDS);
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/ClusterServiceManager.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/ClusterServiceManager.java
+index d2ddfc352..9786cec55 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/ClusterServiceManager.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/ClusterServiceManager.java
+@@ -16,7 +16,6 @@
+ */
+ package org.apache.rocketmq.proxy.service;
+
+-import java.util.concurrent.Executors;
+ import java.util.concurrent.ScheduledExecutorService;
+ import java.util.concurrent.TimeUnit;
+ import org.apache.rocketmq.broker.client.ClientChannelInfo;
+@@ -27,23 +26,24 @@ import org.apache.rocketmq.broker.client.ProducerChangeListener;
+ import org.apache.rocketmq.broker.client.ProducerGroupEvent;
+ import org.apache.rocketmq.broker.client.ProducerManager;
+ import org.apache.rocketmq.client.common.NameserverAccessConfig;
++import org.apache.rocketmq.client.impl.mqclient.DoNothingClientRemotingProcessor;
++import org.apache.rocketmq.client.impl.mqclient.MQClientAPIFactory;
+ import org.apache.rocketmq.common.constant.LoggerName;
++import org.apache.rocketmq.common.utils.AbstractStartAndShutdown;
++import org.apache.rocketmq.common.utils.ThreadUtils;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+-import org.apache.rocketmq.common.utils.AbstractStartAndShutdown;
+ import org.apache.rocketmq.proxy.common.ProxyContext;
+ import org.apache.rocketmq.proxy.config.ConfigurationManager;
+ import org.apache.rocketmq.proxy.config.ProxyConfig;
+ import org.apache.rocketmq.proxy.service.admin.AdminService;
+ import org.apache.rocketmq.proxy.service.admin.DefaultAdminService;
+ import org.apache.rocketmq.proxy.service.client.ClusterConsumerManager;
++import org.apache.rocketmq.proxy.service.client.ProxyClientRemotingProcessor;
+ import org.apache.rocketmq.proxy.service.message.ClusterMessageService;
+ import org.apache.rocketmq.proxy.service.message.MessageService;
+ import org.apache.rocketmq.proxy.service.metadata.ClusterMetadataService;
+ import org.apache.rocketmq.proxy.service.metadata.MetadataService;
+-import org.apache.rocketmq.client.impl.mqclient.DoNothingClientRemotingProcessor;
+-import org.apache.rocketmq.client.impl.mqclient.MQClientAPIFactory;
+-import org.apache.rocketmq.proxy.service.client.ProxyClientRemotingProcessor;
+ import org.apache.rocketmq.proxy.service.relay.ClusterProxyRelayService;
+ import org.apache.rocketmq.proxy.service.relay.ProxyRelayService;
+ import org.apache.rocketmq.proxy.service.route.ClusterTopicRouteService;
+@@ -73,7 +73,7 @@ public class ClusterServiceManager extends AbstractStartAndShutdown implements S
+ ProxyConfig proxyConfig = ConfigurationManager.getProxyConfig();
+ NameserverAccessConfig nameserverAccessConfig = new NameserverAccessConfig(proxyConfig.getNamesrvAddr(),
+ proxyConfig.getNamesrvDomain(), proxyConfig.getNamesrvDomainSubgroup());
+- this.scheduledExecutorService = Executors.newScheduledThreadPool(3);
++ this.scheduledExecutorService = ThreadUtils.newScheduledThreadPool(3);
+
+ this.messagingClientAPIFactory = new MQClientAPIFactory(
+ nameserverAccessConfig,
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/LocalServiceManager.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/LocalServiceManager.java
+index 4d1ca7b66..59cd92685 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/LocalServiceManager.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/LocalServiceManager.java
+@@ -16,7 +16,6 @@
+ */
+ package org.apache.rocketmq.proxy.service;
+
+-import java.util.concurrent.Executors;
+ import java.util.concurrent.ScheduledExecutorService;
+ import java.util.concurrent.TimeUnit;
+ import org.apache.rocketmq.broker.BrokerController;
+@@ -28,6 +27,7 @@ import org.apache.rocketmq.client.impl.mqclient.MQClientAPIFactory;
+ import org.apache.rocketmq.common.ThreadFactoryImpl;
+ import org.apache.rocketmq.common.utils.AbstractStartAndShutdown;
+ import org.apache.rocketmq.common.utils.StartAndShutdown;
++import org.apache.rocketmq.common.utils.ThreadUtils;
+ import org.apache.rocketmq.proxy.config.ConfigurationManager;
+ import org.apache.rocketmq.proxy.config.ProxyConfig;
+ import org.apache.rocketmq.proxy.service.admin.AdminService;
+@@ -58,7 +58,7 @@ public class LocalServiceManager extends AbstractStartAndShutdown implements Ser
+ private final MQClientAPIFactory mqClientAPIFactory;
+ private final ChannelManager channelManager;
+
+- private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(
++ private final ScheduledExecutorService scheduledExecutorService = ThreadUtils.newSingleThreadScheduledExecutor(
+ new ThreadFactoryImpl("LocalServiceManagerScheduledThread"));
+
+ public LocalServiceManager(BrokerController brokerController, RPCHook rpcHook) {
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/receipt/DefaultReceiptHandleManager.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/receipt/DefaultReceiptHandleManager.java
+index 69f44344a..207603fe8 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/receipt/DefaultReceiptHandleManager.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/receipt/DefaultReceiptHandleManager.java
+@@ -24,7 +24,6 @@ import java.util.Set;
+ import java.util.concurrent.CompletableFuture;
+ import java.util.concurrent.ConcurrentHashMap;
+ import java.util.concurrent.ConcurrentMap;
+-import java.util.concurrent.Executors;
+ import java.util.concurrent.ScheduledExecutorService;
+ import java.util.concurrent.ThreadPoolExecutor;
+ import java.util.concurrent.TimeUnit;
+@@ -42,20 +41,21 @@ import org.apache.rocketmq.common.thread.ThreadPoolMonitor;
+ import org.apache.rocketmq.common.utils.AbstractStartAndShutdown;
+ import org.apache.rocketmq.common.utils.ConcurrentHashMapUtils;
+ import org.apache.rocketmq.common.utils.StartAndShutdown;
++import org.apache.rocketmq.common.utils.ThreadUtils;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+-import org.apache.rocketmq.proxy.common.RenewEvent;
+ import org.apache.rocketmq.proxy.common.MessageReceiptHandle;
+ import org.apache.rocketmq.proxy.common.ProxyContext;
+ import org.apache.rocketmq.proxy.common.ProxyException;
+ import org.apache.rocketmq.proxy.common.ProxyExceptionCode;
+ import org.apache.rocketmq.proxy.common.ReceiptHandleGroup;
++import org.apache.rocketmq.proxy.common.ReceiptHandleGroupKey;
++import org.apache.rocketmq.proxy.common.RenewEvent;
+ import org.apache.rocketmq.proxy.common.RenewStrategyPolicy;
+ import org.apache.rocketmq.proxy.common.channel.ChannelHelper;
+ import org.apache.rocketmq.proxy.common.utils.ExceptionUtils;
+ import org.apache.rocketmq.proxy.config.ConfigurationManager;
+ import org.apache.rocketmq.proxy.config.ProxyConfig;
+-import org.apache.rocketmq.proxy.common.ReceiptHandleGroupKey;
+ import org.apache.rocketmq.proxy.service.metadata.MetadataService;
+ import org.apache.rocketmq.remoting.protocol.subscription.RetryPolicy;
+ import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfig;
+@@ -68,7 +68,7 @@ public class DefaultReceiptHandleManager extends AbstractStartAndShutdown implem
+ protected final StateEventListener<RenewEvent> eventListener;
+ protected final static RetryPolicy RENEW_POLICY = new RenewStrategyPolicy();
+ protected final ScheduledExecutorService scheduledExecutorService =
+- Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("RenewalScheduledThread_"));
++ ThreadUtils.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("RenewalScheduledThread_"));
+ protected final ThreadPoolExecutor renewalWorkerService;
+
+ public DefaultReceiptHandleManager(MetadataService metadataService, ConsumerManager consumerManager, StateEventListener<RenewEvent> eventListener) {
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java
+index caf62a1e0..ccf094c03 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java
+@@ -19,25 +19,24 @@ package org.apache.rocketmq.proxy.service.route;
+ import com.github.benmanes.caffeine.cache.CacheLoader;
+ import com.github.benmanes.caffeine.cache.Caffeine;
+ import com.github.benmanes.caffeine.cache.LoadingCache;
++import com.google.common.base.Optional;
+ import java.time.Duration;
+ import java.util.List;
+-import java.util.concurrent.Executors;
+ import java.util.concurrent.ScheduledExecutorService;
+ import java.util.concurrent.ThreadPoolExecutor;
+ import java.util.concurrent.TimeUnit;
+-
+-import com.google.common.base.Optional;
+ import org.apache.rocketmq.client.ClientConfig;
+ import org.apache.rocketmq.client.exception.MQClientException;
++import org.apache.rocketmq.client.impl.mqclient.MQClientAPIFactory;
+ import org.apache.rocketmq.client.latency.MQFaultStrategy;
+ import org.apache.rocketmq.client.latency.Resolver;
+ import org.apache.rocketmq.client.latency.ServiceDetector;
+-import org.apache.rocketmq.client.impl.mqclient.MQClientAPIFactory;
+ import org.apache.rocketmq.common.ThreadFactoryImpl;
+ import org.apache.rocketmq.common.constant.LoggerName;
+ import org.apache.rocketmq.common.message.MessageQueue;
+ import org.apache.rocketmq.common.thread.ThreadPoolMonitor;
+ import org.apache.rocketmq.common.utils.AbstractStartAndShutdown;
++import org.apache.rocketmq.common.utils.ThreadUtils;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+ import org.apache.rocketmq.proxy.common.Address;
+@@ -63,7 +62,7 @@ public abstract class TopicRouteService extends AbstractStartAndShutdown {
+ public TopicRouteService(MQClientAPIFactory mqClientAPIFactory) {
+ ProxyConfig config = ConfigurationManager.getProxyConfig();
+
+- this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(
++ this.scheduledExecutorService = ThreadUtils.newSingleThreadScheduledExecutor(
+ new ThreadFactoryImpl("TopicRouteService_")
+ );
+ this.cacheRefreshExecutor = ThreadPoolMonitor.createAndMonitor(
+diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java
+index 8491f4354..64621dd6c 100644
+--- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java
++++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java
+@@ -61,7 +61,6 @@ import java.util.concurrent.ConcurrentHashMap;
+ import java.util.concurrent.ConcurrentMap;
+ import java.util.concurrent.ExecutorService;
+ import java.util.concurrent.Executors;
+-import java.util.concurrent.ThreadPoolExecutor;
+ import java.util.concurrent.TimeUnit;
+ import java.util.concurrent.atomic.AtomicInteger;
+ import java.util.concurrent.atomic.AtomicReference;
+@@ -71,6 +70,7 @@ import org.apache.commons.lang3.StringUtils;
+ import org.apache.rocketmq.common.Pair;
+ import org.apache.rocketmq.common.ThreadFactoryImpl;
+ import org.apache.rocketmq.common.constant.LoggerName;
++import org.apache.rocketmq.common.utils.ThreadUtils;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+ import org.apache.rocketmq.remoting.ChannelEventListener;
+@@ -142,7 +142,7 @@ public class NettyRemotingClient extends NettyRemotingAbstract implements Remoti
+
+ this.publicExecutor = Executors.newFixedThreadPool(publicThreadNums, new ThreadFactoryImpl("NettyClientPublicExecutor_"));
+
+- this.scanExecutor = new ThreadPoolExecutor(4, 10, 60, TimeUnit.SECONDS,
++ this.scanExecutor = ThreadUtils.newThreadPoolExecutor(4, 10, 60, TimeUnit.SECONDS,
+ new ArrayBlockingQueue<>(32), new ThreadFactoryImpl("NettyClientScan_thread_"));
+
+ if (eventLoopGroup != null) {
+diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java
+index e626260c9..aa0d46542 100644
+--- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java
++++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java
+@@ -61,6 +61,7 @@ import org.apache.rocketmq.common.constant.HAProxyConstants;
+ import org.apache.rocketmq.common.constant.LoggerName;
+ import org.apache.rocketmq.common.utils.BinaryUtil;
+ import org.apache.rocketmq.common.utils.NetworkUtil;
++import org.apache.rocketmq.common.utils.ThreadUtils;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+ import org.apache.rocketmq.remoting.ChannelEventListener;
+@@ -83,7 +84,6 @@ import java.util.concurrent.ConcurrentMap;
+ import java.util.concurrent.ExecutorService;
+ import java.util.concurrent.Executors;
+ import java.util.concurrent.ScheduledExecutorService;
+-import java.util.concurrent.ScheduledThreadPoolExecutor;
+ import java.util.concurrent.ThreadPoolExecutor;
+ import java.util.concurrent.TimeUnit;
+
+@@ -171,7 +171,7 @@ public class NettyRemotingServer extends NettyRemotingAbstract implements Remoti
+ }
+
+ private ScheduledExecutorService buildScheduleExecutor() {
+- return new ScheduledThreadPoolExecutor(1,
++ return ThreadUtils.newScheduledThreadPool(1,
+ new ThreadFactoryImpl("NettyServerScheduler_", true),
+ new ThreadPoolExecutor.DiscardOldestPolicy());
+ }
+diff --git a/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java b/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java
+index f2a54ddf6..02ea47f13 100644
+--- a/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java
++++ b/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java
+@@ -48,7 +48,6 @@ import java.util.concurrent.ConcurrentLinkedQueue;
+ import java.util.concurrent.ConcurrentMap;
+ import java.util.concurrent.ExecutionException;
+ import java.util.concurrent.ExecutorService;
+-import java.util.concurrent.Executors;
+ import java.util.concurrent.LinkedBlockingQueue;
+ import java.util.concurrent.ScheduledExecutorService;
+ import java.util.concurrent.ThreadPoolExecutor;
+@@ -83,6 +82,7 @@ import org.apache.rocketmq.common.topic.TopicValidator;
+ import org.apache.rocketmq.common.utils.CleanupPolicyUtils;
+ import org.apache.rocketmq.common.utils.QueueTypeUtils;
+ import org.apache.rocketmq.common.utils.ServiceProvider;
++import org.apache.rocketmq.common.utils.ThreadUtils;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+ import org.apache.rocketmq.remoting.protocol.body.HARuntimeInfo;
+@@ -205,7 +205,7 @@ public class DefaultMessageStore implements MessageStore {
+ private ConcurrentMap<String, TopicConfig> topicConfigTable;
+
+ private final ScheduledExecutorService scheduledCleanQueueExecutorService =
+- Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("StoreCleanQueueScheduledThread"));
++ ThreadUtils.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("StoreCleanQueueScheduledThread"));
+
+ public DefaultMessageStore(final MessageStoreConfig messageStoreConfig, final BrokerStatsManager brokerStatsManager,
+ final MessageArrivingListener messageArrivingListener, final BrokerConfig brokerConfig, final ConcurrentMap<String, TopicConfig> topicConfigTable) throws IOException {
+@@ -253,7 +253,7 @@ public class DefaultMessageStore implements MessageStore {
+ this.transientStorePool = new TransientStorePool(messageStoreConfig.getTransientStorePoolSize(), messageStoreConfig.getMappedFileSizeCommitLog());
+
+ this.scheduledExecutorService =
+- Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("StoreScheduledThread", getBrokerIdentity()));
++ ThreadUtils.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("StoreScheduledThread", getBrokerIdentity()));
+
+ this.dispatcherList = new LinkedList<>();
+ this.dispatcherList.addLast(new CommitLogDispatcherBuildConsumeQueue());
+@@ -2915,7 +2915,7 @@ public class DefaultMessageStore implements MessageStore {
+ private final ExecutorService batchDispatchRequestExecutor;
+
+ public MainBatchDispatchRequestService() {
+- batchDispatchRequestExecutor = new ThreadPoolExecutor(
++ batchDispatchRequestExecutor = ThreadUtils.newThreadPoolExecutor(
+ DefaultMessageStore.this.getMessageStoreConfig().getBatchDispatchRequestThreadPoolNums(),
+ DefaultMessageStore.this.getMessageStoreConfig().getBatchDispatchRequestThreadPoolNums(),
+ 1000 * 60,
+diff --git a/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAService.java b/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAService.java
+index d5393fdca..f20bc3e28 100644
+--- a/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAService.java
++++ b/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAService.java
+@@ -17,10 +17,26 @@
+
+ package org.apache.rocketmq.store.ha.autoswitch;
+
+-
++import java.io.IOException;
++import java.nio.channels.SocketChannel;
++import java.util.ArrayList;
++import java.util.HashSet;
++import java.util.Iterator;
++import java.util.List;
++import java.util.Map;
++import java.util.Objects;
++import java.util.Set;
++import java.util.concurrent.ConcurrentHashMap;
++import java.util.concurrent.ExecutorService;
++import java.util.concurrent.locks.Lock;
++import java.util.concurrent.locks.ReadWriteLock;
++import java.util.concurrent.locks.ReentrantReadWriteLock;
++import java.util.function.Consumer;
++import java.util.stream.Collectors;
+ import org.apache.rocketmq.common.ThreadFactoryImpl;
+ import org.apache.rocketmq.common.constant.LoggerName;
+ import org.apache.rocketmq.common.utils.ConcurrentHashMapUtils;
++import org.apache.rocketmq.common.utils.ThreadUtils;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+ import org.apache.rocketmq.remoting.protocol.EpochEntry;
+@@ -36,30 +52,12 @@ import org.apache.rocketmq.store.ha.HAClient;
+ import org.apache.rocketmq.store.ha.HAConnection;
+ import org.apache.rocketmq.store.ha.HAConnectionStateNotificationService;
+
+-import java.io.IOException;
+-import java.nio.channels.SocketChannel;
+-import java.util.ArrayList;
+-import java.util.HashSet;
+-import java.util.List;
+-import java.util.Iterator;
+-import java.util.Map;
+-import java.util.Objects;
+-import java.util.Set;
+-import java.util.concurrent.ConcurrentHashMap;
+-import java.util.concurrent.ExecutorService;
+-import java.util.concurrent.Executors;
+-import java.util.concurrent.locks.Lock;
+-import java.util.concurrent.locks.ReadWriteLock;
+-import java.util.concurrent.locks.ReentrantReadWriteLock;
+-import java.util.function.Consumer;
+-import java.util.stream.Collectors;
+-
+ /**
+ * SwitchAble ha service, support switch role to master or slave.
+ */
+ public class AutoSwitchHAService extends DefaultHAService {
+ private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME);
+- private final ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactoryImpl("AutoSwitchHAService_Executor_"));
++ private final ExecutorService executorService = ThreadUtils.newSingleThreadExecutor(new ThreadFactoryImpl("AutoSwitchHAService_Executor_"));
+ private final ConcurrentHashMap<Long/*brokerId*/, Long/*lastCaughtUpTimestamp*/> connectionCaughtUpTimeTable = new ConcurrentHashMap<>();
+ private final List<Consumer<Set<Long/*brokerId*/>>> syncStateSetChangedListeners = new ArrayList<>();
+ private final Set<Long/*brokerId*/> syncStateSet = new HashSet<>();
+diff --git a/store/src/main/java/org/apache/rocketmq/store/kv/CompactionStore.java b/store/src/main/java/org/apache/rocketmq/store/kv/CompactionStore.java
+index b37c90726..639084fa2 100644
+--- a/store/src/main/java/org/apache/rocketmq/store/kv/CompactionStore.java
++++ b/store/src/main/java/org/apache/rocketmq/store/kv/CompactionStore.java
+@@ -16,17 +16,25 @@
+ */
+ package org.apache.rocketmq.store.kv;
+
+-import java.util.Random;
++import java.io.File;
++import java.io.IOException;
++import java.nio.file.Files;
++import java.nio.file.Paths;
+ import java.util.Iterator;
+ import java.util.List;
+ import java.util.Map;
+ import java.util.Objects;
+ import java.util.Optional;
++import java.util.Random;
++import java.util.concurrent.ConcurrentHashMap;
++import java.util.concurrent.ScheduledExecutorService;
++import java.util.concurrent.TimeUnit;
+ import org.apache.rocketmq.common.ThreadFactoryImpl;
+ import org.apache.rocketmq.common.TopicConfig;
+ import org.apache.rocketmq.common.attribute.CleanupPolicy;
+ import org.apache.rocketmq.common.constant.LoggerName;
+ import org.apache.rocketmq.common.utils.CleanupPolicyUtils;
++import org.apache.rocketmq.common.utils.ThreadUtils;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+ import org.apache.rocketmq.store.DefaultMessageStore;
+@@ -35,15 +43,6 @@ import org.apache.rocketmq.store.GetMessageResult;
+ import org.apache.rocketmq.store.SelectMappedBufferResult;
+ import org.apache.rocketmq.store.config.MessageStoreConfig;
+
+-import java.io.File;
+-import java.io.IOException;
+-import java.nio.file.Files;
+-import java.nio.file.Paths;
+-import java.util.concurrent.ConcurrentHashMap;
+-import java.util.concurrent.Executors;
+-import java.util.concurrent.ScheduledExecutorService;
+-import java.util.concurrent.TimeUnit;
+-
+ public class CompactionStore {
+
+ public static final String COMPACTION_DIR = "compaction";
+@@ -76,7 +75,7 @@ public class CompactionStore {
+ this.positionMgr = new CompactionPositionMgr(compactionPath);
+ this.compactionThreadNum = Math.min(Runtime.getRuntime().availableProcessors(), Math.max(1, config.getCompactionThreadNum()));
+
+- this.compactionSchedule = Executors.newScheduledThreadPool(this.compactionThreadNum,
++ this.compactionSchedule = ThreadUtils.newScheduledThreadPool(this.compactionThreadNum,
+ new ThreadFactoryImpl("compactionSchedule_"));
+ this.offsetMapSize = config.getMaxOffsetMapSize() / compactionThreadNum;
+
+diff --git a/store/src/main/java/org/apache/rocketmq/store/queue/ConsumeQueueStore.java b/store/src/main/java/org/apache/rocketmq/store/queue/ConsumeQueueStore.java
+index 8d38503b3..d03d15d65 100644
+--- a/store/src/main/java/org/apache/rocketmq/store/queue/ConsumeQueueStore.java
++++ b/store/src/main/java/org/apache/rocketmq/store/queue/ConsumeQueueStore.java
+@@ -24,7 +24,6 @@ import java.util.concurrent.CountDownLatch;
+ import java.util.concurrent.ExecutorService;
+ import java.util.concurrent.FutureTask;
+ import java.util.concurrent.LinkedBlockingQueue;
+-import java.util.concurrent.ThreadPoolExecutor;
+ import java.util.concurrent.TimeUnit;
+ import org.apache.rocketmq.common.ThreadFactoryImpl;
+ import org.apache.rocketmq.common.TopicConfig;
+@@ -34,6 +33,7 @@ import org.apache.rocketmq.common.message.MessageDecoder;
+ import org.apache.rocketmq.common.message.MessageExt;
+ import org.apache.rocketmq.common.topic.TopicValidator;
+ import org.apache.rocketmq.common.utils.QueueTypeUtils;
++import org.apache.rocketmq.common.utils.ThreadUtils;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+ import org.apache.rocketmq.store.CommitLog;
+@@ -175,7 +175,7 @@ public class ConsumeQueueStore {
+ }
+
+ private ExecutorService buildExecutorService(BlockingQueue<Runnable> blockingQueue, String threadNamePrefix) {
+- return new ThreadPoolExecutor(
++ return ThreadUtils.newThreadPoolExecutor(
+ this.messageStore.getBrokerConfig().getRecoverThreadPoolNums(),
+ this.messageStore.getBrokerConfig().getRecoverThreadPoolNums(),
+ 1000 * 60,
+diff --git a/store/src/main/java/org/apache/rocketmq/store/stats/BrokerStatsManager.java b/store/src/main/java/org/apache/rocketmq/store/stats/BrokerStatsManager.java
+index 2dd3fc5b5..489d7b4fb 100644
+--- a/store/src/main/java/org/apache/rocketmq/store/stats/BrokerStatsManager.java
++++ b/store/src/main/java/org/apache/rocketmq/store/stats/BrokerStatsManager.java
+@@ -17,7 +17,6 @@
+ package org.apache.rocketmq.store.stats;
+
+ import java.util.HashMap;
+-import java.util.concurrent.Executors;
+ import java.util.concurrent.ScheduledExecutorService;
+ import org.apache.commons.lang3.tuple.Pair;
+ import org.apache.rocketmq.common.BrokerConfig;
+@@ -32,13 +31,14 @@ import org.apache.rocketmq.common.statistics.StatisticsItemScheduledPrinter;
+ import org.apache.rocketmq.common.statistics.StatisticsItemStateGetter;
+ import org.apache.rocketmq.common.statistics.StatisticsKindMeta;
+ import org.apache.rocketmq.common.statistics.StatisticsManager;
++import org.apache.rocketmq.common.stats.MomentStatsItemSet;
+ import org.apache.rocketmq.common.stats.Stats;
++import org.apache.rocketmq.common.stats.StatsItem;
++import org.apache.rocketmq.common.stats.StatsItemSet;
+ import org.apache.rocketmq.common.topic.TopicValidator;
++import org.apache.rocketmq.common.utils.ThreadUtils;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+-import org.apache.rocketmq.common.stats.MomentStatsItemSet;
+-import org.apache.rocketmq.common.stats.StatsItem;
+-import org.apache.rocketmq.common.stats.StatsItemSet;
+
+ public class BrokerStatsManager {
+
+@@ -281,11 +281,11 @@ public class BrokerStatsManager {
+
+ private void initScheduleService() {
+ this.scheduledExecutorService =
+- Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("BrokerStatsThread", true, brokerConfig));
++ ThreadUtils.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("BrokerStatsThread", true, brokerConfig));
+ this.commercialExecutor =
+- Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("CommercialStatsThread", true, brokerConfig));
++ ThreadUtils.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("CommercialStatsThread", true, brokerConfig));
+ this.accountExecutor =
+- Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("AccountStatsThread", true, brokerConfig));
++ ThreadUtils.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("AccountStatsThread", true, brokerConfig));
+ }
+
+ public MomentStatsItemSet getMomentStatsItemSetFallSize() {
+diff --git a/store/src/main/java/org/apache/rocketmq/store/timer/TimerMessageStore.java b/store/src/main/java/org/apache/rocketmq/store/timer/TimerMessageStore.java
+index 181f7087a..0d50de65a 100644
+--- a/store/src/main/java/org/apache/rocketmq/store/timer/TimerMessageStore.java
++++ b/store/src/main/java/org/apache/rocketmq/store/timer/TimerMessageStore.java
+@@ -35,7 +35,6 @@ import java.util.Set;
+ import java.util.concurrent.BlockingQueue;
+ import java.util.concurrent.ConcurrentSkipListSet;
+ import java.util.concurrent.CountDownLatch;
+-import java.util.concurrent.Executors;
+ import java.util.concurrent.LinkedBlockingDeque;
+ import java.util.concurrent.ScheduledExecutorService;
+ import java.util.concurrent.TimeUnit;
+@@ -54,6 +53,7 @@ import org.apache.rocketmq.common.message.MessageDecoder;
+ import org.apache.rocketmq.common.message.MessageExt;
+ import org.apache.rocketmq.common.message.MessageExtBrokerInner;
+ import org.apache.rocketmq.common.topic.TopicValidator;
++import org.apache.rocketmq.common.utils.ThreadUtils;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+ import org.apache.rocketmq.store.ConsumeQueue;
+@@ -174,11 +174,11 @@ public class TimerMessageStore {
+ this.lastBrokerRole = storeConfig.getBrokerRole();
+
+ if (messageStore instanceof DefaultMessageStore) {
+- scheduler = Executors.newSingleThreadScheduledExecutor(
++ scheduler = ThreadUtils.newSingleThreadScheduledExecutor(
+ new ThreadFactoryImpl("TimerScheduledThread",
+ ((DefaultMessageStore) messageStore).getBrokerIdentity()));
+ } else {
+- scheduler = Executors.newSingleThreadScheduledExecutor(
++ scheduler = ThreadUtils.newSingleThreadScheduledExecutor(
+ new ThreadFactoryImpl("TimerScheduledThread"));
+ }
+
+diff --git a/test/src/main/java/org/apache/rocketmq/test/util/StatUtil.java b/test/src/main/java/org/apache/rocketmq/test/util/StatUtil.java
+index f3d105bc6..080b7e385 100644
+--- a/test/src/main/java/org/apache/rocketmq/test/util/StatUtil.java
++++ b/test/src/main/java/org/apache/rocketmq/test/util/StatUtil.java
+@@ -28,7 +28,6 @@ import java.util.concurrent.ScheduledExecutorService;
+ import java.util.concurrent.TimeUnit;
+ import java.util.concurrent.atomic.AtomicInteger;
+ import java.util.concurrent.atomic.AtomicLong;
+-
+ import javax.annotation.Generated;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/common/TieredStoreExecutor.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/common/TieredStoreExecutor.java
+index 6dd0e8846..65d586f43 100644
+--- a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/common/TieredStoreExecutor.java
++++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/common/TieredStoreExecutor.java
+@@ -20,10 +20,10 @@ import java.util.concurrent.BlockingQueue;
+ import java.util.concurrent.ExecutorService;
+ import java.util.concurrent.LinkedBlockingQueue;
+ import java.util.concurrent.ScheduledExecutorService;
+-import java.util.concurrent.ScheduledThreadPoolExecutor;
+ import java.util.concurrent.ThreadPoolExecutor;
+ import java.util.concurrent.TimeUnit;
+ import org.apache.rocketmq.common.ThreadFactoryImpl;
++import org.apache.rocketmq.common.utils.ThreadUtils;
+
+ public class TieredStoreExecutor {
+
+@@ -43,20 +43,20 @@ public class TieredStoreExecutor {
+ public static ExecutorService compactIndexFileExecutor;
+
+ public static void init() {
+- commonScheduledExecutor = new ScheduledThreadPoolExecutor(
++ commonScheduledExecutor = ThreadUtils.newScheduledThreadPool(
+ Math.max(4, Runtime.getRuntime().availableProcessors()),
+ new ThreadFactoryImpl("TieredCommonExecutor_"));
+
+- commitExecutor = new ScheduledThreadPoolExecutor(
++ commitExecutor = ThreadUtils.newScheduledThreadPool(
+ Math.max(16, Runtime.getRuntime().availableProcessors() * 4),
+ new ThreadFactoryImpl("TieredCommitExecutor_"));
+
+- cleanExpiredFileExecutor = new ScheduledThreadPoolExecutor(
++ cleanExpiredFileExecutor = ThreadUtils.newScheduledThreadPool(
+ Math.max(4, Runtime.getRuntime().availableProcessors()),
+ new ThreadFactoryImpl("TieredCleanFileExecutor_"));
+
+ dispatchThreadPoolQueue = new LinkedBlockingQueue<>(QUEUE_CAPACITY);
+- dispatchExecutor = new ThreadPoolExecutor(
++ dispatchExecutor = ThreadUtils.newThreadPoolExecutor(
+ Math.max(2, Runtime.getRuntime().availableProcessors()),
+ Math.max(16, Runtime.getRuntime().availableProcessors() * 4),
+ 1000 * 60,
+@@ -66,7 +66,7 @@ public class TieredStoreExecutor {
+ new ThreadPoolExecutor.DiscardOldestPolicy());
+
+ fetchDataThreadPoolQueue = new LinkedBlockingQueue<>(QUEUE_CAPACITY);
+- fetchDataExecutor = new ThreadPoolExecutor(
++ fetchDataExecutor = ThreadUtils.newThreadPoolExecutor(
+ Math.max(16, Runtime.getRuntime().availableProcessors() * 4),
+ Math.max(64, Runtime.getRuntime().availableProcessors() * 8),
+ 1000 * 60,
+@@ -75,7 +75,7 @@ public class TieredStoreExecutor {
+ new ThreadFactoryImpl("TieredFetchExecutor_"));
+
+ compactIndexFileThreadPoolQueue = new LinkedBlockingQueue<>(QUEUE_CAPACITY);
+- compactIndexFileExecutor = new ThreadPoolExecutor(
++ compactIndexFileExecutor = ThreadUtils.newThreadPoolExecutor(
+ 1,
+ 1,
+ 1000 * 60,
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExtImpl.java b/tools/src/main/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExtImpl.java
+index fa3596d51..1ebff6d8a 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExtImpl.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExtImpl.java
+@@ -66,6 +66,7 @@ import org.apache.rocketmq.common.namesrv.NamesrvUtil;
+ import org.apache.rocketmq.common.topic.TopicValidator;
+ import org.apache.rocketmq.common.utils.NetworkUtil;
+ import org.apache.rocketmq.common.BoundaryType;
++import org.apache.rocketmq.common.utils.ThreadUtils;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+ import org.apache.rocketmq.remoting.RPCHook;
+@@ -193,7 +194,7 @@ public class DefaultMQAdminExtImpl implements MQAdminExt, MQAdminExtInner {
+
+ int threadPoolCoreSize = Integer.parseInt(System.getProperty("rocketmq.admin.threadpool.coresize", "20"));
+
+- this.threadPoolExecutor = new ThreadPoolExecutor(threadPoolCoreSize, 100, 5, TimeUnit.MINUTES, new LinkedBlockingQueue<>(), new ThreadFactoryImpl("DefaultMQAdminExtImpl_"));
++ this.threadPoolExecutor = (ThreadPoolExecutor) ThreadUtils.newThreadPoolExecutor(threadPoolCoreSize, 100, 5, TimeUnit.MINUTES, new LinkedBlockingQueue<>(), new ThreadFactoryImpl("DefaultMQAdminExtImpl_"));
+
+ break;
+ case RUNNING:
+--
+2.32.0.windows.2
+
+
+From dad6b4dadfec7a58e78a6715ec16c2eb6b17ff27 Mon Sep 17 00:00:00 2001
+From: Ziyi Tan <ajb459684460@gmail.com>
+Date: Mon, 11 Sep 2023 14:34:10 +0800
+Subject: [PATCH 2/6] [ISSUE #7334] `registerIncrementBrokerData` for single
+ topic update (#7335)
+
+Signed-off-by: Ziy1-Tan <ajb459684460@gmail.com>
+---
+ .../broker/topic/TopicConfigManager.java | 30 +++++++++++++++----
+ 1 file changed, 25 insertions(+), 5 deletions(-)
+
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java b/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java
+index 4e3c1736c..754605438 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java
+@@ -290,7 +290,11 @@ public class TopicConfigManager extends ConfigManager {
+ }
+
+ if (createNew) {
+- this.brokerController.registerBrokerAll(false, true, true);
++ if (brokerController.getBrokerConfig().isEnableSingleTopicRegister()) {
++ this.brokerController.registerSingleTopicAll(topicConfig);
++ } else {
++ this.brokerController.registerIncrementBrokerData(topicConfig, dataVersion);
++ }
+ }
+
+ return topicConfig;
+@@ -394,7 +398,11 @@ public class TopicConfigManager extends ConfigManager {
+ }
+
+ if (createNew) {
+- this.brokerController.registerBrokerAll(false, true, true);
++ if (brokerController.getBrokerConfig().isEnableSingleTopicRegister()) {
++ this.brokerController.registerSingleTopicAll(topicConfig);
++ } else {
++ this.brokerController.registerIncrementBrokerData(topicConfig, dataVersion);
++ }
+ }
+
+ return topicConfig;
+@@ -435,7 +443,11 @@ public class TopicConfigManager extends ConfigManager {
+ }
+
+ if (createNew) {
+- this.brokerController.registerBrokerAll(false, true, true);
++ if (brokerController.getBrokerConfig().isEnableSingleTopicRegister()) {
++ this.brokerController.registerSingleTopicAll(topicConfig);
++ } else {
++ this.brokerController.registerIncrementBrokerData(topicConfig, dataVersion);
++ }
+ }
+
+ return topicConfig;
+@@ -461,7 +473,11 @@ public class TopicConfigManager extends ConfigManager {
+ dataVersion.nextVersion(stateMachineVersion);
+
+ this.persist();
+- this.brokerController.registerBrokerAll(false, true, true);
++ if (brokerController.getBrokerConfig().isEnableSingleTopicRegister()) {
++ this.brokerController.registerSingleTopicAll(topicConfig);
++ } else {
++ this.brokerController.registerIncrementBrokerData(topicConfig, dataVersion);
++ }
+ }
+ }
+
+@@ -484,7 +500,11 @@ public class TopicConfigManager extends ConfigManager {
+ dataVersion.nextVersion(stateMachineVersion);
+
+ this.persist();
+- this.brokerController.registerBrokerAll(false, true, true);
++ if (brokerController.getBrokerConfig().isEnableSingleTopicRegister()) {
++ this.brokerController.registerSingleTopicAll(topicConfig);
++ } else {
++ this.brokerController.registerIncrementBrokerData(topicConfig, dataVersion);
++ }
+ }
+ }
+
+--
+2.32.0.windows.2
+
+
+From 0dbd0772b99f618f757d42cd64542b83e2100e4f Mon Sep 17 00:00:00 2001
+From: Ziyi Tan <ajb459684460@gmail.com>
+Date: Mon, 11 Sep 2023 15:48:07 +0800
+Subject: [PATCH 3/6] [ISSUE #7326] Split the request to register to the
+ nameserver (#7325)
+
+Signed-off-by: Ziy1-Tan <ajb459684460@gmail.com>
+---
+ .../rocketmq/broker/BrokerController.java | 41 +++++++++++--------
+ .../broker/topic/TopicConfigManager.java | 21 ++++++++++
+ .../apache/rocketmq/common/BrokerConfig.java | 24 +++++++++++
+ .../test/route/CreateAndUpdateTopicIT.java | 31 ++++++++++++++
+ 4 files changed, 99 insertions(+), 18 deletions(-)
+
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java
+index 275b64b1a..9e49f636d 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java
+@@ -1765,29 +1765,34 @@ public class BrokerController {
+ }
+
+ public synchronized void registerBrokerAll(final boolean checkOrderConfig, boolean oneway, boolean forceRegister) {
++ ConcurrentMap<String, TopicConfig> topicConfigMap = this.getTopicConfigManager().getTopicConfigTable();
++ ConcurrentHashMap<String, TopicConfig> topicConfigTable = new ConcurrentHashMap<>();
+
+- TopicConfigAndMappingSerializeWrapper topicConfigWrapper = new TopicConfigAndMappingSerializeWrapper();
+-
+- topicConfigWrapper.setDataVersion(this.getTopicConfigManager().getDataVersion());
+- topicConfigWrapper.setTopicConfigTable(this.getTopicConfigManager().getTopicConfigTable());
+-
+- topicConfigWrapper.setTopicQueueMappingInfoMap(this.getTopicQueueMappingManager().getTopicQueueMappingTable().entrySet().stream().map(
+- entry -> new AbstractMap.SimpleImmutableEntry<>(entry.getKey(), TopicQueueMappingDetail.cloneAsMappingInfo(entry.getValue()))
+- ).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
+-
+- if (!PermName.isWriteable(this.getBrokerConfig().getBrokerPermission())
+- || !PermName.isReadable(this.getBrokerConfig().getBrokerPermission())) {
+- ConcurrentHashMap<String, TopicConfig> topicConfigTable = new ConcurrentHashMap<>();
+- for (TopicConfig topicConfig : topicConfigWrapper.getTopicConfigTable().values()) {
+- TopicConfig tmp =
++ for (TopicConfig topicConfig : topicConfigMap.values()) {
++ if (!PermName.isWriteable(this.getBrokerConfig().getBrokerPermission())
++ || !PermName.isReadable(this.getBrokerConfig().getBrokerPermission())) {
++ topicConfigTable.put(topicConfig.getTopicName(),
+ new TopicConfig(topicConfig.getTopicName(), topicConfig.getReadQueueNums(), topicConfig.getWriteQueueNums(),
+- topicConfig.getPerm() & this.brokerConfig.getBrokerPermission(), topicConfig.getTopicSysFlag());
+- topicConfigTable.put(topicConfig.getTopicName(), tmp);
++ topicConfig.getPerm() & getBrokerConfig().getBrokerPermission()));
++ } else {
++ topicConfigTable.put(topicConfig.getTopicName(), topicConfig);
++ }
++
++ if (this.brokerConfig.isEnableSplitRegistration()
++ && topicConfigTable.size() >= this.brokerConfig.getSplitRegistrationSize()) {
++ TopicConfigAndMappingSerializeWrapper topicConfigWrapper = this.getTopicConfigManager().buildSerializeWrapper(topicConfigTable);
++ doRegisterBrokerAll(checkOrderConfig, oneway, topicConfigWrapper);
++ topicConfigTable.clear();
+ }
+- topicConfigWrapper.setTopicConfigTable(topicConfigTable);
+ }
+
+- if (forceRegister || needRegister(this.brokerConfig.getBrokerClusterName(),
++ Map<String, TopicQueueMappingInfo> topicQueueMappingInfoMap = this.getTopicQueueMappingManager().getTopicQueueMappingTable().entrySet().stream()
++ .map(entry -> new AbstractMap.SimpleImmutableEntry<>(entry.getKey(), TopicQueueMappingDetail.cloneAsMappingInfo(entry.getValue())))
++ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
++
++ TopicConfigAndMappingSerializeWrapper topicConfigWrapper = this.getTopicConfigManager().
++ buildSerializeWrapper(topicConfigTable, topicQueueMappingInfoMap);
++ if (this.brokerConfig.isEnableSplitRegistration() || forceRegister || needRegister(this.brokerConfig.getBrokerClusterName(),
+ this.getBrokerAddr(),
+ this.brokerConfig.getBrokerName(),
+ this.brokerConfig.getBrokerId(),
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java b/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java
+index 754605438..8537929be 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java
+@@ -29,6 +29,7 @@ import java.util.concurrent.locks.ReentrantLock;
+
+ import com.google.common.collect.ImmutableMap;
+
++import com.google.common.collect.Maps;
+ import org.apache.commons.lang3.StringUtils;
+ import org.apache.rocketmq.broker.BrokerController;
+ import org.apache.rocketmq.broker.BrokerPathConfigHelper;
+@@ -47,7 +48,9 @@ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+ import org.apache.rocketmq.remoting.protocol.DataVersion;
+ import org.apache.rocketmq.remoting.protocol.body.KVTable;
++import org.apache.rocketmq.remoting.protocol.body.TopicConfigAndMappingSerializeWrapper;
+ import org.apache.rocketmq.remoting.protocol.body.TopicConfigSerializeWrapper;
++import org.apache.rocketmq.remoting.protocol.statictopic.TopicQueueMappingInfo;
+
+ import static com.google.common.base.Preconditions.checkNotNull;
+
+@@ -609,6 +612,24 @@ public class TopicConfigManager extends ConfigManager {
+ return topicConfigSerializeWrapper;
+ }
+
++ public TopicConfigAndMappingSerializeWrapper buildSerializeWrapper(final ConcurrentMap<String, TopicConfig> topicConfigTable) {
++ return buildSerializeWrapper(topicConfigTable, Maps.newHashMap());
++ }
++
++ public TopicConfigAndMappingSerializeWrapper buildSerializeWrapper(
++ final ConcurrentMap<String, TopicConfig> topicConfigTable,
++ final Map<String, TopicQueueMappingInfo> topicQueueMappingInfoMap
++ ) {
++ TopicConfigAndMappingSerializeWrapper topicConfigWrapper = new TopicConfigAndMappingSerializeWrapper();
++ topicConfigWrapper.setTopicConfigTable(topicConfigTable);
++ topicConfigWrapper.setTopicQueueMappingInfoMap(topicQueueMappingInfoMap);
++ topicConfigWrapper.setDataVersion(this.getDataVersion());
++ if (this.brokerController.getBrokerConfig().isEnableSplitRegistration()) {
++ this.getDataVersion().nextVersion();
++ }
++ return topicConfigWrapper;
++ }
++
+ @Override
+ public String encode() {
+ return encode(false);
+diff --git a/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java b/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java
+index 45d26b29c..0d248c4e1 100644
+--- a/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java
++++ b/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java
+@@ -396,6 +396,14 @@ public class BrokerConfig extends BrokerIdentity {
+
+ private boolean enableMixedMessageType = false;
+
++ /**
++ * This flag and deleteTopicWithBrokerRegistration flag in the NameServer cannot be set to true at the same time,
++ * otherwise there will be a loss of routing
++ */
++ private boolean enableSplitRegistration = false;
++
++ private int splitRegistrationSize = 800;
++
+ public long getMaxPopPollingSize() {
+ return maxPopPollingSize;
+ }
+@@ -1731,4 +1739,20 @@ public class BrokerConfig extends BrokerIdentity {
+ public void setEnableMixedMessageType(boolean enableMixedMessageType) {
+ this.enableMixedMessageType = enableMixedMessageType;
+ }
++
++ public boolean isEnableSplitRegistration() {
++ return enableSplitRegistration;
++ }
++
++ public void setEnableSplitRegistration(boolean enableSplitRegistration) {
++ this.enableSplitRegistration = enableSplitRegistration;
++ }
++
++ public int getSplitRegistrationSize() {
++ return splitRegistrationSize;
++ }
++
++ public void setSplitRegistrationSize(int splitRegistrationSize) {
++ this.splitRegistrationSize = splitRegistrationSize;
++ }
+ }
+diff --git a/test/src/test/java/org/apache/rocketmq/test/route/CreateAndUpdateTopicIT.java b/test/src/test/java/org/apache/rocketmq/test/route/CreateAndUpdateTopicIT.java
+index 7e3c7b871..2370e68c0 100644
+--- a/test/src/test/java/org/apache/rocketmq/test/route/CreateAndUpdateTopicIT.java
++++ b/test/src/test/java/org/apache/rocketmq/test/route/CreateAndUpdateTopicIT.java
+@@ -17,6 +17,7 @@
+
+ package org.apache.rocketmq.test.route;
+
++import org.apache.rocketmq.common.TopicConfig;
+ import org.apache.rocketmq.remoting.protocol.route.TopicRouteData;
+ import org.apache.rocketmq.test.base.BaseConf;
+ import org.apache.rocketmq.test.util.MQAdminTestUtils;
+@@ -111,4 +112,34 @@ public class CreateAndUpdateTopicIT extends BaseConf {
+ brokerController3.getBrokerConfig().setEnableSingleTopicRegister(false);
+ namesrvController.getNamesrvConfig().setDeleteTopicWithBrokerRegistration(false);
+ }
++
++ @Test
++ public void testCreateOrUpdateTopic_EnableSplitRegistration() {
++ brokerController1.getBrokerConfig().setEnableSplitRegistration(true);
++ brokerController2.getBrokerConfig().setEnableSplitRegistration(true);
++ brokerController3.getBrokerConfig().setEnableSplitRegistration(true);
++
++ String testTopic = "test-topic-";
++
++ for (int i = 0; i < 1000; i++) {
++ TopicConfig topicConfig = new TopicConfig(testTopic + i, 8, 8);
++ brokerController1.getTopicConfigManager().updateTopicConfig(topicConfig);
++ brokerController2.getTopicConfigManager().updateTopicConfig(topicConfig);
++ brokerController3.getTopicConfigManager().updateTopicConfig(topicConfig);
++ }
++
++ brokerController1.registerBrokerAll(false, true, true);
++ brokerController2.registerBrokerAll(false, true, true);
++ brokerController3.registerBrokerAll(false, true, true);
++
++ for (int i = 0; i < 1000; i++) {
++ TopicRouteData route = MQAdminTestUtils.examineTopicRouteInfo(NAMESRV_ADDR, testTopic + i);
++ assertThat(route.getBrokerDatas()).hasSize(3);
++ assertThat(route.getQueueDatas()).hasSize(3);
++ }
++
++ brokerController1.getBrokerConfig().setEnableSplitRegistration(false);
++ brokerController2.getBrokerConfig().setEnableSplitRegistration(false);
++ brokerController3.getBrokerConfig().setEnableSplitRegistration(false);
++ }
+ }
+--
+2.32.0.windows.2
+
+
+From a9e353285cea762b0c5eab567bdfa8e5c8c2d279 Mon Sep 17 00:00:00 2001
+From: rongtong <jinrongtong5@163.com>
+Date: Mon, 11 Sep 2023 15:55:18 +0800
+Subject: [PATCH 4/6] Add the configuration of topicQueueLock number to better
+ support different scenarios (#7317)
+
+---
+ .../main/java/org/apache/rocketmq/store/CommitLog.java | 2 +-
+ .../java/org/apache/rocketmq/store/TopicQueueLock.java | 8 ++++++++
+ .../rocketmq/store/config/MessageStoreConfig.java | 10 ++++++++++
+ 3 files changed, 19 insertions(+), 1 deletion(-)
+
+diff --git a/store/src/main/java/org/apache/rocketmq/store/CommitLog.java b/store/src/main/java/org/apache/rocketmq/store/CommitLog.java
+index e6ee3bacc..456bf2b86 100644
+--- a/store/src/main/java/org/apache/rocketmq/store/CommitLog.java
++++ b/store/src/main/java/org/apache/rocketmq/store/CommitLog.java
+@@ -122,7 +122,7 @@ public class CommitLog implements Swappable {
+
+ this.flushDiskWatcher = new FlushDiskWatcher();
+
+- this.topicQueueLock = new TopicQueueLock();
++ this.topicQueueLock = new TopicQueueLock(messageStore.getMessageStoreConfig().getTopicQueueLockNum());
+
+ this.commitLogSize = messageStore.getMessageStoreConfig().getMappedFileSizeCommitLog();
+ }
+diff --git a/store/src/main/java/org/apache/rocketmq/store/TopicQueueLock.java b/store/src/main/java/org/apache/rocketmq/store/TopicQueueLock.java
+index a78eeed23..5a131b5c3 100644
+--- a/store/src/main/java/org/apache/rocketmq/store/TopicQueueLock.java
++++ b/store/src/main/java/org/apache/rocketmq/store/TopicQueueLock.java
+@@ -34,6 +34,14 @@ public class TopicQueueLock {
+ }
+ }
+
++ public TopicQueueLock(int size) {
++ this.size = size;
++ this.lockList = new ArrayList<>(size);
++ for (int i = 0; i < this.size; i++) {
++ this.lockList.add(new ReentrantLock());
++ }
++ }
++
+ public void lock(String topicQueueKey) {
+ Lock lock = this.lockList.get((topicQueueKey.hashCode() & 0x7fffffff) % this.size);
+ lock.lock();
+diff --git a/store/src/main/java/org/apache/rocketmq/store/config/MessageStoreConfig.java b/store/src/main/java/org/apache/rocketmq/store/config/MessageStoreConfig.java
+index efb728ac0..9fa448043 100644
+--- a/store/src/main/java/org/apache/rocketmq/store/config/MessageStoreConfig.java
++++ b/store/src/main/java/org/apache/rocketmq/store/config/MessageStoreConfig.java
+@@ -401,6 +401,8 @@ public class MessageStoreConfig {
+ private long memTableFlushInterval = 60 * 60 * 1000L;
+ private boolean enableRocksDBLog = false;
+
++ private int topicQueueLockNum = 32;
++
+ public boolean isDebugLockEnable() {
+ return debugLockEnable;
+ }
+@@ -1751,4 +1753,12 @@ public class MessageStoreConfig {
+ public void setEnableRocksDBLog(boolean enableRocksDBLog) {
+ this.enableRocksDBLog = enableRocksDBLog;
+ }
++
++ public int getTopicQueueLockNum() {
++ return topicQueueLockNum;
++ }
++
++ public void setTopicQueueLockNum(int topicQueueLockNum) {
++ this.topicQueueLockNum = topicQueueLockNum;
++ }
+ }
+--
+2.32.0.windows.2
+
+
+From 57f04c95d3a2ba6b91583058a6e4eda209f72d6e Mon Sep 17 00:00:00 2001
+From: guyinyou <36399867+guyinyou@users.noreply.github.com>
+Date: Mon, 11 Sep 2023 18:23:25 +0800
+Subject: [PATCH 5/6] [ISSUE #7343] Rollback modifications to registerProcessor
+
+Co-authored-by: guyinyou <guyinyou.gyy@alibaba-inc.com>
+---
+ .../java/org/apache/rocketmq/broker/BrokerController.java | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java
+index 9e49f636d..13a3feb4e 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java
+@@ -827,6 +827,8 @@ public class BrokerController {
+
+ initializeResources();
+
++ registerProcessor();
++
+ initializeScheduledTasks();
+
+ initialTransaction();
+@@ -1687,8 +1689,6 @@ public class BrokerController {
+ }
+ }
+ }, 10, 5, TimeUnit.SECONDS);
+-
+- registerProcessor();
+ }
+
+ protected void scheduleSendHeartbeat() {
+--
+2.32.0.windows.2
+
+
+From dad6ad09d13dadc36b6342671c77f619bbb8c522 Mon Sep 17 00:00:00 2001
+From: Ao Qiao <qiao_ao@foxmail.com>
+Date: Tue, 12 Sep 2023 08:28:45 +0800
+Subject: [PATCH 6/6] [ISSUE #7340] Abstract Duplicate code into a method in
+ `TopicConfigManager` (#7341)
+
+---
+ .../broker/topic/TopicConfigManager.java | 44 ++++++-------------
+ 1 file changed, 14 insertions(+), 30 deletions(-)
+
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java b/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java
+index 8537929be..511d29e12 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java
+@@ -293,11 +293,7 @@ public class TopicConfigManager extends ConfigManager {
+ }
+
+ if (createNew) {
+- if (brokerController.getBrokerConfig().isEnableSingleTopicRegister()) {
+- this.brokerController.registerSingleTopicAll(topicConfig);
+- } else {
+- this.brokerController.registerIncrementBrokerData(topicConfig, dataVersion);
+- }
++ registerBrokerData(topicConfig);
+ }
+
+ return topicConfig;
+@@ -337,11 +333,7 @@ public class TopicConfigManager extends ConfigManager {
+ log.error("createTopicIfAbsent ", e);
+ }
+ if (createNew && register) {
+- if (brokerController.getBrokerConfig().isEnableSingleTopicRegister()) {
+- this.brokerController.registerSingleTopicAll(topicConfig);
+- } else {
+- this.brokerController.registerIncrementBrokerData(topicConfig, dataVersion);
+- }
++ registerBrokerData(topicConfig);
+ }
+ return getTopicConfig(topicConfig.getTopicName());
+ }
+@@ -401,11 +393,7 @@ public class TopicConfigManager extends ConfigManager {
+ }
+
+ if (createNew) {
+- if (brokerController.getBrokerConfig().isEnableSingleTopicRegister()) {
+- this.brokerController.registerSingleTopicAll(topicConfig);
+- } else {
+- this.brokerController.registerIncrementBrokerData(topicConfig, dataVersion);
+- }
++ registerBrokerData(topicConfig);
+ }
+
+ return topicConfig;
+@@ -446,11 +434,7 @@ public class TopicConfigManager extends ConfigManager {
+ }
+
+ if (createNew) {
+- if (brokerController.getBrokerConfig().isEnableSingleTopicRegister()) {
+- this.brokerController.registerSingleTopicAll(topicConfig);
+- } else {
+- this.brokerController.registerIncrementBrokerData(topicConfig, dataVersion);
+- }
++ registerBrokerData(topicConfig);
+ }
+
+ return topicConfig;
+@@ -476,11 +460,7 @@ public class TopicConfigManager extends ConfigManager {
+ dataVersion.nextVersion(stateMachineVersion);
+
+ this.persist();
+- if (brokerController.getBrokerConfig().isEnableSingleTopicRegister()) {
+- this.brokerController.registerSingleTopicAll(topicConfig);
+- } else {
+- this.brokerController.registerIncrementBrokerData(topicConfig, dataVersion);
+- }
++ registerBrokerData(topicConfig);
+ }
+ }
+
+@@ -503,11 +483,7 @@ public class TopicConfigManager extends ConfigManager {
+ dataVersion.nextVersion(stateMachineVersion);
+
+ this.persist();
+- if (brokerController.getBrokerConfig().isEnableSingleTopicRegister()) {
+- this.brokerController.registerSingleTopicAll(topicConfig);
+- } else {
+- this.brokerController.registerIncrementBrokerData(topicConfig, dataVersion);
+- }
++ registerBrokerData(topicConfig);
+ }
+ }
+
+@@ -699,6 +675,14 @@ public class TopicConfigManager extends ConfigManager {
+ }
+ }
+
++ private void registerBrokerData(TopicConfig topicConfig) {
++ if (brokerController.getBrokerConfig().isEnableSingleTopicRegister()) {
++ this.brokerController.registerSingleTopicAll(topicConfig);
++ } else {
++ this.brokerController.registerIncrementBrokerData(topicConfig, dataVersion);
++ }
++ }
++
+ public boolean containsTopic(String topic) {
+ return topicConfigTable.containsKey(topic);
+ }
+--
+2.32.0.windows.2
+
diff --git a/patch018-backport-enhancement-of-tiered-storage.patch b/patch018-backport-enhancement-of-tiered-storage.patch
new file mode 100644
index 000000000..ed79df9c7
--- /dev/null
+++ b/patch018-backport-enhancement-of-tiered-storage.patch
@@ -0,0 +1,601 @@
+From 1a8e7cb17cb29ed33b0196b52e452a6e76ade781 Mon Sep 17 00:00:00 2001
+From: yuz10 <845238369@qq.com>
+Date: Tue, 12 Sep 2023 19:33:41 +0800
+Subject: [PATCH 1/5] [ISSUE #7345] Fix wrong result of searchOffset in tiered
+ storage
+
+---
+ .../tieredstore/file/TieredFlatFile.java | 5 +-
+ .../tieredstore/file/TieredFlatFileTest.java | 46 +++++++++++++++++--
+ 2 files changed, 46 insertions(+), 5 deletions(-)
+
+diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredFlatFile.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredFlatFile.java
+index 426c4e09d..d973179ee 100644
+--- a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredFlatFile.java
++++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredFlatFile.java
+@@ -365,7 +365,10 @@ public class TieredFlatFile {
+ if (!segmentList.isEmpty()) {
+ return boundaryType == BoundaryType.UPPER ? segmentList.get(0) : segmentList.get(segmentList.size() - 1);
+ }
+- return fileSegmentList.isEmpty() ? null : fileSegmentList.get(fileSegmentList.size() - 1);
++ if (fileSegmentList.isEmpty()) {
++ return null;
++ }
++ return boundaryType == BoundaryType.UPPER ? fileSegmentList.get(fileSegmentList.size() - 1) : fileSegmentList.get(0);
+ } finally {
+ fileSegmentLock.readLock().unlock();
+ }
+diff --git a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/file/TieredFlatFileTest.java b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/file/TieredFlatFileTest.java
+index 7a4d05969..7e2fbf201 100644
+--- a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/file/TieredFlatFileTest.java
++++ b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/file/TieredFlatFileTest.java
+@@ -16,10 +16,7 @@
+ */
+ package org.apache.rocketmq.tieredstore.file;
+
+-import java.io.IOException;
+-import java.nio.ByteBuffer;
+-import java.util.ArrayList;
+-import java.util.List;
++import org.apache.rocketmq.common.BoundaryType;
+ import org.apache.rocketmq.common.message.MessageQueue;
+ import org.apache.rocketmq.tieredstore.TieredStoreTestUtil;
+ import org.apache.rocketmq.tieredstore.common.FileSegmentType;
+@@ -35,6 +32,11 @@ import org.junit.Assert;
+ import org.junit.Before;
+ import org.junit.Test;
+
++import java.io.IOException;
++import java.nio.ByteBuffer;
++import java.util.ArrayList;
++import java.util.List;
++
+ public class TieredFlatFileTest {
+
+ private final String storePath = TieredStoreTestUtil.getRandomStorePath();
+@@ -301,4 +303,40 @@ public class TieredFlatFileTest {
+ fileQueue.rollingNewFile();
+ Assert.assertEquals(2, fileQueue.getFileSegmentCount());
+ }
++
++ @Test
++ public void testGetFileByTime() {
++ String filePath = TieredStoreUtil.toPath(queue);
++ TieredFlatFile tieredFlatFile = fileQueueFactory.createFlatFileForCommitLog(filePath);
++ TieredFileSegment fileSegment1 = new MemoryFileSegment(FileSegmentType.CONSUME_QUEUE, queue, 1100, storeConfig);
++ fileSegment1.setMinTimestamp(100);
++ fileSegment1.setMaxTimestamp(200);
++
++ TieredFileSegment fileSegment2 = new MemoryFileSegment(FileSegmentType.CONSUME_QUEUE, queue, 1100, storeConfig);
++ fileSegment2.setMinTimestamp(200);
++ fileSegment2.setMaxTimestamp(300);
++
++ tieredFlatFile.getFileSegmentList().add(fileSegment1);
++ tieredFlatFile.getFileSegmentList().add(fileSegment2);
++
++ TieredFileSegment segmentUpper = tieredFlatFile.getFileByTime(400, BoundaryType.UPPER);
++ Assert.assertEquals(fileSegment2, segmentUpper);
++
++ TieredFileSegment segmentLower = tieredFlatFile.getFileByTime(400, BoundaryType.LOWER);
++ Assert.assertEquals(fileSegment2, segmentLower);
++
++
++ TieredFileSegment segmentUpper2 = tieredFlatFile.getFileByTime(0, BoundaryType.UPPER);
++ Assert.assertEquals(fileSegment1, segmentUpper2);
++
++ TieredFileSegment segmentLower2 = tieredFlatFile.getFileByTime(0, BoundaryType.LOWER);
++ Assert.assertEquals(fileSegment1, segmentLower2);
++
++
++ TieredFileSegment segmentUpper3 = tieredFlatFile.getFileByTime(200, BoundaryType.UPPER);
++ Assert.assertEquals(fileSegment1, segmentUpper3);
++
++ TieredFileSegment segmentLower3 = tieredFlatFile.getFileByTime(200, BoundaryType.LOWER);
++ Assert.assertEquals(fileSegment2, segmentLower3);
++ }
+ }
+--
+2.32.0.windows.2
+
+
+From fd32dae2ab59f86dd215eeec405bf4fa6212bcb3 Mon Sep 17 00:00:00 2001
+From: lizhimins <707364882@qq.com>
+Date: Tue, 12 Sep 2023 19:58:08 +0800
+Subject: [PATCH 2/5] [ISSUE #6633] Not clear uninitialized files and fix
+ metadata recover (#7342)
+
+---
+ .../tieredstore/file/TieredFlatFile.java | 53 +++++++------------
+ .../file/TieredFlatFileManager.java | 10 ++--
+ 2 files changed, 22 insertions(+), 41 deletions(-)
+
+diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredFlatFile.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredFlatFile.java
+index d973179ee..d96eb6e8f 100644
+--- a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredFlatFile.java
++++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredFlatFile.java
+@@ -16,7 +16,6 @@
+ */
+ package org.apache.rocketmq.tieredstore.file;
+
+-import com.alibaba.fastjson.JSON;
+ import com.google.common.annotations.VisibleForTesting;
+ import java.nio.ByteBuffer;
+ import java.util.ArrayList;
+@@ -25,13 +24,13 @@ import java.util.Comparator;
+ import java.util.HashSet;
+ import java.util.LinkedList;
+ import java.util.List;
+-import java.util.Objects;
+ import java.util.Set;
+ import java.util.concurrent.CompletableFuture;
+ import java.util.concurrent.CopyOnWriteArrayList;
+ import java.util.concurrent.locks.ReentrantReadWriteLock;
+ import java.util.stream.Collectors;
+ import javax.annotation.Nullable;
++import org.apache.rocketmq.common.BoundaryType;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+ import org.apache.rocketmq.tieredstore.common.AppendResult;
+@@ -43,7 +42,6 @@ import org.apache.rocketmq.tieredstore.metadata.TieredMetadataStore;
+ import org.apache.rocketmq.tieredstore.provider.FileSegmentAllocator;
+ import org.apache.rocketmq.tieredstore.provider.TieredFileSegment;
+ import org.apache.rocketmq.tieredstore.util.TieredStoreUtil;
+-import org.apache.rocketmq.common.BoundaryType;
+
+ public class TieredFlatFile {
+
+@@ -177,7 +175,10 @@ public class TieredFlatFile {
+ }
+ }
+
+- private FileSegmentMetadata getOrCreateFileSegmentMetadata(TieredFileSegment fileSegment) {
++ /**
++ * FileQueue Status: Sealed | Sealed | Sealed | Not sealed, Allow appended && Not Full
++ */
++ public void updateFileSegment(TieredFileSegment fileSegment) {
+
+ FileSegmentMetadata metadata = tieredMetadataStore.getFileSegment(
+ this.filePath, fileSegment.getFileType(), fileSegment.getBaseOffset());
+@@ -186,45 +187,24 @@ public class TieredFlatFile {
+ if (metadata == null) {
+ metadata = new FileSegmentMetadata(
+ this.filePath, fileSegment.getBaseOffset(), fileSegment.getFileType().getType());
+- metadata.setCreateTimestamp(fileSegment.getMinTimestamp());
+- metadata.setBeginTimestamp(fileSegment.getMinTimestamp());
+- metadata.setEndTimestamp(fileSegment.getMaxTimestamp());
+- if (fileSegment.isClosed()) {
+- metadata.setStatus(FileSegmentMetadata.STATUS_DELETED);
+- }
+- this.tieredMetadataStore.updateFileSegment(metadata);
++ metadata.setCreateTimestamp(System.currentTimeMillis());
+ }
+- return metadata;
+- }
+-
+- /**
+- * FileQueue Status: Sealed | Sealed | Sealed | Not sealed, Allow appended && Not Full
+- */
+- public void updateFileSegment(TieredFileSegment fileSegment) {
+- FileSegmentMetadata segmentMetadata = getOrCreateFileSegmentMetadata(fileSegment);
+
+- if (segmentMetadata.getStatus() == FileSegmentMetadata.STATUS_NEW
+- && fileSegment.isFull()
+- && !fileSegment.needCommit()) {
++ metadata.setSize(fileSegment.getCommitPosition());
++ metadata.setBeginTimestamp(fileSegment.getMinTimestamp());
++ metadata.setEndTimestamp(fileSegment.getMaxTimestamp());
+
+- segmentMetadata.markSealed();
++ if (fileSegment.isFull() && !fileSegment.needCommit()) {
++ if (metadata.getStatus() == FileSegmentMetadata.STATUS_NEW) {
++ metadata.markSealed();
++ }
+ }
+
+ if (fileSegment.isClosed()) {
+- segmentMetadata.setStatus(FileSegmentMetadata.STATUS_DELETED);
++ metadata.setStatus(FileSegmentMetadata.STATUS_DELETED);
+ }
+
+- segmentMetadata.setSize(fileSegment.getCommitPosition());
+- segmentMetadata.setEndTimestamp(fileSegment.getMaxTimestamp());
+-
+- FileSegmentMetadata metadata = tieredMetadataStore.getFileSegment(
+- this.filePath, fileSegment.getFileType(), fileSegment.getBaseOffset());
+-
+- if (!Objects.equals(metadata, segmentMetadata)) {
+- this.tieredMetadataStore.updateFileSegment(segmentMetadata);
+- logger.debug("TieredFlatFile#UpdateSegmentMetadata, filePath: {}, content: {}",
+- segmentMetadata.getPath(), JSON.toJSONString(segmentMetadata));
+- }
++ this.tieredMetadataStore.updateFileSegment(metadata);
+ }
+
+ private void checkAndFixFileSize() {
+@@ -598,6 +578,9 @@ public class TieredFlatFile {
+ logger.error("TieredFlatFile#destroy: mark file segment: {} is deleted failed", fileSegment.getPath(), e);
+ }
+ fileSegment.destroyFile();
++ if (!fileSegment.exists()) {
++ tieredMetadataStore.deleteFileSegment(filePath, fileType, fileSegment.getBaseOffset());
++ }
+ }
+ fileSegmentList.clear();
+ needCommitFileSegmentList.clear();
+diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredFlatFileManager.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredFlatFileManager.java
+index 7c744af3b..087ea8c9c 100644
+--- a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredFlatFileManager.java
++++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/file/TieredFlatFileManager.java
+@@ -136,15 +136,13 @@ public class TieredFlatFileManager {
+ TimeUnit.HOURS.toMillis(storeConfig.getTieredStoreFileReservedTime());
+ for (CompositeQueueFlatFile flatFile : deepCopyFlatFileToList()) {
+ TieredStoreExecutor.cleanExpiredFileExecutor.submit(() -> {
+- flatFile.getCompositeFlatFileLock().lock();
+ try {
++ flatFile.getCompositeFlatFileLock().lock();
+ flatFile.cleanExpiredFile(expiredTimeStamp);
+ flatFile.destroyExpiredFile();
+- if (flatFile.getConsumeQueueBaseOffset() == -1) {
+- logger.info("Clean flatFile because file not initialized, topic={}, queueId={}",
+- flatFile.getMessageQueue().getTopic(), flatFile.getMessageQueue().getQueueId());
+- destroyCompositeFile(flatFile.getMessageQueue());
+- }
++ } catch (Throwable t) {
++ logger.error("Do Clean expired file error, topic={}, queueId={}",
++ flatFile.getMessageQueue().getTopic(), flatFile.getMessageQueue().getQueueId(), t);
+ } finally {
+ flatFile.getCompositeFlatFileLock().unlock();
+ }
+--
+2.32.0.windows.2
+
+
+From 4a8e0d5b851d1f9573cda79b7d2e42ee498809da Mon Sep 17 00:00:00 2001
+From: guyinyou <36399867+guyinyou@users.noreply.github.com>
+Date: Wed, 13 Sep 2023 16:08:03 +0800
+Subject: [PATCH 3/5] [ISSUE #7351] Allow mqadmin to operate slave nodes
+
+Co-authored-by: guyinyou <guyinyou.gyy@alibaba-inc.com>
+---
+ .../processor/AdminBrokerProcessor.java | 12 --
+ .../processor/AdminBrokerProcessorTest.java | 106 ------------------
+ 2 files changed, 118 deletions(-)
+
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessor.java
+index 8fbcd3c94..9e48431be 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessor.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessor.java
+@@ -406,9 +406,6 @@ public class AdminBrokerProcessor implements NettyRequestProcessor {
+ private synchronized RemotingCommand updateAndCreateTopic(ChannelHandlerContext ctx,
+ RemotingCommand request) throws RemotingCommandException {
+ final RemotingCommand response = RemotingCommand.createResponseCommand(null);
+- if (validateSlave(response)) {
+- return response;
+- }
+ final CreateTopicRequestHeader requestHeader =
+ (CreateTopicRequestHeader) request.decodeCommandCustomHeader(CreateTopicRequestHeader.class);
+
+@@ -519,9 +516,6 @@ public class AdminBrokerProcessor implements NettyRequestProcessor {
+ private synchronized RemotingCommand deleteTopic(ChannelHandlerContext ctx,
+ RemotingCommand request) throws RemotingCommandException {
+ final RemotingCommand response = RemotingCommand.createResponseCommand(null);
+- if (validateSlave(response)) {
+- return response;
+- }
+ DeleteTopicRequestHeader requestHeader =
+ (DeleteTopicRequestHeader) request.decodeCommandCustomHeader(DeleteTopicRequestHeader.class);
+
+@@ -1413,9 +1407,6 @@ public class AdminBrokerProcessor implements NettyRequestProcessor {
+ private RemotingCommand updateAndCreateSubscriptionGroup(ChannelHandlerContext ctx, RemotingCommand request)
+ throws RemotingCommandException {
+ final RemotingCommand response = RemotingCommand.createResponseCommand(null);
+- if (validateSlave(response)) {
+- return response;
+- }
+
+ LOGGER.info("AdminBrokerProcessor#updateAndCreateSubscriptionGroup called by {}",
+ RemotingHelper.parseChannelRemoteAddr(ctx.channel()));
+@@ -1480,9 +1471,6 @@ public class AdminBrokerProcessor implements NettyRequestProcessor {
+ private RemotingCommand deleteSubscriptionGroup(ChannelHandlerContext ctx,
+ RemotingCommand request) throws RemotingCommandException {
+ final RemotingCommand response = RemotingCommand.createResponseCommand(null);
+- if (validateSlave(response)) {
+- return response;
+- }
+ DeleteSubscriptionGroupRequestHeader requestHeader =
+ (DeleteSubscriptionGroupRequestHeader) request.decodeCommandCustomHeader(DeleteSubscriptionGroupRequestHeader.class);
+
+diff --git a/broker/src/test/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessorTest.java b/broker/src/test/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessorTest.java
+index 9d17011b6..ec252cece 100644
+--- a/broker/src/test/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessorTest.java
++++ b/broker/src/test/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessorTest.java
+@@ -76,7 +76,6 @@ import org.apache.rocketmq.remoting.protocol.subscription.SubscriptionGroupConfi
+ import org.apache.rocketmq.store.DefaultMessageStore;
+ import org.apache.rocketmq.store.MessageStore;
+ import org.apache.rocketmq.store.SelectMappedBufferResult;
+-import org.apache.rocketmq.store.config.BrokerRole;
+ import org.apache.rocketmq.store.config.MessageStoreConfig;
+ import org.apache.rocketmq.store.logfile.DefaultMappedFile;
+ import org.apache.rocketmq.store.stats.BrokerStats;
+@@ -250,32 +249,6 @@ public class AdminBrokerProcessorTest {
+ assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS);
+ }
+
+- @Test
+- public void testUpdateAndCreateTopicOnSlaveInRocksdb() throws Exception {
+- if (notToBeExecuted()) {
+- return;
+- }
+- initRocksdbTopicManager();
+- testUpdateAndCreateTopicOnSlave();
+- }
+-
+- @Test
+- public void testUpdateAndCreateTopicOnSlave() throws Exception {
+- // setup
+- MessageStoreConfig messageStoreConfig = mock(MessageStoreConfig.class);
+- when(messageStoreConfig.getBrokerRole()).thenReturn(BrokerRole.SLAVE);
+- defaultMessageStore = mock(DefaultMessageStore.class);
+- when(brokerController.getMessageStoreConfig()).thenReturn(messageStoreConfig);
+-
+- // test on slave
+- String topic = "TEST_CREATE_TOPIC";
+- RemotingCommand request = buildCreateTopicRequest(topic);
+- RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request);
+- assertThat(response.getCode()).isEqualTo(ResponseCode.SYSTEM_ERROR);
+- assertThat(response.getRemark()).isEqualTo("Can't modify topic or subscription group from slave broker, " +
+- "please execute it from master broker.");
+- }
+-
+ @Test
+ public void testDeleteTopicInRocksdb() throws Exception {
+ if (notToBeExecuted()) {
+@@ -301,31 +274,6 @@ public class AdminBrokerProcessorTest {
+ assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS);
+ }
+
+- @Test
+- public void testDeleteTopicOnSlaveInRocksdb() throws Exception {
+- if (notToBeExecuted()) {
+- return;
+- }
+- initRocksdbTopicManager();
+- testDeleteTopicOnSlave();
+- }
+-
+- @Test
+- public void testDeleteTopicOnSlave() throws Exception {
+- // setup
+- MessageStoreConfig messageStoreConfig = mock(MessageStoreConfig.class);
+- when(messageStoreConfig.getBrokerRole()).thenReturn(BrokerRole.SLAVE);
+- defaultMessageStore = mock(DefaultMessageStore.class);
+- when(brokerController.getMessageStoreConfig()).thenReturn(messageStoreConfig);
+-
+- String topic = "TEST_DELETE_TOPIC";
+- RemotingCommand request = buildDeleteTopicRequest(topic);
+- RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request);
+- assertThat(response.getCode()).isEqualTo(ResponseCode.SYSTEM_ERROR);
+- assertThat(response.getRemark()).isEqualTo("Can't modify topic or subscription group from slave broker, " +
+- "please execute it from master broker.");
+- }
+-
+ @Test
+ public void testDeleteWithPopRetryTopic() throws Exception {
+ String topic = "topicA";
+@@ -538,36 +486,6 @@ public class AdminBrokerProcessorTest {
+ assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS);
+ }
+
+- @Test
+- public void testUpdateAndCreateSubscriptionGroupOnSlaveInRocksdb() throws Exception {
+- initRocksdbSubscriptionManager();
+- testUpdateAndCreateSubscriptionGroupOnSlave();
+- }
+-
+- @Test
+- public void testUpdateAndCreateSubscriptionGroupOnSlave() throws RemotingCommandException {
+- // Setup
+- MessageStoreConfig messageStoreConfig = mock(MessageStoreConfig.class);
+- when(messageStoreConfig.getBrokerRole()).thenReturn(BrokerRole.SLAVE);
+- defaultMessageStore = mock(DefaultMessageStore.class);
+- when(brokerController.getMessageStoreConfig()).thenReturn(messageStoreConfig);
+-
+- // Test
+- RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.UPDATE_AND_CREATE_SUBSCRIPTIONGROUP, null);
+- SubscriptionGroupConfig subscriptionGroupConfig = new SubscriptionGroupConfig();
+- subscriptionGroupConfig.setBrokerId(1);
+- subscriptionGroupConfig.setGroupName("groupId");
+- subscriptionGroupConfig.setConsumeEnable(Boolean.TRUE);
+- subscriptionGroupConfig.setConsumeBroadcastEnable(Boolean.TRUE);
+- subscriptionGroupConfig.setRetryMaxTimes(111);
+- subscriptionGroupConfig.setConsumeFromMinEnable(Boolean.TRUE);
+- request.setBody(JSON.toJSON(subscriptionGroupConfig).toString().getBytes());
+- RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request);
+- assertThat(response.getCode()).isEqualTo(ResponseCode.SYSTEM_ERROR);
+- assertThat(response.getRemark()).isEqualTo("Can't modify topic or subscription group from slave broker, " +
+- "please execute it from master broker.");
+- }
+-
+ @Test
+ public void testGetAllSubscriptionGroupInRocksdb() throws Exception {
+ initRocksdbSubscriptionManager();
+@@ -596,30 +514,6 @@ public class AdminBrokerProcessorTest {
+ assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS);
+ }
+
+- @Test
+- public void testDeleteSubscriptionGroupOnSlaveInRocksdb() throws Exception {
+- initRocksdbSubscriptionManager();
+- testDeleteSubscriptionGroupOnSlave();
+- }
+-
+- @Test
+- public void testDeleteSubscriptionGroupOnSlave() throws RemotingCommandException {
+- // Setup
+- MessageStoreConfig messageStoreConfig = mock(MessageStoreConfig.class);
+- when(messageStoreConfig.getBrokerRole()).thenReturn(BrokerRole.SLAVE);
+- defaultMessageStore = mock(DefaultMessageStore.class);
+- when(brokerController.getMessageStoreConfig()).thenReturn(messageStoreConfig);
+-
+- // Test
+- RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.DELETE_SUBSCRIPTIONGROUP, null);
+- request.addExtField("groupName", "GID-Group-Name");
+- request.addExtField("removeOffset", "true");
+- RemotingCommand response = adminBrokerProcessor.processRequest(handlerContext, request);
+- assertThat(response.getCode()).isEqualTo(ResponseCode.SYSTEM_ERROR);
+- assertThat(response.getRemark()).isEqualTo("Can't modify topic or subscription group from slave broker, " +
+- "please execute it from master broker.");
+- }
+-
+ @Test
+ public void testGetTopicStatsInfo() throws RemotingCommandException {
+ RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_TOPIC_STATS_INFO, null);
+--
+2.32.0.windows.2
+
+
+From 831fcc76cd7cd362bb6c136c287c624bb7eaf40a Mon Sep 17 00:00:00 2001
+From: lizhimins <707364882@qq.com>
+Date: Tue, 19 Sep 2023 10:04:04 +0800
+Subject: [PATCH 4/5] [ISSUE #7363] Fix get message from tiered storage return
+ incorrect next pull offset (#7365)
+
+---
+ .../tieredstore/TieredMessageFetcher.java | 2 +-
+ .../tieredstore/TieredMessageStore.java | 29 ++++++++++---------
+ .../tieredstore/TieredMessageStoreTest.java | 5 ++--
+ 3 files changed, 20 insertions(+), 16 deletions(-)
+
+diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredMessageFetcher.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredMessageFetcher.java
+index 766ff64f6..c948fa3fa 100644
+--- a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredMessageFetcher.java
++++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredMessageFetcher.java
+@@ -319,7 +319,7 @@ public class TieredMessageFetcher implements MessageStoreFetcher {
+ }
+
+ // if cache is miss, immediately pull messages
+- LOGGER.warn("TieredMessageFetcher#getMessageFromCacheAsync: cache miss: " +
++ LOGGER.info("TieredMessageFetcher#getMessageFromCacheAsync: cache miss: " +
+ "topic: {}, queue: {}, queue offset: {}, max message num: {}",
+ mq.getTopic(), mq.getQueueId(), queueOffset, maxCount);
+
+diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredMessageStore.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredMessageStore.java
+index 9fb1b2f01..d7d13d61e 100644
+--- a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredMessageStore.java
++++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredMessageStore.java
+@@ -147,6 +147,11 @@ public class TieredMessageStore extends AbstractPluginMessageStore {
+ public CompletableFuture<GetMessageResult> getMessageAsync(String group, String topic,
+ int queueId, long offset, int maxMsgNums, MessageFilter messageFilter) {
+
++ // For system topic, force reading from local store
++ if (TieredStoreUtil.isSystemTopic(topic) || PopAckConstants.isStartWithRevivePrefix(topic)) {
++ return next.getMessageAsync(group, topic, queueId, offset, maxMsgNums, messageFilter);
++ }
++
+ if (fetchFromCurrentStore(topic, queueId, offset, maxMsgNums)) {
+ logger.trace("GetMessageAsync from current store, topic: {}, queue: {}, offset: {}", topic, queueId, offset);
+ } else {
+@@ -158,6 +163,7 @@ public class TieredMessageStore extends AbstractPluginMessageStore {
+ return fetcher
+ .getMessageAsync(group, topic, queueId, offset, maxMsgNums, messageFilter)
+ .thenApply(result -> {
++
+ Attributes latencyAttributes = TieredStoreMetricsManager.newAttributesBuilder()
+ .put(TieredStoreMetricsConstant.LABEL_OPERATION, TieredStoreMetricsConstant.OPERATION_API_GET_MESSAGE)
+ .put(TieredStoreMetricsConstant.LABEL_TOPIC, topic)
+@@ -166,8 +172,7 @@ public class TieredMessageStore extends AbstractPluginMessageStore {
+ TieredStoreMetricsManager.apiLatency.record(stopwatch.elapsed(TimeUnit.MILLISECONDS), latencyAttributes);
+
+ if (result.getStatus() == GetMessageStatus.OFFSET_FOUND_NULL ||
+- result.getStatus() == GetMessageStatus.OFFSET_OVERFLOW_ONE ||
+- result.getStatus() == GetMessageStatus.OFFSET_OVERFLOW_BADLY) {
++ result.getStatus() == GetMessageStatus.NO_MATCHED_LOGIC_QUEUE) {
+
+ if (next.checkInStoreByConsumeOffset(topic, queueId, offset)) {
+ TieredStoreMetricsManager.fallbackTotal.add(1, latencyAttributes);
+@@ -178,14 +183,8 @@ public class TieredMessageStore extends AbstractPluginMessageStore {
+ }
+ }
+
+- // Fetch system topic data from the broker when using the force level.
+- if (result.getStatus() == GetMessageStatus.NO_MATCHED_LOGIC_QUEUE) {
+- if (TieredStoreUtil.isSystemTopic(topic) || PopAckConstants.isStartWithRevivePrefix(topic)) {
+- return next.getMessage(group, topic, queueId, offset, maxMsgNums, messageFilter);
+- }
+- }
+-
+ if (result.getStatus() != GetMessageStatus.FOUND &&
++ result.getStatus() != GetMessageStatus.NO_MATCHED_LOGIC_QUEUE &&
+ result.getStatus() != GetMessageStatus.OFFSET_OVERFLOW_ONE &&
+ result.getStatus() != GetMessageStatus.OFFSET_OVERFLOW_BADLY) {
+ logger.warn("GetMessageAsync not found and message is not in next store, result: {}, " +
+@@ -206,10 +205,14 @@ public class TieredMessageStore extends AbstractPluginMessageStore {
+ if (minOffsetInQueue >= 0 && minOffsetInQueue < result.getMinOffset()) {
+ result.setMinOffset(minOffsetInQueue);
+ }
+- long maxOffsetInQueue = next.getMaxOffsetInQueue(topic, queueId);
+- if (maxOffsetInQueue >= 0 && maxOffsetInQueue > result.getMaxOffset()) {
+- result.setMaxOffset(maxOffsetInQueue);
+- }
++
++ // In general, the local cq offset is slightly greater than the commit offset in read message,
++ // so there is no need to update the maximum offset to the local cq offset here,
++ // otherwise it will cause repeated consumption after next begin offset over commit offset.
++
++ logger.trace("GetMessageAsync result, group: {}, topic: {}, queueId: {}, offset: {}, count:{}, {}",
++ group, topic, queueId, offset, maxMsgNums, result);
++
+ return result;
+ }).exceptionally(e -> {
+ logger.error("GetMessageAsync from tiered store failed", e);
+diff --git a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/TieredMessageStoreTest.java b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/TieredMessageStoreTest.java
+index 2451199c2..07af1fc8b 100644
+--- a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/TieredMessageStoreTest.java
++++ b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/TieredMessageStoreTest.java
+@@ -168,7 +168,7 @@ public class TieredMessageStoreTest {
+ GetMessageResult result1 = new GetMessageResult();
+ result1.setStatus(GetMessageStatus.FOUND);
+ GetMessageResult result2 = new GetMessageResult();
+- result2.setStatus(GetMessageStatus.MESSAGE_WAS_REMOVING);
++ result2.setStatus(GetMessageStatus.OFFSET_OVERFLOW_BADLY);
+
+ when(fetcher.getMessageAsync(anyString(), anyString(), anyInt(), anyLong(), anyInt(), any())).thenReturn(CompletableFuture.completedFuture(result1));
+ when(nextStore.getMessage(anyString(), anyString(), anyInt(), anyLong(), anyInt(), any())).thenReturn(result2);
+@@ -188,7 +188,8 @@ public class TieredMessageStoreTest {
+ properties.setProperty("tieredStorageLevel", "3");
+ configuration.update(properties);
+ when(nextStore.checkInStoreByConsumeOffset(anyString(), anyInt(), anyLong())).thenReturn(true);
+- Assert.assertSame(result2, store.getMessage("group", mq.getTopic(), mq.getQueueId(), 0, 0, null));
++ Assert.assertEquals(result2.getStatus(),
++ store.getMessage("group", mq.getTopic(), mq.getQueueId(), 0, 0, null).getStatus());
+ }
+
+ @Test
+--
+2.32.0.windows.2
+
+
+From f05a8da760dfade411ad56ef874f477988479cf9 Mon Sep 17 00:00:00 2001
+From: rongtong <jinrongtong5@163.com>
+Date: Wed, 20 Sep 2023 15:06:21 +0800
+Subject: [PATCH 5/5] Print admin queue watermark in log (#7372)
+
+---
+ .../main/java/org/apache/rocketmq/broker/BrokerController.java | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java
+index 13a3feb4e..53e2e1b62 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java
+@@ -1182,6 +1182,7 @@ public class BrokerController {
+ LOG_WATER_MARK.info("[WATERMARK] ClientManager Queue Size: {} SlowTimeMills: {}", this.clientManagerThreadPoolQueue.size(), this.headSlowTimeMills(this.clientManagerThreadPoolQueue));
+ LOG_WATER_MARK.info("[WATERMARK] Heartbeat Queue Size: {} SlowTimeMills: {}", this.heartbeatThreadPoolQueue.size(), this.headSlowTimeMills(this.heartbeatThreadPoolQueue));
+ LOG_WATER_MARK.info("[WATERMARK] Ack Queue Size: {} SlowTimeMills: {}", this.ackThreadPoolQueue.size(), headSlowTimeMills(this.ackThreadPoolQueue));
++ LOG_WATER_MARK.info("[WATERMARK] Admin Queue Size: {} SlowTimeMills: {}", this.adminBrokerThreadPoolQueue.size(), headSlowTimeMills(this.adminBrokerThreadPoolQueue));
+ }
+
+ public MessageStore getMessageStore() {
+--
+2.32.0.windows.2
+
diff --git a/patch019-backport-some-bugfix.patch b/patch019-backport-some-bugfix.patch
new file mode 100644
index 000000000..d85f01c98
--- /dev/null
+++ b/patch019-backport-some-bugfix.patch
@@ -0,0 +1,1499 @@
+From 42fcd278ca84f6988d48a7d11271fc81b921d59a Mon Sep 17 00:00:00 2001
+From: lk <xdkxlk@outlook.com>
+Date: Wed, 20 Sep 2023 15:41:23 +0800
+Subject: [PATCH 01/12] [ISSUE #7374] Prepare to release Apache RocketMQ 5.1.4
+ (#7375)
+
+---
+ common/src/main/java/org/apache/rocketmq/common/MQVersion.java | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/common/src/main/java/org/apache/rocketmq/common/MQVersion.java b/common/src/main/java/org/apache/rocketmq/common/MQVersion.java
+index bfd07a895..4f1990ff8 100644
+--- a/common/src/main/java/org/apache/rocketmq/common/MQVersion.java
++++ b/common/src/main/java/org/apache/rocketmq/common/MQVersion.java
+@@ -18,7 +18,7 @@ package org.apache.rocketmq.common;
+
+ public class MQVersion {
+
+- public static final int CURRENT_VERSION = Version.V5_1_3.ordinal();
++ public static final int CURRENT_VERSION = Version.V5_1_4.ordinal();
+
+ public static String getVersionDesc(int value) {
+ int length = Version.values().length;
+--
+2.32.0.windows.2
+
+
+From b8610d87bb55de1f4413460c05da529dab60c1c1 Mon Sep 17 00:00:00 2001
+From: Jixiang Jin <lollipop@apache.org>
+Date: Thu, 21 Sep 2023 16:21:44 +0800
+Subject: [PATCH 02/12] Replace loggingMetricExporter with
+ OtlpJsonLoggingMetricExporter. (#7373)
+
+* Replace loggingMetricExporter with OtlpJsonLoggingMetricExporter.
+
+* Fix bazel workspace
+
+* Move OtlpJsonLoggingMetricExporter to proxy and controller.
+
+* Fix Bazel imports.
+---
+ WORKSPACE | 1 +
+ broker/BUILD.bazel | 1 +
+ .../rocketmq/broker/metrics/BrokerMetricsManager.java | 9 +++++----
+ broker/src/main/resources/rmq.broker.logback.xml | 5 +++++
+ common/BUILD.bazel | 1 +
+ common/pom.xml | 4 ++++
+ controller/BUILD.bazel | 1 +
+ .../controller/metrics/ControllerMetricsManager.java | 9 +++++----
+ pom.xml | 5 +++++
+ proxy/BUILD.bazel | 1 +
+ .../rocketmq/proxy/metrics/ProxyMetricsManager.java | 11 ++++++-----
+ proxy/src/main/resources/rmq.proxy.logback.xml | 5 +++++
+ tieredstore/BUILD.bazel | 1 +
+ 13 files changed, 41 insertions(+), 13 deletions(-)
+
+diff --git a/WORKSPACE b/WORKSPACE
+index 3126f2d1d..8640485ba 100644
+--- a/WORKSPACE
++++ b/WORKSPACE
+@@ -92,6 +92,7 @@ maven_install(
+ "io.opentelemetry:opentelemetry-exporter-prometheus:1.29.0-alpha",
+ "io.opentelemetry:opentelemetry-exporter-logging:1.29.0",
+ "io.opentelemetry:opentelemetry-sdk:1.29.0",
++ "io.opentelemetry:opentelemetry-exporter-logging-otlp:1.29.0",
+ "com.squareup.okio:okio-jvm:3.0.0",
+ "io.opentelemetry:opentelemetry-api:1.29.0",
+ "io.opentelemetry:opentelemetry-sdk-metrics:1.29.0",
+diff --git a/broker/BUILD.bazel b/broker/BUILD.bazel
+index 6adcdc7b9..64cb1b341 100644
+--- a/broker/BUILD.bazel
++++ b/broker/BUILD.bazel
+@@ -44,6 +44,7 @@ java_library(
+ "@maven//:io_opentelemetry_opentelemetry_exporter_otlp",
+ "@maven//:io_opentelemetry_opentelemetry_exporter_prometheus",
+ "@maven//:io_opentelemetry_opentelemetry_exporter_logging",
++ "@maven//:io_opentelemetry_opentelemetry_exporter_logging_otlp",
+ "@maven//:io_opentelemetry_opentelemetry_sdk",
+ "@maven//:io_opentelemetry_opentelemetry_sdk_common",
+ "@maven//:io_opentelemetry_opentelemetry_sdk_metrics",
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/metrics/BrokerMetricsManager.java b/broker/src/main/java/org/apache/rocketmq/broker/metrics/BrokerMetricsManager.java
+index 6af5afc14..39af18b9f 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/metrics/BrokerMetricsManager.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/metrics/BrokerMetricsManager.java
+@@ -23,7 +23,7 @@ import io.opentelemetry.api.metrics.LongCounter;
+ import io.opentelemetry.api.metrics.LongHistogram;
+ import io.opentelemetry.api.metrics.Meter;
+ import io.opentelemetry.api.metrics.ObservableLongGauge;
+-import io.opentelemetry.exporter.logging.LoggingMetricExporter;
++import io.opentelemetry.exporter.logging.otlp.OtlpJsonLoggingMetricExporter;
+ import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter;
+ import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporterBuilder;
+ import io.opentelemetry.exporter.prometheus.PrometheusHttpServer;
+@@ -36,6 +36,7 @@ import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder;
+ import io.opentelemetry.sdk.metrics.View;
+ import io.opentelemetry.sdk.metrics.ViewBuilder;
+ import io.opentelemetry.sdk.metrics.data.AggregationTemporality;
++import io.opentelemetry.sdk.metrics.export.MetricExporter;
+ import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader;
+ import io.opentelemetry.sdk.metrics.internal.SdkMeterProviderUtil;
+ import io.opentelemetry.sdk.resources.Resource;
+@@ -113,7 +114,7 @@ public class BrokerMetricsManager {
+ private OtlpGrpcMetricExporter metricExporter;
+ private PeriodicMetricReader periodicMetricReader;
+ private PrometheusHttpServer prometheusHttpServer;
+- private LoggingMetricExporter loggingMetricExporter;
++ private MetricExporter loggingMetricExporter;
+ private Meter brokerMeter;
+
+ public static Supplier<AttributesBuilder> attributesBuilderSupplier = Attributes::builder;
+@@ -327,8 +328,8 @@ public class BrokerMetricsManager {
+ if (metricsExporterType == MetricsExporterType.LOG) {
+ SLF4JBridgeHandler.removeHandlersForRootLogger();
+ SLF4JBridgeHandler.install();
+- loggingMetricExporter = LoggingMetricExporter.create(brokerConfig.isMetricsInDelta() ? AggregationTemporality.DELTA : AggregationTemporality.CUMULATIVE);
+- java.util.logging.Logger.getLogger(LoggingMetricExporter.class.getName()).setLevel(java.util.logging.Level.FINEST);
++ loggingMetricExporter = OtlpJsonLoggingMetricExporter.create(brokerConfig.isMetricsInDelta() ? AggregationTemporality.DELTA : AggregationTemporality.CUMULATIVE);
++ java.util.logging.Logger.getLogger(OtlpJsonLoggingMetricExporter.class.getName()).setLevel(java.util.logging.Level.FINEST);
+ periodicMetricReader = PeriodicMetricReader.builder(loggingMetricExporter)
+ .setInterval(brokerConfig.getMetricLoggingExporterIntervalInMills(), TimeUnit.MILLISECONDS)
+ .build();
+diff --git a/broker/src/main/resources/rmq.broker.logback.xml b/broker/src/main/resources/rmq.broker.logback.xml
+index 3c51e59d4..32dc29736 100644
+--- a/broker/src/main/resources/rmq.broker.logback.xml
++++ b/broker/src/main/resources/rmq.broker.logback.xml
+@@ -672,6 +672,11 @@
+ <appender-ref ref="RocketmqTrafficSiftingAppender"/>
+ </logger>
+
++ <!-- Use json formatter to log metrics -->
++ <logger name="io.opentelemetry.exporter.logging.otlp.OtlpJsonLoggingMetricExporter" additivity="false" level="INFO">
++ <appender-ref ref="RocketmqBrokerMetricsSiftingAppender"/>
++ </logger>
++
+ <logger name="io.opentelemetry.exporter.logging.LoggingMetricExporter" additivity="false" level="INFO">
+ <appender-ref ref="RocketmqBrokerMetricsSiftingAppender"/>
+ </logger>
+diff --git a/common/BUILD.bazel b/common/BUILD.bazel
+index a95a19ccd..e6701d0fc 100644
+--- a/common/BUILD.bazel
++++ b/common/BUILD.bazel
+@@ -35,6 +35,7 @@ java_library(
+ "@maven//:io_opentelemetry_opentelemetry_sdk",
+ "@maven//:io_opentelemetry_opentelemetry_sdk_common",
+ "@maven//:io_opentelemetry_opentelemetry_sdk_metrics",
++ "@maven//:io_opentelemetry_opentelemetry_exporter_logging_otlp",
+ "@maven//:org_apache_commons_commons_lang3",
+ "@maven//:org_lz4_lz4_java",
+ "@maven//:io_github_aliyunmq_rocketmq_slf4j_api",
+diff --git a/common/pom.xml b/common/pom.xml
+index 31eb0f087..accc7f0a8 100644
+--- a/common/pom.xml
++++ b/common/pom.xml
+@@ -80,6 +80,10 @@
+ <groupId>io.opentelemetry</groupId>
+ <artifactId>opentelemetry-sdk</artifactId>
+ </dependency>
++ <dependency>
++ <groupId>io.opentelemetry</groupId>
++ <artifactId>opentelemetry-exporter-logging-otlp</artifactId>
++ </dependency>
+ <dependency>
+ <groupId>io.grpc</groupId>
+ <artifactId>grpc-stub</artifactId>
+diff --git a/controller/BUILD.bazel b/controller/BUILD.bazel
+index 843d9dc77..b2b743eb2 100644
+--- a/controller/BUILD.bazel
++++ b/controller/BUILD.bazel
+@@ -49,6 +49,7 @@ java_library(
+ "@maven//:io_opentelemetry_opentelemetry_sdk_common",
+ "@maven//:io_opentelemetry_opentelemetry_sdk_metrics",
+ "@maven//:io_opentelemetry_opentelemetry_exporter_logging",
++ "@maven//:io_opentelemetry_opentelemetry_exporter_logging_otlp",
+ "@maven//:org_slf4j_jul_to_slf4j",
+ ],
+ )
+diff --git a/controller/src/main/java/org/apache/rocketmq/controller/metrics/ControllerMetricsManager.java b/controller/src/main/java/org/apache/rocketmq/controller/metrics/ControllerMetricsManager.java
+index 650740bcc..be9e77eea 100644
+--- a/controller/src/main/java/org/apache/rocketmq/controller/metrics/ControllerMetricsManager.java
++++ b/controller/src/main/java/org/apache/rocketmq/controller/metrics/ControllerMetricsManager.java
+@@ -26,7 +26,7 @@ import io.opentelemetry.api.metrics.LongHistogram;
+ import io.opentelemetry.api.metrics.LongUpDownCounter;
+ import io.opentelemetry.api.metrics.Meter;
+ import io.opentelemetry.api.metrics.ObservableLongGauge;
+-import io.opentelemetry.exporter.logging.LoggingMetricExporter;
++import io.opentelemetry.exporter.logging.otlp.OtlpJsonLoggingMetricExporter;
+ import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter;
+ import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporterBuilder;
+ import io.opentelemetry.exporter.prometheus.PrometheusHttpServer;
+@@ -38,6 +38,7 @@ import io.opentelemetry.sdk.metrics.SdkMeterProvider;
+ import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder;
+ import io.opentelemetry.sdk.metrics.View;
+ import io.opentelemetry.sdk.metrics.data.AggregationTemporality;
++import io.opentelemetry.sdk.metrics.export.MetricExporter;
+ import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader;
+ import io.opentelemetry.sdk.resources.Resource;
+ import java.io.File;
+@@ -121,7 +122,7 @@ public class ControllerMetricsManager {
+
+ private PrometheusHttpServer prometheusHttpServer;
+
+- private LoggingMetricExporter loggingMetricExporter;
++ private MetricExporter loggingMetricExporter;
+
+ public static ControllerMetricsManager getInstance(ControllerManager controllerManager) {
+ if (instance == null) {
+@@ -364,8 +365,8 @@ public class ControllerMetricsManager {
+ if (type == MetricsExporterType.LOG) {
+ SLF4JBridgeHandler.removeHandlersForRootLogger();
+ SLF4JBridgeHandler.install();
+- loggingMetricExporter = LoggingMetricExporter.create(config.isMetricsInDelta() ? AggregationTemporality.DELTA : AggregationTemporality.CUMULATIVE);
+- java.util.logging.Logger.getLogger(LoggingMetricExporter.class.getName()).setLevel(java.util.logging.Level.FINEST);
++ loggingMetricExporter = OtlpJsonLoggingMetricExporter.create(config.isMetricsInDelta() ? AggregationTemporality.DELTA : AggregationTemporality.CUMULATIVE);
++ java.util.logging.Logger.getLogger(OtlpJsonLoggingMetricExporter.class.getName()).setLevel(java.util.logging.Level.FINEST);
+ periodicMetricReader = PeriodicMetricReader.builder(loggingMetricExporter)
+ .setInterval(config.getMetricLoggingExporterIntervalInMills(), TimeUnit.MILLISECONDS)
+ .build();
+diff --git a/pom.xml b/pom.xml
+index 9f0b3eb96..4b382c6da 100644
+--- a/pom.xml
++++ b/pom.xml
+@@ -974,6 +974,11 @@
+ <artifactId>opentelemetry-sdk</artifactId>
+ <version>${opentelemetry.version}</version>
+ </dependency>
++ <dependency>
++ <groupId>io.opentelemetry</groupId>
++ <artifactId>opentelemetry-exporter-logging-otlp</artifactId>
++ <version>${opentelemetry.version}</version>
++ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>jul-to-slf4j</artifactId>
+diff --git a/proxy/BUILD.bazel b/proxy/BUILD.bazel
+index b4f3c16e2..cb7af9254 100644
+--- a/proxy/BUILD.bazel
++++ b/proxy/BUILD.bazel
+@@ -52,6 +52,7 @@ java_library(
+ "@maven//:io_opentelemetry_opentelemetry_exporter_otlp",
+ "@maven//:io_opentelemetry_opentelemetry_exporter_prometheus",
+ "@maven//:io_opentelemetry_opentelemetry_exporter_logging",
++ "@maven//:io_opentelemetry_opentelemetry_exporter_logging_otlp",
+ "@maven//:io_opentelemetry_opentelemetry_sdk",
+ "@maven//:io_opentelemetry_opentelemetry_sdk_common",
+ "@maven//:io_opentelemetry_opentelemetry_sdk_metrics",
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/metrics/ProxyMetricsManager.java b/proxy/src/main/java/org/apache/rocketmq/proxy/metrics/ProxyMetricsManager.java
+index f5050858f..2b8dac5d8 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/metrics/ProxyMetricsManager.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/metrics/ProxyMetricsManager.java
+@@ -21,15 +21,16 @@ import io.opentelemetry.api.common.Attributes;
+ import io.opentelemetry.api.common.AttributesBuilder;
+ import io.opentelemetry.api.metrics.Meter;
+ import io.opentelemetry.api.metrics.ObservableLongGauge;
++import io.opentelemetry.exporter.logging.otlp.OtlpJsonLoggingMetricExporter;
+ import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter;
+ import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporterBuilder;
+ import io.opentelemetry.exporter.prometheus.PrometheusHttpServer;
+-import io.opentelemetry.exporter.logging.LoggingMetricExporter;
+ import io.opentelemetry.sdk.OpenTelemetrySdk;
+ import io.opentelemetry.sdk.metrics.InstrumentType;
+ import io.opentelemetry.sdk.metrics.SdkMeterProvider;
+ import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder;
+ import io.opentelemetry.sdk.metrics.data.AggregationTemporality;
++import io.opentelemetry.sdk.metrics.export.MetricExporter;
+ import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader;
+ import io.opentelemetry.sdk.resources.Resource;
+ import java.util.HashMap;
+@@ -42,9 +43,9 @@ import org.apache.rocketmq.broker.metrics.BrokerMetricsManager;
+ import org.apache.rocketmq.common.constant.LoggerName;
+ import org.apache.rocketmq.common.metrics.MetricsExporterType;
+ import org.apache.rocketmq.common.utils.StartAndShutdown;
+-import org.apache.rocketmq.proxy.config.ProxyConfig;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
++import org.apache.rocketmq.proxy.config.ProxyConfig;
+ import org.slf4j.bridge.SLF4JBridgeHandler;
+
+ import static org.apache.rocketmq.broker.metrics.BrokerMetricsConstant.AGGREGATION_DELTA;
+@@ -67,7 +68,7 @@ public class ProxyMetricsManager implements StartAndShutdown {
+ private OtlpGrpcMetricExporter metricExporter;
+ private PeriodicMetricReader periodicMetricReader;
+ private PrometheusHttpServer prometheusHttpServer;
+- private LoggingMetricExporter loggingMetricExporter;
++ private MetricExporter loggingMetricExporter;
+
+ public static ObservableLongGauge proxyUp = null;
+
+@@ -221,8 +222,8 @@ public class ProxyMetricsManager implements StartAndShutdown {
+ if (metricsExporterType == MetricsExporterType.LOG) {
+ SLF4JBridgeHandler.removeHandlersForRootLogger();
+ SLF4JBridgeHandler.install();
+- loggingMetricExporter = LoggingMetricExporter.create(proxyConfig.isMetricsInDelta() ? AggregationTemporality.DELTA : AggregationTemporality.CUMULATIVE);
+- java.util.logging.Logger.getLogger(LoggingMetricExporter.class.getName()).setLevel(java.util.logging.Level.FINEST);
++ loggingMetricExporter = OtlpJsonLoggingMetricExporter.create(proxyConfig.isMetricsInDelta() ? AggregationTemporality.DELTA : AggregationTemporality.CUMULATIVE);
++ java.util.logging.Logger.getLogger(OtlpJsonLoggingMetricExporter.class.getName()).setLevel(java.util.logging.Level.FINEST);
+ periodicMetricReader = PeriodicMetricReader.builder(loggingMetricExporter)
+ .setInterval(proxyConfig.getMetricLoggingExporterIntervalInMills(), TimeUnit.MILLISECONDS)
+ .build();
+diff --git a/proxy/src/main/resources/rmq.proxy.logback.xml b/proxy/src/main/resources/rmq.proxy.logback.xml
+index d38827f92..f968a45e6 100644
+--- a/proxy/src/main/resources/rmq.proxy.logback.xml
++++ b/proxy/src/main/resources/rmq.proxy.logback.xml
+@@ -418,6 +418,11 @@
+ <appender-ref ref="RocketmqProxyWatermarkAppender" />
+ </logger>
+
++ <!-- Use json formatter to log metrics -->
++ <logger name="io.opentelemetry.exporter.logging.otlp.OtlpJsonLoggingMetricExporter" additivity="false" level="INFO">
++ <appender-ref ref="RocketmqProxyMetricsAppender"/>
++ </logger>
++
+ <logger name="io.opentelemetry.exporter.logging.LoggingMetricExporter" additivity="false" level="INFO">
+ <appender-ref ref="RocketmqProxyMetricsAppender" />
+ </logger>
+diff --git a/tieredstore/BUILD.bazel b/tieredstore/BUILD.bazel
+index 5b3885a4e..dea2c561b 100644
+--- a/tieredstore/BUILD.bazel
++++ b/tieredstore/BUILD.bazel
+@@ -36,6 +36,7 @@ java_library(
+ "@maven//:io_opentelemetry_opentelemetry_sdk",
+ "@maven//:io_opentelemetry_opentelemetry_sdk_common",
+ "@maven//:io_opentelemetry_opentelemetry_sdk_metrics",
++ "@maven//:io_opentelemetry_opentelemetry_exporter_logging_otlp",
+ "@maven//:org_apache_commons_commons_lang3",
+ "@maven//:org_apache_tomcat_annotations_api",
+ "@maven//:com_alibaba_fastjson",
+--
+2.32.0.windows.2
+
+
+From 1a681bdf9b5c5ab0be446d6394c0cac8768f45d9 Mon Sep 17 00:00:00 2001
+From: lk <xdkxlk@outlook.com>
+Date: Thu, 21 Sep 2023 19:58:29 +0800
+Subject: [PATCH 03/12] [maven-release-plugin] prepare release
+ rocketmq-all-5.1.4 (#7377)
+
+---
+ acl/pom.xml | 2 +-
+ broker/pom.xml | 2 +-
+ client/pom.xml | 2 +-
+ common/pom.xml | 2 +-
+ container/pom.xml | 2 +-
+ controller/pom.xml | 2 +-
+ distribution/pom.xml | 2 +-
+ example/pom.xml | 2 +-
+ filter/pom.xml | 2 +-
+ namesrv/pom.xml | 2 +-
+ openmessaging/pom.xml | 2 +-
+ pom.xml | 4 ++--
+ proxy/pom.xml | 2 +-
+ remoting/pom.xml | 2 +-
+ srvutil/pom.xml | 2 +-
+ store/pom.xml | 2 +-
+ test/pom.xml | 2 +-
+ tieredstore/pom.xml | 2 +-
+ tools/pom.xml | 2 +-
+ 19 files changed, 20 insertions(+), 20 deletions(-)
+
+diff --git a/acl/pom.xml b/acl/pom.xml
+index 989c0cf77..9f6838b00 100644
+--- a/acl/pom.xml
++++ b/acl/pom.xml
+@@ -13,7 +13,7 @@
+ <parent>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-all</artifactId>
+- <version>5.1.4-SNAPSHOT</version>
++ <version>5.1.4</version>
+ </parent>
+ <artifactId>rocketmq-acl</artifactId>
+ <name>rocketmq-acl ${project.version}</name>
+diff --git a/broker/pom.xml b/broker/pom.xml
+index 16e026276..d483e67ba 100644
+--- a/broker/pom.xml
++++ b/broker/pom.xml
+@@ -13,7 +13,7 @@
+ <parent>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-all</artifactId>
+- <version>5.1.4-SNAPSHOT</version>
++ <version>5.1.4</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+diff --git a/client/pom.xml b/client/pom.xml
+index c59a43889..4febedc6d 100644
+--- a/client/pom.xml
++++ b/client/pom.xml
+@@ -19,7 +19,7 @@
+ <parent>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-all</artifactId>
+- <version>5.1.4-SNAPSHOT</version>
++ <version>5.1.4</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+diff --git a/common/pom.xml b/common/pom.xml
+index accc7f0a8..b70873dfa 100644
+--- a/common/pom.xml
++++ b/common/pom.xml
+@@ -19,7 +19,7 @@
+ <parent>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-all</artifactId>
+- <version>5.1.4-SNAPSHOT</version>
++ <version>5.1.4</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+diff --git a/container/pom.xml b/container/pom.xml
+index c8499f127..e6c1f4b4d 100644
+--- a/container/pom.xml
++++ b/container/pom.xml
+@@ -18,7 +18,7 @@
+ <parent>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-all</artifactId>
+- <version>5.1.4-SNAPSHOT</version>
++ <version>5.1.4</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+diff --git a/controller/pom.xml b/controller/pom.xml
+index 3346c7c82..46a3834c6 100644
+--- a/controller/pom.xml
++++ b/controller/pom.xml
+@@ -19,7 +19,7 @@
+ <parent>
+ <artifactId>rocketmq-all</artifactId>
+ <groupId>org.apache.rocketmq</groupId>
+- <version>5.1.4-SNAPSHOT</version>
++ <version>5.1.4</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <packaging>jar</packaging>
+diff --git a/distribution/pom.xml b/distribution/pom.xml
+index dbde2d9d4..346c4de35 100644
+--- a/distribution/pom.xml
++++ b/distribution/pom.xml
+@@ -20,7 +20,7 @@
+ <parent>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-all</artifactId>
+- <version>5.1.4-SNAPSHOT</version>
++ <version>5.1.4</version>
+ </parent>
+ <artifactId>rocketmq-distribution</artifactId>
+ <name>rocketmq-distribution ${project.version}</name>
+diff --git a/example/pom.xml b/example/pom.xml
+index 862fc3169..9e7db43f8 100644
+--- a/example/pom.xml
++++ b/example/pom.xml
+@@ -19,7 +19,7 @@
+ <parent>
+ <artifactId>rocketmq-all</artifactId>
+ <groupId>org.apache.rocketmq</groupId>
+- <version>5.1.4-SNAPSHOT</version>
++ <version>5.1.4</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+diff --git a/filter/pom.xml b/filter/pom.xml
+index 3fe51ceae..84189066d 100644
+--- a/filter/pom.xml
++++ b/filter/pom.xml
+@@ -20,7 +20,7 @@
+ <parent>
+ <artifactId>rocketmq-all</artifactId>
+ <groupId>org.apache.rocketmq</groupId>
+- <version>5.1.4-SNAPSHOT</version>
++ <version>5.1.4</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+diff --git a/namesrv/pom.xml b/namesrv/pom.xml
+index 684b2683c..7c218078a 100644
+--- a/namesrv/pom.xml
++++ b/namesrv/pom.xml
+@@ -19,7 +19,7 @@
+ <parent>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-all</artifactId>
+- <version>5.1.4-SNAPSHOT</version>
++ <version>5.1.4</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+diff --git a/openmessaging/pom.xml b/openmessaging/pom.xml
+index aaa4c896c..fd499e3de 100644
+--- a/openmessaging/pom.xml
++++ b/openmessaging/pom.xml
+@@ -20,7 +20,7 @@
+ <parent>
+ <artifactId>rocketmq-all</artifactId>
+ <groupId>org.apache.rocketmq</groupId>
+- <version>5.1.4-SNAPSHOT</version>
++ <version>5.1.4</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+diff --git a/pom.xml b/pom.xml
+index 4b382c6da..0e1d04f15 100644
+--- a/pom.xml
++++ b/pom.xml
+@@ -28,7 +28,7 @@
+ <inceptionYear>2012</inceptionYear>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-all</artifactId>
+- <version>5.1.4-SNAPSHOT</version>
++ <version>5.1.4</version>
+ <packaging>pom</packaging>
+ <name>Apache RocketMQ ${project.version}</name>
+ <url>http://rocketmq.apache.org/</url>
+@@ -37,7 +37,7 @@
+ <url>git@github.com:apache/rocketmq.git</url>
+ <connection>scm:git:git@github.com:apache/rocketmq.git</connection>
+ <developerConnection>scm:git:git@github.com:apache/rocketmq.git</developerConnection>
+- <tag>HEAD</tag>
++ <tag>rocketmq-all-5.1.4</tag>
+ </scm>
+
+ <mailingLists>
+diff --git a/proxy/pom.xml b/proxy/pom.xml
+index 3fbea107a..abf242eee 100644
+--- a/proxy/pom.xml
++++ b/proxy/pom.xml
+@@ -20,7 +20,7 @@
+ <parent>
+ <artifactId>rocketmq-all</artifactId>
+ <groupId>org.apache.rocketmq</groupId>
+- <version>5.1.4-SNAPSHOT</version>
++ <version>5.1.4</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+diff --git a/remoting/pom.xml b/remoting/pom.xml
+index 8a43c5c30..fc70cb62e 100644
+--- a/remoting/pom.xml
++++ b/remoting/pom.xml
+@@ -19,7 +19,7 @@
+ <parent>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-all</artifactId>
+- <version>5.1.4-SNAPSHOT</version>
++ <version>5.1.4</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+diff --git a/srvutil/pom.xml b/srvutil/pom.xml
+index fa54ad019..d7f946cce 100644
+--- a/srvutil/pom.xml
++++ b/srvutil/pom.xml
+@@ -19,7 +19,7 @@
+ <parent>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-all</artifactId>
+- <version>5.1.4-SNAPSHOT</version>
++ <version>5.1.4</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+diff --git a/store/pom.xml b/store/pom.xml
+index 38f04009d..6d6983c5d 100644
+--- a/store/pom.xml
++++ b/store/pom.xml
+@@ -19,7 +19,7 @@
+ <parent>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-all</artifactId>
+- <version>5.1.4-SNAPSHOT</version>
++ <version>5.1.4</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+diff --git a/test/pom.xml b/test/pom.xml
+index 8f25c35c9..39090e426 100644
+--- a/test/pom.xml
++++ b/test/pom.xml
+@@ -20,7 +20,7 @@
+ <parent>
+ <artifactId>rocketmq-all</artifactId>
+ <groupId>org.apache.rocketmq</groupId>
+- <version>5.1.4-SNAPSHOT</version>
++ <version>5.1.4</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+diff --git a/tieredstore/pom.xml b/tieredstore/pom.xml
+index c476040ba..7b209751f 100644
+--- a/tieredstore/pom.xml
++++ b/tieredstore/pom.xml
+@@ -19,7 +19,7 @@
+ <parent>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-all</artifactId>
+- <version>5.1.4-SNAPSHOT</version>
++ <version>5.1.4</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+diff --git a/tools/pom.xml b/tools/pom.xml
+index 1c3b431bc..806787ec9 100644
+--- a/tools/pom.xml
++++ b/tools/pom.xml
+@@ -19,7 +19,7 @@
+ <parent>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-all</artifactId>
+- <version>5.1.4-SNAPSHOT</version>
++ <version>5.1.4</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+--
+2.32.0.windows.2
+
+
+From 73b3fde83765e066541e3455cd1e6604292a9b7c Mon Sep 17 00:00:00 2001
+From: lk <xdkxlk@outlook.com>
+Date: Fri, 22 Sep 2023 10:08:59 +0800
+Subject: [PATCH 04/12] [maven-release-plugin] prepare for next development
+ iteration (#7379)
+
+---
+ acl/pom.xml | 2 +-
+ broker/pom.xml | 2 +-
+ client/pom.xml | 2 +-
+ common/pom.xml | 2 +-
+ container/pom.xml | 2 +-
+ controller/pom.xml | 2 +-
+ distribution/pom.xml | 2 +-
+ example/pom.xml | 2 +-
+ filter/pom.xml | 2 +-
+ namesrv/pom.xml | 2 +-
+ openmessaging/pom.xml | 2 +-
+ pom.xml | 4 ++--
+ proxy/pom.xml | 2 +-
+ remoting/pom.xml | 2 +-
+ srvutil/pom.xml | 2 +-
+ store/pom.xml | 2 +-
+ test/pom.xml | 2 +-
+ tieredstore/pom.xml | 2 +-
+ tools/pom.xml | 2 +-
+ 19 files changed, 20 insertions(+), 20 deletions(-)
+
+diff --git a/acl/pom.xml b/acl/pom.xml
+index 9f6838b00..8a296e5ae 100644
+--- a/acl/pom.xml
++++ b/acl/pom.xml
+@@ -13,7 +13,7 @@
+ <parent>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-all</artifactId>
+- <version>5.1.4</version>
++ <version>5.1.5-SNAPSHOT</version>
+ </parent>
+ <artifactId>rocketmq-acl</artifactId>
+ <name>rocketmq-acl ${project.version}</name>
+diff --git a/broker/pom.xml b/broker/pom.xml
+index d483e67ba..add83045d 100644
+--- a/broker/pom.xml
++++ b/broker/pom.xml
+@@ -13,7 +13,7 @@
+ <parent>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-all</artifactId>
+- <version>5.1.4</version>
++ <version>5.1.5-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+diff --git a/client/pom.xml b/client/pom.xml
+index 4febedc6d..d6fb3889b 100644
+--- a/client/pom.xml
++++ b/client/pom.xml
+@@ -19,7 +19,7 @@
+ <parent>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-all</artifactId>
+- <version>5.1.4</version>
++ <version>5.1.5-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+diff --git a/common/pom.xml b/common/pom.xml
+index b70873dfa..6104c3ac6 100644
+--- a/common/pom.xml
++++ b/common/pom.xml
+@@ -19,7 +19,7 @@
+ <parent>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-all</artifactId>
+- <version>5.1.4</version>
++ <version>5.1.5-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+diff --git a/container/pom.xml b/container/pom.xml
+index e6c1f4b4d..8af231e01 100644
+--- a/container/pom.xml
++++ b/container/pom.xml
+@@ -18,7 +18,7 @@
+ <parent>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-all</artifactId>
+- <version>5.1.4</version>
++ <version>5.1.5-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+diff --git a/controller/pom.xml b/controller/pom.xml
+index 46a3834c6..8432b220b 100644
+--- a/controller/pom.xml
++++ b/controller/pom.xml
+@@ -19,7 +19,7 @@
+ <parent>
+ <artifactId>rocketmq-all</artifactId>
+ <groupId>org.apache.rocketmq</groupId>
+- <version>5.1.4</version>
++ <version>5.1.5-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <packaging>jar</packaging>
+diff --git a/distribution/pom.xml b/distribution/pom.xml
+index 346c4de35..73474d34a 100644
+--- a/distribution/pom.xml
++++ b/distribution/pom.xml
+@@ -20,7 +20,7 @@
+ <parent>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-all</artifactId>
+- <version>5.1.4</version>
++ <version>5.1.5-SNAPSHOT</version>
+ </parent>
+ <artifactId>rocketmq-distribution</artifactId>
+ <name>rocketmq-distribution ${project.version}</name>
+diff --git a/example/pom.xml b/example/pom.xml
+index 9e7db43f8..a8c7f5382 100644
+--- a/example/pom.xml
++++ b/example/pom.xml
+@@ -19,7 +19,7 @@
+ <parent>
+ <artifactId>rocketmq-all</artifactId>
+ <groupId>org.apache.rocketmq</groupId>
+- <version>5.1.4</version>
++ <version>5.1.5-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+diff --git a/filter/pom.xml b/filter/pom.xml
+index 84189066d..892f46e9d 100644
+--- a/filter/pom.xml
++++ b/filter/pom.xml
+@@ -20,7 +20,7 @@
+ <parent>
+ <artifactId>rocketmq-all</artifactId>
+ <groupId>org.apache.rocketmq</groupId>
+- <version>5.1.4</version>
++ <version>5.1.5-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+diff --git a/namesrv/pom.xml b/namesrv/pom.xml
+index 7c218078a..e320ed573 100644
+--- a/namesrv/pom.xml
++++ b/namesrv/pom.xml
+@@ -19,7 +19,7 @@
+ <parent>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-all</artifactId>
+- <version>5.1.4</version>
++ <version>5.1.5-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+diff --git a/openmessaging/pom.xml b/openmessaging/pom.xml
+index fd499e3de..f10c8af6f 100644
+--- a/openmessaging/pom.xml
++++ b/openmessaging/pom.xml
+@@ -20,7 +20,7 @@
+ <parent>
+ <artifactId>rocketmq-all</artifactId>
+ <groupId>org.apache.rocketmq</groupId>
+- <version>5.1.4</version>
++ <version>5.1.5-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+diff --git a/pom.xml b/pom.xml
+index 0e1d04f15..4202d4095 100644
+--- a/pom.xml
++++ b/pom.xml
+@@ -28,7 +28,7 @@
+ <inceptionYear>2012</inceptionYear>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-all</artifactId>
+- <version>5.1.4</version>
++ <version>5.1.5-SNAPSHOT</version>
+ <packaging>pom</packaging>
+ <name>Apache RocketMQ ${project.version}</name>
+ <url>http://rocketmq.apache.org/</url>
+@@ -37,7 +37,7 @@
+ <url>git@github.com:apache/rocketmq.git</url>
+ <connection>scm:git:git@github.com:apache/rocketmq.git</connection>
+ <developerConnection>scm:git:git@github.com:apache/rocketmq.git</developerConnection>
+- <tag>rocketmq-all-5.1.4</tag>
++ <tag>HEAD</tag>
+ </scm>
+
+ <mailingLists>
+diff --git a/proxy/pom.xml b/proxy/pom.xml
+index abf242eee..5c5349a8c 100644
+--- a/proxy/pom.xml
++++ b/proxy/pom.xml
+@@ -20,7 +20,7 @@
+ <parent>
+ <artifactId>rocketmq-all</artifactId>
+ <groupId>org.apache.rocketmq</groupId>
+- <version>5.1.4</version>
++ <version>5.1.5-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+diff --git a/remoting/pom.xml b/remoting/pom.xml
+index fc70cb62e..f78480680 100644
+--- a/remoting/pom.xml
++++ b/remoting/pom.xml
+@@ -19,7 +19,7 @@
+ <parent>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-all</artifactId>
+- <version>5.1.4</version>
++ <version>5.1.5-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+diff --git a/srvutil/pom.xml b/srvutil/pom.xml
+index d7f946cce..894e9cc6f 100644
+--- a/srvutil/pom.xml
++++ b/srvutil/pom.xml
+@@ -19,7 +19,7 @@
+ <parent>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-all</artifactId>
+- <version>5.1.4</version>
++ <version>5.1.5-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+diff --git a/store/pom.xml b/store/pom.xml
+index 6d6983c5d..e979030e8 100644
+--- a/store/pom.xml
++++ b/store/pom.xml
+@@ -19,7 +19,7 @@
+ <parent>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-all</artifactId>
+- <version>5.1.4</version>
++ <version>5.1.5-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+diff --git a/test/pom.xml b/test/pom.xml
+index 39090e426..168cbab0b 100644
+--- a/test/pom.xml
++++ b/test/pom.xml
+@@ -20,7 +20,7 @@
+ <parent>
+ <artifactId>rocketmq-all</artifactId>
+ <groupId>org.apache.rocketmq</groupId>
+- <version>5.1.4</version>
++ <version>5.1.5-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+diff --git a/tieredstore/pom.xml b/tieredstore/pom.xml
+index 7b209751f..b2ea40bf3 100644
+--- a/tieredstore/pom.xml
++++ b/tieredstore/pom.xml
+@@ -19,7 +19,7 @@
+ <parent>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-all</artifactId>
+- <version>5.1.4</version>
++ <version>5.1.5-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+diff --git a/tools/pom.xml b/tools/pom.xml
+index 806787ec9..e1daa57a6 100644
+--- a/tools/pom.xml
++++ b/tools/pom.xml
+@@ -19,7 +19,7 @@
+ <parent>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-all</artifactId>
+- <version>5.1.4</version>
++ <version>5.1.5-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+--
+2.32.0.windows.2
+
+
+From 88a9d939ce110381b3b418370d4711c0c214dc7f Mon Sep 17 00:00:00 2001
+From: Ji Juntao <juntao.jjt@alibaba-inc.com>
+Date: Sat, 23 Sep 2023 17:38:27 +0800
+Subject: [PATCH 05/12] [ISSUE #7381] Fix the problem of inaccurate timer
+ message metric (#7382)
+
+* correct the timerMetrics' result.
+
+* for further extension.
+
+* checkstyle.
+
+* use toLong.
+---
+ .../store/timer/TimerMessageStore.java | 20 +++++++++++++++----
+ .../rocketmq/store/timer/TimerMetrics.java | 5 ++++-
+ .../rocketmq/store/timer/TimerRequest.java | 7 +++++--
+ .../store/timer/TimerMetricsTest.java | 10 ++++++++--
+ 4 files changed, 33 insertions(+), 9 deletions(-)
+
+diff --git a/store/src/main/java/org/apache/rocketmq/store/timer/TimerMessageStore.java b/store/src/main/java/org/apache/rocketmq/store/timer/TimerMessageStore.java
+index 0d50de65a..ac4c61cd6 100644
+--- a/store/src/main/java/org/apache/rocketmq/store/timer/TimerMessageStore.java
++++ b/store/src/main/java/org/apache/rocketmq/store/timer/TimerMessageStore.java
+@@ -41,6 +41,7 @@ import java.util.concurrent.TimeUnit;
+ import java.util.concurrent.atomic.AtomicInteger;
+ import java.util.function.Function;
+ import org.apache.commons.collections.CollectionUtils;
++import org.apache.commons.lang3.math.NumberUtils;
+ import org.apache.rocketmq.common.ServiceThread;
+ import org.apache.rocketmq.common.ThreadFactoryImpl;
+ import org.apache.rocketmq.common.TopicFilterType;
+@@ -599,7 +600,12 @@ public class TimerMessageStore {
+ if (null == msg || null == msg.getProperty(MessageConst.PROPERTY_REAL_TOPIC)) {
+ return;
+ }
+- timerMetrics.addAndGet(msg.getProperty(MessageConst.PROPERTY_REAL_TOPIC), value);
++ if (msg.getProperty(TIMER_ENQUEUE_MS) != null
++ && NumberUtils.toLong(msg.getProperty(TIMER_ENQUEUE_MS)) == Long.MAX_VALUE) {
++ return;
++ }
++ // pass msg into addAndGet, for further more judgement extension.
++ timerMetrics.addAndGet(msg, value);
+ } catch (Throwable t) {
+ if (frequency.incrementAndGet() % 1000 == 0) {
+ LOGGER.error("error in adding metric", t);
+@@ -1323,6 +1329,7 @@ public class TimerMessageStore {
+ perfCounterTicks.startTick(ENQUEUE_PUT);
+ DefaultStoreMetricsManager.incTimerEnqueueCount(getRealTopic(req.getMsg()));
+ if (shouldRunningDequeue && req.getDelayTime() < currWriteTimeMs) {
++ req.setEnqueueTime(Long.MAX_VALUE);
+ dequeuePutQueue.put(req);
+ } else {
+ boolean doEnqueueRes = doEnqueue(
+@@ -1452,9 +1459,14 @@ public class TimerMessageStore {
+ }
+ try {
+ perfCounterTicks.startTick(DEQUEUE_PUT);
+- DefaultStoreMetricsManager.incTimerDequeueCount(getRealTopic(tr.getMsg()));
+- addMetric(tr.getMsg(), -1);
+- MessageExtBrokerInner msg = convert(tr.getMsg(), tr.getEnqueueTime(), needRoll(tr.getMagic()));
++ MessageExt msgExt = tr.getMsg();
++ DefaultStoreMetricsManager.incTimerDequeueCount(getRealTopic(msgExt));
++ if (tr.getEnqueueTime() == Long.MAX_VALUE) {
++ // never enqueue, mark it.
++ MessageAccessor.putProperty(msgExt, TIMER_ENQUEUE_MS, String.valueOf(Long.MAX_VALUE));
++ }
++ addMetric(msgExt, -1);
++ MessageExtBrokerInner msg = convert(msgExt, tr.getEnqueueTime(), needRoll(tr.getMagic()));
+ doRes = PUT_NEED_RETRY != doPut(msg, needRoll(tr.getMagic()));
+ while (!doRes && !isStopped()) {
+ if (!isRunningDequeue()) {
+diff --git a/store/src/main/java/org/apache/rocketmq/store/timer/TimerMetrics.java b/store/src/main/java/org/apache/rocketmq/store/timer/TimerMetrics.java
+index e7b00cc07..7f8fedd8a 100644
+--- a/store/src/main/java/org/apache/rocketmq/store/timer/TimerMetrics.java
++++ b/store/src/main/java/org/apache/rocketmq/store/timer/TimerMetrics.java
+@@ -38,6 +38,8 @@ import java.util.concurrent.locks.Lock;
+ import java.util.concurrent.locks.ReentrantLock;
+ import org.apache.rocketmq.common.ConfigManager;
+ import org.apache.rocketmq.common.constant.LoggerName;
++import org.apache.rocketmq.common.message.MessageConst;
++import org.apache.rocketmq.common.message.MessageExt;
+ import org.apache.rocketmq.common.topic.TopicValidator;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+@@ -78,7 +80,8 @@ public class TimerMetrics extends ConfigManager {
+ return distPair.getCount().addAndGet(value);
+ }
+
+- public long addAndGet(String topic, int value) {
++ public long addAndGet(MessageExt msg, int value) {
++ String topic = msg.getProperty(MessageConst.PROPERTY_REAL_TOPIC);
+ Metric pair = getTopicPair(topic);
+ getDataVersion().nextVersion();
+ pair.setTimeStamp(System.currentTimeMillis());
+diff --git a/store/src/main/java/org/apache/rocketmq/store/timer/TimerRequest.java b/store/src/main/java/org/apache/rocketmq/store/timer/TimerRequest.java
+index 1dd64f759..1b25d355c 100644
+--- a/store/src/main/java/org/apache/rocketmq/store/timer/TimerRequest.java
++++ b/store/src/main/java/org/apache/rocketmq/store/timer/TimerRequest.java
+@@ -27,8 +27,9 @@ public class TimerRequest {
+ private final int sizePy;
+ private final long delayTime;
+
+- private final long enqueueTime;
+ private final int magic;
++
++ private long enqueueTime;
+ private MessageExt msg;
+
+
+@@ -94,7 +95,9 @@ public class TimerRequest {
+ public void setLatch(CountDownLatch latch) {
+ this.latch = latch;
+ }
+-
++ public void setEnqueueTime(long enqueueTime) {
++ this.enqueueTime = enqueueTime;
++ }
+ public void idempotentRelease() {
+ idempotentRelease(true);
+ }
+diff --git a/store/src/test/java/org/apache/rocketmq/store/timer/TimerMetricsTest.java b/store/src/test/java/org/apache/rocketmq/store/timer/TimerMetricsTest.java
+index b7392cc45..3c7b9b67f 100644
+--- a/store/src/test/java/org/apache/rocketmq/store/timer/TimerMetricsTest.java
++++ b/store/src/test/java/org/apache/rocketmq/store/timer/TimerMetricsTest.java
+@@ -16,6 +16,9 @@
+ */
+ package org.apache.rocketmq.store.timer;
+
++import org.apache.rocketmq.common.message.MessageAccessor;
++import org.apache.rocketmq.common.message.MessageConst;
++import org.apache.rocketmq.common.message.MessageExt;
+ import org.junit.Assert;
+ import org.junit.Test;
+
+@@ -31,8 +34,11 @@ public class TimerMetricsTest {
+
+ TimerMetrics first = new TimerMetrics(baseDir);
+ Assert.assertTrue(first.load());
+- first.addAndGet("AAA", 1000);
+- first.addAndGet("BBB", 2000);
++ MessageExt msg = new MessageExt();
++ MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_TOPIC, "AAA");
++ first.addAndGet(msg, 1000);
++ MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_TOPIC, "BBB");
++ first.addAndGet(msg, 2000);
+ Assert.assertEquals(1000, first.getTimingCount("AAA"));
+ Assert.assertEquals(2000, first.getTimingCount("BBB"));
+ long curr = System.currentTimeMillis();
+--
+2.32.0.windows.2
+
+
+From d7e5c4d1a4e048cd97f0b29a96a0fc575927a03e Mon Sep 17 00:00:00 2001
+From: guyinyou <36399867+guyinyou@users.noreply.github.com>
+Date: Mon, 25 Sep 2023 13:37:36 +0800
+Subject: [PATCH 06/12] [ISSUE #7389] Fix the problem that getLastMappedFile
+ function affects performance
+
+Co-authored-by: guyinyou <guyinyou.gyy@alibaba-inc.com>
+---
+ .../apache/rocketmq/store/MappedFileQueue.java | 15 +++++++++++++--
+ 1 file changed, 13 insertions(+), 2 deletions(-)
+
+diff --git a/store/src/main/java/org/apache/rocketmq/store/MappedFileQueue.java b/store/src/main/java/org/apache/rocketmq/store/MappedFileQueue.java
+index 32b90d14f..9a0824829 100644
+--- a/store/src/main/java/org/apache/rocketmq/store/MappedFileQueue.java
++++ b/store/src/main/java/org/apache/rocketmq/store/MappedFileQueue.java
+@@ -377,8 +377,19 @@ public class MappedFileQueue implements Swappable {
+ }
+
+ public MappedFile getLastMappedFile() {
+- MappedFile[] mappedFiles = this.mappedFiles.toArray(new MappedFile[0]);
+- return mappedFiles.length == 0 ? null : mappedFiles[mappedFiles.length - 1];
++ MappedFile mappedFileLast = null;
++ while (!this.mappedFiles.isEmpty()) {
++ try {
++ mappedFileLast = this.mappedFiles.get(this.mappedFiles.size() - 1);
++ break;
++ } catch (IndexOutOfBoundsException e) {
++ //continue;
++ } catch (Exception e) {
++ log.error("getLastMappedFile has exception.", e);
++ break;
++ }
++ }
++ return mappedFileLast;
+ }
+
+ public boolean resetOffset(long offset) {
+--
+2.32.0.windows.2
+
+
+From 3fd43353fdf880deb5d63ba3ad50cc6e3259dc3a Mon Sep 17 00:00:00 2001
+From: Zhouxiang Zhan <zhouxzhan@apache.org>
+Date: Tue, 26 Sep 2023 13:53:51 +0800
+Subject: [PATCH 07/12] [ISSUE #7393] Add timeout configuration for grpc server
+ (#7394)
+
+* Add timeout configuration for grpc server
+
+* Add proxyConfig
+---
+ .../java/org/apache/rocketmq/proxy/ProxyStartup.java | 1 +
+ .../apache/rocketmq/proxy/config/ProxyConfig.java | 9 +++++++++
+ .../org/apache/rocketmq/proxy/grpc/GrpcServer.java | 10 ++++++++--
+ .../rocketmq/proxy/grpc/GrpcServerBuilder.java | 12 +++++++++++-
+ 4 files changed, 29 insertions(+), 3 deletions(-)
+
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/ProxyStartup.java b/proxy/src/main/java/org/apache/rocketmq/proxy/ProxyStartup.java
+index 06d5f4525..3b2ca99bf 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/ProxyStartup.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/ProxyStartup.java
+@@ -85,6 +85,7 @@ public class ProxyStartup {
+ .addService(ChannelzService.newInstance(100))
+ .addService(ProtoReflectionService.newInstance())
+ .configInterceptor(accessValidators)
++ .shutdownTime(ConfigurationManager.getProxyConfig().getGrpcShutdownTimeSeconds(), TimeUnit.SECONDS)
+ .build();
+ PROXY_START_AND_SHUTDOWN.appendStartAndShutdown(grpcServer);
+
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java b/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java
+index b2478fec3..c0d00d864 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java
+@@ -87,6 +87,7 @@ public class ProxyConfig implements ConfigFile {
+ */
+ private String proxyMode = ProxyMode.CLUSTER.name();
+ private Integer grpcServerPort = 8081;
++ private long grpcShutdownTimeSeconds = 30;
+ private int grpcBossLoopNum = 1;
+ private int grpcWorkerLoopNum = PROCESSOR_NUMBER * 2;
+ private boolean enableGrpcEpoll = false;
+@@ -443,6 +444,14 @@ public class ProxyConfig implements ConfigFile {
+ this.grpcServerPort = grpcServerPort;
+ }
+
++ public long getGrpcShutdownTimeSeconds() {
++ return grpcShutdownTimeSeconds;
++ }
++
++ public void setGrpcShutdownTimeSeconds(long grpcShutdownTimeSeconds) {
++ this.grpcShutdownTimeSeconds = grpcShutdownTimeSeconds;
++ }
++
+ public boolean isUseEndpointPortFromRequest() {
+ return useEndpointPortFromRequest;
+ }
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/GrpcServer.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/GrpcServer.java
+index 1bffa3c0b..d5b896fe1 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/GrpcServer.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/GrpcServer.java
+@@ -29,8 +29,14 @@ public class GrpcServer implements StartAndShutdown {
+
+ private final Server server;
+
+- protected GrpcServer(Server server) {
++ private final long timeout;
++
++ private final TimeUnit unit;
++
++ protected GrpcServer(Server server, long timeout, TimeUnit unit) {
+ this.server = server;
++ this.timeout = timeout;
++ this.unit = unit;
+ }
+
+ public void start() throws Exception {
+@@ -40,7 +46,7 @@ public class GrpcServer implements StartAndShutdown {
+
+ public void shutdown() {
+ try {
+- this.server.shutdown().awaitTermination(30, TimeUnit.SECONDS);
++ this.server.shutdown().awaitTermination(timeout, unit);
+ log.info("grpc server shutdown successfully.");
+ } catch (Exception e) {
+ e.printStackTrace();
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/GrpcServerBuilder.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/GrpcServerBuilder.java
+index 9cddd3013..0e79006f6 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/GrpcServerBuilder.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/GrpcServerBuilder.java
+@@ -41,6 +41,10 @@ public class GrpcServerBuilder {
+ private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME);
+ protected NettyServerBuilder serverBuilder;
+
++ protected long time = 30;
++
++ protected TimeUnit unit = TimeUnit.SECONDS;
++
+ public static GrpcServerBuilder newBuilder(ThreadPoolExecutor executor, int port) {
+ return new GrpcServerBuilder(executor, port);
+ }
+@@ -77,6 +81,12 @@ public class GrpcServerBuilder {
+ port, bossLoopNum, workerLoopNum, maxInboundMessageSize);
+ }
+
++ public GrpcServerBuilder shutdownTime(long time, TimeUnit unit) {
++ this.time = time;
++ this.unit = unit;
++ return this;
++ }
++
+ public GrpcServerBuilder addService(BindableService service) {
+ this.serverBuilder.addService(service);
+ return this;
+@@ -93,7 +103,7 @@ public class GrpcServerBuilder {
+ }
+
+ public GrpcServer build() {
+- return new GrpcServer(this.serverBuilder.build());
++ return new GrpcServer(this.serverBuilder.build(), time, unit);
+ }
+
+ public GrpcServerBuilder configInterceptor(List<AccessValidator> accessValidators) {
+--
+2.32.0.windows.2
+
+
+From c3b86cd1e3c068bc5847671c899a284e49a2ecdc Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?=E7=9F=B3=E8=87=BB=E8=87=BB=28Steven=20shi=29?=
+ <shirenchuang@users.noreply.github.com>
+Date: Tue, 26 Sep 2023 16:07:13 +0800
+Subject: [PATCH 08/12] [ISSUE #7398] Fix ExportConfigsCommand NPE (#7399)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Co-authored-by: 石臻臻 <shirenchuang.src@cainiao.com>
+---
+ .../command/export/ExportConfigsCommand.java | 42 ++++++++++++-------
+ 1 file changed, 26 insertions(+), 16 deletions(-)
+
+diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/export/ExportConfigsCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/export/ExportConfigsCommand.java
+index 03613b29c..c3f96d597 100644
+--- a/tools/src/main/java/org/apache/rocketmq/tools/command/export/ExportConfigsCommand.java
++++ b/tools/src/main/java/org/apache/rocketmq/tools/command/export/ExportConfigsCommand.java
+@@ -20,6 +20,7 @@ import java.util.HashMap;
+ import java.util.List;
+ import java.util.Map;
+ import java.util.Map.Entry;
++import java.util.Arrays;
+ import java.util.Properties;
+
+ import com.alibaba.fastjson.JSON;
+@@ -106,24 +107,33 @@ public class ExportConfigsCommand implements SubCommand {
+ }
+ }
+
++
+ private Properties needBrokerProprties(Properties properties) {
++ List<String> propertyKeys = Arrays.asList(
++ "brokerClusterName",
++ "brokerId",
++ "brokerName",
++ "brokerRole",
++ "fileReservedTime",
++ "filterServerNums",
++ "flushDiskType",
++ "maxMessageSize",
++ "messageDelayLevel",
++ "msgTraceTopicName",
++ "slaveReadEnable",
++ "traceOn",
++ "traceTopicEnable",
++ "useTLS",
++ "autoCreateTopicEnable",
++ "autoCreateSubscriptionGroup"
++ );
++
+ Properties newProperties = new Properties();
+- newProperties.setProperty("brokerClusterName", properties.getProperty("brokerClusterName"));
+- newProperties.setProperty("brokerId", properties.getProperty("brokerId"));
+- newProperties.setProperty("brokerName", properties.getProperty("brokerName"));
+- newProperties.setProperty("brokerRole", properties.getProperty("brokerRole"));
+- newProperties.setProperty("fileReservedTime", properties.getProperty("fileReservedTime"));
+- newProperties.setProperty("filterServerNums", properties.getProperty("filterServerNums"));
+- newProperties.setProperty("flushDiskType", properties.getProperty("flushDiskType"));
+- newProperties.setProperty("maxMessageSize", properties.getProperty("maxMessageSize"));
+- newProperties.setProperty("messageDelayLevel", properties.getProperty("messageDelayLevel"));
+- newProperties.setProperty("msgTraceTopicName", properties.getProperty("msgTraceTopicName"));
+- newProperties.setProperty("slaveReadEnable", properties.getProperty("slaveReadEnable"));
+- newProperties.setProperty("traceOn", properties.getProperty("traceOn"));
+- newProperties.setProperty("traceTopicEnable", properties.getProperty("traceTopicEnable"));
+- newProperties.setProperty("useTLS", properties.getProperty("useTLS"));
+- newProperties.setProperty("autoCreateTopicEnable", properties.getProperty("autoCreateTopicEnable"));
+- newProperties.setProperty("autoCreateSubscriptionGroup", properties.getProperty("autoCreateSubscriptionGroup"));
++ propertyKeys.stream()
++ .filter(key -> properties.getProperty(key) != null)
++ .forEach(key -> newProperties.setProperty(key, properties.getProperty(key)));
++
+ return newProperties;
+ }
++
+ }
+--
+2.32.0.windows.2
+
+
+From 959a98120cea8022498557a308aff35e3d8def2a Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?=E7=9F=B3=E8=87=BB=E8=87=BB=28Steven=20shi=29?=
+ <shirenchuang@users.noreply.github.com>
+Date: Wed, 27 Sep 2023 01:59:58 +0800
+Subject: [PATCH 09/12] [ISSUE #7400] Fix getBrokerEpochSubCommand NPE
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Co-authored-by: 石臻臻 <shirenchuang.src@cainiao.com>
+---
+ .../broker/processor/AdminBrokerProcessor.java | 10 ++++++++--
+ 1 file changed, 8 insertions(+), 2 deletions(-)
+
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessor.java
+index 9e48431be..e77120e15 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessor.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessor.java
+@@ -2736,10 +2736,16 @@ public class AdminBrokerProcessor implements NettyRequestProcessor {
+ final ReplicasManager replicasManager = this.brokerController.getReplicasManager();
+ assert replicasManager != null;
+ final BrokerConfig brokerConfig = this.brokerController.getBrokerConfig();
++ final RemotingCommand response = RemotingCommand.createResponseCommand(null);
++
++ if (!brokerConfig.isEnableControllerMode()) {
++ response.setCode(ResponseCode.SYSTEM_ERROR);
++ response.setRemark("this request only for controllerMode ");
++ return response;
++ }
+ final EpochEntryCache entryCache = new EpochEntryCache(brokerConfig.getBrokerClusterName(),
+- brokerConfig.getBrokerName(), brokerConfig.getBrokerId(), replicasManager.getEpochEntries(), this.brokerController.getMessageStore().getMaxPhyOffset());
++ brokerConfig.getBrokerName(), brokerConfig.getBrokerId(), replicasManager.getEpochEntries(), this.brokerController.getMessageStore().getMaxPhyOffset());
+
+- final RemotingCommand response = RemotingCommand.createResponseCommand(null);
+ response.setBody(entryCache.encode());
+ response.setCode(ResponseCode.SUCCESS);
+ response.setRemark(null);
+--
+2.32.0.windows.2
+
+
+From 0a6ae4605fef4eaab6262fbd370aba887d8ae58b Mon Sep 17 00:00:00 2001
+From: tiger lee <1026203200@qq.com>
+Date: Wed, 27 Sep 2023 14:43:15 +0800
+Subject: [PATCH 10/12] [ISSUE #7396] Fix wrong word in
+ BrokerController#doResterBrokerAll (#7397)
+
+---
+ .../main/java/org/apache/rocketmq/broker/BrokerController.java | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java
+index 53e2e1b62..d4bded600 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java
+@@ -1807,7 +1807,7 @@ public class BrokerController {
+ TopicConfigSerializeWrapper topicConfigWrapper) {
+
+ if (shutdown) {
+- BrokerController.LOG.info("BrokerController#doResterBrokerAll: broker has shutdown, no need to register any more.");
++ BrokerController.LOG.info("BrokerController#doRegisterBrokerAll: broker has shutdown, no need to register any more.");
+ return;
+ }
+ List<RegisterBrokerResult> registerBrokerResultList = this.brokerOuterAPI.registerBrokerAll(
+--
+2.32.0.windows.2
+
+
+From 4f1b42a7c5557bcadd6b9982a0c9bd876622c69e Mon Sep 17 00:00:00 2001
+From: ShuangxiDing <dingshuangxi888@gmail.com>
+Date: Thu, 28 Sep 2023 16:52:02 +0800
+Subject: [PATCH 11/12] [ISSUE #7410] Handle the Exception when the Proxy
+ requests the client
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Co-authored-by: 徒钟 <shuangxi.dsx@alibaba-inc.com>
+---
+ .../remoting/channel/RemotingChannel.java | 23 ++++++++++++++-----
+ 1 file changed, 17 insertions(+), 6 deletions(-)
+
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/channel/RemotingChannel.java b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/channel/RemotingChannel.java
+index 40946cabf..d755fdcc4 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/channel/RemotingChannel.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/channel/RemotingChannel.java
+@@ -34,6 +34,7 @@ import org.apache.rocketmq.common.utils.NetworkUtil;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+ import org.apache.rocketmq.proxy.common.channel.ChannelHelper;
++import org.apache.rocketmq.proxy.common.utils.ExceptionUtils;
+ import org.apache.rocketmq.proxy.common.utils.FutureUtils;
+ import org.apache.rocketmq.proxy.config.ConfigurationManager;
+ import org.apache.rocketmq.proxy.processor.channel.ChannelExtendAttributeGetter;
+@@ -158,10 +159,15 @@ public class RemotingChannel extends ProxyChannel implements RemoteChannelConver
+ if (response.getCode() == ResponseCode.SUCCESS) {
+ ConsumerRunningInfo consumerRunningInfo = ConsumerRunningInfo.decode(response.getBody(), ConsumerRunningInfo.class);
+ responseFuture.complete(new ProxyRelayResult<>(ResponseCode.SUCCESS, "", consumerRunningInfo));
++ } else {
++ String errMsg = String.format("get consumer running info failed, code:%s remark:%s", response.getCode(), response.getRemark());
++ RuntimeException e = new RuntimeException(errMsg);
++ responseFuture.completeExceptionally(e);
+ }
+- String errMsg = String.format("get consumer running info failed, code:%s remark:%s", response.getCode(), response.getRemark());
+- RuntimeException e = new RuntimeException(errMsg);
+- responseFuture.completeExceptionally(e);
++ })
++ .exceptionally(t -> {
++ responseFuture.completeExceptionally(ExceptionUtils.getRealException(t));
++ return null;
+ });
+ return CompletableFuture.completedFuture(null);
+ } catch (Throwable t) {
+@@ -183,10 +189,15 @@ public class RemotingChannel extends ProxyChannel implements RemoteChannelConver
+ if (response.getCode() == ResponseCode.SUCCESS) {
+ ConsumeMessageDirectlyResult result = ConsumeMessageDirectlyResult.decode(response.getBody(), ConsumeMessageDirectlyResult.class);
+ responseFuture.complete(new ProxyRelayResult<>(ResponseCode.SUCCESS, "", result));
++ } else {
++ String errMsg = String.format("consume message directly failed, code:%s remark:%s", response.getCode(), response.getRemark());
++ RuntimeException e = new RuntimeException(errMsg);
++ responseFuture.completeExceptionally(e);
+ }
+- String errMsg = String.format("consume message directly failed, code:%s remark:%s", response.getCode(), response.getRemark());
+- RuntimeException e = new RuntimeException(errMsg);
+- responseFuture.completeExceptionally(e);
++ })
++ .exceptionally(t -> {
++ responseFuture.completeExceptionally(ExceptionUtils.getRealException(t));
++ return null;
+ });
+ return CompletableFuture.completedFuture(null);
+ } catch (Throwable t) {
+--
+2.32.0.windows.2
+
+
+From c36bb78e850129b9db40adc5b0e1b9bfd5c8fd2e Mon Sep 17 00:00:00 2001
+From: shriVATSA54 <116296557+shriVATSA54@users.noreply.github.com>
+Date: Sat, 7 Oct 2023 12:22:39 +0530
+Subject: [PATCH 12/12] [ISSUE 7313] Enhancement Optimization Method name
+ (#7420)
+
+* Enhancment/method_name/#7313/
+
+* Enhancment/method_name/#7313/
+
+* Enhancment/method_name/#7313/
+---
+ .../tieredstore/provider/TieredStoreTopicBlackListFilter.java | 2 +-
+ .../rocketmq/tieredstore/provider/TieredStoreTopicFilter.java | 2 +-
+ .../provider/TieredStoreTopicBlackListFilterTest.java | 2 +-
+ 3 files changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/TieredStoreTopicBlackListFilter.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/TieredStoreTopicBlackListFilter.java
+index 50adbb713..f8bf165bc 100644
+--- a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/TieredStoreTopicBlackListFilter.java
++++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/TieredStoreTopicBlackListFilter.java
+@@ -39,7 +39,7 @@ public class TieredStoreTopicBlackListFilter implements TieredStoreTopicFilter {
+ }
+
+ @Override
+- public void addTopicToWhiteList(String topicName) {
++ public void addTopicToBlackList(String topicName) {
+ this.topicBlackSet.add(topicName);
+ }
+ }
+diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/TieredStoreTopicFilter.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/TieredStoreTopicFilter.java
+index 3f26b8b02..f983ed6e9 100644
+--- a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/TieredStoreTopicFilter.java
++++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/provider/TieredStoreTopicFilter.java
+@@ -21,5 +21,5 @@ public interface TieredStoreTopicFilter {
+
+ boolean filterTopic(String topicName);
+
+- void addTopicToWhiteList(String topicName);
++ void addTopicToBlackList(String topicName);
+ }
+diff --git a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/provider/TieredStoreTopicBlackListFilterTest.java b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/provider/TieredStoreTopicBlackListFilterTest.java
+index 2bf48173c..fbaafa1b4 100644
+--- a/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/provider/TieredStoreTopicBlackListFilterTest.java
++++ b/tieredstore/src/test/java/org/apache/rocketmq/tieredstore/provider/TieredStoreTopicBlackListFilterTest.java
+@@ -30,7 +30,7 @@ public class TieredStoreTopicBlackListFilterTest {
+
+ String topicName = "WhiteTopic";
+ Assert.assertFalse(topicFilter.filterTopic(topicName));
+- topicFilter.addTopicToWhiteList(topicName);
++ topicFilter.addTopicToBlackList(topicName);
+ Assert.assertTrue(topicFilter.filterTopic(topicName));
+ }
+ }
+\ No newline at end of file
+--
+2.32.0.windows.2
+
diff --git a/patch020-backport-add-goaway-mechanism.patch b/patch020-backport-add-goaway-mechanism.patch
new file mode 100644
index 000000000..122db0325
--- /dev/null
+++ b/patch020-backport-add-goaway-mechanism.patch
@@ -0,0 +1,3696 @@
+From 84156084a4c5228e1d2fe21e068fff330bbc40d1 Mon Sep 17 00:00:00 2001
+From: Zhouxiang Zhan <zhouxzhan@apache.org>
+Date: Sun, 8 Oct 2023 11:13:25 +0800
+Subject: [PATCH 1/7] [ISSUE #7321] Refector NettyRemotingAbstract with unify
+ future implementation (#7322)
+
+* Refector NettyRemotingAbstract
+
+* Add invoke with future method
+
+* Deprecate InvokeCallback#operationComplete
+
+* Add operationSuccess and operationException for InvokeCallback
+
+* fix unit test
+
+* fix unit test
+
+* Keep InvokeCallback#operationComplete
+
+* Optimize invokeAsyncImpl operationComplete
+
+* Add unit test for NettyRemotingClient
+
+* fix checkstyle
+---
+ .../rocketmq/broker/out/BrokerOuterAPI.java | 147 +++++----
+ .../rocketmq/client/impl/MQAdminImpl.java | 71 ++--
+ .../rocketmq/client/impl/MQClientAPIImpl.java | 239 +++++++-------
+ .../client/impl/mqclient/MQClientAPIExt.java | 309 ++++++++----------
+ .../client/impl/MQClientAPIImplTest.java | 12 +-
+ .../remoting/RemotingProtocolServer.java | 22 +-
+ .../service/mqclient/MQClientAPIExtTest.java | 97 +++---
+ .../rocketmq/remoting/InvokeCallback.java | 15 +
+ .../rocketmq/remoting/RemotingClient.java | 27 +-
+ .../remoting/netty/NettyRemotingAbstract.java | 123 ++++---
+ .../remoting/netty/NettyRemotingClient.java | 33 +-
+ .../remoting/netty/ResponseFuture.java | 15 +
+ .../rocketmq/remoting/rpc/RpcClientImpl.java | 29 +-
+ .../rocketmq/remoting/RemotingServerTest.java | 22 +-
+ .../rocketmq/remoting/netty/MockChannel.java | 21 +-
+ .../remoting/netty/MockChannelPromise.java | 191 +++++++++++
+ .../netty/NettyRemotingAbstractTest.java | 54 ++-
+ .../netty/NettyRemotingClientTest.java | 185 ++++++++++-
+ 18 files changed, 1029 insertions(+), 583 deletions(-)
+ rename client/src/main/java/org/apache/rocketmq/client/impl/BaseInvokeCallback.java => remoting/src/test/java/org/apache/rocketmq/remoting/netty/MockChannel.java (57%)
+ create mode 100644 remoting/src/test/java/org/apache/rocketmq/remoting/netty/MockChannelPromise.java
+
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/out/BrokerOuterAPI.java b/broker/src/main/java/org/apache/rocketmq/broker/out/BrokerOuterAPI.java
+index 9dfb8127d..6fde48dd9 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/out/BrokerOuterAPI.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/out/BrokerOuterAPI.java
+@@ -73,6 +73,7 @@ import org.apache.rocketmq.remoting.exception.RemotingTimeoutException;
+ import org.apache.rocketmq.remoting.exception.RemotingTooMuchRequestException;
+ import org.apache.rocketmq.remoting.netty.NettyClientConfig;
+ import org.apache.rocketmq.remoting.netty.NettyRemotingClient;
++import org.apache.rocketmq.remoting.netty.ResponseFuture;
+ import org.apache.rocketmq.remoting.protocol.BrokerSyncInfo;
+ import org.apache.rocketmq.remoting.protocol.DataVersion;
+ import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+@@ -107,6 +108,8 @@ import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeader;
+ import org.apache.rocketmq.remoting.protocol.header.SendMessageRequestHeaderV2;
+ import org.apache.rocketmq.remoting.protocol.header.SendMessageResponseHeader;
+ import org.apache.rocketmq.remoting.protocol.header.controller.AlterSyncStateSetRequestHeader;
++import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterRequestHeader;
++import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterResponseHeader;
+ import org.apache.rocketmq.remoting.protocol.header.controller.GetMetaDataResponseHeader;
+ import org.apache.rocketmq.remoting.protocol.header.controller.GetReplicaInfoRequestHeader;
+ import org.apache.rocketmq.remoting.protocol.header.controller.GetReplicaInfoResponseHeader;
+@@ -124,8 +127,6 @@ import org.apache.rocketmq.remoting.protocol.header.namesrv.RegisterBrokerReques
+ import org.apache.rocketmq.remoting.protocol.header.namesrv.RegisterBrokerResponseHeader;
+ import org.apache.rocketmq.remoting.protocol.header.namesrv.RegisterTopicRequestHeader;
+ import org.apache.rocketmq.remoting.protocol.header.namesrv.UnRegisterBrokerRequestHeader;
+-import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterRequestHeader;
+-import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterResponseHeader;
+ import org.apache.rocketmq.remoting.protocol.heartbeat.SubscriptionData;
+ import org.apache.rocketmq.remoting.protocol.namesrv.RegisterBrokerResult;
+ import org.apache.rocketmq.remoting.protocol.route.BrokerData;
+@@ -151,7 +152,6 @@ public class BrokerOuterAPI {
+ private final RpcClient rpcClient;
+ private String nameSrvAddr = null;
+
+-
+ public BrokerOuterAPI(final NettyClientConfig nettyClientConfig) {
+ this(nettyClientConfig, new DynamicalExtFieldRPCHook(), new ClientMetadata());
+ }
+@@ -459,7 +459,7 @@ public class BrokerOuterAPI {
+ * @param filterServerList
+ * @param oneway
+ * @param timeoutMills
+- * @param compressed default false
++ * @param compressed default false
+ * @return
+ */
+ public List<RegisterBrokerResult> registerBrokerAll(
+@@ -643,7 +643,6 @@ public class BrokerOuterAPI {
+ queueDatas.add(queueData);
+ final byte[] topicRouteBody = topicRouteData.encode();
+
+-
+ List<String> nameServerAddressList = this.remotingClient.getNameServerAddressList();
+ final CountDownLatch countDownLatch = new CountDownLatch(nameServerAddressList.size());
+ for (final String namesrvAddr : nameServerAddressList) {
+@@ -910,25 +909,33 @@ public class BrokerOuterAPI {
+ RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.LOCK_BATCH_MQ, null);
+
+ request.setBody(requestBody.encode());
+- this.remotingClient.invokeAsync(addr, request, timeoutMillis, responseFuture -> {
+- if (callback == null) {
+- return;
++ this.remotingClient.invokeAsync(addr, request, timeoutMillis, new InvokeCallback() {
++ @Override
++ public void operationComplete(ResponseFuture responseFuture) {
++
+ }
+
+- try {
+- RemotingCommand response = responseFuture.getResponseCommand();
+- if (response != null) {
+- if (response.getCode() == ResponseCode.SUCCESS) {
+- LockBatchResponseBody responseBody = LockBatchResponseBody.decode(response.getBody(),
+- LockBatchResponseBody.class);
+- Set<MessageQueue> messageQueues = responseBody.getLockOKMQSet();
+- callback.onSuccess(messageQueues);
+- } else {
+- callback.onException(new MQBrokerException(response.getCode(), response.getRemark()));
+- }
++ @Override
++ public void operationSucceed(RemotingCommand response) {
++ if (callback == null) {
++ return;
+ }
+- } catch (Throwable ignored) {
++ if (response.getCode() == ResponseCode.SUCCESS) {
++ LockBatchResponseBody responseBody = LockBatchResponseBody.decode(response.getBody(),
++ LockBatchResponseBody.class);
++ Set<MessageQueue> messageQueues = responseBody.getLockOKMQSet();
++ callback.onSuccess(messageQueues);
++ } else {
++ callback.onException(new MQBrokerException(response.getCode(), response.getRemark()));
++ }
++ }
+
++ @Override
++ public void operationFail(Throwable throwable) {
++ if (callback == null) {
++ return;
++ }
++ callback.onException(throwable);
+ }
+ });
+ }
+@@ -942,22 +949,30 @@ public class BrokerOuterAPI {
+
+ request.setBody(requestBody.encode());
+
+- this.remotingClient.invokeAsync(addr, request, timeoutMillis, responseFuture -> {
+- if (callback == null) {
+- return;
++ this.remotingClient.invokeAsync(addr, request, timeoutMillis, new InvokeCallback() {
++ @Override
++ public void operationComplete(ResponseFuture responseFuture) {
++
+ }
+
+- try {
+- RemotingCommand response = responseFuture.getResponseCommand();
+- if (response != null) {
+- if (response.getCode() == ResponseCode.SUCCESS) {
+- callback.onSuccess();
+- } else {
+- callback.onException(new MQBrokerException(response.getCode(), response.getRemark()));
+- }
++ @Override
++ public void operationSucceed(RemotingCommand response) {
++ if (callback == null) {
++ return;
+ }
+- } catch (Throwable ignored) {
++ if (response.getCode() == ResponseCode.SUCCESS) {
++ callback.onSuccess();
++ } else {
++ callback.onException(new MQBrokerException(response.getCode(), response.getRemark()));
++ }
++ }
+
++ @Override
++ public void operationFail(Throwable throwable) {
++ if (callback == null) {
++ return;
++ }
++ callback.onException(throwable);
+ }
+ });
+ }
+@@ -983,21 +998,27 @@ public class BrokerOuterAPI {
+ CompletableFuture<SendResult> cf = new CompletableFuture<>();
+ final String msgId = msg.getMsgId();
+ try {
+- this.remotingClient.invokeAsync(brokerAddr, request, timeoutMillis, responseFuture -> {
+- RemotingCommand response = responseFuture.getResponseCommand();
+- if (null != response) {
+- SendResult sendResult = null;
++ this.remotingClient.invokeAsync(brokerAddr, request, timeoutMillis, new InvokeCallback() {
++ @Override
++ public void operationComplete(ResponseFuture responseFuture) {
++
++ }
++
++ @Override
++ public void operationSucceed(RemotingCommand response) {
+ try {
+- sendResult = this.processSendResponse(brokerName, msg, response);
++ SendResult sendResult = processSendResponse(brokerName, msg, response);
+ cf.complete(sendResult);
+ } catch (MQBrokerException | RemotingCommandException e) {
+ LOGGER.error("processSendResponse in sendMessageToSpecificBrokerAsync failed, msgId=" + msgId, e);
+ cf.completeExceptionally(e);
+ }
+- } else {
+- cf.complete(null);
+ }
+
++ @Override
++ public void operationFail(Throwable throwable) {
++ cf.completeExceptionally(throwable);
++ }
+ });
+ } catch (Throwable t) {
+ LOGGER.error("invokeAsync failed in sendMessageToSpecificBrokerAsync, msgId=" + msgId, t);
+@@ -1057,7 +1078,7 @@ public class BrokerOuterAPI {
+ }
+ if (sendStatus != null) {
+ SendMessageResponseHeader responseHeader =
+- (SendMessageResponseHeader) response.decodeCommandCustomHeader(SendMessageResponseHeader.class);
++ (SendMessageResponseHeader) response.decodeCommandCustomHeader(SendMessageResponseHeader.class);
+
+ //If namespace not null , reset Topic without namespace.
+ String topic = msg.getTopic();
+@@ -1073,8 +1094,8 @@ public class BrokerOuterAPI {
+ uniqMsgId = sb.toString();
+ }
+ SendResult sendResult = new SendResult(sendStatus,
+- uniqMsgId,
+- responseHeader.getMsgId(), messageQueue, responseHeader.getQueueOffset());
++ uniqMsgId,
++ responseHeader.getMsgId(), messageQueue, responseHeader.getQueueOffset());
+ sendResult.setTransactionId(responseHeader.getTransactionId());
+ String regionId = response.getExtFields().get(MessageConst.PROPERTY_MSG_REGION);
+ String traceOn = response.getExtFields().get(MessageConst.PROPERTY_TRACE_SWITCH);
+@@ -1218,8 +1239,9 @@ public class BrokerOuterAPI {
+ /**
+ * Broker try to elect itself as a master in broker set
+ */
+- public Pair<ElectMasterResponseHeader, Set<Long>> brokerElect(String controllerAddress, String clusterName, String brokerName,
+- Long brokerId) throws Exception {
++ public Pair<ElectMasterResponseHeader, Set<Long>> brokerElect(String controllerAddress, String clusterName,
++ String brokerName,
++ Long brokerId) throws Exception {
+
+ final ElectMasterRequestHeader requestHeader = ElectMasterRequestHeader.ofBrokerTrigger(clusterName, brokerName, brokerId);
+ RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CONTROLLER_ELECT_MASTER, requestHeader);
+@@ -1237,7 +1259,8 @@ public class BrokerOuterAPI {
+ throw new MQBrokerException(response.getCode(), response.getRemark());
+ }
+
+- public GetNextBrokerIdResponseHeader getNextBrokerId(final String clusterName, final String brokerName, final String controllerAddress) throws Exception {
++ public GetNextBrokerIdResponseHeader getNextBrokerId(final String clusterName, final String brokerName,
++ final String controllerAddress) throws Exception {
+ final GetNextBrokerIdRequestHeader requestHeader = new GetNextBrokerIdRequestHeader(clusterName, brokerName);
+ final RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CONTROLLER_GET_NEXT_BROKER_ID, requestHeader);
+ final RemotingCommand response = this.remotingClient.invokeSync(controllerAddress, request, 3000);
+@@ -1248,7 +1271,8 @@ public class BrokerOuterAPI {
+ throw new MQBrokerException(response.getCode(), response.getRemark());
+ }
+
+- public ApplyBrokerIdResponseHeader applyBrokerId(final String clusterName, final String brokerName, final Long brokerId, final String registerCheckCode, final String controllerAddress) throws Exception {
++ public ApplyBrokerIdResponseHeader applyBrokerId(final String clusterName, final String brokerName,
++ final Long brokerId, final String registerCheckCode, final String controllerAddress) throws Exception {
+ final ApplyBrokerIdRequestHeader requestHeader = new ApplyBrokerIdRequestHeader(clusterName, brokerName, brokerId, registerCheckCode);
+ final RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CONTROLLER_APPLY_BROKER_ID, requestHeader);
+ final RemotingCommand response = this.remotingClient.invokeSync(controllerAddress, request, 3000);
+@@ -1259,7 +1283,9 @@ public class BrokerOuterAPI {
+ throw new MQBrokerException(response.getCode(), response.getRemark());
+ }
+
+- public Pair<RegisterBrokerToControllerResponseHeader, Set<Long>> registerBrokerToController(final String clusterName, final String brokerName, final Long brokerId, final String brokerAddress, final String controllerAddress) throws Exception {
++ public Pair<RegisterBrokerToControllerResponseHeader, Set<Long>> registerBrokerToController(
++ final String clusterName, final String brokerName, final Long brokerId, final String brokerAddress,
++ final String controllerAddress) throws Exception {
+ final RegisterBrokerToControllerRequestHeader requestHeader = new RegisterBrokerToControllerRequestHeader(clusterName, brokerName, brokerId, brokerAddress);
+ final RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CONTROLLER_REGISTER_BROKER, requestHeader);
+ final RemotingCommand response = this.remotingClient.invokeSync(controllerAddress, request, 3000);
+@@ -1355,16 +1381,25 @@ public class BrokerOuterAPI {
+
+ RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, requestHeader);
+ CompletableFuture<PullResult> pullResultFuture = new CompletableFuture<>();
+- this.remotingClient.invokeAsync(brokerAddr, request, timeoutMillis, responseFuture -> {
+- if (responseFuture.getCause() != null) {
+- pullResultFuture.complete(new PullResult(PullStatus.NO_MATCHED_MSG, -1, -1, -1, new ArrayList<>()));
+- return;
++ this.remotingClient.invokeAsync(brokerAddr, request, timeoutMillis, new InvokeCallback() {
++ @Override
++ public void operationComplete(ResponseFuture responseFuture) {
++
+ }
+- try {
+- PullResultExt pullResultExt = this.processPullResponse(responseFuture.getResponseCommand(), brokerAddr);
+- this.processPullResult(pullResultExt, brokerName, queueId);
+- pullResultFuture.complete(pullResultExt);
+- } catch (Exception e) {
++
++ @Override
++ public void operationSucceed(RemotingCommand response) {
++ try {
++ PullResultExt pullResultExt = processPullResponse(response, brokerAddr);
++ processPullResult(pullResultExt, brokerName, queueId);
++ pullResultFuture.complete(pullResultExt);
++ } catch (Exception e) {
++ pullResultFuture.complete(new PullResult(PullStatus.NO_MATCHED_MSG, -1, -1, -1, new ArrayList<>()));
++ }
++ }
++
++ @Override
++ public void operationFail(Throwable throwable) {
+ pullResultFuture.complete(new PullResult(PullStatus.NO_MATCHED_MSG, -1, -1, -1, new ArrayList<>()));
+ }
+ });
+diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/MQAdminImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/MQAdminImpl.java
+index 1ef3a9483..83835bd3d 100644
+--- a/client/src/main/java/org/apache/rocketmq/client/impl/MQAdminImpl.java
++++ b/client/src/main/java/org/apache/rocketmq/client/impl/MQAdminImpl.java
+@@ -44,6 +44,8 @@ import org.apache.rocketmq.common.message.MessageExt;
+ import org.apache.rocketmq.common.message.MessageId;
+ import org.apache.rocketmq.common.message.MessageQueue;
+ import org.apache.rocketmq.common.utils.NetworkUtil;
++import org.apache.rocketmq.logging.org.slf4j.Logger;
++import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+ import org.apache.rocketmq.remoting.InvokeCallback;
+ import org.apache.rocketmq.remoting.exception.RemotingCommandException;
+ import org.apache.rocketmq.remoting.exception.RemotingException;
+@@ -55,8 +57,6 @@ import org.apache.rocketmq.remoting.protocol.header.QueryMessageRequestHeader;
+ import org.apache.rocketmq.remoting.protocol.header.QueryMessageResponseHeader;
+ import org.apache.rocketmq.remoting.protocol.route.BrokerData;
+ import org.apache.rocketmq.remoting.protocol.route.TopicRouteData;
+-import org.apache.rocketmq.logging.org.slf4j.Logger;
+-import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+
+ public class MQAdminImpl {
+
+@@ -357,44 +357,51 @@ public class MQAdminImpl {
+ new InvokeCallback() {
+ @Override
+ public void operationComplete(ResponseFuture responseFuture) {
++
++ }
++
++ @Override
++ public void operationSucceed(RemotingCommand response) {
+ try {
+- RemotingCommand response = responseFuture.getResponseCommand();
+- if (response != null) {
+- switch (response.getCode()) {
+- case ResponseCode.SUCCESS: {
+- QueryMessageResponseHeader responseHeader = null;
+- try {
+- responseHeader =
+- (QueryMessageResponseHeader) response
+- .decodeCommandCustomHeader(QueryMessageResponseHeader.class);
+- } catch (RemotingCommandException e) {
+- log.error("decodeCommandCustomHeader exception", e);
+- return;
+- }
+-
+- List<MessageExt> wrappers =
+- MessageDecoder.decodes(ByteBuffer.wrap(response.getBody()), true);
+-
+- QueryResult qr = new QueryResult(responseHeader.getIndexLastUpdateTimestamp(), wrappers);
+- try {
+- lock.writeLock().lock();
+- queryResultList.add(qr);
+- } finally {
+- lock.writeLock().unlock();
+- }
+- break;
++ switch (response.getCode()) {
++ case ResponseCode.SUCCESS: {
++ QueryMessageResponseHeader responseHeader = null;
++ try {
++ responseHeader =
++ (QueryMessageResponseHeader) response
++ .decodeCommandCustomHeader(QueryMessageResponseHeader.class);
++ } catch (RemotingCommandException e) {
++ log.error("decodeCommandCustomHeader exception", e);
++ return;
+ }
+- default:
+- log.warn("getResponseCommand failed, {} {}", response.getCode(), response.getRemark());
+- break;
++
++ List<MessageExt> wrappers =
++ MessageDecoder.decodes(ByteBuffer.wrap(response.getBody()), true);
++
++ QueryResult qr = new QueryResult(responseHeader.getIndexLastUpdateTimestamp(), wrappers);
++ try {
++ lock.writeLock().lock();
++ queryResultList.add(qr);
++ } finally {
++ lock.writeLock().unlock();
++ }
++ break;
+ }
+- } else {
+- log.warn("getResponseCommand return null");
++ default:
++ log.warn("getResponseCommand failed, {} {}", response.getCode(), response.getRemark());
++ break;
+ }
++
+ } finally {
+ countDownLatch.countDown();
+ }
+ }
++
++ @Override
++ public void operationFail(Throwable throwable) {
++ log.error("queryMessage error, requestHeader={}", requestHeader);
++ countDownLatch.countDown();
++ }
+ }, isUniqKey);
+ } catch (Exception e) {
+ log.warn("queryMessage exception", e);
+diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java
+index 3201a493f..2407e5737 100644
+--- a/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java
++++ b/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java
+@@ -33,7 +33,6 @@ import java.util.concurrent.atomic.AtomicInteger;
+ import org.apache.commons.lang3.StringUtils;
+ import org.apache.rocketmq.client.ClientConfig;
+ import org.apache.rocketmq.client.Validators;
+-import org.apache.rocketmq.client.common.ClientErrorCode;
+ import org.apache.rocketmq.client.consumer.AckCallback;
+ import org.apache.rocketmq.client.consumer.AckResult;
+ import org.apache.rocketmq.client.consumer.AckStatus;
+@@ -653,10 +652,13 @@ public class MQClientAPIImpl implements NameServerUpdateCallback {
+ this.remotingClient.invokeAsync(addr, request, timeoutMillis, new InvokeCallback() {
+ @Override
+ public void operationComplete(ResponseFuture responseFuture) {
+- long cost = System.currentTimeMillis() - beginStartTime;
+- RemotingCommand response = responseFuture.getResponseCommand();
+- if (null == sendCallback && response != null) {
+
++ }
++
++ @Override
++ public void operationSucceed(RemotingCommand response) {
++ long cost = System.currentTimeMillis() - beginStartTime;
++ if (null == sendCallback) {
+ try {
+ SendResult sendResult = MQClientAPIImpl.this.processSendResponse(brokerName, msg, response, addr);
+ if (context != null && sendResult != null) {
+@@ -666,46 +668,47 @@ public class MQClientAPIImpl implements NameServerUpdateCallback {
+ } catch (Throwable e) {
+ }
+
+- producer.updateFaultItem(brokerName, System.currentTimeMillis() - responseFuture.getBeginTimestamp(), false, true);
++ producer.updateFaultItem(brokerName, System.currentTimeMillis() - beginStartTime, false, true);
+ return;
+ }
+
+- if (response != null) {
++ try {
++ SendResult sendResult = MQClientAPIImpl.this.processSendResponse(brokerName, msg, response, addr);
++ assert sendResult != null;
++ if (context != null) {
++ context.setSendResult(sendResult);
++ context.getProducer().executeSendMessageHookAfter(context);
++ }
++
+ try {
+- SendResult sendResult = MQClientAPIImpl.this.processSendResponse(brokerName, msg, response, addr);
+- assert sendResult != null;
+- if (context != null) {
+- context.setSendResult(sendResult);
+- context.getProducer().executeSendMessageHookAfter(context);
+- }
++ sendCallback.onSuccess(sendResult);
++ } catch (Throwable e) {
++ }
+
+- try {
+- sendCallback.onSuccess(sendResult);
+- } catch (Throwable e) {
+- }
++ producer.updateFaultItem(brokerName, System.currentTimeMillis() - beginStartTime, false, true);
++ } catch (Exception e) {
++ producer.updateFaultItem(brokerName, System.currentTimeMillis() - beginStartTime, true, true);
++ onExceptionImpl(brokerName, msg, timeoutMillis - cost, request, sendCallback, topicPublishInfo, instance,
++ retryTimesWhenSendFailed, times, e, context, false, producer);
++ }
++ }
+
+- producer.updateFaultItem(brokerName, System.currentTimeMillis() - responseFuture.getBeginTimestamp(), false, true);
+- } catch (Exception e) {
+- producer.updateFaultItem(brokerName, System.currentTimeMillis() - responseFuture.getBeginTimestamp(), true, true);
+- onExceptionImpl(brokerName, msg, timeoutMillis - cost, request, sendCallback, topicPublishInfo, instance,
+- retryTimesWhenSendFailed, times, e, context, false, producer);
+- }
++ @Override
++ public void operationFail(Throwable throwable) {
++ producer.updateFaultItem(brokerName, System.currentTimeMillis() - beginStartTime, true, true);
++ long cost = System.currentTimeMillis() - beginStartTime;
++ if (throwable instanceof RemotingSendRequestException) {
++ MQClientException ex = new MQClientException("send request failed", throwable);
++ onExceptionImpl(brokerName, msg, timeoutMillis - cost, request, sendCallback, topicPublishInfo, instance,
++ retryTimesWhenSendFailed, times, ex, context, true, producer);
++ } else if (throwable instanceof RemotingTimeoutException) {
++ MQClientException ex = new MQClientException("wait response timeout, cost=" + cost, throwable);
++ onExceptionImpl(brokerName, msg, timeoutMillis - cost, request, sendCallback, topicPublishInfo, instance,
++ retryTimesWhenSendFailed, times, ex, context, true, producer);
+ } else {
+- producer.updateFaultItem(brokerName, System.currentTimeMillis() - responseFuture.getBeginTimestamp(), true, true);
+- if (!responseFuture.isSendRequestOK()) {
+- MQClientException ex = new MQClientException("send request failed", responseFuture.getCause());
+- onExceptionImpl(brokerName, msg, timeoutMillis - cost, request, sendCallback, topicPublishInfo, instance,
+- retryTimesWhenSendFailed, times, ex, context, true, producer);
+- } else if (responseFuture.isTimeout()) {
+- MQClientException ex = new MQClientException("wait response timeout " + responseFuture.getTimeoutMillis() + "ms",
+- responseFuture.getCause());
+- onExceptionImpl(brokerName, msg, timeoutMillis - cost, request, sendCallback, topicPublishInfo, instance,
+- retryTimesWhenSendFailed, times, ex, context, true, producer);
+- } else {
+- MQClientException ex = new MQClientException("unknow reseaon", responseFuture.getCause());
+- onExceptionImpl(brokerName, msg, timeoutMillis - cost, request, sendCallback, topicPublishInfo, instance,
+- retryTimesWhenSendFailed, times, ex, context, true, producer);
+- }
++ MQClientException ex = new MQClientException("unknow reseaon", throwable);
++ onExceptionImpl(brokerName, msg, timeoutMillis - cost, request, sendCallback, topicPublishInfo, instance,
++ retryTimesWhenSendFailed, times, ex, context, true, producer);
+ }
+ }
+ });
+@@ -857,30 +860,25 @@ public class MQClientAPIImpl implements NameServerUpdateCallback {
+ final long timeoutMillis, final PopCallback popCallback
+ ) throws RemotingException, InterruptedException {
+ final RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.POP_MESSAGE, requestHeader);
+- this.remotingClient.invokeAsync(addr, request, timeoutMillis, new BaseInvokeCallback(MQClientAPIImpl.this) {
++ this.remotingClient.invokeAsync(addr, request, timeoutMillis, new InvokeCallback() {
+ @Override
+- public void onComplete(ResponseFuture responseFuture) {
+- RemotingCommand response = responseFuture.getResponseCommand();
+- if (response != null) {
+- try {
+- PopResult
+- popResult = MQClientAPIImpl.this.processPopResponse(brokerName, response, requestHeader.getTopic(), requestHeader);
+- assert popResult != null;
+- popCallback.onSuccess(popResult);
+- } catch (Exception e) {
+- popCallback.onException(e);
+- }
+- } else {
+- if (!responseFuture.isSendRequestOK()) {
+- popCallback.onException(new MQClientException(ClientErrorCode.CONNECT_BROKER_EXCEPTION, "send request failed to " + addr + ". Request: " + request, responseFuture.getCause()));
+- } else if (responseFuture.isTimeout()) {
+- popCallback.onException(new MQClientException(ClientErrorCode.ACCESS_BROKER_TIMEOUT, "wait response from " + addr + " timeout :" + responseFuture.getTimeoutMillis() + "ms" + ". Request: " + request,
+- responseFuture.getCause()));
+- } else {
+- popCallback.onException(new MQClientException("unknown reason. addr: " + addr + ", timeoutMillis: " + timeoutMillis + ". Request: " + request, responseFuture.getCause()));
+- }
++ public void operationComplete(ResponseFuture responseFuture) {
++
++ }
++
++ @Override
++ public void operationSucceed(RemotingCommand response) {
++ try {
++ PopResult popResult = MQClientAPIImpl.this.processPopResponse(brokerName, response, requestHeader.getTopic(), requestHeader);
++ popCallback.onSuccess(popResult);
++ } catch (Exception e) {
++ popCallback.onException(e);
+ }
+ }
++ @Override
++ public void operationFail(Throwable throwable) {
++ popCallback.onException(throwable);
++ }
+ });
+ }
+
+@@ -959,34 +957,26 @@ public class MQClientAPIImpl implements NameServerUpdateCallback {
+ request.setBody(requestBody.encode());
+ }
+ }
+- this.remotingClient.invokeAsync(addr, request, timeOut, new BaseInvokeCallback(MQClientAPIImpl.this) {
++ this.remotingClient.invokeAsync(addr, request, timeOut, new InvokeCallback() {
++ @Override
++ public void operationComplete(ResponseFuture responseFuture) {
++
++ }
+
+ @Override
+- public void onComplete(ResponseFuture responseFuture) {
+- RemotingCommand response = responseFuture.getResponseCommand();
+- if (response != null) {
+- try {
+- AckResult ackResult = new AckResult();
+- if (ResponseCode.SUCCESS == response.getCode()) {
+- ackResult.setStatus(AckStatus.OK);
+- } else {
+- ackResult.setStatus(AckStatus.NO_EXIST);
+- }
+- ackCallback.onSuccess(ackResult);
+- } catch (Exception e) {
+- ackCallback.onException(e);
+- }
++ public void operationSucceed(RemotingCommand response) {
++ AckResult ackResult = new AckResult();
++ if (ResponseCode.SUCCESS == response.getCode()) {
++ ackResult.setStatus(AckStatus.OK);
+ } else {
+- if (!responseFuture.isSendRequestOK()) {
+- ackCallback.onException(new MQClientException(ClientErrorCode.CONNECT_BROKER_EXCEPTION, "send request failed to " + addr + ". Request: " + request, responseFuture.getCause()));
+- } else if (responseFuture.isTimeout()) {
+- ackCallback.onException(new MQClientException(ClientErrorCode.ACCESS_BROKER_TIMEOUT, "wait response from " + addr + " timeout :" + responseFuture.getTimeoutMillis() + "ms" + ". Request: " + request,
+- responseFuture.getCause()));
+- } else {
+- ackCallback.onException(new MQClientException("unknown reason. addr: " + addr + ", timeoutMillis: " + timeOut + ". Request: " + request, responseFuture.getCause()));
+- }
++ ackResult.setStatus(AckStatus.NO_EXIST);
+ }
++ ackCallback.onSuccess(ackResult);
++ }
+
++ @Override
++ public void operationFail(Throwable throwable) {
++ ackCallback.onException(throwable);
+ }
+ });
+ }
+@@ -999,39 +989,37 @@ public class MQClientAPIImpl implements NameServerUpdateCallback {
+ final AckCallback ackCallback
+ ) throws RemotingException, MQBrokerException, InterruptedException {
+ final RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CHANGE_MESSAGE_INVISIBLETIME, requestHeader);
+- this.remotingClient.invokeAsync(addr, request, timeoutMillis, new BaseInvokeCallback(MQClientAPIImpl.this) {
++ this.remotingClient.invokeAsync(addr, request, timeoutMillis, new InvokeCallback() {
+ @Override
+- public void onComplete(ResponseFuture responseFuture) {
+- RemotingCommand response = responseFuture.getResponseCommand();
+- if (response != null) {
+- try {
+- ChangeInvisibleTimeResponseHeader responseHeader = (ChangeInvisibleTimeResponseHeader) response.decodeCommandCustomHeader(ChangeInvisibleTimeResponseHeader.class);
+- AckResult ackResult = new AckResult();
+- if (ResponseCode.SUCCESS == response.getCode()) {
+- ackResult.setStatus(AckStatus.OK);
+- ackResult.setPopTime(responseHeader.getPopTime());
+- ackResult.setExtraInfo(ExtraInfoUtil
+- .buildExtraInfo(requestHeader.getOffset(), responseHeader.getPopTime(), responseHeader.getInvisibleTime(),
+- responseHeader.getReviveQid(), requestHeader.getTopic(), brokerName, requestHeader.getQueueId()) + MessageConst.KEY_SEPARATOR
+- + requestHeader.getOffset());
+- } else {
+- ackResult.setStatus(AckStatus.NO_EXIST);
+- }
+- ackCallback.onSuccess(ackResult);
+- } catch (Exception e) {
+- ackCallback.onException(e);
+- }
+- } else {
+- if (!responseFuture.isSendRequestOK()) {
+- ackCallback.onException(new MQClientException(ClientErrorCode.CONNECT_BROKER_EXCEPTION, "send request failed to " + addr + ". Request: " + request, responseFuture.getCause()));
+- } else if (responseFuture.isTimeout()) {
+- ackCallback.onException(new MQClientException(ClientErrorCode.ACCESS_BROKER_TIMEOUT, "wait response from " + addr + " timeout :" + responseFuture.getTimeoutMillis() + "ms" + ". Request: " + request,
+- responseFuture.getCause()));
++ public void operationComplete(ResponseFuture responseFuture) {
++
++ }
++
++ @Override
++ public void operationSucceed(RemotingCommand response) {
++ try {
++ ChangeInvisibleTimeResponseHeader responseHeader = (ChangeInvisibleTimeResponseHeader) response.decodeCommandCustomHeader(ChangeInvisibleTimeResponseHeader.class);
++ AckResult ackResult = new AckResult();
++ if (ResponseCode.SUCCESS == response.getCode()) {
++ ackResult.setStatus(AckStatus.OK);
++ ackResult.setPopTime(responseHeader.getPopTime());
++ ackResult.setExtraInfo(ExtraInfoUtil
++ .buildExtraInfo(requestHeader.getOffset(), responseHeader.getPopTime(), responseHeader.getInvisibleTime(),
++ responseHeader.getReviveQid(), requestHeader.getTopic(), brokerName, requestHeader.getQueueId()) + MessageConst.KEY_SEPARATOR
++ + requestHeader.getOffset());
+ } else {
+- ackCallback.onException(new MQClientException("unknown reason. addr: " + addr + ", timeoutMillis: " + timeoutMillis + ". Request: " + request, responseFuture.getCause()));
++ ackResult.setStatus(AckStatus.NO_EXIST);
+ }
++ ackCallback.onSuccess(ackResult);
++ } catch (Exception e) {
++ ackCallback.onException(e);
+ }
+ }
++
++ @Override
++ public void operationFail(Throwable throwable) {
++ ackCallback.onException(throwable);
++ }
+ });
+ }
+
+@@ -1044,26 +1032,23 @@ public class MQClientAPIImpl implements NameServerUpdateCallback {
+ this.remotingClient.invokeAsync(addr, request, timeoutMillis, new InvokeCallback() {
+ @Override
+ public void operationComplete(ResponseFuture responseFuture) {
+- RemotingCommand response = responseFuture.getResponseCommand();
+- if (response != null) {
+- try {
+- PullResult pullResult = MQClientAPIImpl.this.processPullResponse(response, addr);
+- assert pullResult != null;
+- pullCallback.onSuccess(pullResult);
+- } catch (Exception e) {
+- pullCallback.onException(e);
+- }
+- } else {
+- if (!responseFuture.isSendRequestOK()) {
+- pullCallback.onException(new MQClientException(ClientErrorCode.CONNECT_BROKER_EXCEPTION, "send request failed to " + addr + ". Request: " + request, responseFuture.getCause()));
+- } else if (responseFuture.isTimeout()) {
+- pullCallback.onException(new MQClientException(ClientErrorCode.ACCESS_BROKER_TIMEOUT, "wait response from " + addr + " timeout :" + responseFuture.getTimeoutMillis() + "ms" + ". Request: " + request,
+- responseFuture.getCause()));
+- } else {
+- pullCallback.onException(new MQClientException("unknown reason. addr: " + addr + ", timeoutMillis: " + timeoutMillis + ". Request: " + request, responseFuture.getCause()));
+- }
++
++ }
++
++ @Override
++ public void operationSucceed(RemotingCommand response) {
++ try {
++ PullResult pullResult = MQClientAPIImpl.this.processPullResponse(response, addr);
++ pullCallback.onSuccess(pullResult);
++ } catch (Exception e) {
++ pullCallback.onException(e);
+ }
+ }
++
++ @Override
++ public void operationFail(Throwable throwable) {
++ pullCallback.onException(throwable);
++ }
+ });
+ }
+
+diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/mqclient/MQClientAPIExt.java b/client/src/main/java/org/apache/rocketmq/client/impl/mqclient/MQClientAPIExt.java
+index d7c8ef8d9..f3102e175 100644
+--- a/client/src/main/java/org/apache/rocketmq/client/impl/mqclient/MQClientAPIExt.java
++++ b/client/src/main/java/org/apache/rocketmq/client/impl/mqclient/MQClientAPIExt.java
+@@ -30,7 +30,6 @@ import org.apache.rocketmq.client.consumer.PullCallback;
+ import org.apache.rocketmq.client.consumer.PullResult;
+ import org.apache.rocketmq.client.consumer.PullStatus;
+ import org.apache.rocketmq.client.exception.MQBrokerException;
+-import org.apache.rocketmq.client.exception.MQClientException;
+ import org.apache.rocketmq.client.exception.OffsetNotFoundException;
+ import org.apache.rocketmq.client.impl.ClientRemotingProcessor;
+ import org.apache.rocketmq.client.impl.CommunicationMode;
+@@ -47,6 +46,7 @@ import org.apache.rocketmq.common.message.MessageExt;
+ import org.apache.rocketmq.common.message.MessageQueue;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
++import org.apache.rocketmq.remoting.InvokeCallback;
+ import org.apache.rocketmq.remoting.RPCHook;
+ import org.apache.rocketmq.remoting.exception.RemotingCommandException;
+ import org.apache.rocketmq.remoting.netty.NettyClientConfig;
+@@ -106,19 +106,6 @@ public class MQClientAPIExt extends MQClientAPIImpl {
+ return false;
+ }
+
+- protected static MQClientException processNullResponseErr(ResponseFuture responseFuture) {
+- MQClientException ex;
+- if (!responseFuture.isSendRequestOK()) {
+- ex = new MQClientException("send request failed", responseFuture.getCause());
+- } else if (responseFuture.isTimeout()) {
+- ex = new MQClientException("wait response timeout " + responseFuture.getTimeoutMillis() + "ms",
+- responseFuture.getCause());
+- } else {
+- ex = new MQClientException("unknown reason", responseFuture.getCause());
+- }
+- return ex;
+- }
+-
+ public CompletableFuture<Void> sendHeartbeatOneway(
+ String brokerAddr,
+ HeartbeatData heartbeatData,
+@@ -146,24 +133,15 @@ public class MQClientAPIExt extends MQClientAPIImpl {
+ request.setLanguage(clientConfig.getLanguage());
+ request.setBody(heartbeatData.encode());
+
+- CompletableFuture<Integer> future = new CompletableFuture<>();
+- try {
+- this.getRemotingClient().invokeAsync(brokerAddr, request, timeoutMillis, responseFuture -> {
+- RemotingCommand response = responseFuture.getResponseCommand();
+- if (response != null) {
+- if (ResponseCode.SUCCESS == response.getCode()) {
+- future.complete(response.getVersion());
+- } else {
+- future.completeExceptionally(new MQBrokerException(response.getCode(), response.getRemark(), brokerAddr));
+- }
+- } else {
+- future.completeExceptionally(processNullResponseErr(responseFuture));
+- }
+- });
+- } catch (Throwable t) {
+- future.completeExceptionally(t);
+- }
+- return future;
++ return this.getRemotingClient().invoke(brokerAddr, request, timeoutMillis).thenCompose(response -> {
++ CompletableFuture<Integer> future0 = new CompletableFuture<>();
++ if (ResponseCode.SUCCESS == response.getCode()) {
++ future0.complete(response.getVersion());
++ } else {
++ future0.completeExceptionally(new MQBrokerException(response.getCode(), response.getRemark(), brokerAddr));
++ }
++ return future0;
++ });
+ }
+
+ public CompletableFuture<SendResult> sendMessageAsync(
+@@ -177,24 +155,15 @@ public class MQClientAPIExt extends MQClientAPIImpl {
+ RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE_V2, requestHeaderV2);
+ request.setBody(msg.getBody());
+
+- CompletableFuture<SendResult> future = new CompletableFuture<>();
+- try {
+- this.getRemotingClient().invokeAsync(brokerAddr, request, timeoutMillis, responseFuture -> {
+- RemotingCommand response = responseFuture.getResponseCommand();
+- if (response != null) {
+- try {
+- future.complete(this.processSendResponse(brokerName, msg, response, brokerAddr));
+- } catch (Exception e) {
+- future.completeExceptionally(e);
+- }
+- } else {
+- future.completeExceptionally(processNullResponseErr(responseFuture));
+- }
+- });
+- } catch (Throwable t) {
+- future.completeExceptionally(t);
+- }
+- return future;
++ return this.getRemotingClient().invoke(brokerAddr, request, timeoutMillis).thenCompose(response -> {
++ CompletableFuture<SendResult> future0 = new CompletableFuture<>();
++ try {
++ future0.complete(this.processSendResponse(brokerName, msg, response, brokerAddr));
++ } catch (Exception e) {
++ future0.completeExceptionally(e);
++ }
++ return future0;
++ });
+ }
+
+ public CompletableFuture<SendResult> sendMessageAsync(
+@@ -216,17 +185,14 @@ public class MQClientAPIExt extends MQClientAPIImpl {
+ msgBatch.setBody(body);
+
+ request.setBody(body);
+- this.getRemotingClient().invokeAsync(brokerAddr, request, timeoutMillis, responseFuture -> {
+- RemotingCommand response = responseFuture.getResponseCommand();
+- if (response != null) {
+- try {
+- future.complete(this.processSendResponse(brokerName, msgBatch, response, brokerAddr));
+- } catch (Exception e) {
+- future.completeExceptionally(e);
+- }
+- } else {
+- future.completeExceptionally(processNullResponseErr(responseFuture));
++ return this.getRemotingClient().invoke(brokerAddr, request, timeoutMillis).thenCompose(response -> {
++ CompletableFuture<SendResult> future0 = new CompletableFuture<>();
++ try {
++ future0.complete(processSendResponse(brokerName, msgBatch, response, brokerAddr));
++ } catch (Exception e) {
++ future0.completeExceptionally(e);
+ }
++ return future0;
+ });
+ } catch (Throwable t) {
+ future.completeExceptionally(t);
+@@ -240,21 +206,7 @@ public class MQClientAPIExt extends MQClientAPIImpl {
+ long timeoutMillis
+ ) {
+ RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CONSUMER_SEND_MSG_BACK, requestHeader);
+-
+- CompletableFuture<RemotingCommand> future = new CompletableFuture<>();
+- try {
+- this.getRemotingClient().invokeAsync(brokerAddr, request, timeoutMillis, responseFuture -> {
+- RemotingCommand response = responseFuture.getResponseCommand();
+- if (response != null) {
+- future.complete(response);
+- } else {
+- future.completeExceptionally(processNullResponseErr(responseFuture));
+- }
+- });
+- } catch (Throwable t) {
+- future.completeExceptionally(t);
+- }
+- return future;
++ return this.getRemotingClient().invoke(brokerAddr, request, timeoutMillis);
+ }
+
+ public CompletableFuture<PopResult> popMessageAsync(
+@@ -402,38 +354,31 @@ public class MQClientAPIExt extends MQClientAPIImpl {
+ QueryConsumerOffsetRequestHeader requestHeader,
+ long timeoutMillis
+ ) {
+- CompletableFuture<Long> future = new CompletableFuture<>();
+- try {
+- RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.QUERY_CONSUMER_OFFSET, requestHeader);
+- this.getRemotingClient().invokeAsync(brokerAddr, request, timeoutMillis, responseFuture -> {
+- RemotingCommand response = responseFuture.getResponseCommand();
+- if (response != null) {
+- switch (response.getCode()) {
+- case ResponseCode.SUCCESS: {
+- try {
+- QueryConsumerOffsetResponseHeader responseHeader =
+- (QueryConsumerOffsetResponseHeader) response.decodeCommandCustomHeader(QueryConsumerOffsetResponseHeader.class);
+- future.complete(responseHeader.getOffset());
+- } catch (RemotingCommandException e) {
+- future.completeExceptionally(e);
+- }
+- break;
+- }
+- case ResponseCode.QUERY_NOT_FOUND: {
+- future.completeExceptionally(new OffsetNotFoundException(response.getCode(), response.getRemark(), brokerAddr));
+- break;
+- }
+- default:
+- break;
++ RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.QUERY_CONSUMER_OFFSET, requestHeader);
++ return this.getRemotingClient().invoke(brokerAddr, request, timeoutMillis).thenCompose(response -> {
++ CompletableFuture<Long> future0 = new CompletableFuture<>();
++ switch (response.getCode()) {
++ case ResponseCode.SUCCESS: {
++ try {
++ QueryConsumerOffsetResponseHeader responseHeader =
++ (QueryConsumerOffsetResponseHeader) response.decodeCommandCustomHeader(QueryConsumerOffsetResponseHeader.class);
++ future0.complete(responseHeader.getOffset());
++ } catch (RemotingCommandException e) {
++ future0.completeExceptionally(e);
+ }
+- } else {
+- future.completeExceptionally(processNullResponseErr(responseFuture));
++ break;
+ }
+- });
+- } catch (Throwable t) {
+- future.completeExceptionally(t);
+- }
+- return future;
++ case ResponseCode.QUERY_NOT_FOUND: {
++ future0.completeExceptionally(new OffsetNotFoundException(response.getCode(), response.getRemark(), brokerAddr));
++ break;
++ }
++ default: {
++ future0.completeExceptionally(new MQBrokerException(response.getCode(), response.getRemark()));
++ break;
++ }
++ }
++ return future0;
++ });
+ }
+
+ public CompletableFuture<Void> updateConsumerOffsetOneWay(
+@@ -461,9 +406,14 @@ public class MQClientAPIExt extends MQClientAPIImpl {
+
+ CompletableFuture<List<String>> future = new CompletableFuture<>();
+ try {
+- this.getRemotingClient().invokeAsync(brokerAddr, request, timeoutMillis, responseFuture -> {
+- RemotingCommand response = responseFuture.getResponseCommand();
+- if (response != null) {
++ this.getRemotingClient().invokeAsync(brokerAddr, request, timeoutMillis, new InvokeCallback() {
++ @Override
++ public void operationComplete(ResponseFuture responseFuture) {
++
++ }
++
++ @Override
++ public void operationSucceed(RemotingCommand response) {
+ switch (response.getCode()) {
+ case ResponseCode.SUCCESS: {
+ if (response.getBody() != null) {
+@@ -485,8 +435,11 @@ public class MQClientAPIExt extends MQClientAPIImpl {
+ break;
+ }
+ future.completeExceptionally(new MQBrokerException(response.getCode(), response.getRemark()));
+- } else {
+- future.completeExceptionally(processNullResponseErr(responseFuture));
++ }
++
++ @Override
++ public void operationFail(Throwable throwable) {
++ future.completeExceptionally(throwable);
+ }
+ });
+ } catch (Throwable t) {
+@@ -501,9 +454,14 @@ public class MQClientAPIExt extends MQClientAPIImpl {
+
+ CompletableFuture<Long> future = new CompletableFuture<>();
+ try {
+- this.getRemotingClient().invokeAsync(brokerAddr, request, timeoutMillis, responseFuture -> {
+- RemotingCommand response = responseFuture.getResponseCommand();
+- if (response != null) {
++ this.getRemotingClient().invokeAsync(brokerAddr, request, timeoutMillis, new InvokeCallback() {
++ @Override
++ public void operationComplete(ResponseFuture responseFuture) {
++
++ }
++
++ @Override
++ public void operationSucceed(RemotingCommand response) {
+ if (ResponseCode.SUCCESS == response.getCode()) {
+ try {
+ GetMaxOffsetResponseHeader responseHeader = (GetMaxOffsetResponseHeader) response.decodeCommandCustomHeader(GetMaxOffsetResponseHeader.class);
+@@ -513,8 +471,11 @@ public class MQClientAPIExt extends MQClientAPIImpl {
+ }
+ }
+ future.completeExceptionally(new MQBrokerException(response.getCode(), response.getRemark()));
+- } else {
+- future.completeExceptionally(processNullResponseErr(responseFuture));
++ }
++
++ @Override
++ public void operationFail(Throwable throwable) {
++ future.completeExceptionally(throwable);
+ }
+ });
+ } catch (Throwable t) {
+@@ -529,9 +490,14 @@ public class MQClientAPIExt extends MQClientAPIImpl {
+
+ CompletableFuture<Long> future = new CompletableFuture<>();
+ try {
+- this.getRemotingClient().invokeAsync(brokerAddr, request, timeoutMillis, responseFuture -> {
+- RemotingCommand response = responseFuture.getResponseCommand();
+- if (response != null) {
++ this.getRemotingClient().invokeAsync(brokerAddr, request, timeoutMillis, new InvokeCallback() {
++ @Override
++ public void operationComplete(ResponseFuture responseFuture) {
++
++ }
++
++ @Override
++ public void operationSucceed(RemotingCommand response) {
+ if (ResponseCode.SUCCESS == response.getCode()) {
+ try {
+ GetMinOffsetResponseHeader responseHeader = (GetMinOffsetResponseHeader) response.decodeCommandCustomHeader(GetMinOffsetResponseHeader.class);
+@@ -541,8 +507,11 @@ public class MQClientAPIExt extends MQClientAPIImpl {
+ }
+ }
+ future.completeExceptionally(new MQBrokerException(response.getCode(), response.getRemark()));
+- } else {
+- future.completeExceptionally(processNullResponseErr(responseFuture));
++ }
++
++ @Override
++ public void operationFail(Throwable throwable) {
++ future.completeExceptionally(throwable);
+ }
+ });
+ } catch (Throwable t) {
+@@ -555,57 +524,41 @@ public class MQClientAPIExt extends MQClientAPIImpl {
+ long timeoutMillis) {
+ RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.SEARCH_OFFSET_BY_TIMESTAMP, requestHeader);
+
+- CompletableFuture<Long> future = new CompletableFuture<>();
+- try {
+- this.getRemotingClient().invokeAsync(brokerAddr, request, timeoutMillis, responseFuture -> {
+- RemotingCommand response = responseFuture.getResponseCommand();
+- if (response != null) {
+- if (response.getCode() == ResponseCode.SUCCESS) {
+- try {
+- SearchOffsetResponseHeader responseHeader = (SearchOffsetResponseHeader) response.decodeCommandCustomHeader(SearchOffsetResponseHeader.class);
+- future.complete(responseHeader.getOffset());
+- } catch (Throwable t) {
+- future.completeExceptionally(t);
+- }
+- }
+- future.completeExceptionally(new MQBrokerException(response.getCode(), response.getRemark()));
+- } else {
+- future.completeExceptionally(processNullResponseErr(responseFuture));
++ return this.getRemotingClient().invoke(brokerAddr, request, timeoutMillis).thenCompose(response -> {
++ CompletableFuture<Long> future0 = new CompletableFuture<>();
++ if (response.getCode() == ResponseCode.SUCCESS) {
++ try {
++ SearchOffsetResponseHeader responseHeader = (SearchOffsetResponseHeader) response.decodeCommandCustomHeader(SearchOffsetResponseHeader.class);
++ future0.complete(responseHeader.getOffset());
++ } catch (Throwable t) {
++ future0.completeExceptionally(t);
+ }
+- });
+- } catch (Throwable t) {
+- future.completeExceptionally(t);
+- }
+- return future;
++ } else {
++ future0.completeExceptionally(new MQBrokerException(response.getCode(), response.getRemark()));
++ }
++ return future0;
++ });
+ }
+
+ public CompletableFuture<Set<MessageQueue>> lockBatchMQWithFuture(String brokerAddr,
+ LockBatchRequestBody requestBody, long timeoutMillis) {
+- CompletableFuture<Set<MessageQueue>> future = new CompletableFuture<>();
+ RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.LOCK_BATCH_MQ, null);
+ request.setBody(requestBody.encode());
+- try {
+- this.getRemotingClient().invokeAsync(brokerAddr, request, timeoutMillis, responseFuture -> {
+- RemotingCommand response = responseFuture.getResponseCommand();
+- if (response != null) {
+- if (response.getCode() == ResponseCode.SUCCESS) {
+- try {
+- LockBatchResponseBody responseBody = LockBatchResponseBody.decode(response.getBody(), LockBatchResponseBody.class);
+- Set<MessageQueue> messageQueues = responseBody.getLockOKMQSet();
+- future.complete(messageQueues);
+- } catch (Throwable t) {
+- future.completeExceptionally(t);
+- }
+- }
+- future.completeExceptionally(new MQBrokerException(response.getCode(), response.getRemark()));
+- } else {
+- future.completeExceptionally(processNullResponseErr(responseFuture));
++ return this.getRemotingClient().invoke(brokerAddr, request, timeoutMillis).thenCompose(response -> {
++ CompletableFuture<Set<MessageQueue>> future0 = new CompletableFuture<>();
++ if (response.getCode() == ResponseCode.SUCCESS) {
++ try {
++ LockBatchResponseBody responseBody = LockBatchResponseBody.decode(response.getBody(), LockBatchResponseBody.class);
++ Set<MessageQueue> messageQueues = responseBody.getLockOKMQSet();
++ future0.complete(messageQueues);
++ } catch (Throwable t) {
++ future0.completeExceptionally(t);
+ }
+- });
+- } catch (Exception e) {
+- future.completeExceptionally(e);
+- }
+- return future;
++ } else {
++ future0.completeExceptionally(new MQBrokerException(response.getCode(), response.getRemark()));
++ }
++ return future0;
++ });
+ }
+
+ public CompletableFuture<Void> unlockBatchMQOneway(String brokerAddr,
+@@ -624,25 +577,21 @@ public class MQClientAPIExt extends MQClientAPIImpl {
+
+ public CompletableFuture<Boolean> notification(String brokerAddr, NotificationRequestHeader requestHeader,
+ long timeoutMillis) {
+- CompletableFuture<Boolean> future = new CompletableFuture<>();
+ RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.NOTIFICATION, requestHeader);
+- try {
+- this.getRemotingClient().invoke(brokerAddr, request, timeoutMillis).thenAccept(response -> {
+- if (response.getCode() == ResponseCode.SUCCESS) {
+- try {
+- NotificationResponseHeader responseHeader = (NotificationResponseHeader) response.decodeCommandCustomHeader(NotificationResponseHeader.class);
+- future.complete(responseHeader.isHasMsg());
+- } catch (Throwable t) {
+- future.completeExceptionally(t);
+- }
+- } else {
+- future.completeExceptionally(new MQBrokerException(response.getCode(), response.getRemark()));
++ return this.getRemotingClient().invoke(brokerAddr, request, timeoutMillis).thenCompose(response -> {
++ CompletableFuture<Boolean> future0 = new CompletableFuture<>();
++ if (response.getCode() == ResponseCode.SUCCESS) {
++ try {
++ NotificationResponseHeader responseHeader = (NotificationResponseHeader) response.decodeCommandCustomHeader(NotificationResponseHeader.class);
++ future0.complete(responseHeader.isHasMsg());
++ } catch (Throwable t) {
++ future0.completeExceptionally(t);
+ }
+- });
+- } catch (Throwable t) {
+- future.completeExceptionally(t);
+- }
+- return future;
++ } else {
++ future0.completeExceptionally(new MQBrokerException(response.getCode(), response.getRemark()));
++ }
++ return future0;
++ });
+ }
+
+ public CompletableFuture<RemotingCommand> invoke(String brokerAddr, RemotingCommand request, long timeoutMillis) {
+diff --git a/client/src/test/java/org/apache/rocketmq/client/impl/MQClientAPIImplTest.java b/client/src/test/java/org/apache/rocketmq/client/impl/MQClientAPIImplTest.java
+index d13f2cfe4..c152d38ea 100644
+--- a/client/src/test/java/org/apache/rocketmq/client/impl/MQClientAPIImplTest.java
++++ b/client/src/test/java/org/apache/rocketmq/client/impl/MQClientAPIImplTest.java
+@@ -212,7 +212,7 @@ public class MQClientAPIImplTest {
+ RemotingCommand request = mock.getArgument(1);
+ ResponseFuture responseFuture = new ResponseFuture(null, request.getOpaque(), 3 * 1000, null, null);
+ responseFuture.setResponseCommand(createSendMessageSuccessResponse(request));
+- callback.operationComplete(responseFuture);
++ callback.operationSucceed(responseFuture.getResponseCommand());
+ return null;
+ }
+ }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any(InvokeCallback.class));
+@@ -386,7 +386,7 @@ public class MQClientAPIImplTest {
+ RemotingCommand request = mock.getArgument(1);
+ ResponseFuture responseFuture = new ResponseFuture(null, request.getOpaque(), 3 * 1000, null, null);
+ responseFuture.setResponseCommand(createSendMessageSuccessResponse(request));
+- callback.operationComplete(responseFuture);
++ callback.operationSucceed(responseFuture.getResponseCommand());
+ return null;
+ }
+ }).when(remotingClient).invokeAsync(Matchers.anyString(), Matchers.any(RemotingCommand.class), Matchers.anyLong(), Matchers.any(InvokeCallback.class));
+@@ -472,7 +472,7 @@ public class MQClientAPIImplTest {
+ message.putUserProperty("key", "value");
+ response.setBody(MessageDecoder.encode(message, false));
+ responseFuture.setResponseCommand(response);
+- callback.operationComplete(responseFuture);
++ callback.operationSucceed(responseFuture.getResponseCommand());
+ return null;
+ }
+ }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any(InvokeCallback.class));
+@@ -543,7 +543,7 @@ public class MQClientAPIImplTest {
+ message.getProperties().put(MessageConst.PROPERTY_INNER_MULTI_QUEUE_OFFSET, String.valueOf(0));
+ response.setBody(MessageDecoder.encode(message, false));
+ responseFuture.setResponseCommand(response);
+- callback.operationComplete(responseFuture);
++ callback.operationSucceed(responseFuture.getResponseCommand());
+ return null;
+ }
+ }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any(InvokeCallback.class));
+@@ -585,7 +585,7 @@ public class MQClientAPIImplTest {
+ response.setOpaque(request.getOpaque());
+ response.setCode(ResponseCode.SUCCESS);
+ responseFuture.setResponseCommand(response);
+- callback.operationComplete(responseFuture);
++ callback.operationSucceed(responseFuture.getResponseCommand());
+ return null;
+ }
+ }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any(InvokeCallback.class));
+@@ -622,7 +622,7 @@ public class MQClientAPIImplTest {
+ responseHeader.setPopTime(System.currentTimeMillis());
+ responseHeader.setInvisibleTime(10 * 1000L);
+ responseFuture.setResponseCommand(response);
+- callback.operationComplete(responseFuture);
++ callback.operationSucceed(responseFuture.getResponseCommand());
+ return null;
+ }
+ }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any(InvokeCallback.class));
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/RemotingProtocolServer.java b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/RemotingProtocolServer.java
+index fe07090d5..3227d1e1c 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/RemotingProtocolServer.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/remoting/RemotingProtocolServer.java
+@@ -26,7 +26,6 @@ import java.util.concurrent.ScheduledExecutorService;
+ import java.util.concurrent.ThreadPoolExecutor;
+ import java.util.concurrent.TimeUnit;
+ import org.apache.rocketmq.acl.AccessValidator;
+-import org.apache.rocketmq.client.exception.MQClientException;
+ import org.apache.rocketmq.common.constant.LoggerName;
+ import org.apache.rocketmq.common.future.FutureTaskExt;
+ import org.apache.rocketmq.common.thread.ThreadPoolMonitor;
+@@ -51,10 +50,12 @@ import org.apache.rocketmq.proxy.remoting.channel.RemotingChannelManager;
+ import org.apache.rocketmq.proxy.remoting.pipeline.AuthenticationPipeline;
+ import org.apache.rocketmq.proxy.remoting.pipeline.RequestPipeline;
+ import org.apache.rocketmq.remoting.ChannelEventListener;
++import org.apache.rocketmq.remoting.InvokeCallback;
+ import org.apache.rocketmq.remoting.RemotingServer;
+ import org.apache.rocketmq.remoting.netty.NettyRemotingServer;
+ import org.apache.rocketmq.remoting.netty.NettyServerConfig;
+ import org.apache.rocketmq.remoting.netty.RequestTask;
++import org.apache.rocketmq.remoting.netty.ResponseFuture;
+ import org.apache.rocketmq.remoting.netty.TlsSystemConfig;
+ import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+ import org.apache.rocketmq.remoting.protocol.RequestCode;
+@@ -239,12 +240,21 @@ public class RemotingProtocolServer implements StartAndShutdown, RemotingProxyOu
+ long timeoutMillis) {
+ CompletableFuture<RemotingCommand> future = new CompletableFuture<>();
+ try {
+- this.defaultRemotingServer.invokeAsync(channel, request, timeoutMillis, responseFuture -> {
+- if (responseFuture.getResponseCommand() == null) {
+- future.completeExceptionally(new MQClientException("response is null after send request to client", responseFuture.getCause()));
+- return;
++ this.defaultRemotingServer.invokeAsync(channel, request, timeoutMillis, new InvokeCallback() {
++ @Override
++ public void operationComplete(ResponseFuture responseFuture) {
++
++ }
++
++ @Override
++ public void operationSucceed(RemotingCommand response) {
++ future.complete(response);
++ }
++
++ @Override
++ public void operationFail(Throwable throwable) {
++ future.completeExceptionally(throwable);
+ }
+- future.complete(responseFuture.getResponseCommand());
+ });
+ } catch (Throwable t) {
+ future.completeExceptionally(t);
+diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/service/mqclient/MQClientAPIExtTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/service/mqclient/MQClientAPIExtTest.java
+index 3f3a4ae40..e2d05b0f5 100644
+--- a/proxy/src/test/java/org/apache/rocketmq/proxy/service/mqclient/MQClientAPIExtTest.java
++++ b/proxy/src/test/java/org/apache/rocketmq/proxy/service/mqclient/MQClientAPIExtTest.java
+@@ -24,6 +24,7 @@ import java.util.ArrayList;
+ import java.util.List;
+ import java.util.Set;
+ import java.util.UUID;
++import java.util.concurrent.CompletableFuture;
+ import java.util.concurrent.ThreadLocalRandom;
+ import java.util.concurrent.atomic.AtomicReference;
+ import java.util.stream.Collectors;
+@@ -85,6 +86,7 @@ import static org.mockito.ArgumentMatchers.any;
+ import static org.mockito.ArgumentMatchers.anyLong;
+ import static org.mockito.ArgumentMatchers.anyString;
+ import static org.mockito.Mockito.doAnswer;
++import static org.mockito.Mockito.doReturn;
+
+ @RunWith(MockitoJUnitRunner.class)
+ public class MQClientAPIExtTest {
+@@ -109,13 +111,9 @@ public class MQClientAPIExtTest {
+
+ @Test
+ public void testSendHeartbeatAsync() throws Exception {
+- doAnswer((Answer<Void>) mock -> {
+- InvokeCallback invokeCallback = mock.getArgument(3);
+- ResponseFuture responseFuture = new ResponseFuture(null, 0, 3000, invokeCallback, null);
+- responseFuture.putResponse(RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, ""));
+- invokeCallback.operationComplete(responseFuture);
+- return null;
+- }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any());
++ CompletableFuture<RemotingCommand> future = new CompletableFuture<>();
++ future.complete(RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, ""));
++ doReturn(future).when(remotingClient).invoke(anyString(), any(RemotingCommand.class), anyLong());
+
+ assertNotNull(mqClientAPI.sendHeartbeatAsync(BROKER_ADDR, new HeartbeatData(), TIMEOUT).get());
+ }
+@@ -123,20 +121,16 @@ public class MQClientAPIExtTest {
+ @Test
+ public void testSendMessageAsync() throws Exception {
+ AtomicReference<String> msgIdRef = new AtomicReference<>();
+- doAnswer((Answer<Void>) mock -> {
+- InvokeCallback invokeCallback = mock.getArgument(3);
+- ResponseFuture responseFuture = new ResponseFuture(null, 0, 3000, invokeCallback, null);
+- RemotingCommand response = RemotingCommand.createResponseCommand(SendMessageResponseHeader.class);
+- SendMessageResponseHeader sendMessageResponseHeader = (SendMessageResponseHeader) response.readCustomHeader();
+- sendMessageResponseHeader.setMsgId(msgIdRef.get());
+- sendMessageResponseHeader.setQueueId(0);
+- sendMessageResponseHeader.setQueueOffset(1L);
+- response.setCode(ResponseCode.SUCCESS);
+- response.makeCustomHeaderToNet();
+- responseFuture.putResponse(response);
+- invokeCallback.operationComplete(responseFuture);
+- return null;
+- }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any());
++ CompletableFuture<RemotingCommand> future = new CompletableFuture<>();
++ RemotingCommand response = RemotingCommand.createResponseCommand(SendMessageResponseHeader.class);
++ SendMessageResponseHeader sendMessageResponseHeader = (SendMessageResponseHeader) response.readCustomHeader();
++ sendMessageResponseHeader.setMsgId(msgIdRef.get());
++ sendMessageResponseHeader.setQueueId(0);
++ sendMessageResponseHeader.setQueueOffset(1L);
++ response.setCode(ResponseCode.SUCCESS);
++ response.makeCustomHeaderToNet();
++ future.complete(response);
++ doReturn(future).when(remotingClient).invoke(anyString(), any(RemotingCommand.class), anyLong());
+
+ MessageExt messageExt = createMessage();
+ msgIdRef.set(MessageClientIDSetter.getUniqID(messageExt));
+@@ -150,20 +144,16 @@ public class MQClientAPIExtTest {
+
+ @Test
+ public void testSendMessageListAsync() throws Exception {
+- doAnswer((Answer<Void>) mock -> {
+- InvokeCallback invokeCallback = mock.getArgument(3);
+- ResponseFuture responseFuture = new ResponseFuture(null, 0, 3000, invokeCallback, null);
+- RemotingCommand response = RemotingCommand.createResponseCommand(SendMessageResponseHeader.class);
+- SendMessageResponseHeader sendMessageResponseHeader = (SendMessageResponseHeader) response.readCustomHeader();
+- sendMessageResponseHeader.setMsgId("");
+- sendMessageResponseHeader.setQueueId(0);
+- sendMessageResponseHeader.setQueueOffset(1L);
+- response.setCode(ResponseCode.SUCCESS);
+- response.makeCustomHeaderToNet();
+- responseFuture.putResponse(response);
+- invokeCallback.operationComplete(responseFuture);
+- return null;
+- }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any());
++ CompletableFuture<RemotingCommand> future = new CompletableFuture<>();
++ RemotingCommand response = RemotingCommand.createResponseCommand(SendMessageResponseHeader.class);
++ SendMessageResponseHeader sendMessageResponseHeader = (SendMessageResponseHeader) response.readCustomHeader();
++ sendMessageResponseHeader.setMsgId("");
++ sendMessageResponseHeader.setQueueId(0);
++ sendMessageResponseHeader.setQueueOffset(1L);
++ response.setCode(ResponseCode.SUCCESS);
++ response.makeCustomHeaderToNet();
++ future.complete(response);
++ doReturn(future).when(remotingClient).invoke(anyString(), any(RemotingCommand.class), anyLong());
+
+ List<MessageExt> messageExtList = new ArrayList<>();
+ StringBuilder sb = new StringBuilder();
+@@ -182,13 +172,9 @@ public class MQClientAPIExtTest {
+
+ @Test
+ public void testSendMessageBackAsync() throws Exception {
+- doAnswer((Answer<Void>) mock -> {
+- InvokeCallback invokeCallback = mock.getArgument(3);
+- ResponseFuture responseFuture = new ResponseFuture(null, 0, 3000, invokeCallback, null);
+- responseFuture.putResponse(RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, ""));
+- invokeCallback.operationComplete(responseFuture);
+- return null;
+- }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any());
++ CompletableFuture<RemotingCommand> future = new CompletableFuture<>();
++ future.complete(RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, ""));
++ doReturn(future).when(remotingClient).invoke(anyString(), any(RemotingCommand.class), anyLong());
+
+ RemotingCommand remotingCommand = mqClientAPI.sendMessageBackAsync(BROKER_ADDR, new ConsumerSendMsgBackRequestHeader(), TIMEOUT)
+ .get();
+@@ -285,7 +271,7 @@ public class MQClientAPIExtTest {
+ body.setConsumerIdList(clientIds);
+ response.setBody(body.encode());
+ responseFuture.putResponse(response);
+- invokeCallback.operationComplete(responseFuture);
++ invokeCallback.operationSucceed(responseFuture.getResponseCommand());
+ return null;
+ }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any());
+
+@@ -302,7 +288,7 @@ public class MQClientAPIExtTest {
+ response.setCode(ResponseCode.SYSTEM_ERROR);
+ response.makeCustomHeaderToNet();
+ responseFuture.putResponse(response);
+- invokeCallback.operationComplete(responseFuture);
++ invokeCallback.operationSucceed(responseFuture.getResponseCommand());
+ return null;
+ }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any());
+
+@@ -322,7 +308,7 @@ public class MQClientAPIExtTest {
+ response.setCode(ResponseCode.SUCCESS);
+ response.makeCustomHeaderToNet();
+ responseFuture.putResponse(response);
+- invokeCallback.operationComplete(responseFuture);
++ invokeCallback.operationSucceed(responseFuture.getResponseCommand());
+ return null;
+ }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any());
+
+@@ -335,18 +321,15 @@ public class MQClientAPIExtTest {
+ @Test
+ public void testSearchOffsetAsync() throws Exception {
+ long offset = ThreadLocalRandom.current().nextLong();
+- doAnswer((Answer<Void>) mock -> {
+- InvokeCallback invokeCallback = mock.getArgument(3);
+- ResponseFuture responseFuture = new ResponseFuture(null, 0, 3000, invokeCallback, null);
+- RemotingCommand response = RemotingCommand.createResponseCommand(SearchOffsetResponseHeader.class);
+- SearchOffsetResponseHeader responseHeader = (SearchOffsetResponseHeader) response.readCustomHeader();
+- responseHeader.setOffset(offset);
+- response.setCode(ResponseCode.SUCCESS);
+- response.makeCustomHeaderToNet();
+- responseFuture.putResponse(response);
+- invokeCallback.operationComplete(responseFuture);
+- return null;
+- }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any());
++ CompletableFuture<RemotingCommand> future = new CompletableFuture<>();
++ RemotingCommand response = RemotingCommand.createResponseCommand(SearchOffsetResponseHeader.class);
++ SearchOffsetResponseHeader responseHeader = (SearchOffsetResponseHeader) response.readCustomHeader();
++ responseHeader.setOffset(offset);
++ response.setCode(ResponseCode.SUCCESS);
++ response.makeCustomHeaderToNet();
++ future.complete(response);
++
++ doReturn(future).when(remotingClient).invoke(anyString(), any(RemotingCommand.class), anyLong());
+
+ SearchOffsetRequestHeader requestHeader = new SearchOffsetRequestHeader();
+ requestHeader.setTopic(TOPIC);
+diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/InvokeCallback.java b/remoting/src/main/java/org/apache/rocketmq/remoting/InvokeCallback.java
+index ce78fa923..6be491745 100644
+--- a/remoting/src/main/java/org/apache/rocketmq/remoting/InvokeCallback.java
++++ b/remoting/src/main/java/org/apache/rocketmq/remoting/InvokeCallback.java
+@@ -17,7 +17,22 @@
+ package org.apache.rocketmq.remoting;
+
+ import org.apache.rocketmq.remoting.netty.ResponseFuture;
++import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+
+ public interface InvokeCallback {
++ /**
++ * This method is expected to be invoked after {@link #operationSucceed(RemotingCommand)}
++ * or {@link #operationFail(Throwable)}
++ *
++ * @param responseFuture the returned object contains response or exception
++ */
+ void operationComplete(final ResponseFuture responseFuture);
++
++ default void operationSucceed(final RemotingCommand response) {
++
++ }
++
++ default void operationFail(final Throwable throwable) {
++
++ }
+ }
+diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/RemotingClient.java b/remoting/src/main/java/org/apache/rocketmq/remoting/RemotingClient.java
+index ff0b3df95..c8389eedb 100644
+--- a/remoting/src/main/java/org/apache/rocketmq/remoting/RemotingClient.java
++++ b/remoting/src/main/java/org/apache/rocketmq/remoting/RemotingClient.java
+@@ -20,11 +20,11 @@ import java.util.List;
+ import java.util.concurrent.CompletableFuture;
+ import java.util.concurrent.ExecutorService;
+ import org.apache.rocketmq.remoting.exception.RemotingConnectException;
+-import org.apache.rocketmq.remoting.exception.RemotingException;
+ import org.apache.rocketmq.remoting.exception.RemotingSendRequestException;
+ import org.apache.rocketmq.remoting.exception.RemotingTimeoutException;
+ import org.apache.rocketmq.remoting.exception.RemotingTooMuchRequestException;
+ import org.apache.rocketmq.remoting.netty.NettyRequestProcessor;
++import org.apache.rocketmq.remoting.netty.ResponseFuture;
+ import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+
+ public interface RemotingClient extends RemotingService {
+@@ -51,18 +51,21 @@ public interface RemotingClient extends RemotingService {
+ final long timeoutMillis) {
+ CompletableFuture<RemotingCommand> future = new CompletableFuture<>();
+ try {
+- invokeAsync(addr, request, timeoutMillis, responseFuture -> {
+- RemotingCommand response = responseFuture.getResponseCommand();
+- if (response != null) {
++ invokeAsync(addr, request, timeoutMillis, new InvokeCallback() {
++
++ @Override
++ public void operationComplete(ResponseFuture responseFuture) {
++
++ }
++
++ @Override
++ public void operationSucceed(RemotingCommand response) {
+ future.complete(response);
+- } else {
+- if (!responseFuture.isSendRequestOK()) {
+- future.completeExceptionally(new RemotingSendRequestException(addr, responseFuture.getCause()));
+- } else if (responseFuture.isTimeout()) {
+- future.completeExceptionally(new RemotingTimeoutException(addr, timeoutMillis, responseFuture.getCause()));
+- } else {
+- future.completeExceptionally(new RemotingException(request.toString(), responseFuture.getCause()));
+- }
++ }
++
++ @Override
++ public void operationFail(Throwable throwable) {
++ future.completeExceptionally(throwable);
+ }
+ });
+ } catch (Throwable t) {
+diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingAbstract.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingAbstract.java
+index fce2de267..12e66f913 100644
+--- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingAbstract.java
++++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingAbstract.java
+@@ -23,20 +23,23 @@ import io.netty.handler.ssl.SslContext;
+ import io.netty.handler.ssl.SslHandler;
+ import io.netty.util.concurrent.Future;
+ import io.opentelemetry.api.common.AttributesBuilder;
+-import java.net.SocketAddress;
+ import java.util.ArrayList;
+ import java.util.HashMap;
+ import java.util.Iterator;
+ import java.util.LinkedList;
+ import java.util.List;
+ import java.util.Map.Entry;
++import java.util.concurrent.CompletableFuture;
+ import java.util.concurrent.ConcurrentHashMap;
+ import java.util.concurrent.ConcurrentMap;
++import java.util.concurrent.ExecutionException;
+ import java.util.concurrent.ExecutorService;
+ import java.util.concurrent.LinkedBlockingQueue;
+ import java.util.concurrent.RejectedExecutionException;
+ import java.util.concurrent.Semaphore;
+ import java.util.concurrent.TimeUnit;
++import java.util.concurrent.TimeoutException;
++import java.util.concurrent.atomic.AtomicReference;
+ import java.util.function.Consumer;
+ import javax.annotation.Nullable;
+ import org.apache.rocketmq.common.AbortProcessException;
+@@ -125,7 +128,7 @@ public abstract class NettyRemotingAbstract {
+ * Constructor, specifying capacity of one-way and asynchronous semaphores.
+ *
+ * @param permitsOneway Number of permits for one-way requests.
+- * @param permitsAsync Number of permits for asynchronous requests.
++ * @param permitsAsync Number of permits for asynchronous requests.
+ */
+ public NettyRemotingAbstract(final int permitsOneway, final int permitsAsync) {
+ this.semaphoreOneway = new Semaphore(permitsOneway, true);
+@@ -367,8 +370,7 @@ public abstract class NettyRemotingAbstract {
+ responseFuture.release();
+ }
+ } else {
+- log.warn("receive response, but not matched any request, " + RemotingHelper.parseChannelRemoteAddr(ctx.channel()));
+- log.warn(cmd.toString());
++ log.warn("receive response, cmd={}, but not matched any request, address={}", cmd, RemotingHelper.parseChannelRemoteAddr(ctx.channel()));
+ }
+ }
+
+@@ -467,57 +469,68 @@ public abstract class NettyRemotingAbstract {
+ public RemotingCommand invokeSyncImpl(final Channel channel, final RemotingCommand request,
+ final long timeoutMillis)
+ throws InterruptedException, RemotingSendRequestException, RemotingTimeoutException {
+- //get the request id
+- final int opaque = request.getOpaque();
+-
+ try {
+- final ResponseFuture responseFuture = new ResponseFuture(channel, opaque, timeoutMillis, null, null);
+- this.responseTable.put(opaque, responseFuture);
+- final SocketAddress addr = channel.remoteAddress();
+- channel.writeAndFlush(request).addListener((ChannelFutureListener) f -> {
+- if (f.isSuccess()) {
+- responseFuture.setSendRequestOK(true);
+- return;
+- }
+-
+- responseFuture.setSendRequestOK(false);
+- responseTable.remove(opaque);
+- responseFuture.setCause(f.cause());
+- responseFuture.putResponse(null);
+- log.warn("Failed to write a request command to {}, caused by underlying I/O operation failure", addr);
+- });
++ return invokeImpl(channel, request, timeoutMillis).thenApply(ResponseFuture::getResponseCommand)
++ .get(timeoutMillis, TimeUnit.MILLISECONDS);
++ } catch (ExecutionException e) {
++ throw new RemotingSendRequestException(channel.remoteAddress().toString(), e.getCause());
++ } catch (TimeoutException e) {
++ throw new RemotingTimeoutException(channel.remoteAddress().toString(), timeoutMillis, e.getCause());
++ }
++ }
+
+- RemotingCommand responseCommand = responseFuture.waitResponse(timeoutMillis);
+- if (null == responseCommand) {
+- if (responseFuture.isSendRequestOK()) {
+- throw new RemotingTimeoutException(RemotingHelper.parseSocketAddressAddr(addr), timeoutMillis,
+- responseFuture.getCause());
+- } else {
+- throw new RemotingSendRequestException(RemotingHelper.parseSocketAddressAddr(addr), responseFuture.getCause());
+- }
++ public CompletableFuture<ResponseFuture> invokeImpl(final Channel channel, final RemotingCommand request,
++ final long timeoutMillis) {
++ String channelRemoteAddr = RemotingHelper.parseChannelRemoteAddr(channel);
++ doBeforeRpcHooks(channelRemoteAddr, request);
++ return invoke0(channel, request, timeoutMillis).whenComplete((v, t) -> {
++ if (t == null) {
++ doAfterRpcHooks(channelRemoteAddr, request, v.getResponseCommand());
+ }
+-
+- return responseCommand;
+- } finally {
+- this.responseTable.remove(opaque);
+- }
++ });
+ }
+
+- public void invokeAsyncImpl(final Channel channel, final RemotingCommand request, final long timeoutMillis,
+- final InvokeCallback invokeCallback)
+- throws InterruptedException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException {
++ protected CompletableFuture<ResponseFuture> invoke0(final Channel channel, final RemotingCommand request,
++ final long timeoutMillis) {
++ CompletableFuture<ResponseFuture> future = new CompletableFuture<>();
+ long beginStartTime = System.currentTimeMillis();
+ final int opaque = request.getOpaque();
+- boolean acquired = this.semaphoreAsync.tryAcquire(timeoutMillis, TimeUnit.MILLISECONDS);
++
++ boolean acquired;
++ try {
++ acquired = this.semaphoreAsync.tryAcquire(timeoutMillis, TimeUnit.MILLISECONDS);
++ } catch (Throwable t) {
++ future.completeExceptionally(t);
++ return future;
++ }
+ if (acquired) {
+ final SemaphoreReleaseOnlyOnce once = new SemaphoreReleaseOnlyOnce(this.semaphoreAsync);
+ long costTime = System.currentTimeMillis() - beginStartTime;
+ if (timeoutMillis < costTime) {
+ once.release();
+- throw new RemotingTimeoutException("invokeAsyncImpl call timeout");
++ future.completeExceptionally(new RemotingTimeoutException("invokeAsyncImpl call timeout"));
++ return future;
+ }
+
+- final ResponseFuture responseFuture = new ResponseFuture(channel, opaque, timeoutMillis - costTime, invokeCallback, once);
++ AtomicReference<ResponseFuture> responseFutureReference = new AtomicReference<>();
++ final ResponseFuture responseFuture = new ResponseFuture(channel, opaque, request, timeoutMillis - costTime,
++ new InvokeCallback() {
++ @Override
++ public void operationComplete(ResponseFuture responseFuture) {
++
++ }
++
++ @Override
++ public void operationSucceed(RemotingCommand response) {
++ future.complete(responseFutureReference.get());
++ }
++
++ @Override
++ public void operationFail(Throwable throwable) {
++ future.completeExceptionally(throwable);
++ }
++ }, once);
++ responseFutureReference.set(responseFuture);
+ this.responseTable.put(opaque, responseFuture);
+ try {
+ channel.writeAndFlush(request).addListener((ChannelFutureListener) f -> {
+@@ -528,15 +541,17 @@ public abstract class NettyRemotingAbstract {
+ requestFail(opaque);
+ log.warn("send a request command to channel <{}> failed.", RemotingHelper.parseChannelRemoteAddr(channel));
+ });
++ return future;
+ } catch (Exception e) {
+ responseTable.remove(opaque);
+ responseFuture.release();
+ log.warn("send a request command to channel <" + RemotingHelper.parseChannelRemoteAddr(channel) + "> Exception", e);
+- throw new RemotingSendRequestException(RemotingHelper.parseChannelRemoteAddr(channel), e);
++ future.completeExceptionally(new RemotingSendRequestException(RemotingHelper.parseChannelRemoteAddr(channel), e));
++ return future;
+ }
+ } else {
+ if (timeoutMillis <= 0) {
+- throw new RemotingTooMuchRequestException("invokeAsyncImpl invoke too fast");
++ future.completeExceptionally(new RemotingTooMuchRequestException("invokeAsyncImpl invoke too fast"));
+ } else {
+ String info =
+ String.format("invokeAsyncImpl tryAcquire semaphore timeout, %dms, waiting thread nums: %d semaphoreAsyncValue: %d",
+@@ -545,11 +560,31 @@ public abstract class NettyRemotingAbstract {
+ this.semaphoreAsync.availablePermits()
+ );
+ log.warn(info);
+- throw new RemotingTimeoutException(info);
++ future.completeExceptionally(new RemotingTimeoutException(info));
+ }
++ return future;
+ }
+ }
+
++ public void invokeAsyncImpl(final Channel channel, final RemotingCommand request, final long timeoutMillis,
++ final InvokeCallback invokeCallback) {
++ invokeImpl(channel, request, timeoutMillis)
++ .whenComplete((v, t) -> {
++ if (t == null) {
++ invokeCallback.operationComplete(v);
++ } else {
++ ResponseFuture responseFuture = new ResponseFuture(channel, request.getOpaque(), request, timeoutMillis, null, null);
++ responseFuture.setCause(t);
++ invokeCallback.operationComplete(responseFuture);
++ }
++ })
++ .thenAccept(responseFuture -> invokeCallback.operationSucceed(responseFuture.getResponseCommand()))
++ .exceptionally(t -> {
++ invokeCallback.operationFail(t);
++ return null;
++ });
++ }
++
+ private void requestFail(final int opaque) {
+ ResponseFuture responseFuture = responseTable.remove(opaque);
+ if (responseFuture != null) {
+diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java
+index 64621dd6c..d784351a5 100644
+--- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java
++++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java
+@@ -527,15 +527,13 @@ public class NettyRemotingClient extends NettyRemotingAbstract implements Remoti
+ if (channel != null && channel.isActive()) {
+ long left = timeoutMillis;
+ try {
+- doBeforeRpcHooks(channelRemoteAddr, request);
+ long costTime = System.currentTimeMillis() - beginStartTime;
+ left -= costTime;
+ if (left <= 0) {
+ throw new RemotingTimeoutException("invokeSync call the addr[" + channelRemoteAddr + "] timeout");
+ }
+ RemotingCommand response = this.invokeSyncImpl(channel, request, left);
+- doAfterRpcHooks(channelRemoteAddr, request, response);
+- this.updateChannelLastResponseTime(addr);
++ updateChannelLastResponseTime(addr);
+ return response;
+ } catch (RemotingSendRequestException e) {
+ LOGGER.warn("invokeSync: send request exception, so close the channel[{}]", channelRemoteAddr);
+@@ -727,18 +725,11 @@ public class NettyRemotingClient extends NettyRemotingAbstract implements Remoti
+ final Channel channel = this.getAndCreateChannel(addr);
+ String channelRemoteAddr = RemotingHelper.parseChannelRemoteAddr(channel);
+ if (channel != null && channel.isActive()) {
+- try {
+- doBeforeRpcHooks(channelRemoteAddr, request);
+- long costTime = System.currentTimeMillis() - beginStartTime;
+- if (timeoutMillis < costTime) {
+- throw new RemotingTooMuchRequestException("invokeAsync call the addr[" + channelRemoteAddr + "] timeout");
+- }
+- this.invokeAsyncImpl(channel, request, timeoutMillis - costTime, new InvokeCallbackWrapper(invokeCallback, addr));
+- } catch (RemotingSendRequestException e) {
+- LOGGER.warn("invokeAsync: send request exception, so close the channel[{}]", channelRemoteAddr);
+- this.closeChannel(addr, channel);
+- throw e;
++ long costTime = System.currentTimeMillis() - beginStartTime;
++ if (timeoutMillis < costTime) {
++ throw new RemotingTooMuchRequestException("invokeAsync call the addr[" + channelRemoteAddr + "] timeout");
+ }
++ this.invokeAsyncImpl(channel, request, timeoutMillis - costTime, new InvokeCallbackWrapper(invokeCallback, addr));
+ } else {
+ this.closeChannel(addr, channel);
+ throw new RemotingConnectException(addr);
+@@ -931,11 +922,19 @@ public class NettyRemotingClient extends NettyRemotingAbstract implements Remoti
+
+ @Override
+ public void operationComplete(ResponseFuture responseFuture) {
+- if (responseFuture != null && responseFuture.isSendRequestOK() && responseFuture.getResponseCommand() != null) {
+- NettyRemotingClient.this.updateChannelLastResponseTime(addr);
+- }
+ this.invokeCallback.operationComplete(responseFuture);
+ }
++
++ @Override
++ public void operationSucceed(RemotingCommand response) {
++ updateChannelLastResponseTime(addr);
++ this.invokeCallback.operationSucceed(response);
++ }
++
++ @Override
++ public void operationFail(final Throwable throwable) {
++ this.invokeCallback.operationFail(throwable);
++ }
+ }
+
+ class NettyClientHandler extends SimpleChannelInboundHandler<RemotingCommand> {
+diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/ResponseFuture.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/ResponseFuture.java
+index 19f705d74..0882818fe 100644
+--- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/ResponseFuture.java
++++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/ResponseFuture.java
+@@ -22,6 +22,9 @@ import java.util.concurrent.TimeUnit;
+ import java.util.concurrent.atomic.AtomicBoolean;
+ import org.apache.rocketmq.remoting.InvokeCallback;
+ import org.apache.rocketmq.remoting.common.SemaphoreReleaseOnlyOnce;
++import org.apache.rocketmq.remoting.exception.RemotingException;
++import org.apache.rocketmq.remoting.exception.RemotingSendRequestException;
++import org.apache.rocketmq.remoting.exception.RemotingTimeoutException;
+ import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+
+ public class ResponseFuture {
+@@ -59,6 +62,18 @@ public class ResponseFuture {
+ public void executeInvokeCallback() {
+ if (invokeCallback != null) {
+ if (this.executeCallbackOnlyOnce.compareAndSet(false, true)) {
++ RemotingCommand response = getResponseCommand();
++ if (response != null) {
++ invokeCallback.operationSucceed(response);
++ } else {
++ if (!isSendRequestOK()) {
++ invokeCallback.operationFail(new RemotingSendRequestException(channel.remoteAddress().toString(), getCause()));
++ } else if (isTimeout()) {
++ invokeCallback.operationFail(new RemotingTimeoutException(channel.remoteAddress().toString(), getTimeoutMillis(), getCause()));
++ } else {
++ invokeCallback.operationFail(new RemotingException(getRequestCommand().toString(), getCause()));
++ }
++ }
+ invokeCallback.operationComplete(this);
+ }
+ }
+diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcClientImpl.java b/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcClientImpl.java
+index 133e0ed31..5328e8845 100644
+--- a/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcClientImpl.java
++++ b/remoting/src/main/java/org/apache/rocketmq/remoting/rpc/RpcClientImpl.java
+@@ -160,31 +160,38 @@ public class RpcClientImpl implements RpcClient {
+ InvokeCallback callback = new InvokeCallback() {
+ @Override
+ public void operationComplete(ResponseFuture responseFuture) {
+- RemotingCommand responseCommand = responseFuture.getResponseCommand();
+- if (responseCommand == null) {
+- processFailedResponse(addr, requestCommand, responseFuture, rpcResponsePromise);
+- return;
+- }
++
++ }
++
++ @Override
++ public void operationSucceed(RemotingCommand response) {
+ try {
+- switch (responseCommand.getCode()) {
++ switch (response.getCode()) {
+ case ResponseCode.SUCCESS:
+ case ResponseCode.PULL_NOT_FOUND:
+ case ResponseCode.PULL_RETRY_IMMEDIATELY:
+ case ResponseCode.PULL_OFFSET_MOVED:
+ PullMessageResponseHeader responseHeader =
+- (PullMessageResponseHeader) responseCommand.decodeCommandCustomHeader(PullMessageResponseHeader.class);
+- rpcResponsePromise.setSuccess(new RpcResponse(responseCommand.getCode(), responseHeader, responseCommand.getBody()));
++ (PullMessageResponseHeader) response.decodeCommandCustomHeader(PullMessageResponseHeader.class);
++ rpcResponsePromise.setSuccess(new RpcResponse(response.getCode(), responseHeader, response.getBody()));
+ default:
+- RpcResponse rpcResponse = new RpcResponse(new RpcException(responseCommand.getCode(), "unexpected remote response code"));
++ RpcResponse rpcResponse = new RpcResponse(new RpcException(response.getCode(), "unexpected remote response code"));
+ rpcResponsePromise.setSuccess(rpcResponse);
+
+ }
+ } catch (Exception e) {
+- String errorMessage = "process failed. addr: " + addr + ", timeoutMillis: " + responseFuture.getTimeoutMillis() + ". Request: " + requestCommand;
+- RpcResponse rpcResponse = new RpcResponse(new RpcException(ResponseCode.RPC_UNKNOWN, errorMessage, e));
++ String errorMessage = "process failed. addr: " + addr + ", timeoutMillis: " + timeoutMillis + ". Request: " + requestCommand;
++ RpcResponse rpcResponse = new RpcResponse(new RpcException(ResponseCode.RPC_UNKNOWN, errorMessage, e));
+ rpcResponsePromise.setSuccess(rpcResponse);
+ }
+ }
++
++ @Override
++ public void operationFail(Throwable throwable) {
++ String errorMessage = "process failed. addr: " + addr + ". Request: " + requestCommand;
++ RpcResponse rpcResponse = new RpcResponse(new RpcException(ResponseCode.RPC_UNKNOWN, errorMessage, throwable));
++ rpcResponsePromise.setSuccess(rpcResponse);
++ }
+ };
+
+ this.remotingClient.invokeAsync(addr, requestCommand, timeoutMillis, callback);
+diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/RemotingServerTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/RemotingServerTest.java
+index 90072960b..d0da0eb2e 100644
+--- a/remoting/src/test/java/org/apache/rocketmq/remoting/RemotingServerTest.java
++++ b/remoting/src/test/java/org/apache/rocketmq/remoting/RemotingServerTest.java
+@@ -26,12 +26,12 @@ import org.apache.rocketmq.remoting.exception.RemotingConnectException;
+ import org.apache.rocketmq.remoting.exception.RemotingSendRequestException;
+ import org.apache.rocketmq.remoting.exception.RemotingTimeoutException;
+ import org.apache.rocketmq.remoting.exception.RemotingTooMuchRequestException;
+-import org.apache.rocketmq.remoting.netty.ResponseFuture;
+-import org.apache.rocketmq.remoting.netty.NettyServerConfig;
+ import org.apache.rocketmq.remoting.netty.NettyClientConfig;
+-import org.apache.rocketmq.remoting.netty.NettyRemotingServer;
+ import org.apache.rocketmq.remoting.netty.NettyRemotingClient;
++import org.apache.rocketmq.remoting.netty.NettyRemotingServer;
+ import org.apache.rocketmq.remoting.netty.NettyRequestProcessor;
++import org.apache.rocketmq.remoting.netty.NettyServerConfig;
++import org.apache.rocketmq.remoting.netty.ResponseFuture;
+ import org.apache.rocketmq.remoting.protocol.LanguageCode;
+ import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+ import org.junit.AfterClass;
+@@ -40,7 +40,6 @@ import org.junit.Test;
+
+ import static org.assertj.core.api.Assertions.assertThat;
+ import static org.junit.Assert.assertNotNull;
+-import static org.junit.Assert.assertTrue;
+
+ public class RemotingServerTest {
+ private static RemotingServer remotingServer;
+@@ -122,10 +121,19 @@ public class RemotingServerTest {
+ remotingClient.invokeAsync("localhost:" + remotingServer.localListenPort(), request, 1000 * 3, new InvokeCallback() {
+ @Override
+ public void operationComplete(ResponseFuture responseFuture) {
++
++ }
++
++ @Override
++ public void operationSucceed(RemotingCommand response) {
+ latch.countDown();
+- assertTrue(responseFuture != null);
+- assertThat(responseFuture.getResponseCommand().getLanguage()).isEqualTo(LanguageCode.JAVA);
+- assertThat(responseFuture.getResponseCommand().getExtFields()).hasSize(2);
++ assertThat(response.getLanguage()).isEqualTo(LanguageCode.JAVA);
++ assertThat(response.getExtFields()).hasSize(2);
++ }
++
++ @Override
++ public void operationFail(Throwable throwable) {
++
+ }
+ });
+ latch.await();
+diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/BaseInvokeCallback.java b/remoting/src/test/java/org/apache/rocketmq/remoting/netty/MockChannel.java
+similarity index 57%
+rename from client/src/main/java/org/apache/rocketmq/client/impl/BaseInvokeCallback.java
+rename to remoting/src/test/java/org/apache/rocketmq/remoting/netty/MockChannel.java
+index 80188832e..8ddcdf35d 100644
+--- a/client/src/main/java/org/apache/rocketmq/client/impl/BaseInvokeCallback.java
++++ b/remoting/src/test/java/org/apache/rocketmq/remoting/netty/MockChannel.java
+@@ -15,23 +15,14 @@
+ * limitations under the License.
+ */
+
+-package org.apache.rocketmq.client.impl;
++package org.apache.rocketmq.remoting.netty;
+
+-import org.apache.rocketmq.remoting.InvokeCallback;
+-import org.apache.rocketmq.remoting.netty.ResponseFuture;
+-
+-public abstract class BaseInvokeCallback implements InvokeCallback {
+- private final MQClientAPIImpl mqClientAPI;
+-
+- public BaseInvokeCallback(MQClientAPIImpl mqClientAPI) {
+- this.mqClientAPI = mqClientAPI;
+- }
++import io.netty.channel.ChannelFuture;
++import io.netty.channel.local.LocalChannel;
+
++public class MockChannel extends LocalChannel {
+ @Override
+- public void operationComplete(final ResponseFuture responseFuture) {
+- mqClientAPI.execRpcHooksAfterRequest(responseFuture);
+- onComplete(responseFuture);
++ public ChannelFuture writeAndFlush(Object msg) {
++ return new MockChannelPromise(MockChannel.this);
+ }
+-
+- public abstract void onComplete(final ResponseFuture responseFuture);
+ }
+diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/netty/MockChannelPromise.java b/remoting/src/test/java/org/apache/rocketmq/remoting/netty/MockChannelPromise.java
+new file mode 100644
+index 000000000..9c3a35487
+--- /dev/null
++++ b/remoting/src/test/java/org/apache/rocketmq/remoting/netty/MockChannelPromise.java
+@@ -0,0 +1,191 @@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one or more
++ * contributor license agreements. See the NOTICE file distributed with
++ * this work for additional information regarding copyright ownership.
++ * The ASF licenses this file to You under the Apache License, Version 2.0
++ * (the "License"); you may not use this file except in compliance with
++ * the License. You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package org.apache.rocketmq.remoting.netty;
++
++import io.netty.channel.Channel;
++import io.netty.channel.ChannelPromise;
++import io.netty.util.concurrent.Future;
++import io.netty.util.concurrent.GenericFutureListener;
++import java.util.concurrent.ExecutionException;
++import java.util.concurrent.TimeUnit;
++import java.util.concurrent.TimeoutException;
++import org.jetbrains.annotations.NotNull;
++
++public class MockChannelPromise implements ChannelPromise {
++ protected Channel channel;
++
++ public MockChannelPromise(Channel channel) {
++ this.channel = channel;
++ }
++
++ @Override
++ public Channel channel() {
++ return channel;
++ }
++
++ @Override
++ public ChannelPromise setSuccess(Void result) {
++ return this;
++ }
++
++ @Override
++ public ChannelPromise setSuccess() {
++ return this;
++ }
++
++ @Override
++ public boolean trySuccess() {
++ return false;
++ }
++
++ @Override
++ public ChannelPromise setFailure(Throwable cause) {
++ return this;
++ }
++
++ @Override
++ public ChannelPromise addListener(GenericFutureListener<? extends Future<? super Void>> listener) {
++ return this;
++ }
++
++ @Override
++ public ChannelPromise addListeners(GenericFutureListener<? extends Future<? super Void>>... listeners) {
++ return this;
++ }
++
++ @Override
++ public ChannelPromise removeListener(GenericFutureListener<? extends Future<? super Void>> listener) {
++ return this;
++ }
++
++ @Override
++ public ChannelPromise removeListeners(GenericFutureListener<? extends Future<? super Void>>... listeners) {
++ return this;
++ }
++
++ @Override
++ public ChannelPromise sync() throws InterruptedException {
++ return this;
++ }
++
++ @Override
++ public ChannelPromise syncUninterruptibly() {
++ return this;
++ }
++
++ @Override
++ public ChannelPromise await() throws InterruptedException {
++ return this;
++ }
++
++ @Override
++ public ChannelPromise awaitUninterruptibly() {
++ return this;
++ }
++
++ @Override
++ public ChannelPromise unvoid() {
++ return this;
++ }
++
++ @Override
++ public boolean isVoid() {
++ return false;
++ }
++
++ @Override
++ public boolean trySuccess(Void result) {
++ return false;
++ }
++
++ @Override
++ public boolean tryFailure(Throwable cause) {
++ return false;
++ }
++
++ @Override
++ public boolean setUncancellable() {
++ return false;
++ }
++
++ @Override
++ public boolean isSuccess() {
++ return false;
++ }
++
++ @Override
++ public boolean isCancellable() {
++ return false;
++ }
++
++ @Override
++ public Throwable cause() {
++ return null;
++ }
++
++ @Override
++ public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
++ return false;
++ }
++
++ @Override
++ public boolean await(long timeoutMillis) throws InterruptedException {
++ return false;
++ }
++
++ @Override
++ public boolean awaitUninterruptibly(long timeout, TimeUnit unit) {
++ return false;
++ }
++
++ @Override
++ public boolean awaitUninterruptibly(long timeoutMillis) {
++ return false;
++ }
++
++ @Override
++ public Void getNow() {
++ return null;
++ }
++
++ @Override
++ public boolean cancel(boolean mayInterruptIfRunning) {
++ return false;
++ }
++
++ @Override
++ public boolean isCancelled() {
++ return false;
++ }
++
++ @Override
++ public boolean isDone() {
++ return false;
++ }
++
++ @Override
++ public Void get() throws InterruptedException, ExecutionException {
++ return null;
++ }
++
++ @Override
++ public Void get(long timeout,
++ @NotNull java.util.concurrent.TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
++ return null;
++ }
++}
+diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyRemotingAbstractTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyRemotingAbstractTest.java
+index 8381c132b..dbbea86ea 100644
+--- a/remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyRemotingAbstractTest.java
++++ b/remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyRemotingAbstractTest.java
+@@ -39,9 +39,19 @@ public class NettyRemotingAbstractTest {
+ final Semaphore semaphore = new Semaphore(0);
+ ResponseFuture responseFuture = new ResponseFuture(null, 1, 3000, new InvokeCallback() {
+ @Override
+- public void operationComplete(final ResponseFuture responseFuture) {
++ public void operationComplete(ResponseFuture responseFuture) {
++
++ }
++
++ @Override
++ public void operationSucceed(RemotingCommand response) {
+ assertThat(semaphore.availablePermits()).isEqualTo(0);
+ }
++
++ @Override
++ public void operationFail(Throwable throwable) {
++
++ }
+ }, new SemaphoreReleaseOnlyOnce(semaphore));
+
+ remotingAbstract.responseTable.putIfAbsent(1, responseFuture);
+@@ -75,9 +85,19 @@ public class NettyRemotingAbstractTest {
+ final Semaphore semaphore = new Semaphore(0);
+ ResponseFuture responseFuture = new ResponseFuture(null, 1, 3000, new InvokeCallback() {
+ @Override
+- public void operationComplete(final ResponseFuture responseFuture) {
++ public void operationComplete(ResponseFuture responseFuture) {
++
++ }
++
++ @Override
++ public void operationSucceed(RemotingCommand response) {
+ assertThat(semaphore.availablePermits()).isEqualTo(0);
+ }
++
++ @Override
++ public void operationFail(Throwable throwable) {
++
++ }
+ }, new SemaphoreReleaseOnlyOnce(semaphore));
+
+ remotingAbstract.responseTable.putIfAbsent(1, responseFuture);
+@@ -98,7 +118,18 @@ public class NettyRemotingAbstractTest {
+ // mock timeout
+ ResponseFuture responseFuture = new ResponseFuture(null, dummyId, -1000, new InvokeCallback() {
+ @Override
+- public void operationComplete(final ResponseFuture responseFuture) {
++ public void operationComplete(ResponseFuture responseFuture) {
++
++ }
++
++ @Override
++ public void operationSucceed(RemotingCommand response) {
++
++ }
++
++ @Override
++ public void operationFail(Throwable throwable) {
++
+ }
+ }, null);
+ remotingAbstract.responseTable.putIfAbsent(dummyId, responseFuture);
+@@ -111,7 +142,22 @@ public class NettyRemotingAbstractTest {
+ final Semaphore semaphore = new Semaphore(0);
+ RemotingCommand request = RemotingCommand.createRequestCommand(1, null);
+ ResponseFuture responseFuture = new ResponseFuture(null, 1, request, 3000,
+- responseFuture1 -> assertThat(semaphore.availablePermits()).isEqualTo(0), new SemaphoreReleaseOnlyOnce(semaphore));
++ new InvokeCallback() {
++ @Override
++ public void operationComplete(ResponseFuture responseFuture) {
++
++ }
++
++ @Override
++ public void operationSucceed(RemotingCommand response) {
++ assertThat(semaphore.availablePermits()).isEqualTo(0);
++ }
++
++ @Override
++ public void operationFail(Throwable throwable) {
++
++ }
++ }, new SemaphoreReleaseOnlyOnce(semaphore));
+
+ remotingAbstract.responseTable.putIfAbsent(1, responseFuture);
+ RemotingCommand response = RemotingCommand.createResponseCommand(0, "Foo");
+diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyRemotingClientTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyRemotingClientTest.java
+index 8fabbb21d..e72e7bd53 100644
+--- a/remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyRemotingClientTest.java
++++ b/remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyRemotingClientTest.java
+@@ -16,10 +16,17 @@
+ */
+ package org.apache.rocketmq.remoting.netty;
+
++import io.netty.channel.Channel;
++import io.netty.channel.ChannelFuture;
++import io.netty.channel.local.LocalChannel;
+ import java.util.concurrent.CompletableFuture;
++import java.util.concurrent.ExecutionException;
+ import java.util.concurrent.ExecutorService;
+ import java.util.concurrent.Executors;
++import java.util.concurrent.Semaphore;
+ import org.apache.rocketmq.remoting.InvokeCallback;
++import org.apache.rocketmq.remoting.RPCHook;
++import org.apache.rocketmq.remoting.common.SemaphoreReleaseOnlyOnce;
+ import org.apache.rocketmq.remoting.exception.RemotingConnectException;
+ import org.apache.rocketmq.remoting.exception.RemotingException;
+ import org.apache.rocketmq.remoting.exception.RemotingSendRequestException;
+@@ -29,23 +36,33 @@ import org.apache.rocketmq.remoting.protocol.RequestCode;
+ import org.apache.rocketmq.remoting.protocol.ResponseCode;
+ import org.junit.Test;
+ import org.junit.runner.RunWith;
++import org.mockito.Mock;
+ import org.mockito.Spy;
+ import org.mockito.junit.MockitoJUnitRunner;
+
+ import static org.assertj.core.api.Assertions.assertThat;
++import static org.assertj.core.api.Assertions.assertThatThrownBy;
+ import static org.assertj.core.api.AssertionsForClassTypes.catchThrowable;
+ import static org.mockito.ArgumentMatchers.any;
+ import static org.mockito.ArgumentMatchers.anyLong;
+ import static org.mockito.ArgumentMatchers.anyString;
++import static org.mockito.ArgumentMatchers.eq;
+ import static org.mockito.Mockito.doAnswer;
++import static org.mockito.Mockito.doReturn;
++import static org.mockito.Mockito.mock;
++import static org.mockito.Mockito.never;
++import static org.mockito.Mockito.times;
++import static org.mockito.Mockito.verify;
+
+ @RunWith(MockitoJUnitRunner.class)
+ public class NettyRemotingClientTest {
+ @Spy
+ private NettyRemotingClient remotingClient = new NettyRemotingClient(new NettyClientConfig());
++ @Mock
++ private RPCHook rpcHookMock;
+
+ @Test
+- public void testSetCallbackExecutor() throws NoSuchFieldException, IllegalAccessException {
++ public void testSetCallbackExecutor() {
+ ExecutorService customized = Executors.newCachedThreadPool();
+ remotingClient.setCallbackExecutor(customized);
+ assertThat(remotingClient.getCallbackExecutor()).isEqualTo(customized);
+@@ -61,7 +78,7 @@ public class NettyRemotingClientTest {
+ InvokeCallback callback = invocation.getArgument(3);
+ ResponseFuture responseFuture = new ResponseFuture(null, request.getOpaque(), 3 * 1000, null, null);
+ responseFuture.setResponseCommand(response);
+- callback.operationComplete(responseFuture);
++ callback.operationSucceed(responseFuture.getResponseCommand());
+ return null;
+ }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any(InvokeCallback.class));
+
+@@ -78,9 +95,7 @@ public class NettyRemotingClientTest {
+ response.setCode(ResponseCode.SUCCESS);
+ doAnswer(invocation -> {
+ InvokeCallback callback = invocation.getArgument(3);
+- ResponseFuture responseFuture = new ResponseFuture(null, request.getOpaque(), 3 * 1000, null, null);
+- responseFuture.setSendRequestOK(false);
+- callback.operationComplete(responseFuture);
++ callback.operationFail(new RemotingSendRequestException(null));
+ return null;
+ }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any(InvokeCallback.class));
+
+@@ -97,8 +112,7 @@ public class NettyRemotingClientTest {
+ response.setCode(ResponseCode.SUCCESS);
+ doAnswer(invocation -> {
+ InvokeCallback callback = invocation.getArgument(3);
+- ResponseFuture responseFuture = new ResponseFuture(null, request.getOpaque(), -1L, null, null);
+- callback.operationComplete(responseFuture);
++ callback.operationFail(new RemotingTimeoutException(""));
+ return null;
+ }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any(InvokeCallback.class));
+
+@@ -115,8 +129,7 @@ public class NettyRemotingClientTest {
+ response.setCode(ResponseCode.SUCCESS);
+ doAnswer(invocation -> {
+ InvokeCallback callback = invocation.getArgument(3);
+- ResponseFuture responseFuture = new ResponseFuture(null, request.getOpaque(), 3 * 1000, null, null);
+- callback.operationComplete(responseFuture);
++ callback.operationFail(new RemotingException(null));
+ return null;
+ }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any(InvokeCallback.class));
+
+@@ -134,4 +147,158 @@ public class NettyRemotingClientTest {
+ assertThat(e.getMessage()).contains(addr);
+ }
+ }
++
++ @Test
++ public void testInvoke0() throws ExecutionException, InterruptedException {
++ RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, null);
++ RemotingCommand response = RemotingCommand.createResponseCommand(null);
++ response.setCode(ResponseCode.SUCCESS);
++ Channel channel = new MockChannel() {
++ @Override
++ public ChannelFuture writeAndFlush(Object msg) {
++ ResponseFuture responseFuture = remotingClient.responseTable.get(request.getOpaque());
++ responseFuture.setResponseCommand(response);
++ responseFuture.executeInvokeCallback();
++ return super.writeAndFlush(msg);
++ }
++ };
++ CompletableFuture<ResponseFuture> future = remotingClient.invoke0(channel, request, 1000L);
++ assertThat(future.get().getResponseCommand()).isEqualTo(response);
++ }
++
++ @Test
++ public void testInvoke0WithException() {
++ RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, null);
++ RemotingCommand response = RemotingCommand.createResponseCommand(null);
++ response.setCode(ResponseCode.SUCCESS);
++ Channel channel = new MockChannel() {
++ @Override
++ public ChannelFuture writeAndFlush(Object msg) {
++ ResponseFuture responseFuture = remotingClient.responseTable.get(request.getOpaque());
++ responseFuture.executeInvokeCallback();
++ return super.writeAndFlush(msg);
++ }
++ };
++ CompletableFuture<ResponseFuture> future = remotingClient.invoke0(channel, request, 1000L);
++ assertThatThrownBy(future::get).getCause().isInstanceOf(RemotingException.class);
++ }
++
++ @Test
++ public void testInvokeSync() throws RemotingSendRequestException, RemotingTimeoutException, InterruptedException {
++ remotingClient.registerRPCHook(rpcHookMock);
++
++ Channel channel = new LocalChannel();
++ RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, null);
++ RemotingCommand response = RemotingCommand.createResponseCommand(null);
++ response.setCode(ResponseCode.SUCCESS);
++ ResponseFuture responseFuture = new ResponseFuture(channel, request.getOpaque(), request, 1000, new InvokeCallback() {
++ @Override
++ public void operationComplete(ResponseFuture responseFuture) {
++
++ }
++ }, new SemaphoreReleaseOnlyOnce(new Semaphore(1)));
++ responseFuture.setResponseCommand(response);
++ CompletableFuture<ResponseFuture> future = new CompletableFuture<>();
++ future.complete(responseFuture);
++
++ doReturn(future).when(remotingClient).invoke0(any(Channel.class), any(RemotingCommand.class), anyLong());
++ RemotingCommand actual = remotingClient.invokeSyncImpl(channel, request, 1000);
++ assertThat(actual).isEqualTo(response);
++
++ verify(rpcHookMock).doBeforeRequest(anyString(), eq(request));
++ verify(rpcHookMock).doAfterResponse(anyString(), eq(request), eq(response));
++ }
++
++ @Test
++ public void testInvokeAsync() {
++ remotingClient.registerRPCHook(rpcHookMock);
++ Channel channel = new LocalChannel();
++ RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, null);
++ RemotingCommand response = RemotingCommand.createResponseCommand(null);
++ response.setCode(ResponseCode.SUCCESS);
++ ResponseFuture responseFuture = new ResponseFuture(channel, request.getOpaque(), request, 1000, new InvokeCallback() {
++ @Override
++ public void operationComplete(ResponseFuture responseFuture) {
++
++ }
++ }, new SemaphoreReleaseOnlyOnce(new Semaphore(1)));
++ responseFuture.setResponseCommand(response);
++ CompletableFuture<ResponseFuture> future = new CompletableFuture<>();
++ future.complete(responseFuture);
++
++ doReturn(future).when(remotingClient).invoke0(any(Channel.class), any(RemotingCommand.class), anyLong());
++
++ InvokeCallback callback = mock(InvokeCallback.class);
++ remotingClient.invokeAsyncImpl(channel, request, 1000, callback);
++ verify(callback, times(1)).operationSucceed(eq(response));
++ verify(callback, times(1)).operationComplete(eq(responseFuture));
++ verify(callback, never()).operationFail(any());
++
++ verify(rpcHookMock).doBeforeRequest(anyString(), eq(request));
++ verify(rpcHookMock).doAfterResponse(anyString(), eq(request), eq(response));
++ }
++
++ @Test
++ public void testInvokeAsyncFail() {
++ remotingClient.registerRPCHook(rpcHookMock);
++ RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, null);
++
++ Channel channel = new LocalChannel();
++ CompletableFuture<ResponseFuture> future = new CompletableFuture<>();
++ future.completeExceptionally(new RemotingException(null));
++
++ doReturn(future).when(remotingClient).invoke0(any(Channel.class), any(RemotingCommand.class), anyLong());
++
++ InvokeCallback callback = mock(InvokeCallback.class);
++ remotingClient.invokeAsyncImpl(channel, request, 1000, callback);
++ verify(callback, never()).operationSucceed(any());
++ verify(callback, times(1)).operationComplete(any());
++ verify(callback, times(1)).operationFail(any());
++
++ verify(rpcHookMock).doBeforeRequest(anyString(), eq(request));
++ verify(rpcHookMock, never()).doAfterResponse(anyString(), eq(request), any());
++ }
++
++ @Test
++ public void testInvokeImpl() throws ExecutionException, InterruptedException {
++ remotingClient.registerRPCHook(rpcHookMock);
++ Channel channel = new LocalChannel();
++ RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, null);
++ RemotingCommand response = RemotingCommand.createResponseCommand(null);
++ response.setCode(ResponseCode.SUCCESS);
++ ResponseFuture responseFuture = new ResponseFuture(channel, request.getOpaque(), request, 1000, new InvokeCallback() {
++ @Override
++ public void operationComplete(ResponseFuture responseFuture) {
++
++ }
++ }, new SemaphoreReleaseOnlyOnce(new Semaphore(1)));
++ responseFuture.setResponseCommand(response);
++ CompletableFuture<ResponseFuture> future = new CompletableFuture<>();
++ future.complete(responseFuture);
++
++ doReturn(future).when(remotingClient).invoke0(any(Channel.class), any(RemotingCommand.class), anyLong());
++
++ CompletableFuture<ResponseFuture> future0 = remotingClient.invokeImpl(channel, request, 1000);
++ assertThat(future0.get()).isEqualTo(responseFuture);
++
++ verify(rpcHookMock).doBeforeRequest(anyString(), eq(request));
++ verify(rpcHookMock).doAfterResponse(anyString(), eq(request), eq(response));
++ }
++
++ @Test
++ public void testInvokeImplFail() {
++ remotingClient.registerRPCHook(rpcHookMock);
++ RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, null);
++
++ Channel channel = new LocalChannel();
++ CompletableFuture<ResponseFuture> future = new CompletableFuture<>();
++ future.completeExceptionally(new RemotingException(null));
++
++ doReturn(future).when(remotingClient).invoke0(any(Channel.class), any(RemotingCommand.class), anyLong());
++
++ assertThatThrownBy(() -> remotingClient.invokeImpl(channel, request, 1000).get()).getCause().isInstanceOf(RemotingException.class);
++
++ verify(rpcHookMock).doBeforeRequest(anyString(), eq(request));
++ verify(rpcHookMock, never()).doAfterResponse(anyString(), eq(request), any());
++ }
+ }
+--
+2.32.0.windows.2
+
+
+From b9ffe0f9576f68b8a37cf3e2f68051658ae5a9a2 Mon Sep 17 00:00:00 2001
+From: Zhouxiang Zhan <zhouxzhan@apache.org>
+Date: Sun, 8 Oct 2023 16:33:44 +0800
+Subject: [PATCH 2/7] [ISSUE #7296] Add ChannelEventListener for
+ MQClientAPIImpl (#7324)
+
+* Add ChannelEventListener for MQClientAPIImpl
+
+* add heartbeat when channel connect
+
+* remove log
+
+* Add enableHeartbeatChannelEventListener for ClientConfig
+---
+ .../apache/rocketmq/client/ClientConfig.java | 55 ++++++++++++++-----
+ .../rocketmq/client/impl/MQClientAPIImpl.java | 9 ++-
+ .../client/impl/factory/MQClientInstance.java | 35 +++++++++++-
+ .../remoting/netty/NettyRemotingClient.java | 2 +
+ 4 files changed, 85 insertions(+), 16 deletions(-)
+
+diff --git a/client/src/main/java/org/apache/rocketmq/client/ClientConfig.java b/client/src/main/java/org/apache/rocketmq/client/ClientConfig.java
+index bb0fe3522..f9843cc02 100644
+--- a/client/src/main/java/org/apache/rocketmq/client/ClientConfig.java
++++ b/client/src/main/java/org/apache/rocketmq/client/ClientConfig.java
+@@ -94,6 +94,8 @@ public class ClientConfig {
+ private boolean sendLatencyEnable = Boolean.parseBoolean(System.getProperty(SEND_LATENCY_ENABLE, "false"));
+ private boolean startDetectorEnable = Boolean.parseBoolean(System.getProperty(START_DETECTOR_ENABLE, "false"));
+
++ private boolean enableHeartbeatChannelEventListener = true;
++
+ public String buildMQClientId() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(this.getClientIP());
+@@ -201,6 +203,7 @@ public class ClientConfig {
+ this.useHeartbeatV2 = cc.useHeartbeatV2;
+ this.startDetectorEnable = cc.startDetectorEnable;
+ this.sendLatencyEnable = cc.sendLatencyEnable;
++ this.enableHeartbeatChannelEventListener = cc.enableHeartbeatChannelEventListener;
+ this.detectInterval = cc.detectInterval;
+ this.detectTimeout = cc.detectTimeout;
+ }
+@@ -228,6 +231,7 @@ public class ClientConfig {
+ cc.enableStreamRequestType = enableStreamRequestType;
+ cc.useHeartbeatV2 = useHeartbeatV2;
+ cc.startDetectorEnable = startDetectorEnable;
++ cc.enableHeartbeatChannelEventListener = enableHeartbeatChannelEventListener;
+ cc.sendLatencyEnable = sendLatencyEnable;
+ cc.detectInterval = detectInterval;
+ cc.detectTimeout = detectTimeout;
+@@ -418,6 +422,14 @@ public class ClientConfig {
+ this.startDetectorEnable = startDetectorEnable;
+ }
+
++ public boolean isEnableHeartbeatChannelEventListener() {
++ return enableHeartbeatChannelEventListener;
++ }
++
++ public void setEnableHeartbeatChannelEventListener(boolean enableHeartbeatChannelEventListener) {
++ this.enableHeartbeatChannelEventListener = enableHeartbeatChannelEventListener;
++ }
++
+ public int getDetectTimeout() {
+ return this.detectTimeout;
+ }
+@@ -444,19 +456,34 @@ public class ClientConfig {
+
+ @Override
+ public String toString() {
+- return "ClientConfig [namesrvAddr=" + namesrvAddr
+- + ", clientIP=" + clientIP + ", instanceName=" + instanceName
+- + ", clientCallbackExecutorThreads=" + clientCallbackExecutorThreads
+- + ", pollNameServerInterval=" + pollNameServerInterval
+- + ", heartbeatBrokerInterval=" + heartbeatBrokerInterval
+- + ", persistConsumerOffsetInterval=" + persistConsumerOffsetInterval
+- + ", pullTimeDelayMillsWhenException=" + pullTimeDelayMillsWhenException
+- + ", unitMode=" + unitMode + ", unitName=" + unitName
+- + ", vipChannelEnabled=" + vipChannelEnabled + ", useTLS=" + useTLS
+- + ", socksProxyConfig=" + socksProxyConfig + ", language=" + language.name()
+- + ", namespace=" + namespace + ", mqClientApiTimeout=" + mqClientApiTimeout
+- + ", decodeReadBody=" + decodeReadBody + ", decodeDecompressBody=" + decodeDecompressBody
+- + ", sendLatencyEnable=" + sendLatencyEnable + ", startDetectorEnable=" + startDetectorEnable
+- + ", enableStreamRequestType=" + enableStreamRequestType + ", useHeartbeatV2=" + useHeartbeatV2 + "]";
++ return "ClientConfig{" +
++ "namesrvAddr='" + namesrvAddr + '\'' +
++ ", clientIP='" + clientIP + '\'' +
++ ", instanceName='" + instanceName + '\'' +
++ ", clientCallbackExecutorThreads=" + clientCallbackExecutorThreads +
++ ", namespace='" + namespace + '\'' +
++ ", namespaceInitialized=" + namespaceInitialized +
++ ", accessChannel=" + accessChannel +
++ ", pollNameServerInterval=" + pollNameServerInterval +
++ ", heartbeatBrokerInterval=" + heartbeatBrokerInterval +
++ ", persistConsumerOffsetInterval=" + persistConsumerOffsetInterval +
++ ", pullTimeDelayMillsWhenException=" + pullTimeDelayMillsWhenException +
++ ", unitMode=" + unitMode +
++ ", unitName='" + unitName + '\'' +
++ ", decodeReadBody=" + decodeReadBody +
++ ", decodeDecompressBody=" + decodeDecompressBody +
++ ", vipChannelEnabled=" + vipChannelEnabled +
++ ", useHeartbeatV2=" + useHeartbeatV2 +
++ ", useTLS=" + useTLS +
++ ", socksProxyConfig='" + socksProxyConfig + '\'' +
++ ", mqClientApiTimeout=" + mqClientApiTimeout +
++ ", detectTimeout=" + detectTimeout +
++ ", detectInterval=" + detectInterval +
++ ", language=" + language +
++ ", enableStreamRequestType=" + enableStreamRequestType +
++ ", sendLatencyEnable=" + sendLatencyEnable +
++ ", startDetectorEnable=" + startDetectorEnable +
++ ", enableHeartbeatChannelEventListener=" + enableHeartbeatChannelEventListener +
++ '}';
+ }
+ }
+diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java
+index 2407e5737..e152be811 100644
+--- a/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java
++++ b/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java
+@@ -79,6 +79,7 @@ import org.apache.rocketmq.common.sysflag.PullSysFlag;
+ import org.apache.rocketmq.common.topic.TopicValidator;
+ import org.apache.rocketmq.logging.org.slf4j.Logger;
+ import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
++import org.apache.rocketmq.remoting.ChannelEventListener;
+ import org.apache.rocketmq.remoting.CommandCustomHeader;
+ import org.apache.rocketmq.remoting.InvokeCallback;
+ import org.apache.rocketmq.remoting.RPCHook;
+@@ -246,10 +247,16 @@ public class MQClientAPIImpl implements NameServerUpdateCallback {
+ public MQClientAPIImpl(final NettyClientConfig nettyClientConfig,
+ final ClientRemotingProcessor clientRemotingProcessor,
+ RPCHook rpcHook, final ClientConfig clientConfig) {
++ this(nettyClientConfig, clientRemotingProcessor, rpcHook, clientConfig, null);
++ }
++
++ public MQClientAPIImpl(final NettyClientConfig nettyClientConfig,
++ final ClientRemotingProcessor clientRemotingProcessor,
++ RPCHook rpcHook, final ClientConfig clientConfig, final ChannelEventListener channelEventListener) {
+ this.clientConfig = clientConfig;
+ topAddressing = new DefaultTopAddressing(MixAll.getWSAddr(), clientConfig.getUnitName());
+ topAddressing.registerChangeCallBack(this);
+- this.remotingClient = new NettyRemotingClient(nettyClientConfig, null);
++ this.remotingClient = new NettyRemotingClient(nettyClientConfig, channelEventListener);
+ this.clientRemotingProcessor = clientRemotingProcessor;
+
+ // Inject stream rpc hook first to make reserve field signature
+diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/factory/MQClientInstance.java b/client/src/main/java/org/apache/rocketmq/client/impl/factory/MQClientInstance.java
+index 9484b26f8..09534a176 100644
+--- a/client/src/main/java/org/apache/rocketmq/client/impl/factory/MQClientInstance.java
++++ b/client/src/main/java/org/apache/rocketmq/client/impl/factory/MQClientInstance.java
+@@ -16,6 +16,7 @@
+ */
+ package org.apache.rocketmq.client.impl.factory;
+
++import io.netty.channel.Channel;
+ import java.util.Collections;
+ import java.util.HashMap;
+ import java.util.HashSet;
+@@ -65,6 +66,7 @@ import org.apache.rocketmq.common.message.MessageExt;
+ import org.apache.rocketmq.common.message.MessageQueue;
+ import org.apache.rocketmq.common.message.MessageQueueAssignment;
+ import org.apache.rocketmq.common.topic.TopicValidator;
++import org.apache.rocketmq.remoting.ChannelEventListener;
+ import org.apache.rocketmq.remoting.RPCHook;
+ import org.apache.rocketmq.remoting.common.HeartbeatV2Result;
+ import org.apache.rocketmq.remoting.exception.RemotingException;
+@@ -151,7 +153,38 @@ public class MQClientInstance {
+ this.nettyClientConfig.setUseTLS(clientConfig.isUseTLS());
+ this.nettyClientConfig.setSocksProxyConfig(clientConfig.getSocksProxyConfig());
+ ClientRemotingProcessor clientRemotingProcessor = new ClientRemotingProcessor(this);
+- this.mQClientAPIImpl = new MQClientAPIImpl(this.nettyClientConfig, clientRemotingProcessor, rpcHook, clientConfig);
++ ChannelEventListener channelEventListener;
++ if (clientConfig.isEnableHeartbeatChannelEventListener()) {
++ channelEventListener = new ChannelEventListener() {
++ private final ConcurrentMap<String, HashMap<Long, String>> brokerAddrTable = MQClientInstance.this.brokerAddrTable;
++ @Override
++ public void onChannelConnect(String remoteAddr, Channel channel) {
++ for (Map.Entry<String, HashMap<Long, String>> addressEntry : brokerAddrTable.entrySet()) {
++ for (String address : addressEntry.getValue().values()) {
++ if (address.equals(remoteAddr)) {
++ sendHeartbeatToAllBrokerWithLockV2(false);
++ break;
++ }
++ }
++ }
++ }
++
++ @Override
++ public void onChannelClose(String remoteAddr, Channel channel) {
++ }
++
++ @Override
++ public void onChannelException(String remoteAddr, Channel channel) {
++ }
++
++ @Override
++ public void onChannelIdle(String remoteAddr, Channel channel) {
++ }
++ };
++ } else {
++ channelEventListener = null;
++ }
++ this.mQClientAPIImpl = new MQClientAPIImpl(this.nettyClientConfig, clientRemotingProcessor, rpcHook, clientConfig, channelEventListener);
+
+ if (this.clientConfig.getNamesrvAddr() != null) {
+ this.mQClientAPIImpl.updateNameServerAddressList(this.clientConfig.getNamesrvAddr());
+diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java
+index d784351a5..8631d0447 100644
+--- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java
++++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java
+@@ -229,6 +229,8 @@ public class NettyRemotingClient extends NettyRemotingAbstract implements Remoti
+ handler.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
+ }
+
++ nettyEventExecutor.start();
++
+ TimerTask timerTaskScanResponseTable = new TimerTask() {
+ @Override
+ public void run(Timeout timeout) {
+--
+2.32.0.windows.2
+
+
+From 3808387e1389278edbe4ef023d200ecb3015622b Mon Sep 17 00:00:00 2001
+From: lk <xdkxlk@outlook.com>
+Date: Mon, 9 Oct 2023 16:07:56 +0800
+Subject: [PATCH 3/7] [ISSUE #7429] clean channel map when CLIENT_UNREGISTER in
+ proxy
+
+---
+ .../service/sysmessage/HeartbeatSyncer.java | 31 ++++++---
+ .../sysmessage/HeartbeatSyncerTest.java | 68 +++++++++++++++++++
+ 2 files changed, 88 insertions(+), 11 deletions(-)
+
+diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatSyncer.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatSyncer.java
+index f70c06b8f..fee3ea87d 100644
+--- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatSyncer.java
++++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatSyncer.java
+@@ -18,6 +18,7 @@
+ package org.apache.rocketmq.proxy.service.sysmessage;
+
+ import com.alibaba.fastjson.JSON;
++import io.netty.channel.Channel;
+ import java.nio.charset.StandardCharsets;
+ import java.util.List;
+ import java.util.Map;
+@@ -73,16 +74,8 @@ public class HeartbeatSyncer extends AbstractSystemMessageSyncer {
+ );
+ this.consumerManager.appendConsumerIdsChangeListener(new ConsumerIdsChangeListener() {
+ @Override
+- public void handle(ConsumerGroupEvent event, String s, Object... args) {
+- if (event == ConsumerGroupEvent.CLIENT_UNREGISTER) {
+- if (args == null || args.length < 1) {
+- return;
+- }
+- if (args[0] instanceof ClientChannelInfo) {
+- ClientChannelInfo clientChannelInfo = (ClientChannelInfo) args[0];
+- remoteChannelMap.remove(clientChannelInfo.getChannel().id().asLongText());
+- }
+- }
++ public void handle(ConsumerGroupEvent event, String group, Object... args) {
++ processConsumerGroupEvent(event, group, args);
+ }
+
+ @Override
+@@ -98,6 +91,18 @@ public class HeartbeatSyncer extends AbstractSystemMessageSyncer {
+ super.shutdown();
+ }
+
++ protected void processConsumerGroupEvent(ConsumerGroupEvent event, String group, Object... args) {
++ if (event == ConsumerGroupEvent.CLIENT_UNREGISTER) {
++ if (args == null || args.length < 1) {
++ return;
++ }
++ if (args[0] instanceof ClientChannelInfo) {
++ ClientChannelInfo clientChannelInfo = (ClientChannelInfo) args[0];
++ remoteChannelMap.remove(buildKey(group, clientChannelInfo.getChannel()));
++ }
++ }
++ }
++
+ public void onConsumerRegister(String consumerGroup, ClientChannelInfo clientChannelInfo,
+ ConsumeType consumeType, MessageModel messageModel, ConsumeFromWhere consumeFromWhere,
+ Set<SubscriptionData> subList) {
+@@ -189,7 +194,7 @@ public class HeartbeatSyncer extends AbstractSystemMessageSyncer {
+ }
+
+ RemoteChannel decodedChannel = RemoteChannel.decode(data.getChannelData());
+- RemoteChannel channel = remoteChannelMap.computeIfAbsent(data.getGroup() + "@" + decodedChannel.id().asLongText(), key -> decodedChannel);
++ RemoteChannel channel = remoteChannelMap.computeIfAbsent(buildKey(data.getGroup(), decodedChannel), key -> decodedChannel);
+ channel.setExtendAttribute(decodedChannel.getChannelExtendAttribute());
+ ClientChannelInfo clientChannelInfo = new ClientChannelInfo(
+ channel,
+@@ -228,4 +233,8 @@ public class HeartbeatSyncer extends AbstractSystemMessageSyncer {
+ // use local address, remoting port and grpc port to build unique local proxy Id
+ return proxyConfig.getLocalServeAddr() + "%" + proxyConfig.getRemotingListenPort() + "%" + proxyConfig.getGrpcServerPort();
+ }
++
++ private static String buildKey(String group, Channel channel) {
++ return group + "@" + channel.id().asLongText();
++ }
+ }
+diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatSyncerTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatSyncerTest.java
+index 43fba3d03..9a2c5e343 100644
+--- a/proxy/src/test/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatSyncerTest.java
++++ b/proxy/src/test/java/org/apache/rocketmq/proxy/service/sysmessage/HeartbeatSyncerTest.java
+@@ -27,6 +27,7 @@ import com.google.common.collect.Sets;
+ import io.netty.channel.Channel;
+ import io.netty.channel.ChannelId;
+ import java.time.Duration;
++import java.util.Collections;
+ import java.util.HashMap;
+ import java.util.HashSet;
+ import java.util.List;
+@@ -35,6 +36,7 @@ import java.util.concurrent.CompletableFuture;
+ import java.util.stream.Collectors;
+ import org.apache.commons.lang3.RandomStringUtils;
+ import org.apache.rocketmq.broker.client.ClientChannelInfo;
++import org.apache.rocketmq.broker.client.ConsumerGroupEvent;
+ import org.apache.rocketmq.broker.client.ConsumerManager;
+ import org.apache.rocketmq.client.impl.mqclient.MQClientAPIExt;
+ import org.apache.rocketmq.client.impl.mqclient.MQClientAPIFactory;
+@@ -320,6 +322,72 @@ public class HeartbeatSyncerTest extends InitConfigTest {
+ }
+ }
+
++ @Test
++ public void testProcessConsumerGroupEventForRemoting() {
++ String consumerGroup = "consumerGroup";
++ Channel channel = createMockChannel();
++ RemotingProxyOutClient remotingProxyOutClient = mock(RemotingProxyOutClient.class);
++ RemotingChannel remotingChannel = new RemotingChannel(remotingProxyOutClient, proxyRelayService, channel, clientId, Collections.emptySet());
++ ClientChannelInfo clientChannelInfo = new ClientChannelInfo(
++ remotingChannel,
++ clientId,
++ LanguageCode.JAVA,
++ 4
++ );
++
++ testProcessConsumerGroupEvent(consumerGroup, clientChannelInfo);
++ }
++
++ @Test
++ public void testProcessConsumerGroupEventForGrpcV2() {
++ String consumerGroup = "consumerGroup";
++ GrpcClientSettingsManager grpcClientSettingsManager = mock(GrpcClientSettingsManager.class);
++ GrpcChannelManager grpcChannelManager = mock(GrpcChannelManager.class);
++ GrpcClientChannel grpcClientChannel = new GrpcClientChannel(
++ proxyRelayService, grpcClientSettingsManager, grpcChannelManager,
++ ProxyContext.create().setRemoteAddress(remoteAddress).setLocalAddress(localAddress),
++ clientId);
++ ClientChannelInfo clientChannelInfo = new ClientChannelInfo(
++ grpcClientChannel,
++ clientId,
++ LanguageCode.JAVA,
++ 5
++ );
++
++ testProcessConsumerGroupEvent(consumerGroup, clientChannelInfo);
++ }
++
++ private void testProcessConsumerGroupEvent(String consumerGroup, ClientChannelInfo clientChannelInfo) {
++ HeartbeatSyncer heartbeatSyncer = new HeartbeatSyncer(topicRouteService, adminService, consumerManager, mqClientAPIFactory, null);
++ SendResult okSendResult = new SendResult();
++ okSendResult.setSendStatus(SendStatus.SEND_OK);
++
++ ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
++ doReturn(CompletableFuture.completedFuture(okSendResult)).when(this.mqClientAPIExt)
++ .sendMessageAsync(anyString(), anyString(), messageArgumentCaptor.capture(), any(), anyLong());
++
++ heartbeatSyncer.onConsumerRegister(
++ consumerGroup,
++ clientChannelInfo,
++ ConsumeType.CONSUME_PASSIVELY,
++ MessageModel.CLUSTERING,
++ ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET,
++ Collections.emptySet()
++ );
++ await().atMost(Duration.ofSeconds(3)).until(() -> messageArgumentCaptor.getAllValues().size() == 1);
++
++ // change local serve addr, to simulate other proxy receive messages
++ heartbeatSyncer.localProxyId = RandomStringUtils.randomAlphabetic(10);
++ ArgumentCaptor<ClientChannelInfo> channelInfoArgumentCaptor = ArgumentCaptor.forClass(ClientChannelInfo.class);
++ doReturn(true).when(consumerManager).registerConsumer(anyString(), channelInfoArgumentCaptor.capture(), any(), any(), any(), any(), anyBoolean());
++
++ heartbeatSyncer.consumeMessage(convertFromMessage(messageArgumentCaptor.getAllValues()), null);
++ assertEquals(1, heartbeatSyncer.remoteChannelMap.size());
++
++ heartbeatSyncer.processConsumerGroupEvent(ConsumerGroupEvent.CLIENT_UNREGISTER, consumerGroup, channelInfoArgumentCaptor.getValue());
++ assertTrue(heartbeatSyncer.remoteChannelMap.isEmpty());
++ }
++
+ private MessageExt convertFromMessage(Message message) {
+ MessageExt messageExt = new MessageExt();
+ messageExt.setTopic(message.getTopic());
+--
+2.32.0.windows.2
+
+
+From 0027a1486d4f2d6f7dce3010751167e883783945 Mon Sep 17 00:00:00 2001
+From: redlsz <szliu0927@gmail.com>
+Date: Mon, 9 Oct 2023 16:52:10 +0800
+Subject: [PATCH 4/7] [ISSUE #7412] Fix pop revive message error when reput
+ checkpoint
+
+---
+ .../org/apache/rocketmq/broker/processor/PopReviveService.java | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java
+index 93167db37..d5174d3d1 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java
+@@ -595,6 +595,7 @@ public class PopReviveService extends ServiceThread {
+ newCk.setCId(oldCK.getCId());
+ newCk.setTopic(oldCK.getTopic());
+ newCk.setQueueId(oldCK.getQueueId());
++ newCk.setBrokerName(oldCK.getBrokerName());
+ newCk.addDiff(0);
+ MessageExtBrokerInner ckMsg = brokerController.getPopMessageProcessor().buildCkMsg(newCk, queueId);
+ brokerController.getMessageStore().putMessage(ckMsg);
+--
+2.32.0.windows.2
+
+
+From b18e564addbcff50165a5e1d9d4ab7db789d901b Mon Sep 17 00:00:00 2001
+From: rongtong <jinrongtong5@163.com>
+Date: Mon, 9 Oct 2023 21:43:01 +0800
+Subject: [PATCH 5/7] [ISSUE #7431] Fix flaky test of
+ DLedgerControllerTest#testBrokerLifecycleListener (#7432)
+
+* Fix flaky test of DLedgerControllerTest#testBrokerLifecycleListener
+---
+ .../impl/DLedgerControllerTest.java | 26 ++++++++++++-------
+ 1 file changed, 17 insertions(+), 9 deletions(-)
+
+diff --git a/controller/src/test/java/org/apache/rocketmq/controller/impl/DLedgerControllerTest.java b/controller/src/test/java/org/apache/rocketmq/controller/impl/DLedgerControllerTest.java
+index 595a5cb65..d6e5449c5 100644
+--- a/controller/src/test/java/org/apache/rocketmq/controller/impl/DLedgerControllerTest.java
++++ b/controller/src/test/java/org/apache/rocketmq/controller/impl/DLedgerControllerTest.java
+@@ -63,7 +63,8 @@ public class DLedgerControllerTest {
+ private List<String> baseDirs;
+ private List<DLedgerController> controllers;
+
+- public DLedgerController launchController(final String group, final String peers, final String selfId, final boolean isEnableElectUncleanMaster) {
++ public DLedgerController launchController(final String group, final String peers, final String selfId,
++ final boolean isEnableElectUncleanMaster) {
+ String tmpdir = System.getProperty("java.io.tmpdir");
+ final String path = (StringUtils.endsWith(tmpdir, File.separator) ? tmpdir : tmpdir + File.separator) + group + File.separator + selfId;
+ baseDirs.add(path);
+@@ -121,11 +122,11 @@ public class DLedgerControllerTest {
+ final RegisterBrokerToControllerRequestHeader registerBrokerToControllerRequestHeader = new RegisterBrokerToControllerRequestHeader(clusterName, brokerName, nextBrokerId, brokerAddress);
+ RemotingCommand remotingCommand2 = leader.registerBroker(registerBrokerToControllerRequestHeader).get(2, TimeUnit.SECONDS);
+
+-
+ assertEquals(ResponseCode.SUCCESS, remotingCommand2.getCode());
+ }
+
+- public void brokerTryElectMaster(Controller leader, String clusterName, String brokerName, String brokerAddress, Long brokerId,
++ public void brokerTryElectMaster(Controller leader, String clusterName, String brokerName, String brokerAddress,
++ Long brokerId,
+ boolean exceptSuccess) throws Exception {
+ final ElectMasterRequestHeader electMasterRequestHeader = ElectMasterRequestHeader.ofBrokerTrigger(clusterName, brokerName, brokerId);
+ RemotingCommand command = leader.electMaster(electMasterRequestHeader).get(2, TimeUnit.SECONDS);
+@@ -186,9 +187,9 @@ public class DLedgerControllerTest {
+ registerNewBroker(leader, DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, DEFAULT_IP[1], 2L);
+ registerNewBroker(leader, DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, DEFAULT_IP[2], 3L);
+ // try elect
+- brokerTryElectMaster(leader, DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, DEFAULT_IP[0], 1L,true);
+- brokerTryElectMaster(leader, DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, DEFAULT_IP[1], 2L, false);
+- brokerTryElectMaster(leader, DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, DEFAULT_IP[2], 3L,false);
++ brokerTryElectMaster(leader, DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, DEFAULT_IP[0], 1L, true);
++ brokerTryElectMaster(leader, DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, DEFAULT_IP[1], 2L, false);
++ brokerTryElectMaster(leader, DEFAULT_CLUSTER_NAME, DEFAULT_BROKER_NAME, DEFAULT_IP[2], 3L, false);
+ final RemotingCommand getInfoResponse = leader.getReplicaInfo(new GetReplicaInfoRequestHeader(DEFAULT_BROKER_NAME)).get(10, TimeUnit.SECONDS);
+ final GetReplicaInfoResponseHeader replicaInfo = (GetReplicaInfoResponseHeader) getInfoResponse.readCustomHeader();
+ assertEquals(1, replicaInfo.getMasterEpoch().intValue());
+@@ -239,6 +240,8 @@ public class DLedgerControllerTest {
+ @Test
+ public void testBrokerLifecycleListener() throws Exception {
+ final DLedgerController leader = mockMetaData(false);
++
++ assertTrue(leader.isLeaderState());
+ // Mock that master broker has been inactive, and try to elect a new master from sync-state-set
+ // But we shut down two controller, so the ElectMasterEvent will be appended to DLedger failed.
+ // So the statemachine still keep the stale master's information
+@@ -247,15 +250,20 @@ public class DLedgerControllerTest {
+ dLedgerController.shutdown();
+ controllers.remove(dLedgerController);
+ }
++
+ final ElectMasterRequestHeader request = ElectMasterRequestHeader.ofControllerTrigger(DEFAULT_BROKER_NAME);
+ setBrokerElectPolicy(leader, 1L);
+ Exception exception = null;
++ RemotingCommand remotingCommand = null;
+ try {
+- leader.electMaster(request).get(5, TimeUnit.SECONDS);
++ remotingCommand = leader.electMaster(request).get(5, TimeUnit.SECONDS);
+ } catch (Exception e) {
+ exception = e;
+ }
+- assertNotNull(exception);
++
++ assertTrue(exception != null ||
++ remotingCommand != null && remotingCommand.getCode() == ResponseCode.CONTROLLER_NOT_LEADER);
++
+ // Shut down leader controller
+ leader.shutdown();
+ controllers.remove(leader);
+@@ -272,7 +280,7 @@ public class DLedgerControllerTest {
+ setBrokerAlivePredicate(newLeader, 1L);
+ // Check if the statemachine is stale
+ final RemotingCommand resp = newLeader.getReplicaInfo(new GetReplicaInfoRequestHeader(DEFAULT_BROKER_NAME)).
+- get(10, TimeUnit.SECONDS);
++ get(10, TimeUnit.SECONDS);
+ final GetReplicaInfoResponseHeader replicaInfo = (GetReplicaInfoResponseHeader) resp.readCustomHeader();
+ assertEquals(1, replicaInfo.getMasterBrokerId().longValue());
+ assertEquals(1, replicaInfo.getMasterEpoch().intValue());
+--
+2.32.0.windows.2
+
+
+From 38d3d5d95d371ac89f7d491a4c8719b4a22c60e1 Mon Sep 17 00:00:00 2001
+From: mxsm <ljbmxsm@gmail.com>
+Date: Tue, 10 Oct 2023 09:37:04 +0800
+Subject: [PATCH 6/7] [ISSUE #7433]Update the version in the README.md document
+ to 5.1.4 (#7434)
+
+---
+ README.md | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/README.md b/README.md
+index 56d253ce1..5aaa2ba73 100644
+--- a/README.md
++++ b/README.md
+@@ -49,21 +49,21 @@ $ java -version
+ java version "1.8.0_121"
+ ```
+
+-For Windows users, click [here](https://dist.apache.org/repos/dist/release/rocketmq/5.1.3/rocketmq-all-5.1.3-bin-release.zip) to download the 5.1.3 RocketMQ binary release,
++For Windows users, click [here](https://dist.apache.org/repos/dist/release/rocketmq/5.1.4/rocketmq-all-5.1.4-bin-release.zip) to download the 5.1.4 RocketMQ binary release,
+ unpack it to your local disk, such as `D:\rocketmq`.
+ For macOS and Linux users, execute following commands:
+
+ ```shell
+ # Download release from the Apache mirror
+-$ wget https://dist.apache.org/repos/dist/release/rocketmq/5.1.3/rocketmq-all-5.1.3-bin-release.zip
++$ wget https://dist.apache.org/repos/dist/release/rocketmq/5.1.4/rocketmq-all-5.1.4-bin-release.zip
+
+ # Unpack the release
+-$ unzip rocketmq-all-5.1.3-bin-release.zip
++$ unzip rocketmq-all-5.1.4-bin-release.zip
+ ```
+
+ Prepare a terminal and change to the extracted `bin` directory:
+ ```shell
+-$ cd rocketmq-all-5.1.3-bin-release/bin
++$ cd rocketmq-all-5.1.4-bin-release/bin
+ ```
+
+ **1) Start NameServer**
+--
+2.32.0.windows.2
+
+
+From 4acb43ecee03e429d036e3ff4c28bd402d1b30c7 Mon Sep 17 00:00:00 2001
+From: Zhouxiang Zhan <zhouxzhan@apache.org>
+Date: Tue, 10 Oct 2023 13:54:01 +0800
+Subject: [PATCH 7/7] [ISSUE #7330] Add goaway and reconnection mechanism
+ (#7331)
+
+* Add shutdown wait for NettyRemotingServer
+
+* Add goaway and reconnection mechanism
+
+* Add client version check
+
+* Add enableTransparentRetry for NettyClientConfig
+
+* Add enableReconnectForGoAway for NettyClientConfig
+
+* fix unit test
+
+* fix client version check
+---
+ .../remoting/netty/NettyClientConfig.java | 30 ++++
+ .../remoting/netty/NettyRemotingAbstract.java | 15 ++
+ .../remoting/netty/NettyRemotingClient.java | 153 ++++++++++++++++--
+ .../remoting/netty/NettyRemotingServer.java | 31 ++--
+ .../remoting/netty/NettyServerConfig.java | 19 +++
+ .../remoting/protocol/ResponseCode.java | 2 +
+ .../netty/NettyRemotingClientTest.java | 39 ++---
+ 7 files changed, 239 insertions(+), 50 deletions(-)
+
+diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyClientConfig.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyClientConfig.java
+index b2e7df754..c28288786 100644
+--- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyClientConfig.java
++++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyClientConfig.java
+@@ -53,6 +53,12 @@ public class NettyClientConfig {
+ private boolean disableCallbackExecutor = false;
+ private boolean disableNettyWorkerGroup = false;
+
++ private long maxReconnectIntervalTimeSeconds = 60;
++
++ private boolean enableReconnectForGoAway = true;
++
++ private boolean enableTransparentRetry = true;
++
+ public boolean isClientCloseSocketIfTimeout() {
+ return clientCloseSocketIfTimeout;
+ }
+@@ -181,6 +187,30 @@ public class NettyClientConfig {
+ this.disableNettyWorkerGroup = disableNettyWorkerGroup;
+ }
+
++ public long getMaxReconnectIntervalTimeSeconds() {
++ return maxReconnectIntervalTimeSeconds;
++ }
++
++ public void setMaxReconnectIntervalTimeSeconds(long maxReconnectIntervalTimeSeconds) {
++ this.maxReconnectIntervalTimeSeconds = maxReconnectIntervalTimeSeconds;
++ }
++
++ public boolean isEnableReconnectForGoAway() {
++ return enableReconnectForGoAway;
++ }
++
++ public void setEnableReconnectForGoAway(boolean enableReconnectForGoAway) {
++ this.enableReconnectForGoAway = enableReconnectForGoAway;
++ }
++
++ public boolean isEnableTransparentRetry() {
++ return enableTransparentRetry;
++ }
++
++ public void setEnableTransparentRetry(boolean enableTransparentRetry) {
++ this.enableTransparentRetry = enableTransparentRetry;
++ }
++
+ public String getSocksProxyConfig() {
+ return socksProxyConfig;
+ }
+diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingAbstract.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingAbstract.java
+index 12e66f913..07ace28ea 100644
+--- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingAbstract.java
++++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingAbstract.java
+@@ -40,9 +40,11 @@ import java.util.concurrent.Semaphore;
+ import java.util.concurrent.TimeUnit;
+ import java.util.concurrent.TimeoutException;
+ import java.util.concurrent.atomic.AtomicReference;
++import java.util.concurrent.atomic.AtomicBoolean;
+ import java.util.function.Consumer;
+ import javax.annotation.Nullable;
+ import org.apache.rocketmq.common.AbortProcessException;
++import org.apache.rocketmq.common.MQVersion;
+ import org.apache.rocketmq.common.Pair;
+ import org.apache.rocketmq.common.ServiceThread;
+ import org.apache.rocketmq.common.UtilAll;
+@@ -60,6 +62,7 @@ import org.apache.rocketmq.remoting.exception.RemotingTooMuchRequestException;
+ import org.apache.rocketmq.remoting.metrics.RemotingMetricsManager;
+ import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+ import org.apache.rocketmq.remoting.protocol.RemotingSysResponseCode;
++import org.apache.rocketmq.remoting.protocol.ResponseCode;
+
+ import static org.apache.rocketmq.remoting.metrics.RemotingMetricsConstant.LABEL_IS_LONG_POLLING;
+ import static org.apache.rocketmq.remoting.metrics.RemotingMetricsConstant.LABEL_REQUEST_CODE;
+@@ -120,6 +123,8 @@ public abstract class NettyRemotingAbstract {
+ */
+ protected List<RPCHook> rpcHooks = new ArrayList<>();
+
++ protected AtomicBoolean isShuttingDown = new AtomicBoolean(false);
++
+ static {
+ NettyLogger.initNettyLogger();
+ }
+@@ -264,6 +269,16 @@ public abstract class NettyRemotingAbstract {
+
+ Runnable run = buildProcessRequestHandler(ctx, cmd, pair, opaque);
+
++ if (isShuttingDown.get()) {
++ if (cmd.getVersion() > MQVersion.Version.V5_1_4.ordinal()) {
++ final RemotingCommand response = RemotingCommand.createResponseCommand(ResponseCode.GO_AWAY,
++ "please go away");
++ response.setOpaque(opaque);
++ writeResponse(ctx.channel(), cmd, response);
++ return;
++ }
++ }
++
+ if (pair.getObject1().rejectRequest()) {
+ final RemotingCommand response = RemotingCommand.createResponseCommand(RemotingSysResponseCode.SYSTEM_BUSY,
+ "[REJECTREQUEST]system busy, start flow control for a while");
+diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java
+index 8631d0447..4bc51bd83 100644
+--- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java
++++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java
+@@ -18,6 +18,7 @@ package org.apache.rocketmq.remoting.netty;
+
+ import com.alibaba.fastjson.JSON;
+ import com.alibaba.fastjson.TypeReference;
++import com.google.common.base.Stopwatch;
+ import io.netty.bootstrap.Bootstrap;
+ import io.netty.buffer.PooledByteBufAllocator;
+ import io.netty.channel.Channel;
+@@ -48,6 +49,7 @@ import java.io.IOException;
+ import java.net.InetSocketAddress;
+ import java.net.SocketAddress;
+ import java.security.cert.CertificateException;
++import java.time.Duration;
+ import java.util.ArrayList;
+ import java.util.Collections;
+ import java.util.HashMap;
+@@ -57,6 +59,7 @@ import java.util.Map;
+ import java.util.Random;
+ import java.util.Set;
+ import java.util.concurrent.ArrayBlockingQueue;
++import java.util.concurrent.CompletableFuture;
+ import java.util.concurrent.ConcurrentHashMap;
+ import java.util.concurrent.ConcurrentMap;
+ import java.util.concurrent.ExecutorService;
+@@ -66,6 +69,7 @@ import java.util.concurrent.atomic.AtomicInteger;
+ import java.util.concurrent.atomic.AtomicReference;
+ import java.util.concurrent.locks.Lock;
+ import java.util.concurrent.locks.ReentrantLock;
++import java.util.concurrent.locks.ReentrantReadWriteLock;
+ import org.apache.commons.lang3.StringUtils;
+ import org.apache.rocketmq.common.Pair;
+ import org.apache.rocketmq.common.ThreadFactoryImpl;
+@@ -82,6 +86,7 @@ import org.apache.rocketmq.remoting.exception.RemotingSendRequestException;
+ import org.apache.rocketmq.remoting.exception.RemotingTimeoutException;
+ import org.apache.rocketmq.remoting.exception.RemotingTooMuchRequestException;
+ import org.apache.rocketmq.remoting.protocol.RemotingCommand;
++import org.apache.rocketmq.remoting.protocol.ResponseCode;
+ import org.apache.rocketmq.remoting.proxy.SocksProxyConfig;
+
+ public class NettyRemotingClient extends NettyRemotingAbstract implements RemotingClient {
+@@ -97,6 +102,7 @@ public class NettyRemotingClient extends NettyRemotingAbstract implements Remoti
+ private final Map<String /* cidr */, SocksProxyConfig /* proxy */> proxyMap = new HashMap<>();
+ private final ConcurrentHashMap<String /* cidr */, Bootstrap> bootstrapMap = new ConcurrentHashMap<>();
+ private final ConcurrentMap<String /* addr */, ChannelWrapper> channelTables = new ConcurrentHashMap<>();
++ private final ConcurrentMap<Channel, ChannelWrapper> channelWrapperTables = new ConcurrentHashMap<>();
+
+ private final HashedWheelTimer timer = new HashedWheelTimer(r -> new Thread(r, "ClientHouseKeepingService"));
+
+@@ -356,9 +362,10 @@ public class NettyRemotingClient extends NettyRemotingAbstract implements Remoti
+ this.timer.stop();
+
+ for (String addr : this.channelTables.keySet()) {
+- this.closeChannel(addr, this.channelTables.get(addr).getChannel());
++ this.channelTables.get(addr).close();
+ }
+
++ this.channelWrapperTables.clear();
+ this.channelTables.clear();
+
+ this.eventLoopGroupWorker.shutdownGracefully();
+@@ -416,7 +423,10 @@ public class NettyRemotingClient extends NettyRemotingAbstract implements Remoti
+ }
+
+ if (removeItemFromTable) {
+- this.channelTables.remove(addrRemote);
++ ChannelWrapper channelWrapper = this.channelWrapperTables.remove(channel);
++ if (channelWrapper != null && channelWrapper.tryClose(channel)) {
++ this.channelTables.remove(addrRemote);
++ }
+ LOGGER.info("closeChannel: the channel[{}] was removed from channel table", addrRemote);
+ }
+
+@@ -463,7 +473,10 @@ public class NettyRemotingClient extends NettyRemotingAbstract implements Remoti
+ }
+
+ if (removeItemFromTable) {
+- this.channelTables.remove(addrRemote);
++ ChannelWrapper channelWrapper = this.channelWrapperTables.remove(channel);
++ if (channelWrapper != null && channelWrapper.tryClose(channel)) {
++ this.channelTables.remove(addrRemote);
++ }
+ LOGGER.info("closeChannel: the channel[{}] was removed from channel table", addrRemote);
+ RemotingHelper.closeChannel(channel);
+ }
+@@ -511,7 +524,7 @@ public class NettyRemotingClient extends NettyRemotingAbstract implements Remoti
+ if (addr.contains(namesrvAddr)) {
+ ChannelWrapper channelWrapper = this.channelTables.get(addr);
+ if (channelWrapper != null) {
+- closeChannel(channelWrapper.getChannel());
++ channelWrapper.close();
+ }
+ }
+ }
+@@ -689,8 +702,9 @@ public class NettyRemotingClient extends NettyRemotingAbstract implements Remoti
+ ChannelFuture channelFuture = fetchBootstrap(addr)
+ .connect(hostAndPort[0], Integer.parseInt(hostAndPort[1]));
+ LOGGER.info("createChannel: begin to connect remote host[{}] asynchronously", addr);
+- cw = new ChannelWrapper(channelFuture);
++ cw = new ChannelWrapper(addr, channelFuture);
+ this.channelTables.put(addr, cw);
++ this.channelWrapperTables.put(channelFuture.channel(), cw);
+ }
+ } catch (Exception e) {
+ LOGGER.error("createChannel: create channel exception", e);
+@@ -758,6 +772,64 @@ public class NettyRemotingClient extends NettyRemotingAbstract implements Remoti
+ }
+ }
+
++ @Override
++ public CompletableFuture<RemotingCommand> invoke(String addr, RemotingCommand request,
++ long timeoutMillis) {
++ CompletableFuture<RemotingCommand> future = new CompletableFuture<>();
++ try {
++ final Channel channel = this.getAndCreateChannel(addr);
++ if (channel != null && channel.isActive()) {
++ return invokeImpl(channel, request, timeoutMillis).whenComplete((v, t) -> {
++ if (t == null) {
++ updateChannelLastResponseTime(addr);
++ }
++ }).thenApply(ResponseFuture::getResponseCommand);
++ } else {
++ this.closeChannel(addr, channel);
++ future.completeExceptionally(new RemotingConnectException(addr));
++ }
++ } catch (Throwable t) {
++ future.completeExceptionally(t);
++ }
++ return future;
++ }
++
++ @Override
++ public CompletableFuture<ResponseFuture> invokeImpl(final Channel channel, final RemotingCommand request,
++ final long timeoutMillis) {
++ Stopwatch stopwatch = Stopwatch.createStarted();
++ return super.invokeImpl(channel, request, timeoutMillis).thenCompose(responseFuture -> {
++ RemotingCommand response = responseFuture.getResponseCommand();
++ if (response.getCode() == ResponseCode.GO_AWAY) {
++ if (nettyClientConfig.isEnableReconnectForGoAway()) {
++ ChannelWrapper channelWrapper = channelWrapperTables.computeIfPresent(channel, (channel0, channelWrapper0) -> {
++ try {
++ if (channelWrapper0.reconnect()) {
++ LOGGER.info("Receive go away from channel {}, recreate the channel", channel0);
++ channelWrapperTables.put(channelWrapper0.getChannel(), channelWrapper0);
++ }
++ } catch (Throwable t) {
++ LOGGER.error("Channel {} reconnect error", channelWrapper0, t);
++ }
++ return channelWrapper0;
++ });
++ if (channelWrapper != null) {
++ if (nettyClientConfig.isEnableTransparentRetry()) {
++ long duration = stopwatch.elapsed(TimeUnit.MILLISECONDS);
++ stopwatch.stop();
++ RemotingCommand retryRequest = RemotingCommand.createRequestCommand(request.getCode(), request.readCustomHeader());
++ Channel retryChannel = channelWrapper.getChannel();
++ if (channel != retryChannel) {
++ return super.invokeImpl(retryChannel, retryRequest, timeoutMillis - duration);
++ }
++ }
++ }
++ }
++ }
++ return CompletableFuture.completedFuture(responseFuture);
++ });
++ }
++
+ @Override
+ public void registerProcessor(int requestCode, NettyRequestProcessor processor, ExecutorService executor) {
+ ExecutorService executorThis = executor;
+@@ -877,30 +949,41 @@ public class NettyRemotingClient extends NettyRemotingAbstract implements Remoti
+ }
+ }
+
+- static class ChannelWrapper {
+- private final ChannelFuture channelFuture;
++ class ChannelWrapper {
++ private final ReentrantReadWriteLock lock;
++ private ChannelFuture channelFuture;
+ // only affected by sync or async request, oneway is not included.
++ private ChannelFuture channelToClose;
+ private long lastResponseTime;
++ private volatile long lastReconnectTimestamp = 0L;
++ private final String channelAddress;
+
+- public ChannelWrapper(ChannelFuture channelFuture) {
++ public ChannelWrapper(String address, ChannelFuture channelFuture) {
++ this.lock = new ReentrantReadWriteLock();
+ this.channelFuture = channelFuture;
+ this.lastResponseTime = System.currentTimeMillis();
++ this.channelAddress = address;
+ }
+
+ public boolean isOK() {
+- return this.channelFuture.channel() != null && this.channelFuture.channel().isActive();
++ return getChannel() != null && getChannel().isActive();
+ }
+
+ public boolean isWritable() {
+- return this.channelFuture.channel().isWritable();
++ return getChannel().isWritable();
+ }
+
+ private Channel getChannel() {
+- return this.channelFuture.channel();
++ return getChannelFuture().channel();
+ }
+
+ public ChannelFuture getChannelFuture() {
+- return channelFuture;
++ lock.readLock().lock();
++ try {
++ return this.channelFuture;
++ } finally {
++ lock.readLock().unlock();
++ }
+ }
+
+ public long getLastResponseTime() {
+@@ -910,6 +993,52 @@ public class NettyRemotingClient extends NettyRemotingAbstract implements Remoti
+ public void updateLastResponseTime() {
+ this.lastResponseTime = System.currentTimeMillis();
+ }
++
++ public boolean reconnect() {
++ if (lock.writeLock().tryLock()) {
++ try {
++ if (lastReconnectTimestamp == 0L || System.currentTimeMillis() - lastReconnectTimestamp > Duration.ofSeconds(nettyClientConfig.getMaxReconnectIntervalTimeSeconds()).toMillis()) {
++ channelToClose = channelFuture;
++ String[] hostAndPort = getHostAndPort(channelAddress);
++ channelFuture = fetchBootstrap(channelAddress)
++ .connect(hostAndPort[0], Integer.parseInt(hostAndPort[1]));
++ lastReconnectTimestamp = System.currentTimeMillis();
++ return true;
++ }
++ } finally {
++ lock.writeLock().unlock();
++ }
++ }
++ return false;
++ }
++
++ public boolean tryClose(Channel channel) {
++ try {
++ lock.readLock().lock();
++ if (channelFuture != null) {
++ if (channelFuture.channel().equals(channel)) {
++ return true;
++ }
++ }
++ } finally {
++ lock.readLock().unlock();
++ }
++ return false;
++ }
++
++ public void close() {
++ try {
++ lock.writeLock().lock();
++ if (channelFuture != null) {
++ closeChannel(channelFuture.channel());
++ }
++ if (channelToClose != null) {
++ closeChannel(channelToClose.channel());
++ }
++ } finally {
++ lock.writeLock().unlock();
++ }
++ }
+ }
+
+ class InvokeCallbackWrapper implements InvokeCallback {
+diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java
+index aa0d46542..735d36168 100644
+--- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java
++++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java
+@@ -53,6 +53,19 @@ import io.netty.util.HashedWheelTimer;
+ import io.netty.util.Timeout;
+ import io.netty.util.TimerTask;
+ import io.netty.util.concurrent.DefaultEventExecutorGroup;
++import java.io.IOException;
++import java.net.InetSocketAddress;
++import java.security.cert.CertificateException;
++import java.time.Duration;
++import java.util.List;
++import java.util.NoSuchElementException;
++import java.util.concurrent.ConcurrentHashMap;
++import java.util.concurrent.ConcurrentMap;
++import java.util.concurrent.ExecutorService;
++import java.util.concurrent.Executors;
++import java.util.concurrent.ScheduledExecutorService;
++import java.util.concurrent.ThreadPoolExecutor;
++import java.util.concurrent.TimeUnit;
+ import org.apache.commons.collections.CollectionUtils;
+ import org.apache.commons.lang3.StringUtils;
+ import org.apache.rocketmq.common.Pair;
+@@ -74,19 +87,6 @@ import org.apache.rocketmq.remoting.exception.RemotingTimeoutException;
+ import org.apache.rocketmq.remoting.exception.RemotingTooMuchRequestException;
+ import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+
+-import java.io.IOException;
+-import java.net.InetSocketAddress;
+-import java.security.cert.CertificateException;
+-import java.util.List;
+-import java.util.NoSuchElementException;
+-import java.util.concurrent.ConcurrentHashMap;
+-import java.util.concurrent.ConcurrentMap;
+-import java.util.concurrent.ExecutorService;
+-import java.util.concurrent.Executors;
+-import java.util.concurrent.ScheduledExecutorService;
+-import java.util.concurrent.ThreadPoolExecutor;
+-import java.util.concurrent.TimeUnit;
+-
+ @SuppressWarnings("NullableProblems")
+ public class NettyRemotingServer extends NettyRemotingAbstract implements RemotingServer {
+ private static final Logger log = LoggerFactory.getLogger(LoggerName.ROCKETMQ_REMOTING_NAME);
+@@ -305,6 +305,10 @@ public class NettyRemotingServer extends NettyRemotingAbstract implements Remoti
+ @Override
+ public void shutdown() {
+ try {
++ if (nettyServerConfig.isEnableShutdownGracefully() && isShuttingDown.compareAndSet(false, true)) {
++ Thread.sleep(Duration.ofSeconds(nettyServerConfig.getShutdownWaitTimeSeconds()).toMillis());
++ }
++
+ this.timer.stop();
+
+ this.eventLoopGroupBoss.shutdownGracefully();
+@@ -736,6 +740,7 @@ public class NettyRemotingServer extends NettyRemotingAbstract implements Remoti
+
+ @Override
+ public void shutdown() {
++ isShuttingDown.set(true);
+ if (this.serverChannel != null) {
+ try {
+ this.serverChannel.close().await(5, TimeUnit.SECONDS);
+diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyServerConfig.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyServerConfig.java
+index 59ef2c84f..756661f62 100644
+--- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyServerConfig.java
++++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyServerConfig.java
+@@ -38,6 +38,9 @@ public class NettyServerConfig implements Cloneable {
+ private int serverSocketBacklog = NettySystemConfig.socketBacklog;
+ private boolean serverPooledByteBufAllocatorEnable = true;
+
++ private boolean enableShutdownGracefully = false;
++ private int shutdownWaitTimeSeconds = 30;
++
+ /**
+ * make install
+ *
+@@ -171,4 +174,20 @@ public class NettyServerConfig implements Cloneable {
+ public void setWriteBufferHighWaterMark(int writeBufferHighWaterMark) {
+ this.writeBufferHighWaterMark = writeBufferHighWaterMark;
+ }
++
++ public boolean isEnableShutdownGracefully() {
++ return enableShutdownGracefully;
++ }
++
++ public void setEnableShutdownGracefully(boolean enableShutdownGracefully) {
++ this.enableShutdownGracefully = enableShutdownGracefully;
++ }
++
++ public int getShutdownWaitTimeSeconds() {
++ return shutdownWaitTimeSeconds;
++ }
++
++ public void setShutdownWaitTimeSeconds(int shutdownWaitTimeSeconds) {
++ this.shutdownWaitTimeSeconds = shutdownWaitTimeSeconds;
++ }
+ }
+diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/ResponseCode.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/ResponseCode.java
+index e81dadf2e..be945c48f 100644
+--- a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/ResponseCode.java
++++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/ResponseCode.java
+@@ -99,6 +99,8 @@ public class ResponseCode extends RemotingSysResponseCode {
+ public static final int RPC_SEND_TO_CHANNEL_FAILED = -1004;
+ public static final int RPC_TIME_OUT = -1006;
+
++ public static final int GO_AWAY = 1500;
++
+ /**
+ * Controller response code
+ */
+diff --git a/remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyRemotingClientTest.java b/remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyRemotingClientTest.java
+index e72e7bd53..1cc6b4f46 100644
+--- a/remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyRemotingClientTest.java
++++ b/remoting/src/test/java/org/apache/rocketmq/remoting/netty/NettyRemotingClientTest.java
+@@ -47,7 +47,6 @@ import static org.mockito.ArgumentMatchers.any;
+ import static org.mockito.ArgumentMatchers.anyLong;
+ import static org.mockito.ArgumentMatchers.anyString;
+ import static org.mockito.ArgumentMatchers.eq;
+-import static org.mockito.Mockito.doAnswer;
+ import static org.mockito.Mockito.doReturn;
+ import static org.mockito.Mockito.mock;
+ import static org.mockito.Mockito.never;
+@@ -74,13 +73,11 @@ public class NettyRemotingClientTest {
+
+ RemotingCommand response = RemotingCommand.createResponseCommand(null);
+ response.setCode(ResponseCode.SUCCESS);
+- doAnswer(invocation -> {
+- InvokeCallback callback = invocation.getArgument(3);
+- ResponseFuture responseFuture = new ResponseFuture(null, request.getOpaque(), 3 * 1000, null, null);
+- responseFuture.setResponseCommand(response);
+- callback.operationSucceed(responseFuture.getResponseCommand());
+- return null;
+- }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any(InvokeCallback.class));
++ ResponseFuture responseFuture = new ResponseFuture(null, request.getOpaque(), 3 * 1000, null, null);
++ responseFuture.setResponseCommand(response);
++ CompletableFuture<RemotingCommand> future0 = new CompletableFuture<>();
++ future0.complete(responseFuture.getResponseCommand());
++ doReturn(future0).when(remotingClient).invoke(anyString(), any(RemotingCommand.class), anyLong());
+
+ CompletableFuture<RemotingCommand> future = remotingClient.invoke("0.0.0.0", request, 1000);
+ RemotingCommand actual = future.get();
+@@ -93,11 +90,9 @@ public class NettyRemotingClientTest {
+
+ RemotingCommand response = RemotingCommand.createResponseCommand(null);
+ response.setCode(ResponseCode.SUCCESS);
+- doAnswer(invocation -> {
+- InvokeCallback callback = invocation.getArgument(3);
+- callback.operationFail(new RemotingSendRequestException(null));
+- return null;
+- }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any(InvokeCallback.class));
++ CompletableFuture<RemotingCommand> future0 = new CompletableFuture<>();
++ future0.completeExceptionally(new RemotingSendRequestException(null));
++ doReturn(future0).when(remotingClient).invoke(anyString(), any(RemotingCommand.class), anyLong());
+
+ CompletableFuture<RemotingCommand> future = remotingClient.invoke("0.0.0.0", request, 1000);
+ Throwable thrown = catchThrowable(future::get);
+@@ -110,11 +105,9 @@ public class NettyRemotingClientTest {
+
+ RemotingCommand response = RemotingCommand.createResponseCommand(null);
+ response.setCode(ResponseCode.SUCCESS);
+- doAnswer(invocation -> {
+- InvokeCallback callback = invocation.getArgument(3);
+- callback.operationFail(new RemotingTimeoutException(""));
+- return null;
+- }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any(InvokeCallback.class));
++ CompletableFuture<RemotingCommand> future0 = new CompletableFuture<>();
++ future0.completeExceptionally(new RemotingTimeoutException(""));
++ doReturn(future0).when(remotingClient).invoke(anyString(), any(RemotingCommand.class), anyLong());
+
+ CompletableFuture<RemotingCommand> future = remotingClient.invoke("0.0.0.0", request, 1000);
+ Throwable thrown = catchThrowable(future::get);
+@@ -125,13 +118,9 @@ public class NettyRemotingClientTest {
+ public void testRemotingException() throws Exception {
+ RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.PULL_MESSAGE, null);
+
+- RemotingCommand response = RemotingCommand.createResponseCommand(null);
+- response.setCode(ResponseCode.SUCCESS);
+- doAnswer(invocation -> {
+- InvokeCallback callback = invocation.getArgument(3);
+- callback.operationFail(new RemotingException(null));
+- return null;
+- }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any(InvokeCallback.class));
++ CompletableFuture<RemotingCommand> future0 = new CompletableFuture<>();
++ future0.completeExceptionally(new RemotingException(""));
++ doReturn(future0).when(remotingClient).invoke(anyString(), any(RemotingCommand.class), anyLong());
+
+ CompletableFuture<RemotingCommand> future = remotingClient.invoke("0.0.0.0", request, 1000);
+ Throwable thrown = catchThrowable(future::get);
+--
+2.32.0.windows.2
+
diff --git a/patch021-backport-some-enhancements.patch b/patch021-backport-some-enhancements.patch
new file mode 100644
index 000000000..839b65fea
--- /dev/null
+++ b/patch021-backport-some-enhancements.patch
@@ -0,0 +1,344 @@
+From dc3f22ffe9eb83ace991b68921076093c7c0da5f Mon Sep 17 00:00:00 2001
+From: LetLetMe <43874697+LetLetMe@users.noreply.github.com>
+Date: Tue, 10 Oct 2023 17:39:23 +0800
+Subject: [PATCH 1/6] add getter for class Message ,fix json serialize bug
+ (#7439)
+
+Co-authored-by: LetLetMe <allen.hyt@alibaba-inc.com>
+---
+ .../rocketmq/common/message/Message.java | 24 ++++++++++++++++++-
+ 1 file changed, 23 insertions(+), 1 deletion(-)
+
+diff --git a/common/src/main/java/org/apache/rocketmq/common/message/Message.java b/common/src/main/java/org/apache/rocketmq/common/message/Message.java
+index e02b526a1..c7997c473 100644
+--- a/common/src/main/java/org/apache/rocketmq/common/message/Message.java
++++ b/common/src/main/java/org/apache/rocketmq/common/message/Message.java
+@@ -218,14 +218,36 @@ public class Message implements Serializable {
+ public void setDelayTimeSec(long sec) {
+ this.putProperty(MessageConst.PROPERTY_TIMER_DELAY_SEC, String.valueOf(sec));
+ }
++
++ public long getDelayTimeSec() {
++ String t = this.getProperty(MessageConst.PROPERTY_TIMER_DELAY_SEC);
++ if (t != null) {
++ return Long.parseLong(t);
++ }
++ return 0;
++ }
++
+ public void setDelayTimeMs(long timeMs) {
+ this.putProperty(MessageConst.PROPERTY_TIMER_DELAY_MS, String.valueOf(timeMs));
+ }
++
++ public long getDelayTimeMs() {
++ String t = this.getProperty(MessageConst.PROPERTY_TIMER_DELAY_MS);
++ if (t != null) {
++ return Long.parseLong(t);
++ }
++ return 0;
++ }
++
+ public void setDeliverTimeMs(long timeMs) {
+ this.putProperty(MessageConst.PROPERTY_TIMER_DELIVER_MS, String.valueOf(timeMs));
+ }
+
+ public long getDeliverTimeMs() {
+- return Long.parseLong(this.getUserProperty(MessageConst.PROPERTY_TIMER_DELIVER_MS));
++ String t = this.getProperty(MessageConst.PROPERTY_TIMER_DELIVER_MS);
++ if (t != null) {
++ return Long.parseLong(t);
++ }
++ return 0;
+ }
+ }
+--
+2.32.0.windows.2
+
+
+From 7e4879a3bc120d6289aabc8354a2811f349ac8a6 Mon Sep 17 00:00:00 2001
+From: fujian-zfj <2573259572@qq.com>
+Date: Wed, 11 Oct 2023 14:45:07 +0800
+Subject: [PATCH 2/6] [ISSUE #7441] Fix log "Init the confirmOffset" keep
+ printing error in controller mode (#7442)
+
+* typo int readme[ecosystem]
+
+* fix keep printing log problem
+---
+ store/src/main/java/org/apache/rocketmq/store/CommitLog.java | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/store/src/main/java/org/apache/rocketmq/store/CommitLog.java b/store/src/main/java/org/apache/rocketmq/store/CommitLog.java
+index 456bf2b86..f98e9a284 100644
+--- a/store/src/main/java/org/apache/rocketmq/store/CommitLog.java
++++ b/store/src/main/java/org/apache/rocketmq/store/CommitLog.java
+@@ -580,7 +580,7 @@ public class CommitLog implements Swappable {
+ return this.defaultMessageStore.getMaxPhyOffset();
+ }
+ // First time it will compute the confirmOffset.
+- if (this.confirmOffset <= 0) {
++ if (this.confirmOffset < 0) {
+ setConfirmOffset(((AutoSwitchHAService) this.defaultMessageStore.getHaService()).computeConfirmOffset());
+ log.info("Init the confirmOffset to {}.", this.confirmOffset);
+ }
+--
+2.32.0.windows.2
+
+
+From 5d492c338258d07613103e6ae16df4c6fa5b3838 Mon Sep 17 00:00:00 2001
+From: rongtong <jinrongtong5@163.com>
+Date: Fri, 13 Oct 2023 11:23:30 +0800
+Subject: [PATCH 3/6] [ISSUE #7444] Fix testCalculateFileSizeInPath test can
+ not rerun in same environment (#7445)
+
+* Fix testCalculateFileSizeInPath test can not rerun in same environment
+
+* Ensure that files are always deleted
+---
+ .../apache/rocketmq/common/UtilAllTest.java | 83 +++++++++++--------
+ 1 file changed, 48 insertions(+), 35 deletions(-)
+
+diff --git a/common/src/test/java/org/apache/rocketmq/common/UtilAllTest.java b/common/src/test/java/org/apache/rocketmq/common/UtilAllTest.java
+index f568a65f4..a0653d7fc 100644
+--- a/common/src/test/java/org/apache/rocketmq/common/UtilAllTest.java
++++ b/common/src/test/java/org/apache/rocketmq/common/UtilAllTest.java
+@@ -238,41 +238,54 @@ public class UtilAllTest {
+ */
+ String basePath = System.getProperty("java.io.tmpdir") + File.separator + "testCalculateFileSizeInPath";
+ File baseFile = new File(basePath);
+- // test empty path
+- assertEquals(0, UtilAll.calculateFileSizeInPath(baseFile));
+-
+- // create baseDir
+- assertTrue(baseFile.mkdirs());
+-
+- File file0 = new File(baseFile, "file_0");
+- assertTrue(file0.createNewFile());
+- writeFixedBytesToFile(file0, 1313);
+-
+- assertEquals(1313, UtilAll.calculateFileSizeInPath(baseFile));
+-
+- // build a file tree like above
+- File dir1 = new File(baseFile, "dir_1");
+- dir1.mkdirs();
+- File file10 = new File(dir1, "file_1_0");
+- File file11 = new File(dir1, "file_1_1");
+- File dir12 = new File(dir1, "dir_1_2");
+- dir12.mkdirs();
+- File file120 = new File(dir12, "file_1_2_0");
+- File dir2 = new File(baseFile, "dir_2");
+- dir2.mkdirs();
+-
+- // write all file with 1313 bytes data
+- assertTrue(file10.createNewFile());
+- writeFixedBytesToFile(file10, 1313);
+- assertTrue(file11.createNewFile());
+- writeFixedBytesToFile(file11, 1313);
+- assertTrue(file120.createNewFile());
+- writeFixedBytesToFile(file120, 1313);
+-
+- assertEquals(1313 * 4, UtilAll.calculateFileSizeInPath(baseFile));
+-
+- // clear all file
+- baseFile.deleteOnExit();
++ try {
++ // test empty path
++ assertEquals(0, UtilAll.calculateFileSizeInPath(baseFile));
++
++ // create baseDir
++ assertTrue(baseFile.mkdirs());
++
++ File file0 = new File(baseFile, "file_0");
++ assertTrue(file0.createNewFile());
++ writeFixedBytesToFile(file0, 1313);
++
++ assertEquals(1313, UtilAll.calculateFileSizeInPath(baseFile));
++
++ // build a file tree like above
++ File dir1 = new File(baseFile, "dir_1");
++ dir1.mkdirs();
++ File file10 = new File(dir1, "file_1_0");
++ File file11 = new File(dir1, "file_1_1");
++ File dir12 = new File(dir1, "dir_1_2");
++ dir12.mkdirs();
++ File file120 = new File(dir12, "file_1_2_0");
++ File dir2 = new File(baseFile, "dir_2");
++ dir2.mkdirs();
++
++ // write all file with 1313 bytes data
++ assertTrue(file10.createNewFile());
++ writeFixedBytesToFile(file10, 1313);
++ assertTrue(file11.createNewFile());
++ writeFixedBytesToFile(file11, 1313);
++ assertTrue(file120.createNewFile());
++ writeFixedBytesToFile(file120, 1313);
++
++ assertEquals(1313 * 4, UtilAll.calculateFileSizeInPath(baseFile));
++ } finally {
++ deleteFolder(baseFile);
++ }
++ }
++
++ public static void deleteFolder(File folder) {
++ if (folder.isDirectory()) {
++ File[] files = folder.listFiles();
++ if (files != null) {
++ for (File file : files) {
++ deleteFolder(file);
++ }
++ }
++ }
++ folder.delete();
+ }
+
+ private void writeFixedBytesToFile(File file, int size) throws Exception {
+--
+2.32.0.windows.2
+
+
+From 28427d40129e3aa0c6f951535617e5cac0a8211b Mon Sep 17 00:00:00 2001
+From: Lei Sun <yuncun.sl@alibaba-inc.com>
+Date: Fri, 13 Oct 2023 13:42:27 +0800
+Subject: [PATCH 4/6] [ISSUE #7425] Add RoccketmqControllerConsole log to fix
+ bug (#7458)
+
+---
+ .../org/apache/rocketmq/common/constant/LoggerName.java | 1 +
+ .../org/apache/rocketmq/controller/ControllerStartup.java | 7 ++++---
+ controller/src/main/resources/rmq.controller.logback.xml | 4 ++++
+ 3 files changed, 9 insertions(+), 3 deletions(-)
+
+diff --git a/common/src/main/java/org/apache/rocketmq/common/constant/LoggerName.java b/common/src/main/java/org/apache/rocketmq/common/constant/LoggerName.java
+index cb04b00b3..61310893f 100644
+--- a/common/src/main/java/org/apache/rocketmq/common/constant/LoggerName.java
++++ b/common/src/main/java/org/apache/rocketmq/common/constant/LoggerName.java
+@@ -21,6 +21,7 @@ public class LoggerName {
+ public static final String NAMESRV_LOGGER_NAME = "RocketmqNamesrv";
+ public static final String NAMESRV_CONSOLE_LOGGER_NAME = "RocketmqNamesrvConsole";
+ public static final String CONTROLLER_LOGGER_NAME = "RocketmqController";
++ public static final String CONTROLLER_CONSOLE_NAME = "RocketmqControllerConsole";
+ public static final String NAMESRV_WATER_MARK_LOGGER_NAME = "RocketmqNamesrvWaterMark";
+ public static final String BROKER_LOGGER_NAME = "RocketmqBroker";
+ public static final String BROKER_CONSOLE_NAME = "RocketmqConsole";
+diff --git a/controller/src/main/java/org/apache/rocketmq/controller/ControllerStartup.java b/controller/src/main/java/org/apache/rocketmq/controller/ControllerStartup.java
+index 401720d05..9e96a704d 100644
+--- a/controller/src/main/java/org/apache/rocketmq/controller/ControllerStartup.java
++++ b/controller/src/main/java/org/apache/rocketmq/controller/ControllerStartup.java
+@@ -94,9 +94,10 @@ public class ControllerStartup {
+ }
+
+ if (commandLine.hasOption('p')) {
+- MixAll.printObjectProperties(null, controllerConfig);
+- MixAll.printObjectProperties(null, nettyServerConfig);
+- MixAll.printObjectProperties(null, nettyClientConfig);
++ Logger console = LoggerFactory.getLogger(LoggerName.CONTROLLER_CONSOLE_NAME);
++ MixAll.printObjectProperties(console, controllerConfig);
++ MixAll.printObjectProperties(console, nettyServerConfig);
++ MixAll.printObjectProperties(console, nettyClientConfig);
+ System.exit(0);
+ }
+
+diff --git a/controller/src/main/resources/rmq.controller.logback.xml b/controller/src/main/resources/rmq.controller.logback.xml
+index bb158213a..18083e8f9 100644
+--- a/controller/src/main/resources/rmq.controller.logback.xml
++++ b/controller/src/main/resources/rmq.controller.logback.xml
+@@ -116,6 +116,10 @@
+ <appender-ref ref="RocketmqControllerAppender"/>
+ </logger>
+
++ <logger name="RocketmqControllerConsole" additivity="false" level="INFO">
++ <appender-ref ref="STDOUT"/>
++ </logger>
++
+ <logger name="RocketmqCommon" additivity="false" level="INFO">
+ <appender-ref ref="RocketmqControllerAppender"/>
+ </logger>
+--
+2.32.0.windows.2
+
+
+From dc62d7f2e1ce4f99364599f8e23d65fd88eb1cd4 Mon Sep 17 00:00:00 2001
+From: LetLetMe <43874697+LetLetMe@users.noreply.github.com>
+Date: Fri, 13 Oct 2023 13:45:48 +0800
+Subject: [PATCH 5/6] [ISSUE #7451] Override toString for
+ TopicConfigAndQueueMapping
+
+---
+ .../statictopic/TopicConfigAndQueueMapping.java | 10 ++++++++++
+ 1 file changed, 10 insertions(+)
+
+diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicConfigAndQueueMapping.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicConfigAndQueueMapping.java
+index c937fec23..d13692735 100644
+--- a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicConfigAndQueueMapping.java
++++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/statictopic/TopicConfigAndQueueMapping.java
+@@ -16,6 +16,7 @@
+ */
+ package org.apache.rocketmq.remoting.protocol.statictopic;
+
++import org.apache.commons.lang3.StringUtils;
+ import org.apache.commons.lang3.builder.EqualsBuilder;
+ import org.apache.commons.lang3.builder.HashCodeBuilder;
+ import org.apache.rocketmq.common.TopicConfig;
+@@ -60,4 +61,13 @@ public class TopicConfigAndQueueMapping extends TopicConfig {
+ .append(mappingDetail)
+ .toHashCode();
+ }
++
++ @Override
++ public String toString() {
++ String string = super.toString();
++ if (StringUtils.isNotBlank(string)) {
++ string = string.substring(0, string.length() - 1) + ", mappingDetail=" + mappingDetail + "]";
++ }
++ return string;
++ }
+ }
+--
+2.32.0.windows.2
+
+
+From 2113fa371b9c2bf7c512f8ad234e51c616f1362c Mon Sep 17 00:00:00 2001
+From: guyinyou <36399867+guyinyou@users.noreply.github.com>
+Date: Fri, 13 Oct 2023 13:47:09 +0800
+Subject: [PATCH 6/6] [ISSUE #7453] Fix the problem in constructing the
+ GetMessageResult (#7456)
+
+* Fix the problem in constructing the GetMessageResult
+
+* Optimize the initialization size of GetMessageResult
+---
+ .../apache/rocketmq/broker/processor/PeekMessageProcessor.java | 3 +--
+ .../apache/rocketmq/broker/processor/PopMessageProcessor.java | 3 +--
+ 2 files changed, 2 insertions(+), 4 deletions(-)
+
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PeekMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PeekMessageProcessor.java
+index a8358c4ff..e1e0e13e5 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PeekMessageProcessor.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PeekMessageProcessor.java
+@@ -129,8 +129,7 @@ public class PeekMessageProcessor implements NettyRequestProcessor {
+ }
+ int randomQ = random.nextInt(100);
+ int reviveQid = randomQ % this.brokerController.getBrokerConfig().getReviveQueueNum();
+- int commercialSizePerMsg = this.brokerController.getBrokerConfig().getCommercialSizePerMsg();
+- GetMessageResult getMessageResult = new GetMessageResult(commercialSizePerMsg);
++ GetMessageResult getMessageResult = new GetMessageResult(requestHeader.getMaxMsgNums());
+ boolean needRetry = randomQ % 5 == 0;
+ long popTime = System.currentTimeMillis();
+ long restNum = 0;
+diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java
+index 441f7de08..0d9bdf143 100644
+--- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java
++++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java
+@@ -347,8 +347,7 @@ public class PopMessageProcessor implements NettyRequestProcessor {
+ reviveQid = (int) Math.abs(ckMessageNumber.getAndIncrement() % this.brokerController.getBrokerConfig().getReviveQueueNum());
+ }
+
+- int commercialSizePerMsg = this.brokerController.getBrokerConfig().getCommercialSizePerMsg();
+- GetMessageResult getMessageResult = new GetMessageResult(commercialSizePerMsg);
++ GetMessageResult getMessageResult = new GetMessageResult(requestHeader.getMaxMsgNums());
+ ExpressionMessageFilter finalMessageFilter = messageFilter;
+ StringBuilder finalOrderCountInfo = orderCountInfo;
+
+--
+2.32.0.windows.2
+
diff --git a/pom.xml b/pom.xml
index 4202d4095..a3f7c2270 100644
--- a/pom.xml
+++ b/pom.xml
@@ -137,7 +137,7 @@
<opentelemetry-exporter-prometheus.version>1.29.0-alpha</opentelemetry-exporter-prometheus.version>
<jul-to-slf4j.version>2.0.6</jul-to-slf4j.version>
<s3.version>2.20.29</s3.version>
- <rocksdb.version>1.0.3</rocksdb.version>
+ <rocksdb.version>1.0.2</rocksdb.version>
<jackson-databind.version>2.13.4.2</jackson-databind.version>
<!-- Test dependencies -->
@@ -713,7 +713,7 @@
<version>${slf4j-api.version}</version>
</dependency>
<dependency>
- <groupId>io.github.aliyunmq</groupId>
+ <groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-rocksdb</artifactId>
<version>${rocksdb.version}</version>
</dependency>
diff --git a/store/pom.xml b/store/pom.xml
index e979030e8..e1e616123 100644
--- a/store/pom.xml
+++ b/store/pom.xml
@@ -58,6 +58,10 @@
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ </dependency>
<!-- Required by DLedger -->
<dependency>
<groupId>org.slf4j</groupId>
diff --git a/store/src/main/java/org/apache/rocketmq/store/CommitLog.java b/store/src/main/java/org/apache/rocketmq/store/CommitLog.java
index f98e9a284..93102799b 100644
--- a/store/src/main/java/org/apache/rocketmq/store/CommitLog.java
+++ b/store/src/main/java/org/apache/rocketmq/store/CommitLog.java
@@ -60,6 +60,8 @@ import org.apache.rocketmq.store.ha.HAService;
import org.apache.rocketmq.store.ha.autoswitch.AutoSwitchHAService;
import org.apache.rocketmq.store.logfile.MappedFile;
import org.apache.rocketmq.store.util.LibC;
+import org.rocksdb.RocksDBException;
+
import sun.nio.ch.DirectBuffer;
/**
@@ -299,8 +301,9 @@ public class CommitLog implements Swappable {
/**
* When the normal exit, data recovery, all memory data have been flush
+ * @throws RocksDBException only in rocksdb mode
*/
- public void recoverNormally(long maxPhyOffsetOfConsumeQueue) {
+ public void recoverNormally(long maxPhyOffsetOfConsumeQueue) throws RocksDBException {
boolean checkCRCOnRecover = this.defaultMessageStore.getMessageStoreConfig().isCheckCRCOnRecover();
boolean checkDupInfo = this.defaultMessageStore.getMessageStoreConfig().isDuplicationEnable();
final List<MappedFile> mappedFiles = this.mappedFileQueue.getMappedFiles();
@@ -369,21 +372,22 @@ public class CommitLog implements Swappable {
this.setConfirmOffset(lastValidMsgPhyOffset);
}
- this.mappedFileQueue.setFlushedWhere(processOffset);
- this.mappedFileQueue.setCommittedWhere(processOffset);
- this.mappedFileQueue.truncateDirtyFiles(processOffset);
-
// Clear ConsumeQueue redundant data
if (maxPhyOffsetOfConsumeQueue >= processOffset) {
log.warn("maxPhyOffsetOfConsumeQueue({}) >= processOffset({}), truncate dirty logic files", maxPhyOffsetOfConsumeQueue, processOffset);
this.defaultMessageStore.truncateDirtyLogicFiles(processOffset);
}
+
+ this.mappedFileQueue.setFlushedWhere(processOffset);
+ this.mappedFileQueue.setCommittedWhere(processOffset);
+ this.mappedFileQueue.truncateDirtyFiles(processOffset);
} else {
// Commitlog case files are deleted
log.warn("The commitlog files are deleted, and delete the consume queue files");
this.mappedFileQueue.setFlushedWhere(0);
this.mappedFileQueue.setCommittedWhere(0);
- this.defaultMessageStore.destroyLogics();
+ this.defaultMessageStore.getQueueStore().destroy();
+ this.defaultMessageStore.getQueueStore().loadAfterDestroy();
}
}
@@ -626,8 +630,10 @@ public class CommitLog implements Swappable {
return -1;
}
- @Deprecated
- public void recoverAbnormally(long maxPhyOffsetOfConsumeQueue) {
+ /**
+ * @throws RocksDBException only in rocksdb mode
+ */
+ public void recoverAbnormally(long maxPhyOffsetOfConsumeQueue) throws RocksDBException {
// recover by the minimum time stamp
boolean checkCRCOnRecover = this.defaultMessageStore.getMessageStoreConfig().isCheckCRCOnRecover();
boolean checkDupInfo = this.defaultMessageStore.getMessageStoreConfig().isDuplicationEnable();
@@ -705,6 +711,9 @@ public class CommitLog implements Swappable {
}
}
+ // only for rocksdb mode
+ this.getMessageStore().finishCommitLogDispatch();
+
processOffset += mappedFileOffset;
if (this.defaultMessageStore.getBrokerConfig().isEnableControllerMode()) {
if (this.defaultMessageStore.getConfirmOffset() < this.defaultMessageStore.getMinPhyOffset()) {
@@ -717,22 +726,24 @@ public class CommitLog implements Swappable {
} else {
this.setConfirmOffset(lastValidMsgPhyOffset);
}
- this.mappedFileQueue.setFlushedWhere(processOffset);
- this.mappedFileQueue.setCommittedWhere(processOffset);
- this.mappedFileQueue.truncateDirtyFiles(processOffset);
// Clear ConsumeQueue redundant data
if (maxPhyOffsetOfConsumeQueue >= processOffset) {
log.warn("maxPhyOffsetOfConsumeQueue({}) >= processOffset({}), truncate dirty logic files", maxPhyOffsetOfConsumeQueue, processOffset);
this.defaultMessageStore.truncateDirtyLogicFiles(processOffset);
}
+
+ this.mappedFileQueue.setFlushedWhere(processOffset);
+ this.mappedFileQueue.setCommittedWhere(processOffset);
+ this.mappedFileQueue.truncateDirtyFiles(processOffset);
}
// Commitlog case files are deleted
else {
log.warn("The commitlog files are deleted, and delete the consume queue files");
this.mappedFileQueue.setFlushedWhere(0);
this.mappedFileQueue.setCommittedWhere(0);
- this.defaultMessageStore.destroyLogics();
+ this.defaultMessageStore.getQueueStore().destroy();
+ this.defaultMessageStore.getQueueStore().loadAfterDestroy();
}
}
@@ -755,7 +766,7 @@ public class CommitLog implements Swappable {
this.getMessageStore().onCommitLogAppend(msg, result, commitLogFile);
}
- private boolean isMappedFileMatchedRecover(final MappedFile mappedFile) {
+ private boolean isMappedFileMatchedRecover(final MappedFile mappedFile) throws RocksDBException {
ByteBuffer byteBuffer = mappedFile.sliceByteBuffer();
int magicCode = byteBuffer.getInt(MessageDecoder.MESSAGE_MAGIC_CODE_POSITION);
@@ -763,28 +774,37 @@ public class CommitLog implements Swappable {
return false;
}
- int sysFlag = byteBuffer.getInt(MessageDecoder.SYSFLAG_POSITION);
- int bornhostLength = (sysFlag & MessageSysFlag.BORNHOST_V6_FLAG) == 0 ? 8 : 20;
- int msgStoreTimePos = 4 + 4 + 4 + 4 + 4 + 8 + 8 + 4 + 8 + bornhostLength;
- long storeTimestamp = byteBuffer.getLong(msgStoreTimePos);
- if (0 == storeTimestamp) {
- return false;
- }
-
- if (this.defaultMessageStore.getMessageStoreConfig().isMessageIndexEnable()
- && this.defaultMessageStore.getMessageStoreConfig().isMessageIndexSafe()) {
- if (storeTimestamp <= this.defaultMessageStore.getStoreCheckpoint().getMinTimestampIndex()) {
- log.info("find check timestamp, {} {}",
- storeTimestamp,
- UtilAll.timeMillisToHumanString(storeTimestamp));
+ if (this.defaultMessageStore.getMessageStoreConfig().isEnableRocksDBStore()) {
+ final long maxPhyOffsetInConsumeQueue = this.defaultMessageStore.getQueueStore().getMaxPhyOffsetInConsumeQueue();
+ long phyOffset = byteBuffer.getLong(MessageDecoder.MESSAGE_PHYSIC_OFFSET_POSITION);
+ if (phyOffset <= maxPhyOffsetInConsumeQueue) {
+ log.info("find check. beginPhyOffset: {}, maxPhyOffsetInConsumeQueue: {}", phyOffset, maxPhyOffsetInConsumeQueue);
return true;
}
} else {
- if (storeTimestamp <= this.defaultMessageStore.getStoreCheckpoint().getMinTimestamp()) {
- log.info("find check timestamp, {} {}",
- storeTimestamp,
- UtilAll.timeMillisToHumanString(storeTimestamp));
- return true;
+ int sysFlag = byteBuffer.getInt(MessageDecoder.SYSFLAG_POSITION);
+ int bornHostLength = (sysFlag & MessageSysFlag.BORNHOST_V6_FLAG) == 0 ? 8 : 20;
+ int msgStoreTimePos = 4 + 4 + 4 + 4 + 4 + 8 + 8 + 4 + 8 + bornHostLength;
+ long storeTimestamp = byteBuffer.getLong(msgStoreTimePos);
+ if (0 == storeTimestamp) {
+ return false;
+ }
+
+ if (this.defaultMessageStore.getMessageStoreConfig().isMessageIndexEnable()
+ && this.defaultMessageStore.getMessageStoreConfig().isMessageIndexSafe()) {
+ if (storeTimestamp <= this.defaultMessageStore.getStoreCheckpoint().getMinTimestampIndex()) {
+ log.info("find check timestamp, {} {}",
+ storeTimestamp,
+ UtilAll.timeMillisToHumanString(storeTimestamp));
+ return true;
+ }
+ } else {
+ if (storeTimestamp <= this.defaultMessageStore.getStoreCheckpoint().getMinTimestamp()) {
+ log.info("find check timestamp, {} {}",
+ storeTimestamp,
+ UtilAll.timeMillisToHumanString(storeTimestamp));
+ return true;
+ }
}
}
@@ -958,8 +978,6 @@ public class CommitLog implements Swappable {
beginTimeInLock = 0;
return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, result));
case UNKNOWN_ERROR:
- beginTimeInLock = 0;
- return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, result));
default:
beginTimeInLock = 0;
return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, result));
@@ -974,6 +992,8 @@ public class CommitLog implements Swappable {
if (AppendMessageStatus.PUT_OK.equals(result.getStatus())) {
this.defaultMessageStore.increaseOffset(msg, getMessageNum(msg));
}
+ } catch (RocksDBException e) {
+ return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, result));
} finally {
topicQueueLock.unlock(topicQueueKey);
}
@@ -997,7 +1017,7 @@ public class CommitLog implements Swappable {
public CompletableFuture<PutMessageResult> asyncPutMessages(final MessageExtBatch messageExtBatch) {
messageExtBatch.setStoreTimestamp(System.currentTimeMillis());
- AppendMessageResult result;
+ AppendMessageResult result = null;
StoreStatsService storeStatsService = this.defaultMessageStore.getStoreStatsService();
@@ -1133,7 +1153,9 @@ public class CommitLog implements Swappable {
if (AppendMessageStatus.PUT_OK.equals(result.getStatus())) {
this.defaultMessageStore.increaseOffset(messageExtBatch, (short) putMessageContext.getBatchSize());
}
- } finally {
+ } catch (RocksDBException e) {
+ return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, result));
+ } finally {
topicQueueLock.unlock(topicQueueKey);
}
diff --git a/store/src/main/java/org/apache/rocketmq/store/CommitLogDispatcher.java b/store/src/main/java/org/apache/rocketmq/store/CommitLogDispatcher.java
index 9d6fa6ad9..f3a7b7c5c 100644
--- a/store/src/main/java/org/apache/rocketmq/store/CommitLogDispatcher.java
+++ b/store/src/main/java/org/apache/rocketmq/store/CommitLogDispatcher.java
@@ -17,6 +17,8 @@
package org.apache.rocketmq.store;
+import org.rocksdb.RocksDBException;
+
/**
* Dispatcher of commit log.
*/
@@ -25,6 +27,7 @@ public interface CommitLogDispatcher {
/**
* Dispatch messages from store to build consume queues, indexes, and filter data
* @param request dispatch message request
+ * @throws RocksDBException only in rocksdb mode
*/
- void dispatch(final DispatchRequest request);
+ void dispatch(final DispatchRequest request) throws RocksDBException;
}
diff --git a/store/src/main/java/org/apache/rocketmq/store/ConsumeQueue.java b/store/src/main/java/org/apache/rocketmq/store/ConsumeQueue.java
index 56bee2af3..623509c8b 100644
--- a/store/src/main/java/org/apache/rocketmq/store/ConsumeQueue.java
+++ b/store/src/main/java/org/apache/rocketmq/store/ConsumeQueue.java
@@ -24,13 +24,12 @@ import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.common.BoundaryType;
import org.apache.rocketmq.common.MixAll;
+import org.apache.rocketmq.common.Pair;
import org.apache.rocketmq.common.attribute.CQType;
import org.apache.rocketmq.common.constant.LoggerName;
import org.apache.rocketmq.common.message.MessageAccessor;
import org.apache.rocketmq.common.message.MessageConst;
-import org.apache.rocketmq.common.message.MessageDecoder;
import org.apache.rocketmq.common.message.MessageExtBrokerInner;
-import org.apache.rocketmq.common.topic.TopicValidator;
import org.apache.rocketmq.logging.org.slf4j.Logger;
import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
import org.apache.rocketmq.store.config.BrokerRole;
@@ -39,9 +38,9 @@ import org.apache.rocketmq.store.logfile.MappedFile;
import org.apache.rocketmq.store.queue.ConsumeQueueInterface;
import org.apache.rocketmq.store.queue.CqUnit;
import org.apache.rocketmq.store.queue.FileQueueLifeCycle;
+import org.apache.rocketmq.store.queue.MultiDispatch;
import org.apache.rocketmq.store.queue.QueueOffsetOperator;
import org.apache.rocketmq.store.queue.ReferredIterator;
-import org.apache.rocketmq.store.timer.TimerMessageStore;
public class ConsumeQueue implements ConsumeQueueInterface, FileQueueLifeCycle {
private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME);
@@ -703,7 +702,7 @@ public class ConsumeQueue implements ConsumeQueueInterface, FileQueueLifeCycle {
this.messageStore.getStoreCheckpoint().setPhysicMsgTimestamp(request.getStoreTimestamp());
}
this.messageStore.getStoreCheckpoint().setLogicsMsgTimestamp(request.getStoreTimestamp());
- if (checkMultiDispatchQueue(request)) {
+ if (MultiDispatch.checkMultiDispatchQueue(this.messageStore.getMessageStoreConfig(), request)) {
multiDispatchLmqQueue(request, maxRetries);
}
return;
@@ -725,25 +724,6 @@ public class ConsumeQueue implements ConsumeQueueInterface, FileQueueLifeCycle {
this.messageStore.getRunningFlags().makeLogicsQueueError();
}
- private boolean checkMultiDispatchQueue(DispatchRequest dispatchRequest) {
- if (!this.messageStore.getMessageStoreConfig().isEnableMultiDispatch()
- || dispatchRequest.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)
- || dispatchRequest.getTopic().equals(TimerMessageStore.TIMER_TOPIC)
- || dispatchRequest.getTopic().equals(TopicValidator.RMQ_SYS_SCHEDULE_TOPIC)) {
- return false;
- }
- Map<String, String> prop = dispatchRequest.getPropertiesMap();
- if (prop == null || prop.isEmpty()) {
- return false;
- }
- String multiDispatchQueue = prop.get(MessageConst.PROPERTY_INNER_MULTI_DISPATCH);
- String multiQueueOffset = prop.get(MessageConst.PROPERTY_INNER_MULTI_QUEUE_OFFSET);
- if (StringUtils.isBlank(multiDispatchQueue) || StringUtils.isBlank(multiQueueOffset)) {
- return false;
- }
- return true;
- }
-
private void multiDispatchLmqQueue(DispatchRequest request, int maxRetries) {
Map<String, String> prop = request.getPropertiesMap();
String multiDispatchQueue = prop.get(MessageConst.PROPERTY_INNER_MULTI_DISPATCH);
@@ -765,9 +745,7 @@ public class ConsumeQueue implements ConsumeQueueInterface, FileQueueLifeCycle {
queueId = 0;
}
doDispatchLmqQueue(request, maxRetries, queueName, queueOffset, queueId);
-
}
- return;
}
private void doDispatchLmqQueue(DispatchRequest request, int maxRetries, String queueName, long queueOffset,
@@ -802,7 +780,7 @@ public class ConsumeQueue implements ConsumeQueueInterface, FileQueueLifeCycle {
// Handling the multi dispatch message. In the context of a light message queue (as defined in RIP-28),
// light message queues are constructed based on message properties, which requires special handling of queue offset of the light message queue.
- if (!isNeedHandleMultiDispatch(msg)) {
+ if (!MultiDispatch.isNeedHandleMultiDispatch(this.messageStore.getMessageStoreConfig(), msg.getTopic())) {
return;
}
String multiDispatchQueue = msg.getProperty(MessageConst.PROPERTY_INNER_MULTI_DISPATCH);
@@ -812,14 +790,14 @@ public class ConsumeQueue implements ConsumeQueueInterface, FileQueueLifeCycle {
String[] queues = multiDispatchQueue.split(MixAll.MULTI_DISPATCH_QUEUE_SPLITTER);
Long[] queueOffsets = new Long[queues.length];
for (int i = 0; i < queues.length; i++) {
- String key = queueKey(queues[i], msg);
- if (messageStore.getMessageStoreConfig().isEnableLmq() && MixAll.isLmq(key)) {
+ if (this.messageStore.getMessageStoreConfig().isEnableLmq() && MixAll.isLmq(queues[i])) {
+ String key = MultiDispatch.lmqQueueKey(queues[i]);
queueOffsets[i] = queueOffsetOperator.getLmqOffset(key);
}
}
MessageAccessor.putProperty(msg, MessageConst.PROPERTY_INNER_MULTI_QUEUE_OFFSET,
StringUtils.join(queueOffsets, MixAll.MULTI_DISPATCH_QUEUE_SPLITTER));
- removeWaitStorePropertyString(msg);
+ msg.removeWaitStorePropertyString();
}
@Override
@@ -830,7 +808,7 @@ public class ConsumeQueue implements ConsumeQueueInterface, FileQueueLifeCycle {
// Handling the multi dispatch message. In the context of a light message queue (as defined in RIP-28),
// light message queues are constructed based on message properties, which requires special handling of queue offset of the light message queue.
- if (!isNeedHandleMultiDispatch(msg)) {
+ if (!MultiDispatch.isNeedHandleMultiDispatch(this.messageStore.getMessageStoreConfig(), msg.getTopic())) {
return;
}
String multiDispatchQueue = msg.getProperty(MessageConst.PROPERTY_INNER_MULTI_DISPATCH);
@@ -839,45 +817,13 @@ public class ConsumeQueue implements ConsumeQueueInterface, FileQueueLifeCycle {
}
String[] queues = multiDispatchQueue.split(MixAll.MULTI_DISPATCH_QUEUE_SPLITTER);
for (int i = 0; i < queues.length; i++) {
- String key = queueKey(queues[i], msg);
- if (messageStore.getMessageStoreConfig().isEnableLmq() && MixAll.isLmq(key)) {
+ if (this.messageStore.getMessageStoreConfig().isEnableLmq() && MixAll.isLmq(queues[i])) {
+ String key = MultiDispatch.lmqQueueKey(queues[i]);
queueOffsetOperator.increaseLmqOffset(key, (short) 1);
}
}
}
- public boolean isNeedHandleMultiDispatch(MessageExtBrokerInner msg) {
- return messageStore.getMessageStoreConfig().isEnableMultiDispatch()
- && !msg.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)
- && !msg.getTopic().equals(TimerMessageStore.TIMER_TOPIC)
- && !msg.getTopic().equals(TopicValidator.RMQ_SYS_SCHEDULE_TOPIC);
- }
-
- public String queueKey(String queueName, MessageExtBrokerInner msgInner) {
- StringBuilder keyBuilder = new StringBuilder();
- keyBuilder.append(queueName);
- keyBuilder.append('-');
- int queueId = msgInner.getQueueId();
- if (messageStore.getMessageStoreConfig().isEnableLmq() && MixAll.isLmq(queueName)) {
- queueId = 0;
- }
- keyBuilder.append(queueId);
- return keyBuilder.toString();
- }
-
- private void removeWaitStorePropertyString(MessageExtBrokerInner msgInner) {
- if (msgInner.getProperties().containsKey(MessageConst.PROPERTY_WAIT_STORE_MSG_OK)) {
- // There is no need to store "WAIT=true", remove it from propertiesString to save 9 bytes for each message.
- // It works for most case. In some cases msgInner.setPropertiesString invoked later and replace it.
- String waitStoreMsgOKValue = msgInner.getProperties().remove(MessageConst.PROPERTY_WAIT_STORE_MSG_OK);
- msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties()));
- // Reput to properties, since msgInner.isWaitStoreMsgOK() will be invoked later
- msgInner.getProperties().put(MessageConst.PROPERTY_WAIT_STORE_MSG_OK, waitStoreMsgOKValue);
- } else {
- msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties()));
- }
- }
-
private boolean putMessagePositionInfo(final long offset, final int size, final long tagsCode,
final long cqOffset) {
@@ -965,6 +911,11 @@ public class ConsumeQueue implements ConsumeQueueInterface, FileQueueLifeCycle {
return new ConsumeQueueIterator(sbr);
}
+ @Override
+ public ReferredIterator<CqUnit> iterateFrom(long startIndex, int count) {
+ return iterateFrom(startIndex);
+ }
+
@Override
public CqUnit get(long offset) {
ReferredIterator<CqUnit> it = iterateFrom(offset);
@@ -974,6 +925,20 @@ public class ConsumeQueue implements ConsumeQueueInterface, FileQueueLifeCycle {
return it.nextAndRelease();
}
+ @Override
+ public Pair<CqUnit, Long> getCqUnitAndStoreTime(long index) {
+ CqUnit cqUnit = get(index);
+ Long messageStoreTime = this.messageStore.getQueueStore().getStoreTime(cqUnit);
+ return new Pair<>(cqUnit, messageStoreTime);
+ }
+
+ @Override
+ public Pair<CqUnit, Long> getEarliestUnitAndStoreTime() {
+ CqUnit cqUnit = getEarliestUnit();
+ Long messageStoreTime = this.messageStore.getQueueStore().getStoreTime(cqUnit);
+ return new Pair<>(cqUnit, messageStoreTime);
+ }
+
@Override
public CqUnit getEarliestUnit() {
/**
diff --git a/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java b/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java
index 02ea47f13..99a54e2d7 100644
--- a/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java
+++ b/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java
@@ -105,32 +105,35 @@ import org.apache.rocketmq.store.logfile.MappedFile;
import org.apache.rocketmq.store.metrics.DefaultStoreMetricsManager;
import org.apache.rocketmq.store.queue.ConsumeQueueInterface;
import org.apache.rocketmq.store.queue.ConsumeQueueStore;
+import org.apache.rocketmq.store.queue.ConsumeQueueStoreInterface;
import org.apache.rocketmq.store.queue.CqUnit;
import org.apache.rocketmq.store.queue.ReferredIterator;
import org.apache.rocketmq.store.stats.BrokerStatsManager;
import org.apache.rocketmq.store.timer.TimerMessageStore;
import org.apache.rocketmq.store.util.PerfCounter;
+import org.rocksdb.RocksDBException;
public class DefaultMessageStore implements MessageStore {
- private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME);
+ protected static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME);
+ protected static final Logger ERROR_LOG = LoggerFactory.getLogger(LoggerName.STORE_ERROR_LOGGER_NAME);
public final PerfCounter.Ticks perfs = new PerfCounter.Ticks(LOGGER);
private final MessageStoreConfig messageStoreConfig;
// CommitLog
- private final CommitLog commitLog;
+ protected final CommitLog commitLog;
- private final ConsumeQueueStore consumeQueueStore;
+ protected final ConsumeQueueStoreInterface consumeQueueStore;
private final FlushConsumeQueueService flushConsumeQueueService;
- private final CleanCommitLogService cleanCommitLogService;
+ protected final CleanCommitLogService cleanCommitLogService;
private final CleanConsumeQueueService cleanConsumeQueueService;
private final CorrectLogicOffsetService correctLogicOffsetService;
- private final IndexService indexService;
+ protected final IndexService indexService;
private final AllocateMappedFileService allocateMappedFileService;
@@ -147,7 +150,7 @@ public class DefaultMessageStore implements MessageStore {
private final TransientStorePool transientStorePool;
- private final RunningFlags runningFlags = new RunningFlags();
+ protected final RunningFlags runningFlags = new RunningFlags();
private final SystemClock systemClock = new SystemClock();
private final ScheduledExecutorService scheduledExecutorService;
@@ -156,6 +159,7 @@ public class DefaultMessageStore implements MessageStore {
private final BrokerConfig brokerConfig;
private volatile boolean shutdown = true;
+ protected boolean notifyMessageArriveInBatch = false;
private StoreCheckpoint storeCheckpoint;
private TimerMessageStore timerMessageStore;
@@ -182,7 +186,7 @@ public class DefaultMessageStore implements MessageStore {
private volatile long brokerInitMaxOffset = -1L;
- protected List<PutMessageHook> putMessageHookList = new ArrayList<>();
+ private List<PutMessageHook> putMessageHookList = new ArrayList<>();
private SendMessageBackHook sendMessageBackHook;
@@ -222,12 +226,12 @@ public class DefaultMessageStore implements MessageStore {
this.commitLog = new CommitLog(this);
}
- this.consumeQueueStore = new ConsumeQueueStore(this, this.messageStoreConfig);
+ this.consumeQueueStore = createConsumeQueueStore();
- this.flushConsumeQueueService = new FlushConsumeQueueService();
+ this.flushConsumeQueueService = createFlushConsumeQueueService();
this.cleanCommitLogService = new CleanCommitLogService();
- this.cleanConsumeQueueService = new CleanConsumeQueueService();
- this.correctLogicOffsetService = new CorrectLogicOffsetService();
+ this.cleanConsumeQueueService = createCleanConsumeQueueService();
+ this.correctLogicOffsetService = createCorrectLogicOffsetService();
this.storeStatsService = new StoreStatsService(getBrokerIdentity());
this.indexService = new IndexService(this);
@@ -273,6 +277,22 @@ public class DefaultMessageStore implements MessageStore {
parseDelayLevel();
}
+ public ConsumeQueueStoreInterface createConsumeQueueStore() {
+ return new ConsumeQueueStore(this);
+ }
+
+ public CleanConsumeQueueService createCleanConsumeQueueService() {
+ return new CleanConsumeQueueService();
+ }
+
+ public FlushConsumeQueueService createFlushConsumeQueueService() {
+ return new FlushConsumeQueueService();
+ }
+
+ public CorrectLogicOffsetService createCorrectLogicOffsetService() {
+ return new CorrectLogicOffsetService();
+ }
+
public boolean parseDelayLevel() {
HashMap<String, Long> timeUnitTable = new HashMap<>();
timeUnitTable.put("s", 1000L);
@@ -305,7 +325,7 @@ public class DefaultMessageStore implements MessageStore {
}
@Override
- public void truncateDirtyLogicFiles(long phyOffset) {
+ public void truncateDirtyLogicFiles(long phyOffset) throws RocksDBException {
this.consumeQueueStore.truncateDirty(phyOffset);
}
@@ -393,6 +413,7 @@ public class DefaultMessageStore implements MessageStore {
this.flushConsumeQueueService.start();
this.commitLog.start();
+ this.consumeQueueStore.start();
this.storeStatsService.start();
if (this.haService != null) {
@@ -481,6 +502,7 @@ public class DefaultMessageStore implements MessageStore {
this.storeStatsService.shutdown();
this.commitLog.shutdown();
this.reputMessageService.shutdown();
+ this.consumeQueueStore.shutdown();
// dispatch-related services must be shut down after reputMessageService
this.indexService.shutdown();
if (this.compactionService != null) {
@@ -515,7 +537,7 @@ public class DefaultMessageStore implements MessageStore {
@Override
public void destroy() {
- this.destroyLogics();
+ this.consumeQueueStore.destroy();
this.commitLog.destroy();
this.indexService.destroy();
this.deleteFile(StorePathConfigHelper.getAbortFile(this.messageStoreConfig.getStorePathRootDir()));
@@ -541,11 +563,6 @@ public class DefaultMessageStore implements MessageStore {
return commitLogSize + consumeQueueSize + indexFileSize;
}
- @Override
- public void destroyLogics() {
- this.consumeQueueStore.destroy();
- }
-
@Override
public CompletableFuture<PutMessageResult> asyncPutMessage(MessageExtBrokerInner msg) {
@@ -687,7 +704,7 @@ public class DefaultMessageStore implements MessageStore {
return commitLog;
}
- public void truncateDirtyFiles(long offsetToTruncate) {
+ public void truncateDirtyFiles(long offsetToTruncate) throws RocksDBException {
LOGGER.info("truncate dirty files to {}", offsetToTruncate);
@@ -700,12 +717,12 @@ public class DefaultMessageStore implements MessageStore {
long oldReputFromOffset = this.reputMessageService.getReputFromOffset();
- // truncate commitLog
- this.commitLog.truncateDirtyFiles(offsetToTruncate);
-
// truncate consume queue
this.truncateDirtyLogicFiles(offsetToTruncate);
+ // truncate commitLog
+ this.commitLog.truncateDirtyFiles(offsetToTruncate);
+
this.recoverTopicQueueTable();
if (!messageStoreConfig.isEnableBuildConsumeQueueConcurrently()) {
@@ -723,7 +740,7 @@ public class DefaultMessageStore implements MessageStore {
}
@Override
- public boolean truncateFiles(long offsetToTruncate) {
+ public boolean truncateFiles(long offsetToTruncate) throws RocksDBException {
if (offsetToTruncate >= this.getMaxPhyOffset()) {
LOGGER.info("no need to truncate files, truncate offset is {}, max physical offset is {}", offsetToTruncate, this.getMaxPhyOffset());
return true;
@@ -825,17 +842,19 @@ public class DefaultMessageStore implements MessageStore {
while (getResult.getBufferTotalSize() <= 0
&& nextBeginOffset < maxOffset
&& cqFileNum++ < this.messageStoreConfig.getTravelCqFileNumWhenGetMessage()) {
- ReferredIterator<CqUnit> bufferConsumeQueue = consumeQueue.iterateFrom(nextBeginOffset);
-
- if (bufferConsumeQueue == null) {
- status = GetMessageStatus.OFFSET_FOUND_NULL;
- nextBeginOffset = nextOffsetCorrection(nextBeginOffset, this.consumeQueueStore.rollNextFile(consumeQueue, nextBeginOffset));
- LOGGER.warn("consumer request topic: " + topic + "offset: " + offset + " minOffset: " + minOffset + " maxOffset: "
- + maxOffset + ", but access logic queue failed. Correct nextBeginOffset to " + nextBeginOffset);
- break;
- }
+ ReferredIterator<CqUnit> bufferConsumeQueue = null;
try {
+ bufferConsumeQueue = consumeQueue.iterateFrom(nextBeginOffset, maxMsgNums);
+
+ if (bufferConsumeQueue == null) {
+ status = GetMessageStatus.OFFSET_FOUND_NULL;
+ nextBeginOffset = nextOffsetCorrection(nextBeginOffset, this.consumeQueueStore.rollNextFile(consumeQueue, nextBeginOffset));
+ LOGGER.warn("consumer request topic: " + topic + ", offset: " + offset + ", minOffset: " + minOffset + ", maxOffset: "
+ + maxOffset + ", but access logic queue failed. Correct nextBeginOffset to " + nextBeginOffset);
+ break;
+ }
+
long nextPhyFileStartOffset = Long.MIN_VALUE;
while (bufferConsumeQueue.hasNext()
&& nextBeginOffset < maxOffset) {
@@ -905,8 +924,13 @@ public class DefaultMessageStore implements MessageStore {
status = GetMessageStatus.FOUND;
nextPhyFileStartOffset = Long.MIN_VALUE;
}
+ } catch (RocksDBException e) {
+ ERROR_LOG.error("getMessage Failed. cid: {}, topic: {}, queueId: {}, offset: {}, minOffset: {}, maxOffset: {}, {}",
+ group, topic, queueId, offset, minOffset, maxOffset, e.getMessage());
} finally {
- bufferConsumeQueue.release();
+ if (bufferConsumeQueue != null) {
+ bufferConsumeQueue.release();
+ }
}
}
@@ -975,12 +999,12 @@ public class DefaultMessageStore implements MessageStore {
@Override
public long getMinOffsetInQueue(String topic, int queueId) {
- ConsumeQueueInterface logic = this.findConsumeQueue(topic, queueId);
- if (logic != null) {
- return logic.getMinOffsetInQueue();
+ try {
+ return this.consumeQueueStore.getMinOffsetInQueue(topic, queueId);
+ } catch (RocksDBException e) {
+ ERROR_LOG.error("getMinOffsetInQueue Failed. topic: {}, queueId: {}", topic, queueId, e);
+ return -1;
}
-
- return -1;
}
@Override
@@ -997,38 +1021,27 @@ public class DefaultMessageStore implements MessageStore {
public long getCommitLogOffsetInQueue(String topic, int queueId, long consumeQueueOffset) {
ConsumeQueueInterface consumeQueue = findConsumeQueue(topic, queueId);
if (consumeQueue != null) {
-
- ReferredIterator<CqUnit> bufferConsumeQueue = consumeQueue.iterateFrom(consumeQueueOffset);
- if (bufferConsumeQueue != null) {
- try {
- if (bufferConsumeQueue.hasNext()) {
- long offsetPy = bufferConsumeQueue.next().getPos();
- return offsetPy;
- }
- } finally {
- bufferConsumeQueue.release();
- }
+ CqUnit cqUnit = consumeQueue.get(consumeQueueOffset);
+ if (cqUnit != null) {
+ return cqUnit.getPos();
}
}
-
return 0;
}
@Override
public long getOffsetInQueueByTime(String topic, int queueId, long timestamp) {
- return getOffsetInQueueByTime(topic, queueId, timestamp, BoundaryType.LOWER);
+ return this.getOffsetInQueueByTime(topic, queueId, timestamp, BoundaryType.LOWER);
}
+ @Override
public long getOffsetInQueueByTime(String topic, int queueId, long timestamp, BoundaryType boundaryType) {
- ConsumeQueueInterface logic = this.findConsumeQueue(topic, queueId);
- if (logic != null) {
- long resultOffset = logic.getOffsetInQueueByTime(timestamp, boundaryType);
- // Make sure the result offset is in valid range.
- resultOffset = Math.max(resultOffset, logic.getMinOffsetInQueue());
- resultOffset = Math.min(resultOffset, logic.getMaxOffsetInQueue());
- return resultOffset;
+ try {
+ return this.consumeQueueStore.getOffsetInQueueByTime(topic, queueId, timestamp, boundaryType);
+ } catch (RocksDBException e) {
+ ERROR_LOG.error("getOffsetInQueueByTime Failed. topic: {}, queueId: {}, timestamp: {} boundaryType: {}, {}",
+ topic, queueId, timestamp, boundaryType, e.getMessage());
}
-
return 0;
}
@@ -1088,6 +1101,10 @@ public class DefaultMessageStore implements MessageStore {
return StorePathConfigHelper.getStorePathConsumeQueue(this.messageStoreConfig.getStorePathRootDir());
}
+ public MessageArrivingListener getMessageArrivingListener() {
+ return messageArrivingListener;
+ }
+
@Override
public HashMap<String, String> getRuntimeInfo() {
HashMap<String, String> result = this.storeStatsService.getRuntimeInfo();
@@ -1121,7 +1138,6 @@ public class DefaultMessageStore implements MessageStore {
return this.commitLog.getMaxOffset();
}
-
@Override
public long getMinPhyOffset() {
return this.commitLog.getMinOffset();
@@ -1141,7 +1157,10 @@ public class DefaultMessageStore implements MessageStore {
public long getEarliestMessageTime(String topic, int queueId) {
ConsumeQueueInterface logicQueue = this.findConsumeQueue(topic, queueId);
if (logicQueue != null) {
- return getStoreTime(logicQueue.getEarliestUnit());
+ Pair<CqUnit, Long> pair = logicQueue.getEarliestUnitAndStoreTime();
+ if (pair != null && pair.getObject2() != null) {
+ return pair.getObject2();
+ }
}
return -1;
@@ -1152,19 +1171,6 @@ public class DefaultMessageStore implements MessageStore {
return CompletableFuture.completedFuture(getEarliestMessageTime(topic, queueId));
}
- protected long getStoreTime(CqUnit result) {
- if (result != null) {
- try {
- final long phyOffset = result.getPos();
- final int size = result.getSize();
- long storeTime = this.getCommitLog().pickupStoreTimestamp(phyOffset, size);
- return storeTime;
- } catch (Exception e) {
- }
- }
- return -1;
- }
-
@Override
public long getEarliestMessageTime() {
long minPhyOffset = this.getMinPhyOffset();
@@ -1179,13 +1185,16 @@ public class DefaultMessageStore implements MessageStore {
public long getMessageStoreTimeStamp(String topic, int queueId, long consumeQueueOffset) {
ConsumeQueueInterface logicQueue = this.findConsumeQueue(topic, queueId);
if (logicQueue != null) {
- return getStoreTime(logicQueue.get(consumeQueueOffset));
+ Pair<CqUnit, Long> pair = logicQueue.getCqUnitAndStoreTime(consumeQueueOffset);
+ if (pair != null && pair.getObject2() != null) {
+ return pair.getObject2();
+ }
}
-
return -1;
}
- @Override public CompletableFuture<Long> getMessageStoreTimeStampAsync(String topic, int queueId,
+ @Override
+ public CompletableFuture<Long> getMessageStoreTimeStampAsync(String topic, int queueId,
long consumeQueueOffset) {
return CompletableFuture.completedFuture(getMessageStoreTimeStamp(topic, queueId, consumeQueueOffset));
}
@@ -1354,6 +1363,7 @@ public class DefaultMessageStore implements MessageStore {
* If offset table is cleaned, and old messages are dispatching after the old consume queue is cleaned,
* consume queue will be created with old offset, then later message with new offset table can not be
* dispatched to consume queue.
+ * @throws RocksDBException only in rocksdb mode
*/
@Override
public int deleteTopics(final Set<String> deleteTopics) {
@@ -1363,17 +1373,19 @@ public class DefaultMessageStore implements MessageStore {
int deleteCount = 0;
for (String topic : deleteTopics) {
- ConcurrentMap<Integer, ConsumeQueueInterface> queueTable =
- this.consumeQueueStore.getConsumeQueueTable().get(topic);
+ ConcurrentMap<Integer, ConsumeQueueInterface> queueTable = this.consumeQueueStore.findConsumeQueueMap(topic);
if (queueTable == null || queueTable.isEmpty()) {
continue;
}
for (ConsumeQueueInterface cq : queueTable.values()) {
- this.consumeQueueStore.destroy(cq);
- LOGGER.info("DeleteTopic: ConsumeQueue has been cleaned, topic={}, queueId={}",
- cq.getTopic(), cq.getQueueId());
+ try {
+ this.consumeQueueStore.destroy(cq);
+ } catch (RocksDBException e) {
+ LOGGER.error("DeleteTopic: ConsumeQueue cleans error!, topic={}, queueId={}", cq.getTopic(), cq.getQueueId(), e);
+ }
+ LOGGER.info("DeleteTopic: ConsumeQueue has been cleaned, topic={}, queueId={}", cq.getTopic(), cq.getQueueId());
this.consumeQueueStore.removeTopicQueueTable(cq.getTopic(), cq.getQueueId());
}
@@ -1852,14 +1864,18 @@ public class DefaultMessageStore implements MessageStore {
return file.exists();
}
- private void recover(final boolean lastExitOK) {
- boolean recoverConcurrently = this.brokerConfig.isRecoverConcurrently();
+ private boolean isRecoverConcurrently() {
+ return this.brokerConfig.isRecoverConcurrently() && !this.messageStoreConfig.isEnableRocksDBStore();
+ }
+
+ private void recover(final boolean lastExitOK) throws RocksDBException {
+ boolean recoverConcurrently = this.isRecoverConcurrently();
LOGGER.info("message store recover mode: {}", recoverConcurrently ? "concurrent" : "normal");
// recover consume queue
long recoverConsumeQueueStart = System.currentTimeMillis();
this.recoverConsumeQueue();
- long maxPhyOffsetOfConsumeQueue = this.getMaxOffsetInConsumeQueue();
+ long maxPhyOffsetOfConsumeQueue = this.consumeQueueStore.getMaxPhyOffsetInConsumeQueue();
long recoverConsumeQueueEnd = System.currentTimeMillis();
// recover commitlog
@@ -1894,23 +1910,25 @@ public class DefaultMessageStore implements MessageStore {
return messageStoreConfig;
}
+ @Override
+ public void finishCommitLogDispatch() {
+ // ignore
+ }
+
@Override
public TransientStorePool getTransientStorePool() {
return transientStorePool;
}
private void recoverConsumeQueue() {
- if (!this.brokerConfig.isRecoverConcurrently()) {
+ if (!this.isRecoverConcurrently()) {
this.consumeQueueStore.recover();
} else {
this.consumeQueueStore.recoverConcurrently();
}
}
- private long getMaxOffsetInConsumeQueue() {
- return this.consumeQueueStore.getMaxOffsetInConsumeQueue();
- }
-
+ @Override
public void recoverTopicQueueTable() {
long minPhyOffset = this.commitLog.getMinOffset();
this.consumeQueueStore.recoverOffsetTable(minPhyOffset);
@@ -1949,13 +1967,17 @@ public class DefaultMessageStore implements MessageStore {
return runningFlags;
}
- public void doDispatch(DispatchRequest req) {
+ public void doDispatch(DispatchRequest req) throws RocksDBException {
for (CommitLogDispatcher dispatcher : this.dispatcherList) {
dispatcher.dispatch(req);
}
}
- public void putMessagePositionInfo(DispatchRequest dispatchRequest) {
+ /**
+ * @param dispatchRequest
+ * @throws RocksDBException only in rocksdb mode
+ */
+ protected void putMessagePositionInfo(DispatchRequest dispatchRequest) throws RocksDBException {
this.consumeQueueStore.putMessagePositionInfoWrapper(dispatchRequest);
}
@@ -2054,7 +2076,7 @@ public class DefaultMessageStore implements MessageStore {
}
@Override
- public ConsumeQueueStore getQueueStore() {
+ public ConsumeQueueStoreInterface getQueueStore() {
return consumeQueueStore;
}
@@ -2065,7 +2087,7 @@ public class DefaultMessageStore implements MessageStore {
@Override
public void onCommitLogDispatch(DispatchRequest dispatchRequest, boolean doDispatch, MappedFile commitLogFile,
- boolean isRecover, boolean isFileEnd) {
+ boolean isRecover, boolean isFileEnd) throws RocksDBException {
if (doDispatch && !isFileEnd) {
this.doDispatch(dispatchRequest);
}
@@ -2082,7 +2104,7 @@ public class DefaultMessageStore implements MessageStore {
}
@Override
- public void assignOffset(MessageExtBrokerInner msg) {
+ public void assignOffset(MessageExtBrokerInner msg) throws RocksDBException {
final int tranType = MessageSysFlag.getTransactionValue(msg.getSysFlag());
if (tranType == MessageSysFlag.TRANSACTION_NOT_TYPE || tranType == MessageSysFlag.TRANSACTION_COMMIT_TYPE) {
@@ -2127,12 +2149,12 @@ public class DefaultMessageStore implements MessageStore {
class CommitLogDispatcherBuildConsumeQueue implements CommitLogDispatcher {
@Override
- public void dispatch(DispatchRequest request) {
+ public void dispatch(DispatchRequest request) throws RocksDBException {
final int tranType = MessageSysFlag.getTransactionValue(request.getSysFlag());
switch (tranType) {
case MessageSysFlag.TRANSACTION_NOT_TYPE:
case MessageSysFlag.TRANSACTION_COMMIT_TYPE:
- DefaultMessageStore.this.putMessagePositionInfo(request);
+ putMessagePositionInfo(request);
break;
case MessageSysFlag.TRANSACTION_PREPARED_TYPE:
case MessageSysFlag.TRANSACTION_ROLLBACK_TYPE:
@@ -2278,7 +2300,7 @@ public class DefaultMessageStore implements MessageStore {
return DefaultMessageStore.this.brokerConfig.getIdentifier() + CleanCommitLogService.class.getSimpleName();
}
- private boolean isTimeToDelete() {
+ protected boolean isTimeToDelete() {
String when = DefaultMessageStore.this.getMessageStoreConfig().getDeleteWhen();
if (UtilAll.isItTimeToDo(when)) {
DefaultMessageStore.LOGGER.info("it's time to reclaim disk space, " + when);
@@ -2436,7 +2458,7 @@ public class DefaultMessageStore implements MessageStore {
}
class CleanConsumeQueueService {
- private long lastPhysicalMinOffset = 0;
+ protected long lastPhysicalMinOffset = 0;
public void run() {
try {
@@ -2446,7 +2468,7 @@ public class DefaultMessageStore implements MessageStore {
}
}
- private void deleteExpiredFiles() {
+ protected void deleteExpiredFiles() {
int deleteLogicsFilesInterval = DefaultMessageStore.this.getMessageStoreConfig().getDeleteConsumeQueueFilesInterval();
long minOffset = DefaultMessageStore.this.commitLog.getMinOffset();
@@ -2551,7 +2573,7 @@ public class DefaultMessageStore implements MessageStore {
if (cqUnit.getPos() >= minPhyOffset) {
- // Normal case, do not need correct.
+ // Normal case, do not need to correct.
return false;
}
}
@@ -2741,6 +2763,18 @@ public class DefaultMessageStore implements MessageStore {
}
+ @Override
+ public void notifyMessageArriveIfNecessary(DispatchRequest dispatchRequest) {
+ if (DefaultMessageStore.this.brokerConfig.isLongPollingEnable()
+ && DefaultMessageStore.this.messageArrivingListener != null) {
+ DefaultMessageStore.this.messageArrivingListener.arriving(dispatchRequest.getTopic(),
+ dispatchRequest.getQueueId(), dispatchRequest.getConsumeQueueOffset() + 1,
+ dispatchRequest.getTagsCode(), dispatchRequest.getStoreTimestamp(),
+ dispatchRequest.getBitMap(), dispatchRequest.getPropertiesMap());
+ DefaultMessageStore.this.reputMessageService.notifyMessageArrive4MultiQueue(dispatchRequest);
+ }
+ }
+
class ReputMessageService extends ServiceThread {
protected volatile long reputFromOffset = 0;
@@ -2810,13 +2844,8 @@ public class DefaultMessageStore implements MessageStore {
if (size > 0) {
DefaultMessageStore.this.doDispatch(dispatchRequest);
- if (DefaultMessageStore.this.brokerConfig.isLongPollingEnable()
- && DefaultMessageStore.this.messageArrivingListener != null) {
- DefaultMessageStore.this.messageArrivingListener.arriving(dispatchRequest.getTopic(),
- dispatchRequest.getQueueId(), dispatchRequest.getConsumeQueueOffset() + 1,
- dispatchRequest.getTagsCode(), dispatchRequest.getStoreTimestamp(),
- dispatchRequest.getBitMap(), dispatchRequest.getPropertiesMap());
- notifyMessageArrive4MultiQueue(dispatchRequest);
+ if (!notifyMessageArriveInBatch) {
+ notifyMessageArriveIfNecessary(dispatchRequest);
}
this.reputFromOffset += size;
@@ -2850,9 +2879,14 @@ public class DefaultMessageStore implements MessageStore {
}
}
}
+ } catch (RocksDBException e) {
+ ERROR_LOG.info("dispatch message to cq exception. reputFromOffset: {}", this.reputFromOffset, e);
+ return;
} finally {
result.release();
}
+
+ finishCommitLogDispatch();
}
}
@@ -2989,7 +3023,7 @@ public class DefaultMessageStore implements MessageStore {
// dispatchRequestsList:[
// {dispatchRequests:[{dispatchRequest}, {dispatchRequest}]},
// {dispatchRequests:[{dispatchRequest}, {dispatchRequest}]}]
- private void dispatch() {
+ private void dispatch() throws Exception {
dispatchRequestsList.clear();
dispatchRequestOrderlyQueue.get(dispatchRequestsList);
if (!dispatchRequestsList.isEmpty()) {
@@ -2997,21 +3031,15 @@ public class DefaultMessageStore implements MessageStore {
for (DispatchRequest dispatchRequest : dispatchRequests) {
DefaultMessageStore.this.doDispatch(dispatchRequest);
// wake up long-polling
- if (DefaultMessageStore.this.brokerConfig.isLongPollingEnable()
- && DefaultMessageStore.this.messageArrivingListener != null) {
- DefaultMessageStore.this.messageArrivingListener.arriving(dispatchRequest.getTopic(),
- dispatchRequest.getQueueId(), dispatchRequest.getConsumeQueueOffset() + 1,
- dispatchRequest.getTagsCode(), dispatchRequest.getStoreTimestamp(),
- dispatchRequest.getBitMap(), dispatchRequest.getPropertiesMap());
- DefaultMessageStore.this.reputMessageService.notifyMessageArrive4MultiQueue(dispatchRequest);
- }
+ DefaultMessageStore.this.notifyMessageArriveIfNecessary(dispatchRequest);
+
if (!DefaultMessageStore.this.getMessageStoreConfig().isDuplicationEnable() &&
- DefaultMessageStore.this.getMessageStoreConfig().getBrokerRole() == BrokerRole.SLAVE) {
+ DefaultMessageStore.this.getMessageStoreConfig().getBrokerRole() == BrokerRole.SLAVE) {
DefaultMessageStore.this.storeStatsService
- .getSinglePutMessageTopicTimesTotal(dispatchRequest.getTopic()).add(1);
+ .getSinglePutMessageTopicTimesTotal(dispatchRequest.getTopic()).add(1);
DefaultMessageStore.this.storeStatsService
- .getSinglePutMessageTopicSizeTotal(dispatchRequest.getTopic())
- .add(dispatchRequest.getMsgSize());
+ .getSinglePutMessageTopicSizeTotal(dispatchRequest.getTopic())
+ .add(dispatchRequest.getMsgSize());
}
}
}
@@ -3079,7 +3107,7 @@ public class DefaultMessageStore implements MessageStore {
public void doReput() {
if (this.reputFromOffset < DefaultMessageStore.this.commitLog.getMinOffset()) {
LOGGER.warn("The reputFromOffset={} is smaller than minPyOffset={}, this usually indicate that the dispatch behind too much and the commitlog has expired.",
- this.reputFromOffset, DefaultMessageStore.this.commitLog.getMinOffset());
+ this.reputFromOffset, DefaultMessageStore.this.commitLog.getMinOffset());
this.reputFromOffset = DefaultMessageStore.this.commitLog.getMinOffset();
}
for (boolean doNext = true; this.isCommitLogAvailable() && doNext; ) {
@@ -3138,6 +3166,9 @@ public class DefaultMessageStore implements MessageStore {
result.release();
}
}
+
+ // only for rocksdb mode
+ finishCommitLogDispatch();
}
/**
@@ -3180,8 +3211,8 @@ public class DefaultMessageStore implements MessageStore {
if (this.isCommitLogAvailable()) {
LOGGER.warn("shutdown concurrentReputMessageService, but CommitLog have not finish to be dispatched, CommitLog max" +
- " offset={}, reputFromOffset={}", DefaultMessageStore.this.commitLog.getMaxOffset(),
- this.reputFromOffset);
+ " offset={}, reputFromOffset={}", DefaultMessageStore.this.commitLog.getMaxOffset(),
+ this.reputFromOffset);
}
this.mainBatchDispatchRequestService.shutdown();
diff --git a/store/src/main/java/org/apache/rocketmq/store/MessageStore.java b/store/src/main/java/org/apache/rocketmq/store/MessageStore.java
index 989cbbe31..814c6d1bf 100644
--- a/store/src/main/java/org/apache/rocketmq/store/MessageStore.java
+++ b/store/src/main/java/org/apache/rocketmq/store/MessageStore.java
@@ -16,10 +16,6 @@
*/
package org.apache.rocketmq.store;
-import io.opentelemetry.api.common.AttributesBuilder;
-import io.opentelemetry.api.metrics.Meter;
-import io.opentelemetry.sdk.metrics.InstrumentSelector;
-import io.opentelemetry.sdk.metrics.ViewBuilder;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.LinkedList;
@@ -40,10 +36,15 @@ import org.apache.rocketmq.store.hook.PutMessageHook;
import org.apache.rocketmq.store.hook.SendMessageBackHook;
import org.apache.rocketmq.store.logfile.MappedFile;
import org.apache.rocketmq.store.queue.ConsumeQueueInterface;
-import org.apache.rocketmq.store.queue.ConsumeQueueStore;
+import org.apache.rocketmq.store.queue.ConsumeQueueStoreInterface;
import org.apache.rocketmq.store.stats.BrokerStatsManager;
import org.apache.rocketmq.store.timer.TimerMessageStore;
import org.apache.rocketmq.store.util.PerfCounter;
+import org.rocksdb.RocksDBException;
+import io.opentelemetry.api.common.AttributesBuilder;
+import io.opentelemetry.api.metrics.Meter;
+import io.opentelemetry.sdk.metrics.InstrumentSelector;
+import io.opentelemetry.sdk.metrics.ViewBuilder;
/**
* This class defines contracting interfaces to implement, allowing third-party vendor to use customized message store.
@@ -545,7 +546,7 @@ public interface MessageStore {
void setConfirmOffset(long phyOffset);
/**
- * Check if the operation system page cache is busy or not.
+ * Check if the operating system page cache is busy or not.
*
* @return true if the OS page cache is busy; false otherwise.
*/
@@ -620,9 +621,18 @@ public interface MessageStore {
* @param commitLogFile commit log file
* @param isRecover is from recover process
* @param isFileEnd if the dispatch request represents 'file end'
+ * @throws RocksDBException only in rocksdb mode
*/
void onCommitLogDispatch(DispatchRequest dispatchRequest, boolean doDispatch, MappedFile commitLogFile,
- boolean isRecover, boolean isFileEnd);
+ boolean isRecover, boolean isFileEnd) throws RocksDBException;
+
+ /**
+ * Only used in rocksdb mode, because we build consumeQueue in batch(default 16 dispatchRequests)
+ * It will be triggered in two cases:
+ * @see org.apache.rocketmq.store.DefaultMessageStore.ReputMessageService#doReput
+ * @see CommitLog#recoverAbnormally
+ */
+ void finishCommitLogDispatch();
/**
* Get the message store config
@@ -691,13 +701,9 @@ public interface MessageStore {
* Truncate dirty logic files
*
* @param phyOffset physical offset
+ * @throws RocksDBException only in rocksdb mode
*/
- void truncateDirtyLogicFiles(long phyOffset);
-
- /**
- * Destroy logics files
- */
- void destroyLogics();
+ void truncateDirtyLogicFiles(long phyOffset) throws RocksDBException;
/**
* Unlock mappedFile
@@ -718,7 +724,7 @@ public interface MessageStore {
*
* @return the queue store
*/
- ConsumeQueueStore getQueueStore();
+ ConsumeQueueStoreInterface getQueueStore();
/**
* If 'sync disk flush' is configured in this message store
@@ -739,8 +745,9 @@ public interface MessageStore {
* yourself.
*
* @param msg message
+ * @throws RocksDBException
*/
- void assignOffset(MessageExtBrokerInner msg);
+ void assignOffset(MessageExtBrokerInner msg) throws RocksDBException;
/**
* Increase queue offset in memory table. If there is a race condition, you need to lock/unlock this method
@@ -835,14 +842,15 @@ public interface MessageStore {
*
* @param offsetToTruncate offset to truncate
* @return true if truncate succeed, false otherwise
+ * @throws RocksDBException only in rocksdb mode
*/
- boolean truncateFiles(long offsetToTruncate);
+ boolean truncateFiles(long offsetToTruncate) throws RocksDBException;
/**
- * Check if the offset is align with one message.
+ * Check if the offset is aligned with one message.
*
* @param offset offset to check
- * @return true if align, false otherwise
+ * @return true if aligned, false otherwise
*/
boolean isOffsetAligned(long offset);
@@ -971,4 +979,14 @@ public interface MessageStore {
* @param attributesBuilderSupplier metrics attributes builder
*/
void initMetrics(Meter meter, Supplier<AttributesBuilder> attributesBuilderSupplier);
+
+ /**
+ * Recover topic queue table
+ */
+ void recoverTopicQueueTable();
+
+ /**
+ * notify message arrive if necessary
+ */
+ void notifyMessageArriveIfNecessary(DispatchRequest dispatchRequest);
}
diff --git a/store/src/main/java/org/apache/rocketmq/store/RocksDBMessageStore.java b/store/src/main/java/org/apache/rocketmq/store/RocksDBMessageStore.java
new file mode 100644
index 000000000..87ccb5474
--- /dev/null
+++ b/store/src/main/java/org/apache/rocketmq/store/RocksDBMessageStore.java
@@ -0,0 +1,169 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.store;
+
+import java.io.IOException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.rocketmq.common.BrokerConfig;
+import org.apache.rocketmq.common.TopicConfig;
+import org.apache.rocketmq.common.UtilAll;
+import org.apache.rocketmq.store.config.MessageStoreConfig;
+import org.apache.rocketmq.store.config.StorePathConfigHelper;
+import org.apache.rocketmq.store.queue.ConsumeQueueInterface;
+import org.apache.rocketmq.store.queue.ConsumeQueueStoreInterface;
+import org.apache.rocketmq.store.queue.RocksDBConsumeQueue;
+import org.apache.rocketmq.store.queue.RocksDBConsumeQueueStore;
+import org.apache.rocketmq.store.stats.BrokerStatsManager;
+import org.rocksdb.RocksDBException;
+
+public class RocksDBMessageStore extends DefaultMessageStore {
+
+ public RocksDBMessageStore(final MessageStoreConfig messageStoreConfig, final BrokerStatsManager brokerStatsManager,
+ final MessageArrivingListener messageArrivingListener, final BrokerConfig brokerConfig, final ConcurrentMap<String, TopicConfig> topicConfigTable) throws
+ IOException {
+ super(messageStoreConfig, brokerStatsManager, messageArrivingListener, brokerConfig, topicConfigTable);
+ notifyMessageArriveInBatch = true;
+ }
+
+ @Override
+ public ConsumeQueueStoreInterface createConsumeQueueStore() {
+ return new RocksDBConsumeQueueStore(this);
+ }
+
+ @Override
+ public CleanConsumeQueueService createCleanConsumeQueueService() {
+ return new RocksDBCleanConsumeQueueService();
+ }
+
+ @Override
+ public FlushConsumeQueueService createFlushConsumeQueueService() {
+ return new RocksDBFlushConsumeQueueService();
+ }
+
+ @Override
+ public CorrectLogicOffsetService createCorrectLogicOffsetService() {
+ return new RocksDBCorrectLogicOffsetService();
+ }
+
+ /**
+ * Try to set topicQueueTable = new HashMap<>(), otherwise it will cause bug when broker role changes.
+ * And unlike method in DefaultMessageStore, we don't need to really recover topic queue table advance,
+ * because we can recover topic queue table from rocksdb when we need to use it.
+ * @see RocksDBConsumeQueue#assignQueueOffset
+ */
+ @Override
+ public void recoverTopicQueueTable() {
+ this.consumeQueueStore.setTopicQueueTable(new ConcurrentHashMap<>());
+ }
+
+ @Override
+ public void finishCommitLogDispatch() {
+ try {
+ putMessagePositionInfo(null);
+ } catch (RocksDBException e) {
+ ERROR_LOG.info("try to finish commitlog dispatch error.", e);
+ }
+ }
+
+ @Override
+ public ConsumeQueueInterface getConsumeQueue(String topic, int queueId) {
+ return findConsumeQueue(topic, queueId);
+ }
+
+ class RocksDBCleanConsumeQueueService extends CleanConsumeQueueService {
+ private final double diskSpaceWarningLevelRatio =
+ Double.parseDouble(System.getProperty("rocketmq.broker.diskSpaceWarningLevelRatio", "0.90"));
+
+ private final double diskSpaceCleanForciblyRatio =
+ Double.parseDouble(System.getProperty("rocketmq.broker.diskSpaceCleanForciblyRatio", "0.85"));
+
+ @Override
+ protected void deleteExpiredFiles() {
+
+ long minOffset = RocksDBMessageStore.this.commitLog.getMinOffset();
+ if (minOffset > this.lastPhysicalMinOffset) {
+ this.lastPhysicalMinOffset = minOffset;
+
+ boolean spaceFull = isSpaceToDelete();
+ boolean timeUp = cleanCommitLogService.isTimeToDelete();
+ if (spaceFull || timeUp) {
+ RocksDBMessageStore.this.consumeQueueStore.cleanExpired(minOffset);
+ }
+
+ RocksDBMessageStore.this.indexService.deleteExpiredFile(minOffset);
+ }
+ }
+
+ private boolean isSpaceToDelete() {
+ double ratio = RocksDBMessageStore.this.getMessageStoreConfig().getDiskMaxUsedSpaceRatio() / 100.0;
+
+ String storePathLogics = StorePathConfigHelper
+ .getStorePathConsumeQueue(RocksDBMessageStore.this.getMessageStoreConfig().getStorePathRootDir());
+ double logicsRatio = UtilAll.getDiskPartitionSpaceUsedPercent(storePathLogics);
+ if (logicsRatio > diskSpaceWarningLevelRatio) {
+ boolean diskOk = RocksDBMessageStore.this.runningFlags.getAndMakeLogicDiskFull();
+ if (diskOk) {
+ RocksDBMessageStore.LOGGER.error("logics disk maybe full soon " + logicsRatio + ", so mark disk full");
+ }
+ } else if (logicsRatio > diskSpaceCleanForciblyRatio) {
+ } else {
+ boolean diskOk = RocksDBMessageStore.this.runningFlags.getAndMakeLogicDiskOK();
+ if (!diskOk) {
+ RocksDBMessageStore.LOGGER.info("logics disk space OK " + logicsRatio + ", so mark disk ok");
+ }
+ }
+
+ if (logicsRatio < 0 || logicsRatio > ratio) {
+ RocksDBMessageStore.LOGGER.info("logics disk maybe full soon, so reclaim space, " + logicsRatio);
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ class RocksDBFlushConsumeQueueService extends FlushConsumeQueueService {
+ /**
+ * There is no need to flush consume queue,
+ * we put all consume queues in RocksDBConsumeQueueStore,
+ * it depends on rocksdb to flush consume queue to disk(sorted string table),
+ * we even don't flush WAL of consume store, since we think it can recover consume queue from commitlog.
+ */
+ @Override
+ public void run() {
+
+ }
+ }
+
+ class RocksDBCorrectLogicOffsetService extends CorrectLogicOffsetService {
+ /**
+ * There is no need to correct min offset of consume queue, we already fix this problem.
+ * @see org.apache.rocketmq.store.queue.RocksDBConsumeQueueOffsetTable#getMinCqOffset
+ */
+ public void run() {
+
+ }
+ }
+
+ @Override
+ public long estimateMessageCount(String topic, int queueId, long from, long to, MessageFilter filter) {
+ // todo
+ return 0;
+ }
+}
diff --git a/store/src/main/java/org/apache/rocketmq/store/RunningFlags.java b/store/src/main/java/org/apache/rocketmq/store/RunningFlags.java
index 2ae6879aa..91fcb155a 100644
--- a/store/src/main/java/org/apache/rocketmq/store/RunningFlags.java
+++ b/store/src/main/java/org/apache/rocketmq/store/RunningFlags.java
@@ -30,6 +30,8 @@ public class RunningFlags {
private static final int FENCED_BIT = 1 << 5;
+ private static final int LOGIC_DISK_FULL_BIT = 1 << 5;
+
private volatile int flagBits = 0;
public RunningFlags() {
@@ -63,6 +65,10 @@ public class RunningFlags {
return result;
}
+ public void clearLogicsQueueError() {
+ this.flagBits &= ~WRITE_LOGICS_QUEUE_ERROR_BIT;
+ }
+
public boolean getAndMakeWriteable() {
boolean result = this.isWriteable();
if (!result) {
@@ -72,7 +78,7 @@ public class RunningFlags {
}
public boolean isWriteable() {
- if ((this.flagBits & (NOT_WRITEABLE_BIT | WRITE_LOGICS_QUEUE_ERROR_BIT | DISK_FULL_BIT | WRITE_INDEX_FILE_ERROR_BIT | FENCED_BIT)) == 0) {
+ if ((this.flagBits & (NOT_WRITEABLE_BIT | WRITE_LOGICS_QUEUE_ERROR_BIT | DISK_FULL_BIT | WRITE_INDEX_FILE_ERROR_BIT | FENCED_BIT | LOGIC_DISK_FULL_BIT)) == 0) {
return true;
}
@@ -81,7 +87,7 @@ public class RunningFlags {
//for consume queue, just ignore the DISK_FULL_BIT
public boolean isCQWriteable() {
- if ((this.flagBits & (NOT_WRITEABLE_BIT | WRITE_LOGICS_QUEUE_ERROR_BIT | WRITE_INDEX_FILE_ERROR_BIT)) == 0) {
+ if ((this.flagBits & (NOT_WRITEABLE_BIT | WRITE_LOGICS_QUEUE_ERROR_BIT | WRITE_INDEX_FILE_ERROR_BIT | LOGIC_DISK_FULL_BIT)) == 0) {
return true;
}
@@ -139,4 +145,16 @@ public class RunningFlags {
this.flagBits &= ~DISK_FULL_BIT;
return result;
}
+
+ public boolean getAndMakeLogicDiskFull() {
+ boolean result = !((this.flagBits & LOGIC_DISK_FULL_BIT) == LOGIC_DISK_FULL_BIT);
+ this.flagBits |= LOGIC_DISK_FULL_BIT;
+ return result;
+ }
+
+ public boolean getAndMakeLogicDiskOK() {
+ boolean result = !((this.flagBits & LOGIC_DISK_FULL_BIT) == LOGIC_DISK_FULL_BIT);
+ this.flagBits &= ~LOGIC_DISK_FULL_BIT;
+ return result;
+ }
}
diff --git a/store/src/main/java/org/apache/rocketmq/store/config/MessageStoreConfig.java b/store/src/main/java/org/apache/rocketmq/store/config/MessageStoreConfig.java
index 9fa448043..028facbdc 100644
--- a/store/src/main/java/org/apache/rocketmq/store/config/MessageStoreConfig.java
+++ b/store/src/main/java/org/apache/rocketmq/store/config/MessageStoreConfig.java
@@ -397,8 +397,10 @@ public class MessageStoreConfig {
private int batchDispatchRequestThreadPoolNums = 16;
// rocksdb mode
+ private long cleanRocksDBDirtyCQIntervalMin = 60;
+ private long statRocksDBCQIntervalSec = 10;
+ private long memTableFlushIntervalMs = 60 * 60 * 1000L;
private boolean realTimePersistRocksDBConfig = true;
- private long memTableFlushInterval = 60 * 60 * 1000L;
private boolean enableRocksDBLog = false;
private int topicQueueLockNum = 32;
@@ -499,6 +501,10 @@ public class MessageStoreConfig {
this.mappedFileSizeCommitLog = mappedFileSizeCommitLog;
}
+ public boolean isEnableRocksDBStore() {
+ return StoreType.DEFAULT_ROCKSDB.getStoreType().equalsIgnoreCase(this.storeType);
+ }
+
public String getStoreType() {
return storeType;
}
@@ -508,7 +514,6 @@ public class MessageStoreConfig {
}
public int getMappedFileSizeConsumeQueue() {
-
int factor = (int) Math.ceil(this.mappedFileSizeConsumeQueue / (ConsumeQueue.CQ_STORE_UNIT_SIZE * 1.0));
return (int) (factor * ConsumeQueue.CQ_STORE_UNIT_SIZE);
}
@@ -1738,12 +1743,28 @@ public class MessageStoreConfig {
this.realTimePersistRocksDBConfig = realTimePersistRocksDBConfig;
}
- public long getMemTableFlushInterval() {
- return memTableFlushInterval;
+ public long getStatRocksDBCQIntervalSec() {
+ return statRocksDBCQIntervalSec;
+ }
+
+ public void setStatRocksDBCQIntervalSec(long statRocksDBCQIntervalSec) {
+ this.statRocksDBCQIntervalSec = statRocksDBCQIntervalSec;
+ }
+
+ public long getCleanRocksDBDirtyCQIntervalMin() {
+ return cleanRocksDBDirtyCQIntervalMin;
+ }
+
+ public void setCleanRocksDBDirtyCQIntervalMin(long cleanRocksDBDirtyCQIntervalMin) {
+ this.cleanRocksDBDirtyCQIntervalMin = cleanRocksDBDirtyCQIntervalMin;
+ }
+
+ public long getMemTableFlushIntervalMs() {
+ return memTableFlushIntervalMs;
}
- public void setMemTableFlushInterval(long memTableFlushInterval) {
- this.memTableFlushInterval = memTableFlushInterval;
+ public void setMemTableFlushIntervalMs(long memTableFlushIntervalMs) {
+ this.memTableFlushIntervalMs = memTableFlushIntervalMs;
}
public boolean isEnableRocksDBLog() {
diff --git a/store/src/main/java/org/apache/rocketmq/store/dledger/DLedgerCommitLog.java b/store/src/main/java/org/apache/rocketmq/store/dledger/DLedgerCommitLog.java
index d5f6acdc0..70371d83b 100644
--- a/store/src/main/java/org/apache/rocketmq/store/dledger/DLedgerCommitLog.java
+++ b/store/src/main/java/org/apache/rocketmq/store/dledger/DLedgerCommitLog.java
@@ -16,26 +16,13 @@
*/
package org.apache.rocketmq.store.dledger;
-import io.openmessaging.storage.dledger.AppendFuture;
-import io.openmessaging.storage.dledger.BatchAppendFuture;
-import io.openmessaging.storage.dledger.DLedgerConfig;
-import io.openmessaging.storage.dledger.DLedgerServer;
-import io.openmessaging.storage.dledger.entry.DLedgerEntry;
-import io.openmessaging.storage.dledger.protocol.AppendEntryRequest;
-import io.openmessaging.storage.dledger.protocol.AppendEntryResponse;
-import io.openmessaging.storage.dledger.protocol.BatchAppendEntryRequest;
-import io.openmessaging.storage.dledger.protocol.DLedgerResponseCode;
-import io.openmessaging.storage.dledger.store.file.DLedgerMmapFileStore;
-import io.openmessaging.storage.dledger.store.file.MmapFile;
-import io.openmessaging.storage.dledger.store.file.MmapFileList;
-import io.openmessaging.storage.dledger.store.file.SelectMmapBufferResult;
-import io.openmessaging.storage.dledger.utils.DLedgerUtils;
import java.net.Inet6Address;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
+
import org.apache.rocketmq.common.UtilAll;
import org.apache.rocketmq.common.message.MessageDecoder;
import org.apache.rocketmq.common.message.MessageExtBatch;
@@ -54,6 +41,22 @@ import org.apache.rocketmq.store.SelectMappedBufferResult;
import org.apache.rocketmq.store.StoreStatsService;
import org.apache.rocketmq.store.config.MessageStoreConfig;
import org.apache.rocketmq.store.logfile.MappedFile;
+import org.rocksdb.RocksDBException;
+
+import io.openmessaging.storage.dledger.AppendFuture;
+import io.openmessaging.storage.dledger.BatchAppendFuture;
+import io.openmessaging.storage.dledger.DLedgerConfig;
+import io.openmessaging.storage.dledger.DLedgerServer;
+import io.openmessaging.storage.dledger.entry.DLedgerEntry;
+import io.openmessaging.storage.dledger.protocol.AppendEntryRequest;
+import io.openmessaging.storage.dledger.protocol.AppendEntryResponse;
+import io.openmessaging.storage.dledger.protocol.BatchAppendEntryRequest;
+import io.openmessaging.storage.dledger.protocol.DLedgerResponseCode;
+import io.openmessaging.storage.dledger.store.file.DLedgerMmapFileStore;
+import io.openmessaging.storage.dledger.store.file.MmapFile;
+import io.openmessaging.storage.dledger.store.file.MmapFileList;
+import io.openmessaging.storage.dledger.store.file.SelectMmapBufferResult;
+import io.openmessaging.storage.dledger.utils.DLedgerUtils;
/**
* Store all metadata downtime for recovery, data protection reliability
@@ -269,7 +272,7 @@ public class DLedgerCommitLog extends CommitLog {
return null;
}
-
+
@Override
public boolean getData(final long offset, final int size, final ByteBuffer byteBuffer) {
if (offset < dividedCommitlogOffset) {
@@ -287,7 +290,7 @@ public class DLedgerCommitLog extends CommitLog {
return false;
}
- private void recover(long maxPhyOffsetOfConsumeQueue) {
+ private void recover(long maxPhyOffsetOfConsumeQueue) throws RocksDBException {
dLedgerFileStore.load();
if (dLedgerFileList.getMappedFiles().size() > 0) {
dLedgerFileStore.recover();
@@ -341,12 +344,12 @@ public class DLedgerCommitLog extends CommitLog {
}
@Override
- public void recoverNormally(long maxPhyOffsetOfConsumeQueue) {
+ public void recoverNormally(long maxPhyOffsetOfConsumeQueue) throws RocksDBException {
recover(maxPhyOffsetOfConsumeQueue);
}
@Override
- public void recoverAbnormally(long maxPhyOffsetOfConsumeQueue) {
+ public void recoverAbnormally(long maxPhyOffsetOfConsumeQueue) throws RocksDBException {
recover(maxPhyOffsetOfConsumeQueue);
}
@@ -469,9 +472,6 @@ public class DLedgerCommitLog extends CommitLog {
String msgId = MessageDecoder.createMessageId(buffer, msg.getStoreHostBytes(), wroteOffset);
elapsedTimeInLock = this.defaultMessageStore.getSystemClock().now() - beginTimeInDledgerLock;
appendResult = new AppendMessageResult(AppendMessageStatus.PUT_OK, wroteOffset, encodeResult.getData().length, msgId, System.currentTimeMillis(), queueOffset, elapsedTimeInLock);
- } catch (Exception e) {
- log.error("Put message error", e);
- return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR)));
} finally {
beginTimeInDledgerLock = 0;
putMessageLock.unlock();
@@ -482,6 +482,9 @@ public class DLedgerCommitLog extends CommitLog {
}
defaultMessageStore.increaseOffset(msg, getMessageNum(msg));
+ } catch (Exception e) {
+ log.error("Put message error", e);
+ return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR)));
} finally {
topicQueueLock.unlock(topicQueueKey);
}
@@ -611,9 +614,6 @@ public class DLedgerCommitLog extends CommitLog {
appendResult = new AppendMessageResult(AppendMessageStatus.PUT_OK, firstWroteOffset, encodeResult.totalMsgLen,
msgIdBuilder.toString(), System.currentTimeMillis(), queueOffset, elapsedTimeInLock);
appendResult.setMsgNum(msgNum);
- } catch (Exception e) {
- log.error("Put message error", e);
- return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR)));
} finally {
beginTimeInDledgerLock = 0;
putMessageLock.unlock();
@@ -626,7 +626,10 @@ public class DLedgerCommitLog extends CommitLog {
defaultMessageStore.increaseOffset(messageExtBatch, (short) batchNum);
- } finally {
+ } catch (Exception e) {
+ log.error("Put message error", e);
+ return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR)));
+ } finally {
topicQueueLock.unlock(encodeResult.queueOffsetKey);
}
diff --git a/store/src/main/java/org/apache/rocketmq/store/ha/HAService.java b/store/src/main/java/org/apache/rocketmq/store/ha/HAService.java
index 467da603d..aaea7d690 100644
--- a/store/src/main/java/org/apache/rocketmq/store/ha/HAService.java
+++ b/store/src/main/java/org/apache/rocketmq/store/ha/HAService.java
@@ -25,6 +25,7 @@ import org.apache.rocketmq.remoting.protocol.body.HARuntimeInfo;
import org.apache.rocketmq.store.CommitLog;
import org.apache.rocketmq.store.DefaultMessageStore;
import org.apache.rocketmq.store.config.MessageStoreConfig;
+import org.rocksdb.RocksDBException;
public interface HAService {
@@ -53,7 +54,7 @@ public interface HAService {
*
* @param masterEpoch the new masterEpoch
*/
- default boolean changeToMaster(int masterEpoch) {
+ default boolean changeToMaster(int masterEpoch) throws RocksDBException {
return false;
}
diff --git a/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAClient.java b/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAClient.java
index 936db0c4c..176c25a96 100644
--- a/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAClient.java
+++ b/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAClient.java
@@ -432,7 +432,7 @@ public class AutoSwitchHAClient extends ServiceThread implements HAClient {
/**
* Compare the master and slave's epoch file, find consistent point, do truncate.
*/
- private boolean doTruncate(List<EpochEntry> masterEpochEntries, long masterEndOffset) throws IOException {
+ private boolean doTruncate(List<EpochEntry> masterEpochEntries, long masterEndOffset) throws Exception {
if (this.epochCache.getEntrySize() == 0) {
// If epochMap is empty, means the broker is a new replicas
LOGGER.info("Slave local epochCache is empty, skip truncate log");
diff --git a/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAService.java b/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAService.java
index f20bc3e28..64dad9aef 100644
--- a/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAService.java
+++ b/store/src/main/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHAService.java
@@ -51,6 +51,7 @@ import org.apache.rocketmq.store.ha.GroupTransferService;
import org.apache.rocketmq.store.ha.HAClient;
import org.apache.rocketmq.store.ha.HAConnection;
import org.apache.rocketmq.store.ha.HAConnectionStateNotificationService;
+import org.rocksdb.RocksDBException;
/**
* SwitchAble ha service, support switch role to master or slave.
@@ -111,7 +112,7 @@ public class AutoSwitchHAService extends DefaultHAService {
}
@Override
- public boolean changeToMaster(int masterEpoch) {
+ public boolean changeToMaster(int masterEpoch) throws RocksDBException {
final int lastEpoch = this.epochCache.lastEpoch();
if (masterEpoch < lastEpoch) {
LOGGER.warn("newMasterEpoch {} < lastEpoch {}, fail to change to master", masterEpoch, lastEpoch);
@@ -315,7 +316,7 @@ public class AutoSwitchHAService extends DefaultHAService {
final EpochEntry currentLeaderEpoch = this.epochCache.lastEntry();
if (slaveMaxOffset >= currentLeaderEpoch.getStartOffset()) {
LOGGER.info("The slave {} has caught up, slaveMaxOffset: {}, confirmOffset: {}, epoch: {}, leader epoch startOffset: {}.",
- slaveBrokerId, slaveMaxOffset, confirmOffset, currentLeaderEpoch.getEpoch(), currentLeaderEpoch.getStartOffset());
+ slaveBrokerId, slaveMaxOffset, confirmOffset, currentLeaderEpoch.getEpoch(), currentLeaderEpoch.getStartOffset());
currentSyncStateSet.add(slaveBrokerId);
markSynchronizingSyncStateSet(currentSyncStateSet);
// Notify the upper layer that syncStateSet changed.
@@ -491,7 +492,7 @@ public class AutoSwitchHAService extends DefaultHAService {
/**
* Try to truncate incomplete msg transferred from master.
*/
- public long truncateInvalidMsg() {
+ public long truncateInvalidMsg() throws RocksDBException {
long dispatchBehind = this.defaultMessageStore.dispatchBehindBytes();
if (dispatchBehind <= 0) {
LOGGER.info("Dispatch complete, skip truncate");
diff --git a/store/src/main/java/org/apache/rocketmq/store/plugin/AbstractPluginMessageStore.java b/store/src/main/java/org/apache/rocketmq/store/plugin/AbstractPluginMessageStore.java
index ab9fc6da7..2f2ce9812 100644
--- a/store/src/main/java/org/apache/rocketmq/store/plugin/AbstractPluginMessageStore.java
+++ b/store/src/main/java/org/apache/rocketmq/store/plugin/AbstractPluginMessageStore.java
@@ -17,10 +17,6 @@
package org.apache.rocketmq.store.plugin;
-import io.opentelemetry.api.common.AttributesBuilder;
-import io.opentelemetry.api.metrics.Meter;
-import io.opentelemetry.sdk.metrics.InstrumentSelector;
-import io.opentelemetry.sdk.metrics.ViewBuilder;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.LinkedList;
@@ -55,10 +51,16 @@ import org.apache.rocketmq.store.hook.PutMessageHook;
import org.apache.rocketmq.store.hook.SendMessageBackHook;
import org.apache.rocketmq.store.logfile.MappedFile;
import org.apache.rocketmq.store.queue.ConsumeQueueInterface;
-import org.apache.rocketmq.store.queue.ConsumeQueueStore;
+import org.apache.rocketmq.store.queue.ConsumeQueueStoreInterface;
import org.apache.rocketmq.store.stats.BrokerStatsManager;
import org.apache.rocketmq.store.timer.TimerMessageStore;
import org.apache.rocketmq.store.util.PerfCounter;
+import org.rocksdb.RocksDBException;
+
+import io.opentelemetry.api.common.AttributesBuilder;
+import io.opentelemetry.api.metrics.Meter;
+import io.opentelemetry.sdk.metrics.InstrumentSelector;
+import io.opentelemetry.sdk.metrics.ViewBuilder;
public abstract class AbstractPluginMessageStore implements MessageStore {
protected MessageStore next = null;
@@ -457,7 +459,7 @@ public abstract class AbstractPluginMessageStore implements MessageStore {
}
@Override
- public boolean truncateFiles(long offsetToTruncate) {
+ public boolean truncateFiles(long offsetToTruncate) throws RocksDBException {
return next.truncateFiles(offsetToTruncate);
}
@@ -511,7 +513,7 @@ public abstract class AbstractPluginMessageStore implements MessageStore {
@Override
public void onCommitLogDispatch(DispatchRequest dispatchRequest, boolean doDispatch, MappedFile commitLogFile,
- boolean isRecover, boolean isFileEnd) {
+ boolean isRecover, boolean isFileEnd) throws RocksDBException {
next.onCommitLogDispatch(dispatchRequest, doDispatch, commitLogFile, isRecover, isFileEnd);
}
@@ -551,15 +553,10 @@ public abstract class AbstractPluginMessageStore implements MessageStore {
}
@Override
- public void truncateDirtyLogicFiles(long phyOffset) {
+ public void truncateDirtyLogicFiles(long phyOffset) throws RocksDBException {
next.truncateDirtyLogicFiles(phyOffset);
}
- @Override
- public void destroyLogics() {
- next.destroyLogics();
- }
-
@Override
public void unlockMappedFile(MappedFile unlockMappedFile) {
next.unlockMappedFile(unlockMappedFile);
@@ -571,7 +568,7 @@ public abstract class AbstractPluginMessageStore implements MessageStore {
}
@Override
- public ConsumeQueueStore getQueueStore() {
+ public ConsumeQueueStoreInterface getQueueStore() {
return next.getQueueStore();
}
@@ -586,7 +583,7 @@ public abstract class AbstractPluginMessageStore implements MessageStore {
}
@Override
- public void assignOffset(MessageExtBrokerInner msg) {
+ public void assignOffset(MessageExtBrokerInner msg) throws RocksDBException {
next.assignOffset(msg);
}
@@ -649,4 +646,19 @@ public abstract class AbstractPluginMessageStore implements MessageStore {
public void initMetrics(Meter meter, Supplier<AttributesBuilder> attributesBuilderSupplier) {
next.initMetrics(meter, attributesBuilderSupplier);
}
+
+ @Override
+ public void finishCommitLogDispatch() {
+ next.finishCommitLogDispatch();
+ }
+
+ @Override
+ public void recoverTopicQueueTable() {
+ next.recoverTopicQueueTable();
+ }
+
+ @Override
+ public void notifyMessageArriveIfNecessary(DispatchRequest dispatchRequest) {
+ next.notifyMessageArriveIfNecessary(dispatchRequest);
+ }
}
diff --git a/store/src/main/java/org/apache/rocketmq/store/queue/AbstractConsumeQueueStore.java b/store/src/main/java/org/apache/rocketmq/store/queue/AbstractConsumeQueueStore.java
new file mode 100644
index 000000000..30054fa50
--- /dev/null
+++ b/store/src/main/java/org/apache/rocketmq/store/queue/AbstractConsumeQueueStore.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.store.queue;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import org.apache.rocketmq.common.constant.LoggerName;
+import org.apache.rocketmq.common.message.MessageExtBrokerInner;
+import org.apache.rocketmq.logging.org.slf4j.Logger;
+import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+import org.apache.rocketmq.store.DefaultMessageStore;
+import org.apache.rocketmq.store.DispatchRequest;
+import org.apache.rocketmq.store.config.MessageStoreConfig;
+import org.rocksdb.RocksDBException;
+
+public abstract class AbstractConsumeQueueStore implements ConsumeQueueStoreInterface {
+ protected static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME);
+
+ protected final DefaultMessageStore messageStore;
+ protected final MessageStoreConfig messageStoreConfig;
+ protected final QueueOffsetOperator queueOffsetOperator = new QueueOffsetOperator();
+ protected final ConcurrentMap<String/* topic */, ConcurrentMap<Integer/* queueId */, ConsumeQueueInterface>> consumeQueueTable;
+
+ public AbstractConsumeQueueStore(DefaultMessageStore messageStore) {
+ this.messageStore = messageStore;
+ this.messageStoreConfig = messageStore.getMessageStoreConfig();
+ this.consumeQueueTable = new ConcurrentHashMap<>(32);
+ }
+
+ @Override
+ public void putMessagePositionInfoWrapper(ConsumeQueueInterface consumeQueue, DispatchRequest request) {
+ consumeQueue.putMessagePositionInfoWrapper(request);
+ }
+
+ @Override
+ public Long getMaxOffset(String topic, int queueId) {
+ return this.queueOffsetOperator.currentQueueOffset(topic + "-" + queueId);
+ }
+
+ @Override
+ public void setTopicQueueTable(ConcurrentMap<String, Long> topicQueueTable) {
+ this.queueOffsetOperator.setTopicQueueTable(topicQueueTable);
+ this.queueOffsetOperator.setLmqTopicQueueTable(topicQueueTable);
+ }
+
+ @Override
+ public ConcurrentMap getTopicQueueTable() {
+ return this.queueOffsetOperator.getTopicQueueTable();
+ }
+
+ @Override
+ public void assignQueueOffset(MessageExtBrokerInner msg) throws RocksDBException {
+ ConsumeQueueInterface consumeQueue = findOrCreateConsumeQueue(msg.getTopic(), msg.getQueueId());
+ consumeQueue.assignQueueOffset(this.queueOffsetOperator, msg);
+ }
+
+ @Override
+ public void increaseQueueOffset(MessageExtBrokerInner msg, short messageNum) {
+ ConsumeQueueInterface consumeQueue = findOrCreateConsumeQueue(msg.getTopic(), msg.getQueueId());
+ consumeQueue.increaseQueueOffset(this.queueOffsetOperator, msg, messageNum);
+ }
+
+ @Override
+ public void removeTopicQueueTable(String topic, Integer queueId) {
+ this.queueOffsetOperator.remove(topic, queueId);
+ }
+
+ @Override
+ public ConcurrentMap<String, ConcurrentMap<Integer, ConsumeQueueInterface>> getConsumeQueueTable() {
+ return this.consumeQueueTable;
+ }
+
+ @Override
+ public ConcurrentMap<Integer, ConsumeQueueInterface> findConsumeQueueMap(String topic) {
+ return this.consumeQueueTable.get(topic);
+ }
+
+ @Override
+ public long getStoreTime(CqUnit cqUnit) {
+ if (cqUnit != null) {
+ try {
+ final long phyOffset = cqUnit.getPos();
+ final int size = cqUnit.getSize();
+ long storeTime = this.messageStore.getCommitLog().pickupStoreTimestamp(phyOffset, size);
+ return storeTime;
+ } catch (Exception e) {
+ }
+ }
+ return -1;
+ }
+}
diff --git a/store/src/main/java/org/apache/rocketmq/store/queue/BatchConsumeQueue.java b/store/src/main/java/org/apache/rocketmq/store/queue/BatchConsumeQueue.java
index 387c233bf..7108c835c 100644
--- a/store/src/main/java/org/apache/rocketmq/store/queue/BatchConsumeQueue.java
+++ b/store/src/main/java/org/apache/rocketmq/store/queue/BatchConsumeQueue.java
@@ -24,6 +24,7 @@ import java.util.Map;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.function.Function;
import org.apache.commons.lang3.StringUtils;
+import org.apache.rocketmq.common.Pair;
import org.apache.rocketmq.common.BoundaryType;
import org.apache.rocketmq.common.attribute.CQType;
import org.apache.rocketmq.common.constant.LoggerName;
@@ -311,6 +312,11 @@ public class BatchConsumeQueue implements ConsumeQueueInterface {
return new BatchConsumeQueueIterator(sbr);
}
+ @Override
+ public ReferredIterator<CqUnit> iterateFrom(long startIndex, int count) {
+ return iterateFrom(startIndex);
+ }
+
@Override
public CqUnit get(long offset) {
ReferredIterator<CqUnit> it = iterateFrom(offset);
@@ -320,6 +326,20 @@ public class BatchConsumeQueue implements ConsumeQueueInterface {
return it.nextAndRelease();
}
+ @Override
+ public Pair<CqUnit, Long> getCqUnitAndStoreTime(long index) {
+ CqUnit cqUnit = get(index);
+ Long messageStoreTime = this.messageStore.getQueueStore().getStoreTime(cqUnit);
+ return new Pair<>(cqUnit, messageStoreTime);
+ }
+
+ @Override
+ public Pair<CqUnit, Long> getEarliestUnitAndStoreTime() {
+ CqUnit cqUnit = getEarliestUnit();
+ Long messageStoreTime = this.messageStore.getQueueStore().getStoreTime(cqUnit);
+ return new Pair<>(cqUnit, messageStoreTime);
+ }
+
@Override
public CqUnit getEarliestUnit() {
return get(minOffsetInQueue);
diff --git a/store/src/main/java/org/apache/rocketmq/store/queue/ConsumeQueueInterface.java b/store/src/main/java/org/apache/rocketmq/store/queue/ConsumeQueueInterface.java
index 55d080829..c65f2a68b 100644
--- a/store/src/main/java/org/apache/rocketmq/store/queue/ConsumeQueueInterface.java
+++ b/store/src/main/java/org/apache/rocketmq/store/queue/ConsumeQueueInterface.java
@@ -18,10 +18,12 @@
package org.apache.rocketmq.store.queue;
import org.apache.rocketmq.common.BoundaryType;
+import org.apache.rocketmq.common.Pair;
import org.apache.rocketmq.common.attribute.CQType;
import org.apache.rocketmq.common.message.MessageExtBrokerInner;
import org.apache.rocketmq.store.DispatchRequest;
import org.apache.rocketmq.store.MessageFilter;
+import org.rocksdb.RocksDBException;
public interface ConsumeQueueInterface extends FileQueueLifeCycle {
/**
@@ -44,6 +46,16 @@ public interface ConsumeQueueInterface extends FileQueueLifeCycle {
*/
ReferredIterator<CqUnit> iterateFrom(long startIndex);
+ /**
+ * Get the units from the start offset.
+ *
+ * @param startIndex start index
+ * @param count the unit counts will be iterated
+ * @return the unit iterateFrom
+ * @throws RocksDBException only in rocksdb mode
+ */
+ ReferredIterator<CqUnit> iterateFrom(long startIndex, int count) throws RocksDBException;
+
/**
* Get cq unit at specified index
* @param index index
@@ -51,6 +63,18 @@ public interface ConsumeQueueInterface extends FileQueueLifeCycle {
*/
CqUnit get(long index);
+ /**
+ * Get earliest cq unit
+ * @return the cq unit and message storeTime at index
+ */
+ Pair<CqUnit, Long> getCqUnitAndStoreTime(long index);
+
+ /**
+ * Get earliest cq unit
+ * @return earliest cq unit and message storeTime
+ */
+ Pair<CqUnit, Long> getEarliestUnitAndStoreTime();
+
/**
* Get earliest cq unit
* @return earliest cq unit
@@ -153,8 +177,9 @@ public interface ConsumeQueueInterface extends FileQueueLifeCycle {
* Assign queue offset.
* @param queueOffsetAssigner the delegated queue offset assigner
* @param msg message itself
+ * @throws RocksDBException only in rocksdb mode
*/
- void assignQueueOffset(QueueOffsetOperator queueOffsetAssigner, MessageExtBrokerInner msg);
+ void assignQueueOffset(QueueOffsetOperator queueOffsetAssigner, MessageExtBrokerInner msg) throws RocksDBException;
/**
diff --git a/store/src/main/java/org/apache/rocketmq/store/queue/ConsumeQueueStore.java b/store/src/main/java/org/apache/rocketmq/store/queue/ConsumeQueueStore.java
index d03d15d65..616511b67 100644
--- a/store/src/main/java/org/apache/rocketmq/store/queue/ConsumeQueueStore.java
+++ b/store/src/main/java/org/apache/rocketmq/store/queue/ConsumeQueueStore.java
@@ -16,64 +16,128 @@
*/
package org.apache.rocketmq.store.queue;
+import java.io.File;
import java.nio.ByteBuffer;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Optional;
import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.FutureTask;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
+import org.apache.rocketmq.common.BoundaryType;
import org.apache.rocketmq.common.ThreadFactoryImpl;
import org.apache.rocketmq.common.TopicConfig;
import org.apache.rocketmq.common.attribute.CQType;
-import org.apache.rocketmq.common.constant.LoggerName;
import org.apache.rocketmq.common.message.MessageDecoder;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.topic.TopicValidator;
import org.apache.rocketmq.common.utils.QueueTypeUtils;
import org.apache.rocketmq.common.utils.ThreadUtils;
-import org.apache.rocketmq.logging.org.slf4j.Logger;
-import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
import org.apache.rocketmq.store.CommitLog;
import org.apache.rocketmq.store.ConsumeQueue;
import org.apache.rocketmq.store.DefaultMessageStore;
import org.apache.rocketmq.store.DispatchRequest;
-import org.apache.rocketmq.common.message.MessageExtBrokerInner;
import org.apache.rocketmq.store.SelectMappedBufferResult;
-import org.apache.rocketmq.store.config.MessageStoreConfig;
-
-import java.io.File;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
import static java.lang.String.format;
import static org.apache.rocketmq.store.config.StorePathConfigHelper.getStorePathBatchConsumeQueue;
import static org.apache.rocketmq.store.config.StorePathConfigHelper.getStorePathConsumeQueue;
-public class ConsumeQueueStore {
- private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME);
+public class ConsumeQueueStore extends AbstractConsumeQueueStore {
- protected final DefaultMessageStore messageStore;
- protected final MessageStoreConfig messageStoreConfig;
- protected final QueueOffsetOperator queueOffsetOperator = new QueueOffsetOperator();
- protected final ConcurrentMap<String/* topic */, ConcurrentMap<Integer/* queueId */, ConsumeQueueInterface>> consumeQueueTable;
+ public ConsumeQueueStore(DefaultMessageStore messageStore) {
+ super(messageStore);
+ }
- public ConsumeQueueStore(DefaultMessageStore messageStore, MessageStoreConfig messageStoreConfig) {
- this.messageStore = messageStore;
- this.messageStoreConfig = messageStoreConfig;
- this.consumeQueueTable = new ConcurrentHashMap<>(32);
+ @Override
+ public void start() {
+ log.info("Default ConsumeQueueStore start!");
}
- private FileQueueLifeCycle getLifeCycle(String topic, int queueId) {
- return findOrCreateConsumeQueue(topic, queueId);
+ @Override
+ public boolean load() {
+ boolean cqLoadResult = loadConsumeQueues(getStorePathConsumeQueue(this.messageStoreConfig.getStorePathRootDir()), CQType.SimpleCQ);
+ boolean bcqLoadResult = loadConsumeQueues(getStorePathBatchConsumeQueue(this.messageStoreConfig.getStorePathRootDir()), CQType.BatchCQ);
+ return cqLoadResult && bcqLoadResult;
+ }
+
+ @Override
+ public boolean loadAfterDestroy() {
+ return true;
+ }
+
+ @Override
+ public void recover() {
+ for (ConcurrentMap<Integer, ConsumeQueueInterface> maps : this.consumeQueueTable.values()) {
+ for (ConsumeQueueInterface logic : maps.values()) {
+ this.recover(logic);
+ }
+ }
}
+ @Override
+ public boolean recoverConcurrently() {
+ int count = 0;
+ for (ConcurrentMap<Integer, ConsumeQueueInterface> maps : this.consumeQueueTable.values()) {
+ count += maps.values().size();
+ }
+ final CountDownLatch countDownLatch = new CountDownLatch(count);
+ BlockingQueue<Runnable> recoverQueue = new LinkedBlockingQueue<>();
+ final ExecutorService executor = buildExecutorService(recoverQueue, "RecoverConsumeQueueThread_");
+ List<FutureTask<Boolean>> result = new ArrayList<>(count);
+ try {
+ for (ConcurrentMap<Integer, ConsumeQueueInterface> maps : this.consumeQueueTable.values()) {
+ for (final ConsumeQueueInterface logic : maps.values()) {
+ FutureTask<Boolean> futureTask = new FutureTask<>(() -> {
+ boolean ret = true;
+ try {
+ logic.recover();
+ } catch (Throwable e) {
+ ret = false;
+ log.error("Exception occurs while recover consume queue concurrently, " +
+ "topic={}, queueId={}", logic.getTopic(), logic.getQueueId(), e);
+ } finally {
+ countDownLatch.countDown();
+ }
+ return ret;
+ });
+
+ result.add(futureTask);
+ executor.submit(futureTask);
+ }
+ }
+ countDownLatch.await();
+ for (FutureTask<Boolean> task : result) {
+ if (task != null && task.isDone()) {
+ if (!task.get()) {
+ return false;
+ }
+ }
+ }
+ } catch (Exception e) {
+ log.error("Exception occurs while recover consume queue concurrently", e);
+ return false;
+ } finally {
+ executor.shutdown();
+ }
+ return true;
+ }
+
+ @Override
+ public boolean shutdown() {
+ return true;
+ }
+
+ @Override
public long rollNextFile(ConsumeQueueInterface consumeQueue, final long offset) {
FileQueueLifeCycle fileQueueLifeCycle = getLifeCycle(consumeQueue.getTopic(), consumeQueue.getQueueId());
return fileQueueLifeCycle.rollNextFile(offset);
@@ -83,32 +147,53 @@ public class ConsumeQueueStore {
consumeQueue.correctMinOffset(minCommitLogOffset);
}
- /**
- * Apply the dispatched request and build the consume queue. This function should be idempotent.
- *
- * @param consumeQueue consume queue
- * @param request dispatch request
- */
- public void putMessagePositionInfoWrapper(ConsumeQueueInterface consumeQueue, DispatchRequest request) {
- consumeQueue.putMessagePositionInfoWrapper(request);
- }
-
+ @Override
public void putMessagePositionInfoWrapper(DispatchRequest dispatchRequest) {
ConsumeQueueInterface cq = this.findOrCreateConsumeQueue(dispatchRequest.getTopic(), dispatchRequest.getQueueId());
this.putMessagePositionInfoWrapper(cq, dispatchRequest);
}
+ @Override
+ public List<ByteBuffer> rangeQuery(String topic, int queueId, long startIndex, int num) {
+ return null;
+ }
+
+ @Override
+ public ByteBuffer get(String topic, int queueId, long startIndex) {
+ return null;
+ }
+
+ @Override
+ public long getMaxOffsetInQueue(String topic, int queueId) {
+ ConsumeQueueInterface logic = findOrCreateConsumeQueue(topic, queueId);
+ if (logic != null) {
+ return logic.getMaxOffsetInQueue();
+ }
+ return 0;
+ }
+
+ @Override
+ public long getOffsetInQueueByTime(String topic, int queueId, long timestamp, BoundaryType boundaryType) {
+ ConsumeQueueInterface logic = findOrCreateConsumeQueue(topic, queueId);
+ if (logic != null) {
+ long resultOffset = logic.getOffsetInQueueByTime(timestamp, boundaryType);
+ // Make sure the result offset is in valid range.
+ resultOffset = Math.max(resultOffset, logic.getMinOffsetInQueue());
+ resultOffset = Math.min(resultOffset, logic.getMaxOffsetInQueue());
+ return resultOffset;
+ }
+ return 0;
+ }
+
+ private FileQueueLifeCycle getLifeCycle(String topic, int queueId) {
+ return findOrCreateConsumeQueue(topic, queueId);
+ }
+
public boolean load(ConsumeQueueInterface consumeQueue) {
FileQueueLifeCycle fileQueueLifeCycle = getLifeCycle(consumeQueue.getTopic(), consumeQueue.getQueueId());
return fileQueueLifeCycle.load();
}
- public boolean load() {
- boolean cqLoadResult = loadConsumeQueues(getStorePathConsumeQueue(this.messageStoreConfig.getStorePathRootDir()), CQType.SimpleCQ);
- boolean bcqLoadResult = loadConsumeQueues(getStorePathBatchConsumeQueue(this.messageStoreConfig.getStorePathRootDir()), CQType.BatchCQ);
- return cqLoadResult && bcqLoadResult;
- }
-
private boolean loadConsumeQueues(String storePath, CQType cqType) {
File dirLogic = new File(storePath);
File[] fileTopicList = dirLogic.listFiles();
@@ -189,62 +274,17 @@ public class ConsumeQueueStore {
fileQueueLifeCycle.recover();
}
- public void recover() {
- for (ConcurrentMap<Integer, ConsumeQueueInterface> maps : this.consumeQueueTable.values()) {
- for (ConsumeQueueInterface logic : maps.values()) {
- this.recover(logic);
- }
- }
- }
-
- public boolean recoverConcurrently() {
- int count = 0;
- for (ConcurrentMap<Integer, ConsumeQueueInterface> maps : this.consumeQueueTable.values()) {
- count += maps.values().size();
- }
- final CountDownLatch countDownLatch = new CountDownLatch(count);
- BlockingQueue<Runnable> recoverQueue = new LinkedBlockingQueue<>();
- final ExecutorService executor = buildExecutorService(recoverQueue, "RecoverConsumeQueueThread_");
- List<FutureTask<Boolean>> result = new ArrayList<>(count);
- try {
- for (ConcurrentMap<Integer, ConsumeQueueInterface> maps : this.consumeQueueTable.values()) {
- for (final ConsumeQueueInterface logic : maps.values()) {
- FutureTask<Boolean> futureTask = new FutureTask<>(() -> {
- boolean ret = true;
- try {
- logic.recover();
- } catch (Throwable e) {
- ret = false;
- log.error("Exception occurs while recover consume queue concurrently, " +
- "topic={}, queueId={}", logic.getTopic(), logic.getQueueId(), e);
- } finally {
- countDownLatch.countDown();
- }
- return ret;
- });
-
- result.add(futureTask);
- executor.submit(futureTask);
- }
- }
- countDownLatch.await();
- for (FutureTask<Boolean> task : result) {
- if (task != null && task.isDone()) {
- if (!task.get()) {
- return false;
- }
- }
- }
- } catch (Exception e) {
- log.error("Exception occurs while recover consume queue concurrently", e);
- return false;
- } finally {
- executor.shutdown();
+ @Override
+ public Long getMaxPhyOffsetInConsumeQueue(String topic, int queueId) {
+ ConsumeQueueInterface logic = findOrCreateConsumeQueue(topic, queueId);
+ if (logic != null) {
+ return logic.getMaxPhysicOffset();
}
- return true;
+ return null;
}
- public long getMaxOffsetInConsumeQueue() {
+ @Override
+ public long getMaxPhyOffsetInConsumeQueue() {
long maxPhysicOffset = -1L;
for (ConcurrentMap<Integer, ConsumeQueueInterface> maps : this.consumeQueueTable.values()) {
for (ConsumeQueueInterface logic : maps.values()) {
@@ -256,11 +296,22 @@ public class ConsumeQueueStore {
return maxPhysicOffset;
}
+ @Override
+ public long getMinOffsetInQueue(String topic, int queueId) {
+ ConsumeQueueInterface logic = findOrCreateConsumeQueue(topic, queueId);
+ if (logic != null) {
+ return logic.getMinOffsetInQueue();
+ }
+
+ return -1;
+ }
+
public void checkSelf(ConsumeQueueInterface consumeQueue) {
FileQueueLifeCycle fileQueueLifeCycle = getLifeCycle(consumeQueue.getTopic(), consumeQueue.getQueueId());
fileQueueLifeCycle.checkSelf();
}
+ @Override
public void checkSelf() {
for (Map.Entry<String, ConcurrentMap<Integer, ConsumeQueueInterface>> topicEntry : this.consumeQueueTable.entrySet()) {
for (Map.Entry<Integer, ConsumeQueueInterface> cqEntry : topicEntry.getValue().entrySet()) {
@@ -269,16 +320,19 @@ public class ConsumeQueueStore {
}
}
+ @Override
public boolean flush(ConsumeQueueInterface consumeQueue, int flushLeastPages) {
FileQueueLifeCycle fileQueueLifeCycle = getLifeCycle(consumeQueue.getTopic(), consumeQueue.getQueueId());
return fileQueueLifeCycle.flush(flushLeastPages);
}
+ @Override
public void destroy(ConsumeQueueInterface consumeQueue) {
FileQueueLifeCycle fileQueueLifeCycle = getLifeCycle(consumeQueue.getTopic(), consumeQueue.getQueueId());
fileQueueLifeCycle.destroy();
}
+ @Override
public int deleteExpiredFile(ConsumeQueueInterface consumeQueue, long minCommitLogPos) {
FileQueueLifeCycle fileQueueLifeCycle = getLifeCycle(consumeQueue.getTopic(), consumeQueue.getQueueId());
return fileQueueLifeCycle.deleteExpiredFile(minCommitLogPos);
@@ -300,21 +354,20 @@ public class ConsumeQueueStore {
fileQueueLifeCycle.cleanSwappedMap(forceCleanSwapIntervalMs);
}
+ @Override
public boolean isFirstFileAvailable(ConsumeQueueInterface consumeQueue) {
FileQueueLifeCycle fileQueueLifeCycle = getLifeCycle(consumeQueue.getTopic(), consumeQueue.getQueueId());
return fileQueueLifeCycle.isFirstFileAvailable();
}
+ @Override
public boolean isFirstFileExist(ConsumeQueueInterface consumeQueue) {
FileQueueLifeCycle fileQueueLifeCycle = getLifeCycle(consumeQueue.getTopic(), consumeQueue.getQueueId());
return fileQueueLifeCycle.isFirstFileExist();
}
+ @Override
public ConsumeQueueInterface findOrCreateConsumeQueue(String topic, int queueId) {
- return doFindOrCreateConsumeQueue(topic, queueId);
- }
-
- private ConsumeQueueInterface doFindOrCreateConsumeQueue(String topic, int queueId) {
ConcurrentMap<Integer, ConsumeQueueInterface> map = consumeQueueTable.get(topic);
if (null == map) {
ConcurrentMap<Integer, ConsumeQueueInterface> newMap = new ConcurrentHashMap<>(128);
@@ -361,46 +414,15 @@ public class ConsumeQueueStore {
return logic;
}
- public Long getMaxOffset(String topic, int queueId) {
- return this.queueOffsetOperator.currentQueueOffset(topic + "-" + queueId);
- }
-
- public void setTopicQueueTable(ConcurrentMap<String, Long> topicQueueTable) {
- this.queueOffsetOperator.setTopicQueueTable(topicQueueTable);
- this.queueOffsetOperator.setLmqTopicQueueTable(topicQueueTable);
- }
-
- public ConcurrentMap getTopicQueueTable() {
- return this.queueOffsetOperator.getTopicQueueTable();
- }
-
public void setBatchTopicQueueTable(ConcurrentMap<String, Long> batchTopicQueueTable) {
this.queueOffsetOperator.setBatchTopicQueueTable(batchTopicQueueTable);
}
- public void assignQueueOffset(MessageExtBrokerInner msg) {
- ConsumeQueueInterface consumeQueue = findOrCreateConsumeQueue(msg.getTopic(), msg.getQueueId());
- consumeQueue.assignQueueOffset(this.queueOffsetOperator, msg);
- }
-
- public void increaseQueueOffset(MessageExtBrokerInner msg, short messageNum) {
- ConsumeQueueInterface consumeQueue = findOrCreateConsumeQueue(msg.getTopic(), msg.getQueueId());
- consumeQueue.increaseQueueOffset(this.queueOffsetOperator, msg, messageNum);
- }
-
public void updateQueueOffset(String topic, int queueId, long offset) {
String topicQueueKey = topic + "-" + queueId;
this.queueOffsetOperator.updateQueueOffset(topicQueueKey, offset);
}
- public void removeTopicQueueTable(String topic, Integer queueId) {
- this.queueOffsetOperator.remove(topic, queueId);
- }
-
- public ConcurrentMap<String, ConcurrentMap<Integer, ConsumeQueueInterface>> getConsumeQueueTable() {
- return consumeQueueTable;
- }
-
private void putConsumeQueue(final String topic, final int queueId, final ConsumeQueueInterface consumeQueue) {
ConcurrentMap<Integer/* queueId */, ConsumeQueueInterface> map = this.consumeQueueTable.get(topic);
if (null == map) {
@@ -412,6 +434,7 @@ public class ConsumeQueueStore {
}
}
+ @Override
public void recoverOffsetTable(long minPhyOffset) {
ConcurrentMap<String, Long> cqOffsetTable = new ConcurrentHashMap<>(1024);
ConcurrentMap<String, Long> bcqOffsetTable = new ConcurrentHashMap<>(1024);
@@ -431,7 +454,7 @@ public class ConsumeQueueStore {
}
}
- //Correct unSubmit consumeOffset
+ // Correct unSubmit consumeOffset
if (messageStoreConfig.isDuplicationEnable()) {
SelectMappedBufferResult lastBuffer = null;
long startReadOffset = messageStore.getCommitLog().getConfirmOffset() == -1 ? 0 : messageStore.getCommitLog().getConfirmOffset();
@@ -476,6 +499,7 @@ public class ConsumeQueueStore {
this.setBatchTopicQueueTable(bcqOffsetTable);
}
+ @Override
public void destroy() {
for (ConcurrentMap<Integer, ConsumeQueueInterface> maps : this.consumeQueueTable.values()) {
for (ConsumeQueueInterface logic : maps.values()) {
@@ -484,8 +508,9 @@ public class ConsumeQueueStore {
}
}
+ @Override
public void cleanExpired(long minCommitLogOffset) {
- Iterator<Map.Entry<String, ConcurrentMap<Integer, ConsumeQueueInterface>>> it = this.consumeQueueTable.entrySet().iterator();
+ Iterator<Entry<String, ConcurrentMap<Integer, ConsumeQueueInterface>>> it = this.consumeQueueTable.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, ConcurrentMap<Integer, ConsumeQueueInterface>> next = it.next();
String topic = next.getKey();
@@ -526,14 +551,16 @@ public class ConsumeQueueStore {
}
}
- public void truncateDirty(long phyOffset) {
+ @Override
+ public void truncateDirty(long offsetToTruncate) {
for (ConcurrentMap<Integer, ConsumeQueueInterface> maps : this.consumeQueueTable.values()) {
for (ConsumeQueueInterface logic : maps.values()) {
- this.truncateDirtyLogicFiles(logic, phyOffset);
+ this.truncateDirtyLogicFiles(logic, offsetToTruncate);
}
}
}
+ @Override
public long getTotalSize() {
long totalSize = 0;
for (ConcurrentMap<Integer, ConsumeQueueInterface> maps : this.consumeQueueTable.values()) {
diff --git a/store/src/main/java/org/apache/rocketmq/store/queue/ConsumeQueueStoreInterface.java b/store/src/main/java/org/apache/rocketmq/store/queue/ConsumeQueueStoreInterface.java
new file mode 100644
index 000000000..268803dcc
--- /dev/null
+++ b/store/src/main/java/org/apache/rocketmq/store/queue/ConsumeQueueStoreInterface.java
@@ -0,0 +1,289 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.store.queue;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.concurrent.ConcurrentMap;
+import org.apache.rocketmq.common.BoundaryType;
+import org.apache.rocketmq.common.message.MessageExtBrokerInner;
+import org.apache.rocketmq.store.DispatchRequest;
+import org.rocksdb.RocksDBException;
+
+public interface ConsumeQueueStoreInterface {
+
+ /**
+ * Start the consumeQueueStore
+ */
+ void start();
+
+ /**
+ * Load from file.
+ * @return true if loaded successfully.
+ */
+ boolean load();
+
+ /**
+ * load after destroy
+ */
+ boolean loadAfterDestroy();
+
+ /**
+ * Recover from file.
+ */
+ void recover();
+
+ /**
+ * Recover concurrently from file.
+ * @return true if recovered successfully.
+ */
+ boolean recoverConcurrently();
+
+ /**
+ * Shutdown the consumeQueueStore
+ * @return true if shutdown successfully.
+ */
+ boolean shutdown();
+
+ /**
+ * destroy all consumeQueues
+ */
+ void destroy();
+
+ /**
+ * destroy the specific consumeQueue
+ * @throws RocksDBException only in rocksdb mode
+ */
+ void destroy(ConsumeQueueInterface consumeQueue) throws RocksDBException;
+
+ /**
+ * Flush cache to file.
+ * @param consumeQueue the consumeQueue will be flushed
+ * @param flushLeastPages the minimum number of pages to be flushed
+ * @return true if any data has been flushed.
+ */
+ boolean flush(ConsumeQueueInterface consumeQueue, int flushLeastPages);
+
+ /**
+ * clean expired data from minPhyOffset
+ * @param minPhyOffset
+ */
+ void cleanExpired(long minPhyOffset);
+
+ /**
+ * Check files.
+ */
+ void checkSelf();
+
+ /**
+ * Delete expired files ending at min commit log position.
+ * @param consumeQueue
+ * @param minCommitLogPos min commit log position
+ * @return deleted file numbers.
+ */
+ int deleteExpiredFile(ConsumeQueueInterface consumeQueue, long minCommitLogPos);
+
+ /**
+ * Is the first file available?
+ * @param consumeQueue
+ * @return true if it's available
+ */
+ boolean isFirstFileAvailable(ConsumeQueueInterface consumeQueue);
+
+ /**
+ * Does the first file exist?
+ * @param consumeQueue
+ * @return true if it exists
+ */
+ boolean isFirstFileExist(ConsumeQueueInterface consumeQueue);
+
+ /**
+ * Roll to next file.
+ * @param consumeQueue
+ * @param offset next beginning offset
+ * @return the beginning offset of the next file
+ */
+ long rollNextFile(ConsumeQueueInterface consumeQueue, final long offset);
+
+ /**
+ * truncate dirty data
+ * @param offsetToTruncate
+ * @throws RocksDBException only in rocksdb mode
+ */
+ void truncateDirty(long offsetToTruncate) throws RocksDBException;
+
+ /**
+ * Apply the dispatched request and build the consume queue. This function should be idempotent.
+ *
+ * @param consumeQueue consume queue
+ * @param request dispatch request
+ */
+ void putMessagePositionInfoWrapper(ConsumeQueueInterface consumeQueue, DispatchRequest request);
+
+ /**
+ * Apply the dispatched request. This function should be idempotent.
+ *
+ * @param request dispatch request
+ * @throws RocksDBException only in rocksdb mode will throw exception
+ */
+ void putMessagePositionInfoWrapper(DispatchRequest request) throws RocksDBException;
+
+ /**
+ * range query cqUnit(ByteBuffer) in rocksdb
+ * @param topic
+ * @param queueId
+ * @param startIndex
+ * @param num
+ * @return the byteBuffer list of the topic-queueId in rocksdb
+ * @throws RocksDBException only in rocksdb mode
+ */
+ List<ByteBuffer> rangeQuery(final String topic, final int queueId, final long startIndex, final int num) throws RocksDBException;
+
+ /**
+ * query cqUnit(ByteBuffer) in rocksdb
+ * @param topic
+ * @param queueId
+ * @param startIndex
+ * @return the byteBuffer of the topic-queueId in rocksdb
+ * @throws RocksDBException only in rocksdb mode
+ */
+ ByteBuffer get(final String topic, final int queueId, final long startIndex) throws RocksDBException;
+
+ /**
+ * get consumeQueue table
+ * @return the consumeQueue table
+ */
+ ConcurrentMap<String, ConcurrentMap<Integer, ConsumeQueueInterface>> getConsumeQueueTable();
+
+ /**
+ * Assign queue offset.
+ * @param msg message itself
+ * @throws RocksDBException only in rocksdb mode
+ */
+ void assignQueueOffset(MessageExtBrokerInner msg) throws RocksDBException;
+
+ /**
+ * Increase queue offset.
+ * @param msg message itself
+ * @param messageNum message number
+ */
+ void increaseQueueOffset(MessageExtBrokerInner msg, short messageNum);
+
+ /**
+ * recover topicQueue table by minPhyOffset
+ * @param minPhyOffset
+ */
+ void recoverOffsetTable(long minPhyOffset);
+
+ /**
+ * set topicQueue table
+ * @param topicQueueTable
+ */
+ void setTopicQueueTable(ConcurrentMap<String, Long> topicQueueTable);
+
+ /**
+ * remove topic-queueId from topicQueue table
+ * @param topic
+ * @param queueId
+ */
+ void removeTopicQueueTable(String topic, Integer queueId);
+
+ /**
+ * get topicQueue table
+ * @return the topicQueue table
+ */
+ ConcurrentMap getTopicQueueTable();
+
+ /**
+ * get the max physical offset in consumeQueue
+ * @param topic
+ * @param queueId
+ * @return
+ */
+ Long getMaxPhyOffsetInConsumeQueue(String topic, int queueId);
+
+ /**
+ * get maxOffset of specific topic-queueId in topicQueue table
+ * @param topic
+ * @param queueId
+ * @return the max offset in QueueOffsetOperator
+ */
+ Long getMaxOffset(String topic, int queueId);
+
+ /**
+ * get max physic offset in consumeQueue
+ * @return the max physic offset in consumeQueue
+ * @throws RocksDBException only in rocksdb mode
+ */
+ long getMaxPhyOffsetInConsumeQueue() throws RocksDBException;
+
+ /**
+ * get min logic offset of specific topic-queueId in consumeQueue
+ * @param topic
+ * @param queueId
+ * @return the min logic offset of specific topic-queueId in consumeQueue
+ * @throws RocksDBException only in rocksdb mode
+ */
+ long getMinOffsetInQueue(final String topic, final int queueId) throws RocksDBException;
+
+ /**
+ * get max logic offset of specific topic-queueId in consumeQueue
+ * @param topic
+ * @param queueId
+ * @return the max logic offset of specific topic-queueId in consumeQueue
+ * @throws RocksDBException only in rocksdb mode
+ */
+ long getMaxOffsetInQueue(final String topic, final int queueId) throws RocksDBException;
+
+ /**
+ * Get the message whose timestamp is the smallest, greater than or equal to the given time and when there are more
+ * than one message satisfy the condition, decide which one to return based on boundaryType.
+ * @param timestamp timestamp
+ * @param boundaryType Lower or Upper
+ * @return the offset(index)
+ * @throws RocksDBException only in rocksdb mode
+ */
+ long getOffsetInQueueByTime(String topic, int queueId, long timestamp, BoundaryType boundaryType) throws RocksDBException;
+
+ /**
+ * find or create the consumeQueue
+ * @param topic
+ * @param queueId
+ * @return the consumeQueue
+ */
+ ConsumeQueueInterface findOrCreateConsumeQueue(String topic, int queueId);
+
+ /**
+ * find the consumeQueueMap of topic
+ * @param topic
+ * @return the consumeQueueMap of topic
+ */
+ ConcurrentMap<Integer, ConsumeQueueInterface> findConsumeQueueMap(String topic);
+
+ /**
+ * get the total size of all consumeQueue
+ * @return the total size of all consumeQueue
+ */
+ long getTotalSize();
+
+ /**
+ * Get store time from commitlog by cqUnit
+ * @param cqUnit
+ * @return
+ */
+ long getStoreTime(CqUnit cqUnit);
+}
diff --git a/store/src/main/java/org/apache/rocketmq/store/queue/MultiDispatch.java b/store/src/main/java/org/apache/rocketmq/store/queue/MultiDispatch.java
new file mode 100644
index 000000000..d6291d908
--- /dev/null
+++ b/store/src/main/java/org/apache/rocketmq/store/queue/MultiDispatch.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.store.queue;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.rocketmq.common.MixAll;
+import org.apache.rocketmq.common.message.MessageConst;
+import org.apache.rocketmq.common.topic.TopicValidator;
+import org.apache.rocketmq.store.DispatchRequest;
+import org.apache.rocketmq.store.config.MessageStoreConfig;
+
+public class MultiDispatch {
+
+ public static String lmqQueueKey(String queueName) {
+ StringBuilder keyBuilder = new StringBuilder();
+ keyBuilder.append(queueName);
+ keyBuilder.append('-');
+ int queueId = 0;
+ keyBuilder.append(queueId);
+ return keyBuilder.toString();
+ }
+
+ public static boolean isNeedHandleMultiDispatch(MessageStoreConfig messageStoreConfig, String topic) {
+ return messageStoreConfig.isEnableMultiDispatch()
+ && !topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)
+ && !topic.startsWith(TopicValidator.SYSTEM_TOPIC_PREFIX)
+ && !topic.equals(TopicValidator.RMQ_SYS_SCHEDULE_TOPIC);
+ }
+
+ public static boolean checkMultiDispatchQueue(MessageStoreConfig messageStoreConfig, DispatchRequest dispatchRequest) {
+ if (!isNeedHandleMultiDispatch(messageStoreConfig, dispatchRequest.getTopic())) {
+ return false;
+ }
+ Map<String, String> prop = dispatchRequest.getPropertiesMap();
+ if (prop == null || prop.isEmpty()) {
+ return false;
+ }
+ String multiDispatchQueue = prop.get(MessageConst.PROPERTY_INNER_MULTI_DISPATCH);
+ String multiQueueOffset = prop.get(MessageConst.PROPERTY_INNER_MULTI_QUEUE_OFFSET);
+ if (StringUtils.isBlank(multiDispatchQueue) || StringUtils.isBlank(multiQueueOffset)) {
+ return false;
+ }
+ return true;
+ }
+
+ public static List<DispatchRequest> checkMultiDispatchQueue(MessageStoreConfig messageStoreConfig, List<DispatchRequest> dispatchRequests) {
+ if (!messageStoreConfig.isEnableMultiDispatch() || dispatchRequests == null || dispatchRequests.size() == 0) {
+ return null;
+ }
+ List<DispatchRequest> result = new ArrayList<>();
+ for (DispatchRequest dispatchRequest : dispatchRequests) {
+ if (checkMultiDispatchQueue(messageStoreConfig, dispatchRequest)) {
+ result.add(dispatchRequest);
+ }
+ }
+ return dispatchRequests;
+ }
+}
diff --git a/store/src/main/java/org/apache/rocketmq/store/queue/QueueOffsetOperator.java b/store/src/main/java/org/apache/rocketmq/store/queue/QueueOffsetOperator.java
index 2545bbf52..8da374828 100644
--- a/store/src/main/java/org/apache/rocketmq/store/queue/QueueOffsetOperator.java
+++ b/store/src/main/java/org/apache/rocketmq/store/queue/QueueOffsetOperator.java
@@ -41,6 +41,10 @@ public class QueueOffsetOperator {
return ConcurrentHashMapUtils.computeIfAbsent(this.topicQueueTable, topicQueueKey, k -> 0L);
}
+ public Long getTopicQueueNextOffset(String topicQueueKey) {
+ return this.topicQueueTable.get(topicQueueKey);
+ }
+
public void increaseQueueOffset(String topicQueueKey, short messageNum) {
Long queueOffset = ConcurrentHashMapUtils.computeIfAbsent(this.topicQueueTable, topicQueueKey, k -> 0L);
topicQueueTable.put(topicQueueKey, queueOffset + messageNum);
@@ -63,6 +67,10 @@ public class QueueOffsetOperator {
return ConcurrentHashMapUtils.computeIfAbsent(this.lmqTopicQueueTable, topicQueueKey, k -> 0L);
}
+ public Long getLmqTopicQueueNextOffset(String topicQueueKey) {
+ return this.lmqTopicQueueTable.get(topicQueueKey);
+ }
+
public void increaseLmqOffset(String topicQueueKey, short messageNum) {
Long lmqOffset = ConcurrentHashMapUtils.computeIfAbsent(this.lmqTopicQueueTable, topicQueueKey, k -> 0L);
this.lmqTopicQueueTable.put(topicQueueKey, lmqOffset + messageNum);
diff --git a/store/src/main/java/org/apache/rocketmq/store/queue/RocksDBConsumeQueue.java b/store/src/main/java/org/apache/rocketmq/store/queue/RocksDBConsumeQueue.java
new file mode 100644
index 000000000..759be395d
--- /dev/null
+++ b/store/src/main/java/org/apache/rocketmq/store/queue/RocksDBConsumeQueue.java
@@ -0,0 +1,437 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.store.queue;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.rocketmq.common.BoundaryType;
+import org.apache.rocketmq.common.MixAll;
+import org.apache.rocketmq.common.Pair;
+import org.apache.rocketmq.common.attribute.CQType;
+import org.apache.rocketmq.common.constant.LoggerName;
+import org.apache.rocketmq.common.message.MessageAccessor;
+import org.apache.rocketmq.common.message.MessageConst;
+import org.apache.rocketmq.common.message.MessageExtBrokerInner;
+import org.apache.rocketmq.logging.org.slf4j.Logger;
+import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+import org.apache.rocketmq.store.ConsumeQueue;
+import org.apache.rocketmq.store.DispatchRequest;
+import org.apache.rocketmq.store.MessageFilter;
+import org.apache.rocketmq.store.MessageStore;
+import org.rocksdb.RocksDBException;
+
+public class RocksDBConsumeQueue implements ConsumeQueueInterface {
+ private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME);
+ private static final Logger ERROR_LOG = LoggerFactory.getLogger(LoggerName.STORE_ERROR_LOGGER_NAME);
+
+ private final MessageStore messageStore;
+ private final String topic;
+ private final int queueId;
+
+ public RocksDBConsumeQueue(final MessageStore messageStore, final String topic, final int queueId) {
+ this.messageStore = messageStore;
+ this.topic = topic;
+ this.queueId = queueId;
+ }
+
+ public RocksDBConsumeQueue(final String topic, final int queueId) {
+ this.messageStore = null;
+ this.topic = topic;
+ this.queueId = queueId;
+ }
+
+ @Override
+ public boolean load() {
+ return true;
+ }
+
+ @Override
+ public void recover() {
+ // ignore
+ }
+
+ @Override
+ public void checkSelf() {
+ // ignore
+ }
+
+ @Override
+ public boolean flush(final int flushLeastPages) {
+ return true;
+ }
+
+ @Override
+ public void destroy() {
+ // ignore
+ }
+
+ @Override
+ public void truncateDirtyLogicFiles(long maxCommitLogPos) {
+ // ignored
+ }
+
+ @Override
+ public int deleteExpiredFile(long minCommitLogPos) {
+ return 0;
+ }
+
+ @Override
+ public long rollNextFile(long nextBeginOffset) {
+ return 0;
+ }
+
+ @Override
+ public boolean isFirstFileAvailable() {
+ return true;
+ }
+
+ @Override
+ public boolean isFirstFileExist() {
+ return true;
+ }
+
+ @Override
+ public void swapMap(int reserveNum, long forceSwapIntervalMs, long normalSwapIntervalMs) {
+ // ignore
+ }
+
+ @Override
+ public void cleanSwappedMap(long forceCleanSwapIntervalMs) {
+ // ignore
+ }
+
+ @Override
+ public long getMaxOffsetInQueue() {
+ try {
+ return this.messageStore.getQueueStore().getMaxOffsetInQueue(topic, queueId);
+ } catch (RocksDBException e) {
+ ERROR_LOG.error("getMaxOffsetInQueue Failed. topic: {}, queueId: {}", topic, queueId, e);
+ return 0;
+ }
+ }
+
+ @Override
+ public long getMessageTotalInQueue() {
+ try {
+ long maxOffsetInQueue = this.messageStore.getQueueStore().getMaxOffsetInQueue(topic, queueId);
+ long minOffsetInQueue = this.messageStore.getQueueStore().getMinOffsetInQueue(topic, queueId);
+ return maxOffsetInQueue - minOffsetInQueue;
+ } catch (RocksDBException e) {
+ ERROR_LOG.error("getMessageTotalInQueue Failed. topic: {}, queueId: {}, {}", topic, queueId, e);
+ }
+ return -1;
+ }
+
+ /**
+ * We already implement it in RocksDBConsumeQueueStore.
+ * @see RocksDBConsumeQueueStore#getOffsetInQueueByTime
+ * @param timestamp timestamp
+ * @return
+ */
+ @Override
+ public long getOffsetInQueueByTime(long timestamp) {
+ return 0;
+ }
+
+ /**
+ * We already implement it in RocksDBConsumeQueueStore.
+ * @see RocksDBConsumeQueueStore#getOffsetInQueueByTime
+ * @param timestamp timestamp
+ * @param boundaryType Lower or Upper
+ * @return
+ */
+ @Override
+ public long getOffsetInQueueByTime(long timestamp, BoundaryType boundaryType) {
+ return 0;
+ }
+
+ @Override
+ public long getMaxPhysicOffset() {
+ Long maxPhyOffset = this.messageStore.getQueueStore().getMaxPhyOffsetInConsumeQueue(topic, queueId);
+ return maxPhyOffset == null ? -1 : maxPhyOffset;
+ }
+
+ @Override
+ public long getMinLogicOffset() {
+ return 0;
+ }
+
+ @Override
+ public CQType getCQType() {
+ return CQType.RocksDBCQ;
+ }
+
+ @Override
+ public long getTotalSize() {
+ // ignored
+ return 0;
+ }
+
+ @Override
+ public int getUnitSize() {
+ // attention: unitSize should equal to 'ConsumeQueue.CQ_STORE_UNIT_SIZE'
+ return ConsumeQueue.CQ_STORE_UNIT_SIZE;
+ }
+
+ /**
+ * Ignored, we already implement this method
+ * @see org.apache.rocketmq.store.queue.RocksDBConsumeQueueOffsetTable#getMinCqOffset(String, int)
+ */
+ @Override
+ public void correctMinOffset(long minCommitLogOffset) {
+
+ }
+
+ /**
+ * Ignored, in rocksdb mode, we build cq in RocksDBConsumeQueueStore
+ * @see org.apache.rocketmq.store.queue.RocksDBConsumeQueueStore#putMessagePosition()
+ */
+ @Override
+ public void putMessagePositionInfoWrapper(DispatchRequest request) {
+
+ }
+
+ @Override
+ public void assignQueueOffset(QueueOffsetOperator queueOffsetOperator, MessageExtBrokerInner msg) throws RocksDBException {
+ String topicQueueKey = getTopic() + "-" + getQueueId();
+ Long queueOffset = queueOffsetOperator.getTopicQueueNextOffset(topicQueueKey);
+ if (queueOffset == null) {
+ // we will recover topic queue table from rocksdb when we use it.
+ queueOffset = this.messageStore.getQueueStore().getMaxOffsetInQueue(topic, queueId);
+ queueOffsetOperator.updateQueueOffset(topicQueueKey, queueOffset);
+ }
+ msg.setQueueOffset(queueOffset);
+
+ // Handling the multi dispatch message. In the context of a light message queue (as defined in RIP-28),
+ // light message queues are constructed based on message properties, which requires special handling of queue offset of the light message queue.
+ if (!MultiDispatch.isNeedHandleMultiDispatch(this.messageStore.getMessageStoreConfig(), msg.getTopic())) {
+ return;
+ }
+ String multiDispatchQueue = msg.getProperty(MessageConst.PROPERTY_INNER_MULTI_DISPATCH);
+ if (StringUtils.isBlank(multiDispatchQueue)) {
+ return;
+ }
+ String[] queues = multiDispatchQueue.split(MixAll.MULTI_DISPATCH_QUEUE_SPLITTER);
+ Long[] queueOffsets = new Long[queues.length];
+ for (int i = 0; i < queues.length; i++) {
+ if (this.messageStore.getMessageStoreConfig().isEnableLmq() && MixAll.isLmq(queues[i])) {
+ String key = MultiDispatch.lmqQueueKey(queues[i]);
+ queueOffsets[i] = queueOffsetOperator.getLmqTopicQueueNextOffset(key);
+ }
+ }
+ MessageAccessor.putProperty(msg, MessageConst.PROPERTY_INNER_MULTI_QUEUE_OFFSET,
+ StringUtils.join(queueOffsets, MixAll.MULTI_DISPATCH_QUEUE_SPLITTER));
+ msg.removeWaitStorePropertyString();
+ }
+
+ @Override
+ public void increaseQueueOffset(QueueOffsetOperator queueOffsetOperator, MessageExtBrokerInner msg, short messageNum) {
+ String topicQueueKey = getTopic() + "-" + getQueueId();
+ queueOffsetOperator.increaseQueueOffset(topicQueueKey, messageNum);
+
+ // Handling the multi dispatch message. In the context of a light message queue (as defined in RIP-28),
+ // light message queues are constructed based on message properties, which requires special handling of queue offset of the light message queue.
+ if (!MultiDispatch.isNeedHandleMultiDispatch(this.messageStore.getMessageStoreConfig(), msg.getTopic())) {
+ return;
+ }
+ String multiDispatchQueue = msg.getProperty(MessageConst.PROPERTY_INNER_MULTI_DISPATCH);
+ if (StringUtils.isBlank(multiDispatchQueue)) {
+ return;
+ }
+ String[] queues = multiDispatchQueue.split(MixAll.MULTI_DISPATCH_QUEUE_SPLITTER);
+ for (int i = 0; i < queues.length; i++) {
+ if (this.messageStore.getMessageStoreConfig().isEnableLmq() && MixAll.isLmq(queues[i])) {
+ String key = MultiDispatch.lmqQueueKey(queues[i]);
+ queueOffsetOperator.increaseLmqOffset(key, (short) 1);
+ }
+ }
+ }
+
+ @Override
+ public long estimateMessageCount(long from, long to, MessageFilter filter) {
+ // todo
+ return 0;
+ }
+
+ @Override
+ public long getMinOffsetInQueue() {
+ return this.messageStore.getMinOffsetInQueue(this.topic, this.queueId);
+ }
+
+ private int pullNum(long cqOffset, long maxCqOffset) {
+ long diffLong = maxCqOffset - cqOffset;
+ if (diffLong < Integer.MAX_VALUE) {
+ int diffInt = (int) diffLong;
+ return diffInt > 16 ? 16 : diffInt;
+ }
+ return 16;
+ }
+
+ @Override
+ public ReferredIterator<CqUnit> iterateFrom(final long startIndex) {
+ try {
+ long maxCqOffset = getMaxOffsetInQueue();
+ if (startIndex < maxCqOffset) {
+ int num = pullNum(startIndex, maxCqOffset);
+ return iterateFrom0(startIndex, num);
+ }
+ } catch (RocksDBException e) {
+ log.error("[RocksDBConsumeQueue] iterateFrom error!", e);
+ }
+ return null;
+ }
+
+ @Override
+ public ReferredIterator<CqUnit> iterateFrom(long startIndex, int count) throws RocksDBException {
+ long maxCqOffset = getMaxOffsetInQueue();
+ if (startIndex < maxCqOffset) {
+ int num = Math.min((int)(maxCqOffset - startIndex), count);
+ return iterateFrom0(startIndex, num);
+ }
+ return null;
+ }
+
+ @Override
+ public CqUnit get(long index) {
+ Pair<CqUnit, Long> pair = getCqUnitAndStoreTime(index);
+ return pair == null ? null : pair.getObject1();
+ }
+
+ @Override
+ public Pair<CqUnit, Long> getCqUnitAndStoreTime(long index) {
+ ByteBuffer byteBuffer;
+ try {
+ byteBuffer = this.messageStore.getQueueStore().get(topic, queueId, index);
+ } catch (RocksDBException e) {
+ ERROR_LOG.error("getUnitAndStoreTime Failed. topic: {}, queueId: {}", topic, queueId, e);
+ return null;
+ }
+ if (byteBuffer == null || byteBuffer.remaining() < RocksDBConsumeQueueTable.CQ_UNIT_SIZE) {
+ return null;
+ }
+ long phyOffset = byteBuffer.getLong();
+ int size = byteBuffer.getInt();
+ long tagCode = byteBuffer.getLong();
+ long messageStoreTime = byteBuffer.getLong();
+ return new Pair<>(new CqUnit(index, phyOffset, size, tagCode), messageStoreTime);
+ }
+
+ @Override
+ public Pair<CqUnit, Long> getEarliestUnitAndStoreTime() {
+ try {
+ long minOffset = this.messageStore.getQueueStore().getMinOffsetInQueue(topic, queueId);
+ return getCqUnitAndStoreTime(minOffset);
+ } catch (RocksDBException e) {
+ ERROR_LOG.error("getEarliestUnitAndStoreTime Failed. topic: {}, queueId: {}", topic, queueId, e);
+ }
+ return null;
+ }
+
+ @Override
+ public CqUnit getEarliestUnit() {
+ Pair<CqUnit, Long> pair = getEarliestUnitAndStoreTime();
+ return pair == null ? null : pair.getObject1();
+ }
+
+ @Override
+ public CqUnit getLatestUnit() {
+ try {
+ long maxOffset = this.messageStore.getQueueStore().getMaxOffsetInQueue(topic, queueId);
+ return get(maxOffset);
+ } catch (RocksDBException e) {
+ ERROR_LOG.error("getLatestUnit Failed. topic: {}, queueId: {}, {}", topic, queueId, e.getMessage());
+ }
+ return null;
+ }
+
+ @Override
+ public long getLastOffset() {
+ return getMaxPhysicOffset();
+ }
+
+ private ReferredIterator<CqUnit> iterateFrom0(final long startIndex, final int count) throws RocksDBException {
+ List<ByteBuffer> byteBufferList = this.messageStore.getQueueStore().rangeQuery(topic, queueId, startIndex, count);
+ if (byteBufferList == null || byteBufferList.isEmpty()) {
+ if (this.messageStore.getMessageStoreConfig().isEnableRocksDBLog()) {
+ log.warn("iterateFrom0 - find nothing, startIndex:{}, count:{}", startIndex, count);
+ }
+ return null;
+ }
+ return new RocksDBConsumeQueueIterator(byteBufferList, startIndex);
+ }
+
+ @Override
+ public String getTopic() {
+ return topic;
+ }
+
+ @Override
+ public int getQueueId() {
+ return queueId;
+ }
+
+ private class RocksDBConsumeQueueIterator implements ReferredIterator<CqUnit> {
+ private final List<ByteBuffer> byteBufferList;
+ private final long startIndex;
+ private final int totalCount;
+ private int currentIndex;
+
+ public RocksDBConsumeQueueIterator(final List<ByteBuffer> byteBufferList, final long startIndex) {
+ this.byteBufferList = byteBufferList;
+ this.startIndex = startIndex;
+ this.totalCount = byteBufferList.size();
+ this.currentIndex = 0;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return this.currentIndex < this.totalCount;
+ }
+
+ @Override
+ public CqUnit next() {
+ if (!hasNext()) {
+ return null;
+ }
+ final int currentIndex = this.currentIndex;
+ final ByteBuffer byteBuffer = this.byteBufferList.get(currentIndex);
+ CqUnit cqUnit = new CqUnit(this.startIndex + currentIndex, byteBuffer.getLong(), byteBuffer.getInt(), byteBuffer.getLong());
+ this.currentIndex++;
+ return cqUnit;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("remove");
+ }
+
+ @Override
+ public void release() {
+ }
+
+ @Override
+ public CqUnit nextAndRelease() {
+ try {
+ return next();
+ } finally {
+ release();
+ }
+ }
+ }
+}
diff --git a/store/src/main/java/org/apache/rocketmq/store/queue/RocksDBConsumeQueueOffsetTable.java b/store/src/main/java/org/apache/rocketmq/store/queue/RocksDBConsumeQueueOffsetTable.java
new file mode 100644
index 000000000..6fa66282e
--- /dev/null
+++ b/store/src/main/java/org/apache/rocketmq/store/queue/RocksDBConsumeQueueOffsetTable.java
@@ -0,0 +1,641 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.store.queue;
+
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.rocketmq.common.Pair;
+import org.apache.rocketmq.common.TopicConfig;
+import org.apache.rocketmq.common.constant.LoggerName;
+import org.apache.rocketmq.common.topic.TopicValidator;
+import org.apache.rocketmq.logging.org.slf4j.Logger;
+import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+import org.apache.rocketmq.store.ConsumeQueue;
+import org.apache.rocketmq.store.DefaultMessageStore;
+import org.apache.rocketmq.store.DispatchRequest;
+import org.apache.rocketmq.store.rocksdb.ConsumeQueueRocksDBStorage;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.RocksDBException;
+import org.rocksdb.RocksIterator;
+import org.rocksdb.WriteBatch;
+
+import static org.apache.rocketmq.common.utils.DataConverter.CHARSET_UTF8;
+import static org.apache.rocketmq.store.queue.RocksDBConsumeQueueStore.CTRL_1;
+
+public class RocksDBConsumeQueueOffsetTable {
+ private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME);
+ private static final Logger ERROR_LOG = LoggerFactory.getLogger(LoggerName.STORE_ERROR_LOGGER_NAME);
+ private static final Logger ROCKSDB_LOG = LoggerFactory.getLogger(LoggerName.ROCKSDB_LOGGER_NAME);
+
+ private static final byte[] MAX_BYTES = "max".getBytes(CHARSET_UTF8);
+ private static final byte[] MIN_BYTES = "min".getBytes(CHARSET_UTF8);
+
+ /**
+ * Rocksdb ConsumeQueue's Offset unit. Format:
+ *
+ * <pre>
+ * ┌─────────────────────────┬───────────┬───────────────────────┬───────────┬───────────┬───────────┬─────────────┐
+ * │ Topic Bytes Array Size │ CTRL_1 │ Topic Bytes Array │ CTRL_1 │ Max(Min) │ CTRL_1 │ QueueId │
+ * │ (4 Bytes) │ (1 Bytes) │ (n Bytes) │ (1 Bytes) │ (3 Bytes) │ (1 Bytes) │ (4 Bytes) │
+ * ├─────────────────────────┴───────────┴───────────────────────┴───────────┴───────────┴───────────┴─────────────┤
+ * │ Key Unit │
+ * │ │
+ * </pre>
+ *
+ * <pre>
+ * ┌─────────────────────────────┬────────────────────────┐
+ * │ CommitLog Physical Offset │ ConsumeQueue Offset │
+ * │ (8 Bytes) │ (8 Bytes) │
+ * ├─────────────────────────────┴────────────────────────┤
+ * │ Value Unit │
+ * │ │
+ * </pre>
+ * ConsumeQueue's Offset unit. Size: CommitLog Physical Offset(8) + ConsumeQueue Offset(8) = 16 Bytes
+ */
+ private static final int OFFSET_PHY_OFFSET = 0;
+ private static final int OFFSET_CQ_OFFSET = 8;
+ /**
+ *
+ * ┌─────────────────────────┬───────────┬───────────┬───────────┬───────────┬─────────────┐
+ * │ Topic Bytes Array Size │ CTRL_1 │ CTRL_1 │ Max(Min) │ CTRL_1 │ QueueId │
+ * │ (4 Bytes) │ (1 Bytes) │ (1 Bytes) │ (3 Bytes) │ (1 Bytes) │ (4 Bytes) │
+ * ├─────────────────────────┴───────────┴───────────┴───────────┴───────────┴─────────────┤
+ */
+ private static final int OFFSET_KEY_LENGTH_WITHOUT_TOPIC_BYTES = 4 + 1 + 1 + 3 + 1 + 4;
+ private static final int OFFSET_VALUE_LENGTH = 8 + 8;
+
+ /**
+ * We use a new system topic='CHECKPOINT_TOPIC' to record the maxPhyOffset built by CQ dispatch thread.
+ * @see ConsumeQueueStore#getMaxPhyOffsetInConsumeQueue(), we use it to find the maxPhyOffset built by CQ dispatch thread.
+ * If we do not record the maxPhyOffset, it may take us a long time to start traversing from the head of
+ * RocksDBConsumeQueueOffsetTable to find it.
+ */
+ private static final String MAX_PHYSICAL_OFFSET_CHECKPOINT = TopicValidator.RMQ_SYS_ROCKSDB_OFFSET_TOPIC;
+ private static final byte[] MAX_PHYSICAL_OFFSET_CHECKPOINT_BYTES = MAX_PHYSICAL_OFFSET_CHECKPOINT.getBytes(CHARSET_UTF8);
+ private static final int INNER_CHECKPOINT_TOPIC_LEN = OFFSET_KEY_LENGTH_WITHOUT_TOPIC_BYTES + MAX_PHYSICAL_OFFSET_CHECKPOINT_BYTES.length;
+ private static final ByteBuffer INNER_CHECKPOINT_TOPIC = ByteBuffer.allocateDirect(INNER_CHECKPOINT_TOPIC_LEN);
+ private static final byte[] MAX_PHYSICAL_OFFSET_CHECKPOINT_KEY = new byte[INNER_CHECKPOINT_TOPIC_LEN];
+ private final ByteBuffer maxPhyOffsetBB;
+ static {
+ buildOffsetKeyByteBuffer0(INNER_CHECKPOINT_TOPIC, MAX_PHYSICAL_OFFSET_CHECKPOINT_BYTES, 0, true);
+ INNER_CHECKPOINT_TOPIC.position(0).limit(INNER_CHECKPOINT_TOPIC_LEN);
+ INNER_CHECKPOINT_TOPIC.get(MAX_PHYSICAL_OFFSET_CHECKPOINT_KEY);
+ }
+
+ private final RocksDBConsumeQueueTable rocksDBConsumeQueueTable;
+ private final ConsumeQueueRocksDBStorage rocksDBStorage;
+ private final DefaultMessageStore messageStore;
+
+ private ColumnFamilyHandle offsetCFH;
+
+ /**
+ * Although we have already put max(min) consumeQueueOffset and physicalOffset in rocksdb, we still hope to get them
+ * from heap to avoid accessing rocksdb.
+ * @see ConsumeQueue#getMaxPhysicOffset(), maxPhysicOffset --> topicQueueMaxCqOffset
+ * @see ConsumeQueue#getMinLogicOffset(), minLogicOffset --> topicQueueMinOffset
+ */
+ private final Map<String/* topic-queueId */, PhyAndCQOffset> topicQueueMinOffset;
+ private final Map<String/* topic-queueId */, Long> topicQueueMaxCqOffset;
+
+ public RocksDBConsumeQueueOffsetTable(RocksDBConsumeQueueTable rocksDBConsumeQueueTable,
+ ConsumeQueueRocksDBStorage rocksDBStorage, DefaultMessageStore messageStore) {
+ this.rocksDBConsumeQueueTable = rocksDBConsumeQueueTable;
+ this.rocksDBStorage = rocksDBStorage;
+ this.messageStore = messageStore;
+ this.topicQueueMinOffset = new ConcurrentHashMap(1024);
+ this.topicQueueMaxCqOffset = new ConcurrentHashMap(1024);
+
+ this.maxPhyOffsetBB = ByteBuffer.allocateDirect(8);
+ }
+
+ public void load() {
+ this.offsetCFH = this.rocksDBStorage.getOffsetCFHandle();
+ }
+
+ public void updateTempTopicQueueMaxOffset(final Pair<ByteBuffer, ByteBuffer> offsetBBPair,
+ final byte[] topicBytes, final DispatchRequest request,
+ final Map<ByteBuffer, Pair<ByteBuffer, DispatchRequest>> tempTopicQueueMaxOffsetMap) {
+ buildOffsetKeyAndValueByteBuffer(offsetBBPair, topicBytes, request);
+ ByteBuffer topicQueueId = offsetBBPair.getObject1();
+ ByteBuffer maxOffsetBB = offsetBBPair.getObject2();
+ Pair<ByteBuffer, DispatchRequest> old = tempTopicQueueMaxOffsetMap.get(topicQueueId);
+ if (old == null) {
+ tempTopicQueueMaxOffsetMap.put(topicQueueId, new Pair(maxOffsetBB, request));
+ } else {
+ long oldMaxOffset = old.getObject1().getLong(OFFSET_CQ_OFFSET);
+ long maxOffset = maxOffsetBB.getLong(OFFSET_CQ_OFFSET);
+ if (maxOffset >= oldMaxOffset) {
+ ERROR_LOG.error("cqOffset invalid1. old: {}, now: {}", oldMaxOffset, maxOffset);
+ }
+ }
+ }
+
+ public void putMaxPhyAndCqOffset(final Map<ByteBuffer, Pair<ByteBuffer, DispatchRequest>> tempTopicQueueMaxOffsetMap,
+ final WriteBatch writeBatch, final long maxPhyOffset) throws RocksDBException {
+ for (Map.Entry<ByteBuffer, Pair<ByteBuffer, DispatchRequest>> entry : tempTopicQueueMaxOffsetMap.entrySet()) {
+ writeBatch.put(this.offsetCFH, entry.getKey(), entry.getValue().getObject1());
+ }
+
+ appendMaxPhyOffset(writeBatch, maxPhyOffset);
+ }
+
+ public void putHeapMaxCqOffset(final Map<ByteBuffer, Pair<ByteBuffer, DispatchRequest>> tempTopicQueueMaxOffsetMap) {
+ for (Map.Entry<ByteBuffer, Pair<ByteBuffer, DispatchRequest>> entry : tempTopicQueueMaxOffsetMap.entrySet()) {
+ DispatchRequest request = entry.getValue().getObject2();
+ putHeapMaxCqOffset(request.getTopic(), request.getQueueId(), request.getConsumeQueueOffset());
+ }
+ }
+
+ /**
+ * When topic is deleted, we clean up its offset info in rocksdb.
+ * @param topic
+ * @param queueId
+ * @throws RocksDBException
+ */
+ public void destroyOffset(String topic, int queueId, WriteBatch writeBatch) throws RocksDBException {
+ final byte[] topicBytes = topic.getBytes(CHARSET_UTF8);
+ final ByteBuffer minOffsetKey = buildOffsetKeyByteBuffer(topicBytes, queueId, false);
+ byte[] minOffsetBytes = this.rocksDBStorage.getOffset(minOffsetKey.array());
+ Long startCQOffset = (minOffsetBytes != null) ? ByteBuffer.wrap(minOffsetBytes).getLong(OFFSET_CQ_OFFSET) : null;
+
+ final ByteBuffer maxOffsetKey = buildOffsetKeyByteBuffer(topicBytes, queueId, true);
+ byte[] maxOffsetBytes = this.rocksDBStorage.getOffset(maxOffsetKey.array());
+ Long endCQOffset = (maxOffsetBytes != null) ? ByteBuffer.wrap(maxOffsetBytes).getLong(OFFSET_CQ_OFFSET) : null;
+
+ writeBatch.delete(this.offsetCFH, minOffsetKey.array());
+ writeBatch.delete(this.offsetCFH, maxOffsetKey.array());
+
+ String topicQueueId = buildTopicQueueId(topic, queueId);
+ removeHeapMinCqOffset(topicQueueId);
+ removeHeapMaxCqOffset(topicQueueId);
+
+ log.info("RocksDB offset table delete topic: {}, queueId: {}, minOffset: {}, maxOffset: {}", topic, queueId,
+ startCQOffset, endCQOffset);
+ }
+
+ private void appendMaxPhyOffset(final WriteBatch writeBatch, final long maxPhyOffset) throws RocksDBException {
+ final ByteBuffer maxPhyOffsetBB = this.maxPhyOffsetBB;
+ maxPhyOffsetBB.position(0).limit(8);
+ maxPhyOffsetBB.putLong(maxPhyOffset);
+ maxPhyOffsetBB.flip();
+
+ INNER_CHECKPOINT_TOPIC.position(0).limit(INNER_CHECKPOINT_TOPIC_LEN);
+ writeBatch.put(this.offsetCFH, INNER_CHECKPOINT_TOPIC, maxPhyOffsetBB);
+ }
+
+ public long getMaxPhyOffset() throws RocksDBException {
+ byte[] valueBytes = this.rocksDBStorage.getOffset(MAX_PHYSICAL_OFFSET_CHECKPOINT_KEY);
+ if (valueBytes == null) {
+ return 0;
+ }
+ ByteBuffer valueBB = ByteBuffer.wrap(valueBytes);
+ return valueBB.getLong(0);
+ }
+
+ /**
+ * Traverse the offset table to find dirty topic
+ * @param existTopicSet
+ * @return
+ */
+ public Map<String, Set<Integer>> iterateOffsetTable2FindDirty(final Set<String> existTopicSet) {
+ Map<String/* topic */, Set<Integer/* queueId */>> topicQueueIdToBeDeletedMap = new HashMap<>();
+
+ RocksIterator iterator = null;
+ try {
+ iterator = rocksDBStorage.seekOffsetCF();
+ if (iterator == null) {
+ return topicQueueIdToBeDeletedMap;
+ }
+ for (iterator.seekToFirst(); iterator.isValid(); iterator.next()) {
+ byte[] key = iterator.key();
+ byte[] value = iterator.value();
+ if (key == null || key.length <= OFFSET_KEY_LENGTH_WITHOUT_TOPIC_BYTES
+ || value == null || value.length != OFFSET_VALUE_LENGTH) {
+ continue;
+ }
+ ByteBuffer keyBB = ByteBuffer.wrap(key);
+ int topicLen = keyBB.getInt(0);
+ byte[] topicBytes = new byte[topicLen];
+ /**
+ * "Topic Bytes Array Size" + "CTRL_1" = 4 + 1
+ */
+ keyBB.position(4 + 1);
+ keyBB.get(topicBytes);
+ String topic = new String(topicBytes, CHARSET_UTF8);
+ if (TopicValidator.isSystemTopic(topic)) {
+ continue;
+ }
+
+ /**
+ * "Topic Bytes Array Size" + "CTRL_1" + "Topic Bytes Array" + "CTRL_1" + "Max(min)" + "CTRL_1"
+ * = 4 + 1 + topicLen + 1 + 3 + 1
+ */
+ int queueId = keyBB.getInt(4 + 1 + topicLen + 1 + 3 + 1);
+
+ if (!existTopicSet.contains(topic)) {
+ ByteBuffer valueBB = ByteBuffer.wrap(value);
+ long cqOffset = valueBB.getLong(OFFSET_CQ_OFFSET);
+
+ Set<Integer> topicQueueIdSet = topicQueueIdToBeDeletedMap.get(topic);
+ if (topicQueueIdSet == null) {
+ Set<Integer> newSet = new HashSet<>();
+ newSet.add(queueId);
+ topicQueueIdToBeDeletedMap.put(topic, newSet);
+ } else {
+ topicQueueIdSet.add(queueId);
+ }
+ ERROR_LOG.info("RocksDBConsumeQueueOffsetTable has dirty cqOffset. topic: {}, queueId: {}, cqOffset: {}",
+ topic, queueId, cqOffset);
+ }
+ }
+ } catch (Exception e) {
+ ERROR_LOG.error("iterateOffsetTable2MarkDirtyCQ Failed.", e);
+ } finally {
+ if (iterator != null) {
+ iterator.close();
+ }
+ }
+ return topicQueueIdToBeDeletedMap;
+ }
+
+ public Long getMaxCqOffset(String topic, int queueId) throws RocksDBException {
+ Long maxCqOffset = getHeapMaxCqOffset(topic, queueId);
+
+ if (maxCqOffset == null) {
+ final ByteBuffer byteBuffer = getMaxPhyAndCqOffsetInKV(topic, queueId);
+ maxCqOffset = (byteBuffer != null) ? byteBuffer.getLong(OFFSET_CQ_OFFSET) : null;
+ String topicQueueId = buildTopicQueueId(topic, queueId);
+ this.topicQueueMaxCqOffset.putIfAbsent(topicQueueId, maxCqOffset != null ? maxCqOffset : -1L);
+ if (messageStore.getMessageStoreConfig().isEnableRocksDBLog()) {
+ ROCKSDB_LOG.warn("updateMaxOffsetInQueue. {}, {}", topicQueueId, maxCqOffset);
+ }
+ }
+
+ return maxCqOffset;
+ }
+
+ /**
+ * truncate dirty offset in rocksdb
+ * @param offsetToTruncate
+ * @throws RocksDBException
+ */
+ public void truncateDirty(long offsetToTruncate) throws RocksDBException {
+ correctMaxPyhOffset(offsetToTruncate);
+
+ ConcurrentMap<String, TopicConfig> allTopicConfigMap = this.messageStore.getTopicConfigs();
+ if (allTopicConfigMap == null) {
+ return;
+ }
+ for (TopicConfig topicConfig : allTopicConfigMap.values()) {
+ for (int i = 0; i < topicConfig.getWriteQueueNums(); i++) {
+ truncateDirtyOffset(topicConfig.getTopicName(), i);
+ }
+ }
+ }
+
+ private Pair<Boolean, Long> isMinOffsetOk(final String topic, final int queueId, final long minPhyOffset) throws RocksDBException {
+ PhyAndCQOffset phyAndCQOffset = getHeapMinOffset(topic, queueId);
+ if (phyAndCQOffset != null) {
+ final long phyOffset = phyAndCQOffset.getPhyOffset();
+ final long cqOffset = phyAndCQOffset.getCqOffset();
+
+ return (phyOffset >= minPhyOffset) ? new Pair(true, cqOffset) : new Pair(false, cqOffset);
+ }
+ ByteBuffer byteBuffer = getMinPhyAndCqOffsetInKV(topic, queueId);
+ if (byteBuffer == null) {
+ return new Pair(false, 0L);
+ }
+ final long phyOffset = byteBuffer.getLong(OFFSET_PHY_OFFSET);
+ final long cqOffset = byteBuffer.getLong(OFFSET_CQ_OFFSET);
+ if (phyOffset >= minPhyOffset) {
+ String topicQueueId = buildTopicQueueId(topic, queueId);
+ PhyAndCQOffset newPhyAndCQOffset = new PhyAndCQOffset(phyOffset, cqOffset);
+ this.topicQueueMinOffset.putIfAbsent(topicQueueId, newPhyAndCQOffset);
+ if (messageStore.getMessageStoreConfig().isEnableRocksDBLog()) {
+ ROCKSDB_LOG.warn("updateMinOffsetInQueue. {}, {}", topicQueueId, newPhyAndCQOffset);
+ }
+ return new Pair(true, cqOffset);
+ }
+ return new Pair(false, cqOffset);
+ }
+
+ private void truncateDirtyOffset(String topic, int queueId) throws RocksDBException {
+ final ByteBuffer byteBuffer = getMaxPhyAndCqOffsetInKV(topic, queueId);
+ if (byteBuffer == null) {
+ return;
+ }
+
+ long maxPhyOffset = byteBuffer.getLong(OFFSET_PHY_OFFSET);
+ long maxCqOffset = byteBuffer.getLong(OFFSET_CQ_OFFSET);
+ long maxPhyOffsetInCQ = getMaxPhyOffset();
+
+ if (maxPhyOffset >= maxPhyOffsetInCQ) {
+ correctMaxCqOffset(topic, queueId, maxCqOffset, maxPhyOffsetInCQ);
+ Long newMaxCqOffset = getHeapMaxCqOffset(topic, queueId);
+ ROCKSDB_LOG.warn("truncateDirtyLogicFile topic: {}, queueId: {} from {} to {}", topic, queueId,
+ maxPhyOffset, newMaxCqOffset);
+ }
+ }
+
+ private void correctMaxPyhOffset(long maxPhyOffset) throws RocksDBException {
+ if (!this.rocksDBStorage.hold()) {
+ return;
+ }
+ try {
+ WriteBatch writeBatch = new WriteBatch();
+ long oldMaxPhyOffset = getMaxPhyOffset();
+ if (oldMaxPhyOffset <= maxPhyOffset) {
+ return;
+ }
+ log.info("correctMaxPyhOffset, oldMaxPhyOffset={}, newMaxPhyOffset={}", oldMaxPhyOffset, maxPhyOffset);
+ appendMaxPhyOffset(writeBatch, maxPhyOffset);
+ this.rocksDBStorage.batchPut(writeBatch);
+ } catch (RocksDBException e) {
+ ERROR_LOG.error("correctMaxPyhOffset Failed.", e);
+ throw e;
+ } finally {
+ this.rocksDBStorage.release();
+ }
+ }
+
+ public long getMinCqOffset(String topic, int queueId) throws RocksDBException {
+ final long minPhyOffset = this.messageStore.getMinPhyOffset();
+ Pair<Boolean, Long> pair = isMinOffsetOk(topic, queueId, minPhyOffset);
+ final long cqOffset = pair.getObject2();
+ if (!pair.getObject1() && correctMinCqOffset(topic, queueId, cqOffset, minPhyOffset)) {
+ PhyAndCQOffset phyAndCQOffset = getHeapMinOffset(topic, queueId);
+ if (phyAndCQOffset != null) {
+ if (this.messageStore.getMessageStoreConfig().isEnableRocksDBLog()) {
+ ROCKSDB_LOG.warn("getMinOffsetInQueue miss heap. topic: {}, queueId: {}, old: {}, new: {}",
+ topic, queueId, cqOffset, phyAndCQOffset);
+ }
+ return phyAndCQOffset.getCqOffset();
+ }
+ }
+ return cqOffset;
+ }
+
+ public Long getMaxPhyOffset(String topic, int queueId) {
+ try {
+ ByteBuffer byteBuffer = getMaxPhyAndCqOffsetInKV(topic, queueId);
+ if (byteBuffer != null) {
+ return byteBuffer.getLong(OFFSET_PHY_OFFSET);
+ }
+ } catch (Exception e) {
+ ERROR_LOG.info("getMaxPhyOffset error. topic: {}, queueId: {}", topic, queueId);
+ }
+ return null;
+ }
+
+ private ByteBuffer getMinPhyAndCqOffsetInKV(String topic, int queueId) throws RocksDBException {
+ return getPhyAndCqOffsetInKV(topic, queueId, false);
+ }
+
+ private ByteBuffer getMaxPhyAndCqOffsetInKV(String topic, int queueId) throws RocksDBException {
+ return getPhyAndCqOffsetInKV(topic, queueId, true);
+ }
+
+ private ByteBuffer getPhyAndCqOffsetInKV(String topic, int queueId, boolean max) throws RocksDBException {
+ final byte[] topicBytes = topic.getBytes(CHARSET_UTF8);
+ final ByteBuffer keyBB = buildOffsetKeyByteBuffer(topicBytes, queueId, max);
+
+ byte[] value = this.rocksDBStorage.getOffset(keyBB.array());
+ return (value != null) ? ByteBuffer.wrap(value) : null;
+ }
+
+ private String buildTopicQueueId(final String topic, final int queueId) {
+ return topic + "-" + queueId;
+ }
+
+ private void putHeapMinCqOffset(final String topic, final int queueId, final long minPhyOffset, final long minCQOffset) {
+ String topicQueueId = buildTopicQueueId(topic, queueId);
+ PhyAndCQOffset phyAndCQOffset = new PhyAndCQOffset(minPhyOffset, minCQOffset);
+ this.topicQueueMinOffset.put(topicQueueId, phyAndCQOffset);
+ }
+
+ private void putHeapMaxCqOffset(final String topic, final int queueId, final long maxCQOffset) {
+ String topicQueueId = buildTopicQueueId(topic, queueId);
+ Long oldMaxCqOffset = this.topicQueueMaxCqOffset.put(topicQueueId, maxCQOffset);
+ if (oldMaxCqOffset != null && oldMaxCqOffset > maxCQOffset) {
+ ERROR_LOG.error("cqOffset invalid0. old: {}, now: {}", oldMaxCqOffset, maxCQOffset);
+ }
+ }
+
+ private PhyAndCQOffset getHeapMinOffset(final String topic, final int queueId) {
+ return this.topicQueueMinOffset.get(buildTopicQueueId(topic, queueId));
+ }
+
+ private Long getHeapMaxCqOffset(final String topic, final int queueId) {
+ String topicQueueId = buildTopicQueueId(topic, queueId);
+ return this.topicQueueMaxCqOffset.get(topicQueueId);
+ }
+
+ private PhyAndCQOffset removeHeapMinCqOffset(String topicQueueId) {
+ return this.topicQueueMinOffset.remove(topicQueueId);
+ }
+
+ private Long removeHeapMaxCqOffset(String topicQueueId) {
+ return this.topicQueueMaxCqOffset.remove(topicQueueId);
+ }
+
+ private void updateCqOffset(final String topic, final int queueId, final long phyOffset,
+ final long cqOffset, boolean max) throws RocksDBException {
+ if (!this.rocksDBStorage.hold()) {
+ return;
+ }
+ WriteBatch writeBatch = new WriteBatch();
+ try {
+ final byte[] topicBytes = topic.getBytes(CHARSET_UTF8);
+ final ByteBuffer offsetKey = buildOffsetKeyByteBuffer(topicBytes, queueId, max);
+
+ final ByteBuffer offsetValue = buildOffsetValueByteBuffer(phyOffset, cqOffset);
+ writeBatch.put(this.offsetCFH, offsetKey.array(), offsetValue.array());
+ this.rocksDBStorage.batchPut(writeBatch);
+
+ if (max) {
+ putHeapMaxCqOffset(topic, queueId, cqOffset);
+ } else {
+ putHeapMinCqOffset(topic, queueId, phyOffset, cqOffset);
+ }
+ } catch (RocksDBException e) {
+ ERROR_LOG.error("updateCqOffset({}) failed.", max ? "max" : "min", e);
+ throw e;
+ } finally {
+ writeBatch.close();
+ this.rocksDBStorage.release();
+ if (messageStore.getMessageStoreConfig().isEnableRocksDBLog()) {
+ ROCKSDB_LOG.warn("updateCqOffset({}). topic: {}, queueId: {}, phyOffset: {}, cqOffset: {}",
+ max ? "max" : "min", topic, queueId, phyOffset, cqOffset);
+ }
+ }
+ }
+
+ private boolean correctMaxCqOffset(final String topic, final int queueId, final long maxCQOffset,
+ final long maxPhyOffsetInCQ) throws RocksDBException {
+ // 'getMinOffsetInQueue' may correct minCqOffset and put it into heap
+ long minCQOffset = getMinCqOffset(topic, queueId);
+ PhyAndCQOffset minPhyAndCQOffset = getHeapMinOffset(topic, queueId);
+ if (minPhyAndCQOffset == null
+ || minPhyAndCQOffset.getCqOffset() != minCQOffset
+ || minPhyAndCQOffset.getPhyOffset() > maxPhyOffsetInCQ) {
+ ROCKSDB_LOG.info("[BUG] correctMaxCqOffset error! topic: {}, queueId: {}, maxPhyOffsetInCQ: {}, "
+ + "minCqOffset: {}, phyAndCQOffset: {}",
+ topic, queueId, maxPhyOffsetInCQ, minCQOffset, minPhyAndCQOffset);
+ throw new RocksDBException("correctMaxCqOffset error");
+ }
+
+ long high = maxCQOffset;
+ long low = minCQOffset;
+ PhyAndCQOffset targetPhyAndCQOffset = this.rocksDBConsumeQueueTable.binarySearchInCQ(topic, queueId, high,
+ low, maxPhyOffsetInCQ, false);
+
+ long targetCQOffset = targetPhyAndCQOffset.getCqOffset();
+ long targetPhyOffset = targetPhyAndCQOffset.getPhyOffset();
+
+ if (targetCQOffset == -1) {
+ if (maxCQOffset != minCQOffset) {
+ updateCqOffset(topic, queueId, minPhyAndCQOffset.getPhyOffset(), minCQOffset, true);
+ }
+ if (messageStore.getMessageStoreConfig().isEnableRocksDBLog()) {
+ ROCKSDB_LOG.warn("correct error. {}, {}, {}, {}, {}", topic, queueId, minCQOffset, maxCQOffset, minPhyAndCQOffset.getPhyOffset());
+ }
+ return false;
+ } else {
+ updateCqOffset(topic, queueId, targetPhyOffset, targetCQOffset, true);
+ return true;
+ }
+ }
+
+ private boolean correctMinCqOffset(final String topic, final int queueId,
+ final long minCQOffset, final long minPhyOffset) throws RocksDBException {
+ final ByteBuffer maxBB = getMaxPhyAndCqOffsetInKV(topic, queueId);
+ if (maxBB == null) {
+ updateCqOffset(topic, queueId, minPhyOffset, 0L, false);
+ return true;
+ }
+ final long maxPhyOffset = maxBB.getLong(OFFSET_PHY_OFFSET);
+ final long maxCQOffset = maxBB.getLong(OFFSET_CQ_OFFSET);
+
+ if (maxPhyOffset < minPhyOffset) {
+ updateCqOffset(topic, queueId, minPhyOffset, maxCQOffset + 1, false);
+ return true;
+ }
+
+ long high = maxCQOffset;
+ long low = minCQOffset;
+ PhyAndCQOffset phyAndCQOffset = this.rocksDBConsumeQueueTable.binarySearchInCQ(topic, queueId, high, low,
+ minPhyOffset, true);
+ long targetCQOffset = phyAndCQOffset.getCqOffset();
+ long targetPhyOffset = phyAndCQOffset.getPhyOffset();
+
+ if (targetCQOffset == -1) {
+ if (maxCQOffset != minCQOffset) {
+ updateCqOffset(topic, queueId, maxPhyOffset, maxCQOffset, false);
+ }
+ if (messageStore.getMessageStoreConfig().isEnableRocksDBLog()) {
+ ROCKSDB_LOG.warn("correct error. {}, {}, {}, {}, {}", topic, queueId, minCQOffset, maxCQOffset, minPhyOffset);
+ }
+ return false;
+ } else {
+ updateCqOffset(topic, queueId, targetPhyOffset, targetCQOffset, false);
+ return true;
+ }
+ }
+
+ public static Pair<ByteBuffer, ByteBuffer> getOffsetByteBufferPair() {
+ ByteBuffer offsetKey = ByteBuffer.allocateDirect(RocksDBConsumeQueueStore.MAX_KEY_LEN);
+ ByteBuffer offsetValue = ByteBuffer.allocateDirect(OFFSET_VALUE_LENGTH);
+ return new Pair<>(offsetKey, offsetValue);
+ }
+
+ private void buildOffsetKeyAndValueByteBuffer(final Pair<ByteBuffer, ByteBuffer> offsetBBPair,
+ final byte[] topicBytes, final DispatchRequest request) {
+ final ByteBuffer offsetKey = offsetBBPair.getObject1();
+ buildOffsetKeyByteBuffer(offsetKey, topicBytes, request.getQueueId(), true);
+
+ final ByteBuffer offsetValue = offsetBBPair.getObject2();
+ buildOffsetValueByteBuffer(offsetValue, request.getCommitLogOffset(), request.getConsumeQueueOffset());
+ }
+
+ private ByteBuffer buildOffsetKeyByteBuffer(final byte[] topicBytes, final int queueId, final boolean max) {
+ ByteBuffer byteBuffer = ByteBuffer.allocate(OFFSET_KEY_LENGTH_WITHOUT_TOPIC_BYTES + topicBytes.length);
+ buildOffsetKeyByteBuffer0(byteBuffer, topicBytes, queueId, max);
+ return byteBuffer;
+ }
+
+ private void buildOffsetKeyByteBuffer(final ByteBuffer byteBuffer, final byte[] topicBytes, final int queueId, final boolean max) {
+ byteBuffer.position(0).limit(OFFSET_KEY_LENGTH_WITHOUT_TOPIC_BYTES + topicBytes.length);
+ buildOffsetKeyByteBuffer0(byteBuffer, topicBytes, queueId, max);
+ }
+
+ private static void buildOffsetKeyByteBuffer0(final ByteBuffer byteBuffer, final byte[] topicBytes, final int queueId,
+ final boolean max) {
+ byteBuffer.putInt(topicBytes.length).put(CTRL_1).put(topicBytes).put(CTRL_1);
+ if (max) {
+ byteBuffer.put(MAX_BYTES);
+ } else {
+ byteBuffer.put(MIN_BYTES);
+ }
+ byteBuffer.put(CTRL_1).putInt(queueId);
+ byteBuffer.flip();
+ }
+
+ private void buildOffsetValueByteBuffer(final ByteBuffer byteBuffer, final long phyOffset, final long cqOffset) {
+ byteBuffer.position(0).limit(OFFSET_VALUE_LENGTH);
+ buildOffsetValueByteBuffer0(byteBuffer, phyOffset, cqOffset);
+ }
+
+ private ByteBuffer buildOffsetValueByteBuffer(final long phyOffset, final long cqOffset) {
+ final ByteBuffer byteBuffer = ByteBuffer.allocate(OFFSET_VALUE_LENGTH);
+ buildOffsetValueByteBuffer0(byteBuffer, phyOffset, cqOffset);
+ return byteBuffer;
+ }
+
+ private void buildOffsetValueByteBuffer0(final ByteBuffer byteBuffer, final long phyOffset, final long cqOffset) {
+ byteBuffer.putLong(phyOffset).putLong(cqOffset);
+ byteBuffer.flip();
+ }
+
+ static class PhyAndCQOffset {
+ private final long phyOffset;
+ private final long cqOffset;
+
+ public PhyAndCQOffset(final long phyOffset, final long cqOffset) {
+ this.phyOffset = phyOffset;
+ this.cqOffset = cqOffset;
+ }
+
+ public long getPhyOffset() {
+ return this.phyOffset;
+ }
+
+ public long getCqOffset() {
+ return this.cqOffset;
+ }
+
+ @Override
+ public String toString() {
+ return "[cqOffset=" + cqOffset + ", phyOffset=" + phyOffset + "]";
+ }
+ }
+}
diff --git a/store/src/main/java/org/apache/rocketmq/store/queue/RocksDBConsumeQueueStore.java b/store/src/main/java/org/apache/rocketmq/store/queue/RocksDBConsumeQueueStore.java
new file mode 100644
index 000000000..78456cfcd
--- /dev/null
+++ b/store/src/main/java/org/apache/rocketmq/store/queue/RocksDBConsumeQueueStore.java
@@ -0,0 +1,441 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.store.queue;
+
+import java.io.File;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.rocketmq.common.BoundaryType;
+import org.apache.rocketmq.common.Pair;
+import org.apache.rocketmq.common.ThreadFactoryImpl;
+import org.apache.rocketmq.common.constant.LoggerName;
+import org.apache.rocketmq.common.message.MessageExtBrokerInner;
+import org.apache.rocketmq.common.utils.DataConverter;
+import org.apache.rocketmq.logging.org.slf4j.Logger;
+import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+import org.apache.rocketmq.store.DefaultMessageStore;
+import org.apache.rocketmq.store.DispatchRequest;
+import org.apache.rocketmq.store.config.BrokerRole;
+import org.apache.rocketmq.store.config.StorePathConfigHelper;
+import org.apache.rocketmq.store.rocksdb.ConsumeQueueRocksDBStorage;
+import org.rocksdb.RocksDBException;
+import org.rocksdb.WriteBatch;
+
+public class RocksDBConsumeQueueStore extends AbstractConsumeQueueStore {
+ private static final Logger ERROR_LOG = LoggerFactory.getLogger(LoggerName.STORE_ERROR_LOGGER_NAME);
+ private static final Logger ROCKSDB_LOG = LoggerFactory.getLogger(LoggerName.ROCKSDB_LOGGER_NAME);
+
+ public static final byte CTRL_0 = '\u0000';
+ public static final byte CTRL_1 = '\u0001';
+ public static final byte CTRL_2 = '\u0002';
+
+ private static final int BATCH_SIZE = 16;
+ public static final int MAX_KEY_LEN = 300;
+
+ private final ScheduledExecutorService scheduledExecutorService;
+ private final String storePath;
+
+ /**
+ * we use two tables with different ColumnFamilyHandle, called RocksDBConsumeQueueTable and RocksDBConsumeQueueOffsetTable.
+ * 1.RocksDBConsumeQueueTable uses to store CqUnit[physicalOffset, msgSize, tagHashCode, msgStoreTime]
+ * 2.RocksDBConsumeQueueOffsetTable uses to store physicalOffset and consumeQueueOffset(@see PhyAndCQOffset) of topic-queueId
+ */
+ private final ConsumeQueueRocksDBStorage rocksDBStorage;
+ private final RocksDBConsumeQueueTable rocksDBConsumeQueueTable;
+ private final RocksDBConsumeQueueOffsetTable rocksDBConsumeQueueOffsetTable;
+
+ private final WriteBatch writeBatch;
+ private final List<DispatchRequest> bufferDRList;
+ private final List<Pair<ByteBuffer, ByteBuffer>> cqBBPairList;
+ private final List<Pair<ByteBuffer, ByteBuffer>> offsetBBPairList;
+ private final Map<ByteBuffer, Pair<ByteBuffer, DispatchRequest>> tempTopicQueueMaxOffsetMap;
+ private volatile boolean isCQError = false;
+
+ public RocksDBConsumeQueueStore(DefaultMessageStore messageStore) {
+ super(messageStore);
+
+ this.storePath = StorePathConfigHelper.getStorePathConsumeQueue(messageStoreConfig.getStorePathRootDir());
+ this.rocksDBStorage = new ConsumeQueueRocksDBStorage(messageStore, storePath, 4);
+ this.rocksDBConsumeQueueTable = new RocksDBConsumeQueueTable(rocksDBStorage, messageStore);
+ this.rocksDBConsumeQueueOffsetTable = new RocksDBConsumeQueueOffsetTable(rocksDBConsumeQueueTable, rocksDBStorage, messageStore);
+
+ this.writeBatch = new WriteBatch();
+ this.bufferDRList = new ArrayList(BATCH_SIZE);
+ this.cqBBPairList = new ArrayList(BATCH_SIZE);
+ this.offsetBBPairList = new ArrayList(BATCH_SIZE);
+ for (int i = 0; i < BATCH_SIZE; i++) {
+ this.cqBBPairList.add(RocksDBConsumeQueueTable.getCQByteBufferPair());
+ this.offsetBBPairList.add(RocksDBConsumeQueueOffsetTable.getOffsetByteBufferPair());
+ }
+
+ this.tempTopicQueueMaxOffsetMap = new HashMap<>();
+ this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(
+ new ThreadFactoryImpl("RocksDBConsumeQueueStoreScheduledThread", messageStore.getBrokerIdentity()));
+ }
+
+ @Override
+ public void start() {
+ log.info("RocksDB ConsumeQueueStore start!");
+ this.scheduledExecutorService.scheduleAtFixedRate(() -> {
+ this.rocksDBStorage.statRocksdb(ROCKSDB_LOG);
+ }, 10, this.messageStoreConfig.getStatRocksDBCQIntervalSec(), TimeUnit.SECONDS);
+
+ this.scheduledExecutorService.scheduleWithFixedDelay(() -> {
+ cleanDirty(messageStore.getTopicConfigs().keySet());
+ }, 10, this.messageStoreConfig.getCleanRocksDBDirtyCQIntervalMin(), TimeUnit.MINUTES);
+ }
+
+ private void cleanDirty(final Set<String> existTopicSet) {
+ try {
+ Map<String, Set<Integer>> topicQueueIdToBeDeletedMap =
+ this.rocksDBConsumeQueueOffsetTable.iterateOffsetTable2FindDirty(existTopicSet);
+
+ for (Map.Entry<String, Set<Integer>> entry : topicQueueIdToBeDeletedMap.entrySet()) {
+ String topic = entry.getKey();
+ for (int queueId : entry.getValue()) {
+ destroy(new RocksDBConsumeQueue(topic, queueId));
+ }
+ }
+ } catch (Exception e) {
+ log.error("cleanUnusedTopic Failed.", e);
+ }
+ }
+
+ @Override
+ public boolean load() {
+ boolean result = this.rocksDBStorage.start();
+ this.rocksDBConsumeQueueTable.load();
+ this.rocksDBConsumeQueueOffsetTable.load();
+ log.info("load rocksdb consume queue {}.", result ? "OK" : "Failed");
+ return result;
+ }
+
+ @Override
+ public boolean loadAfterDestroy() {
+ return this.load();
+ }
+
+ @Override
+ public void recover() {
+ // ignored
+ }
+
+ @Override
+ public boolean recoverConcurrently() {
+ return true;
+ }
+
+ @Override
+ public boolean shutdown() {
+ this.scheduledExecutorService.shutdown();
+ return shutdownInner();
+ }
+
+ private boolean shutdownInner() {
+ return this.rocksDBStorage.shutdown();
+ }
+
+ @Override
+ public void putMessagePositionInfoWrapper(DispatchRequest request) throws RocksDBException {
+ if (request == null || this.bufferDRList.size() >= BATCH_SIZE) {
+ putMessagePosition();
+ }
+ if (request != null) {
+ this.bufferDRList.add(request);
+ }
+ }
+
+ public void putMessagePosition() throws RocksDBException {
+ final int maxRetries = 30;
+ for (int i = 0; i < maxRetries; i++) {
+ if (putMessagePosition0()) {
+ if (this.isCQError) {
+ this.messageStore.getRunningFlags().clearLogicsQueueError();
+ this.isCQError = false;
+ }
+ return;
+ } else {
+ ERROR_LOG.warn("{} put cq Failed. retryTime: {}", i);
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ignored) {
+ }
+ }
+ }
+ if (!this.isCQError) {
+ ERROR_LOG.error("[BUG] put CQ Failed.");
+ this.messageStore.getRunningFlags().makeLogicsQueueError();
+ this.isCQError = true;
+ }
+ throw new RocksDBException("put CQ Failed");
+ }
+
+ private boolean putMessagePosition0() {
+ if (!this.rocksDBStorage.hold()) {
+ return false;
+ }
+
+ final Map<ByteBuffer, Pair<ByteBuffer, DispatchRequest>> tempTopicQueueMaxOffsetMap = this.tempTopicQueueMaxOffsetMap;
+ try {
+ final List<DispatchRequest> bufferDRList = this.bufferDRList;
+ final int size = bufferDRList.size();
+ if (size == 0) {
+ return true;
+ }
+ final List<Pair<ByteBuffer, ByteBuffer>> cqBBPairList = this.cqBBPairList;
+ final List<Pair<ByteBuffer, ByteBuffer>> offsetBBPairList = this.offsetBBPairList;
+ final WriteBatch writeBatch = this.writeBatch;
+
+ long maxPhyOffset = 0;
+ for (int i = size - 1; i >= 0; i--) {
+ final DispatchRequest request = bufferDRList.get(i);
+ final byte[] topicBytes = request.getTopic().getBytes(DataConverter.CHARSET_UTF8);
+
+ this.rocksDBConsumeQueueTable.buildAndPutCQByteBuffer(cqBBPairList.get(i), topicBytes, request, writeBatch);
+ this.rocksDBConsumeQueueOffsetTable.updateTempTopicQueueMaxOffset(offsetBBPairList.get(i),
+ topicBytes, request, tempTopicQueueMaxOffsetMap);
+
+ final int msgSize = request.getMsgSize();
+ final long phyOffset = request.getCommitLogOffset();
+ if (phyOffset + msgSize >= maxPhyOffset) {
+ maxPhyOffset = phyOffset + msgSize;
+ }
+ }
+
+ this.rocksDBConsumeQueueOffsetTable.putMaxPhyAndCqOffset(tempTopicQueueMaxOffsetMap, writeBatch, maxPhyOffset);
+
+ // clear writeBatch in batchPut
+ this.rocksDBStorage.batchPut(writeBatch);
+
+ this.rocksDBConsumeQueueOffsetTable.putHeapMaxCqOffset(tempTopicQueueMaxOffsetMap);
+
+ long storeTimeStamp = bufferDRList.get(size - 1).getStoreTimestamp();
+ if (this.messageStore.getMessageStoreConfig().getBrokerRole() == BrokerRole.SLAVE
+ || this.messageStore.getMessageStoreConfig().isEnableDLegerCommitLog()) {
+ this.messageStore.getStoreCheckpoint().setPhysicMsgTimestamp(storeTimeStamp);
+ }
+ this.messageStore.getStoreCheckpoint().setLogicsMsgTimestamp(storeTimeStamp);
+
+ notifyMessageArriveAndClear();
+ return true;
+ } catch (Exception e) {
+ ERROR_LOG.error("putMessagePosition0 Failed.", e);
+ return false;
+ } finally {
+ tempTopicQueueMaxOffsetMap.clear();
+ this.rocksDBStorage.release();
+ }
+ }
+
+ private void notifyMessageArriveAndClear() {
+ final List<DispatchRequest> bufferDRList = this.bufferDRList;
+ try {
+ for (DispatchRequest dp : bufferDRList) {
+ this.messageStore.notifyMessageArriveIfNecessary(dp);
+ }
+ } catch (Exception e) {
+ ERROR_LOG.error("notifyMessageArriveAndClear Failed.", e);
+ } finally {
+ bufferDRList.clear();
+ }
+ }
+
+ @Override
+ public List<ByteBuffer> rangeQuery(final String topic, final int queueId, final long startIndex, final int num) throws RocksDBException {
+ return this.rocksDBConsumeQueueTable.rangeQuery(topic, queueId, startIndex, num);
+ }
+
+ @Override
+ public ByteBuffer get(final String topic, final int queueId, final long cqOffset) throws RocksDBException {
+ return this.rocksDBConsumeQueueTable.getCQInKV(topic, queueId, cqOffset);
+ }
+
+ /**
+ * Ignored, we do not need to recover topicQueueTable and correct minLogicOffset. Because we will correct them
+ * when we use them, we call it lazy correction.
+ * @see RocksDBConsumeQueue#increaseQueueOffset(QueueOffsetOperator, MessageExtBrokerInner, short)
+ * @see org.apache.rocketmq.store.queue.RocksDBConsumeQueueOffsetTable#getMinCqOffset(String, int)
+ */
+ @Override
+ public void recoverOffsetTable(long minPhyOffset) {
+
+ }
+
+ @Override
+ public void destroy() {
+ try {
+ shutdownInner();
+ FileUtils.deleteDirectory(new File(this.storePath));
+ } catch (Exception e) {
+ ERROR_LOG.error("destroy cq Failed. {}", this.storePath, e);
+ }
+ }
+
+ @Override
+ public void destroy(ConsumeQueueInterface consumeQueue) throws RocksDBException {
+ String topic = consumeQueue.getTopic();
+ int queueId = consumeQueue.getQueueId();
+ if (StringUtils.isEmpty(topic) || queueId < 0 || !this.rocksDBStorage.hold()) {
+ return;
+ }
+
+ WriteBatch writeBatch = new WriteBatch();
+ try {
+ this.rocksDBConsumeQueueTable.destroyCQ(topic, queueId, writeBatch);
+ this.rocksDBConsumeQueueOffsetTable.destroyOffset(topic, queueId, writeBatch);
+
+ this.rocksDBStorage.batchPut(writeBatch);
+ } catch (RocksDBException e) {
+ ERROR_LOG.error("kv deleteTopic {} Failed.", topic, e);
+ throw e;
+ } finally {
+ writeBatch.close();
+ this.rocksDBStorage.release();
+ }
+ }
+
+ @Override
+ public boolean flush(ConsumeQueueInterface consumeQueue, int flushLeastPages) {
+ try {
+ this.rocksDBStorage.flushWAL();
+ } catch (Exception e) {
+ }
+ return true;
+ }
+
+ @Override
+ public void checkSelf() {
+ // ignored
+ }
+
+ @Override
+ public int deleteExpiredFile(ConsumeQueueInterface consumeQueue, long minCommitLogPos) {
+ // ignored
+ return 0;
+ }
+
+ /**
+ * We do not need to truncate dirty CQ in RocksDBConsumeQueueTable, Because dirty CQ in RocksDBConsumeQueueTable
+ * will be rewritten by new KV when new messages are appended or will be cleaned up when topics are deleted.
+ * But dirty offset info in RocksDBConsumeQueueOffsetTable must be truncated, because we use offset info in
+ * RocksDBConsumeQueueOffsetTable to rebuild topicQueueTable(@see RocksDBConsumeQueue#increaseQueueOffset).
+ * @param offsetToTruncate
+ * @throws RocksDBException
+ */
+ @Override
+ public void truncateDirty(long offsetToTruncate) throws RocksDBException {
+ long maxPhyOffsetInRocksdb = getMaxPhyOffsetInConsumeQueue();
+ if (offsetToTruncate >= maxPhyOffsetInRocksdb) {
+ return;
+ }
+
+ this.rocksDBConsumeQueueOffsetTable.truncateDirty(offsetToTruncate);
+ }
+
+ @Override
+ public void cleanExpired(final long minPhyOffset) {
+ this.rocksDBStorage.manualCompaction(minPhyOffset);
+ }
+
+ @Override
+ public long getOffsetInQueueByTime(String topic, int queueId, long timestamp, BoundaryType boundaryType) throws RocksDBException {
+ final long minPhysicOffset = this.messageStore.getMinPhyOffset();
+ long low = this.rocksDBConsumeQueueOffsetTable.getMinCqOffset(topic, queueId);
+ Long high = this.rocksDBConsumeQueueOffsetTable.getMaxCqOffset(topic, queueId);
+ if (high == null || high == -1) {
+ return 0;
+ }
+ return this.rocksDBConsumeQueueTable.binarySearchInCQByTime(topic, queueId, high, low, timestamp, minPhysicOffset);
+ }
+
+ @Override
+ public long getMaxOffsetInQueue(String topic, int queueId) throws RocksDBException {
+ Long maxOffset = this.rocksDBConsumeQueueOffsetTable.getMaxCqOffset(topic, queueId);
+ return (maxOffset != null) ? maxOffset + 1 : 0;
+ }
+
+ @Override
+ public long getMinOffsetInQueue(String topic, int queueId) throws RocksDBException {
+ return this.rocksDBConsumeQueueOffsetTable.getMinCqOffset(topic, queueId);
+ }
+
+ @Override
+ public Long getMaxPhyOffsetInConsumeQueue(String topic, int queueId) {
+ return this.rocksDBConsumeQueueOffsetTable.getMaxPhyOffset(topic, queueId);
+ }
+
+ @Override
+ public long getMaxPhyOffsetInConsumeQueue() throws RocksDBException {
+ return this.rocksDBConsumeQueueOffsetTable.getMaxPhyOffset();
+ }
+
+ @Override
+ public ConsumeQueueInterface findOrCreateConsumeQueue(String topic, int queueId) {
+ ConcurrentMap<Integer, ConsumeQueueInterface> map = this.consumeQueueTable.get(topic);
+ if (null == map) {
+ ConcurrentMap<Integer, ConsumeQueueInterface> newMap = new ConcurrentHashMap<>(128);
+ ConcurrentMap<Integer, ConsumeQueueInterface> oldMap = this.consumeQueueTable.putIfAbsent(topic, newMap);
+ if (oldMap != null) {
+ map = oldMap;
+ } else {
+ map = newMap;
+ }
+ }
+
+ ConsumeQueueInterface logic = map.get(queueId);
+ if (logic != null) {
+ return logic;
+ }
+
+ ConsumeQueueInterface newLogic = new RocksDBConsumeQueue(this.messageStore, topic, queueId);
+ ConsumeQueueInterface oldLogic = map.putIfAbsent(queueId, newLogic);
+
+ return oldLogic != null ? oldLogic : newLogic;
+ }
+
+ @Override
+ public long rollNextFile(ConsumeQueueInterface consumeQueue, long offset) {
+ return 0;
+ }
+
+ @Override
+ public boolean isFirstFileExist(ConsumeQueueInterface consumeQueue) {
+ return true;
+ }
+
+ @Override
+ public boolean isFirstFileAvailable(ConsumeQueueInterface consumeQueue) {
+ return true;
+ }
+
+ @Override
+ public long getTotalSize() {
+ return 0;
+ }
+}
diff --git a/store/src/main/java/org/apache/rocketmq/store/queue/RocksDBConsumeQueueTable.java b/store/src/main/java/org/apache/rocketmq/store/queue/RocksDBConsumeQueueTable.java
new file mode 100644
index 000000000..0a735ea27
--- /dev/null
+++ b/store/src/main/java/org/apache/rocketmq/store/queue/RocksDBConsumeQueueTable.java
@@ -0,0 +1,312 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.store.queue;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.rocketmq.common.Pair;
+import org.apache.rocketmq.common.constant.LoggerName;
+import org.apache.rocketmq.logging.org.slf4j.Logger;
+import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+import org.apache.rocketmq.store.DefaultMessageStore;
+import org.apache.rocketmq.store.DispatchRequest;
+import org.apache.rocketmq.store.queue.RocksDBConsumeQueueOffsetTable.PhyAndCQOffset;
+import org.apache.rocketmq.store.rocksdb.ConsumeQueueRocksDBStorage;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.RocksDBException;
+import org.rocksdb.WriteBatch;
+
+import static org.apache.rocketmq.common.utils.DataConverter.CHARSET_UTF8;
+import static org.apache.rocketmq.store.queue.RocksDBConsumeQueueStore.CTRL_0;
+import static org.apache.rocketmq.store.queue.RocksDBConsumeQueueStore.CTRL_1;
+import static org.apache.rocketmq.store.queue.RocksDBConsumeQueueStore.CTRL_2;
+
+/**
+ * We use RocksDBConsumeQueueTable to store cqUnit.
+ */
+public class RocksDBConsumeQueueTable {
+ private static final Logger log = LoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME);
+ private static final Logger ROCKSDB_LOG = LoggerFactory.getLogger(LoggerName.ROCKSDB_LOGGER_NAME);
+ private static final Logger ERROR_LOG = LoggerFactory.getLogger(LoggerName.STORE_ERROR_LOGGER_NAME);
+
+ /**
+ * Rocksdb ConsumeQueue's store unit. Format:
+ *
+ * <pre>
+ * ┌─────────────────────────┬───────────┬───────────────────────┬───────────┬───────────┬───────────┬───────────────────────┐
+ * │ Topic Bytes Array Size │ CTRL_1 │ Topic Bytes Array │ CTRL_1 │ QueueId │ CTRL_1 │ ConsumeQueue Offset │
+ * │ (4 Bytes) │ (1 Bytes) │ (n Bytes) │ (1 Bytes) │ (4 Bytes) │ (1 Bytes) │ (8 Bytes) │
+ * ├─────────────────────────┴───────────┴───────────────────────┴───────────┴───────────┴───────────┴───────────────────────┤
+ * │ Key Unit │
+ * │ │
+ * </pre>
+ *
+ * <pre>
+ * ┌─────────────────────────────┬───────────────────┬──────────────────┬──────────────────┐
+ * │ CommitLog Physical Offset │ Body Size │ Tag HashCode │ Msg Store Time │
+ * │ (8 Bytes) │ (4 Bytes) │ (8 Bytes) │ (8 Bytes) │
+ * ├─────────────────────────────┴───────────────────┴──────────────────┴──────────────────┤
+ * │ Value Unit │
+ * │ │
+ * </pre>
+ * ConsumeQueue's store unit. Size:
+ * CommitLog Physical Offset(8) + Body Size(4) + Tag HashCode(8) + Msg Store Time(8) = 28 Bytes
+ */
+ private static final int PHY_OFFSET_OFFSET = 0;
+ private static final int PHY_MSG_LEN_OFFSET = 8;
+ private static final int MSG_TAG_HASHCODE_OFFSET = 12;
+ private static final int MSG_STORE_TIME_SIZE_OFFSET = 20;
+ public static final int CQ_UNIT_SIZE = 8 + 4 + 8 + 8;
+
+ /**
+ * ┌─────────────────────────┬───────────┬───────────┬───────────┬───────────┬───────────────────────┐
+ * │ Topic Bytes Array Size │ CTRL_1 │ CTRL_1 │ QueueId │ CTRL_1 │ ConsumeQueue Offset │
+ * │ (4 Bytes) │ (1 Bytes) │ (1 Bytes) │ (4 Bytes) │ (1 Bytes) │ (8 Bytes) │
+ * ├─────────────────────────┴───────────┴───────────┴───────────┴───────────┴───────────────────────┤
+ */
+ private static final int CQ_KEY_LENGTH_WITHOUT_TOPIC_BYTES = 4 + 1 + 1 + 4 + 1 + 8;
+
+ /**
+ * ┌─────────────────────────┬───────────┬───────────┬───────────┬───────────────────┐
+ * │ Topic Bytes Array Size │ CTRL_1 │ CTRL_1 │ QueueId │ CTRL_0(CTRL_2) │
+ * │ (4 Bytes) │ (1 Bytes) │ (1 Bytes) │ (4 Bytes) │ (1 Bytes) │
+ * ├─────────────────────────┴───────────┴───────────┴───────────┴───────────────────┤
+ */
+ private static final int DELETE_CQ_KEY_LENGTH_WITHOUT_TOPIC_BYTES = 4 + 1 + 1 + 4 + 1;
+
+ private final ConsumeQueueRocksDBStorage rocksDBStorage;
+ private final DefaultMessageStore messageStore;
+
+ private ColumnFamilyHandle defaultCFH;
+
+ public RocksDBConsumeQueueTable(ConsumeQueueRocksDBStorage rocksDBStorage, DefaultMessageStore messageStore) {
+ this.rocksDBStorage = rocksDBStorage;
+ this.messageStore = messageStore;
+ }
+
+ public void load() {
+ this.defaultCFH = this.rocksDBStorage.getDefaultCFHandle();
+ }
+
+ public void buildAndPutCQByteBuffer(final Pair<ByteBuffer, ByteBuffer> cqBBPair,
+ final byte[] topicBytes, final DispatchRequest request, final WriteBatch writeBatch) throws RocksDBException {
+ final ByteBuffer cqKey = cqBBPair.getObject1();
+ buildCQKeyByteBuffer(cqKey, topicBytes, request.getQueueId(), request.getConsumeQueueOffset());
+
+ final ByteBuffer cqValue = cqBBPair.getObject2();
+ buildCQValueByteBuffer(cqValue, request.getCommitLogOffset(), request.getMsgSize(), request.getTagsCode(), request.getStoreTimestamp());
+
+ writeBatch.put(this.defaultCFH, cqKey, cqValue);
+ }
+
+ public ByteBuffer getCQInKV(final String topic, final int queueId, final long cqOffset) throws RocksDBException {
+ final byte[] topicBytes = topic.getBytes(CHARSET_UTF8);
+ final ByteBuffer keyBB = buildCQKeyByteBuffer(topicBytes, queueId, cqOffset);
+ byte[] value = this.rocksDBStorage.getCQ(keyBB.array());
+ return (value != null) ? ByteBuffer.wrap(value) : null;
+ }
+
+ public List<ByteBuffer> rangeQuery(final String topic, final int queueId, final long startIndex, final int num) throws RocksDBException {
+ final byte[] topicBytes = topic.getBytes(CHARSET_UTF8);
+ final List<ColumnFamilyHandle> defaultCFHList = new ArrayList(num);
+ final ByteBuffer[] resultList = new ByteBuffer[num];
+ final List<Integer> kvIndexList = new ArrayList(num);
+ final List<byte[]> kvKeyList = new ArrayList(num);
+ for (int i = 0; i < num; i++) {
+ final ByteBuffer keyBB = buildCQKeyByteBuffer(topicBytes, queueId, startIndex + i);
+ kvIndexList.add(i);
+ kvKeyList.add(keyBB.array());
+ defaultCFHList.add(this.defaultCFH);
+ }
+ int keyNum = kvIndexList.size();
+ if (keyNum > 0) {
+ List<byte[]> kvValueList = this.rocksDBStorage.multiGet(defaultCFHList, kvKeyList);
+ final int valueNum = kvValueList.size();
+ if (keyNum != valueNum) {
+ throw new RocksDBException("rocksdb bug, multiGet");
+ }
+ for (int i = 0; i < valueNum; i++) {
+ byte[] value = kvValueList.get(i);
+ if (value == null) {
+ continue;
+ }
+ ByteBuffer byteBuffer = ByteBuffer.wrap(value);
+ resultList[kvIndexList.get(i)] = byteBuffer;
+ }
+ }
+
+ final int resultSize = resultList.length;
+ List<ByteBuffer> bbValueList = new ArrayList(resultSize);
+ for (int i = 0; i < resultSize; i++) {
+ ByteBuffer byteBuffer = resultList[i];
+ if (byteBuffer == null) {
+ break;
+ }
+ bbValueList.add(byteBuffer);
+ }
+ return bbValueList;
+ }
+
+ /**
+ * When topic is deleted, we clean up its CqUnit in rocksdb.
+ * @param topic
+ * @param queueId
+ * @throws RocksDBException
+ */
+ public void destroyCQ(final String topic, final int queueId, WriteBatch writeBatch) throws RocksDBException {
+ final byte[] topicBytes = topic.getBytes(CHARSET_UTF8);
+ final ByteBuffer cqStartKey = buildDeleteCQKey(true, topicBytes, queueId);
+ final ByteBuffer cqEndKey = buildDeleteCQKey(false, topicBytes, queueId);
+
+ writeBatch.deleteRange(this.defaultCFH, cqStartKey.array(), cqEndKey.array());
+
+ log.info("Rocksdb consumeQueue table delete topic. {}, {}", topic, queueId);
+ }
+
+ public long binarySearchInCQByTime(String topic, int queueId, long high, long low, long timestamp,
+ long minPhysicOffset) throws RocksDBException {
+ long result = 0;
+ long targetOffset = -1L, leftOffset = -1L, rightOffset = -1L;
+ long leftValue = -1L, rightValue = -1L;
+ while (high >= low) {
+ long midOffset = low + ((high - low) >>> 1);
+ ByteBuffer byteBuffer = getCQInKV(topic, queueId, midOffset);
+ if (byteBuffer == null) {
+ ERROR_LOG.warn("binarySearchInCQByTimeStamp Failed. topic: {}, queueId: {}, timestamp: {}, result: null",
+ topic, queueId, timestamp);
+ low = midOffset + 1;
+ continue;
+ }
+
+ long phyOffset = byteBuffer.getLong(PHY_OFFSET_OFFSET);
+ if (phyOffset < minPhysicOffset) {
+ low = midOffset + 1;
+ leftOffset = midOffset;
+ continue;
+ }
+ long storeTime = byteBuffer.getLong(MSG_STORE_TIME_SIZE_OFFSET);
+ if (storeTime < 0) {
+ return 0;
+ } else if (storeTime == timestamp) {
+ targetOffset = midOffset;
+ break;
+ } else if (storeTime > timestamp) {
+ high = midOffset - 1;
+ rightOffset = midOffset;
+ rightValue = storeTime;
+ } else {
+ low = midOffset + 1;
+ leftOffset = midOffset;
+ leftValue = storeTime;
+ }
+ }
+ if (targetOffset != -1) {
+ result = targetOffset;
+ } else {
+ if (leftValue == -1) {
+ result = rightOffset;
+ } else if (rightValue == -1) {
+ result = leftOffset;
+ } else {
+ result = Math.abs(timestamp - leftValue) > Math.abs(timestamp - rightValue) ? rightOffset : leftOffset;
+ }
+ }
+ return result;
+ }
+
+ public PhyAndCQOffset binarySearchInCQ(String topic, int queueId, long high, long low, long targetPhyOffset,
+ boolean min) throws RocksDBException {
+ long resultCQOffset = -1L;
+ long resultPhyOffset = -1L;
+ while (high >= low) {
+ long midCQOffset = low + ((high - low) >>> 1);
+ ByteBuffer byteBuffer = getCQInKV(topic, queueId, midCQOffset);
+ if (this.messageStore.getMessageStoreConfig().isEnableRocksDBLog()) {
+ ROCKSDB_LOG.warn("binarySearchInCQ. {}, {}, {}, {}, {}", topic, queueId, midCQOffset, low, high);
+ }
+ if (byteBuffer == null) {
+ low = midCQOffset + 1;
+ continue;
+ }
+
+ final long phyOffset = byteBuffer.getLong(PHY_OFFSET_OFFSET);
+ if (phyOffset == targetPhyOffset) {
+ if (min) {
+ resultCQOffset = midCQOffset;
+ resultPhyOffset = phyOffset;
+ }
+ break;
+ } else if (phyOffset > targetPhyOffset) {
+ high = midCQOffset - 1;
+ if (min) {
+ resultCQOffset = midCQOffset;
+ resultPhyOffset = phyOffset;
+ }
+ } else {
+ low = midCQOffset + 1;
+ if (!min) {
+ resultCQOffset = midCQOffset;
+ resultPhyOffset = phyOffset;
+ }
+ }
+ }
+ return new PhyAndCQOffset(resultPhyOffset, resultCQOffset);
+ }
+
+ public static Pair<ByteBuffer, ByteBuffer> getCQByteBufferPair() {
+ ByteBuffer cqKey = ByteBuffer.allocateDirect(RocksDBConsumeQueueStore.MAX_KEY_LEN);
+ ByteBuffer cqValue = ByteBuffer.allocateDirect(CQ_UNIT_SIZE);
+ return new Pair<>(cqKey, cqValue);
+ }
+
+ private ByteBuffer buildCQKeyByteBuffer(final byte[] topicBytes, final int queueId, final long cqOffset) {
+ final ByteBuffer byteBuffer = ByteBuffer.allocate(CQ_KEY_LENGTH_WITHOUT_TOPIC_BYTES + topicBytes.length);
+ buildCQKeyByteBuffer0(byteBuffer, topicBytes, queueId, cqOffset);
+ return byteBuffer;
+ }
+
+ private void buildCQKeyByteBuffer(final ByteBuffer byteBuffer, final byte[] topicBytes, final int queueId, final long cqOffset) {
+ byteBuffer.position(0).limit(CQ_KEY_LENGTH_WITHOUT_TOPIC_BYTES + topicBytes.length);
+ buildCQKeyByteBuffer0(byteBuffer, topicBytes, queueId, cqOffset);
+ }
+
+ private void buildCQKeyByteBuffer0(final ByteBuffer byteBuffer, final byte[] topicBytes, final int queueId, final long cqOffset) {
+ byteBuffer.putInt(topicBytes.length).put(CTRL_1).put(topicBytes).put(CTRL_1).putInt(queueId).put(CTRL_1).putLong(cqOffset);
+ byteBuffer.flip();
+ }
+
+ private void buildCQValueByteBuffer(final ByteBuffer byteBuffer, final long phyOffset, final int msgSize, final long tagsCode, final long storeTimestamp) {
+ byteBuffer.position(0).limit(CQ_UNIT_SIZE);
+ buildCQValueByteBuffer0(byteBuffer, phyOffset, msgSize, tagsCode, storeTimestamp);
+ }
+
+ private void buildCQValueByteBuffer0(final ByteBuffer byteBuffer, final long phyOffset, final int msgSize,
+ final long tagsCode, final long storeTimestamp) {
+ byteBuffer.putLong(phyOffset).putInt(msgSize).putLong(tagsCode).putLong(storeTimestamp);
+ byteBuffer.flip();
+ }
+
+ private ByteBuffer buildDeleteCQKey(final boolean start, final byte[] topicBytes, final int queueId) {
+ final ByteBuffer byteBuffer = ByteBuffer.allocate(DELETE_CQ_KEY_LENGTH_WITHOUT_TOPIC_BYTES + topicBytes.length);
+
+ byteBuffer.putInt(topicBytes.length).put(CTRL_1).put(topicBytes).put(CTRL_1).putInt(queueId).put(start ? CTRL_0 : CTRL_2);
+ byteBuffer.flip();
+ return byteBuffer;
+ }
+}
diff --git a/store/src/main/java/org/apache/rocketmq/store/rocksdb/ConsumeQueueCompactionFilterFactory.java b/store/src/main/java/org/apache/rocketmq/store/rocksdb/ConsumeQueueCompactionFilterFactory.java
new file mode 100644
index 000000000..aa796c4d3
--- /dev/null
+++ b/store/src/main/java/org/apache/rocketmq/store/rocksdb/ConsumeQueueCompactionFilterFactory.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.store.rocksdb;
+
+import org.apache.rocketmq.common.constant.LoggerName;
+import org.apache.rocketmq.logging.org.slf4j.Logger;
+import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
+import org.apache.rocketmq.store.MessageStore;
+import org.rocksdb.AbstractCompactionFilter;
+import org.rocksdb.AbstractCompactionFilterFactory;
+import org.rocksdb.RemoveConsumeQueueCompactionFilter;
+
+public class ConsumeQueueCompactionFilterFactory extends AbstractCompactionFilterFactory<RemoveConsumeQueueCompactionFilter> {
+ private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.ROCKSDB_LOGGER_NAME);
+ private final MessageStore messageStore;
+
+ public ConsumeQueueCompactionFilterFactory(final MessageStore messageStore) {
+ this.messageStore = messageStore;
+ }
+
+ @Override
+ public String name() {
+ return "ConsumeQueueCompactionFilterFactory";
+ }
+
+ @Override
+ public RemoveConsumeQueueCompactionFilter createCompactionFilter(final AbstractCompactionFilter.Context context) {
+ long minPhyOffset = this.messageStore.getMinPhyOffset();
+ LOGGER.info("manualCompaction minPhyOffset: {}, isFull: {}, isManual: {}",
+ minPhyOffset, context.isFullCompaction(), context.isManualCompaction());
+ return new RemoveConsumeQueueCompactionFilter(minPhyOffset);
+ }
+}
diff --git a/store/src/main/java/org/apache/rocketmq/store/rocksdb/ConsumeQueueRocksDBStorage.java b/store/src/main/java/org/apache/rocketmq/store/rocksdb/ConsumeQueueRocksDBStorage.java
new file mode 100644
index 000000000..362684560
--- /dev/null
+++ b/store/src/main/java/org/apache/rocketmq/store/rocksdb/ConsumeQueueRocksDBStorage.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.store.rocksdb;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.rocketmq.common.UtilAll;
+import org.apache.rocketmq.common.config.AbstractRocksDBStorage;
+import org.apache.rocketmq.common.utils.DataConverter;
+import org.apache.rocketmq.store.MessageStore;
+import org.rocksdb.ColumnFamilyDescriptor;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.ColumnFamilyOptions;
+import org.rocksdb.CompactRangeOptions;
+import org.rocksdb.ReadOptions;
+import org.rocksdb.RocksDB;
+import org.rocksdb.RocksDBException;
+import org.rocksdb.RocksIterator;
+import org.rocksdb.WriteBatch;
+import org.rocksdb.WriteOptions;
+
+public class ConsumeQueueRocksDBStorage extends AbstractRocksDBStorage {
+ private final MessageStore messageStore;
+ private volatile ColumnFamilyHandle offsetCFHandle;
+
+ public ConsumeQueueRocksDBStorage(final MessageStore messageStore, final String dbPath, final int prefixLen) {
+ this.messageStore = messageStore;
+ this.dbPath = dbPath;
+ this.readOnly = false;
+ }
+
+ private void initOptions() {
+ this.options = RocksDBOptionsFactory.createDBOptions();
+
+ this.writeOptions = new WriteOptions();
+ this.writeOptions.setSync(false);
+ this.writeOptions.setDisableWAL(true);
+ this.writeOptions.setNoSlowdown(true);
+
+ this.totalOrderReadOptions = new ReadOptions();
+ this.totalOrderReadOptions.setPrefixSameAsStart(false);
+ this.totalOrderReadOptions.setTotalOrderSeek(false);
+
+ this.compactRangeOptions = new CompactRangeOptions();
+ this.compactRangeOptions.setBottommostLevelCompaction(CompactRangeOptions.BottommostLevelCompaction.kForce);
+ this.compactRangeOptions.setAllowWriteStall(true);
+ this.compactRangeOptions.setExclusiveManualCompaction(false);
+ this.compactRangeOptions.setChangeLevel(true);
+ this.compactRangeOptions.setTargetLevel(-1);
+ this.compactRangeOptions.setMaxSubcompactions(4);
+ }
+
+ @Override
+ protected boolean postLoad() {
+ try {
+ UtilAll.ensureDirOK(this.dbPath);
+
+ initOptions();
+
+ final List<ColumnFamilyDescriptor> cfDescriptors = new ArrayList();
+
+ ColumnFamilyOptions cqCfOptions = RocksDBOptionsFactory.createCQCFOptions(this.messageStore);
+ this.cfOptions.add(cqCfOptions);
+ cfDescriptors.add(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cqCfOptions));
+
+ ColumnFamilyOptions offsetCfOptions = RocksDBOptionsFactory.createOffsetCFOptions();
+ this.cfOptions.add(offsetCfOptions);
+ cfDescriptors.add(new ColumnFamilyDescriptor("offset".getBytes(DataConverter.CHARSET_UTF8), offsetCfOptions));
+
+ final List<ColumnFamilyHandle> cfHandles = new ArrayList();
+ open(cfDescriptors, cfHandles);
+
+ this.defaultCFHandle = cfHandles.get(0);
+ this.offsetCFHandle = cfHandles.get(1);
+ } catch (final Exception e) {
+ LOGGER.error("postLoad Failed. {}", this.dbPath, e);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected void preShutdown() {
+ this.offsetCFHandle.close();
+ }
+
+ public byte[] getCQ(final byte[] keyBytes) throws RocksDBException {
+ return get(this.defaultCFHandle, this.totalOrderReadOptions, keyBytes);
+ }
+
+ public byte[] getOffset(final byte[] keyBytes) throws RocksDBException {
+ return get(this.offsetCFHandle, this.totalOrderReadOptions, keyBytes);
+ }
+
+ public List<byte[]> multiGet(final List<ColumnFamilyHandle> cfhList, final List<byte[]> keys) throws RocksDBException {
+ return multiGet(this.totalOrderReadOptions, cfhList, keys);
+ }
+
+ public void batchPut(final WriteBatch batch) throws RocksDBException {
+ batchPut(this.writeOptions, batch);
+ }
+
+ public void manualCompaction(final long minPhyOffset) {
+ try {
+ manualCompaction(minPhyOffset, this.compactRangeOptions);
+ } catch (Exception e) {
+ LOGGER.error("manualCompaction Failed. minPhyOffset: {}", minPhyOffset, e);
+ }
+ }
+
+ public RocksIterator seekOffsetCF() {
+ return this.db.newIterator(this.offsetCFHandle, this.totalOrderReadOptions);
+ }
+
+ public ColumnFamilyHandle getOffsetCFHandle() {
+ return this.offsetCFHandle;
+ }
+}
\ No newline at end of file
diff --git a/store/src/main/java/org/apache/rocketmq/store/rocksdb/RocksDBOptionsFactory.java b/store/src/main/java/org/apache/rocketmq/store/rocksdb/RocksDBOptionsFactory.java
new file mode 100644
index 000000000..a3a99d334
--- /dev/null
+++ b/store/src/main/java/org/apache/rocketmq/store/rocksdb/RocksDBOptionsFactory.java
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.store.rocksdb;
+
+import org.apache.rocketmq.common.config.ConfigRocksDBStorage;
+import org.apache.rocketmq.store.MessageStore;
+import org.rocksdb.BlockBasedTableConfig;
+import org.rocksdb.BloomFilter;
+import org.rocksdb.ColumnFamilyOptions;
+import org.rocksdb.CompactionOptionsUniversal;
+import org.rocksdb.CompactionStopStyle;
+import org.rocksdb.CompactionStyle;
+import org.rocksdb.CompressionType;
+import org.rocksdb.DBOptions;
+import org.rocksdb.DataBlockIndexType;
+import org.rocksdb.IndexType;
+import org.rocksdb.InfoLogLevel;
+import org.rocksdb.LRUCache;
+import org.rocksdb.RateLimiter;
+import org.rocksdb.SkipListMemTableConfig;
+import org.rocksdb.Statistics;
+import org.rocksdb.StatsLevel;
+import org.rocksdb.StringAppendOperator;
+import org.rocksdb.WALRecoveryMode;
+import org.rocksdb.util.SizeUnit;
+
+public class RocksDBOptionsFactory {
+
+ public static ColumnFamilyOptions createCQCFOptions(final MessageStore messageStore) {
+ BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig().
+ setFormatVersion(5).
+ setIndexType(IndexType.kBinarySearch).
+ setDataBlockIndexType(DataBlockIndexType.kDataBlockBinaryAndHash).
+ setDataBlockHashTableUtilRatio(0.75).
+ setBlockSize(32 * SizeUnit.KB).
+ setMetadataBlockSize(4 * SizeUnit.KB).
+ setFilterPolicy(new BloomFilter(16, false)).
+ setCacheIndexAndFilterBlocks(false).
+ setCacheIndexAndFilterBlocksWithHighPriority(true).
+ setPinL0FilterAndIndexBlocksInCache(false).
+ setPinTopLevelIndexAndFilter(true).
+ setBlockCache(new LRUCache(1024 * SizeUnit.MB, 8, false)).
+ setWholeKeyFiltering(true);
+
+ ColumnFamilyOptions columnFamilyOptions = new ColumnFamilyOptions();
+ CompactionOptionsUniversal compactionOption = new CompactionOptionsUniversal();
+ compactionOption.setSizeRatio(100).
+ setMaxSizeAmplificationPercent(25).
+ setAllowTrivialMove(true).
+ setMinMergeWidth(2).
+ setMaxMergeWidth(Integer.MAX_VALUE).
+ setStopStyle(CompactionStopStyle.CompactionStopStyleTotalSize).
+ setCompressionSizePercent(-1);
+ return columnFamilyOptions.setMaxWriteBufferNumber(4).
+ setWriteBufferSize(128 * SizeUnit.MB).
+ setMinWriteBufferNumberToMerge(1).
+ setTableFormatConfig(blockBasedTableConfig).
+ setMemTableConfig(new SkipListMemTableConfig()).
+ setCompressionType(CompressionType.LZ4_COMPRESSION).
+ setBottommostCompressionType(CompressionType.ZSTD_COMPRESSION).
+ setNumLevels(7).
+ setCompactionStyle(CompactionStyle.UNIVERSAL).
+ setCompactionOptionsUniversal(compactionOption).
+ setMaxCompactionBytes(100 * SizeUnit.GB).
+ setSoftPendingCompactionBytesLimit(100 * SizeUnit.GB).
+ setHardPendingCompactionBytesLimit(256 * SizeUnit.GB).
+ setLevel0FileNumCompactionTrigger(2).
+ setLevel0SlowdownWritesTrigger(8).
+ setLevel0StopWritesTrigger(10).
+ setTargetFileSizeBase(256 * SizeUnit.MB).
+ setTargetFileSizeMultiplier(2).
+ setMergeOperator(new StringAppendOperator()).
+ setCompactionFilterFactory(new ConsumeQueueCompactionFilterFactory(messageStore)).
+ setReportBgIoStats(true).
+ setOptimizeFiltersForHits(true);
+ }
+
+ public static ColumnFamilyOptions createOffsetCFOptions() {
+ BlockBasedTableConfig blockBasedTableConfig = new BlockBasedTableConfig().
+ setFormatVersion(5).
+ setIndexType(IndexType.kBinarySearch).
+ setDataBlockIndexType(DataBlockIndexType.kDataBlockBinarySearch).
+ setBlockSize(32 * SizeUnit.KB).
+ setFilterPolicy(new BloomFilter(16, false)).
+ setCacheIndexAndFilterBlocks(false).
+ setCacheIndexAndFilterBlocksWithHighPriority(true).
+ setPinL0FilterAndIndexBlocksInCache(false).
+ setPinTopLevelIndexAndFilter(true).
+ setBlockCache(new LRUCache(128 * SizeUnit.MB, 8, false)).
+ setWholeKeyFiltering(true);
+
+ ColumnFamilyOptions columnFamilyOptions = new ColumnFamilyOptions();
+ return columnFamilyOptions.setMaxWriteBufferNumber(4).
+ setWriteBufferSize(64 * SizeUnit.MB).
+ setMinWriteBufferNumberToMerge(1).
+ setTableFormatConfig(blockBasedTableConfig).
+ setMemTableConfig(new SkipListMemTableConfig()).
+ setCompressionType(CompressionType.NO_COMPRESSION).
+ setNumLevels(7).
+ setCompactionStyle(CompactionStyle.LEVEL).
+ setLevel0FileNumCompactionTrigger(2).
+ setLevel0SlowdownWritesTrigger(8).
+ setLevel0StopWritesTrigger(10).
+ setTargetFileSizeBase(64 * SizeUnit.MB).
+ setTargetFileSizeMultiplier(2).
+ setMaxBytesForLevelBase(256 * SizeUnit.MB).
+ setMaxBytesForLevelMultiplier(2).
+ setMergeOperator(new StringAppendOperator()).
+ setInplaceUpdateSupport(true);
+ }
+
+ /**
+ * Create a rocksdb db options, the user must take care to close it after closing db.
+ * @return
+ */
+ public static DBOptions createDBOptions() {
+ //Turn based on https://github.com/facebook/rocksdb/wiki/RocksDB-Tuning-Guide
+ // and http://gitlab.alibaba-inc.com/aloha/aloha/blob/branch_2_5_0/jstorm-core/src/main/java/com/alibaba/jstorm/cache/rocksdb/RocksDbOptionsFactory.java
+ DBOptions options = new DBOptions();
+ Statistics statistics = new Statistics();
+ statistics.setStatsLevel(StatsLevel.EXCEPT_DETAILED_TIMERS);
+ return options.
+ setDbLogDir(ConfigRocksDBStorage.getDBLogDir()).
+ setInfoLogLevel(InfoLogLevel.INFO_LEVEL).
+ setWalRecoveryMode(WALRecoveryMode.PointInTimeRecovery).
+ setManualWalFlush(true).
+ setMaxTotalWalSize(0).
+ setWalSizeLimitMB(0).
+ setWalTtlSeconds(0).
+ setCreateIfMissing(true).
+ setCreateMissingColumnFamilies(true).
+ setMaxOpenFiles(-1).
+ setMaxLogFileSize(1 * SizeUnit.GB).
+ setKeepLogFileNum(5).
+ setMaxManifestFileSize(1 * SizeUnit.GB).
+ setAllowConcurrentMemtableWrite(false).
+ setStatistics(statistics).
+ setAtomicFlush(true).
+ setMaxBackgroundJobs(32).
+ setMaxSubcompactions(8).
+ setParanoidChecks(true).
+ setDelayedWriteRate(16 * SizeUnit.MB).
+ setRateLimiter(new RateLimiter(100 * SizeUnit.MB)).
+ setUseDirectIoForFlushAndCompaction(false).
+ setUseDirectReads(false);
+ }
+}
diff --git a/store/src/main/java/org/apache/rocketmq/store/timer/TimerMessageStore.java b/store/src/main/java/org/apache/rocketmq/store/timer/TimerMessageStore.java
index ac4c61cd6..3ab51a26d 100644
--- a/store/src/main/java/org/apache/rocketmq/store/timer/TimerMessageStore.java
+++ b/store/src/main/java/org/apache/rocketmq/store/timer/TimerMessageStore.java
@@ -57,7 +57,6 @@ import org.apache.rocketmq.common.topic.TopicValidator;
import org.apache.rocketmq.common.utils.ThreadUtils;
import org.apache.rocketmq.logging.org.slf4j.Logger;
import org.apache.rocketmq.logging.org.slf4j.LoggerFactory;
-import org.apache.rocketmq.store.ConsumeQueue;
import org.apache.rocketmq.store.DefaultMessageStore;
import org.apache.rocketmq.store.MessageStore;
import org.apache.rocketmq.store.PutMessageResult;
@@ -66,6 +65,9 @@ import org.apache.rocketmq.store.config.BrokerRole;
import org.apache.rocketmq.store.config.MessageStoreConfig;
import org.apache.rocketmq.store.logfile.MappedFile;
import org.apache.rocketmq.store.metrics.DefaultStoreMetricsManager;
+import org.apache.rocketmq.store.queue.ConsumeQueueInterface;
+import org.apache.rocketmq.store.queue.CqUnit;
+import org.apache.rocketmq.store.queue.ReferredIterator;
import org.apache.rocketmq.store.stats.BrokerStatsManager;
import org.apache.rocketmq.store.util.PerfCounter;
@@ -333,7 +335,7 @@ public class TimerMessageStore {
// if not, use cq offset.
long msgQueueOffset = messageExt.getQueueOffset();
int queueId = messageExt.getQueueId();
- ConsumeQueue cq = (ConsumeQueue) this.messageStore.getConsumeQueue(TIMER_TOPIC, queueId);
+ ConsumeQueueInterface cq = this.messageStore.getConsumeQueue(TIMER_TOPIC, queueId);
if (null == cq) {
return msgQueueOffset;
}
@@ -346,15 +348,18 @@ public class TimerMessageStore {
offsetPy, sizePy);
break;
}
- SelectMappedBufferResult bufferCQ = cq.getIndexBuffer(tmpOffset);
- if (null == bufferCQ) {
- // offset in msg may be greater than offset of cq.
- tmpOffset -= 1;
- continue;
- }
+ ReferredIterator<CqUnit> iterator = null;
try {
- long offsetPyTemp = bufferCQ.getByteBuffer().getLong();
- int sizePyTemp = bufferCQ.getByteBuffer().getInt();
+ iterator = cq.iterateFrom(tmpOffset);
+ CqUnit cqUnit = null;
+ if (null == iterator || (cqUnit = iterator.next()) == null) {
+ // offset in msg may be greater than offset of cq.
+ tmpOffset -= 1;
+ continue;
+ }
+
+ long offsetPyTemp = cqUnit.getPos();
+ int sizePyTemp = cqUnit.getSize();
if (offsetPyTemp == offsetPy && sizePyTemp == sizePy) {
LOGGER.info("reviseQueueOffset check cq offset ok. {}, {}, {}",
tmpOffset, offsetPyTemp, sizePyTemp);
@@ -365,7 +370,9 @@ public class TimerMessageStore {
} catch (Throwable e) {
LOGGER.error("reviseQueueOffset check cq offset error.", e);
} finally {
- bufferCQ.release();
+ if (iterator != null) {
+ iterator.release();
+ }
}
}
@@ -633,7 +640,7 @@ public class TimerMessageStore {
if (!isRunningEnqueue()) {
return false;
}
- ConsumeQueue cq = (ConsumeQueue) this.messageStore.getConsumeQueue(TIMER_TOPIC, queueId);
+ ConsumeQueueInterface cq = this.messageStore.getConsumeQueue(TIMER_TOPIC, queueId);
if (null == cq) {
return false;
}
@@ -643,18 +650,22 @@ public class TimerMessageStore {
currQueueOffset = cq.getMinOffsetInQueue();
}
long offset = currQueueOffset;
- SelectMappedBufferResult bufferCQ = cq.getIndexBuffer(offset);
- if (null == bufferCQ) {
- return false;
- }
+ ReferredIterator<CqUnit> iterator = null;
try {
+ iterator = cq.iterateFrom(offset);
+ if (null == iterator) {
+ return false;
+ }
+
int i = 0;
- for (; i < bufferCQ.getSize(); i += ConsumeQueue.CQ_STORE_UNIT_SIZE) {
+ while (iterator.hasNext()) {
+ i++;
perfCounterTicks.startTick("enqueue_get");
try {
- long offsetPy = bufferCQ.getByteBuffer().getLong();
- int sizePy = bufferCQ.getByteBuffer().getInt();
- bufferCQ.getByteBuffer().getLong(); //tags code
+ CqUnit cqUnit = iterator.next();
+ long offsetPy = cqUnit.getPos();
+ int sizePy = cqUnit.getSize();
+ cqUnit.getTagsCode(); //tags code
MessageExt msgExt = getMessageByCommitOffset(offsetPy, sizePy);
if (null == msgExt) {
perfCounterTicks.getCounter("enqueue_get_miss");
@@ -663,7 +674,7 @@ public class TimerMessageStore {
lastEnqueueButExpiredStoreTime = msgExt.getStoreTimestamp();
long delayedTime = Long.parseLong(msgExt.getProperty(TIMER_OUT_MS));
// use CQ offset, not offset in Message
- msgExt.setQueueOffset(offset + (i / ConsumeQueue.CQ_STORE_UNIT_SIZE));
+ msgExt.setQueueOffset(offset + i);
TimerRequest timerRequest = new TimerRequest(offsetPy, sizePy, delayedTime, System.currentTimeMillis(), MAGIC_DEFAULT, msgExt);
// System.out.printf("build enqueue request, %s%n", timerRequest);
while (!enqueuePutQueue.offer(timerRequest, 3, TimeUnit.SECONDS)) {
@@ -687,14 +698,16 @@ public class TimerMessageStore {
if (!isRunningEnqueue()) {
return false;
}
- currQueueOffset = offset + (i / ConsumeQueue.CQ_STORE_UNIT_SIZE);
+ currQueueOffset = offset + i;
}
- currQueueOffset = offset + (i / ConsumeQueue.CQ_STORE_UNIT_SIZE);
+ currQueueOffset = offset + i;
return i > 0;
} catch (Exception e) {
LOGGER.error("Unknown exception in enqueuing", e);
} finally {
- bufferCQ.release();
+ if (iterator != null) {
+ iterator.release();
+ }
}
return false;
}
@@ -1642,7 +1655,7 @@ public class TimerMessageStore {
if (System.currentTimeMillis() - start > storeConfig.getTimerProgressLogIntervalMs()) {
start = System.currentTimeMillis();
long tmpQueueOffset = currQueueOffset;
- ConsumeQueue cq = (ConsumeQueue) messageStore.getConsumeQueue(TIMER_TOPIC, 0);
+ ConsumeQueueInterface cq = messageStore.getConsumeQueue(TIMER_TOPIC, 0);
long maxOffsetInQueue = cq == null ? 0 : cq.getMaxOffsetInQueue();
TimerMessageStore.LOGGER.info("[{}]Timer progress-check commitRead:[{}] currRead:[{}] currWrite:[{}] readBehind:{} currReadOffset:{} offsetBehind:{} behindMaster:{} " +
"enqPutQueue:{} deqGetQueue:{} deqPutQueue:{} allCongestNum:{} enqExpiredStoreTime:{}",
@@ -1685,7 +1698,7 @@ public class TimerMessageStore {
public long getEnqueueBehindMessages() {
long tmpQueueOffset = currQueueOffset;
- ConsumeQueue cq = (ConsumeQueue) messageStore.getConsumeQueue(TIMER_TOPIC, 0);
+ ConsumeQueueInterface cq = messageStore.getConsumeQueue(TIMER_TOPIC, 0);
long maxOffsetInQueue = cq == null ? 0 : cq.getMaxOffsetInQueue();
return maxOffsetInQueue - tmpQueueOffset;
}
diff --git a/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreTest.java b/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreTest.java
index 12d1e5723..1d09ca86e 100644
--- a/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreTest.java
+++ b/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreTest.java
@@ -428,9 +428,10 @@ public class DefaultMessageStoreTest {
private long getStoreTime(CqUnit cqUnit) {
try {
- Method getStoreTime = getDefaultMessageStore().getClass().getDeclaredMethod("getStoreTime", CqUnit.class);
+ Class abstractConsumeQueueStore = getDefaultMessageStore().getQueueStore().getClass().getSuperclass();
+ Method getStoreTime = abstractConsumeQueueStore.getDeclaredMethod("getStoreTime", CqUnit.class);
getStoreTime.setAccessible(true);
- return (long) getStoreTime.invoke(getDefaultMessageStore(), cqUnit);
+ return (long) getStoreTime.invoke(getDefaultMessageStore().getQueueStore(), cqUnit);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
diff --git a/store/src/test/java/org/apache/rocketmq/store/MultiDispatchTest.java b/store/src/test/java/org/apache/rocketmq/store/MultiDispatchTest.java
index 85626a332..2447bbf68 100644
--- a/store/src/test/java/org/apache/rocketmq/store/MultiDispatchTest.java
+++ b/store/src/test/java/org/apache/rocketmq/store/MultiDispatchTest.java
@@ -28,9 +28,11 @@ import org.apache.rocketmq.common.message.MessageConst;
import org.apache.rocketmq.common.message.MessageDecoder;
import org.apache.rocketmq.common.message.MessageExtBrokerInner;
import org.apache.rocketmq.store.config.MessageStoreConfig;
+import org.apache.rocketmq.store.queue.MultiDispatch;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import org.rocksdb.RocksDBException;
import static org.apache.rocketmq.store.config.StorePathConfigHelper.getStorePathConsumeQueue;
import static org.junit.Assert.assertEquals;
@@ -69,15 +71,15 @@ public class MultiDispatchTest {
}
@Test
- public void queueKey() {
+ public void lmqQueueKey() {
MessageExtBrokerInner messageExtBrokerInner = mock(MessageExtBrokerInner.class);
when(messageExtBrokerInner.getQueueId()).thenReturn(2);
- String ret = consumeQueue.queueKey("%LMQ%lmq123", messageExtBrokerInner);
+ String ret = MultiDispatch.lmqQueueKey("%LMQ%lmq123");
assertEquals(ret, "%LMQ%lmq123-0");
}
@Test
- public void wrapMultiDispatch() {
+ public void wrapMultiDispatch() throws RocksDBException {
MessageExtBrokerInner messageExtBrokerInner = buildMessageMultiQueue();
messageStore.assignOffset(messageExtBrokerInner);
assertEquals(messageExtBrokerInner.getProperty(MessageConst.PROPERTY_INNER_MULTI_QUEUE_OFFSET), "0,0");
diff --git a/store/src/test/java/org/apache/rocketmq/store/RocksDBMessageStoreTest.java b/store/src/test/java/org/apache/rocketmq/store/RocksDBMessageStoreTest.java
new file mode 100644
index 000000000..acf5edf51
--- /dev/null
+++ b/store/src/test/java/org/apache/rocketmq/store/RocksDBMessageStoreTest.java
@@ -0,0 +1,1060 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.store;
+
+import java.io.File;
+import java.io.RandomAccessFile;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.net.UnknownHostException;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.OverlappingFileLockException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import com.google.common.collect.Sets;
+
+import org.apache.rocketmq.common.BrokerConfig;
+import org.apache.rocketmq.common.MixAll;
+import org.apache.rocketmq.common.TopicConfig;
+import org.apache.rocketmq.common.UtilAll;
+import org.apache.rocketmq.common.message.MessageBatch;
+import org.apache.rocketmq.common.message.MessageDecoder;
+import org.apache.rocketmq.common.message.MessageExt;
+import org.apache.rocketmq.common.message.MessageExtBatch;
+import org.apache.rocketmq.common.message.MessageExtBrokerInner;
+import org.apache.rocketmq.store.config.BrokerRole;
+import org.apache.rocketmq.store.config.FlushDiskType;
+import org.apache.rocketmq.store.config.MessageStoreConfig;
+import org.apache.rocketmq.store.config.StorePathConfigHelper;
+import org.apache.rocketmq.store.queue.ConsumeQueueInterface;
+import org.apache.rocketmq.store.queue.CqUnit;
+import org.apache.rocketmq.store.stats.BrokerStatsManager;
+import org.assertj.core.util.Strings;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(MockitoJUnitRunner.class)
+public class RocksDBMessageStoreTest {
+ private final String storeMessage = "Once, there was a chance for me!";
+ private final String messageTopic = "FooBar";
+ private final String storeType = StoreType.DEFAULT_ROCKSDB.getStoreType();
+ private int queueTotal = 100;
+ private AtomicInteger queueId = new AtomicInteger(0);
+ private SocketAddress bornHost;
+ private SocketAddress storeHost;
+ private byte[] messageBody;
+ private MessageStore messageStore;
+
+ @Before
+ public void init() throws Exception {
+ if (notExecuted()) {
+ return;
+ }
+ storeHost = new InetSocketAddress(InetAddress.getLocalHost(), 8123);
+ bornHost = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 0);
+
+ messageStore = buildMessageStore();
+ boolean load = messageStore.load();
+ assertTrue(load);
+ messageStore.start();
+ }
+
+ @Test(expected = OverlappingFileLockException.class)
+ public void test_repeat_restart() throws Exception {
+ if (notExecuted()) {
+ throw new OverlappingFileLockException();
+ }
+ queueTotal = 1;
+ messageBody = storeMessage.getBytes();
+
+ MessageStoreConfig messageStoreConfig = new MessageStoreConfig();
+ messageStoreConfig.setMappedFileSizeCommitLog(1024 * 8);
+ messageStoreConfig.setMappedFileSizeConsumeQueue(1024 * 4);
+ messageStoreConfig.setMaxHashSlotNum(100);
+ messageStoreConfig.setMaxIndexNum(100 * 10);
+ messageStoreConfig.setStorePathRootDir(System.getProperty("java.io.tmpdir") + File.separator + "store");
+ messageStoreConfig.setHaListenPort(0);
+ MessageStore master = new RocksDBMessageStore(messageStoreConfig, null, new MyMessageArrivingListener(), new BrokerConfig(), new ConcurrentHashMap<>());
+
+ boolean load = master.load();
+ assertTrue(load);
+
+ try {
+ master.start();
+ master.start();
+ } finally {
+ master.shutdown();
+ master.destroy();
+ }
+ }
+
+ @After
+ public void destroy() {
+ if (notExecuted()) {
+ return;
+ }
+ messageStore.shutdown();
+ messageStore.destroy();
+
+ MessageStoreConfig messageStoreConfig = new MessageStoreConfig();
+ File file = new File(messageStoreConfig.getStorePathRootDir());
+ UtilAll.deleteFile(file);
+ }
+
+ private MessageStore buildMessageStore() throws Exception {
+ return buildMessageStore(null, "");
+ }
+
+ private MessageStore buildMessageStore(String storePathRootDir, String topic) throws Exception {
+ MessageStoreConfig messageStoreConfig = new MessageStoreConfig();
+ messageStoreConfig.setMappedFileSizeCommitLog(1024 * 1024 * 10);
+ messageStoreConfig.setMappedFileSizeConsumeQueue(1024 * 1024 * 10);
+ messageStoreConfig.setMaxHashSlotNum(10000);
+ messageStoreConfig.setMaxIndexNum(100 * 100);
+ messageStoreConfig.setFlushDiskType(FlushDiskType.SYNC_FLUSH);
+ messageStoreConfig.setFlushIntervalConsumeQueue(1);
+ messageStoreConfig.setStoreType(storeType);
+ messageStoreConfig.setHaListenPort(0);
+ if (Strings.isNullOrEmpty(storePathRootDir)) {
+ UUID uuid = UUID.randomUUID();
+ storePathRootDir = System.getProperty("java.io.tmpdir") + File.separator + "store-" + uuid.toString();
+ }
+ messageStoreConfig.setStorePathRootDir(storePathRootDir);
+ ConcurrentMap<String, TopicConfig> topicConfigTable = new ConcurrentHashMap<>();
+ topicConfigTable.put(topic, new TopicConfig(topic, 1, 1));
+ return new RocksDBMessageStore(messageStoreConfig,
+ new BrokerStatsManager("simpleTest", true),
+ new MyMessageArrivingListener(),
+ new BrokerConfig(), topicConfigTable);
+ }
+
+ @Test
+ public void testWriteAndRead() {
+ if (notExecuted()) {
+ return;
+ }
+ long ipv4HostMsgs = 10;
+ long ipv6HostMsgs = 10;
+ long totalMsgs = ipv4HostMsgs + ipv6HostMsgs;
+ queueTotal = 1;
+ messageBody = storeMessage.getBytes();
+ for (long i = 0; i < ipv4HostMsgs; i++) {
+ messageStore.putMessage(buildMessage());
+ }
+
+ for (long i = 0; i < ipv6HostMsgs; i++) {
+ messageStore.putMessage(buildIPv6HostMessage());
+ }
+
+ StoreTestUtil.waitCommitLogReput((RocksDBMessageStore) messageStore);
+
+ for (long i = 0; i < totalMsgs; i++) {
+ GetMessageResult result = messageStore.getMessage("GROUP_A", "FooBar", 0, i, 1024 * 1024, null);
+ assertThat(result).isNotNull();
+ result.release();
+ }
+ verifyThatMasterIsFunctional(totalMsgs, messageStore);
+ }
+
+ @Test
+ public void testLookMessageByOffset_OffsetIsFirst() {
+ if (notExecuted()) {
+ return;
+ }
+ final int totalCount = 10;
+ int queueId = new Random().nextInt(10);
+ String topic = "FooBar";
+ int firstOffset = 0;
+ AppendMessageResult[] appendMessageResultArray = putMessages(totalCount, topic, queueId);
+ AppendMessageResult firstResult = appendMessageResultArray[0];
+
+ MessageExt messageExt = messageStore.lookMessageByOffset(firstResult.getWroteOffset());
+ MessageExt messageExt1 = getDefaultMessageStore().lookMessageByOffset(firstResult.getWroteOffset(), firstResult.getWroteBytes());
+
+ assertThat(new String(messageExt.getBody())).isEqualTo(buildMessageBodyByOffset(storeMessage, firstOffset));
+ assertThat(new String(messageExt1.getBody())).isEqualTo(buildMessageBodyByOffset(storeMessage, firstOffset));
+ }
+
+ @Test
+ public void testLookMessageByOffset_OffsetIsLast() {
+ if (notExecuted()) {
+ return;
+ }
+ final int totalCount = 10;
+ int queueId = new Random().nextInt(10);
+ String topic = "FooBar";
+ AppendMessageResult[] appendMessageResultArray = putMessages(totalCount, topic, queueId);
+ int lastIndex = totalCount - 1;
+ AppendMessageResult lastResult = appendMessageResultArray[lastIndex];
+
+ MessageExt messageExt = getDefaultMessageStore().lookMessageByOffset(lastResult.getWroteOffset(), lastResult.getWroteBytes());
+
+ assertThat(new String(messageExt.getBody())).isEqualTo(buildMessageBodyByOffset(storeMessage, lastIndex));
+ }
+
+ @Test
+ public void testLookMessageByOffset_OffsetIsOutOfBound() {
+ if (notExecuted()) {
+ return;
+ }
+ final int totalCount = 10;
+ int queueId = new Random().nextInt(10);
+ String topic = "FooBar";
+ AppendMessageResult[] appendMessageResultArray = putMessages(totalCount, topic, queueId);
+ long lastOffset = getMaxOffset(appendMessageResultArray);
+
+ MessageExt messageExt = getDefaultMessageStore().lookMessageByOffset(lastOffset);
+
+ assertThat(messageExt).isNull();
+ }
+
+ @Test
+ public void testGetOffsetInQueueByTime() {
+ if (notExecuted()) {
+ return;
+ }
+ final int totalCount = 10;
+ int queueId = 0;
+ String topic = "FooBar";
+ AppendMessageResult[] appendMessageResults = putMessages(totalCount, topic, queueId, true);
+ //Thread.sleep(10);
+ StoreTestUtil.waitCommitLogReput((RocksDBMessageStore) messageStore);
+
+ ConsumeQueueInterface consumeQueue = getDefaultMessageStore().findConsumeQueue(topic, queueId);
+ for (AppendMessageResult appendMessageResult : appendMessageResults) {
+ long offset = messageStore.getOffsetInQueueByTime(topic, queueId, appendMessageResult.getStoreTimestamp());
+ CqUnit cqUnit = consumeQueue.get(offset);
+ assertThat(cqUnit.getPos()).isEqualTo(appendMessageResult.getWroteOffset());
+ assertThat(cqUnit.getSize()).isEqualTo(appendMessageResult.getWroteBytes());
+ }
+ }
+
+ @Test
+ public void testGetOffsetInQueueByTime_TimestampIsSkewing() {
+ if (notExecuted()) {
+ return;
+ }
+ final int totalCount = 10;
+ int queueId = 0;
+ String topic = "FooBar";
+ AppendMessageResult[] appendMessageResults = putMessages(totalCount, topic, queueId, true);
+ //Thread.sleep(10);
+ StoreTestUtil.waitCommitLogReput((RocksDBMessageStore) messageStore);
+ int skewing = 2;
+
+ ConsumeQueueInterface consumeQueue = getDefaultMessageStore().findConsumeQueue(topic, queueId);
+ for (AppendMessageResult appendMessageResult : appendMessageResults) {
+ long offset = messageStore.getOffsetInQueueByTime(topic, queueId, appendMessageResult.getStoreTimestamp() - skewing);
+ CqUnit cqUnit = consumeQueue.get(offset);
+ assertThat(cqUnit.getPos()).isEqualTo(appendMessageResult.getWroteOffset());
+ assertThat(cqUnit.getSize()).isEqualTo(appendMessageResult.getWroteBytes());
+ }
+ }
+
+ @Test
+ public void testGetOffsetInQueueByTime_TimestampSkewingIsLarge() {
+ if (notExecuted()) {
+ return;
+ }
+ final int totalCount = 10;
+ int queueId = 0;
+ String topic = "FooBar";
+ AppendMessageResult[] appendMessageResults = putMessages(totalCount, topic, queueId, true);
+ //Thread.sleep(10);
+ StoreTestUtil.waitCommitLogReput((RocksDBMessageStore) messageStore);
+ int skewing = 20000;
+
+ ConsumeQueueInterface consumeQueue = getDefaultMessageStore().findConsumeQueue(topic, queueId);
+ for (AppendMessageResult appendMessageResult : appendMessageResults) {
+ long offset = messageStore.getOffsetInQueueByTime(topic, queueId, appendMessageResult.getStoreTimestamp() - skewing);
+ CqUnit cqUnit = consumeQueue.get(offset);
+ assertThat(cqUnit.getPos()).isEqualTo(appendMessageResults[0].getWroteOffset());
+ assertThat(cqUnit.getSize()).isEqualTo(appendMessageResults[0].getWroteBytes());
+ }
+ }
+
+ @Test
+ public void testGetOffsetInQueueByTime_ConsumeQueueNotFound1() {
+ if (notExecuted()) {
+ return;
+ }
+ final int totalCount = 10;
+ int queueId = 0;
+ int wrongQueueId = 1;
+ String topic = "FooBar";
+ AppendMessageResult[] appendMessageResults = putMessages(totalCount, topic, queueId, false);
+ //Thread.sleep(10);
+
+ StoreTestUtil.waitCommitLogReput((RocksDBMessageStore) messageStore);
+
+ long offset = messageStore.getOffsetInQueueByTime(topic, wrongQueueId, appendMessageResults[0].getStoreTimestamp());
+
+ assertThat(offset).isEqualTo(0);
+ }
+
+ @Test
+ public void testGetOffsetInQueueByTime_ConsumeQueueNotFound2() {
+ if (notExecuted()) {
+ return;
+ }
+ final int totalCount = 10;
+ int queueId = 0;
+ int wrongQueueId = 1;
+ String topic = "FooBar";
+ putMessages(totalCount, topic, queueId, false);
+ //Thread.sleep(10);
+ StoreTestUtil.waitCommitLogReput((RocksDBMessageStore) messageStore);
+
+ long messageStoreTimeStamp = messageStore.getMessageStoreTimeStamp(topic, wrongQueueId, 0);
+
+ assertThat(messageStoreTimeStamp).isEqualTo(-1);
+ }
+
+ @Test
+ public void testGetOffsetInQueueByTime_ConsumeQueueOffsetNotExist() {
+ if (notExecuted()) {
+ return;
+ }
+ final int totalCount = 10;
+ int queueId = 0;
+ int wrongQueueId = 1;
+ String topic = "FooBar";
+ putMessages(totalCount, topic, queueId, true);
+ //Thread.sleep(10);
+
+ StoreTestUtil.waitCommitLogReput((RocksDBMessageStore) messageStore);
+
+ long messageStoreTimeStamp = messageStore.getMessageStoreTimeStamp(topic, wrongQueueId, -1);
+
+ assertThat(messageStoreTimeStamp).isEqualTo(-1);
+ }
+
+ @Test
+ public void testGetMessageStoreTimeStamp() {
+ if (notExecuted()) {
+ return;
+ }
+ final int totalCount = 10;
+ int queueId = 0;
+ String topic = "FooBar";
+ AppendMessageResult[] appendMessageResults = putMessages(totalCount, topic, queueId, false);
+ //Thread.sleep(10);
+ StoreTestUtil.waitCommitLogReput((RocksDBMessageStore) messageStore);
+
+ ConsumeQueueInterface consumeQueue = getDefaultMessageStore().findConsumeQueue(topic, queueId);
+ int minOffsetInQueue = (int) consumeQueue.getMinOffsetInQueue();
+ for (int i = minOffsetInQueue; i < consumeQueue.getMaxOffsetInQueue(); i++) {
+ long messageStoreTimeStamp = messageStore.getMessageStoreTimeStamp(topic, queueId, i);
+ assertThat(messageStoreTimeStamp).isEqualTo(appendMessageResults[i].getStoreTimestamp());
+ }
+ }
+
+ @Test
+ public void testGetStoreTime_ParamIsNull() {
+ if (notExecuted()) {
+ return;
+ }
+ long storeTime = getStoreTime(null);
+
+ assertThat(storeTime).isEqualTo(-1);
+ }
+
+ @Test
+ public void testGetStoreTime_EverythingIsOk() {
+ if (notExecuted()) {
+ return;
+ }
+ final int totalCount = 10;
+ int queueId = 0;
+ String topic = "FooBar";
+ AppendMessageResult[] appendMessageResults = putMessages(totalCount, topic, queueId, false);
+ //Thread.sleep(10);
+ StoreTestUtil.waitCommitLogReput((RocksDBMessageStore) messageStore);
+ ConsumeQueueInterface consumeQueue = messageStore.getConsumeQueue(topic, queueId);
+
+ for (int i = 0; i < totalCount; i++) {
+ CqUnit cqUnit = consumeQueue.get(i);
+ long storeTime = getStoreTime(cqUnit);
+ assertThat(storeTime).isEqualTo(appendMessageResults[i].getStoreTimestamp());
+ }
+ }
+
+ @Test
+ public void testGetStoreTime_PhyOffsetIsLessThanCommitLogMinOffset() {
+ if (notExecuted()) {
+ return;
+ }
+ long phyOffset = -10;
+ int size = 138;
+ CqUnit cqUnit = new CqUnit(0, phyOffset, size, 0);
+ long storeTime = getStoreTime(cqUnit);
+
+ assertThat(storeTime).isEqualTo(-1);
+ }
+
+ @Test
+ public void testPutMessage_whenMessagePropertyIsTooLong() {
+ if (notExecuted()) {
+ return;
+ }
+ String topicName = "messagePropertyIsTooLongTest";
+ MessageExtBrokerInner illegalMessage = buildSpecifyLengthPropertyMessage("123".getBytes(StandardCharsets.UTF_8), topicName, Short.MAX_VALUE + 1);
+ assertEquals(messageStore.putMessage(illegalMessage).getPutMessageStatus(), PutMessageStatus.PROPERTIES_SIZE_EXCEEDED);
+ assertEquals(0L, messageStore.getQueueStore().getMaxOffset(topicName, 0).longValue());
+ MessageExtBrokerInner normalMessage = buildSpecifyLengthPropertyMessage("123".getBytes(StandardCharsets.UTF_8), topicName, 100);
+ assertEquals(messageStore.putMessage(normalMessage).getPutMessageStatus(), PutMessageStatus.PUT_OK);
+ assertEquals(1L, messageStore.getQueueStore().getMaxOffset(topicName, 0).longValue());
+ }
+
+ private RocksDBMessageStore getDefaultMessageStore() {
+ return (RocksDBMessageStore) this.messageStore;
+ }
+
+ private AppendMessageResult[] putMessages(int totalCount, String topic, int queueId) {
+ return putMessages(totalCount, topic, queueId, false);
+ }
+
+ private AppendMessageResult[] putMessages(int totalCount, String topic, int queueId, boolean interval) {
+ AppendMessageResult[] appendMessageResultArray = new AppendMessageResult[totalCount];
+ for (int i = 0; i < totalCount; i++) {
+ String messageBody = buildMessageBodyByOffset(storeMessage, i);
+
+ MessageExtBrokerInner msgInner =
+ i < totalCount / 2 ? buildMessage(messageBody.getBytes(), topic) : buildIPv6HostMessage(messageBody.getBytes(), topic);
+ msgInner.setQueueId(queueId);
+ PutMessageResult result = messageStore.putMessage(msgInner);
+ appendMessageResultArray[i] = result.getAppendMessageResult();
+ assertThat(result.getPutMessageStatus()).isEqualTo(PutMessageStatus.PUT_OK);
+ if (interval) {
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Thread sleep ERROR");
+ }
+ }
+ }
+ return appendMessageResultArray;
+ }
+
+ private long getMaxOffset(AppendMessageResult[] appendMessageResultArray) {
+ if (appendMessageResultArray == null) {
+ return 0;
+ }
+ AppendMessageResult last = appendMessageResultArray[appendMessageResultArray.length - 1];
+ return last.getWroteOffset() + last.getWroteBytes();
+ }
+
+ private String buildMessageBodyByOffset(String message, long i) {
+ return String.format("%s offset %d", message, i);
+ }
+
+ private long getStoreTime(CqUnit cqUnit) {
+ try {
+ Class abstractConsumeQueueStore = getDefaultMessageStore().getQueueStore().getClass().getSuperclass();
+ Method getStoreTime = abstractConsumeQueueStore.getDeclaredMethod("getStoreTime", CqUnit.class);
+ getStoreTime.setAccessible(true);
+ return (long) getStoreTime.invoke(getDefaultMessageStore().getQueueStore(), cqUnit);
+ } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private MessageExtBrokerInner buildMessage(byte[] messageBody, String topic) {
+ MessageExtBrokerInner msg = new MessageExtBrokerInner();
+ msg.setTopic(topic);
+ msg.setTags("TAG1");
+ msg.setKeys("Hello");
+ msg.setBody(messageBody);
+ msg.setKeys(String.valueOf(System.currentTimeMillis()));
+ msg.setQueueId(Math.abs(queueId.getAndIncrement()) % queueTotal);
+ msg.setSysFlag(0);
+ msg.setBornTimestamp(System.currentTimeMillis());
+ msg.setStoreHost(storeHost);
+ msg.setBornHost(bornHost);
+ msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties()));
+ return msg;
+ }
+
+ private MessageExtBrokerInner buildSpecifyLengthPropertyMessage(byte[] messageBody, String topic, int length) {
+ StringBuilder stringBuilder = new StringBuilder();
+ Random random = new Random();
+ for (int i = 0; i < length; i++) {
+ stringBuilder.append(random.nextInt(10));
+ }
+ MessageExtBrokerInner msg = new MessageExtBrokerInner();
+ msg.putUserProperty("test", stringBuilder.toString());
+ msg.setTopic(topic);
+ msg.setTags("TAG1");
+ msg.setKeys("Hello");
+ msg.setBody(messageBody);
+ msg.setQueueId(0);
+ msg.setBornTimestamp(System.currentTimeMillis());
+ msg.setStoreHost(storeHost);
+ msg.setBornHost(bornHost);
+ msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties()));
+ return msg;
+ }
+
+ private MessageExtBrokerInner buildIPv6HostMessage(byte[] messageBody, String topic) {
+ MessageExtBrokerInner msg = new MessageExtBrokerInner();
+ msg.setTopic(topic);
+ msg.setTags("TAG1");
+ msg.setKeys("Hello");
+ msg.setBody(messageBody);
+ msg.setMsgId("24084004018081003FAA1DDE2B3F898A00002A9F0000000000000CA0");
+ msg.setKeys(String.valueOf(System.currentTimeMillis()));
+ msg.setQueueId(Math.abs(queueId.getAndIncrement()) % queueTotal);
+ msg.setSysFlag(0);
+ msg.setBornHostV6Flag();
+ msg.setStoreHostAddressV6Flag();
+ msg.setBornTimestamp(System.currentTimeMillis());
+ try {
+ msg.setBornHost(new InetSocketAddress(InetAddress.getByName("1050:0000:0000:0000:0005:0600:300c:326b"), 0));
+ } catch (UnknownHostException e) {
+ e.printStackTrace();
+ assertThat(Boolean.FALSE).isTrue();
+ }
+
+ try {
+ msg.setStoreHost(new InetSocketAddress(InetAddress.getByName("::1"), 0));
+ } catch (UnknownHostException e) {
+ e.printStackTrace();
+ assertThat(Boolean.FALSE).isTrue();
+ }
+ return msg;
+ }
+
+ private MessageExtBrokerInner buildMessage() {
+ return buildMessage(messageBody, messageTopic);
+ }
+
+ public MessageExtBatch buildMessageBatch(MessageBatch msgBatch) {
+ MessageExtBatch msgExtBatch = new MessageExtBatch();
+ msgExtBatch.setTopic(messageTopic);
+ msgExtBatch.setTags("TAG1");
+ msgExtBatch.setKeys("Hello");
+ msgExtBatch.setBody(msgBatch.getBody());
+ msgExtBatch.setKeys(String.valueOf(System.currentTimeMillis()));
+ msgExtBatch.setQueueId(Math.abs(queueId.getAndIncrement()) % queueTotal);
+ msgExtBatch.setSysFlag(0);
+ msgExtBatch.setBornTimestamp(System.currentTimeMillis());
+ msgExtBatch.setStoreHost(storeHost);
+ msgExtBatch.setBornHost(bornHost);
+ return msgExtBatch;
+ }
+
+ @Test
+ public void testGroupCommit() throws Exception {
+ if (notExecuted()) {
+ return;
+ }
+ long totalMsgs = 10;
+ queueTotal = 1;
+ messageBody = storeMessage.getBytes();
+ for (long i = 0; i < totalMsgs; i++) {
+ messageStore.putMessage(buildMessage());
+ }
+
+ for (long i = 0; i < totalMsgs; i++) {
+ GetMessageResult result = messageStore.getMessage("GROUP_A", "TOPIC_A", 0, i, 1024 * 1024, null);
+ assertThat(result).isNotNull();
+ result.release();
+ }
+ verifyThatMasterIsFunctional(totalMsgs, messageStore);
+ }
+
+ @Test
+ public void testMaxOffset() throws InterruptedException {
+ if (notExecuted()) {
+ return;
+ }
+ int firstBatchMessages = 3;
+ int queueId = 0;
+ messageBody = storeMessage.getBytes();
+
+ assertThat(messageStore.getMaxOffsetInQueue(messageTopic, queueId)).isEqualTo(0);
+
+ for (int i = 0; i < firstBatchMessages; i++) {
+ final MessageExtBrokerInner msg = buildMessage();
+ msg.setQueueId(queueId);
+ messageStore.putMessage(msg);
+ }
+
+ while (messageStore.dispatchBehindBytes() != 0) {
+ TimeUnit.MILLISECONDS.sleep(1);
+ }
+
+ assertThat(messageStore.getMaxOffsetInQueue(messageTopic, queueId)).isEqualTo(firstBatchMessages);
+
+ // Disable the dispatcher
+ messageStore.getDispatcherList().clear();
+
+ int secondBatchMessages = 2;
+
+ for (int i = 0; i < secondBatchMessages; i++) {
+ final MessageExtBrokerInner msg = buildMessage();
+ msg.setQueueId(queueId);
+ messageStore.putMessage(msg);
+ }
+
+ assertThat(messageStore.getMaxOffsetInQueue(messageTopic, queueId)).isEqualTo(firstBatchMessages);
+ assertThat(messageStore.getMaxOffsetInQueue(messageTopic, queueId, true)).isEqualTo(firstBatchMessages);
+ assertThat(messageStore.getMaxOffsetInQueue(messageTopic, queueId, false)).isEqualTo(firstBatchMessages + secondBatchMessages);
+ }
+
+ private MessageExtBrokerInner buildIPv6HostMessage() {
+ return buildIPv6HostMessage(messageBody, "FooBar");
+ }
+
+ private void verifyThatMasterIsFunctional(long totalMsgs, MessageStore master) {
+ for (long i = 0; i < totalMsgs; i++) {
+ master.putMessage(buildMessage());
+ }
+
+ StoreTestUtil.waitCommitLogReput((RocksDBMessageStore) messageStore);
+
+ for (long i = 0; i < totalMsgs; i++) {
+ GetMessageResult result = master.getMessage("GROUP_A", "FooBar", 0, i, 1024 * 1024, null);
+ assertThat(result).isNotNull();
+ result.release();
+
+ }
+ }
+
+ @Test
+ public void testPullSize() throws Exception {
+ if (notExecuted()) {
+ return;
+ }
+ String topic = "pullSizeTopic";
+
+ for (int i = 0; i < 32; i++) {
+ MessageExtBrokerInner messageExtBrokerInner = buildMessage();
+ messageExtBrokerInner.setTopic(topic);
+ messageExtBrokerInner.setQueueId(0);
+ messageStore.putMessage(messageExtBrokerInner);
+ }
+ // wait for consume queue build
+ // the sleep time should be great than consume queue flush interval
+ //Thread.sleep(100);
+ StoreTestUtil.waitCommitLogReput((RocksDBMessageStore) messageStore);
+ String group = "simple";
+ GetMessageResult getMessageResult32 = messageStore.getMessage(group, topic, 0, 0, 32, null);
+ assertThat(getMessageResult32.getMessageBufferList().size()).isEqualTo(32);
+ getMessageResult32.release();
+
+ GetMessageResult getMessageResult20 = messageStore.getMessage(group, topic, 0, 0, 20, null);
+ assertThat(getMessageResult20.getMessageBufferList().size()).isEqualTo(20);
+
+ getMessageResult20.release();
+ GetMessageResult getMessageResult45 = messageStore.getMessage(group, topic, 0, 0, 10, null);
+ assertThat(getMessageResult45.getMessageBufferList().size()).isEqualTo(10);
+ getMessageResult45.release();
+
+ }
+
+ @Test
+ public void testRecover() throws Exception {
+ if (notExecuted()) {
+ return;
+ }
+ String topic = "recoverTopic";
+ messageBody = storeMessage.getBytes();
+ for (int i = 0; i < 100; i++) {
+ MessageExtBrokerInner messageExtBrokerInner = buildMessage();
+ messageExtBrokerInner.setTopic(topic);
+ messageExtBrokerInner.setQueueId(0);
+ messageStore.putMessage(messageExtBrokerInner);
+ }
+
+ // Thread.sleep(100);//wait for build consumer queue
+ StoreTestUtil.waitCommitLogReput((RocksDBMessageStore) messageStore);
+
+ long maxPhyOffset = messageStore.getMaxPhyOffset();
+ long maxCqOffset = messageStore.getMaxOffsetInQueue(topic, 0);
+
+ //1.just reboot
+ messageStore.shutdown();
+ String storeRootDir = ((RocksDBMessageStore) messageStore).getMessageStoreConfig().getStorePathRootDir();
+ messageStore = buildMessageStore(storeRootDir, topic);
+ boolean load = messageStore.load();
+ assertTrue(load);
+ messageStore.start();
+ assertTrue(maxPhyOffset == messageStore.getMaxPhyOffset());
+ assertTrue(maxCqOffset == messageStore.getMaxOffsetInQueue(topic, 0));
+
+ //2.damage commit-log and reboot normal
+ for (int i = 0; i < 100; i++) {
+ MessageExtBrokerInner messageExtBrokerInner = buildMessage();
+ messageExtBrokerInner.setTopic(topic);
+ messageExtBrokerInner.setQueueId(0);
+ messageStore.putMessage(messageExtBrokerInner);
+ }
+ //Thread.sleep(100);
+ StoreTestUtil.waitCommitLogReput((RocksDBMessageStore) messageStore);
+ long secondLastPhyOffset = messageStore.getMaxPhyOffset();
+ long secondLastCqOffset = messageStore.getMaxOffsetInQueue(topic, 0);
+
+ MessageExtBrokerInner messageExtBrokerInner = buildMessage();
+ messageExtBrokerInner.setTopic(topic);
+ messageExtBrokerInner.setQueueId(0);
+ messageStore.putMessage(messageExtBrokerInner);
+
+
+ messageStore.shutdown();
+
+ //damage last message
+ damageCommitLog((RocksDBMessageStore) messageStore, secondLastPhyOffset);
+
+ //reboot
+ messageStore = buildMessageStore(storeRootDir, topic);
+ load = messageStore.load();
+ assertTrue(load);
+ messageStore.start();
+ assertTrue(secondLastPhyOffset == messageStore.getMaxPhyOffset());
+ assertTrue(secondLastCqOffset == messageStore.getMaxOffsetInQueue(topic, 0));
+
+ //3.damage commitlog and reboot abnormal
+ for (int i = 0; i < 100; i++) {
+ messageExtBrokerInner = buildMessage();
+ messageExtBrokerInner.setTopic(topic);
+ messageExtBrokerInner.setQueueId(0);
+ messageStore.putMessage(messageExtBrokerInner);
+ }
+ //Thread.sleep(100);
+ StoreTestUtil.waitCommitLogReput((RocksDBMessageStore) messageStore);
+ secondLastPhyOffset = messageStore.getMaxPhyOffset();
+ secondLastCqOffset = messageStore.getMaxOffsetInQueue(topic, 0);
+
+ messageExtBrokerInner = buildMessage();
+ messageExtBrokerInner.setTopic(topic);
+ messageExtBrokerInner.setQueueId(0);
+ messageStore.putMessage(messageExtBrokerInner);
+ messageStore.shutdown();
+
+ //damage last message
+ damageCommitLog((RocksDBMessageStore) messageStore, secondLastPhyOffset);
+ //add abort file
+ String fileName = StorePathConfigHelper.getAbortFile(((RocksDBMessageStore) messageStore).getMessageStoreConfig().getStorePathRootDir());
+ File file = new File(fileName);
+ UtilAll.ensureDirOK(file.getParent());
+ file.createNewFile();
+
+ messageStore = buildMessageStore(storeRootDir, topic);
+ load = messageStore.load();
+ assertTrue(load);
+ messageStore.start();
+ assertTrue(secondLastPhyOffset == messageStore.getMaxPhyOffset());
+ assertTrue(secondLastCqOffset == messageStore.getMaxOffsetInQueue(topic, 0));
+
+ //message write again
+ for (int i = 0; i < 100; i++) {
+ messageExtBrokerInner = buildMessage();
+ messageExtBrokerInner.setTopic(topic);
+ messageExtBrokerInner.setQueueId(0);
+ messageStore.putMessage(messageExtBrokerInner);
+ }
+ }
+
+ @Test
+ public void testStorePathOK() {
+ if (notExecuted()) {
+ return;
+ }
+ if (messageStore instanceof RocksDBMessageStore) {
+ assertTrue(fileExists(((RocksDBMessageStore) messageStore).getStorePathPhysic()));
+ assertTrue(fileExists(((RocksDBMessageStore) messageStore).getStorePathLogic()));
+ }
+ }
+
+ private boolean fileExists(String path) {
+ if (path != null) {
+ File f = new File(path);
+ return f.exists();
+ }
+ return false;
+ }
+
+ private void damageCommitLog(RocksDBMessageStore store, long offset) throws Exception {
+ assertThat(store).isNotNull();
+ MessageStoreConfig messageStoreConfig = store.getMessageStoreConfig();
+ File file = new File(messageStoreConfig.getStorePathCommitLog() + File.separator + "00000000000000000000");
+ try (RandomAccessFile raf = new RandomAccessFile(file, "rw");
+ FileChannel fileChannel = raf.getChannel()) {
+ MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, 1024 * 1024 * 10);
+ int bodyLen = mappedByteBuffer.getInt((int) offset + 84);
+ int topicLenIndex = (int) offset + 84 + bodyLen + 4;
+ mappedByteBuffer.position(topicLenIndex);
+ mappedByteBuffer.putInt(0);
+ mappedByteBuffer.putInt(0);
+ mappedByteBuffer.putInt(0);
+ mappedByteBuffer.putInt(0);
+ mappedByteBuffer.force();
+ fileChannel.force(true);
+ }
+ }
+
+ @Test
+ public void testPutMsgExceedsMaxLength() {
+ if (notExecuted()) {
+ return;
+ }
+ messageBody = new byte[4 * 1024 * 1024 + 1];
+ MessageExtBrokerInner msg = buildMessage();
+
+ PutMessageResult result = messageStore.putMessage(msg);
+ assertThat(result.getPutMessageStatus()).isEqualTo(PutMessageStatus.MESSAGE_ILLEGAL);
+ }
+
+ @Test
+ public void testPutMsgBatchExceedsMaxLength() {
+ if (notExecuted()) {
+ return;
+ }
+ messageBody = new byte[4 * 1024 * 1024 + 1];
+ MessageExtBrokerInner msg1 = buildMessage();
+ MessageExtBrokerInner msg2 = buildMessage();
+ MessageExtBrokerInner msg3 = buildMessage();
+
+ MessageBatch msgBatch = MessageBatch.generateFromList(Arrays.asList(msg1, msg2, msg3));
+ msgBatch.setBody(msgBatch.encode());
+
+ MessageExtBatch msgExtBatch = buildMessageBatch(msgBatch);
+
+ try {
+ PutMessageResult result = this.messageStore.putMessages(msgExtBatch);
+ } catch (Exception e) {
+ assertThat(e.getMessage()).contains("message body size exceeded");
+ }
+ }
+
+ @Test
+ public void testPutMsgWhenReplicasNotEnough() {
+ if (notExecuted()) {
+ return;
+ }
+ MessageStoreConfig messageStoreConfig = ((RocksDBMessageStore) this.messageStore).getMessageStoreConfig();
+ messageStoreConfig.setBrokerRole(BrokerRole.SYNC_MASTER);
+ messageStoreConfig.setTotalReplicas(2);
+ messageStoreConfig.setInSyncReplicas(2);
+ messageStoreConfig.setEnableAutoInSyncReplicas(false);
+ ((RocksDBMessageStore) this.messageStore).getBrokerConfig().setEnableSlaveActingMaster(true);
+ this.messageStore.setAliveReplicaNumInGroup(1);
+
+ MessageExtBrokerInner msg = buildMessage();
+ PutMessageResult result = this.messageStore.putMessage(msg);
+ assertThat(result.getPutMessageStatus()).isEqualTo(PutMessageStatus.IN_SYNC_REPLICAS_NOT_ENOUGH);
+ ((RocksDBMessageStore) this.messageStore).getBrokerConfig().setEnableSlaveActingMaster(false);
+ }
+
+ @Test
+ public void testPutMsgWhenAdaptiveDegradation() {
+ if (notExecuted()) {
+ return;
+ }
+ MessageStoreConfig messageStoreConfig = ((RocksDBMessageStore) this.messageStore).getMessageStoreConfig();
+ messageStoreConfig.setBrokerRole(BrokerRole.SYNC_MASTER);
+ messageStoreConfig.setTotalReplicas(2);
+ messageStoreConfig.setInSyncReplicas(2);
+ messageStoreConfig.setEnableAutoInSyncReplicas(true);
+ ((RocksDBMessageStore) this.messageStore).getBrokerConfig().setEnableSlaveActingMaster(true);
+ this.messageStore.setAliveReplicaNumInGroup(1);
+
+ MessageExtBrokerInner msg = buildMessage();
+ PutMessageResult result = this.messageStore.putMessage(msg);
+ assertThat(result.getPutMessageStatus()).isEqualTo(PutMessageStatus.PUT_OK);
+ ((RocksDBMessageStore) this.messageStore).getBrokerConfig().setEnableSlaveActingMaster(false);
+ messageStoreConfig.setEnableAutoInSyncReplicas(false);
+ }
+
+ @Test
+ public void testGetBulkCommitLogData() {
+ if (notExecuted()) {
+ return;
+ }
+ RocksDBMessageStore defaultMessageStore = (RocksDBMessageStore) messageStore;
+
+ messageBody = new byte[2 * 1024 * 1024];
+
+ for (int i = 0; i < 10; i++) {
+ MessageExtBrokerInner msg1 = buildMessage();
+ messageStore.putMessage(msg1);
+ }
+
+ List<SelectMappedBufferResult> bufferResultList = defaultMessageStore.getBulkCommitLogData(0, (int) defaultMessageStore.getMaxPhyOffset());
+ List<MessageExt> msgList = new ArrayList<>();
+ for (SelectMappedBufferResult bufferResult : bufferResultList) {
+ msgList.addAll(MessageDecoder.decodesBatch(bufferResult.getByteBuffer(), true, false, false));
+ bufferResult.release();
+ }
+
+ assertThat(msgList.size()).isEqualTo(10);
+ }
+
+ @Test
+ public void testPutLongMessage() throws Exception {
+ if (notExecuted()) {
+ return;
+ }
+ MessageExtBrokerInner messageExtBrokerInner = buildMessage();
+ CommitLog commitLog = ((RocksDBMessageStore) messageStore).getCommitLog();
+ MessageStoreConfig messageStoreConfig = ((RocksDBMessageStore) messageStore).getMessageStoreConfig();
+ MessageExtEncoder.PutMessageThreadLocal putMessageThreadLocal = commitLog.getPutMessageThreadLocal().get();
+
+ //body size, topic size, properties size exactly equal to max size
+ messageExtBrokerInner.setBody(new byte[messageStoreConfig.getMaxMessageSize()]);
+ messageExtBrokerInner.setTopic(new String(new byte[127]));
+ messageExtBrokerInner.setPropertiesString(new String(new byte[Short.MAX_VALUE]));
+ PutMessageResult encodeResult1 = putMessageThreadLocal.getEncoder().encode(messageExtBrokerInner);
+ assertTrue(encodeResult1 == null);
+
+ //body size exactly more than max message body size
+ messageExtBrokerInner.setBody(new byte[messageStoreConfig.getMaxMessageSize() + 1]);
+ PutMessageResult encodeResult2 = putMessageThreadLocal.getEncoder().encode(messageExtBrokerInner);
+ assertTrue(encodeResult2.getPutMessageStatus() == PutMessageStatus.MESSAGE_ILLEGAL);
+
+ //body size exactly equal to max message size
+ messageExtBrokerInner.setBody(new byte[messageStoreConfig.getMaxMessageSize() + 64 * 1024]);
+ PutMessageResult encodeResult3 = putMessageThreadLocal.getEncoder().encode(messageExtBrokerInner);
+ assertTrue(encodeResult3.getPutMessageStatus() == PutMessageStatus.MESSAGE_ILLEGAL);
+
+ //message properties length more than properties maxSize
+ messageExtBrokerInner.setBody(new byte[messageStoreConfig.getMaxMessageSize()]);
+ messageExtBrokerInner.setPropertiesString(new String(new byte[Short.MAX_VALUE + 1]));
+ PutMessageResult encodeResult4 = putMessageThreadLocal.getEncoder().encode(messageExtBrokerInner);
+ assertTrue(encodeResult4.getPutMessageStatus() == PutMessageStatus.PROPERTIES_SIZE_EXCEEDED);
+
+ //message length more than buffer length capacity
+ messageExtBrokerInner.setBody(new byte[messageStoreConfig.getMaxMessageSize()]);
+ messageExtBrokerInner.setTopic(new String(new byte[Short.MAX_VALUE]));
+ messageExtBrokerInner.setPropertiesString(new String(new byte[Short.MAX_VALUE]));
+ PutMessageResult encodeResult5 = putMessageThreadLocal.getEncoder().encode(messageExtBrokerInner);
+ assertTrue(encodeResult5.getPutMessageStatus() == PutMessageStatus.MESSAGE_ILLEGAL);
+ }
+
+ @Test
+ public void testDynamicMaxMessageSize() {
+ if (notExecuted()) {
+ return;
+ }
+ MessageExtBrokerInner messageExtBrokerInner = buildMessage();
+ MessageStoreConfig messageStoreConfig = ((RocksDBMessageStore) messageStore).getMessageStoreConfig();
+ int originMaxMessageSize = messageStoreConfig.getMaxMessageSize();
+
+ messageExtBrokerInner.setBody(new byte[originMaxMessageSize + 10]);
+ PutMessageResult putMessageResult = messageStore.putMessage(messageExtBrokerInner);
+ assertTrue(putMessageResult.getPutMessageStatus() == PutMessageStatus.MESSAGE_ILLEGAL);
+
+ int newMaxMessageSize = originMaxMessageSize + 10;
+ messageStoreConfig.setMaxMessageSize(newMaxMessageSize);
+ putMessageResult = messageStore.putMessage(messageExtBrokerInner);
+ assertTrue(putMessageResult.getPutMessageStatus() == PutMessageStatus.PUT_OK);
+
+ messageStoreConfig.setMaxMessageSize(10);
+ putMessageResult = messageStore.putMessage(messageExtBrokerInner);
+ assertTrue(putMessageResult.getPutMessageStatus() == PutMessageStatus.MESSAGE_ILLEGAL);
+
+ messageStoreConfig.setMaxMessageSize(originMaxMessageSize);
+ }
+
+ @Test
+ public void testDeleteTopics() {
+ if (notExecuted()) {
+ return;
+ }
+ MessageStoreConfig messageStoreConfig = messageStore.getMessageStoreConfig();
+ ConcurrentMap<String, ConcurrentMap<Integer, ConsumeQueueInterface>> consumeQueueTable =
+ ((RocksDBMessageStore) messageStore).getConsumeQueueTable();
+ for (int i = 0; i < 10; i++) {
+ ConcurrentMap<Integer, ConsumeQueueInterface> cqTable = new ConcurrentHashMap<>();
+ String topicName = "topic-" + i;
+ for (int j = 0; j < 4; j++) {
+ ConsumeQueue consumeQueue = new ConsumeQueue(topicName, j, messageStoreConfig.getStorePathRootDir(),
+ messageStoreConfig.getMappedFileSizeConsumeQueue(), messageStore);
+ cqTable.put(j, consumeQueue);
+ }
+ consumeQueueTable.put(topicName, cqTable);
+ }
+ Assert.assertEquals(consumeQueueTable.size(), 10);
+ HashSet<String> resultSet = Sets.newHashSet("topic-3", "topic-5");
+ messageStore.deleteTopics(Sets.difference(consumeQueueTable.keySet(), resultSet));
+ Assert.assertEquals(consumeQueueTable.size(), 2);
+ Assert.assertEquals(resultSet, consumeQueueTable.keySet());
+ }
+
+ @Test
+ public void testCleanUnusedTopic() {
+ if (notExecuted()) {
+ return;
+ }
+ MessageStoreConfig messageStoreConfig = messageStore.getMessageStoreConfig();
+ ConcurrentMap<String, ConcurrentMap<Integer, ConsumeQueueInterface>> consumeQueueTable =
+ ((RocksDBMessageStore) messageStore).getConsumeQueueTable();
+ for (int i = 0; i < 10; i++) {
+ ConcurrentMap<Integer, ConsumeQueueInterface> cqTable = new ConcurrentHashMap<>();
+ String topicName = "topic-" + i;
+ for (int j = 0; j < 4; j++) {
+ ConsumeQueue consumeQueue = new ConsumeQueue(topicName, j, messageStoreConfig.getStorePathRootDir(),
+ messageStoreConfig.getMappedFileSizeConsumeQueue(), messageStore);
+ cqTable.put(j, consumeQueue);
+ }
+ consumeQueueTable.put(topicName, cqTable);
+ }
+ Assert.assertEquals(consumeQueueTable.size(), 10);
+ HashSet<String> resultSet = Sets.newHashSet("topic-3", "topic-5");
+ messageStore.cleanUnusedTopic(resultSet);
+ Assert.assertEquals(consumeQueueTable.size(), 2);
+ Assert.assertEquals(resultSet, consumeQueueTable.keySet());
+ }
+
+ private class MyMessageArrivingListener implements MessageArrivingListener {
+ @Override
+ public void arriving(String topic, int queueId, long logicOffset, long tagsCode, long msgStoreTime,
+ byte[] filterBitMap, Map<String, String> properties) {
+ }
+ }
+
+ private boolean notExecuted() {
+ return MixAll.isMac();
+ }
+}
+
+
diff --git a/store/src/test/java/org/apache/rocketmq/store/StoreTestUtil.java b/store/src/test/java/org/apache/rocketmq/store/StoreTestUtil.java
index b2d99c3ed..17a2b5e19 100644
--- a/store/src/test/java/org/apache/rocketmq/store/StoreTestUtil.java
+++ b/store/src/test/java/org/apache/rocketmq/store/StoreTestUtil.java
@@ -38,11 +38,16 @@ public class StoreTestUtil {
public static boolean isCommitLogAvailable(DefaultMessageStore store) {
try {
+ Field serviceField = null;
+ if (store instanceof RocksDBMessageStore) {
+ serviceField = store.getClass().getSuperclass().getDeclaredField("reputMessageService");
+ } else {
+ serviceField = store.getClass().getDeclaredField("reputMessageService");
+ }
- Field serviceField = store.getClass().getDeclaredField("reputMessageService");
serviceField.setAccessible(true);
DefaultMessageStore.ReputMessageService reputService =
- (DefaultMessageStore.ReputMessageService) serviceField.get(store);
+ (DefaultMessageStore.ReputMessageService) serviceField.get(store);
Method method = DefaultMessageStore.ReputMessageService.class.getDeclaredMethod("isCommitLogAvailable");
method.setAccessible(true);
diff --git a/store/src/test/java/org/apache/rocketmq/store/ha/HAServerTest.java b/store/src/test/java/org/apache/rocketmq/store/ha/HAServerTest.java
index 54174ac16..fa8f41dbf 100644
--- a/store/src/test/java/org/apache/rocketmq/store/ha/HAServerTest.java
+++ b/store/src/test/java/org/apache/rocketmq/store/ha/HAServerTest.java
@@ -36,7 +36,7 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
-
+import org.rocksdb.RocksDBException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
import static org.mockito.ArgumentMatchers.anyLong;
@@ -114,7 +114,7 @@ public class HAServerTest {
}
@Test
- public void inSyncReplicasNums() throws IOException {
+ public void inSyncReplicasNums() throws IOException, RocksDBException {
DefaultMessageStore messageStore = mockMessageStore();
doReturn(123L).when(messageStore).getMaxPhyOffset();
doReturn(123L).when(messageStore).getMasterFlushedOffset();
@@ -150,7 +150,7 @@ public class HAServerTest {
}
@Test
- public void isSlaveOK() throws IOException {
+ public void isSlaveOK() throws IOException, RocksDBException {
DefaultMessageStore messageStore = mockMessageStore();
doReturn(123L).when(messageStore).getMaxPhyOffset();
doReturn(123L).when(messageStore).getMasterFlushedOffset();
@@ -175,7 +175,8 @@ public class HAServerTest {
}
@Test
- public void putRequest_SingleAck() throws IOException, ExecutionException, InterruptedException, TimeoutException {
+ public void putRequest_SingleAck()
+ throws IOException, ExecutionException, InterruptedException, TimeoutException, RocksDBException {
CommitLog.GroupCommitRequest request = new CommitLog.GroupCommitRequest(124, 4000, 1);
this.haService.putRequest(request);
@@ -192,7 +193,8 @@ public class HAServerTest {
}
@Test
- public void putRequest_MultipleAckAndRequests() throws IOException, ExecutionException, InterruptedException {
+ public void putRequest_MultipleAckAndRequests()
+ throws IOException, ExecutionException, InterruptedException, RocksDBException {
CommitLog.GroupCommitRequest oneAck = new CommitLog.GroupCommitRequest(124, 4000, 2);
this.haService.putRequest(oneAck);
@@ -218,7 +220,7 @@ public class HAServerTest {
}
@Test
- public void getPush2SlaveMaxOffset() throws IOException {
+ public void getPush2SlaveMaxOffset() throws IOException, RocksDBException {
DefaultMessageStore messageStore = mockMessageStore();
doReturn(123L).when(messageStore).getMaxPhyOffset();
doReturn(123L).when(messageStore).getMasterFlushedOffset();
@@ -256,7 +258,7 @@ public class HAServerTest {
this.haClientList.add(haClient);
}
- private DefaultMessageStore mockMessageStore() throws IOException {
+ private DefaultMessageStore mockMessageStore() throws IOException, RocksDBException {
DefaultMessageStore messageStore = mock(DefaultMessageStore.class);
BrokerConfig brokerConfig = mock(BrokerConfig.class);
diff --git a/store/src/test/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHATest.java b/store/src/test/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHATest.java
index 27dcff141..db5c5af4c 100644
--- a/store/src/test/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHATest.java
+++ b/store/src/test/java/org/apache/rocketmq/store/ha/autoswitch/AutoSwitchHATest.java
@@ -41,6 +41,7 @@ import org.junit.Assert;
import org.junit.Assume;
import org.junit.Ignore;
import org.junit.Test;
+import org.rocksdb.RocksDBException;
import java.io.File;
import java.net.InetAddress;
@@ -180,7 +181,7 @@ public class AutoSwitchHATest {
private boolean changeMasterAndPutMessage(DefaultMessageStore master, MessageStoreConfig masterConfig,
DefaultMessageStore slave, long slaveId, MessageStoreConfig slaveConfig, int epoch, String masterHaAddress,
- int totalPutMessageNums) {
+ int totalPutMessageNums) throws RocksDBException {
boolean flag = true;
// Change role
diff --git a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredMessageStore.java b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredMessageStore.java
index d7d13d61e..edaa5d19f 100644
--- a/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredMessageStore.java
+++ b/tieredstore/src/main/java/org/apache/rocketmq/tieredstore/TieredMessageStore.java
@@ -16,17 +16,14 @@
*/
package org.apache.rocketmq.tieredstore;
-import com.google.common.base.Stopwatch;
-import io.opentelemetry.api.common.Attributes;
-import io.opentelemetry.api.common.AttributesBuilder;
-import io.opentelemetry.api.metrics.Meter;
-import io.opentelemetry.sdk.metrics.InstrumentSelector;
-import io.opentelemetry.sdk.metrics.ViewBuilder;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
+
+import com.google.common.base.Stopwatch;
+
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.common.BoundaryType;
import org.apache.rocketmq.common.MixAll;
@@ -55,6 +52,12 @@ import org.apache.rocketmq.tieredstore.metrics.TieredStoreMetricsConstant;
import org.apache.rocketmq.tieredstore.metrics.TieredStoreMetricsManager;
import org.apache.rocketmq.tieredstore.util.TieredStoreUtil;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.common.AttributesBuilder;
+import io.opentelemetry.api.metrics.Meter;
+import io.opentelemetry.sdk.metrics.InstrumentSelector;
+import io.opentelemetry.sdk.metrics.ViewBuilder;
+
public class TieredMessageStore extends AbstractPluginMessageStore {
protected static final Logger logger = LoggerFactory.getLogger(TieredStoreUtil.TIERED_STORE_LOGGER_NAME);
diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/export/ExportMetadataInRocksDBCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/export/ExportMetadataInRocksDBCommand.java
index 2a7d3fba4..1ecb1fa2c 100644
--- a/tools/src/main/java/org/apache/rocketmq/tools/command/export/ExportMetadataInRocksDBCommand.java
+++ b/tools/src/main/java/org/apache/rocketmq/tools/command/export/ExportMetadataInRocksDBCommand.java
@@ -106,8 +106,8 @@ public class ExportMetadataInRocksDBCommand implements SubCommand {
final Map<String, JSONObject> jsonConfig = new HashMap<>();
final Map<String, JSONObject> configTable = new HashMap<>();
iterateKvStore(kvStore, (key, value) -> {
- final String configKey = new String(key, DataConverter.charset);
- final String configValue = new String(value, DataConverter.charset);
+ final String configKey = new String(key, DataConverter.CHARSET_UTF8);
+ final String configValue = new String(value, DataConverter.CHARSET_UTF8);
final JSONObject jsonObject = JSONObject.parseObject(configValue);
configTable.put(configKey, jsonObject);
}
@@ -120,8 +120,8 @@ public class ExportMetadataInRocksDBCommand implements SubCommand {
} else {
AtomicLong count = new AtomicLong(0);
iterateKvStore(kvStore, (key, value) -> {
- final String configKey = new String(key, DataConverter.charset);
- final String configValue = new String(value, DataConverter.charset);
+ final String configKey = new String(key, DataConverter.CHARSET_UTF8);
+ final String configValue = new String(value, DataConverter.CHARSET_UTF8);
System.out.printf("%d, Key: %s, Value: %s%n", count.incrementAndGet(), configKey, configValue);
});
}
diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/metadata/RocksDBConfigToJsonCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/metadata/RocksDBConfigToJsonCommand.java
new file mode 100644
index 000000000..b987ad873
--- /dev/null
+++ b/tools/src/main/java/org/apache/rocketmq/tools/command/metadata/RocksDBConfigToJsonCommand.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.tools.command.metadata;
+
+import com.alibaba.fastjson.JSONObject;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.rocketmq.common.config.RocksDBConfigManager;
+import org.apache.rocketmq.common.utils.DataConverter;
+import org.apache.rocketmq.remoting.RPCHook;
+import org.apache.rocketmq.tools.command.SubCommand;
+import org.apache.rocketmq.tools.command.SubCommandException;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+public class RocksDBConfigToJsonCommand implements SubCommand {
+ private static final String TOPICS_JSON_CONFIG = "topics";
+ private static final String SUBSCRIPTION_GROUP_JSON_CONFIG = "subscriptionGroups";
+
+ @Override
+ public String commandName() {
+ return "rocksDBConfigToJson";
+ }
+
+ @Override
+ public String commandDesc() {
+ return "Convert RocksDB kv config (topics/subscriptionGroups) to json";
+ }
+
+ @Override
+ public Options buildCommandlineOptions(Options options) {
+ Option pathOption = new Option("p", "path", true,
+ "Absolute path to the metadata directory");
+ pathOption.setRequired(true);
+ options.addOption(pathOption);
+
+ Option configTypeOption = new Option("t", "configType", true, "Name of kv config, e.g. " +
+ "topics/subscriptionGroups");
+ configTypeOption.setRequired(true);
+ options.addOption(configTypeOption);
+
+ return options;
+ }
+
+ @Override
+ public void execute(CommandLine commandLine, Options options, RPCHook rpcHook) throws SubCommandException {
+ String path = commandLine.getOptionValue("path").trim();
+ if (StringUtils.isEmpty(path) || !new File(path).exists()) {
+ System.out.print("Rocksdb path is invalid.\n");
+ return;
+ }
+
+ String configType = commandLine.getOptionValue("configType").trim().toLowerCase();
+
+ final long memTableFlushInterval = 60 * 60 * 1000L;
+ RocksDBConfigManager kvConfigManager = new RocksDBConfigManager(memTableFlushInterval);
+ try {
+ if (TOPICS_JSON_CONFIG.toLowerCase().equals(configType)) {
+ // for topics.json
+ final Map<String, JSONObject> topicsJsonConfig = new HashMap<>();
+ final Map<String, JSONObject> topicConfigTable = new HashMap<>();
+ boolean isLoad = kvConfigManager.load(path, (key, value) -> {
+ final String topic = new String(key, DataConverter.CHARSET_UTF8);
+ final String topicConfig = new String(value, DataConverter.CHARSET_UTF8);
+ final JSONObject jsonObject = JSONObject.parseObject(topicConfig);
+ topicConfigTable.put(topic, jsonObject);
+ });
+
+ if (isLoad) {
+ topicsJsonConfig.put("topicConfigTable", (JSONObject) JSONObject.toJSON(topicConfigTable));
+ final String topicsJsonStr = JSONObject.toJSONString(topicsJsonConfig, true);
+ System.out.print(topicsJsonStr + "\n");
+ return;
+ }
+ }
+ if (SUBSCRIPTION_GROUP_JSON_CONFIG.toLowerCase().equals(configType)) {
+ // for subscriptionGroup.json
+ final Map<String, JSONObject> subscriptionGroupJsonConfig = new HashMap<>();
+ final Map<String, JSONObject> subscriptionGroupTable = new HashMap<>();
+ boolean isLoad = kvConfigManager.load(path, (key, value) -> {
+ final String subscriptionGroup = new String(key, DataConverter.CHARSET_UTF8);
+ final String subscriptionGroupConfig = new String(value, DataConverter.CHARSET_UTF8);
+ final JSONObject jsonObject = JSONObject.parseObject(subscriptionGroupConfig);
+ subscriptionGroupTable.put(subscriptionGroup, jsonObject);
+ });
+
+ if (isLoad) {
+ subscriptionGroupJsonConfig.put("subscriptionGroupTable",
+ (JSONObject) JSONObject.toJSON(subscriptionGroupTable));
+ final String subscriptionGroupJsonStr = JSONObject.toJSONString(subscriptionGroupJsonConfig, true);
+ System.out.print(subscriptionGroupJsonStr + "\n");
+ return;
+ }
+ }
+ System.out.print("Config type was not recognized, configType=" + configType + "\n");
+ } finally {
+ kvConfigManager.stop();
+ }
+ }
+}
--
2.32.0.windows.2