From 0c6097bb44156f94d2019f8790651204c90db25c Mon Sep 17 00:00:00 2001 From: shizhili Date: Fri, 8 Dec 2023 09:37:11 +0800 Subject: [PATCH] backport support kv storage for consumequeue --- ...-Support-KV-Storage-for-ConsumeQueue.patch | 29480 ++++++++++++++++ rocketmq.spec | 6 +- 2 files changed, 29485 insertions(+), 1 deletion(-) create mode 100644 patch022-backport-Support-KV-Storage-for-ConsumeQueue.patch diff --git a/patch022-backport-Support-KV-Storage-for-ConsumeQueue.patch b/patch022-backport-Support-KV-Storage-for-ConsumeQueue.patch new file mode 100644 index 0000000..a4a09ae --- /dev/null +++ b/patch022-backport-Support-KV-Storage-for-ConsumeQueue.patch @@ -0,0 +1,29480 @@ +From 0a046b85611bdc93c529650dc951f4cabbb61db9 Mon Sep 17 00:00:00 2001 +From: zhiliatom +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 syncStateSet) { ++ final Integer syncStateSetEpoch, final Set 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 syncStateSet) { ++ public void changeToMaster(final int newMasterEpoch, final int syncStateSetEpoch, final Set 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 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 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 @@ + rocketmq-logback-classic + + +- io.github.aliyunmq ++ org.apache.rocketmq + rocketmq-rocksdb + + +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 fileMetaDataList = this.db.getLiveFilesMetaData(); +- if (fileMetaDataList == null || fileMetaDataList.isEmpty()) { +- return; +- } +- +- List defaultLiveFileDataList = Lists.newArrayList(); +- List 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 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 ++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 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 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 queueFlatFileMap; +++ private final ConcurrentMap 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> 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> futures = new ArrayList<>(); +++ metadataStore.iterateTopic(topicMetadata -> { +++ try { +++ semaphore.acquire(); +++ CompletableFuture 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 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 ++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 ++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 @@ ++ test ++ ++ +++ +++ +++ +++ +++ maven-surefire-plugin +++ ${maven-surefire-plugin.version} +++ +++ 1 +++ false +++ +++ +++ +++ ++ ++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; ++ ++- ++ /** ++ *

In this class, we'll test the following scenarios, each containing several consecutive operations on ACL, ++ *

like updating and deleting ACL, changing config files and checking validations. ++@@ -52,9 +50,6 @@ import java.util.List; ++ *

Case 2: Only conf/acl/plain_acl.yml exists; ++ *

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 ++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 ++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: 十真 ++--- ++ .../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 ++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 ++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 ++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 ++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 ++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 { ++ // ++ Map> 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 ++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 ++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 subList) { ++ boolean updated = false; ++- +++ Set 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> it = this.subscriptionTable.entrySet().iterator(); ++ while (it.hasNext()) { ++ Entry 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 ++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 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 selectorViewPair : RemotingMetricsManager.getMetricsView()) { +++ ViewBuilder viewBuilder = selectorViewPair.getObject2(); +++ SdkMeterProviderUtil.setCardinalityLimit(viewBuilder, brokerConfig.getMetricsOtelCardinalityLimit()); +++ providerBuilder.registerView(selectorViewPair.getObject1(), viewBuilder.build()); ++ } ++ ++- for (Pair selectorViewPair : messageStore.getMetricsView()) { ++- providerBuilder.registerView(selectorViewPair.getObject1(), selectorViewPair.getObject2()); +++ for (Pair selectorViewPair : messageStore.getMetricsView()) { +++ ViewBuilder viewBuilder = selectorViewPair.getObject2(); +++ SdkMeterProviderUtil.setCardinalityLimit(viewBuilder, brokerConfig.getMetricsOtelCardinalityLimit()); +++ providerBuilder.registerView(selectorViewPair.getObject1(), viewBuilder.build()); ++ } ++ ++- for (Pair selectorViewPair : PopMetricsManager.getMetricsView()) { ++- providerBuilder.registerView(selectorViewPair.getObject1(), selectorViewPair.getObject2()); +++ for (Pair 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> getMetricsView() { +++ public static List> getMetricsView() { ++ List 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 @@ ++ ++ ++ ++- +++ ++ ++ brokerContainerLogDir ++ ${file.separator} ++ ++ ++- ++ ++- ${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 ++ ++ true ++ ++ ++- ${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 ++ ++ 1 ++- 10 +++ 3 ++ ++ ++- 500MB +++ 512MB ++ ++ ++ %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n ++@@ -588,6 +588,9 @@ ++ ++ ++ +++ +++ +++ ++ ++ ++ ++@@ -670,7 +673,7 @@ ++ ++ ++ ++- +++ ++ ++ ++ ++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 @@ ++ 2.9.3 ++ 5.3.27 ++ 3.0.0 ++- 1.26.0 ++- 1.26.0-alpha +++ 1.29.0 +++ 1.29.0-alpha ++ 2.0.6 ++ 2.20.29 ++ 1.0.3 ++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> getMetricsView() { +++ public static List> getMetricsView() { ++ List 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> getMetricsView() { +++ public List> 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> getMetricsView(); +++ List> 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> getMetricsView() { +++ public static List> 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> getMetricsView() { +++ public List> 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> getMetricsView() { ++- List> res = super.getMetricsView(); +++ public List> getMetricsView() { +++ List> 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> getMetricsView() { ++- ArrayList> res = new ArrayList<>(); +++ public static List> getMetricsView() { +++ ArrayList> 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 ++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 ++--- ++ .../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 ++--- ++ .../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 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 ++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 ++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 ++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 extraInfoList +++ ) throws RemotingException, MQBrokerException, InterruptedException { +++ String brokerName = null; +++ Map 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 batchAckMessageAsync(String brokerAddr, String topic, String consumerGroup, +++ List extraInfoList) { +++ CompletableFuture 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 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 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 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 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 popMessageAsync() { +++ return client.popMessageAsync( +++ brokerAddr, messageQueue, Duration.ofSeconds(3).toMillis(), 30, group, 3000, false, +++ ConsumeInitMode.MIN, false, ExpressionType.TAG, "*"); +++ } +++ +++ private CompletableFuture 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 ++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 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 ++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 ++ ++* refactor: fix style ++ ++--------- ++ ++Signed-off-by: Ziy1-Tan ++Co-authored-by: Ziy1-Tan ++--- ++ .../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 topicsJsonConfig = new HashMap<>(); +++ final Map 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 subscriptionGroupJsonConfig = new HashMap<>(); +++ final Map 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 ++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>> queryAsync(String topic, String key, long beginTime, long endTime) { +++ public CompletableFuture>> queryAsync(String topic, String key, long beginTime, +++ long endTime) { ++ int hashCode = indexKeyHashMethod(buildKey(topic, key)); ++ int slotPosition = hashCode % maxHashSlotNum; ++ List 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> 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> 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> indexList = indexFile.queryAsync(mq.getTopic(), "key1", 1300, 1300).join(); +++ // do not do schedule task here +++ TieredStoreExecutor.shutdown(); +++ List> 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 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 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?= ++ ++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: 十真 ++--- ++ .../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 ++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 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 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 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 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 ++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 batchAckMessageAsync( +++ String brokerAddr, +++ String topic, +++ String consumerGroup, +++ List extraInfoList, +++ long timeoutMillis +++ ) { +++ CompletableFuture 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 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[] 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 ackMessageInBatch(ProxyContext ctx, String group, String topic, AckMessageRequest request) { +++ List 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 responseCodes = new HashSet<>(); ++- List entryList = new ArrayList<>(); ++- for (CompletableFuture 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 processAckMessage(ProxyContext ctx, AckMessageRequest request, +++ protected CompletableFuture ackMessageOneByOne(ProxyContext ctx, String group, String topic, AckMessageRequest request) { +++ CompletableFuture resultFuture = new CompletableFuture<>(); +++ CompletableFuture[] 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 responseCodes = new HashSet<>(); +++ List entryList = new ArrayList<>(); +++ for (CompletableFuture 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 processAckMessage(ProxyContext ctx, String group, String topic, AckMessageRequest request, ++ AckMessageEntry ackMessageEntry) { ++ CompletableFuture 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 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 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> batchAckMessage( +++ ProxyContext ctx, +++ List handleMessageList, +++ String consumerGroup, +++ String topic, +++ long timeoutMillis +++ ) { +++ CompletableFuture> future = new CompletableFuture<>(); +++ try { +++ List batchAckResultList = new ArrayList<>(handleMessageList.size()); +++ Map> brokerHandleListMap = new HashMap<>(); +++ +++ for (ReceiptHandleMessage handleMessage : handleMessageList) { +++ if (handleMessage.getReceiptHandle().isExpired()) { +++ batchAckResultList.add(new BatchAckResult(handleMessage, EXPIRED_HANDLE_PROXY_EXCEPTION)); +++ continue; +++ } +++ List brokerHandleList = brokerHandleListMap.computeIfAbsent(handleMessage.getReceiptHandle().getBrokerName(), key -> new ArrayList<>()); +++ brokerHandleList.add(handleMessage); +++ } +++ +++ if (brokerHandleListMap.isEmpty()) { +++ return FutureUtils.addExecutor(CompletableFuture.completedFuture(batchAckResultList), this.executor); +++ } +++ Set>> brokerHandleListMapEntrySet = brokerHandleListMap.entrySet(); +++ CompletableFuture>[] futures = new CompletableFuture[brokerHandleListMapEntrySet.size()]; +++ int futureIndex = 0; +++ for (Map.Entry> 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> resultFuture : futures) { +++ batchAckResultList.addAll(resultFuture.join()); +++ } +++ future.complete(batchAckResultList); +++ }); +++ } catch (Throwable t) { +++ future.completeExceptionally(t); +++ } +++ return FutureUtils.addExecutor(future, this.executor); +++ } +++ +++ protected CompletableFuture> processBrokerHandle(ProxyContext ctx, String consumerGroup, String topic, List handleMessageList, long timeoutMillis) { +++ return this.serviceManager.getMessageService().batchAckMessage(ctx, handleMessageList, consumerGroup, topic, timeoutMillis) +++ .thenApply(result -> { +++ List results = new ArrayList<>(); +++ for (ReceiptHandleMessage handleMessage : handleMessageList) { +++ results.add(new BatchAckResult(handleMessage, result)); +++ } +++ return results; +++ }) +++ .exceptionally(throwable -> { +++ List 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 changeInvisibleTime(ProxyContext ctx, ReceiptHandle handle, ++ String messageId, String groupName, String topicName, long invisibleTime, long timeoutMillis) { ++ CompletableFuture 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> batchAckMessage(ProxyContext ctx, +++ List handleMessageList, String consumerGroup, String topic, long timeoutMillis) { +++ return this.consumerProcessor.batchAckMessage(ctx, handleMessageList, consumerGroup, topic, timeoutMillis); +++ } +++ ++ @Override ++ public CompletableFuture 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> batchAckMessage( +++ ProxyContext ctx, +++ List handleMessageList, +++ String consumerGroup, +++ String topic +++ ) { +++ return batchAckMessage(ctx, handleMessageList, consumerGroup, topic, DEFAULT_TIMEOUT_MILLS); +++ } +++ +++ CompletableFuture> batchAckMessage( +++ ProxyContext ctx, +++ List handleMessageList, +++ String consumerGroup, +++ String topic, +++ long timeoutMillis +++ ); +++ ++ default CompletableFuture 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 batchAckMessage(ProxyContext ctx, List handleList, String consumerGroup, +++ String topic, long timeoutMillis) { +++ List 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 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 batchAckMessage(ProxyContext ctx, List 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 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 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 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 batchAckMessage( +++ ProxyContext ctx, +++ List handleList, +++ String consumerGroup, +++ String topic, +++ long timeoutMillis +++ ); +++ ++ CompletableFuture 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>>) invocation -> { +++ List receiptHandleMessageList = invocation.getArgument(1, List.class); +++ List 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 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 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 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 receiptHandleMessageList = new ArrayList<>(); +++ receiptHandleMessageList.add(new ReceiptHandleMessage(expireHandle, expireMessage.getMsgId())); +++ List broker1Msg = new ArrayList<>(); +++ List 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>) invocation -> { +++ List 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 batchAckResultList = this.consumerProcessor.batchAckMessage(createContext(), receiptHandleMessageList, CONSUMER_GROUP, TOPIC, 3000).get(); +++ assertEquals(receiptHandleMessageList.size(), batchAckResultList.size()); +++ +++ // check ackResult for each msg +++ Map 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) 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 ++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 ' 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 ++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 ++ ++* refactor: fix style ++ ++--------- ++ ++Signed-off-by: Ziy1-Tan ++Co-authored-by: Ziy1-Tan ++--- ++ .../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 topicsJsonConfig = new HashMap<>(); +++ final Map 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 subscriptionGroupJsonConfig = new HashMap<>(); +++ final Map 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 ++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>> queryAsync(String topic, String key, long beginTime, long endTime) { +++ public CompletableFuture>> queryAsync(String topic, String key, long beginTime, +++ long endTime) { ++ int hashCode = indexKeyHashMethod(buildKey(topic, key)); ++ int slotPosition = hashCode % maxHashSlotNum; ++ List 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> 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> 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> indexList = indexFile.queryAsync(mq.getTopic(), "key1", 1300, 1300).join(); +++ // do not do schedule task here +++ TieredStoreExecutor.shutdown(); +++ List> 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 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 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?= ++ ++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: 十真 ++--- ++ .../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 ++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 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 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 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 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 ++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 batchAckMessageAsync( +++ String brokerAddr, +++ String topic, +++ String consumerGroup, +++ List extraInfoList, +++ long timeoutMillis +++ ) { +++ CompletableFuture 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 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[] 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 ackMessageInBatch(ProxyContext ctx, String group, String topic, AckMessageRequest request) { +++ List 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 responseCodes = new HashSet<>(); ++- List entryList = new ArrayList<>(); ++- for (CompletableFuture 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 processAckMessage(ProxyContext ctx, AckMessageRequest request, +++ protected CompletableFuture ackMessageOneByOne(ProxyContext ctx, String group, String topic, AckMessageRequest request) { +++ CompletableFuture resultFuture = new CompletableFuture<>(); +++ CompletableFuture[] 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 responseCodes = new HashSet<>(); +++ List entryList = new ArrayList<>(); +++ for (CompletableFuture 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 processAckMessage(ProxyContext ctx, String group, String topic, AckMessageRequest request, ++ AckMessageEntry ackMessageEntry) { ++ CompletableFuture 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 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 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> batchAckMessage( +++ ProxyContext ctx, +++ List handleMessageList, +++ String consumerGroup, +++ String topic, +++ long timeoutMillis +++ ) { +++ CompletableFuture> future = new CompletableFuture<>(); +++ try { +++ List batchAckResultList = new ArrayList<>(handleMessageList.size()); +++ Map> brokerHandleListMap = new HashMap<>(); +++ +++ for (ReceiptHandleMessage handleMessage : handleMessageList) { +++ if (handleMessage.getReceiptHandle().isExpired()) { +++ batchAckResultList.add(new BatchAckResult(handleMessage, EXPIRED_HANDLE_PROXY_EXCEPTION)); +++ continue; +++ } +++ List brokerHandleList = brokerHandleListMap.computeIfAbsent(handleMessage.getReceiptHandle().getBrokerName(), key -> new ArrayList<>()); +++ brokerHandleList.add(handleMessage); +++ } +++ +++ if (brokerHandleListMap.isEmpty()) { +++ return FutureUtils.addExecutor(CompletableFuture.completedFuture(batchAckResultList), this.executor); +++ } +++ Set>> brokerHandleListMapEntrySet = brokerHandleListMap.entrySet(); +++ CompletableFuture>[] futures = new CompletableFuture[brokerHandleListMapEntrySet.size()]; +++ int futureIndex = 0; +++ for (Map.Entry> 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> resultFuture : futures) { +++ batchAckResultList.addAll(resultFuture.join()); +++ } +++ future.complete(batchAckResultList); +++ }); +++ } catch (Throwable t) { +++ future.completeExceptionally(t); +++ } +++ return FutureUtils.addExecutor(future, this.executor); +++ } +++ +++ protected CompletableFuture> processBrokerHandle(ProxyContext ctx, String consumerGroup, String topic, List handleMessageList, long timeoutMillis) { +++ return this.serviceManager.getMessageService().batchAckMessage(ctx, handleMessageList, consumerGroup, topic, timeoutMillis) +++ .thenApply(result -> { +++ List results = new ArrayList<>(); +++ for (ReceiptHandleMessage handleMessage : handleMessageList) { +++ results.add(new BatchAckResult(handleMessage, result)); +++ } +++ return results; +++ }) +++ .exceptionally(throwable -> { +++ List 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 changeInvisibleTime(ProxyContext ctx, ReceiptHandle handle, ++ String messageId, String groupName, String topicName, long invisibleTime, long timeoutMillis) { ++ CompletableFuture 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> batchAckMessage(ProxyContext ctx, +++ List handleMessageList, String consumerGroup, String topic, long timeoutMillis) { +++ return this.consumerProcessor.batchAckMessage(ctx, handleMessageList, consumerGroup, topic, timeoutMillis); +++ } +++ ++ @Override ++ public CompletableFuture 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> batchAckMessage( +++ ProxyContext ctx, +++ List handleMessageList, +++ String consumerGroup, +++ String topic +++ ) { +++ return batchAckMessage(ctx, handleMessageList, consumerGroup, topic, DEFAULT_TIMEOUT_MILLS); +++ } +++ +++ CompletableFuture> batchAckMessage( +++ ProxyContext ctx, +++ List handleMessageList, +++ String consumerGroup, +++ String topic, +++ long timeoutMillis +++ ); +++ ++ default CompletableFuture 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 batchAckMessage(ProxyContext ctx, List handleList, String consumerGroup, +++ String topic, long timeoutMillis) { +++ List 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 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 batchAckMessage(ProxyContext ctx, List 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 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 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 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 batchAckMessage( +++ ProxyContext ctx, +++ List handleList, +++ String consumerGroup, +++ String topic, +++ long timeoutMillis +++ ); +++ ++ CompletableFuture 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>>) invocation -> { +++ List receiptHandleMessageList = invocation.getArgument(1, List.class); +++ List 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 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 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 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 receiptHandleMessageList = new ArrayList<>(); +++ receiptHandleMessageList.add(new ReceiptHandleMessage(expireHandle, expireMessage.getMsgId())); +++ List broker1Msg = new ArrayList<>(); +++ List 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>) invocation -> { +++ List 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 batchAckResultList = this.consumerProcessor.batchAckMessage(createContext(), receiptHandleMessageList, CONSUMER_GROUP, TOPIC, 3000).get(); +++ assertEquals(receiptHandleMessageList.size(), batchAckResultList.size()); +++ +++ // check ackResult for each msg +++ Map 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) 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 ++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() { ++- @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() { +++ @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 ++Date: Fri, 25 Aug 2023 11:17:23 +0800 ++Subject: [PATCH 2/6] [ISSUE #7250] Beautify command rocksDBConfigToJson output ++ ++Co-authored-by: Ziy1-Tan ++--- ++ .../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 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 ++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 ++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 jsonConfig = new HashMap<>(); +++ final Map 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 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 topicsJsonConfig = new HashMap<>(); ++- final Map 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 subscriptionGroupJsonConfig = new HashMap<>(); ++- final Map 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 ++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 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 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 ++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 brokerSupportV2HeartbeatSet = new HashSet(); ++ private final ConcurrentMap 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 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 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 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 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 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 { ++- 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 { ++- private final ConcurrentHashMap faultItemTable = new ConcurrentHashMap<>(16); +++ private final static Logger log = LoggerFactory.getLogger(MQFaultStrategy.class); +++ private final ConcurrentHashMap faultItemTable = new ConcurrentHashMap(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 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 ++ 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 ++ @Override ++ public String pickOneAtLeast() { ++ final Enumeration elements = this.faultItemTable.elements(); ++- List tmpList = new LinkedList<>(); +++ List tmpList = new LinkedList(); ++ 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 { +++ public void setDetectInterval(final int detectInterval) { +++ this.detectInterval = detectInterval; +++ } +++ +++ public class FaultItem implements Comparable { ++ 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 ++ ++ @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 latencyFaultTolerance = new LatencyFaultToleranceImpl(); +++ private LatencyFaultTolerance 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 threadBrokerFilter = new ThreadLocal() { +++ @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 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 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 keys = new ArrayList<>(); ++- ++- public List getKeys() { ++- return keys; ++- } ++- ++- public void setKeys(List 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 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> sendMessage(ProxyContext ctx, QueueSelector queueSelector, ++ String producerGroup, int sysFlag, List messageList, long timeoutMillis) { ++ CompletableFuture> 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 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 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 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 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 transferAddressableQueues(List 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 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 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 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 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 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 ++--- ++ .../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 future = flatFile.getInflightRequest(group, queueOffset, maxCount) ++- .getFuture(queueOffset); +++ CompletableFuture 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 resultFuture; ++ synchronized (flatFile) { ++ int batchSize = maxCount * storeConfig.getReadAheadMinFactor(); ++@@ -453,42 +458,42 @@ public class TieredMessageFetcher implements MessageStoreFetcher { ++ public CompletableFuture 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 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 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 ++ 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 uploadBufferList = new ArrayList<>(); +++ private List bufferList = new ArrayList<>(); +++ private FileSegmentInputStream fileSegmentInputStream; ++ private CompletableFuture flightCommitRequest = CompletableFuture.completedFuture(false); ++ ++ public TieredFileSegment(TieredMessageStoreConfig storeConfig, ++@@ -75,21 +73,13 @@ public abstract class TieredFileSegment implements Comparable ++ 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 ++ this.appendPosition = pos; ++ } ++ ++- private List rollingUploadBuffer() { +++ private List borrowBuffer() { ++ bufferLock.lock(); ++ try { ++- List tmp = uploadBufferList; ++- uploadBufferList = new ArrayList<>(); +++ List tmp = bufferList; +++ bufferList = new ArrayList<>(); ++ return tmp; ++ } finally { ++ bufferLock.unlock(); ++ } ++ } ++ ++- private void sendBackBuffer(TieredFileSegmentInputStream inputStream) { ++- bufferLock.lock(); ++- try { ++- List 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 ++ 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 ++ 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 ++ return appendPosition; ++ } ++ ++- @VisibleForTesting ++ public void setAppendPosition(long appendPosition) { ++ this.appendPosition = appendPosition; ++ } ++@@ -333,6 +318,8 @@ public abstract class TieredFileSegment implements Comparable ++ 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 ++ 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 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 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 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 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, true if data successfully write; false otherwise ++ */ ++- CompletableFuture commit0(TieredFileSegmentInputStream inputStream,long position, int length, boolean append); +++ CompletableFuture 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 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 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 uploadBufferList; +++ protected final List bufferList; ++ ++ /** ++ * total remaining of bytebuffer list ++@@ -65,13 +66,13 @@ public class TieredFileSegmentInputStream extends InputStream { ++ ++ private int markReadPosInCurBuffer = -1; ++ ++- public TieredFileSegmentInputStream(FileSegmentType fileType, List uploadBufferList, ++- int contentLength) { +++ public FileSegmentInputStream( +++ FileSegmentType fileType, List 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 getUploadBufferList() { ++- return uploadBufferList; +++ public List 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 uploadBufferList, ByteBuffer codaBuffer, int contentLength) { +++ public static FileSegmentInputStream build( +++ FileSegmentType fileType, long offset, List 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 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 getUploadBufferList() { +++ public List 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 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 constructor, +++ private void verifyReadAndReset(ByteBuffer expectedByteBuffer, Supplier 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 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 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 commit0(TieredFileSegmentInputStream inputStream, long position, int length, +++ public CompletableFuture 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 ++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 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 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 ++--- ++ .../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 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 ++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 ++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 { ++ * @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 ++ 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 ++ @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 ++ this.faultItemTable.remove(name); ++ } ++ +++ public boolean isStartDetectorEnable() { +++ return startDetectorEnable; +++ } +++ +++ public void setStartDetectorEnable(boolean startDetectorEnable) { +++ this.startDetectorEnable = startDetectorEnable; +++ } ++ @Override ++ public String pickOneAtLeast() { ++ final Enumeration 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 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> 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 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 workQueue) { ++- super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); ++- } ++- ++- public BrokerFixedThreadPoolExecutor(final int corePoolSize, final int maximumPoolSize, final long keepAliveTime, ++- final TimeUnit unit, ++- final BlockingQueue 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 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 workQueue, final ThreadFactory threadFactory, ++- final RejectedExecutionHandler handler) { ++- super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler); ++- } ++- ++- @Override ++- protected RunnableFuture 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 extends FutureTask { ++- private final Runnable runnable; ++- ++- public FutureTaskExt(final Callable 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 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 workQueue, ThreadFactory threadFactory, +++ BlockingQueue 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 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 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 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 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 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 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 RunnableFuture newTaskFor(final Runnable runnable, final T value) { ++- return new FutureTaskExt(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 RunnableFuture 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 RunnableFuture 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 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 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 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 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 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 connectionCaughtUpTimeTable = new ConcurrentHashMap<>(); ++ private final List>> syncStateSetChangedListeners = new ArrayList<>(); ++ private final Set 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 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 ++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 ++--- ++ .../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 ++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 ++--- ++ .../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 topicConfigMap = this.getTopicConfigManager().getTopicConfigTable(); +++ ConcurrentHashMap 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 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 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 topicConfigTable) { +++ return buildSerializeWrapper(topicConfigTable, Maps.newHashMap()); +++ } +++ +++ public TopicConfigAndMappingSerializeWrapper buildSerializeWrapper( +++ final ConcurrentMap topicConfigTable, +++ final Map 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 ++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 ++--- ++ .../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 ++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 ++--- ++ .../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 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 ++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 ++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 ++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 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 @@ ++ ++ ++ +++ +++ +++ +++ +++ ++ ++ ++ ++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 @@ ++ io.opentelemetry ++ opentelemetry-sdk ++ +++ +++ io.opentelemetry +++ opentelemetry-exporter-logging-otlp +++ ++ ++ io.grpc ++ grpc-stub ++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 @@ ++ opentelemetry-sdk ++ ${opentelemetry.version} ++ +++ +++ io.opentelemetry +++ opentelemetry-exporter-logging-otlp +++ ${opentelemetry.version} +++ ++ ++ org.slf4j ++ jul-to-slf4j ++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 @@ ++ ++ ++ +++ +++ +++ +++ +++ ++ ++ ++ ++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 ++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 @@ ++ ++ org.apache.rocketmq ++ rocketmq-all ++- 5.1.4-SNAPSHOT +++ 5.1.4 ++ ++ rocketmq-acl ++ rocketmq-acl ${project.version} ++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 @@ ++ ++ org.apache.rocketmq ++ rocketmq-all ++- 5.1.4-SNAPSHOT +++ 5.1.4 ++ ++ ++ 4.0.0 ++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 @@ ++ ++ org.apache.rocketmq ++ rocketmq-all ++- 5.1.4-SNAPSHOT +++ 5.1.4 ++ ++ ++ 4.0.0 ++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 @@ ++ ++ org.apache.rocketmq ++ rocketmq-all ++- 5.1.4-SNAPSHOT +++ 5.1.4 ++ ++ ++ 4.0.0 ++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 @@ ++ ++ org.apache.rocketmq ++ rocketmq-all ++- 5.1.4-SNAPSHOT +++ 5.1.4 ++ ++ ++ 4.0.0 ++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 @@ ++ ++ rocketmq-all ++ org.apache.rocketmq ++- 5.1.4-SNAPSHOT +++ 5.1.4 ++ ++ 4.0.0 ++ jar ++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 @@ ++ ++ org.apache.rocketmq ++ rocketmq-all ++- 5.1.4-SNAPSHOT +++ 5.1.4 ++ ++ rocketmq-distribution ++ rocketmq-distribution ${project.version} ++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 @@ ++ ++ rocketmq-all ++ org.apache.rocketmq ++- 5.1.4-SNAPSHOT +++ 5.1.4 ++ ++ ++ 4.0.0 ++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 @@ ++ ++ rocketmq-all ++ org.apache.rocketmq ++- 5.1.4-SNAPSHOT +++ 5.1.4 ++ ++ ++ 4.0.0 ++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 @@ ++ ++ org.apache.rocketmq ++ rocketmq-all ++- 5.1.4-SNAPSHOT +++ 5.1.4 ++ ++ ++ 4.0.0 ++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 @@ ++ ++ rocketmq-all ++ org.apache.rocketmq ++- 5.1.4-SNAPSHOT +++ 5.1.4 ++ ++ ++ 4.0.0 ++diff --git a/pom.xml b/pom.xml ++index 4b382c6da..0e1d04f15 100644 ++--- a/pom.xml +++++ b/pom.xml ++@@ -28,7 +28,7 @@ ++ 2012 ++ org.apache.rocketmq ++ rocketmq-all ++- 5.1.4-SNAPSHOT +++ 5.1.4 ++ pom ++ Apache RocketMQ ${project.version} ++ http://rocketmq.apache.org/ ++@@ -37,7 +37,7 @@ ++ git@github.com:apache/rocketmq.git ++ scm:git:git@github.com:apache/rocketmq.git ++ scm:git:git@github.com:apache/rocketmq.git ++- HEAD +++ rocketmq-all-5.1.4 ++ ++ ++ ++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 @@ ++ ++ rocketmq-all ++ org.apache.rocketmq ++- 5.1.4-SNAPSHOT +++ 5.1.4 ++ ++ ++ 4.0.0 ++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 @@ ++ ++ org.apache.rocketmq ++ rocketmq-all ++- 5.1.4-SNAPSHOT +++ 5.1.4 ++ ++ ++ 4.0.0 ++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 @@ ++ ++ org.apache.rocketmq ++ rocketmq-all ++- 5.1.4-SNAPSHOT +++ 5.1.4 ++ ++ ++ 4.0.0 ++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 @@ ++ ++ org.apache.rocketmq ++ rocketmq-all ++- 5.1.4-SNAPSHOT +++ 5.1.4 ++ ++ ++ 4.0.0 ++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 @@ ++ ++ rocketmq-all ++ org.apache.rocketmq ++- 5.1.4-SNAPSHOT +++ 5.1.4 ++ ++ ++ 4.0.0 ++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 @@ ++ ++ org.apache.rocketmq ++ rocketmq-all ++- 5.1.4-SNAPSHOT +++ 5.1.4 ++ ++ ++ 4.0.0 ++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 @@ ++ ++ org.apache.rocketmq ++ rocketmq-all ++- 5.1.4-SNAPSHOT +++ 5.1.4 ++ ++ ++ 4.0.0 ++-- ++2.32.0.windows.2 ++ ++ ++From 73b3fde83765e066541e3455cd1e6604292a9b7c Mon Sep 17 00:00:00 2001 ++From: lk ++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 @@ ++ ++ org.apache.rocketmq ++ rocketmq-all ++- 5.1.4 +++ 5.1.5-SNAPSHOT ++ ++ rocketmq-acl ++ rocketmq-acl ${project.version} ++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 @@ ++ ++ org.apache.rocketmq ++ rocketmq-all ++- 5.1.4 +++ 5.1.5-SNAPSHOT ++ ++ ++ 4.0.0 ++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 @@ ++ ++ org.apache.rocketmq ++ rocketmq-all ++- 5.1.4 +++ 5.1.5-SNAPSHOT ++ ++ ++ 4.0.0 ++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 @@ ++ ++ org.apache.rocketmq ++ rocketmq-all ++- 5.1.4 +++ 5.1.5-SNAPSHOT ++ ++ ++ 4.0.0 ++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 @@ ++ ++ org.apache.rocketmq ++ rocketmq-all ++- 5.1.4 +++ 5.1.5-SNAPSHOT ++ ++ ++ 4.0.0 ++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 @@ ++ ++ rocketmq-all ++ org.apache.rocketmq ++- 5.1.4 +++ 5.1.5-SNAPSHOT ++ ++ 4.0.0 ++ jar ++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 @@ ++ ++ org.apache.rocketmq ++ rocketmq-all ++- 5.1.4 +++ 5.1.5-SNAPSHOT ++ ++ rocketmq-distribution ++ rocketmq-distribution ${project.version} ++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 @@ ++ ++ rocketmq-all ++ org.apache.rocketmq ++- 5.1.4 +++ 5.1.5-SNAPSHOT ++ ++ ++ 4.0.0 ++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 @@ ++ ++ rocketmq-all ++ org.apache.rocketmq ++- 5.1.4 +++ 5.1.5-SNAPSHOT ++ ++ ++ 4.0.0 ++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 @@ ++ ++ org.apache.rocketmq ++ rocketmq-all ++- 5.1.4 +++ 5.1.5-SNAPSHOT ++ ++ ++ 4.0.0 ++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 @@ ++ ++ rocketmq-all ++ org.apache.rocketmq ++- 5.1.4 +++ 5.1.5-SNAPSHOT ++ ++ ++ 4.0.0 ++diff --git a/pom.xml b/pom.xml ++index 0e1d04f15..4202d4095 100644 ++--- a/pom.xml +++++ b/pom.xml ++@@ -28,7 +28,7 @@ ++ 2012 ++ org.apache.rocketmq ++ rocketmq-all ++- 5.1.4 +++ 5.1.5-SNAPSHOT ++ pom ++ Apache RocketMQ ${project.version} ++ http://rocketmq.apache.org/ ++@@ -37,7 +37,7 @@ ++ git@github.com:apache/rocketmq.git ++ scm:git:git@github.com:apache/rocketmq.git ++ scm:git:git@github.com:apache/rocketmq.git ++- rocketmq-all-5.1.4 +++ HEAD ++ ++ ++ ++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 @@ ++ ++ rocketmq-all ++ org.apache.rocketmq ++- 5.1.4 +++ 5.1.5-SNAPSHOT ++ ++ ++ 4.0.0 ++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 @@ ++ ++ org.apache.rocketmq ++ rocketmq-all ++- 5.1.4 +++ 5.1.5-SNAPSHOT ++ ++ ++ 4.0.0 ++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 @@ ++ ++ org.apache.rocketmq ++ rocketmq-all ++- 5.1.4 +++ 5.1.5-SNAPSHOT ++ ++ ++ 4.0.0 ++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 @@ ++ ++ org.apache.rocketmq ++ rocketmq-all ++- 5.1.4 +++ 5.1.5-SNAPSHOT ++ ++ ++ 4.0.0 ++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 @@ ++ ++ rocketmq-all ++ org.apache.rocketmq ++- 5.1.4 +++ 5.1.5-SNAPSHOT ++ ++ ++ 4.0.0 ++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 @@ ++ ++ org.apache.rocketmq ++ rocketmq-all ++- 5.1.4 +++ 5.1.5-SNAPSHOT ++ ++ ++ 4.0.0 ++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 @@ ++ ++ org.apache.rocketmq ++ rocketmq-all ++- 5.1.4 +++ 5.1.5-SNAPSHOT ++ ++ ++ 4.0.0 ++-- ++2.32.0.windows.2 ++ ++ ++From 88a9d939ce110381b3b418370d4711c0c214dc7f Mon Sep 17 00:00:00 2001 ++From: Ji Juntao ++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 ++--- ++ .../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 ++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 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?= ++ ++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: 石臻臻 ++--- ++ .../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 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?= ++ ++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: 石臻臻 ++--- ++ .../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 registerBrokerResultList = this.brokerOuterAPI.registerBrokerAll( ++-- ++2.32.0.windows.2 ++ ++ ++From 4f1b42a7c5557bcadd6b9982a0c9bd876622c69e Mon Sep 17 00:00:00 2001 ++From: ShuangxiDing ++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: 徒钟 ++--- ++ .../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 ++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 registerBrokerAll( ++@@ -643,7 +643,6 @@ public class BrokerOuterAPI { ++ queueDatas.add(queueData); ++ final byte[] topicRouteBody = topicRouteData.encode(); ++ ++- ++ List 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 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 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 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> brokerElect(String controllerAddress, String clusterName, String brokerName, ++- Long brokerId) throws Exception { +++ public Pair> 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> registerBrokerToController(final String clusterName, final String brokerName, final Long brokerId, final String brokerAddress, final String controllerAddress) throws Exception { +++ public Pair> 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 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 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 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 sendHeartbeatOneway( ++ String brokerAddr, ++ HeartbeatData heartbeatData, ++@@ -146,24 +133,15 @@ public class MQClientAPIExt extends MQClientAPIImpl { ++ request.setLanguage(clientConfig.getLanguage()); ++ request.setBody(heartbeatData.encode()); ++ ++- CompletableFuture 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 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 sendMessageAsync( ++@@ -177,24 +155,15 @@ public class MQClientAPIExt extends MQClientAPIImpl { ++ RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE_V2, requestHeaderV2); ++ request.setBody(msg.getBody()); ++ ++- CompletableFuture 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 future0 = new CompletableFuture<>(); +++ try { +++ future0.complete(this.processSendResponse(brokerName, msg, response, brokerAddr)); +++ } catch (Exception e) { +++ future0.completeExceptionally(e); +++ } +++ return future0; +++ }); ++ } ++ ++ public CompletableFuture 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 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 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 popMessageAsync( ++@@ -402,38 +354,31 @@ public class MQClientAPIExt extends MQClientAPIImpl { ++ QueryConsumerOffsetRequestHeader requestHeader, ++ long timeoutMillis ++ ) { ++- CompletableFuture 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 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 updateConsumerOffsetOneWay( ++@@ -461,9 +406,14 @@ public class MQClientAPIExt extends MQClientAPIImpl { ++ ++ CompletableFuture> 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 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 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 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 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> lockBatchMQWithFuture(String brokerAddr, ++ LockBatchRequestBody requestBody, long timeoutMillis) { ++- CompletableFuture> 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 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> future0 = new CompletableFuture<>(); +++ if (response.getCode() == ResponseCode.SUCCESS) { +++ try { +++ LockBatchResponseBody responseBody = LockBatchResponseBody.decode(response.getBody(), LockBatchResponseBody.class); +++ Set 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 unlockBatchMQOneway(String brokerAddr, ++@@ -624,25 +577,21 @@ public class MQClientAPIExt extends MQClientAPIImpl { ++ ++ public CompletableFuture notification(String brokerAddr, NotificationRequestHeader requestHeader, ++ long timeoutMillis) { ++- CompletableFuture 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 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 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 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) 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 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 msgIdRef = new AtomicReference<>(); ++- doAnswer((Answer) 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 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) 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 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 messageExtList = new ArrayList<>(); ++ StringBuilder sb = new StringBuilder(); ++@@ -182,13 +172,9 @@ public class MQClientAPIExtTest { ++ ++ @Test ++ public void testSendMessageBackAsync() throws Exception { ++- doAnswer((Answer) 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 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) 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 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 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 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 invoke0(final Channel channel, final RemotingCommand request, +++ final long timeoutMillis) { +++ CompletableFuture 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 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 { ++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> listener) { +++ return this; +++ } +++ +++ @Override +++ public ChannelPromise addListeners(GenericFutureListener>... listeners) { +++ return this; +++ } +++ +++ @Override +++ public ChannelPromise removeListener(GenericFutureListener> listener) { +++ return this; +++ } +++ +++ @Override +++ public ChannelPromise removeListeners(GenericFutureListener>... 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 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 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 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 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 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 future = new CompletableFuture<>(); +++ future.complete(responseFuture); +++ +++ doReturn(future).when(remotingClient).invoke0(any(Channel.class), any(RemotingCommand.class), anyLong()); +++ +++ CompletableFuture 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 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 ++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> brokerAddrTable = MQClientInstance.this.brokerAddrTable; +++ @Override +++ public void onChannelConnect(String remoteAddr, Channel channel) { +++ for (Map.Entry> 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 ++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 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 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 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 ++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 ++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 baseDirs; ++ private List 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 ++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 ++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 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 proxyMap = new HashMap<>(); ++ private final ConcurrentHashMap bootstrapMap = new ConcurrentHashMap<>(); ++ private final ConcurrentMap channelTables = new ConcurrentHashMap<>(); +++ private final ConcurrentMap 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 invoke(String addr, RemotingCommand request, +++ long timeoutMillis) { +++ CompletableFuture 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 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 future0 = new CompletableFuture<>(); +++ future0.complete(responseFuture.getResponseCommand()); +++ doReturn(future0).when(remotingClient).invoke(anyString(), any(RemotingCommand.class), anyLong()); ++ ++ CompletableFuture 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 future0 = new CompletableFuture<>(); +++ future0.completeExceptionally(new RemotingSendRequestException(null)); +++ doReturn(future0).when(remotingClient).invoke(anyString(), any(RemotingCommand.class), anyLong()); ++ ++ CompletableFuture 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 future0 = new CompletableFuture<>(); +++ future0.completeExceptionally(new RemotingTimeoutException("")); +++ doReturn(future0).when(remotingClient).invoke(anyString(), any(RemotingCommand.class), anyLong()); ++ ++ CompletableFuture 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 future0 = new CompletableFuture<>(); +++ future0.completeExceptionally(new RemotingException("")); +++ doReturn(future0).when(remotingClient).invoke(anyString(), any(RemotingCommand.class), anyLong()); ++ ++ CompletableFuture 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 ++--- ++ .../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 ++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 ++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 @@ ++ ++ ++ +++ +++ +++ +++ ++ ++ ++ ++-- ++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 @@ + 1.29.0-alpha + 2.0.6 + 2.20.29 +- 1.0.3 ++ 1.0.2 + 2.13.4.2 + + +@@ -713,7 +713,7 @@ + ${slf4j-api.version} + + +- io.github.aliyunmq ++ org.apache.rocketmq + rocketmq-rocksdb + ${rocksdb.version} + +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 @@ + com.google.guava + guava + ++ ++ commons-io ++ commons-io ++ + + + org.slf4j +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 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 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 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 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 iterateFrom(long startIndex, int count) { ++ return iterateFrom(startIndex); ++ } ++ + @Override + public CqUnit get(long offset) { + ReferredIterator it = iterateFrom(offset); +@@ -974,6 +925,20 @@ public class ConsumeQueue implements ConsumeQueueInterface, FileQueueLifeCycle { + return it.nextAndRelease(); + } + ++ @Override ++ public Pair getCqUnitAndStoreTime(long index) { ++ CqUnit cqUnit = get(index); ++ Long messageStoreTime = this.messageStore.getQueueStore().getStoreTime(cqUnit); ++ return new Pair<>(cqUnit, messageStoreTime); ++ } ++ ++ @Override ++ public Pair 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 putMessageHookList = new ArrayList<>(); ++ private List 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 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 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 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 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 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 getRuntimeInfo() { + HashMap 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 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 pair = logicQueue.getCqUnitAndStoreTime(consumeQueueOffset); ++ if (pair != null && pair.getObject2() != null) { ++ return pair.getObject2(); ++ } + } +- + return -1; + } + +- @Override public CompletableFuture getMessageStoreTimeStampAsync(String topic, int queueId, ++ @Override ++ public CompletableFuture 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 deleteTopics) { +@@ -1363,17 +1373,19 @@ public class DefaultMessageStore implements MessageStore { + + int deleteCount = 0; + for (String topic : deleteTopics) { +- ConcurrentMap queueTable = +- this.consumeQueueStore.getConsumeQueueTable().get(topic); ++ ConcurrentMap 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 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 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 masterEpochEntries, long masterEndOffset) throws IOException { ++ private boolean doTruncate(List 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 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> 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 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> getConsumeQueueTable() { ++ return this.consumeQueueTable; ++ } ++ ++ @Override ++ public ConcurrentMap 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 iterateFrom(long startIndex, int count) { ++ return iterateFrom(startIndex); ++ } ++ + @Override + public CqUnit get(long offset) { + ReferredIterator it = iterateFrom(offset); +@@ -320,6 +326,20 @@ public class BatchConsumeQueue implements ConsumeQueueInterface { + return it.nextAndRelease(); + } + ++ @Override ++ public Pair getCqUnitAndStoreTime(long index) { ++ CqUnit cqUnit = get(index); ++ Long messageStoreTime = this.messageStore.getQueueStore().getStoreTime(cqUnit); ++ return new Pair<>(cqUnit, messageStoreTime); ++ } ++ ++ @Override ++ public Pair 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 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 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 getCqUnitAndStoreTime(long index); ++ ++ /** ++ * Get earliest cq unit ++ * @return earliest cq unit and message storeTime ++ */ ++ Pair 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> 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 maps : this.consumeQueueTable.values()) { ++ for (ConsumeQueueInterface logic : maps.values()) { ++ this.recover(logic); ++ } ++ } + } + ++ @Override ++ public boolean recoverConcurrently() { ++ int count = 0; ++ for (ConcurrentMap maps : this.consumeQueueTable.values()) { ++ count += maps.values().size(); ++ } ++ final CountDownLatch countDownLatch = new CountDownLatch(count); ++ BlockingQueue recoverQueue = new LinkedBlockingQueue<>(); ++ final ExecutorService executor = buildExecutorService(recoverQueue, "RecoverConsumeQueueThread_"); ++ List> result = new ArrayList<>(count); ++ try { ++ for (ConcurrentMap maps : this.consumeQueueTable.values()) { ++ for (final ConsumeQueueInterface logic : maps.values()) { ++ FutureTask 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 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 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 maps : this.consumeQueueTable.values()) { +- for (ConsumeQueueInterface logic : maps.values()) { +- this.recover(logic); +- } +- } +- } +- +- public boolean recoverConcurrently() { +- int count = 0; +- for (ConcurrentMap maps : this.consumeQueueTable.values()) { +- count += maps.values().size(); +- } +- final CountDownLatch countDownLatch = new CountDownLatch(count); +- BlockingQueue recoverQueue = new LinkedBlockingQueue<>(); +- final ExecutorService executor = buildExecutorService(recoverQueue, "RecoverConsumeQueueThread_"); +- List> result = new ArrayList<>(count); +- try { +- for (ConcurrentMap maps : this.consumeQueueTable.values()) { +- for (final ConsumeQueueInterface logic : maps.values()) { +- FutureTask 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 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 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> topicEntry : this.consumeQueueTable.entrySet()) { + for (Map.Entry 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 map = consumeQueueTable.get(topic); + if (null == map) { + ConcurrentMap 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 topicQueueTable) { +- this.queueOffsetOperator.setTopicQueueTable(topicQueueTable); +- this.queueOffsetOperator.setLmqTopicQueueTable(topicQueueTable); +- } +- +- public ConcurrentMap getTopicQueueTable() { +- return this.queueOffsetOperator.getTopicQueueTable(); +- } +- + public void setBatchTopicQueueTable(ConcurrentMap 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> getConsumeQueueTable() { +- return consumeQueueTable; +- } +- + private void putConsumeQueue(final String topic, final int queueId, final ConsumeQueueInterface consumeQueue) { + ConcurrentMap map = this.consumeQueueTable.get(topic); + if (null == map) { +@@ -412,6 +434,7 @@ public class ConsumeQueueStore { + } + } + ++ @Override + public void recoverOffsetTable(long minPhyOffset) { + ConcurrentMap cqOffsetTable = new ConcurrentHashMap<>(1024); + ConcurrentMap 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 maps : this.consumeQueueTable.values()) { + for (ConsumeQueueInterface logic : maps.values()) { +@@ -484,8 +508,9 @@ public class ConsumeQueueStore { + } + } + ++ @Override + public void cleanExpired(long minCommitLogOffset) { +- Iterator>> it = this.consumeQueueTable.entrySet().iterator(); ++ Iterator>> it = this.consumeQueueTable.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry> 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 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 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 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> 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 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 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 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 checkMultiDispatchQueue(MessageStoreConfig messageStoreConfig, List dispatchRequests) { ++ if (!messageStoreConfig.isEnableMultiDispatch() || dispatchRequests == null || dispatchRequests.size() == 0) { ++ return null; ++ } ++ List 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 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 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 pair = getCqUnitAndStoreTime(index); ++ return pair == null ? null : pair.getObject1(); ++ } ++ ++ @Override ++ public Pair 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 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 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 iterateFrom0(final long startIndex, final int count) throws RocksDBException { ++ List 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 { ++ private final List byteBufferList; ++ private final long startIndex; ++ private final int totalCount; ++ private int currentIndex; ++ ++ public RocksDBConsumeQueueIterator(final List 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: ++ * ++ *

++     * ┌─────────────────────────┬───────────┬───────────────────────┬───────────┬───────────┬───────────┬─────────────┐
++     * │ 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                                                   │
++     * │                                                                                                               │
++     * 
++ * ++ *
++     * ┌─────────────────────────────┬────────────────────────┐
++     * │  CommitLog Physical Offset  │   ConsumeQueue Offset  │
++     * │        (8 Bytes)            │    (8 Bytes)           │
++     * ├─────────────────────────────┴────────────────────────┤
++     * │                     Value Unit                       │
++     * │                                                      │
++     * 
++ * 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 topicQueueMinOffset; ++ private final Map 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 offsetBBPair, ++ final byte[] topicBytes, final DispatchRequest request, ++ final Map> tempTopicQueueMaxOffsetMap) { ++ buildOffsetKeyAndValueByteBuffer(offsetBBPair, topicBytes, request); ++ ByteBuffer topicQueueId = offsetBBPair.getObject1(); ++ ByteBuffer maxOffsetBB = offsetBBPair.getObject2(); ++ Pair 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> tempTopicQueueMaxOffsetMap, ++ final WriteBatch writeBatch, final long maxPhyOffset) throws RocksDBException { ++ for (Map.Entry> entry : tempTopicQueueMaxOffsetMap.entrySet()) { ++ writeBatch.put(this.offsetCFH, entry.getKey(), entry.getValue().getObject1()); ++ } ++ ++ appendMaxPhyOffset(writeBatch, maxPhyOffset); ++ } ++ ++ public void putHeapMaxCqOffset(final Map> tempTopicQueueMaxOffsetMap) { ++ for (Map.Entry> 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> iterateOffsetTable2FindDirty(final Set existTopicSet) { ++ Map> 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 topicQueueIdSet = topicQueueIdToBeDeletedMap.get(topic); ++ if (topicQueueIdSet == null) { ++ Set 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 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 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 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 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 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 bufferDRList; ++ private final List> cqBBPairList; ++ private final List> offsetBBPairList; ++ private final Map> 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 existTopicSet) { ++ try { ++ Map> topicQueueIdToBeDeletedMap = ++ this.rocksDBConsumeQueueOffsetTable.iterateOffsetTable2FindDirty(existTopicSet); ++ ++ for (Map.Entry> 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> tempTopicQueueMaxOffsetMap = this.tempTopicQueueMaxOffsetMap; ++ try { ++ final List bufferDRList = this.bufferDRList; ++ final int size = bufferDRList.size(); ++ if (size == 0) { ++ return true; ++ } ++ final List> cqBBPairList = this.cqBBPairList; ++ final List> 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 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 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 map = this.consumeQueueTable.get(topic); ++ if (null == map) { ++ ConcurrentMap newMap = new ConcurrentHashMap<>(128); ++ ConcurrentMap 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: ++ * ++ *
++     * ┌─────────────────────────┬───────────┬───────────────────────┬───────────┬───────────┬───────────┬───────────────────────┐
++     * │ 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                                                             │
++     * │                                                                                                                         │
++     * 
++ * ++ *
++     * ┌─────────────────────────────┬───────────────────┬──────────────────┬──────────────────┐
++     * │  CommitLog Physical Offset  │      Body Size    │   Tag HashCode   │  Msg Store Time  │
++     * │        (8 Bytes)            │      (4 Bytes)    │    (8 Bytes)     │    (8 Bytes)     │
++     * ├─────────────────────────────┴───────────────────┴──────────────────┴──────────────────┤
++     * │                                                    Value Unit                         │
++     * │                                                                                       │
++     * 
++ * 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 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 rangeQuery(final String topic, final int queueId, final long startIndex, final int num) throws RocksDBException { ++ final byte[] topicBytes = topic.getBytes(CHARSET_UTF8); ++ final List defaultCFHList = new ArrayList(num); ++ final ByteBuffer[] resultList = new ByteBuffer[num]; ++ final List kvIndexList = new ArrayList(num); ++ final List 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 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 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 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 { ++ 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 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 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 multiGet(final List cfhList, final List 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 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 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 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 bufferResultList = defaultMessageStore.getBulkCommitLogData(0, (int) defaultMessageStore.getMaxPhyOffset()); ++ List 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> consumeQueueTable = ++ ((RocksDBMessageStore) messageStore).getConsumeQueueTable(); ++ for (int i = 0; i < 10; i++) { ++ ConcurrentMap 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 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> consumeQueueTable = ++ ((RocksDBMessageStore) messageStore).getConsumeQueueTable(); ++ for (int i = 0; i < 10; i++) { ++ ConcurrentMap 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 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 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 jsonConfig = new HashMap<>(); + final Map 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 topicsJsonConfig = new HashMap<>(); ++ final Map 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 subscriptionGroupJsonConfig = new HashMap<>(); ++ final Map 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 + diff --git a/rocketmq.spec b/rocketmq.spec index f67608c..ed62612 100644 --- a/rocketmq.spec +++ b/rocketmq.spec @@ -5,7 +5,7 @@ Summary: Cloud-Native, Distributed Messaging and Streaming Name: rocketmq Version: 5.1.3 -Release: 22 +Release: 23 License: Apache-2.0 Group: Applications/Message URL: https://rocketmq.apache.org/ @@ -31,6 +31,7 @@ Patch0018: patch018-backport-enhancement-of-tiered-storage.patch Patch0019: patch019-backport-some-bugfix.patch Patch0020: patch020-backport-add-goaway-mechanism.patch Patch0021: patch021-backport-some-enhancements.patch +Patch0022: patch022-backport-Support-KV-Storage-for-ConsumeQueue.patch BuildRequires: java-1.8.0-openjdk-devel, maven, maven-local, git Requires: java-1.8.0-openjdk-devel @@ -65,6 +66,9 @@ exit 0 %changelog +* Fri Dec 8 2023 ShiZhili - 5.1.3-23 +- backport support kv storage + * Tue Dec 5 2023 ShiZhili - 5.1.3-22 - backport some enhancements