diff --git a/broker/pom.xml b/broker/pom.xml
index 3a58aa0c717d542afa684680daa640f662fe16b4..8453452261d4d7aef31b8f35c66cdadd58ff9a44 100644
--- a/broker/pom.xml
+++ b/broker/pom.xml
@@ -66,6 +66,10 @@
org.slf4j
slf4j-api
+
+ com.googlecode.concurrentlinkedhashmap
+ concurrentlinkedhashmap-lru
+
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 194f2850fa57274d6a620c72c403ba1c5f1868dd..e83bea239753a5314e48876b03edbc97e838d155 100644
--- a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java
+++ b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java
@@ -47,19 +47,25 @@ import org.apache.rocketmq.broker.filter.ConsumerFilterManager;
import org.apache.rocketmq.broker.filtersrv.FilterServerManager;
import org.apache.rocketmq.broker.latency.BrokerFastFailure;
import org.apache.rocketmq.broker.latency.BrokerFixedThreadPoolExecutor;
+import org.apache.rocketmq.broker.loadbalance.AssignmentManager;
import org.apache.rocketmq.broker.longpolling.NotifyMessageArrivingListener;
import org.apache.rocketmq.broker.longpolling.PullRequestHoldService;
import org.apache.rocketmq.broker.mqtrace.ConsumeMessageHook;
import org.apache.rocketmq.broker.mqtrace.SendMessageHook;
import org.apache.rocketmq.broker.offset.ConsumerOffsetManager;
+import org.apache.rocketmq.broker.offset.ConsumerOrderInfoManager;
import org.apache.rocketmq.broker.out.BrokerOuterAPI;
import org.apache.rocketmq.broker.plugin.MessageStoreFactory;
import org.apache.rocketmq.broker.plugin.MessageStorePluginContext;
+import org.apache.rocketmq.broker.processor.AckMessageProcessor;
import org.apache.rocketmq.broker.processor.AdminBrokerProcessor;
+import org.apache.rocketmq.broker.processor.ChangeInvisibleTimeProcessor;
import org.apache.rocketmq.broker.processor.ClientManageProcessor;
import org.apache.rocketmq.broker.processor.ConsumerManageProcessor;
import org.apache.rocketmq.broker.processor.EndTransactionProcessor;
+import org.apache.rocketmq.broker.processor.PopMessageProcessor;
import org.apache.rocketmq.broker.processor.PullMessageProcessor;
+import org.apache.rocketmq.broker.processor.QueryAssignmentProcessor;
import org.apache.rocketmq.broker.processor.QueryMessageProcessor;
import org.apache.rocketmq.broker.processor.ReplyMessageProcessor;
import org.apache.rocketmq.broker.processor.SendMessageProcessor;
@@ -118,9 +124,18 @@ public class BrokerController {
private final ConsumerOffsetManager consumerOffsetManager;
private final ConsumerManager consumerManager;
private final ConsumerFilterManager consumerFilterManager;
+ private final ConsumerOrderInfoManager consumerOrderInfoManager;
private final ProducerManager producerManager;
+ private final AssignmentManager assignmentManager;
private final ClientHousekeepingService clientHousekeepingService;
+
private final PullMessageProcessor pullMessageProcessor;
+ private final PopMessageProcessor popMessageProcessor;
+ private final AckMessageProcessor ackMessageProcessor;
+ private final ChangeInvisibleTimeProcessor changeInvisibleTimeProcessor;
+ private final QueryAssignmentProcessor queryAssignmentProcessor;
+ private final ClientManageProcessor clientManageProcessor;
+ private final SendMessageProcessor sendMessageProcessor;
private final PullRequestHoldService pullRequestHoldService;
private final MessageArrivingListener messageArrivingListener;
private final Broker2Client broker2Client;
@@ -132,6 +147,7 @@ public class BrokerController {
"BrokerControllerScheduledThread"));
private final SlaveSynchronize slaveSynchronize;
private final BlockingQueue sendThreadPoolQueue;
+ private final BlockingQueue ackThreadPoolQueue;
private final BlockingQueue pullThreadPoolQueue;
private final BlockingQueue replyThreadPoolQueue;
private final BlockingQueue queryThreadPoolQueue;
@@ -149,12 +165,14 @@ public class BrokerController {
private TopicConfigManager topicConfigManager;
private ExecutorService sendMessageExecutor;
private ExecutorService pullMessageExecutor;
+ private ExecutorService ackMessageExecutor;
private ExecutorService replyMessageExecutor;
private ExecutorService queryMessageExecutor;
private ExecutorService adminBrokerExecutor;
private ExecutorService clientManageExecutor;
private ExecutorService heartbeatExecutor;
private ExecutorService consumerManageExecutor;
+ private ExecutorService loadBalanceExecutor;
private ExecutorService endTransactionExecutor;
private boolean updateMasterHAServerAddrPeriodically = false;
private BrokerStats brokerStats;
@@ -167,6 +185,7 @@ public class BrokerController {
private AbstractTransactionalMessageCheckListener transactionalMessageCheckListener;
private Future> slaveSyncFuture;
private Map accessValidatorMap = new HashMap();
+ private long shouldStartTime;
public BrokerController(
final BrokerConfig brokerConfig,
@@ -182,10 +201,16 @@ public class BrokerController {
this.topicConfigManager = new TopicConfigManager(this);
this.pullMessageProcessor = new PullMessageProcessor(this);
this.pullRequestHoldService = new PullRequestHoldService(this);
- this.messageArrivingListener = new NotifyMessageArrivingListener(this.pullRequestHoldService);
+ this.popMessageProcessor = new PopMessageProcessor(this);
+ this.ackMessageProcessor = new AckMessageProcessor(this);
+ this.changeInvisibleTimeProcessor = new ChangeInvisibleTimeProcessor(this);
+ this.sendMessageProcessor = new SendMessageProcessor(this);
+ this.messageArrivingListener = new NotifyMessageArrivingListener(this.pullRequestHoldService,
+ this.popMessageProcessor);
this.consumerIdsChangeListener = new DefaultConsumerIdsChangeListener(this);
this.consumerManager = new ConsumerManager(this.consumerIdsChangeListener);
this.consumerFilterManager = new ConsumerFilterManager(this);
+ this.consumerOrderInfoManager = new ConsumerOrderInfoManager(this);
this.producerManager = new ProducerManager();
this.clientHousekeepingService = new ClientHousekeepingService(this);
this.broker2Client = new Broker2Client(this);
@@ -193,10 +218,14 @@ public class BrokerController {
this.brokerOuterAPI = new BrokerOuterAPI(nettyClientConfig);
this.filterServerManager = new FilterServerManager(this);
+ this.assignmentManager = new AssignmentManager(this);
+ this.queryAssignmentProcessor = new QueryAssignmentProcessor(this);
+ this.clientManageProcessor = new ClientManageProcessor(this);
this.slaveSynchronize = new SlaveSynchronize(this);
this.sendThreadPoolQueue = new LinkedBlockingQueue(this.brokerConfig.getSendThreadPoolQueueCapacity());
this.pullThreadPoolQueue = new LinkedBlockingQueue(this.brokerConfig.getPullThreadPoolQueueCapacity());
+ this.ackThreadPoolQueue = new LinkedBlockingQueue(this.brokerConfig.getAckThreadPoolQueueCapacity());
this.replyThreadPoolQueue = new LinkedBlockingQueue(this.brokerConfig.getReplyThreadPoolQueueCapacity());
this.queryThreadPoolQueue = new LinkedBlockingQueue(this.brokerConfig.getQueryThreadPoolQueueCapacity());
this.clientManagerThreadPoolQueue = new LinkedBlockingQueue(this.brokerConfig.getClientManagerThreadPoolQueueCapacity());
@@ -215,6 +244,14 @@ public class BrokerController {
);
}
+ public ConsumerIdsChangeListener getConsumerIdsChangeListener() {
+ return consumerIdsChangeListener;
+ }
+
+ public ClientManageProcessor getClientManageProcessor() {
+ return clientManageProcessor;
+ }
+
public BrokerConfig getBrokerConfig() {
return brokerConfig;
}
@@ -281,6 +318,15 @@ public class BrokerController {
this.pullThreadPoolQueue,
new ThreadFactoryImpl("PullMessageThread_"));
+ this.ackMessageExecutor = new BrokerFixedThreadPoolExecutor(
+ this.brokerConfig.getAckMessageThreadPoolNums(),
+ this.brokerConfig.getAckMessageThreadPoolNums(),
+ 1000 * 60,
+ TimeUnit.MILLISECONDS,
+ this.ackThreadPoolQueue,
+ new ThreadFactoryImpl("AckMessageThread_"));
+
+
this.replyMessageExecutor = new BrokerFixedThreadPoolExecutor(
this.brokerConfig.getProcessReplyMessageThreadPoolNums(),
this.brokerConfig.getProcessReplyMessageThreadPoolNums(),
@@ -400,6 +446,10 @@ public class BrokerController {
}
}, 1000 * 10, 1000 * 60, TimeUnit.MILLISECONDS);
+ this.loadBalanceExecutor =
+ Executors.newFixedThreadPool(this.brokerConfig.getLoadBalanceProcessorThreadPoolNums(), new ThreadFactoryImpl(
+ "LoadBalanceProcessorThread_"));
+
if (this.brokerConfig.getNamesrvAddr() != null) {
this.brokerOuterAPI.updateNameServerAddressList(this.brokerConfig.getNamesrvAddr());
log.info("Set user specified name server address: {}", this.brokerConfig.getNamesrvAddr());
@@ -547,23 +597,38 @@ public class BrokerController {
/**
* SendMessageProcessor
*/
- SendMessageProcessor sendProcessor = new SendMessageProcessor(this);
- sendProcessor.registerSendMessageHook(sendMessageHookList);
- sendProcessor.registerConsumeMessageHook(consumeMessageHookList);
-
- this.remotingServer.registerProcessor(RequestCode.SEND_MESSAGE, sendProcessor, this.sendMessageExecutor);
- this.remotingServer.registerProcessor(RequestCode.SEND_MESSAGE_V2, sendProcessor, this.sendMessageExecutor);
- this.remotingServer.registerProcessor(RequestCode.SEND_BATCH_MESSAGE, sendProcessor, this.sendMessageExecutor);
- this.remotingServer.registerProcessor(RequestCode.CONSUMER_SEND_MSG_BACK, sendProcessor, this.sendMessageExecutor);
- this.fastRemotingServer.registerProcessor(RequestCode.SEND_MESSAGE, sendProcessor, this.sendMessageExecutor);
- this.fastRemotingServer.registerProcessor(RequestCode.SEND_MESSAGE_V2, sendProcessor, this.sendMessageExecutor);
- this.fastRemotingServer.registerProcessor(RequestCode.SEND_BATCH_MESSAGE, sendProcessor, this.sendMessageExecutor);
- this.fastRemotingServer.registerProcessor(RequestCode.CONSUMER_SEND_MSG_BACK, sendProcessor, this.sendMessageExecutor);
+
+ sendMessageProcessor.registerSendMessageHook(sendMessageHookList);
+ sendMessageProcessor.registerConsumeMessageHook(consumeMessageHookList);
+
+ this.remotingServer.registerProcessor(RequestCode.SEND_MESSAGE, sendMessageProcessor, this.sendMessageExecutor);
+ this.remotingServer.registerProcessor(RequestCode.SEND_MESSAGE_V2, sendMessageProcessor, this.sendMessageExecutor);
+ this.remotingServer.registerProcessor(RequestCode.SEND_BATCH_MESSAGE, sendMessageProcessor, this.sendMessageExecutor);
+ this.remotingServer.registerProcessor(RequestCode.CONSUMER_SEND_MSG_BACK, sendMessageProcessor, this.sendMessageExecutor);
+ this.fastRemotingServer.registerProcessor(RequestCode.SEND_MESSAGE, sendMessageProcessor, this.sendMessageExecutor);
+ this.fastRemotingServer.registerProcessor(RequestCode.SEND_MESSAGE_V2, sendMessageProcessor, this.sendMessageExecutor);
+ this.fastRemotingServer.registerProcessor(RequestCode.SEND_BATCH_MESSAGE, sendMessageProcessor, this.sendMessageExecutor);
+ this.fastRemotingServer.registerProcessor(RequestCode.CONSUMER_SEND_MSG_BACK, sendMessageProcessor, this.sendMessageExecutor);
/**
* PullMessageProcessor
*/
this.remotingServer.registerProcessor(RequestCode.PULL_MESSAGE, this.pullMessageProcessor, this.pullMessageExecutor);
this.pullMessageProcessor.registerConsumeMessageHook(consumeMessageHookList);
+ /**
+ * PopMessageProcessor
+ */
+ this.remotingServer.registerProcessor(RequestCode.POP_MESSAGE, this.popMessageProcessor, this.pullMessageExecutor);
+
+ /**
+ * AckMessageProcessor
+ */
+ this.remotingServer.registerProcessor(RequestCode.ACK_MESSAGE, this.ackMessageProcessor, this.ackMessageExecutor);
+ this.fastRemotingServer.registerProcessor(RequestCode.ACK_MESSAGE, this.ackMessageProcessor, this.ackMessageExecutor);
+ /**
+ * ChangeInvisibleTimeProcessor
+ */
+ this.remotingServer.registerProcessor(RequestCode.CHANGE_MESSAGE_INVISIBLETIME, this.changeInvisibleTimeProcessor, this.ackMessageExecutor);
+ this.fastRemotingServer.registerProcessor(RequestCode.CHANGE_MESSAGE_INVISIBLETIME, this.changeInvisibleTimeProcessor, this.ackMessageExecutor);
/**
* ReplyMessageProcessor
@@ -589,14 +654,13 @@ public class BrokerController {
/**
* ClientManageProcessor
*/
- ClientManageProcessor clientProcessor = new ClientManageProcessor(this);
- this.remotingServer.registerProcessor(RequestCode.HEART_BEAT, clientProcessor, this.heartbeatExecutor);
- this.remotingServer.registerProcessor(RequestCode.UNREGISTER_CLIENT, clientProcessor, this.clientManageExecutor);
- this.remotingServer.registerProcessor(RequestCode.CHECK_CLIENT_CONFIG, clientProcessor, this.clientManageExecutor);
+ this.remotingServer.registerProcessor(RequestCode.HEART_BEAT, clientManageProcessor, this.heartbeatExecutor);
+ this.remotingServer.registerProcessor(RequestCode.UNREGISTER_CLIENT, clientManageProcessor, this.clientManageExecutor);
+ this.remotingServer.registerProcessor(RequestCode.CHECK_CLIENT_CONFIG, clientManageProcessor, this.clientManageExecutor);
- this.fastRemotingServer.registerProcessor(RequestCode.HEART_BEAT, clientProcessor, this.heartbeatExecutor);
- this.fastRemotingServer.registerProcessor(RequestCode.UNREGISTER_CLIENT, clientProcessor, this.clientManageExecutor);
- this.fastRemotingServer.registerProcessor(RequestCode.CHECK_CLIENT_CONFIG, clientProcessor, this.clientManageExecutor);
+ this.fastRemotingServer.registerProcessor(RequestCode.HEART_BEAT, clientManageProcessor, this.heartbeatExecutor);
+ this.fastRemotingServer.registerProcessor(RequestCode.UNREGISTER_CLIENT, clientManageProcessor, this.clientManageExecutor);
+ this.fastRemotingServer.registerProcessor(RequestCode.CHECK_CLIENT_CONFIG, clientManageProcessor, this.clientManageExecutor);
/**
* ConsumerManageProcessor
@@ -610,6 +674,14 @@ public class BrokerController {
this.fastRemotingServer.registerProcessor(RequestCode.UPDATE_CONSUMER_OFFSET, consumerManageProcessor, this.consumerManageExecutor);
this.fastRemotingServer.registerProcessor(RequestCode.QUERY_CONSUMER_OFFSET, consumerManageProcessor, this.consumerManageExecutor);
+ /**
+ * QueryAssignmentProcessor
+ */
+ this.remotingServer.registerProcessor(RequestCode.QUERY_ASSIGNMENT, queryAssignmentProcessor, loadBalanceExecutor);
+ this.fastRemotingServer.registerProcessor(RequestCode.QUERY_ASSIGNMENT, queryAssignmentProcessor, loadBalanceExecutor);
+ this.remotingServer.registerProcessor(RequestCode.SET_MESSAGE_REQUEST_MODE, queryAssignmentProcessor, loadBalanceExecutor);
+ this.fastRemotingServer.registerProcessor(RequestCode.SET_MESSAGE_REQUEST_MODE, queryAssignmentProcessor, loadBalanceExecutor);
+
/**
* EndTransactionProcessor
*/
@@ -713,6 +785,10 @@ public class BrokerController {
return consumerFilterManager;
}
+ public ConsumerOrderInfoManager getConsumerOrderInfoManager() {
+ return consumerOrderInfoManager;
+ }
+
public ConsumerOffsetManager getConsumerOffsetManager() {
return consumerOffsetManager;
}
@@ -741,6 +817,10 @@ public class BrokerController {
return subscriptionGroupManager;
}
+ public PopMessageProcessor getPopMessageProcessor() {
+ return popMessageProcessor;
+ }
+
public void shutdown() {
if (this.brokerStatsManager != null) {
this.brokerStatsManager.shutdown();
@@ -824,6 +904,11 @@ public class BrokerController {
this.consumerManageExecutor.shutdown();
}
+ {
+ this.popMessageProcessor.getPopBufferMergeService().shutdown();
+ this.ackMessageProcessor.shutdownPopReviveService();
+ }
+
if (this.fileWatchService != null) {
this.fileWatchService.shutdown();
}
@@ -849,6 +934,8 @@ public class BrokerController {
}
public void start() throws Exception {
+ this.shouldStartTime = System.currentTimeMillis();
+
if (this.messageStore != null) {
this.messageStore.start();
}
@@ -857,6 +944,17 @@ public class BrokerController {
this.remotingServer.start();
}
+ {
+ this.popMessageProcessor.getPopLongPollingService().start();
+ this.popMessageProcessor.getPopBufferMergeService().start();
+ this.popMessageProcessor.getQueueLockManager().start();
+ this.ackMessageProcessor.startPopReviveService();
+ }
+
+ {
+ assignmentManager.start();
+ }
+
if (this.fastRemotingServer != null) {
this.fastRemotingServer.start();
}
@@ -1243,4 +1341,20 @@ public class BrokerController {
public ExecutorService getSendMessageExecutor() {
return sendMessageExecutor;
}
+
+ public long getShouldStartTime() {
+ return shouldStartTime;
+ }
+
+ public AssignmentManager getAssignmentManager() {
+ return assignmentManager;
+ }
+
+ public SendMessageProcessor getSendMessageProcessor() {
+ return sendMessageProcessor;
+ }
+
+ public QueryAssignmentProcessor getQueryAssignmentProcessor() {
+ return queryAssignmentProcessor;
+ }
}
diff --git a/broker/src/main/java/org/apache/rocketmq/broker/BrokerPathConfigHelper.java b/broker/src/main/java/org/apache/rocketmq/broker/BrokerPathConfigHelper.java
index 42c8da9f3fba7ab563a898b6191dc0b4246b9df2..43a9946f7106c1f2f4506155c70b3a13e115cde4 100644
--- a/broker/src/main/java/org/apache/rocketmq/broker/BrokerPathConfigHelper.java
+++ b/broker/src/main/java/org/apache/rocketmq/broker/BrokerPathConfigHelper.java
@@ -39,6 +39,10 @@ public class BrokerPathConfigHelper {
return rootDir + File.separator + "config" + File.separator + "consumerOffset.json";
}
+ public static String getConsumerOrderInfoPath(final String rootDir) {
+ return rootDir + File.separator + "config" + File.separator + "consumerOrderInfo.json";
+ }
+
public static String getSubscriptionGroupPath(final String rootDir) {
return rootDir + File.separator + "config" + File.separator + "subscriptionGroup.json";
}
@@ -46,4 +50,8 @@ public class BrokerPathConfigHelper {
public static String getConsumerFilterPath(final String rootDir) {
return rootDir + File.separator + "config" + File.separator + "consumerFilter.json";
}
+
+ public static String getMessageRequestModePath(final String rootDir) {
+ return rootDir + File.separator + "config" + File.separator + "messageRequestMode.json";
+ }
}
diff --git a/broker/src/main/java/org/apache/rocketmq/broker/loadbalance/AssignmentManager.java b/broker/src/main/java/org/apache/rocketmq/broker/loadbalance/AssignmentManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..877ddd83b403d95baf216d20d70314351ba60798
--- /dev/null
+++ b/broker/src/main/java/org/apache/rocketmq/broker/loadbalance/AssignmentManager.java
@@ -0,0 +1,149 @@
+/*
+ * 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.loadbalance;
+
+import com.google.common.collect.Lists;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import org.apache.rocketmq.broker.BrokerController;
+import org.apache.rocketmq.broker.out.BrokerOuterAPI;
+import org.apache.rocketmq.client.exception.MQBrokerException;
+import org.apache.rocketmq.client.impl.factory.MQClientInstance;
+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.protocol.ResponseCode;
+import org.apache.rocketmq.common.protocol.route.TopicRouteData;
+import org.apache.rocketmq.common.topic.TopicValidator;
+import org.apache.rocketmq.logging.InternalLogger;
+import org.apache.rocketmq.logging.InternalLoggerFactory;
+
+
+public class AssignmentManager {
+ private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME);
+
+ private transient BrokerController brokerController;
+
+ private final static long LOCK_TIMEOUT_MILLIS = 3000;
+
+ private final Lock lockNamesrv = new ReentrantLock();
+
+ private final BrokerOuterAPI mQClientAPIImpl;
+
+ private final ConcurrentHashMap> topicSubscribeInfoTable = new ConcurrentHashMap>();
+
+ private ScheduledExecutorService scheduledExecutorService = Executors
+ .newSingleThreadScheduledExecutor(new ThreadFactoryImpl("LoadBalanceManagerScheduledThread"));
+
+ private static final List IGNORE_ROUTE_TOPICS = Lists.newArrayList(
+ TopicValidator.SYSTEM_TOPIC_PREFIX,
+ MixAll.CID_RMQ_SYS_PREFIX,
+ MixAll.DEFAULT_CONSUMER_GROUP,
+ MixAll.TOOLS_CONSUMER_GROUP,
+ MixAll.FILTERSRV_CONSUMER_GROUP,
+ MixAll.MONITOR_CONSUMER_GROUP,
+ MixAll.ONS_HTTP_PROXY_GROUP,
+ MixAll.CID_ONSAPI_PERMISSION_GROUP,
+ MixAll.CID_ONSAPI_OWNER_GROUP,
+ MixAll.CID_ONSAPI_PULL_GROUP
+ );
+
+ private final List ignoreRouteTopics = Lists.newArrayList(IGNORE_ROUTE_TOPICS);
+
+ public AssignmentManager(BrokerController brokerController) {
+ this.brokerController = brokerController;
+ this.mQClientAPIImpl = brokerController.getBrokerOuterAPI();
+ ignoreRouteTopics.add(brokerController.getBrokerConfig().getBrokerClusterName());
+ ignoreRouteTopics.add(brokerController.getBrokerConfig().getBrokerName());
+ }
+
+ public void start() {
+ this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
+
+ @Override
+ public void run() {
+ try {
+ updateTopicRouteInfoFromNameServer();
+ } catch (Exception e) {
+ log.error("ScheduledTask: failed to pull TopicRouteData from NameServer", e);
+ }
+ }
+ }, 200, this.brokerController.getBrokerConfig().getLoadBalancePollNameServerInterval(), TimeUnit.MILLISECONDS);
+ }
+
+
+ public void updateTopicRouteInfoFromNameServer() {
+ Set topicList = new HashSet<>(brokerController.getTopicConfigManager().getTopicConfigTable().keySet());
+
+ LOOP:
+ for (String topic : topicList) {
+ for (String keyword : ignoreRouteTopics) {
+ if (topic.contains(keyword)) {
+ continue LOOP;
+ }
+ }
+
+ this.updateTopicRouteInfoFromNameServer(topic);
+ }
+ }
+
+ public boolean updateTopicRouteInfoFromNameServer(final String topic) {
+ try {
+ TopicRouteData topicRouteData = this.mQClientAPIImpl.getTopicRouteInfoFromNameServer(topic, 1000 * 3);
+ if (topicRouteData != null) {
+ Set newSubscribeInfo = MQClientInstance.topicRouteData2TopicSubscribeInfo(topic, topicRouteData);
+ Set oldSubscribeInfo = topicSubscribeInfoTable.get(topic);
+ boolean changed = !newSubscribeInfo.equals(oldSubscribeInfo);
+
+ if (changed) {
+ log.info("the topic[{}] subscribe message queue changed, old[{}] ,new[{}]", topic, oldSubscribeInfo, newSubscribeInfo);
+ topicSubscribeInfoTable.put(topic, newSubscribeInfo);
+ return true;
+ }
+ } else {
+ log.warn("updateTopicRouteInfoFromNameServer, getTopicRouteInfoFromNameServer return null, Topic: {}", topic);
+ }
+ } catch (Exception e) {
+ if (!topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
+ log.warn("updateTopicRouteInfoFromNameServer Exception", e);
+ if (e instanceof MQBrokerException && ResponseCode.TOPIC_NOT_EXIST == ((MQBrokerException) e).getResponseCode()) {
+ // clean no used topic
+ cleanNoneRouteTopic(topic);
+ }
+ }
+ }
+ return false;
+ }
+
+ private void cleanNoneRouteTopic(String topic) {
+ // clean no used topic
+ topicSubscribeInfoTable.remove(topic);
+ }
+
+
+ public Set getTopicSubscribeInfo(String topic) {
+ return topicSubscribeInfoTable.get(topic);
+ }
+}
diff --git a/broker/src/main/java/org/apache/rocketmq/broker/loadbalance/MessageRequestModeManager.java b/broker/src/main/java/org/apache/rocketmq/broker/loadbalance/MessageRequestModeManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..2b21d3a6dfa81985c8ca1a2d1f435323a325891f
--- /dev/null
+++ b/broker/src/main/java/org/apache/rocketmq/broker/loadbalance/MessageRequestModeManager.java
@@ -0,0 +1,99 @@
+/*
+ * 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.loadbalance;
+
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.rocketmq.broker.BrokerController;
+import org.apache.rocketmq.broker.BrokerPathConfigHelper;
+import org.apache.rocketmq.common.ConfigManager;
+import org.apache.rocketmq.common.protocol.body.SetMessageRequestModeRequestBody;
+import org.apache.rocketmq.remoting.protocol.RemotingSerializable;
+
+
+public class MessageRequestModeManager extends ConfigManager {
+
+ private BrokerController brokerController;
+
+ private ConcurrentHashMap>
+ messageRequestModeMap = new ConcurrentHashMap>();
+
+ public MessageRequestModeManager() {
+ // empty construct for decode
+ }
+
+ public MessageRequestModeManager(BrokerController brokerController) {
+ this.brokerController = brokerController;
+ }
+
+ public void setMessageRequestMode(String topic, String consumerGroup, SetMessageRequestModeRequestBody requestBody) {
+ ConcurrentHashMap consumerGroup2ModeMap = messageRequestModeMap.get(topic);
+ if (consumerGroup2ModeMap == null) {
+ consumerGroup2ModeMap = new ConcurrentHashMap();
+ ConcurrentHashMap pre =
+ messageRequestModeMap.putIfAbsent(topic, consumerGroup2ModeMap);
+ if (pre != null) {
+ consumerGroup2ModeMap = pre;
+ }
+ }
+ consumerGroup2ModeMap.put(consumerGroup, requestBody);
+ }
+
+ public SetMessageRequestModeRequestBody getMessageRequestMode(String topic, String consumerGroup) {
+ ConcurrentHashMap consumerGroup2ModeMap = messageRequestModeMap.get(topic);
+ if (consumerGroup2ModeMap != null) {
+ return consumerGroup2ModeMap.get(consumerGroup);
+ }
+
+ return null;
+ }
+
+ public ConcurrentHashMap> getMessageRequestModeMap() {
+ return this.messageRequestModeMap;
+ }
+
+ public void setMessageRequestModeMap(ConcurrentHashMap> messageRequestModeMap) {
+ this.messageRequestModeMap = messageRequestModeMap;
+ }
+
+
+ @Override
+ public String encode() {
+ return this.encode(false);
+ }
+
+
+ @Override
+ public String configFilePath() {
+ return BrokerPathConfigHelper.getMessageRequestModePath(this.brokerController.getMessageStoreConfig().getStorePathRootDir());
+ }
+
+
+ @Override
+ public void decode(String jsonString) {
+ if (jsonString != null) {
+ MessageRequestModeManager obj = RemotingSerializable.fromJson(jsonString, MessageRequestModeManager.class);
+ if (obj != null) {
+ this.messageRequestModeMap = obj.messageRequestModeMap;
+ }
+ }
+ }
+
+ @Override
+ public String encode(boolean prettyFormat) {
+ return RemotingSerializable.toJson(this, prettyFormat);
+ }
+}
diff --git a/broker/src/main/java/org/apache/rocketmq/broker/longpolling/NotifyMessageArrivingListener.java b/broker/src/main/java/org/apache/rocketmq/broker/longpolling/NotifyMessageArrivingListener.java
index ff0901126f2771b58136208d6012722a9b20f037..780346210c2cb3676f4bde960d356d8264321ae0 100644
--- a/broker/src/main/java/org/apache/rocketmq/broker/longpolling/NotifyMessageArrivingListener.java
+++ b/broker/src/main/java/org/apache/rocketmq/broker/longpolling/NotifyMessageArrivingListener.java
@@ -17,15 +17,17 @@
package org.apache.rocketmq.broker.longpolling;
-import org.apache.rocketmq.store.MessageArrivingListener;
-
import java.util.Map;
+import org.apache.rocketmq.broker.processor.PopMessageProcessor;
+import org.apache.rocketmq.store.MessageArrivingListener;
public class NotifyMessageArrivingListener implements MessageArrivingListener {
private final PullRequestHoldService pullRequestHoldService;
+ private final PopMessageProcessor popMessageProcessor;
- public NotifyMessageArrivingListener(final PullRequestHoldService pullRequestHoldService) {
+ public NotifyMessageArrivingListener(final PullRequestHoldService pullRequestHoldService, final PopMessageProcessor popMessageProcessor) {
this.pullRequestHoldService = pullRequestHoldService;
+ this.popMessageProcessor = popMessageProcessor;
}
@Override
@@ -33,5 +35,6 @@ public class NotifyMessageArrivingListener implements MessageArrivingListener {
long msgStoreTime, byte[] filterBitMap, Map properties) {
this.pullRequestHoldService.notifyMessageArriving(topic, queueId, logicOffset, tagsCode,
msgStoreTime, filterBitMap, properties);
+ this.popMessageProcessor.notifyMessageArriving(topic, queueId);
}
}
diff --git a/broker/src/main/java/org/apache/rocketmq/broker/longpolling/PopRequest.java b/broker/src/main/java/org/apache/rocketmq/broker/longpolling/PopRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..e8e45674d23c12cc5ac998ac2255e74bb0e05145
--- /dev/null
+++ b/broker/src/main/java/org/apache/rocketmq/broker/longpolling/PopRequest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.longpolling;
+
+import io.netty.channel.Channel;
+import java.util.Comparator;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+
+public class PopRequest {
+ private static final AtomicLong COUNTER = new AtomicLong(Long.MIN_VALUE);
+
+ private RemotingCommand remotingCommand;
+ private Channel channel;
+ private long expired;
+ private AtomicBoolean complete = new AtomicBoolean(false);
+ private final long op = COUNTER.getAndIncrement();
+
+ public PopRequest(RemotingCommand remotingCommand, Channel channel, long expired) {
+ this.channel = channel;
+ this.remotingCommand = remotingCommand;
+ this.expired = expired;
+ }
+
+ public Channel getChannel() {
+ return channel;
+ }
+
+ public RemotingCommand getRemotingCommand() {
+ return remotingCommand;
+ }
+
+ public boolean isTimeout() {
+ return System.currentTimeMillis() > (expired - 50);
+ }
+
+ public boolean complete() {
+ return complete.compareAndSet(false, true);
+ }
+
+ public long getExpired() {
+ return expired;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("PopRequest{");
+ sb.append("cmd=").append(remotingCommand);
+ sb.append(", channel=").append(channel);
+ sb.append(", expired=").append(expired);
+ sb.append(", complete=").append(complete);
+ sb.append(", op=").append(op);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ public static final Comparator COMPARATOR = new Comparator() {
+ @Override
+ public int compare(PopRequest o1, PopRequest o2) {
+ int ret = (int) (o1.getExpired() - o2.getExpired());
+
+ if (ret != 0) {
+ return ret;
+ }
+ ret = (int) (o1.op - o2.op);
+ if (ret != 0) {
+ return ret;
+ }
+ return -1;
+ }
+ };
+}
diff --git a/broker/src/main/java/org/apache/rocketmq/broker/offset/ConsumerOrderInfoManager.java b/broker/src/main/java/org/apache/rocketmq/broker/offset/ConsumerOrderInfoManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..68c767fd405dcf0ad74e6d8baae85ce42a4e1f28
--- /dev/null
+++ b/broker/src/main/java/org/apache/rocketmq/broker/offset/ConsumerOrderInfoManager.java
@@ -0,0 +1,426 @@
+/*
+ * 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.offset;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.rocketmq.broker.BrokerController;
+import org.apache.rocketmq.broker.BrokerPathConfigHelper;
+import org.apache.rocketmq.common.ConfigManager;
+import org.apache.rocketmq.common.TopicConfig;
+import org.apache.rocketmq.common.constant.LoggerName;
+import org.apache.rocketmq.logging.InternalLogger;
+import org.apache.rocketmq.logging.InternalLoggerFactory;
+import org.apache.rocketmq.remoting.protocol.RemotingSerializable;
+
+public class ConsumerOrderInfoManager extends ConfigManager {
+
+ private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME);
+ private static final String TOPIC_GROUP_SEPARATOR = "@";
+ private static final long CLEAN_SPAN_FROM_LAST = 24 * 3600 * 1000;
+
+ private ConcurrentHashMap> table =
+ new ConcurrentHashMap<>(128);
+
+ private transient BrokerController brokerController;
+
+ public ConsumerOrderInfoManager() {
+ }
+
+ public ConsumerOrderInfoManager(BrokerController brokerController) {
+ this.brokerController = brokerController;
+ }
+
+ public ConcurrentHashMap> getTable() {
+ return table;
+ }
+
+ public void setTable(ConcurrentHashMap> table) {
+ this.table = table;
+ }
+
+ /**
+ * not thread safe.
+ *
+ * @param topic
+ * @param group
+ * @param queueId
+ * @param msgOffsetList
+ */
+ public int update(String topic, String group, int queueId, List msgOffsetList) {
+ String key = topic + TOPIC_GROUP_SEPARATOR + group;
+ ConcurrentHashMap qs = table.get(key);
+ if (qs == null) {
+ qs = new ConcurrentHashMap<>(16);
+ ConcurrentHashMap old = table.putIfAbsent(key, qs);
+ if (old != null) {
+ qs = old;
+ }
+ }
+
+ OrderInfo orderInfo = qs.get(queueId);
+
+ // start is same.
+ List simple = OrderInfo.simpleO(msgOffsetList);
+ if (orderInfo != null && simple.get(0).equals(orderInfo.getOffsetList().get(0))) {
+ if (simple.equals(orderInfo.getOffsetList())) {
+ orderInfo.setConsumedCount(orderInfo.getConsumedCount() + 1);
+ } else {
+ // reset, because msgs are changed.
+ orderInfo.setConsumedCount(0);
+ }
+ orderInfo.setLastConsumeTimestamp(System.currentTimeMillis());
+ orderInfo.setOffsetList(simple);
+ orderInfo.setCommitOffsetBit(0);
+ } else {
+ orderInfo = new OrderInfo();
+ orderInfo.setOffsetList(simple);
+ orderInfo.setLastConsumeTimestamp(System.currentTimeMillis());
+ orderInfo.setConsumedCount(0);
+ orderInfo.setCommitOffsetBit(0);
+
+ qs.put(queueId, orderInfo);
+ }
+
+ return orderInfo.getConsumedCount();
+ }
+
+ public boolean checkBlock(String topic, String group, int queueId, long invisibleTime) {
+ String key = topic + TOPIC_GROUP_SEPARATOR + group;
+ ConcurrentHashMap qs = table.get(key);
+ if (qs == null) {
+ qs = new ConcurrentHashMap<>(16);
+ ConcurrentHashMap old = table.putIfAbsent(key, qs);
+ if (old != null) {
+ qs = old;
+ }
+ }
+
+ OrderInfo orderInfo = qs.get(queueId);
+
+ if (orderInfo == null) {
+ return false;
+ }
+
+ boolean isBlock = System.currentTimeMillis() - orderInfo.getLastConsumeTimestamp() < invisibleTime;
+
+ return isBlock && !orderInfo.isDone();
+ }
+
+ /**
+ * @param topic
+ * @param group
+ * @param queueId
+ * @param offset
+ * @return -1 : illegal, -2 : no need commit, >= 0 : commit
+ */
+ public long commitAndNext(String topic, String group, int queueId, long offset) {
+ String key = topic + TOPIC_GROUP_SEPARATOR + group;
+ ConcurrentHashMap qs = table.get(key);
+
+ if (qs == null) {
+ return offset + 1;
+ }
+ OrderInfo orderInfo = qs.get(queueId);
+ if (orderInfo == null) {
+ log.warn("OrderInfo is null, {}, {}, {}", key, offset, orderInfo);
+ return offset + 1;
+ }
+
+ List offsetList = orderInfo.getOffsetList();
+ if (offsetList == null || offsetList.isEmpty()) {
+ log.warn("OrderInfo is empty, {}, {}, {}", key, offset, orderInfo);
+ return -1;
+ }
+ Long first = offsetList.get(0);
+ int i = 0, size = offsetList.size();
+ for (; i < size; i++) {
+ long temp;
+ if (i == 0) {
+ temp = first;
+ } else {
+ temp = first + offsetList.get(i);
+ }
+ if (offset == temp) {
+ break;
+ }
+ }
+ // not found
+ if (i >= size) {
+ log.warn("OrderInfo not found commit offset, {}, {}, {}", key, offset, orderInfo);
+ return -1;
+ }
+ //set bit
+ orderInfo.setCommitOffsetBit(orderInfo.getCommitOffsetBit() | (1L << i));
+ if (orderInfo.isDone()) {
+ if (size == 1) {
+ return offsetList.get(0) + 1;
+ } else {
+ return offsetList.get(size - 1) + first + 1;
+ }
+ }
+ return -2;
+ }
+
+ public OrderInfo get(String topic, String group, int queueId) {
+ String key = topic + TOPIC_GROUP_SEPARATOR + group;
+ ConcurrentHashMap qs = table.get(key);
+
+ if (qs == null) {
+ return null;
+ }
+
+ return qs.get(queueId);
+ }
+
+ public int getConsumeCount(String topic, String group, int queueId) {
+ OrderInfo orderInfo = get(topic, group, queueId);
+ return orderInfo == null ? 0 : orderInfo.getConsumedCount();
+ }
+
+ private void autoClean() {
+ if (brokerController == null) {
+ return;
+ }
+ Iterator>> iterator =
+ this.table.entrySet().iterator();
+ while (iterator.hasNext()) {
+ Map.Entry> entry =
+ iterator.next();
+ String topicAtGroup = entry.getKey();
+ ConcurrentHashMap qs = entry.getValue();
+ String[] arrays = topicAtGroup.split(TOPIC_GROUP_SEPARATOR);
+ if (arrays.length != 2) {
+ continue;
+ }
+ String topic = arrays[0];
+ String group = arrays[1];
+
+ TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(topic);
+ if (topicConfig == null) {
+ iterator.remove();
+ log.info("Topic not exist, Clean order info, {}:{}", topicAtGroup, qs);
+ continue;
+ }
+
+ if (this.brokerController.getSubscriptionGroupManager().getSubscriptionGroupTable().get(group) == null) {
+ iterator.remove();
+ log.info("Group not exist, Clean order info, {}:{}", topicAtGroup, qs);
+ continue;
+ }
+
+ if (qs.isEmpty()) {
+ iterator.remove();
+ log.info("Order table is empty, Clean order info, {}:{}", topicAtGroup, qs);
+ continue;
+ }
+
+ Iterator> qsIterator = qs.entrySet().iterator();
+ while (qsIterator.hasNext()) {
+ Map.Entry qsEntry = qsIterator.next();
+
+ if (qsEntry.getKey() >= topicConfig.getReadQueueNums()) {
+ qsIterator.remove();
+ log.info("Queue not exist, Clean order info, {}:{}, {}", topicAtGroup, entry.getValue(), topicConfig);
+ continue;
+ }
+
+ if (System.currentTimeMillis() - qsEntry.getValue().getLastConsumeTimestamp() > CLEAN_SPAN_FROM_LAST) {
+ qsIterator.remove();
+ log.info("Not consume long time, Clean order info, {}:{}, {}", topicAtGroup, entry.getValue(), topicConfig);
+ continue;
+ }
+ }
+ }
+ }
+
+ @Override
+ public String encode() {
+ return this.encode(false);
+ }
+
+ @Override
+ public String configFilePath() {
+ if (brokerController != null) {
+ return BrokerPathConfigHelper.getConsumerOrderInfoPath(this.brokerController.getMessageStoreConfig().getStorePathRootDir());
+ } else {
+ return BrokerPathConfigHelper.getConsumerOrderInfoPath("~");
+ }
+ }
+
+ @Override
+ public void decode(String jsonString) {
+ if (jsonString != null) {
+ ConsumerOrderInfoManager obj = RemotingSerializable.fromJson(jsonString, ConsumerOrderInfoManager.class);
+ if (obj != null) {
+ this.table = obj.table;
+ }
+ }
+ }
+
+ @Override
+ public String encode(boolean prettyFormat) {
+ this.autoClean();
+
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append("{\n").append("\t\"table\":{");
+ Iterator>> iterator =
+ this.table.entrySet().iterator();
+ int count1 = 0;
+ while (iterator.hasNext()) {
+ Map.Entry> entry =
+ iterator.next();
+ if (count1 > 0) {
+ stringBuilder.append(",");
+ }
+ stringBuilder.append("\n\t\t\"").append(entry.getKey()).append("\":{");
+ Iterator> qsIterator = entry.getValue().entrySet().iterator();
+ int count2 = 0;
+ while (qsIterator.hasNext()) {
+ Map.Entry qsEntry = qsIterator.next();
+ if (count2 > 0) {
+ stringBuilder.append(",");
+ }
+ stringBuilder.append("\n\t\t\t").append(qsEntry.getKey()).append(":")
+ .append(qsEntry.getValue().encode());
+ count2++;
+ }
+ stringBuilder.append("\n\t\t}");
+ count1++;
+ }
+ stringBuilder.append("\n\t}").append("\n}");
+ return stringBuilder.toString();
+ }
+
+ public static class OrderInfo {
+ /**
+ * offset
+ */
+ private List offsetList;
+ /**
+ * consumed count
+ */
+ private int consumedCount;
+ /**
+ * last consume timestamp
+ */
+ private long lastConsumeTimestamp;
+ /**
+ * commit offset bit
+ */
+ private long commitOffsetBit;
+
+ public OrderInfo() {
+ }
+
+ public List getOffsetList() {
+ return offsetList;
+ }
+
+ public void setOffsetList(List offsetList) {
+ this.offsetList = offsetList;
+ }
+
+ public static List simpleO(List offsetList) {
+ List simple = new ArrayList<>();
+ if (offsetList.size() == 1) {
+ simple.addAll(offsetList);
+ return simple;
+ }
+ Long first = offsetList.get(0);
+ simple.add(first);
+ for (int i = 1; i < offsetList.size(); i++) {
+ simple.add(offsetList.get(i) - first);
+ }
+ return simple;
+ }
+
+ public int getConsumedCount() {
+ return consumedCount;
+ }
+
+ public void setConsumedCount(int consumedCount) {
+ this.consumedCount = consumedCount;
+ }
+
+ public long getLastConsumeTimestamp() {
+ return lastConsumeTimestamp;
+ }
+
+ public void setLastConsumeTimestamp(long lastConsumeTimestamp) {
+ this.lastConsumeTimestamp = lastConsumeTimestamp;
+ }
+
+ public long getCommitOffsetBit() {
+ return commitOffsetBit;
+ }
+
+ public void setCommitOffsetBit(long commitOffsetBit) {
+ this.commitOffsetBit = commitOffsetBit;
+ }
+
+ @JSONField(serialize = false, deserialize = false)
+ public boolean isDone() {
+ if (offsetList == null || offsetList.isEmpty()) {
+ return true;
+ }
+ int num = offsetList.size();
+ for (byte i = 0; i < num; i++) {
+ if ((commitOffsetBit & (1L << i)) == 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @JSONField(serialize = false, deserialize = false)
+ public String encode() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("{").append("\"c\":").append(getConsumedCount());
+ sb.append(",").append("\"cm\":").append(getCommitOffsetBit());
+ sb.append(",").append("\"l\":").append(getLastConsumeTimestamp());
+ sb.append(",").append("\"o\":[");
+ if (getOffsetList() != null) {
+ for (int i = 0; i < getOffsetList().size(); i++) {
+ sb.append(getOffsetList().get(i));
+ if (i < getOffsetList().size() - 1) {
+ sb.append(",");
+ }
+ }
+ }
+ sb.append("]").append("}");
+ return sb.toString();
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("OrderInfo");
+ sb.append("@").append(this.hashCode());
+ sb.append("{offsetList=").append(offsetList);
+ sb.append(", consumedCount=").append(consumedCount);
+ sb.append(", lastConsumeTimestamp=").append(lastConsumeTimestamp);
+ sb.append(", commitOffsetBit=").append(commitOffsetBit);
+ sb.append(", isDone=").append(isDone());
+ sb.append('}');
+ return sb.toString();
+ }
+ }
+}
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 de7f3fce81c7a70a7e9b0eeb1daa6a2f9dd68c4c..252201a058ed4514cf0361d169deb640ba141958 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
@@ -30,8 +30,6 @@ import org.apache.rocketmq.common.MixAll;
import org.apache.rocketmq.common.ThreadFactoryImpl;
import org.apache.rocketmq.common.UtilAll;
import org.apache.rocketmq.common.constant.LoggerName;
-import org.apache.rocketmq.logging.InternalLogger;
-import org.apache.rocketmq.logging.InternalLoggerFactory;
import org.apache.rocketmq.common.namesrv.RegisterBrokerResult;
import org.apache.rocketmq.common.namesrv.TopAddressing;
import org.apache.rocketmq.common.protocol.RequestCode;
@@ -41,15 +39,20 @@ import org.apache.rocketmq.common.protocol.body.KVTable;
import org.apache.rocketmq.common.protocol.body.RegisterBrokerBody;
import org.apache.rocketmq.common.protocol.body.SubscriptionGroupWrapper;
import org.apache.rocketmq.common.protocol.body.TopicConfigSerializeWrapper;
+import org.apache.rocketmq.common.protocol.header.namesrv.GetRouteInfoRequestHeader;
import org.apache.rocketmq.common.protocol.header.namesrv.QueryDataVersionRequestHeader;
import org.apache.rocketmq.common.protocol.header.namesrv.QueryDataVersionResponseHeader;
import org.apache.rocketmq.common.protocol.header.namesrv.RegisterBrokerRequestHeader;
import org.apache.rocketmq.common.protocol.header.namesrv.RegisterBrokerResponseHeader;
import org.apache.rocketmq.common.protocol.header.namesrv.UnRegisterBrokerRequestHeader;
+import org.apache.rocketmq.common.protocol.route.TopicRouteData;
+import org.apache.rocketmq.logging.InternalLogger;
+import org.apache.rocketmq.logging.InternalLoggerFactory;
import org.apache.rocketmq.remoting.RPCHook;
import org.apache.rocketmq.remoting.RemotingClient;
import org.apache.rocketmq.remoting.exception.RemotingCommandException;
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;
@@ -394,4 +397,39 @@ public class BrokerOuterAPI {
public void registerRPCHook(RPCHook rpcHook) {
remotingClient.registerRPCHook(rpcHook);
}
+
+ public TopicRouteData getTopicRouteInfoFromNameServer(final String topic, final long timeoutMillis)
+ throws RemotingException, MQBrokerException, InterruptedException {
+ return getTopicRouteInfoFromNameServer(topic, timeoutMillis, true);
+ }
+
+ public TopicRouteData getTopicRouteInfoFromNameServer(final String topic, final long timeoutMillis,
+ boolean allowTopicNotExist) throws MQBrokerException, InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException {
+ GetRouteInfoRequestHeader requestHeader = new GetRouteInfoRequestHeader();
+ requestHeader.setTopic(topic);
+
+ RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ROUTEINFO_BY_TOPIC, requestHeader);
+
+ RemotingCommand response = this.remotingClient.invokeSync(null, request, timeoutMillis);
+ assert response != null;
+ switch (response.getCode()) {
+ case ResponseCode.TOPIC_NOT_EXIST: {
+ if (allowTopicNotExist) {
+ log.warn("get Topic [{}] RouteInfoFromNameServer is not exist value", topic);
+ }
+
+ break;
+ }
+ case ResponseCode.SUCCESS: {
+ byte[] body = response.getBody();
+ if (body != null) {
+ return TopicRouteData.decode(body, TopicRouteData.class);
+ }
+ }
+ default:
+ break;
+ }
+
+ throw new MQBrokerException(response.getCode(), response.getRemark());
+ }
}
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
new file mode 100644
index 0000000000000000000000000000000000000000..29f750784c0822df56e5d1f7ab7e39fad426e99c
--- /dev/null
+++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/AckMessageProcessor.java
@@ -0,0 +1,188 @@
+/*
+ * 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.processor;
+
+import com.alibaba.fastjson.JSON;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import org.apache.rocketmq.broker.BrokerController;
+import org.apache.rocketmq.broker.util.MsgUtil;
+import org.apache.rocketmq.common.KeyBuilder;
+import org.apache.rocketmq.common.PopAckConstants;
+import org.apache.rocketmq.common.TopicConfig;
+import org.apache.rocketmq.common.constant.LoggerName;
+import org.apache.rocketmq.common.help.FAQUrl;
+import org.apache.rocketmq.common.message.MessageConst;
+import org.apache.rocketmq.common.message.MessageDecoder;
+import org.apache.rocketmq.common.protocol.ResponseCode;
+import org.apache.rocketmq.common.protocol.header.AckMessageRequestHeader;
+import org.apache.rocketmq.common.protocol.header.ExtraInfoUtil;
+import org.apache.rocketmq.common.utils.DataConverter;
+import org.apache.rocketmq.logging.InternalLogger;
+import org.apache.rocketmq.logging.InternalLoggerFactory;
+import org.apache.rocketmq.remoting.common.RemotingHelper;
+import org.apache.rocketmq.remoting.exception.RemotingCommandException;
+import org.apache.rocketmq.remoting.netty.NettyRequestProcessor;
+import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+import org.apache.rocketmq.store.MessageExtBrokerInner;
+import org.apache.rocketmq.store.PutMessageResult;
+import org.apache.rocketmq.store.PutMessageStatus;
+import org.apache.rocketmq.store.pop.AckMsg;
+
+public class AckMessageProcessor implements NettyRequestProcessor {
+ private static final InternalLogger POP_LOGGER = InternalLoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LOGGER_NAME);
+ private final BrokerController brokerController;
+ private String reviveTopic;
+ private PopReviveService[] popReviveServices;
+
+ public AckMessageProcessor(final BrokerController brokerController) {
+ this.brokerController = brokerController;
+ this.reviveTopic = PopAckConstants.REVIVE_TOPIC + this.brokerController.getBrokerConfig().getBrokerClusterName();
+ this.popReviveServices = new PopReviveService[this.brokerController.getBrokerConfig().getReviveQueueNum()];
+ for (int i = 0; i < this.brokerController.getBrokerConfig().getReviveQueueNum(); i++) {
+ this.popReviveServices[i] = new PopReviveService(i, brokerController, reviveTopic);
+ }
+ }
+
+ @Override
+ public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException {
+ return this.processRequest(ctx.channel(), request, true);
+ }
+
+ @Override
+ public boolean rejectRequest() {
+ return false;
+ }
+
+ public void startPopReviveService() {
+ for (PopReviveService popReviveService : popReviveServices) {
+ popReviveService.start();
+ }
+ }
+
+ public void shutdownPopReviveService() {
+ for (PopReviveService popReviveService : popReviveServices) {
+ popReviveService.shutdown();
+ }
+ }
+
+ private RemotingCommand processRequest(final Channel channel, RemotingCommand request, boolean brokerAllowSuspend) throws RemotingCommandException {
+ final AckMessageRequestHeader requestHeader = (AckMessageRequestHeader) request.decodeCommandCustomHeader(AckMessageRequestHeader.class);
+ MessageExtBrokerInner msgInner = new MessageExtBrokerInner();
+ AckMsg ackMsg = new AckMsg();
+ RemotingCommand response = RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, null);
+ response.setOpaque(request.getOpaque());
+ TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(requestHeader.getTopic());
+ if (null == topicConfig) {
+ POP_LOGGER.error("The topic {} not exist, consumer: {} ", requestHeader.getTopic(), RemotingHelper.parseChannelRemoteAddr(channel));
+ response.setCode(ResponseCode.TOPIC_NOT_EXIST);
+ response.setRemark(String.format("topic[%s] not exist, apply first please! %s", requestHeader.getTopic(), FAQUrl.suggestTodo(FAQUrl.APPLY_TOPIC_URL)));
+ return response;
+ }
+
+ if (requestHeader.getQueueId() >= topicConfig.getReadQueueNums() || requestHeader.getQueueId() < 0) {
+ String errorInfo = String.format("queueId[%d] is illegal, topic:[%s] topicConfig.readQueueNums:[%d] consumer:[%s]",
+ requestHeader.getQueueId(), requestHeader.getTopic(), topicConfig.getReadQueueNums(), channel.remoteAddress());
+ POP_LOGGER.warn(errorInfo);
+ response.setCode(ResponseCode.MESSAGE_ILLEGAL);
+ response.setRemark(errorInfo);
+ return response;
+ }
+ long minOffset = this.brokerController.getMessageStore().getMinOffsetInQueue(requestHeader.getTopic(), requestHeader.getQueueId());
+ long maxOffset = this.brokerController.getMessageStore().getMaxOffsetInQueue(requestHeader.getTopic(), requestHeader.getQueueId());
+ if (requestHeader.getOffset() < minOffset || requestHeader.getOffset() > maxOffset) {
+ response.setCode(ResponseCode.NO_MESSAGE);
+ return response;
+ }
+ String[] extraInfo = ExtraInfoUtil.split(requestHeader.getExtraInfo());
+
+ ackMsg.setAckOffset(requestHeader.getOffset());
+ ackMsg.setStartOffset(ExtraInfoUtil.getCkQueueOffset(extraInfo));
+ ackMsg.setConsumerGroup(requestHeader.getConsumerGroup());
+ ackMsg.setTopic(requestHeader.getTopic());
+ ackMsg.setQueueId(requestHeader.getQueueId());
+ ackMsg.setPopTime(ExtraInfoUtil.getPopTime(extraInfo));
+
+ int rqId = ExtraInfoUtil.getReviveQid(extraInfo);
+
+ if (rqId == KeyBuilder.POP_ORDER_REVIVE_QUEUE) {
+ // order
+ String lockKey = requestHeader.getTopic() + PopAckConstants.SPLIT
+ + requestHeader.getConsumerGroup() + PopAckConstants.SPLIT + requestHeader.getQueueId();
+ long oldOffset = this.brokerController.getConsumerOffsetManager().queryOffset(requestHeader.getConsumerGroup(),
+ requestHeader.getTopic(), requestHeader.getQueueId());
+ if (requestHeader.getOffset() < oldOffset) {
+ return response;
+ }
+ while (!this.brokerController.getPopMessageProcessor().getQueueLockManager().tryLock(lockKey)) {
+ }
+ try {
+ oldOffset = this.brokerController.getConsumerOffsetManager().queryOffset(requestHeader.getConsumerGroup(),
+ requestHeader.getTopic(), requestHeader.getQueueId());
+ if (requestHeader.getOffset() < oldOffset) {
+ return response;
+ }
+ long nextOffset = brokerController.getConsumerOrderInfoManager().commitAndNext(
+ requestHeader.getTopic(), requestHeader.getConsumerGroup(),
+ requestHeader.getQueueId(), requestHeader.getOffset());
+ if (nextOffset > -1) {
+ this.brokerController.getConsumerOffsetManager().commitOffset(channel.remoteAddress().toString(),
+ requestHeader.getConsumerGroup(), requestHeader.getTopic(),
+ requestHeader.getQueueId(),
+ nextOffset);
+ this.brokerController.getPopMessageProcessor().notifyMessageArriving(requestHeader.getTopic(), requestHeader.getConsumerGroup(),
+ requestHeader.getQueueId());
+ } else if (nextOffset == -1) {
+ String errorInfo = String.format("offset is illegal, key:%s, old:%d, commit:%d, next:%d, %s",
+ lockKey, oldOffset, requestHeader.getOffset(), nextOffset, channel.remoteAddress());
+ POP_LOGGER.warn(errorInfo);
+ response.setCode(ResponseCode.MESSAGE_ILLEGAL);
+ response.setRemark(errorInfo);
+ return response;
+ }
+ } finally {
+ this.brokerController.getPopMessageProcessor().getQueueLockManager().unLock(lockKey);
+ }
+ return response;
+ }
+
+ if (this.brokerController.getPopMessageProcessor().getPopBufferMergeService().addAk(rqId, ackMsg)) {
+ return response;
+ }
+
+ msgInner.setTopic(reviveTopic);
+ msgInner.setBody(JSON.toJSONString(ackMsg).getBytes(DataConverter.charset));
+ //msgInner.setQueueId(Integer.valueOf(extraInfo[3]));
+ msgInner.setQueueId(rqId);
+ msgInner.setTags(PopAckConstants.ACK_TAG);
+ msgInner.setBornTimestamp(System.currentTimeMillis());
+ msgInner.setBornHost(this.brokerController.getStoreHost());
+ msgInner.setStoreHost(this.brokerController.getStoreHost());
+ MsgUtil.setMessageDeliverTime(this.brokerController, msgInner, ExtraInfoUtil.getPopTime(extraInfo) + ExtraInfoUtil.getInvisibleTime(extraInfo));
+ msgInner.getProperties().put(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, PopMessageProcessor.genAckUniqueId(ackMsg));
+ msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties()));
+ PutMessageResult putMessageResult = this.brokerController.getMessageStore().putMessage(msgInner);
+ if (putMessageResult.getPutMessageStatus() != PutMessageStatus.PUT_OK
+ && putMessageResult.getPutMessageStatus() != PutMessageStatus.FLUSH_DISK_TIMEOUT
+ && putMessageResult.getPutMessageStatus() != PutMessageStatus.FLUSH_SLAVE_TIMEOUT
+ && putMessageResult.getPutMessageStatus() != PutMessageStatus.SLAVE_NOT_AVAILABLE) {
+ POP_LOGGER.error("put ack msg error:" + putMessageResult);
+ }
+ return response;
+ }
+
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..e40a5e8d901e030b3b261d66dda505c05475f583
--- /dev/null
+++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java
@@ -0,0 +1,195 @@
+/*
+ * 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.processor;
+
+import com.alibaba.fastjson.JSON;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import org.apache.rocketmq.broker.BrokerController;
+import org.apache.rocketmq.broker.util.MsgUtil;
+import org.apache.rocketmq.common.PopAckConstants;
+import org.apache.rocketmq.common.TopicConfig;
+import org.apache.rocketmq.common.constant.LoggerName;
+import org.apache.rocketmq.common.help.FAQUrl;
+import org.apache.rocketmq.common.message.MessageConst;
+import org.apache.rocketmq.common.message.MessageDecoder;
+import org.apache.rocketmq.common.protocol.ResponseCode;
+import org.apache.rocketmq.common.protocol.header.ChangeInvisibleTimeRequestHeader;
+import org.apache.rocketmq.common.protocol.header.ChangeInvisibleTimeResponseHeader;
+import org.apache.rocketmq.common.protocol.header.ExtraInfoUtil;
+import org.apache.rocketmq.common.utils.DataConverter;
+import org.apache.rocketmq.logging.InternalLogger;
+import org.apache.rocketmq.logging.InternalLoggerFactory;
+import org.apache.rocketmq.remoting.common.RemotingHelper;
+import org.apache.rocketmq.remoting.exception.RemotingCommandException;
+import org.apache.rocketmq.remoting.netty.NettyRequestProcessor;
+import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+import org.apache.rocketmq.store.MessageExtBrokerInner;
+import org.apache.rocketmq.store.PutMessageResult;
+import org.apache.rocketmq.store.PutMessageStatus;
+import org.apache.rocketmq.store.pop.AckMsg;
+import org.apache.rocketmq.store.pop.PopCheckPoint;
+
+public class ChangeInvisibleTimeProcessor implements NettyRequestProcessor {
+ private static final InternalLogger POP_LOGGER = InternalLoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LOGGER_NAME);
+ private final BrokerController brokerController;
+ private String reviveTopic;
+
+ public ChangeInvisibleTimeProcessor(final BrokerController brokerController) {
+ this.brokerController = brokerController;
+ this.reviveTopic = PopAckConstants.REVIVE_TOPIC + this.brokerController.getBrokerConfig().getBrokerClusterName();
+
+ }
+
+ @Override
+ public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException {
+ return this.processRequest(ctx.channel(), request, true);
+ }
+
+ @Override
+ public boolean rejectRequest() {
+ return false;
+ }
+
+ private RemotingCommand processRequest(final Channel channel, RemotingCommand request, boolean brokerAllowSuspend) throws RemotingCommandException {
+ final ChangeInvisibleTimeRequestHeader requestHeader = (ChangeInvisibleTimeRequestHeader) request.decodeCommandCustomHeader(ChangeInvisibleTimeRequestHeader.class);
+ RemotingCommand response = RemotingCommand.createResponseCommand(ChangeInvisibleTimeResponseHeader.class);
+ response.setCode(ResponseCode.SUCCESS);
+ response.setOpaque(request.getOpaque());
+ final ChangeInvisibleTimeResponseHeader responseHeader = (ChangeInvisibleTimeResponseHeader) response.readCustomHeader();
+ TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(requestHeader.getTopic());
+ if (null == topicConfig) {
+ POP_LOGGER.error("The topic {} not exist, consumer: {} ", requestHeader.getTopic(), RemotingHelper.parseChannelRemoteAddr(channel));
+ response.setCode(ResponseCode.TOPIC_NOT_EXIST);
+ response.setRemark(String.format("topic[%s] not exist, apply first please! %s", requestHeader.getTopic(), FAQUrl.suggestTodo(FAQUrl.APPLY_TOPIC_URL)));
+ return response;
+ }
+
+ if (requestHeader.getQueueId() >= topicConfig.getReadQueueNums() || requestHeader.getQueueId() < 0) {
+ String errorInfo = String.format("queueId[%d] is illegal, topic:[%s] topicConfig.readQueueNums:[%d] consumer:[%s]",
+ requestHeader.getQueueId(), requestHeader.getTopic(), topicConfig.getReadQueueNums(), channel.remoteAddress());
+ POP_LOGGER.warn(errorInfo);
+ response.setCode(ResponseCode.MESSAGE_ILLEGAL);
+ response.setRemark(errorInfo);
+ return response;
+ }
+ long minOffset = this.brokerController.getMessageStore().getMinOffsetInQueue(requestHeader.getTopic(), requestHeader.getQueueId());
+ long maxOffset = this.brokerController.getMessageStore().getMaxOffsetInQueue(requestHeader.getTopic(), requestHeader.getQueueId());
+ if (requestHeader.getOffset() < minOffset || requestHeader.getOffset() > maxOffset) {
+ response.setCode(ResponseCode.NO_MESSAGE);
+ return response;
+ }
+
+ String[] extraInfo = ExtraInfoUtil.split(requestHeader.getExtraInfo());
+
+ // add new ck
+ long now = System.currentTimeMillis();
+ PutMessageResult ckResult = appendCheckPoint(requestHeader, ExtraInfoUtil.getReviveQid(extraInfo), requestHeader.getQueueId(), requestHeader.getOffset(), now);
+
+ if (ckResult.getPutMessageStatus() != PutMessageStatus.PUT_OK
+ && ckResult.getPutMessageStatus() != PutMessageStatus.FLUSH_DISK_TIMEOUT
+ && ckResult.getPutMessageStatus() != PutMessageStatus.FLUSH_SLAVE_TIMEOUT
+ && ckResult.getPutMessageStatus() != PutMessageStatus.SLAVE_NOT_AVAILABLE) {
+ POP_LOGGER.error("change Invisible, put new ck error: {}", ckResult);
+ response.setCode(ResponseCode.SYSTEM_ERROR);
+ return response;
+ }
+
+ // ack old msg.
+ try {
+ ackOrigin(requestHeader, extraInfo);
+ } catch (Throwable e) {
+ POP_LOGGER.error("change Invisible, put ack msg error: {}, {}", requestHeader.getExtraInfo(), e.getMessage());
+ // cancel new ck?
+ }
+
+ responseHeader.setInvisibleTime(requestHeader.getInvisibleTime());
+ responseHeader.setPopTime(now);
+ responseHeader.setReviveQid(ExtraInfoUtil.getReviveQid(extraInfo));
+ return response;
+ }
+
+ private void ackOrigin(final ChangeInvisibleTimeRequestHeader requestHeader, String[] extraInfo) {
+ MessageExtBrokerInner msgInner = new MessageExtBrokerInner();
+ AckMsg ackMsg = new AckMsg();
+
+ ackMsg.setAckOffset(requestHeader.getOffset());
+ ackMsg.setStartOffset(ExtraInfoUtil.getCkQueueOffset(extraInfo));
+ ackMsg.setConsumerGroup(requestHeader.getConsumerGroup());
+ ackMsg.setTopic(requestHeader.getTopic());
+ ackMsg.setQueueId(requestHeader.getQueueId());
+ ackMsg.setPopTime(ExtraInfoUtil.getPopTime(extraInfo));
+
+ int rqId = ExtraInfoUtil.getReviveQid(extraInfo);
+
+ if (brokerController.getPopMessageProcessor().getPopBufferMergeService().addAk(rqId, ackMsg)) {
+ return;
+ }
+
+ msgInner.setTopic(reviveTopic);
+ msgInner.setBody(JSON.toJSONString(ackMsg).getBytes(DataConverter.charset));
+ msgInner.setQueueId(rqId);
+ msgInner.setTags(PopAckConstants.ACK_TAG);
+ msgInner.setBornTimestamp(System.currentTimeMillis());
+ msgInner.setBornHost(this.brokerController.getStoreHost());
+ msgInner.setStoreHost(this.brokerController.getStoreHost());
+ MsgUtil.setMessageDeliverTime(this.brokerController, msgInner, ExtraInfoUtil.getPopTime(extraInfo) + ExtraInfoUtil.getInvisibleTime(extraInfo));
+ msgInner.getProperties().put(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, PopMessageProcessor.genAckUniqueId(ackMsg));
+ msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties()));
+ PutMessageResult putMessageResult = this.brokerController.getMessageStore().putMessage(msgInner);
+ if (putMessageResult.getPutMessageStatus() != PutMessageStatus.PUT_OK
+ && putMessageResult.getPutMessageStatus() != PutMessageStatus.FLUSH_DISK_TIMEOUT
+ && putMessageResult.getPutMessageStatus() != PutMessageStatus.FLUSH_SLAVE_TIMEOUT
+ && putMessageResult.getPutMessageStatus() != PutMessageStatus.SLAVE_NOT_AVAILABLE) {
+ POP_LOGGER.error("change Invisible, put ack msg fail: {}, {}", ackMsg, putMessageResult);
+ }
+ }
+
+ private PutMessageResult appendCheckPoint(final ChangeInvisibleTimeRequestHeader requestHeader, int reviveQid, int queueId, long offset, long popTime) {
+ // add check point msg to revive log
+ MessageExtBrokerInner msgInner = new MessageExtBrokerInner();
+ msgInner.setTopic(reviveTopic);
+ PopCheckPoint ck = new PopCheckPoint();
+ ck.setBitMap(0);
+ ck.setNum((byte) 1);
+ ck.setPopTime(popTime);
+ ck.setInvisibleTime(requestHeader.getInvisibleTime());
+ ck.getStartOffset(offset);
+ ck.setCId(requestHeader.getConsumerGroup());
+ ck.setTopic(requestHeader.getTopic());
+ ck.setQueueId((byte) queueId);
+ ck.addDiff(0);
+
+ msgInner.setBody(JSON.toJSONString(ck).getBytes(DataConverter.charset));
+ msgInner.setQueueId(reviveQid);
+ msgInner.setTags(PopAckConstants.CK_TAG);
+ msgInner.setBornTimestamp(System.currentTimeMillis());
+ msgInner.setBornHost(this.brokerController.getStoreHost());
+ msgInner.setStoreHost(this.brokerController.getStoreHost());
+ MsgUtil.setMessageDeliverTime(this.brokerController, msgInner, ck.getReviveTime() - PopAckConstants.ackTimeInterval);
+ msgInner.getProperties().put(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, PopMessageProcessor.genCkUniqueId(ck));
+ msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties()));
+ PutMessageResult putMessageResult = this.brokerController.getMessageStore().putMessage(msgInner);
+
+ if (brokerController.getBrokerConfig().isEnablePopLog()) {
+ POP_LOGGER.info("change Invisible , appendCheckPoint, topic {}, queueId {},reviveId {}, cid {}, startOffset {}, rt {}, result {}", requestHeader.getTopic(), queueId, reviveQid, requestHeader.getConsumerGroup(), offset,
+ ck.getReviveTime(), putMessageResult);
+ }
+
+ return putMessageResult;
+ }
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..615a70e4c91899d47689d33678d18578818d9669
--- /dev/null
+++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopBufferMergeService.java
@@ -0,0 +1,731 @@
+/*
+ * 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.processor;
+
+import com.alibaba.fastjson.JSON;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.LinkedBlockingDeque;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.apache.rocketmq.broker.BrokerController;
+import org.apache.rocketmq.broker.util.MsgUtil;
+import org.apache.rocketmq.common.KeyBuilder;
+import org.apache.rocketmq.common.PopAckConstants;
+import org.apache.rocketmq.common.ServiceThread;
+import org.apache.rocketmq.common.constant.LoggerName;
+import org.apache.rocketmq.common.message.MessageConst;
+import org.apache.rocketmq.common.message.MessageDecoder;
+import org.apache.rocketmq.common.utils.DataConverter;
+import org.apache.rocketmq.logging.InternalLogger;
+import org.apache.rocketmq.logging.InternalLoggerFactory;
+import org.apache.rocketmq.store.MessageExtBrokerInner;
+import org.apache.rocketmq.store.PutMessageResult;
+import org.apache.rocketmq.store.PutMessageStatus;
+import org.apache.rocketmq.store.config.BrokerRole;
+import org.apache.rocketmq.store.pop.AckMsg;
+import org.apache.rocketmq.store.pop.PopCheckPoint;
+
+public class PopBufferMergeService extends ServiceThread {
+ private static final InternalLogger POP_LOGGER = InternalLoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LOGGER_NAME);
+ ConcurrentHashMap
+ buffer = new ConcurrentHashMap<>(1024 * 16);
+ ConcurrentHashMap> commitOffsets =
+ new ConcurrentHashMap<>();
+ private volatile boolean serving = true;
+ private AtomicInteger counter = new AtomicInteger(0);
+ private int scanTimes = 0;
+ private final BrokerController brokerController;
+ private final PopMessageProcessor popMessageProcessor;
+ private final PopMessageProcessor.QueueLockManager queueLockManager;
+ private final long interval = 5;
+ private final long minute5 = 5 * 60 * 1000;
+ private final int countOfMinute1 = (int) (60 * 1000 / interval);
+ private final int countOfSecond1 = (int) (1000 / interval);
+ private final int countOfSecond30 = (int) (30 * 1000 / interval);
+
+ private volatile boolean master = false;
+
+ public PopBufferMergeService(BrokerController brokerController, PopMessageProcessor popMessageProcessor) {
+ super();
+ this.brokerController = brokerController;
+ this.popMessageProcessor = popMessageProcessor;
+ this.queueLockManager = popMessageProcessor.getQueueLockManager();
+ }
+
+ private boolean checkAndSetMaster() {
+ this.master = brokerController.getMessageStoreConfig().getBrokerRole() != BrokerRole.SLAVE;
+ return this.master;
+ }
+
+ @Override
+ public String getServiceName() {
+ return "PopBufferMergeService";
+ }
+
+ @Override
+ public void run() {
+ // scan
+ while (!this.isStopped()) {
+ try {
+ if (!checkAndSetMaster()) {
+ // slave
+ this.waitForRunning(interval * 200 * 5);
+ POP_LOGGER.info("Broker is {}, {}, clear all data",
+ brokerController.getMessageStoreConfig().getBrokerRole(), this.master);
+ this.buffer.clear();
+ this.commitOffsets.clear();
+ continue;
+ }
+
+ scan();
+ if (scanTimes % countOfSecond30 == 0) {
+ scanGarbage();
+ }
+
+ this.waitForRunning(interval);
+
+ if (!this.serving && this.buffer.size() == 0 && totalSize() == 0) {
+ this.serving = true;
+ }
+ } catch (Throwable e) {
+ POP_LOGGER.error("PopBufferMergeService error", e);
+ this.waitForRunning(3000);
+ }
+ }
+
+ this.serving = false;
+ try {
+ Thread.sleep(2000);
+ } catch (InterruptedException e) {
+ }
+ if (!checkAndSetMaster()) {
+ return;
+ }
+ while (this.buffer.size() > 0 || totalSize() > 0) {
+ scan();
+ }
+ }
+
+ private int scanCommitOffset() {
+ Iterator>> iterator = this.commitOffsets.entrySet().iterator();
+ int count = 0;
+ while (iterator.hasNext()) {
+ Map.Entry> entry = iterator.next();
+ LinkedBlockingDeque queue = entry.getValue().get();
+ PopCheckPointWrapper pointWrapper;
+ while ((pointWrapper = queue.peek()) != null) {
+ // 1. just offset & stored, not processed by scan
+ // 2. ck is buffer(acked)
+ // 3. ck is buffer(not all acked), all ak are stored and ck is stored
+ if ((pointWrapper.isJustOffset() && pointWrapper.isCkStored()) || isCkDone(pointWrapper)
+ || (isCkDoneForFinish(pointWrapper) && pointWrapper.isCkStored())) {
+ if (commitOffset(pointWrapper)) {
+ queue.poll();
+ } else {
+ break;
+ }
+ } else {
+ if (System.currentTimeMillis() - pointWrapper.getCk().getPopTime()
+ > brokerController.getBrokerConfig().getPopCkStayBufferTime() * 2) {
+ POP_LOGGER.warn("[PopBuffer] ck offset long time not commit, {}", pointWrapper);
+ }
+ break;
+ }
+ }
+ final int qs = queue.size();
+ count += qs;
+ if (qs > 5000 && scanTimes % countOfSecond1 == 0) {
+ POP_LOGGER.info("[PopBuffer] offset queue size too long, {}, {}",
+ entry.getKey(), qs);
+ }
+ }
+ return count;
+ }
+
+ public long getLatestOffset(String lockKey) {
+ QueueWithTime queue = this.commitOffsets.get(lockKey);
+ if (queue == null) {
+ return -1;
+ }
+ PopCheckPointWrapper pointWrapper = queue.get().peekLast();
+ if (pointWrapper != null) {
+ return pointWrapper.getNextBeginOffset();
+ }
+ return -1;
+ }
+
+ public long getLatestOffset(String topic, String group, int queueId) {
+ return getLatestOffset(KeyBuilder.buildPollingKey(topic, group, queueId));
+ }
+
+ private void scanGarbage() {
+ Iterator>> iterator = commitOffsets.entrySet().iterator();
+ while (iterator.hasNext()) {
+ Map.Entry> entry = iterator.next();
+ if (entry.getKey() == null) {
+ continue;
+ }
+ String[] keyArray = entry.getKey().split(PopAckConstants.SPLIT);
+ if (keyArray == null || keyArray.length != 3) {
+ continue;
+ }
+ String topic = keyArray[0];
+ String cid = keyArray[1];
+ if (brokerController.getTopicConfigManager().selectTopicConfig(topic) == null) {
+ POP_LOGGER.info("[PopBuffer]remove not exit topic {} in buffer!", topic);
+ iterator.remove();
+ continue;
+ }
+ if (!brokerController.getSubscriptionGroupManager().getSubscriptionGroupTable().containsKey(cid)) {
+ POP_LOGGER.info("[PopBuffer]remove not exit sub {} of topic {} in buffer!", cid, topic);
+ iterator.remove();
+ continue;
+ }
+ if (System.currentTimeMillis() - entry.getValue().getTime() > minute5) {
+ POP_LOGGER.info("[PopBuffer]remove long time not used sub {} of topic {} in buffer!", cid, topic);
+ iterator.remove();
+ continue;
+ }
+ }
+ }
+
+ private void scan() {
+ long startTime = System.currentTimeMillis();
+ int count = 0, countCk = 0;
+ Iterator> iterator = buffer.entrySet().iterator();
+ while (iterator.hasNext()) {
+ Map.Entry entry = iterator.next();
+ PopCheckPointWrapper pointWrapper = entry.getValue();
+
+ // just process offset(already stored at pull thread), or buffer ck(not stored and ack finish)
+ if ((pointWrapper.isJustOffset() && pointWrapper.isCkStored()) || isCkDone(pointWrapper)
+ || (isCkDoneForFinish(pointWrapper) && pointWrapper.isCkStored())) {
+ if (brokerController.getBrokerConfig().isEnablePopLog()) {
+ POP_LOGGER.info("[PopBuffer]ck done, {}", pointWrapper);
+ }
+ iterator.remove();
+ counter.decrementAndGet();
+ continue;
+ }
+
+ PopCheckPoint point = pointWrapper.getCk();
+ long now = System.currentTimeMillis();
+
+ boolean removeCk = !this.serving;
+ // ck will be timeout
+ if (point.getReviveTime() - now < brokerController.getBrokerConfig().getPopCkStayBufferTimeOut()) {
+ removeCk = true;
+ }
+
+ // the time stayed is too long
+ if (now - point.getPopTime() > brokerController.getBrokerConfig().getPopCkStayBufferTime()) {
+ removeCk = true;
+ }
+
+ if (now - point.getPopTime() > brokerController.getBrokerConfig().getPopCkStayBufferTime() * 2L) {
+ POP_LOGGER.warn("[PopBuffer]ck finish fail, stay too long, {}", pointWrapper);
+ }
+
+ // double check
+ if (isCkDone(pointWrapper)) {
+ continue;
+ } else if (pointWrapper.isJustOffset()) {
+ // just offset should be in store.
+ if (pointWrapper.getReviveQueueOffset() < 0) {
+ putCkToStore(pointWrapper, false);
+ countCk++;
+ }
+ continue;
+ } else if (removeCk) {
+ // put buffer ak to store
+ if (pointWrapper.getReviveQueueOffset() < 0) {
+ putCkToStore(pointWrapper, false);
+ countCk++;
+ }
+
+ if (!pointWrapper.isCkStored()) {
+ continue;
+ }
+
+ for (byte i = 0; i < point.getNum(); i++) {
+ // reput buffer ak to store
+ if (DataConverter.getBit(pointWrapper.getBits().get(), i)
+ && !DataConverter.getBit(pointWrapper.getToStoreBits().get(), i)) {
+ if (putAckToStore(pointWrapper, i)) {
+ count++;
+ markBitCAS(pointWrapper.getToStoreBits(), i);
+ }
+ }
+ }
+
+ if (isCkDoneForFinish(pointWrapper) && pointWrapper.isCkStored()) {
+ if (brokerController.getBrokerConfig().isEnablePopLog()) {
+ POP_LOGGER.info("[PopBuffer]ck finish, {}", pointWrapper);
+ }
+ iterator.remove();
+ counter.decrementAndGet();
+ continue;
+ }
+ }
+ }
+
+ int offsetBufferSize = scanCommitOffset();
+
+ long eclipse = System.currentTimeMillis() - startTime;
+ if (eclipse > brokerController.getBrokerConfig().getPopCkStayBufferTimeOut() - 1000) {
+ POP_LOGGER.warn("[PopBuffer]scan stop, because eclipse too long, PopBufferEclipse={}, " +
+ "PopBufferToStoreAck={}, PopBufferToStoreCk={}, PopBufferSize={}, PopBufferOffsetSize={}",
+ eclipse, count, countCk, counter.get(), offsetBufferSize);
+ this.serving = false;
+ } else {
+ if (scanTimes % countOfSecond1 == 0) {
+ POP_LOGGER.info("[PopBuffer]scan, PopBufferEclipse={}, " +
+ "PopBufferToStoreAck={}, PopBufferToStoreCk={}, PopBufferSize={}, PopBufferOffsetSize={}",
+ eclipse, count, countCk, counter.get(), offsetBufferSize);
+ }
+ }
+ scanTimes++;
+
+ if (scanTimes >= countOfMinute1) {
+ counter.set(this.buffer.size());
+ scanTimes = 0;
+ }
+ }
+
+ private int totalSize() {
+ int count = 0;
+ Iterator>> iterator = this.commitOffsets.entrySet().iterator();
+ while (iterator.hasNext()) {
+ Map.Entry> entry = iterator.next();
+ LinkedBlockingDeque queue = entry.getValue().get();
+ count += queue.size();
+ }
+ return count;
+ }
+
+ private void markBitCAS(AtomicInteger setBits, int index) {
+ while (true) {
+ int bits = setBits.get();
+ if (DataConverter.getBit(bits, index)) {
+ break;
+ }
+
+ int newBits = DataConverter.setBit(bits, index, true);
+ if (setBits.compareAndSet(bits, newBits)) {
+ break;
+ }
+ }
+ }
+
+ private boolean commitOffset(final PopCheckPointWrapper wrapper) {
+ if (wrapper.getNextBeginOffset() < 0) {
+ return true;
+ }
+
+ final PopCheckPoint popCheckPoint = wrapper.getCk();
+ final String lockKey = wrapper.getLockKey();
+
+ if (!queueLockManager.tryLock(lockKey)) {
+ return false;
+ }
+ try {
+ final long offset = brokerController.getConsumerOffsetManager().queryOffset(popCheckPoint.getCId(), popCheckPoint.getTopic(), popCheckPoint.getQueueId());
+ if (wrapper.getNextBeginOffset() > offset) {
+ if (brokerController.getBrokerConfig().isEnablePopLog()) {
+ POP_LOGGER.info("Commit offset, {}, {}", wrapper, offset);
+ }
+ } else {
+ // maybe store offset is not correct.
+ POP_LOGGER.warn("Commit offset, consumer offset less than store, {}, {}", wrapper, offset);
+ }
+ brokerController.getConsumerOffsetManager().commitOffset(getServiceName(),
+ popCheckPoint.getCId(), popCheckPoint.getTopic(), popCheckPoint.getQueueId(), wrapper.getNextBeginOffset());
+ } finally {
+ queueLockManager.unLock(lockKey);
+ }
+ return true;
+ }
+
+ private boolean putOffsetQueue(PopCheckPointWrapper pointWrapper) {
+ QueueWithTime queue = this.commitOffsets.get(pointWrapper.getLockKey());
+ if (queue == null) {
+ queue = new QueueWithTime<>();
+ QueueWithTime old = this.commitOffsets.putIfAbsent(pointWrapper.getLockKey(), queue);
+ if (old != null) {
+ queue = old;
+ }
+ }
+ queue.setTime(pointWrapper.getCk().getPopTime());
+ return queue.get().offer(pointWrapper);
+ }
+
+ private boolean checkQueueOk(PopCheckPointWrapper pointWrapper) {
+ QueueWithTime queue = this.commitOffsets.get(pointWrapper.getLockKey());
+ if (queue == null) {
+ return true;
+ }
+ return queue.get().size() < brokerController.getBrokerConfig().getPopCkOffsetMaxQueueSize();
+ }
+
+ /**
+ * put to store && add to buffer.
+ * @param point
+ * @param reviveQueueId
+ * @param reviveQueueOffset
+ * @param nextBeginOffset
+ * @return
+ */
+ public void addCkJustOffset(PopCheckPoint point, int reviveQueueId, long reviveQueueOffset, long nextBeginOffset) {
+ PopCheckPointWrapper pointWrapper = new PopCheckPointWrapper(reviveQueueId, reviveQueueOffset, point, nextBeginOffset, true);
+
+ this.putCkToStore(pointWrapper, !checkQueueOk(pointWrapper));
+
+ putOffsetQueue(pointWrapper);
+ this.buffer.put(pointWrapper.getMergeKey(), pointWrapper);
+ this.counter.incrementAndGet();
+ if (brokerController.getBrokerConfig().isEnablePopLog()) {
+ POP_LOGGER.info("[PopBuffer]add ck just offset, {}", pointWrapper);
+ }
+ }
+
+ public void addCkMock(String group, String topic, int queueId, long startOffset, long invisibleTime,
+ long popTime, int reviveQueueId, long nextBeginOffset) {
+ final PopCheckPoint ck = new PopCheckPoint();
+ ck.setBitMap(0);
+ ck.setNum((byte) 0);
+ ck.setPopTime(popTime);
+ ck.setInvisibleTime(invisibleTime);
+ ck.getStartOffset(startOffset);
+ ck.setCId(group);
+ ck.setTopic(topic);
+ ck.setQueueId((byte) queueId);
+
+ PopCheckPointWrapper pointWrapper = new PopCheckPointWrapper(reviveQueueId, Long.MAX_VALUE, ck, nextBeginOffset, true);
+ pointWrapper.setCkStored(true);
+
+ putOffsetQueue(pointWrapper);
+ if (brokerController.getBrokerConfig().isEnablePopLog()) {
+ POP_LOGGER.info("[PopBuffer]add ck just offset, mocked, {}", pointWrapper);
+ }
+ }
+
+ public boolean addCk(PopCheckPoint point, int reviveQueueId, long reviveQueueOffset, long nextBeginOffset) {
+ // key: point.getT() + point.getC() + point.getQ() + point.getSo() + point.getPt()
+ if (!brokerController.getBrokerConfig().isEnablePopBufferMerge()) {
+ return false;
+ }
+ if (!serving) {
+ return false;
+ }
+
+ long now = System.currentTimeMillis();
+ if (point.getReviveTime() - now < brokerController.getBrokerConfig().getPopCkStayBufferTimeOut() + 1500) {
+ if (brokerController.getBrokerConfig().isEnablePopLog()) {
+ POP_LOGGER.warn("[PopBuffer]add ck, timeout, {}, {}", point, now);
+ }
+ return false;
+ }
+
+ if (this.counter.get() > brokerController.getBrokerConfig().getPopCkMaxBufferSize()) {
+ POP_LOGGER.warn("[PopBuffer]add ck, max size, {}, {}", point, this.counter.get());
+ return false;
+ }
+
+ PopCheckPointWrapper pointWrapper = new PopCheckPointWrapper(reviveQueueId, reviveQueueOffset, point, nextBeginOffset);
+
+ if (!checkQueueOk(pointWrapper)) {
+ return false;
+ }
+
+ putOffsetQueue(pointWrapper);
+ this.buffer.put(pointWrapper.getMergeKey(), pointWrapper);
+ this.counter.incrementAndGet();
+ if (brokerController.getBrokerConfig().isEnablePopLog()) {
+ POP_LOGGER.info("[PopBuffer]add ck, {}", pointWrapper);
+ }
+ return true;
+ }
+
+ public boolean addAk(int reviveQid, AckMsg ackMsg) {
+ if (!brokerController.getBrokerConfig().isEnablePopBufferMerge()) {
+ return false;
+ }
+ if (!serving) {
+ return false;
+ }
+ try {
+ PopCheckPointWrapper pointWrapper = this.buffer.get(ackMsg.getTopic() + ackMsg.getConsumerGroup() + ackMsg.getQueueId() + ackMsg.getStartOffset() + ackMsg.getPopTime());
+ if (pointWrapper == null) {
+ if (brokerController.getBrokerConfig().isEnablePopLog()) {
+ POP_LOGGER.warn("[PopBuffer]add ack fail, rqId={}, no ck, {}", reviveQid, ackMsg);
+ }
+ return false;
+ }
+
+ if (pointWrapper.isJustOffset()) {
+ return false;
+ }
+
+ PopCheckPoint point = pointWrapper.getCk();
+ long now = System.currentTimeMillis();
+
+ if (point.getReviveTime() - now < brokerController.getBrokerConfig().getPopCkStayBufferTimeOut() + 1500) {
+ if (brokerController.getBrokerConfig().isEnablePopLog()) {
+ POP_LOGGER.warn("[PopBuffer]add ack fail, rqId={}, almost timeout for revive, {}, {}, {}", reviveQid, pointWrapper, ackMsg, now);
+ }
+ return false;
+ }
+
+ if (now - point.getPopTime() > brokerController.getBrokerConfig().getPopCkStayBufferTime() - 1500) {
+ if (brokerController.getBrokerConfig().isEnablePopLog()) {
+ POP_LOGGER.warn("[PopBuffer]add ack fail, rqId={}, stay too long, {}, {}, {}", reviveQid, pointWrapper, ackMsg, now);
+ }
+ return false;
+ }
+
+ int indexOfAck = point.indexOfAck(ackMsg.getAckOffset());
+ if (indexOfAck > -1) {
+ markBitCAS(pointWrapper.getBits(), indexOfAck);
+ } else {
+ POP_LOGGER.error("[PopBuffer]Invalid index of ack, reviveQid={}, {}, {}", reviveQid, ackMsg, point);
+ return true;
+ }
+
+ if (brokerController.getBrokerConfig().isEnablePopLog()) {
+ POP_LOGGER.info("[PopBuffer]add ack, rqId={}, {}, {}", reviveQid, pointWrapper, ackMsg);
+ }
+
+// // check ak done
+// if (isCkDone(pointWrapper)) {
+// // cancel ck for timer
+// cancelCkTimer(pointWrapper);
+// }
+ return true;
+ } catch (Throwable e) {
+ POP_LOGGER.error("[PopBuffer]add ack error, rqId=" + reviveQid + ", " + ackMsg, e);
+ }
+
+ return false;
+ }
+
+ private void putCkToStore(final PopCheckPointWrapper pointWrapper, final boolean runInCurrent) {
+ if (pointWrapper.getReviveQueueOffset() >= 0) {
+ return;
+ }
+ MessageExtBrokerInner msgInner = popMessageProcessor.buildCkMsg(pointWrapper.getCk(), pointWrapper.getReviveQueueId());
+ PutMessageResult putMessageResult = brokerController.getMessageStore().putMessage(msgInner);
+ if (putMessageResult.getPutMessageStatus() != PutMessageStatus.PUT_OK
+ && putMessageResult.getPutMessageStatus() != PutMessageStatus.FLUSH_DISK_TIMEOUT
+ && putMessageResult.getPutMessageStatus() != PutMessageStatus.FLUSH_SLAVE_TIMEOUT
+ && putMessageResult.getPutMessageStatus() != PutMessageStatus.SLAVE_NOT_AVAILABLE) {
+ POP_LOGGER.error("[PopBuffer]put ck to store fail: {}, {}", pointWrapper, putMessageResult);
+ return;
+ }
+ pointWrapper.setCkStored(true);
+ pointWrapper.setReviveQueueOffset(putMessageResult.getAppendMessageResult().getLogicsOffset());
+ if (brokerController.getBrokerConfig().isEnablePopLog()) {
+ POP_LOGGER.info("[PopBuffer]put ck to store ok: {}, {}", pointWrapper, putMessageResult);
+ }
+ }
+
+ private boolean putAckToStore(final PopCheckPointWrapper pointWrapper, byte msgIndex) {
+ PopCheckPoint point = pointWrapper.getCk();
+ MessageExtBrokerInner msgInner = new MessageExtBrokerInner();
+ final AckMsg ackMsg = new AckMsg();
+
+ ackMsg.setAckOffset(point.ackOffsetByIndex(msgIndex));
+ ackMsg.setStartOffset(point.getStartOffset());
+ ackMsg.setConsumerGroup(point.getCId());
+ ackMsg.setTopic(point.getTopic());
+ ackMsg.setQueueId(point.getQueueId());
+ ackMsg.setPopTime(point.getPopTime());
+ msgInner.setTopic(popMessageProcessor.reviveTopic);
+ msgInner.setBody(JSON.toJSONString(ackMsg).getBytes(DataConverter.charset));
+ msgInner.setQueueId(pointWrapper.getReviveQueueId());
+ msgInner.setTags(PopAckConstants.ACK_TAG);
+ msgInner.setBornTimestamp(System.currentTimeMillis());
+ msgInner.setBornHost(brokerController.getStoreHost());
+ msgInner.setStoreHost(brokerController.getStoreHost());
+
+ MsgUtil.setMessageDeliverTime(this.brokerController, msgInner, point.getReviveTime());
+ msgInner.getProperties().put(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, PopMessageProcessor.genAckUniqueId(ackMsg));
+
+ msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties()));
+ PutMessageResult putMessageResult = brokerController.getMessageStore().putMessage(msgInner);
+ if (putMessageResult.getPutMessageStatus() != PutMessageStatus.PUT_OK
+ && putMessageResult.getPutMessageStatus() != PutMessageStatus.FLUSH_DISK_TIMEOUT
+ && putMessageResult.getPutMessageStatus() != PutMessageStatus.FLUSH_SLAVE_TIMEOUT
+ && putMessageResult.getPutMessageStatus() != PutMessageStatus.SLAVE_NOT_AVAILABLE) {
+ POP_LOGGER.error("[PopBuffer]put ack to store fail: {}, {}, {}", pointWrapper, ackMsg, putMessageResult);
+ return false;
+ }
+ if (brokerController.getBrokerConfig().isEnablePopLog()) {
+ POP_LOGGER.info("[PopBuffer]put ack to store ok: {}, {}, {}", pointWrapper, ackMsg, putMessageResult);
+ }
+
+ return true;
+ }
+
+ private boolean isCkDone(PopCheckPointWrapper pointWrapper) {
+ byte num = pointWrapper.getCk().getNum();
+ for (byte i = 0; i < num; i++) {
+ if (!DataConverter.getBit(pointWrapper.getBits().get(), i)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean isCkDoneForFinish(PopCheckPointWrapper pointWrapper) {
+ byte num = pointWrapper.getCk().getNum();
+ int bits = pointWrapper.getBits().get() ^ pointWrapper.getToStoreBits().get();
+ for (byte i = 0; i < num; i++) {
+ if (DataConverter.getBit(bits, i)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public class QueueWithTime {
+ private final LinkedBlockingDeque queue;
+ private long time;
+
+ public QueueWithTime() {
+ this.queue = new LinkedBlockingDeque<>();
+ this.time = System.currentTimeMillis();
+ }
+
+ public void setTime(long popTime) {
+ this.time = popTime;
+ }
+
+ public long getTime() {
+ return time;
+ }
+
+ public LinkedBlockingDeque get() {
+ return queue;
+ }
+ }
+
+ public class PopCheckPointWrapper {
+ private final int reviveQueueId;
+ // -1: not stored, >=0: stored, Long.MAX: storing.
+ private volatile long reviveQueueOffset;
+ private final PopCheckPoint ck;
+ // bit for concurrent
+ private final AtomicInteger bits;
+ // bit for stored buffer ak
+ private final AtomicInteger toStoreBits;
+ private final long nextBeginOffset;
+ private final String lockKey;
+ private final String mergeKey;
+ private final boolean justOffset;
+ private volatile boolean ckStored = false;
+
+ public PopCheckPointWrapper(int reviveQueueId, long reviveQueueOffset, PopCheckPoint point, long nextBeginOffset) {
+ this.reviveQueueId = reviveQueueId;
+ this.reviveQueueOffset = reviveQueueOffset;
+ this.ck = point;
+ this.bits = new AtomicInteger(0);
+ this.toStoreBits = new AtomicInteger(0);
+ this.nextBeginOffset = nextBeginOffset;
+ this.lockKey = ck.getTopic() + PopAckConstants.SPLIT + ck.getCId() + PopAckConstants.SPLIT + ck.getQueueId();
+ this.mergeKey = point.getTopic() + point.getCId() + point.getQueueId() + point.getStartOffset() + point.getPopTime();
+ this.justOffset = false;
+ }
+
+ public PopCheckPointWrapper(int reviveQueueId, long reviveQueueOffset, PopCheckPoint point, long nextBeginOffset,
+ boolean justOffset) {
+ this.reviveQueueId = reviveQueueId;
+ this.reviveQueueOffset = reviveQueueOffset;
+ this.ck = point;
+ this.bits = new AtomicInteger(0);
+ this.toStoreBits = new AtomicInteger(0);
+ this.nextBeginOffset = nextBeginOffset;
+ this.lockKey = ck.getTopic() + PopAckConstants.SPLIT + ck.getCId() + PopAckConstants.SPLIT + ck.getQueueId();
+ this.mergeKey = point.getTopic() + point.getCId() + point.getQueueId() + point.getStartOffset() + point.getPopTime();
+ this.justOffset = justOffset;
+ }
+
+ public int getReviveQueueId() {
+ return reviveQueueId;
+ }
+
+ public long getReviveQueueOffset() {
+ return reviveQueueOffset;
+ }
+
+ public boolean isCkStored() {
+ return ckStored;
+ }
+
+ public void setReviveQueueOffset(long reviveQueueOffset) {
+ this.reviveQueueOffset = reviveQueueOffset;
+ }
+
+ public PopCheckPoint getCk() {
+ return ck;
+ }
+
+ public AtomicInteger getBits() {
+ return bits;
+ }
+
+ public AtomicInteger getToStoreBits() {
+ return toStoreBits;
+ }
+
+ public long getNextBeginOffset() {
+ return nextBeginOffset;
+ }
+
+ public String getLockKey() {
+ return lockKey;
+ }
+
+ public String getMergeKey() {
+ return mergeKey;
+ }
+
+ public boolean isJustOffset() {
+ return justOffset;
+ }
+
+ public void setCkStored(boolean ckStored) {
+ this.ckStored = ckStored;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("CkWrap{");
+ sb.append("rq=").append(reviveQueueId);
+ sb.append(", rqo=").append(reviveQueueOffset);
+ sb.append(", ck=").append(ck);
+ sb.append(", bits=").append(bits);
+ sb.append(", sBits=").append(toStoreBits);
+ sb.append(", nbo=").append(nextBeginOffset);
+ sb.append(", cks=").append(ckStored);
+ sb.append(", jo=").append(justOffset);
+ sb.append('}');
+ return sb.toString();
+ }
+ }
+
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..aa97fc84d057304b2ac60cc4fe5d1cb75b4203d5
--- /dev/null
+++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java
@@ -0,0 +1,967 @@
+/*
+ * 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.processor;
+
+import com.alibaba.fastjson.JSON;
+import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.FileRegion;
+import java.nio.ByteBuffer;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Random;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentSkipListSet;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import org.apache.rocketmq.broker.BrokerController;
+import org.apache.rocketmq.broker.client.ConsumerGroupInfo;
+import org.apache.rocketmq.broker.filter.ConsumerFilterData;
+import org.apache.rocketmq.broker.filter.ConsumerFilterManager;
+import org.apache.rocketmq.broker.filter.ExpressionMessageFilter;
+import org.apache.rocketmq.broker.longpolling.PopRequest;
+import org.apache.rocketmq.broker.pagecache.ManyMessageTransfer;
+import org.apache.rocketmq.broker.util.MsgUtil;
+import org.apache.rocketmq.common.KeyBuilder;
+import org.apache.rocketmq.common.PopAckConstants;
+import org.apache.rocketmq.common.ServiceThread;
+import org.apache.rocketmq.common.TopicConfig;
+import org.apache.rocketmq.common.constant.ConsumeInitMode;
+import org.apache.rocketmq.common.constant.LoggerName;
+import org.apache.rocketmq.common.constant.PermName;
+import org.apache.rocketmq.common.filter.ExpressionType;
+import org.apache.rocketmq.common.filter.FilterAPI;
+import org.apache.rocketmq.common.help.FAQUrl;
+import org.apache.rocketmq.common.message.MessageConst;
+import org.apache.rocketmq.common.message.MessageDecoder;
+import org.apache.rocketmq.common.protocol.ResponseCode;
+import org.apache.rocketmq.common.protocol.header.ExtraInfoUtil;
+import org.apache.rocketmq.common.protocol.header.PopMessageRequestHeader;
+import org.apache.rocketmq.common.protocol.header.PopMessageResponseHeader;
+import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData;
+import org.apache.rocketmq.common.subscription.SubscriptionGroupConfig;
+import org.apache.rocketmq.common.utils.DataConverter;
+import org.apache.rocketmq.logging.InternalLogger;
+import org.apache.rocketmq.logging.InternalLoggerFactory;
+import org.apache.rocketmq.remoting.common.RemotingHelper;
+import org.apache.rocketmq.remoting.exception.RemotingCommandException;
+import org.apache.rocketmq.remoting.netty.NettyRequestProcessor;
+import org.apache.rocketmq.remoting.netty.RequestTask;
+import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+import org.apache.rocketmq.store.GetMessageResult;
+import org.apache.rocketmq.store.GetMessageStatus;
+import org.apache.rocketmq.store.MessageExtBrokerInner;
+import org.apache.rocketmq.store.SelectMappedBufferResult;
+import org.apache.rocketmq.store.pop.AckMsg;
+import org.apache.rocketmq.store.pop.PopCheckPoint;
+
+public class PopMessageProcessor implements NettyRequestProcessor {
+ private static final InternalLogger POP_LOGGER =
+ InternalLoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LOGGER_NAME);
+ private final BrokerController brokerController;
+ private Random random = new Random(System.currentTimeMillis());
+ String reviveTopic;
+ private static final String BORN_TIME = "bornTime";
+
+ private static final int POLLING_SUC = 0;
+ private static final int POLLING_FULL = 1;
+ private static final int POLLING_TIMEOUT = 2;
+ private static final int NOT_POLLING = 3;
+
+ private ConcurrentHashMap> topicCidMap;
+ private ConcurrentLinkedHashMap> pollingMap;
+ private AtomicLong totalPollingNum = new AtomicLong(0);
+ private PopLongPollingService popLongPollingService;
+ private PopBufferMergeService popBufferMergeService;
+ private QueueLockManager queueLockManager;
+
+ public PopMessageProcessor(final BrokerController brokerController) {
+ this.brokerController = brokerController;
+ this.reviveTopic =
+ PopAckConstants.REVIVE_TOPIC + this.brokerController.getBrokerConfig().getBrokerClusterName();
+ // 100000 topic default, 100000 lru topic + cid + qid
+ this.topicCidMap = new ConcurrentHashMap<>(this.brokerController.getBrokerConfig().getPopPollingMapSize());
+ this.pollingMap = new ConcurrentLinkedHashMap.Builder>()
+ .maximumWeightedCapacity(this.brokerController.getBrokerConfig().getPopPollingMapSize()).build();
+ this.popLongPollingService = new PopLongPollingService();
+ this.queueLockManager = new QueueLockManager();
+ this.popBufferMergeService = new PopBufferMergeService(this.brokerController, this);
+ }
+
+ public PopLongPollingService getPopLongPollingService() {
+ return popLongPollingService;
+ }
+
+ public PopBufferMergeService getPopBufferMergeService() {
+ return this.popBufferMergeService;
+ }
+
+ public QueueLockManager getQueueLockManager() {
+ return queueLockManager;
+ }
+
+ public static String genAckUniqueId(AckMsg ackMsg) {
+ return ackMsg.getTopic()
+ + PopAckConstants.SPLIT + ackMsg.getQueueId()
+ + PopAckConstants.SPLIT + ackMsg.getAckOffset()
+ + PopAckConstants.SPLIT + ackMsg.getConsumerGroup()
+ + PopAckConstants.SPLIT + ackMsg.getPopTime()
+ + PopAckConstants.SPLIT + PopAckConstants.ACK_TAG;
+ }
+
+ public static String genCkUniqueId(PopCheckPoint ck) {
+ return ck.getTopic()
+ + PopAckConstants.SPLIT + ck.getQueueId()
+ + PopAckConstants.SPLIT + ck.getStartOffset()
+ + PopAckConstants.SPLIT + ck.getCId()
+ + PopAckConstants.SPLIT + ck.getPopTime()
+ + PopAckConstants.SPLIT + PopAckConstants.CK_TAG;
+ }
+
+ @Override
+ public RemotingCommand processRequest(final ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException {
+ request.addExtField(BORN_TIME, String.valueOf(System.currentTimeMillis()));
+ return this.processRequest(ctx.channel(), request);
+ }
+
+ @Override
+ public boolean rejectRequest() {
+ return false;
+ }
+
+ public ConcurrentLinkedHashMap> getPollingMap() {
+ return pollingMap;
+ }
+
+ public void notifyMessageArriving(final String topic, final int queueId) {
+ ConcurrentHashMap cids = topicCidMap.get(topic);
+ if (cids == null) {
+ return;
+ }
+ for (Entry cid : cids.entrySet()) {
+ if (queueId >= 0) {
+ notifyMessageArriving(topic, cid.getKey(), -1);
+ }
+ notifyMessageArriving(topic, cid.getKey(), queueId);
+ }
+ }
+
+ public void notifyMessageArriving(final String topic, final String cid, final int queueId) {
+ ConcurrentSkipListSet remotingCommands = pollingMap.get(KeyBuilder.buildPollingKey(topic, cid,
+ queueId));
+ if (remotingCommands == null || remotingCommands.isEmpty()) {
+ return;
+ }
+ PopRequest popRequest = remotingCommands.pollFirst();
+ //clean inactive channel
+ while (popRequest != null && !popRequest.getChannel().isActive()) {
+ popRequest = remotingCommands.pollFirst();
+ }
+
+ if (popRequest == null) {
+ return;
+ }
+ totalPollingNum.decrementAndGet();
+ if (brokerController.getBrokerConfig().isEnablePopLog()) {
+ POP_LOGGER.info("lock release , new msg arrive , wakeUp : {}", popRequest);
+ }
+ wakeUp(popRequest);
+ }
+
+ private void wakeUp(final PopRequest request) {
+ if (request == null || !request.complete()) {
+ return;
+ }
+ Runnable run = new Runnable() {
+ @Override
+ public void run() {
+ try {
+ final RemotingCommand response = PopMessageProcessor.this.processRequest(request.getChannel(),
+ request.getRemotingCommand());
+
+ if (response != null) {
+ response.setOpaque(request.getRemotingCommand().getOpaque());
+ response.markResponseType();
+ try {
+ request.getChannel().writeAndFlush(response).addListener(new ChannelFutureListener() {
+ @Override
+ public void operationComplete(ChannelFuture future) throws Exception {
+ if (!future.isSuccess()) {
+ POP_LOGGER.error("ProcessRequestWrapper response to {} failed",
+ future.channel().remoteAddress(), future.cause());
+ POP_LOGGER.error(request.toString());
+ POP_LOGGER.error(response.toString());
+ }
+ }
+ });
+ } catch (Throwable e) {
+ POP_LOGGER.error("ProcessRequestWrapper process request over, but response failed", e);
+ POP_LOGGER.error(request.toString());
+ POP_LOGGER.error(response.toString());
+ }
+ }
+ } catch (RemotingCommandException e1) {
+ POP_LOGGER.error("ExecuteRequestWhenWakeup run", e1);
+ }
+ }
+ };
+ this.brokerController.getPullMessageExecutor().submit(new RequestTask(run, request.getChannel(),
+ request.getRemotingCommand()));
+ }
+
+ private RemotingCommand processRequest(final Channel channel, RemotingCommand request)
+ throws RemotingCommandException {
+ RemotingCommand response = RemotingCommand.createResponseCommand(PopMessageResponseHeader.class);
+ final PopMessageResponseHeader responseHeader = (PopMessageResponseHeader) response.readCustomHeader();
+ final PopMessageRequestHeader requestHeader =
+ (PopMessageRequestHeader) request.decodeCommandCustomHeader(PopMessageRequestHeader.class);
+ StringBuilder startOffsetInfo = new StringBuilder(64);
+ StringBuilder msgOffsetInfo = new StringBuilder(64);
+ StringBuilder orderCountInfo = null;
+ if (requestHeader.isOrder()) {
+ orderCountInfo = new StringBuilder(64);
+ }
+
+ response.setOpaque(request.getOpaque());
+
+ if (brokerController.getBrokerConfig().isEnablePopLog()) {
+ POP_LOGGER.info("receive PopMessage request command, {}", request);
+ }
+
+ if (requestHeader.isTimeoutTooMuch()) {
+ response.setCode(POLLING_TIMEOUT);
+ response.setRemark(String.format("the broker[%s] poping message is timeout too much",
+ this.brokerController.getBrokerConfig().getBrokerIP1()));
+ return response;
+ }
+ if (!PermName.isReadable(this.brokerController.getBrokerConfig().getBrokerPermission())) {
+ response.setCode(ResponseCode.NO_PERMISSION);
+ response.setRemark(String.format("the broker[%s] poping message is forbidden",
+ this.brokerController.getBrokerConfig().getBrokerIP1()));
+ return response;
+ }
+ if (requestHeader.getMaxMsgNums() > 32) {
+ response.setCode(ResponseCode.SYSTEM_ERROR);
+ response.setRemark(String.format("the broker[%s] poping message's num is greater than 32",
+ this.brokerController.getBrokerConfig().getBrokerIP1()));
+ return response;
+ }
+
+ TopicConfig topicConfig =
+ this.brokerController.getTopicConfigManager().selectTopicConfig(requestHeader.getTopic());
+ if (null == topicConfig) {
+ POP_LOGGER.error("The topic {} not exist, consumer: {} ", requestHeader.getTopic(),
+ RemotingHelper.parseChannelRemoteAddr(channel));
+ response.setCode(ResponseCode.TOPIC_NOT_EXIST);
+ response.setRemark(String.format("topic[%s] not exist, apply first please! %s", requestHeader.getTopic(),
+ FAQUrl.suggestTodo(FAQUrl.APPLY_TOPIC_URL)));
+ return response;
+ }
+
+ if (!PermName.isReadable(topicConfig.getPerm())) {
+ response.setCode(ResponseCode.NO_PERMISSION);
+ response.setRemark("the topic[" + requestHeader.getTopic() + "] peeking message is forbidden");
+ return response;
+ }
+
+ if (requestHeader.getQueueId() >= topicConfig.getReadQueueNums()) {
+ String errorInfo = String.format("queueId[%d] is illegal, topic:[%s] topicConfig.readQueueNums:[%d] " +
+ "consumer:[%s]",
+ requestHeader.getQueueId(), requestHeader.getTopic(), topicConfig.getReadQueueNums(),
+ channel.remoteAddress());
+ POP_LOGGER.warn(errorInfo);
+ response.setCode(ResponseCode.SYSTEM_ERROR);
+ response.setRemark(errorInfo);
+ return response;
+ }
+ SubscriptionGroupConfig subscriptionGroupConfig =
+ this.brokerController.getSubscriptionGroupManager().findSubscriptionGroupConfig(requestHeader.getConsumerGroup());
+ if (null == subscriptionGroupConfig) {
+ response.setCode(ResponseCode.SUBSCRIPTION_GROUP_NOT_EXIST);
+ response.setRemark(String.format("subscription group [%s] does not exist, %s",
+ requestHeader.getConsumerGroup(), FAQUrl.suggestTodo(FAQUrl.SUBSCRIPTION_GROUP_NOT_EXIST)));
+ return response;
+ }
+ ConsumerGroupInfo consumerGroupInfo =
+ this.brokerController.getConsumerManager().getConsumerGroupInfo(requestHeader.getConsumerGroup());
+ if (null == consumerGroupInfo) {
+ POP_LOGGER.warn("the consumer's group info not exist, group: {}", requestHeader.getConsumerGroup());
+ response.setCode(ResponseCode.SUBSCRIPTION_NOT_EXIST);
+ response.setRemark("the consumer's group info not exist" + FAQUrl.suggestTodo(FAQUrl.SAME_GROUP_DIFFERENT_TOPIC));
+ return response;
+ }
+
+
+ if (!subscriptionGroupConfig.isConsumeEnable()) {
+ response.setCode(ResponseCode.NO_PERMISSION);
+ response.setRemark("subscription group no permission, " + requestHeader.getConsumerGroup());
+ return response;
+ }
+
+ ExpressionMessageFilter messageFilter = null;
+ if (requestHeader.getExp() != null && requestHeader.getExp().length() > 0) {
+ try {
+ SubscriptionData subscriptionData = FilterAPI.build(requestHeader.getTopic(), requestHeader.getExp(), requestHeader.getExpType());
+ ConsumerFilterData consumerFilterData = null;
+ if (!ExpressionType.isTagType(subscriptionData.getExpressionType())) {
+ consumerFilterData = ConsumerFilterManager.build(
+ requestHeader.getTopic(), requestHeader.getConsumerGroup(), requestHeader.getExp(),
+ requestHeader.getExpType(), System.currentTimeMillis()
+ );
+ if (consumerFilterData == null) {
+ POP_LOGGER.warn("Parse the consumer's subscription[{}] failed, group: {}",
+ requestHeader.getExp(), requestHeader.getConsumerGroup());
+ response.setCode(ResponseCode.SUBSCRIPTION_PARSE_FAILED);
+ response.setRemark("parse the consumer's subscription failed");
+ return response;
+ }
+ }
+ messageFilter = new ExpressionMessageFilter(subscriptionData, consumerFilterData,
+ brokerController.getConsumerFilterManager());
+ } catch (Exception e) {
+ POP_LOGGER.warn("Parse the consumer's subscription[{}] error, group: {}", requestHeader.getExp(),
+ requestHeader.getConsumerGroup());
+ response.setCode(ResponseCode.SUBSCRIPTION_PARSE_FAILED);
+ response.setRemark("parse the consumer's subscription failed");
+ return response;
+ }
+ }
+
+ int randomQ = random.nextInt(100);
+ int reviveQid;
+ if (requestHeader.isOrder()) {
+ reviveQid = KeyBuilder.POP_ORDER_REVIVE_QUEUE;
+ } else {
+ reviveQid = randomQ % this.brokerController.getBrokerConfig().getReviveQueueNum();
+ }
+
+ GetMessageResult getMessageResult = new GetMessageResult();
+
+ long restNum = 0;
+ boolean needRetry = randomQ % 5 == 0;
+ long popTime = System.currentTimeMillis();
+ if (needRetry && !requestHeader.isOrder()) {
+ TopicConfig retryTopicConfig =
+ this.brokerController.getTopicConfigManager().selectTopicConfig(KeyBuilder.buildPopRetryTopic(requestHeader.getTopic(), requestHeader.getConsumerGroup()));
+ if (retryTopicConfig != null) {
+ for (int i = 0; i < retryTopicConfig.getReadQueueNums(); i++) {
+ int queueId = (randomQ + i) % retryTopicConfig.getReadQueueNums();
+ restNum = popMsgFromQueue(true, getMessageResult, requestHeader, queueId, restNum, reviveQid,
+ channel, popTime, messageFilter,
+ startOffsetInfo, msgOffsetInfo, orderCountInfo);
+ }
+ }
+ }
+ if (requestHeader.getQueueId() < 0) {
+ // read all queue
+ for (int i = 0; i < topicConfig.getReadQueueNums(); i++) {
+ int queueId = (randomQ + i) % topicConfig.getReadQueueNums();
+ restNum = popMsgFromQueue(false, getMessageResult, requestHeader, queueId, restNum, reviveQid, channel, popTime, messageFilter,
+ startOffsetInfo, msgOffsetInfo, orderCountInfo);
+ }
+ } else {
+ int queueId = requestHeader.getQueueId();
+ restNum = popMsgFromQueue(false, getMessageResult, requestHeader, queueId, restNum, reviveQid, channel,
+ popTime, messageFilter,
+ startOffsetInfo, msgOffsetInfo, orderCountInfo);
+ }
+ // if not full , fetch retry again
+ if (!needRetry && getMessageResult.getMessageMapedList().size() < requestHeader.getMaxMsgNums() && !requestHeader.isOrder()) {
+ TopicConfig retryTopicConfig =
+ this.brokerController.getTopicConfigManager().selectTopicConfig(KeyBuilder.buildPopRetryTopic(requestHeader.getTopic(), requestHeader.getConsumerGroup()));
+ if (retryTopicConfig != null) {
+ for (int i = 0; i < retryTopicConfig.getReadQueueNums(); i++) {
+ int queueId = (randomQ + i) % retryTopicConfig.getReadQueueNums();
+ restNum = popMsgFromQueue(true, getMessageResult, requestHeader, queueId, restNum, reviveQid,
+ channel, popTime, messageFilter,
+ startOffsetInfo, msgOffsetInfo, orderCountInfo);
+ }
+ }
+ }
+ if (!getMessageResult.getMessageBufferList().isEmpty()) {
+ response.setCode(ResponseCode.SUCCESS);
+ getMessageResult.setStatus(GetMessageStatus.FOUND);
+ if (restNum > 0) {
+ // all queue pop can not notify specified queue pop, and vice versa
+ notifyMessageArriving(requestHeader.getTopic(), requestHeader.getConsumerGroup(),
+ requestHeader.getQueueId());
+ }
+ } else {
+ int pollingResult = polling(channel, request, requestHeader);
+ if (POLLING_SUC == pollingResult) {
+ return null;
+ } else if (POLLING_FULL == pollingResult) {
+ response.setCode(ResponseCode.POLLING_FULL);
+ } else {
+ response.setCode(ResponseCode.POLLING_TIMEOUT);
+ }
+ getMessageResult.setStatus(GetMessageStatus.NO_MESSAGE_IN_QUEUE);
+ }
+ responseHeader.setInvisibleTime(requestHeader.getInvisibleTime());
+ responseHeader.setPopTime(popTime);
+ responseHeader.setReviveQid(reviveQid);
+ responseHeader.setRestNum(restNum);
+ responseHeader.setStartOffsetInfo(startOffsetInfo.toString());
+ responseHeader.setMsgOffsetInfo(msgOffsetInfo.toString());
+ if (requestHeader.isOrder() && orderCountInfo != null) {
+ responseHeader.setOrderCountInfo(orderCountInfo.toString());
+ }
+ response.setRemark(getMessageResult.getStatus().name());
+ switch (response.getCode()) {
+ case ResponseCode.SUCCESS:
+ if (this.brokerController.getBrokerConfig().isTransferMsgByHeap()) {
+ final long beginTimeMills = this.brokerController.getMessageStore().now();
+ final byte[] r = this.readGetMessageResult(getMessageResult, requestHeader.getConsumerGroup(),
+ requestHeader.getTopic(), requestHeader.getQueueId());
+ this.brokerController.getBrokerStatsManager().incGroupGetLatency(requestHeader.getConsumerGroup(),
+ requestHeader.getTopic(), requestHeader.getQueueId(),
+ (int) (this.brokerController.getMessageStore().now() - beginTimeMills));
+ response.setBody(r);
+ } else {
+ final GetMessageResult tmpGetMessageResult = getMessageResult;
+ try {
+ FileRegion fileRegion =
+ new ManyMessageTransfer(response.encodeHeader(getMessageResult.getBufferTotalSize()),
+ getMessageResult);
+ channel.writeAndFlush(fileRegion).addListener(new ChannelFutureListener() {
+ @Override
+ public void operationComplete(ChannelFuture future) throws Exception {
+ tmpGetMessageResult.release();
+ if (!future.isSuccess()) {
+ POP_LOGGER.error("Fail to transfer messages from page cache to {}",
+ channel.remoteAddress(), future.cause());
+ }
+ }
+ });
+ } catch (Throwable e) {
+ POP_LOGGER.error("Error occurred when transferring messages from page cache", e);
+ getMessageResult.release();
+ }
+
+ response = null;
+ }
+ break;
+ default:
+ assert false;
+ }
+ return response;
+ }
+
+ private long popMsgFromQueue(boolean isRetry, GetMessageResult getMessageResult,
+ PopMessageRequestHeader requestHeader, int queueId, long restNum, int reviveQid,
+ Channel channel, long popTime,
+ ExpressionMessageFilter messageFilter, StringBuilder startOffsetInfo,
+ StringBuilder msgOffsetInfo, StringBuilder orderCountInfo) {
+ String topic = isRetry ? KeyBuilder.buildPopRetryTopic(requestHeader.getTopic(),
+ requestHeader.getConsumerGroup()) : requestHeader.getTopic();
+ String lockKey =
+ topic + PopAckConstants.SPLIT + requestHeader.getConsumerGroup() + PopAckConstants.SPLIT + queueId;
+ boolean isOrder = requestHeader.isOrder();
+ long offset = getPopOffset(topic, requestHeader, queueId, false, lockKey);
+ if (!queueLockManager.tryLock(lockKey)) {
+ restNum = this.brokerController.getMessageStore().getMaxOffsetInQueue(topic, queueId) - offset + restNum;
+ return restNum;
+ }
+ offset = getPopOffset(topic, requestHeader, queueId, true, lockKey);
+ GetMessageResult getMessageTmpResult;
+ try {
+ if (isOrder && brokerController.getConsumerOrderInfoManager().checkBlock(topic,
+ requestHeader.getConsumerGroup(), queueId, requestHeader.getInvisibleTime())) {
+ return this.brokerController.getMessageStore().getMaxOffsetInQueue(topic, queueId) - offset + restNum;
+ }
+
+ if (getMessageResult.getMessageMapedList().size() >= requestHeader.getMaxMsgNums()) {
+ restNum =
+ this.brokerController.getMessageStore().getMaxOffsetInQueue(topic, queueId) - offset + restNum;
+ return restNum;
+ }
+ getMessageTmpResult = this.brokerController.getMessageStore().getMessage(requestHeader.getConsumerGroup()
+ , topic, queueId, offset,
+ requestHeader.getMaxMsgNums() - getMessageResult.getMessageMapedList().size(), messageFilter);
+ // maybe store offset is not correct.
+ if (getMessageTmpResult == null
+ || GetMessageStatus.OFFSET_TOO_SMALL.equals(getMessageTmpResult.getStatus())
+ || GetMessageStatus.OFFSET_OVERFLOW_BADLY.equals(getMessageTmpResult.getStatus())
+ || GetMessageStatus.OFFSET_FOUND_NULL.equals(getMessageTmpResult.getStatus())) {
+ // commit offset, because the offset is not correct
+ // If offset in store is greater than cq offset, it will cause duplicate messages,
+ // because offset in PopBuffer is not committed.
+ POP_LOGGER.warn("Pop initial offset, because store is no correct, {}, {}->{}",
+ lockKey, offset, getMessageTmpResult != null ? getMessageTmpResult.getNextBeginOffset() : "null");
+ offset = getMessageTmpResult != null ? getMessageTmpResult.getNextBeginOffset() : 0;
+ this.brokerController.getConsumerOffsetManager().commitOffset(channel.remoteAddress().toString(), requestHeader.getConsumerGroup(), topic,
+ queueId, offset);
+ getMessageTmpResult =
+ this.brokerController.getMessageStore().getMessage(requestHeader.getConsumerGroup(), topic,
+ queueId, offset,
+ requestHeader.getMaxMsgNums() - getMessageResult.getMessageMapedList().size(), messageFilter);
+ }
+
+ restNum = getMessageTmpResult.getMaxOffset() - getMessageTmpResult.getNextBeginOffset() + restNum;
+ if (!getMessageTmpResult.getMessageMapedList().isEmpty()) {
+ this.brokerController.getBrokerStatsManager().incBrokerGetNums(getMessageTmpResult.getMessageCount());
+ this.brokerController.getBrokerStatsManager().incGroupGetNums(requestHeader.getConsumerGroup(), topic,
+ getMessageTmpResult.getMessageCount());
+ this.brokerController.getBrokerStatsManager().incGroupGetSize(requestHeader.getConsumerGroup(), topic,
+ getMessageTmpResult.getBufferTotalSize());
+
+ if (isOrder) {
+ int count = brokerController.getConsumerOrderInfoManager().update(topic,
+ requestHeader.getConsumerGroup(),
+ queueId, getMessageTmpResult.getMessageQueueOffset());
+ this.brokerController.getConsumerOffsetManager().commitOffset(channel.remoteAddress().toString(),
+ requestHeader.getConsumerGroup(), topic, queueId, offset);
+ ExtraInfoUtil.buildOrderCountInfo(orderCountInfo, isRetry, queueId, count);
+ } else {
+ appendCheckPoint(requestHeader, topic, reviveQid, queueId, offset, getMessageTmpResult, popTime);
+ }
+ ExtraInfoUtil.buildStartOffsetInfo(startOffsetInfo, isRetry, queueId, offset);
+ ExtraInfoUtil.buildMsgOffsetInfo(msgOffsetInfo, isRetry, queueId,
+ getMessageTmpResult.getMessageQueueOffset());
+ } else if ((GetMessageStatus.NO_MATCHED_MESSAGE.equals(getMessageTmpResult.getStatus())
+ || GetMessageStatus.OFFSET_FOUND_NULL.equals(getMessageTmpResult.getStatus())
+ || GetMessageStatus.MESSAGE_WAS_REMOVING.equals(getMessageTmpResult.getStatus())
+ || GetMessageStatus.NO_MATCHED_LOGIC_QUEUE.equals(getMessageTmpResult.getStatus()))
+ && getMessageTmpResult.getNextBeginOffset() > -1) {
+ popBufferMergeService.addCkMock(requestHeader.getConsumerGroup(), topic, queueId, offset,
+ requestHeader.getInvisibleTime(), popTime, reviveQid, getMessageTmpResult.getNextBeginOffset());
+ }
+ } finally {
+ queueLockManager.unLock(lockKey);
+ }
+ if (getMessageTmpResult != null) {
+ for (SelectMappedBufferResult mapedBuffer : getMessageTmpResult.getMessageMapedList()) {
+ getMessageResult.addMessage(mapedBuffer);
+ }
+ }
+ return restNum;
+ }
+
+ private long getPopOffset(String topic, PopMessageRequestHeader requestHeader, int queueId, boolean init,
+ String lockKey) {
+ long offset = this.brokerController.getConsumerOffsetManager().queryOffset(requestHeader.getConsumerGroup(),
+ topic, queueId);
+ if (offset < 0) {
+ if (ConsumeInitMode.MIN == requestHeader.getInitMode()) {
+ offset = this.brokerController.getMessageStore().getMinOffsetInQueue(topic, queueId);
+ } else {
+ // pop last one,then commit offset.
+ offset = this.brokerController.getMessageStore().getMaxOffsetInQueue(topic, queueId) - 1;
+ // max & no consumer offset
+ if (offset < 0) {
+ offset = 0;
+ }
+ if (init) {
+ this.brokerController.getConsumerOffsetManager().commitOffset("getPopOffset",
+ requestHeader.getConsumerGroup(), topic,
+ queueId, offset);
+ }
+ }
+ }
+ long bufferOffset = this.popBufferMergeService.getLatestOffset(lockKey);
+ if (bufferOffset < 0) {
+ return offset;
+ } else {
+ return bufferOffset > offset ? bufferOffset : offset;
+ }
+ }
+
+ /**
+ * @param channel
+ * @param remotingCommand
+ * @param requestHeader
+ * @return
+ */
+ private int polling(final Channel channel, RemotingCommand remotingCommand,
+ final PopMessageRequestHeader requestHeader) {
+ if (requestHeader.getPollTime() <= 0 || this.popLongPollingService.isStopped()) {
+ return NOT_POLLING;
+ }
+ ConcurrentHashMap cids = topicCidMap.get(requestHeader.getTopic());
+ if (cids == null) {
+ cids = new ConcurrentHashMap<>();
+ ConcurrentHashMap old = topicCidMap.putIfAbsent(requestHeader.getTopic(), cids);
+ if (old != null) {
+ cids = old;
+ }
+ }
+ cids.putIfAbsent(requestHeader.getConsumerGroup(), Byte.MIN_VALUE);
+ long expired = requestHeader.getBornTime() + requestHeader.getPollTime();
+ final PopRequest request = new PopRequest(remotingCommand, channel, expired);
+ boolean isFull = totalPollingNum.get() >= this.brokerController.getBrokerConfig().getMaxPopPollingSize();
+ if (isFull) {
+ POP_LOGGER.info("polling {}, result POLLING_FULL, total:{}", remotingCommand, totalPollingNum.get());
+ return POLLING_FULL;
+ }
+ boolean isTimeout = request.isTimeout();
+ if (isTimeout) {
+ if (brokerController.getBrokerConfig().isEnablePopLog()) {
+ POP_LOGGER.info("polling {}, result POLLING_TIMEOUT", remotingCommand);
+ }
+ return POLLING_TIMEOUT;
+ }
+ String key = KeyBuilder.buildPollingKey(requestHeader.getTopic(), requestHeader.getConsumerGroup(),
+ requestHeader.getQueueId());
+ ConcurrentSkipListSet queue = pollingMap.get(key);
+ if (queue == null) {
+ queue = new ConcurrentSkipListSet<>(PopRequest.COMPARATOR);
+ ConcurrentSkipListSet old = pollingMap.putIfAbsent(key, queue);
+ if (old != null) {
+ queue = old;
+ }
+ } else {
+ // check size
+ int size = queue.size();
+ if (size > brokerController.getBrokerConfig().getPopPollingSize()) {
+ POP_LOGGER.info("polling {}, result POLLING_FULL, singleSize:{}", remotingCommand, size);
+ return POLLING_FULL;
+ }
+ }
+ if (queue.add(request)) {
+ totalPollingNum.incrementAndGet();
+ if (brokerController.getBrokerConfig().isEnablePopLog()) {
+ POP_LOGGER.info("polling {}, result POLLING_SUC", remotingCommand);
+ }
+ return POLLING_SUC;
+ } else {
+ POP_LOGGER.info("polling {}, result POLLING_FULL, add fail, {}", request, queue);
+ return POLLING_FULL;
+ }
+ }
+
+ public final MessageExtBrokerInner buildCkMsg(final PopCheckPoint ck, final int reviveQid) {
+ MessageExtBrokerInner msgInner = new MessageExtBrokerInner();
+
+ msgInner.setTopic(reviveTopic);
+ msgInner.setBody(JSON.toJSONString(ck).getBytes(DataConverter.charset));
+ msgInner.setQueueId(reviveQid);
+ msgInner.setTags(PopAckConstants.CK_TAG);
+ msgInner.setBornTimestamp(System.currentTimeMillis());
+ msgInner.setBornHost(this.brokerController.getStoreHost());
+ msgInner.setStoreHost(this.brokerController.getStoreHost());
+ MsgUtil.setMessageDeliverTime(this.brokerController, msgInner, ck.getReviveTime() - PopAckConstants.ackTimeInterval);
+ msgInner.getProperties().put(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, genCkUniqueId(ck));
+ msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties()));
+
+ return msgInner;
+ }
+
+ private void appendCheckPoint(final PopMessageRequestHeader requestHeader,
+ final String topic, final int reviveQid, final int queueId, final long offset,
+ final GetMessageResult getMessageTmpResult, final long popTime) {
+ // add check point msg to revive log
+ final PopCheckPoint ck = new PopCheckPoint();
+ ck.setBitMap(0);
+ ck.setNum((byte) getMessageTmpResult.getMessageMapedList().size());
+ ck.setPopTime(popTime);
+ ck.setInvisibleTime(requestHeader.getInvisibleTime());
+ ck.getStartOffset(offset);
+ ck.setCId(requestHeader.getConsumerGroup());
+ ck.setTopic(topic);
+ ck.setQueueId((byte) queueId);
+ for (Long msgQueueOffset : getMessageTmpResult.getMessageQueueOffset()) {
+ ck.addDiff((int) (msgQueueOffset - offset));
+ }
+
+ final boolean addBufferSuc = this.popBufferMergeService.addCk(
+ ck, reviveQid, -1, getMessageTmpResult.getNextBeginOffset()
+ );
+
+ if (addBufferSuc) {
+ return;
+ }
+
+ this.popBufferMergeService.addCkJustOffset(
+ ck, reviveQid, -1, getMessageTmpResult.getNextBeginOffset()
+ );
+ }
+
+ private byte[] readGetMessageResult(final GetMessageResult getMessageResult, final String group,
+ final String topic, final int queueId) {
+ final ByteBuffer byteBuffer = ByteBuffer.allocate(getMessageResult.getBufferTotalSize());
+
+ long storeTimestamp = 0;
+ try {
+ List messageBufferList = getMessageResult.getMessageBufferList();
+ for (ByteBuffer bb : messageBufferList) {
+
+ byteBuffer.put(bb);
+ storeTimestamp = bb.getLong(MessageDecoder.MESSAGE_STORE_TIMESTAMP_POSITION);
+ }
+ } finally {
+ getMessageResult.release();
+ }
+
+ this.brokerController.getBrokerStatsManager().recordDiskFallBehindTime(group, topic, queueId,
+ this.brokerController.getMessageStore().now() - storeTimestamp);
+ return byteBuffer.array();
+ }
+
+ public class PopLongPollingService extends ServiceThread {
+
+ private long lastCleanTime = 0;
+
+ @Override
+ public String getServiceName() {
+ return "PopLongPollingService";
+ }
+
+ private void cleanUnusedResource() {
+ try {
+ {
+ Iterator>> topicCidMapIter = topicCidMap.entrySet().iterator();
+ while (topicCidMapIter.hasNext()) {
+ Entry> entry = topicCidMapIter.next();
+ String topic = entry.getKey();
+ if (brokerController.getTopicConfigManager().selectTopicConfig(topic) == null) {
+ POP_LOGGER.info("remove not exit topic {} in topicCidMap!", topic);
+ topicCidMapIter.remove();
+ continue;
+ }
+ Iterator> cidMapIter = entry.getValue().entrySet().iterator();
+ while (cidMapIter.hasNext()) {
+ Entry cidEntry = cidMapIter.next();
+ String cid = cidEntry.getKey();
+ if (!brokerController.getSubscriptionGroupManager().getSubscriptionGroupTable().containsKey(cid)) {
+ POP_LOGGER.info("remove not exit sub {} of topic {} in topicCidMap!", cid, topic);
+ cidMapIter.remove();
+ }
+ }
+ }
+ }
+
+ {
+ Iterator>> pollingMapIter = pollingMap.entrySet().iterator();
+ while (pollingMapIter.hasNext()) {
+ Entry> entry = pollingMapIter.next();
+ if (entry.getKey() == null) {
+ continue;
+ }
+ String[] keyArray = entry.getKey().split(PopAckConstants.SPLIT);
+ if (keyArray == null || keyArray.length != 3) {
+ continue;
+ }
+ String topic = keyArray[0];
+ String cid = keyArray[1];
+ if (brokerController.getTopicConfigManager().selectTopicConfig(topic) == null) {
+ POP_LOGGER.info("remove not exit topic {} in pollingMap!", topic);
+ pollingMapIter.remove();
+ continue;
+ }
+ if (!brokerController.getSubscriptionGroupManager().getSubscriptionGroupTable().containsKey(cid)) {
+ POP_LOGGER.info("remove not exit sub {} of topic {} in pollingMap!", cid, topic);
+ pollingMapIter.remove();
+ continue;
+ }
+ }
+ }
+ } catch (Throwable e) {
+ POP_LOGGER.error("cleanUnusedResource", e);
+ }
+
+ lastCleanTime = System.currentTimeMillis();
+ }
+
+ @Override
+ public void run() {
+ int i = 0;
+ while (!this.stopped) {
+ try {
+ this.waitForRunning(20);
+ i++;
+ if (pollingMap.isEmpty()) {
+ continue;
+ }
+ long tmpTotalPollingNum = 0;
+ Iterator>> pollingMapIterator = pollingMap.entrySet().iterator();
+ while (pollingMapIterator.hasNext()) {
+ Entry> entry = pollingMapIterator.next();
+ String key = entry.getKey();
+ ConcurrentSkipListSet popQ = entry.getValue();
+ if (popQ == null) {
+ continue;
+ }
+ PopRequest first;
+ do {
+ first = popQ.pollFirst();
+ if (first == null) {
+ break;
+ }
+ if (!first.isTimeout()) {
+ if (popQ.add(first)) {
+ break;
+ } else {
+ POP_LOGGER.info("polling, add fail again: {}", first);
+ }
+ }
+ if (brokerController.getBrokerConfig().isEnablePopLog()) {
+ POP_LOGGER.info("timeout , wakeUp polling : {}", first);
+ }
+ totalPollingNum.decrementAndGet();
+ wakeUp(first);
+ } while (true);
+ if (i >= 100) {
+ long tmpPollingNum = popQ.size();
+ tmpTotalPollingNum = tmpTotalPollingNum + tmpPollingNum;
+ if (tmpPollingNum > 100) {
+ POP_LOGGER.info("polling queue {} , size={} ", key, tmpPollingNum);
+ }
+ }
+ }
+
+ if (i >= 100) {
+ POP_LOGGER.info("pollingMapSize={},tmpTotalSize={},atomicTotalSize={},diffSize={}",
+ pollingMap.size(), tmpTotalPollingNum, totalPollingNum.get(),
+ Math.abs(totalPollingNum.get() - tmpTotalPollingNum));
+ totalPollingNum.set(tmpTotalPollingNum);
+ i = 0;
+ }
+
+ // clean unused
+ if (lastCleanTime == 0 || System.currentTimeMillis() - lastCleanTime > 5 * 60 * 1000) {
+ cleanUnusedResource();
+ }
+ } catch (Throwable e) {
+ POP_LOGGER.error("checkPolling error", e);
+ }
+ }
+ // clean all;
+ try {
+ Iterator>> pollingMapIterator = pollingMap.entrySet().iterator();
+ while (pollingMapIterator.hasNext()) {
+ Entry> entry = pollingMapIterator.next();
+ ConcurrentSkipListSet popQ = entry.getValue();
+ PopRequest first;
+ while ((first = popQ.pollFirst()) != null) {
+ wakeUp(first);
+ }
+ }
+ } catch (Throwable e) {
+ }
+ }
+ }
+
+ static class TimedLock {
+ private final AtomicBoolean lock;
+ private volatile long lockTime;
+
+ public TimedLock() {
+ this.lock = new AtomicBoolean(true);
+ this.lockTime = System.currentTimeMillis();
+ }
+
+ public boolean tryLock() {
+ boolean ret = lock.compareAndSet(true, false);
+ if (ret) {
+ this.lockTime = System.currentTimeMillis();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public void unLock() {
+ lock.set(true);
+ }
+
+ public boolean isLock() {
+ return !lock.get();
+ }
+
+ public long getLockTime() {
+ return lockTime;
+ }
+ }
+
+ public static class QueueLockManager extends ServiceThread {
+ private ConcurrentHashMap expiredLocalCache = new ConcurrentHashMap<>(100000);
+
+ public boolean tryLock(String key) {
+ TimedLock timedLock = expiredLocalCache.get(key);
+
+ if (timedLock == null) {
+ TimedLock old = expiredLocalCache.putIfAbsent(key, new TimedLock());
+ if (old != null) {
+ return false;
+ } else {
+ timedLock = expiredLocalCache.get(key);
+ }
+ }
+
+ if (timedLock == null) {
+ return false;
+ }
+
+ return timedLock.tryLock();
+ }
+
+ /**
+ * is not thread safe, may cause duplicate lock
+ *
+ * @param usedExpireMillis
+ * @return
+ */
+ public int cleanUnusedLock(final long usedExpireMillis) {
+ Iterator> iterator = expiredLocalCache.entrySet().iterator();
+
+ int total = 0;
+ while (iterator.hasNext()) {
+ Entry entry = iterator.next();
+
+ if (System.currentTimeMillis() - entry.getValue().getLockTime() > usedExpireMillis) {
+ iterator.remove();
+ POP_LOGGER.info("Remove unused queue lock: {}, {}, {}", entry.getKey(),
+ entry.getValue().getLockTime(),
+ entry.getValue().isLock());
+ }
+
+ total++;
+ }
+
+ return total;
+ }
+
+ public void unLock(String key) {
+ TimedLock timedLock = expiredLocalCache.get(key);
+ if (timedLock != null) {
+ timedLock.unLock();
+ }
+ }
+
+ @Override
+ public String getServiceName() {
+ return "QueueLockManager";
+ }
+
+ @Override
+ public void run() {
+ while (!isStopped()) {
+ try {
+ this.waitForRunning(60000);
+ int count = cleanUnusedLock(60000);
+ POP_LOGGER.info("QueueLockSize={}", count);
+ } catch (Exception e) {
+ PopMessageProcessor.POP_LOGGER.error("QueueLockManager run error", e);
+ }
+ }
+ }
+ }
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..6d94a75d3e7708202dfed67b23307eb9d0a5fa4d
--- /dev/null
+++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopReviveService.java
@@ -0,0 +1,470 @@
+/*
+ * 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.processor;
+
+import com.alibaba.fastjson.JSON;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import org.apache.rocketmq.broker.BrokerController;
+import org.apache.rocketmq.broker.util.MsgUtil;
+import org.apache.rocketmq.client.consumer.PullResult;
+import org.apache.rocketmq.client.consumer.PullStatus;
+import org.apache.rocketmq.common.KeyBuilder;
+import org.apache.rocketmq.common.MixAll;
+import org.apache.rocketmq.common.PopAckConstants;
+import org.apache.rocketmq.common.ServiceThread;
+import org.apache.rocketmq.common.TopicConfig;
+import org.apache.rocketmq.common.TopicFilterType;
+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.MessageExt;
+import org.apache.rocketmq.common.utils.DataConverter;
+import org.apache.rocketmq.logging.InternalLogger;
+import org.apache.rocketmq.logging.InternalLoggerFactory;
+import org.apache.rocketmq.store.AppendMessageStatus;
+import org.apache.rocketmq.store.GetMessageResult;
+import org.apache.rocketmq.store.MessageExtBrokerInner;
+import org.apache.rocketmq.store.PutMessageResult;
+import org.apache.rocketmq.store.config.BrokerRole;
+import org.apache.rocketmq.store.pop.AckMsg;
+import org.apache.rocketmq.store.pop.PopCheckPoint;
+
+public class PopReviveService extends ServiceThread {
+ private static final InternalLogger POP_LOGGER = InternalLoggerFactory.getLogger(LoggerName.ROCKETMQ_POP_LOGGER_NAME);
+
+ private int queueId;
+ private BrokerController brokerController;
+ private String reviveTopic;
+ private static volatile boolean isMaster = false;
+
+ public PopReviveService(int queueId, BrokerController brokerController, String reviveTopic) {
+ super();
+ this.queueId = queueId;
+ this.brokerController = brokerController;
+ this.reviveTopic = reviveTopic;
+ }
+
+ @Override
+ public String getServiceName() {
+ return "PopReviveService_" + this.queueId;
+ }
+
+ private boolean checkMaster() {
+ return brokerController.getMessageStoreConfig().getBrokerRole() != BrokerRole.SLAVE;
+ }
+
+ private boolean checkAndSetMaster() {
+ isMaster = checkMaster();
+ return isMaster;
+ }
+
+ private void reviveRetry(PopCheckPoint popCheckPoint, MessageExt messageExt) throws Exception {
+ if (!checkAndSetMaster()) {
+ POP_LOGGER.info("slave skip retry , revive topic={}, reviveQueueId={}", reviveTopic, queueId);
+ return;
+ }
+ MessageExtBrokerInner msgInner = new MessageExtBrokerInner();
+ if (!popCheckPoint.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
+ msgInner.setTopic(KeyBuilder.buildPopRetryTopic(popCheckPoint.getTopic(), popCheckPoint.getCId()));
+ } else {
+ msgInner.setTopic(popCheckPoint.getTopic());
+ }
+ msgInner.setBody(messageExt.getBody());
+ msgInner.setQueueId(0);
+ if (messageExt.getTags() != null) {
+ msgInner.setTags(messageExt.getTags());
+ } else {
+ MessageAccessor.setProperties(msgInner, new HashMap());
+ }
+ msgInner.setBornTimestamp(messageExt.getBornTimestamp());
+ msgInner.setBornHost(brokerController.getStoreHost());
+ msgInner.setStoreHost(brokerController.getStoreHost());
+ msgInner.setReconsumeTimes(messageExt.getReconsumeTimes() + 1);
+ msgInner.getProperties().putAll(messageExt.getProperties());
+ if (messageExt.getReconsumeTimes() == 0 || msgInner.getProperties().get(MessageConst.PROPERTY_FIRST_POP_TIME) == null) {
+ msgInner.getProperties().put(MessageConst.PROPERTY_FIRST_POP_TIME, String.valueOf(popCheckPoint.getPopTime()));
+ }
+ msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties()));
+ addRetryTopicIfNoExit(msgInner.getTopic(), popCheckPoint.getCId());
+ PutMessageResult putMessageResult = brokerController.getMessageStore().putMessage(msgInner);
+ if (brokerController.getBrokerConfig().isEnablePopLog()) {
+ POP_LOGGER.info("reviveQueueId={},retry msg , ck={}, msg queueId {}, offset {}, reviveDelay={}, result is {} ",
+ queueId, popCheckPoint, messageExt.getQueueId(), messageExt.getQueueOffset(),
+ (System.currentTimeMillis() - popCheckPoint.getReviveTime()) / 1000, putMessageResult);
+ }
+ if (putMessageResult.getAppendMessageResult() == null || putMessageResult.getAppendMessageResult().getStatus() != AppendMessageStatus.PUT_OK) {
+ throw new Exception("reviveQueueId=" + queueId + ",revive error ,msg is :" + msgInner);
+ }
+ this.brokerController.getBrokerStatsManager().incBrokerPutNums(1);
+ this.brokerController.getBrokerStatsManager().incTopicPutNums(msgInner.getTopic());
+ this.brokerController.getBrokerStatsManager().incTopicPutSize(msgInner.getTopic(), putMessageResult.getAppendMessageResult().getWroteBytes());
+ if (brokerController.getPopMessageProcessor() != null) {
+ brokerController.getPopMessageProcessor().notifyMessageArriving(
+ KeyBuilder.parseNormalTopic(popCheckPoint.getTopic(), popCheckPoint.getCId()),
+ popCheckPoint.getCId(),
+ -1
+ );
+ }
+ }
+
+ private void initPopRetryOffset(String topic, String consumerGroup) {
+ long offset = this.brokerController.getConsumerOffsetManager().queryOffset(consumerGroup, topic, 0);
+ if (offset < 0) {
+ this.brokerController.getConsumerOffsetManager().commitOffset("initPopRetryOffset", consumerGroup, topic,
+ 0, 0);
+ }
+ }
+
+ private void addRetryTopicIfNoExit(String topic, String consumerGroup) {
+ TopicConfig topicConfig = brokerController.getTopicConfigManager().selectTopicConfig(topic);
+ if (topicConfig != null) {
+ return;
+ }
+ topicConfig = new TopicConfig(topic);
+ topicConfig.setReadQueueNums(PopAckConstants.retryQueueNum);
+ topicConfig.setWriteQueueNums(PopAckConstants.retryQueueNum);
+ topicConfig.setTopicFilterType(TopicFilterType.SINGLE_TAG);
+ topicConfig.setPerm(6);
+ topicConfig.setTopicSysFlag(0);
+ brokerController.getTopicConfigManager().updateTopicConfig(topicConfig);
+
+ initPopRetryOffset(topic, consumerGroup);
+ }
+
+ private List getReviveMessage(long offset, int queueId) {
+ PullResult pullResult = getMessage(PopAckConstants.REVIVE_GROUP, reviveTopic, queueId, offset, 32);
+ if (pullResult == null) {
+ return null;
+ }
+ if (reachTail(pullResult, offset)) {
+ POP_LOGGER.info("reviveQueueId={}, reach tail,offset {}", queueId, offset);
+ } else if (pullResult.getPullStatus() == PullStatus.OFFSET_ILLEGAL || pullResult.getPullStatus() == PullStatus.NO_MATCHED_MSG) {
+ POP_LOGGER.error("reviveQueueId={}, OFFSET_ILLEGAL {}, result is {}", queueId, offset, pullResult);
+ if (!checkAndSetMaster()) {
+ POP_LOGGER.info("slave skip offset correct topic={}, reviveQueueId={}", reviveTopic, queueId);
+ return null;
+ }
+ brokerController.getConsumerOffsetManager().commitOffset(PopAckConstants.LOCAL_HOST, PopAckConstants.REVIVE_GROUP, reviveTopic, queueId, pullResult.getNextBeginOffset() - 1);
+ }
+ return pullResult.getMsgFoundList();
+ }
+
+ private boolean reachTail(PullResult pullResult, long offset) {
+ return pullResult.getPullStatus() == PullStatus.NO_NEW_MSG
+ || (pullResult.getPullStatus() == PullStatus.OFFSET_ILLEGAL && offset == pullResult.getMaxOffset());
+ }
+
+ private MessageExt getBizMessage(String topic, long offset, int queueId) {
+ final GetMessageResult getMessageTmpResult = brokerController.getMessageStore().getMessage(PopAckConstants.REVIVE_GROUP, topic, queueId, offset, 1, null);
+ List list = decodeMsgList(getMessageTmpResult);
+ if (list == null || list.isEmpty()) {
+ POP_LOGGER.warn("can not get msg , topic {}, offset {}, queueId {}, result is {}", topic, offset, queueId, getMessageTmpResult);
+ return null;
+ } else {
+ return list.get(0);
+ }
+ }
+
+ public PullResult getMessage(String group, String topic, int queueId, long offset, int nums) {
+ GetMessageResult getMessageResult = brokerController.getMessageStore().getMessage(group, topic, queueId, offset, nums, null);
+
+ if (getMessageResult != null) {
+ PullStatus pullStatus = PullStatus.NO_NEW_MSG;
+ List foundList = null;
+ switch (getMessageResult.getStatus()) {
+ case FOUND:
+ pullStatus = PullStatus.FOUND;
+ foundList = decodeMsgList(getMessageResult);
+ brokerController.getBrokerStatsManager().incGroupGetNums(group, topic, getMessageResult.getMessageCount());
+ brokerController.getBrokerStatsManager().incGroupGetSize(group, topic, getMessageResult.getBufferTotalSize());
+ brokerController.getBrokerStatsManager().incBrokerGetNums(getMessageResult.getMessageCount());
+ brokerController.getBrokerStatsManager().recordDiskFallBehindTime(group, topic, queueId,
+ brokerController.getMessageStore().now() - foundList.get(foundList.size() - 1).getStoreTimestamp());
+ break;
+ case NO_MATCHED_MESSAGE:
+ pullStatus = PullStatus.NO_MATCHED_MSG;
+ POP_LOGGER.warn("no matched message. GetMessageStatus={}, topic={}, groupId={}, requestOffset={}",
+ getMessageResult.getStatus(), topic, group, offset);
+ break;
+ case NO_MESSAGE_IN_QUEUE:
+ pullStatus = PullStatus.NO_NEW_MSG;
+ POP_LOGGER.warn("no new message. GetMessageStatus={}, topic={}, groupId={}, requestOffset={}",
+ getMessageResult.getStatus(), topic, group, offset);
+ break;
+ case MESSAGE_WAS_REMOVING:
+ case NO_MATCHED_LOGIC_QUEUE:
+ case OFFSET_FOUND_NULL:
+ case OFFSET_OVERFLOW_BADLY:
+ case OFFSET_OVERFLOW_ONE:
+ case OFFSET_TOO_SMALL:
+ pullStatus = PullStatus.OFFSET_ILLEGAL;
+ POP_LOGGER.warn("offset illegal. GetMessageStatus={}, topic={}, groupId={}, requestOffset={}",
+ getMessageResult.getStatus(), topic, group, offset);
+ break;
+ default:
+ assert false;
+ break;
+ }
+
+ return new PullResult(pullStatus, getMessageResult.getNextBeginOffset(), getMessageResult.getMinOffset(),
+ getMessageResult.getMaxOffset(), foundList);
+
+ } else {
+ POP_LOGGER.error("get message from store return null. topic={}, groupId={}, requestOffset={}", topic, group, offset);
+ return null;
+ }
+ }
+
+ private List decodeMsgList(GetMessageResult getMessageResult) {
+ List foundList = new ArrayList<>();
+ try {
+ List messageBufferList = getMessageResult.getMessageBufferList();
+ if (messageBufferList != null) {
+ for (int i = 0; i < messageBufferList.size(); i++) {
+ ByteBuffer bb = messageBufferList.get(i);
+ if (bb == null) {
+ POP_LOGGER.error("bb is null {}", getMessageResult);
+ continue;
+ }
+ MessageExt msgExt = MessageDecoder.decode(bb);
+ if (msgExt == null) {
+ POP_LOGGER.error("decode msgExt is null {}", getMessageResult);
+ continue;
+ }
+ // use CQ offset, not offset in Message
+ msgExt.setQueueOffset(getMessageResult.getMessageQueueOffset().get(i));
+ foundList.add(msgExt);
+ }
+ }
+ } finally {
+ getMessageResult.release();
+ }
+
+ return foundList;
+ }
+
+ private void consumeReviveMessage(ConsumeReviveObj consumeReviveObj) {
+ HashMap map = consumeReviveObj.map;
+ long startScanTime = System.currentTimeMillis();
+ long endTime = 0;
+ long oldOffset = brokerController.getConsumerOffsetManager().queryOffset(PopAckConstants.REVIVE_GROUP, reviveTopic, queueId);
+ consumeReviveObj.oldOffset = oldOffset;
+ POP_LOGGER.info("reviveQueueId={}, old offset is {} ", queueId, oldOffset);
+ long offset = oldOffset + 1;
+ long firstRt = 0;
+ // offset self amend
+ while (true) {
+ if (!checkAndSetMaster()) {
+ POP_LOGGER.info("slave skip scan , revive topic={}, reviveQueueId={}", reviveTopic, queueId);
+ break;
+ }
+ List messageExts = getReviveMessage(offset, queueId);
+ if (messageExts == null || messageExts.isEmpty()) {
+ break;
+ }
+ if (System.currentTimeMillis() - startScanTime > brokerController.getBrokerConfig().getReviveScanTime()) {
+ POP_LOGGER.info("reviveQueueId={}, scan timeout ", queueId);
+ break;
+ }
+ for (MessageExt messageExt : messageExts) {
+ if (PopAckConstants.CK_TAG.equals(messageExt.getTags())) {
+ String raw = new String(messageExt.getBody(), DataConverter.charset);
+ if (brokerController.getBrokerConfig().isEnablePopLog()) {
+ POP_LOGGER.info("reviveQueueId={},find ck, offset:{}, raw : {}", messageExt.getQueueId(), messageExt.getQueueOffset(), raw);
+ }
+ PopCheckPoint point = JSON.parseObject(raw, PopCheckPoint.class);
+ if (point.getTopic() == null || point.getCId() == null) {
+ continue;
+ }
+ map.put(point.getTopic() + point.getCId() + point.getQueueId() + point.getStartOffset() + point.getPopTime(), point);
+ point.setReviveOffset(messageExt.getQueueOffset());
+ if (firstRt == 0) {
+ firstRt = point.getReviveTime();
+ }
+ } else if (PopAckConstants.ACK_TAG.equals(messageExt.getTags())) {
+ String raw = new String(messageExt.getBody(), DataConverter.charset);
+ if (brokerController.getBrokerConfig().isEnablePopLog()) {
+ POP_LOGGER.info("reviveQueueId={},find ack, offset:{}, raw : {}", messageExt.getQueueId(), messageExt.getQueueOffset(), raw);
+ }
+ AckMsg ackMsg = JSON.parseObject(raw, AckMsg.class);
+ PopCheckPoint point = map.get(ackMsg.getTopic() + ackMsg.getConsumerGroup() + ackMsg.getQueueId() + ackMsg.getStartOffset() + ackMsg.getPopTime());
+ if (point == null) {
+ continue;
+ }
+ int indexOfAck = point.indexOfAck(ackMsg.getAckOffset());
+ if (indexOfAck > -1) {
+ point.setBitMap(DataConverter.setBit(point.getBitMap(), indexOfAck, true));
+ } else {
+ POP_LOGGER.error("invalid ack index, {}, {}", ackMsg, point);
+ }
+ }
+ long deliverTime = MsgUtil.getMessageDeliverTime(this.brokerController, messageExt);
+ if (deliverTime > endTime) {
+ endTime = deliverTime;
+ }
+ }
+ offset = offset + messageExts.size();
+ }
+ consumeReviveObj.endTime = endTime;
+ }
+
+ private void mergeAndRevive(ConsumeReviveObj consumeReviveObj) throws Throwable {
+ ArrayList sortList = consumeReviveObj.genSortList();
+ POP_LOGGER.info("reviveQueueId={},ck listSize={}", queueId, sortList.size());
+ if (sortList.size() != 0) {
+ POP_LOGGER.info("reviveQueueId={}, 1st ck, startOffset={}, reviveOffset={} ; last ck, startOffset={}, reviveOffset={}", queueId, sortList.get(0).getStartOffset(),
+ sortList.get(0).getReviveOffset(), sortList.get(sortList.size() - 1).getStartOffset(), sortList.get(sortList.size() - 1).getReviveOffset());
+ }
+ long newOffset = consumeReviveObj.oldOffset;
+ for (PopCheckPoint popCheckPoint : sortList) {
+ if (!checkAndSetMaster()) {
+ POP_LOGGER.info("slave skip ck process , revive topic={}, reviveQueueId={}", reviveTopic, queueId);
+ break;
+ }
+ if (consumeReviveObj.endTime - popCheckPoint.getReviveTime() <= (PopAckConstants.ackTimeInterval + PopAckConstants.SECOND)) {
+ break;
+ }
+
+ // check normal topic, skip ck , if normal topic is not exist
+ String normalTopic = KeyBuilder.parseNormalTopic(popCheckPoint.getTopic(), popCheckPoint.getCId());
+ if (brokerController.getTopicConfigManager().selectTopicConfig(normalTopic) == null) {
+ POP_LOGGER.warn("reviveQueueId={},can not get normal topic {} , then continue ", queueId, popCheckPoint.getTopic());
+ newOffset = popCheckPoint.getReviveOffset();
+ continue;
+ }
+ if (null == brokerController.getSubscriptionGroupManager().findSubscriptionGroupConfig(popCheckPoint.getCId())) {
+ POP_LOGGER.warn("reviveQueueId={},can not get cid {} , then continue ", queueId, popCheckPoint.getCId());
+ newOffset = popCheckPoint.getReviveOffset();
+ continue;
+ }
+
+ reviveMsgFromCk(popCheckPoint);
+
+ newOffset = popCheckPoint.getReviveOffset();
+ }
+ if (newOffset > consumeReviveObj.oldOffset) {
+ if (!checkAndSetMaster()) {
+ POP_LOGGER.info("slave skip commit, revive topic={}, reviveQueueId={}", reviveTopic, queueId);
+ return;
+ }
+ brokerController.getConsumerOffsetManager().commitOffset(PopAckConstants.LOCAL_HOST, PopAckConstants.REVIVE_GROUP, reviveTopic, queueId, newOffset);
+ }
+ consumeReviveObj.newOffset = newOffset;
+ }
+
+ private void reviveMsgFromCk(PopCheckPoint popCheckPoint) throws Throwable {
+ for (int j = 0; j < popCheckPoint.getNum(); j++) {
+ if (DataConverter.getBit(popCheckPoint.getBitMap(), j)) {
+ continue;
+ }
+
+ // retry msg
+ long msgOffset = popCheckPoint.ackOffsetByIndex((byte) j);
+ MessageExt messageExt = getBizMessage(popCheckPoint.getTopic(), msgOffset, popCheckPoint.getQueueId());
+ if (messageExt == null) {
+ POP_LOGGER.warn("reviveQueueId={},can not get biz msg topic is {}, offset is {} , then continue ",
+ queueId, popCheckPoint.getTopic(), msgOffset);
+ continue;
+ }
+ //skip ck from last epoch
+ if (popCheckPoint.getPopTime() < messageExt.getStoreTimestamp()) {
+ POP_LOGGER.warn("reviveQueueId={},skip ck from last epoch {}", queueId, popCheckPoint);
+ continue;
+ }
+ reviveRetry(popCheckPoint, messageExt);
+ }
+ }
+
+ @Override
+ public void run() {
+ int slow = 1;
+ while (!this.isStopped()) {
+ try {
+ if (System.currentTimeMillis() < brokerController.getShouldStartTime()) {
+ POP_LOGGER.info("PopReviveService Ready to run after {}", brokerController.getShouldStartTime());
+ this.waitForRunning(1000);
+ continue;
+ }
+ this.waitForRunning(brokerController.getBrokerConfig().getReviveInterval());
+ if (!checkAndSetMaster()) {
+ POP_LOGGER.info("slave skip start revive topic={}, reviveQueueId={}", reviveTopic, queueId);
+ continue;
+ }
+
+ POP_LOGGER.info("start revive topic={}, reviveQueueId={}", reviveTopic, queueId);
+ ConsumeReviveObj consumeReviveObj = new ConsumeReviveObj();
+ consumeReviveMessage(consumeReviveObj);
+
+ if (!checkAndSetMaster()) {
+ POP_LOGGER.info("slave skip scan , revive topic={}, reviveQueueId={}", reviveTopic, queueId);
+ continue;
+ }
+
+ mergeAndRevive(consumeReviveObj);
+
+ ArrayList sortList = consumeReviveObj.sortList;
+ long delay = 0;
+ if (sortList != null && !sortList.isEmpty()) {
+ delay = (System.currentTimeMillis() - sortList.get(0).getReviveTime()) / 1000;
+ slow = 1;
+ }
+
+ POP_LOGGER.info("reviveQueueId={},revive finish,old offset is {}, new offset is {}, ckDelay={} ",
+ queueId, consumeReviveObj.oldOffset, consumeReviveObj.newOffset, delay);
+
+ if (sortList == null || sortList.isEmpty()) {
+ POP_LOGGER.info("reviveQueueId={},has no new msg ,take a rest {}", queueId, slow);
+ this.waitForRunning(slow * brokerController.getBrokerConfig().getReviveInterval());
+ if (slow < brokerController.getBrokerConfig().getReviveMaxSlow()) {
+ slow++;
+ }
+ }
+
+ } catch (Throwable e) {
+ POP_LOGGER.error("reviveQueueId=" + queueId + ",revive error", e);
+ }
+ }
+ }
+
+ static class ConsumeReviveObj {
+ HashMap map = new HashMap<>();
+ ArrayList sortList;
+ long oldOffset;
+ long endTime;
+ long newOffset;
+
+ ArrayList genSortList() {
+ if (sortList != null) {
+ return sortList;
+ }
+ sortList = new ArrayList<>(map.values());
+ Collections.sort(sortList, new Comparator() {
+ @Override
+ public int compare(PopCheckPoint o1, PopCheckPoint o2) {
+ return (int) (o1.getReviveOffset() - o2.getReviveOffset());
+ }
+ });
+ return sortList;
+ }
+ }
+}
diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/QueryAssignmentProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/QueryAssignmentProcessor.java
new file mode 100644
index 0000000000000000000000000000000000000000..fdc320de87f3062398bb5b69692e821fbed951d2
--- /dev/null
+++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/QueryAssignmentProcessor.java
@@ -0,0 +1,307 @@
+/*
+ * 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.processor;
+
+import io.netty.channel.ChannelHandlerContext;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.rocketmq.broker.BrokerController;
+import org.apache.rocketmq.broker.client.ConsumerGroupInfo;
+import org.apache.rocketmq.broker.loadbalance.AssignmentManager;
+import org.apache.rocketmq.broker.loadbalance.MessageRequestModeManager;
+import org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy;
+import org.apache.rocketmq.client.consumer.rebalance.AllocateMessageQueueAveragely;
+import org.apache.rocketmq.client.consumer.rebalance.AllocateMessageQueueAveragelyByCircle;
+import org.apache.rocketmq.common.MixAll;
+import org.apache.rocketmq.common.constant.LoggerName;
+import org.apache.rocketmq.common.message.MessageQueue;
+import org.apache.rocketmq.common.message.MessageQueueAssignment;
+import org.apache.rocketmq.common.message.MessageRequestMode;
+import org.apache.rocketmq.common.protocol.RequestCode;
+import org.apache.rocketmq.common.protocol.ResponseCode;
+import org.apache.rocketmq.common.protocol.body.QueryAssignmentRequestBody;
+import org.apache.rocketmq.common.protocol.body.QueryAssignmentResponseBody;
+import org.apache.rocketmq.common.protocol.body.SetMessageRequestModeRequestBody;
+import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;
+import org.apache.rocketmq.logging.InternalLogger;
+import org.apache.rocketmq.logging.InternalLoggerFactory;
+import org.apache.rocketmq.remoting.common.RemotingHelper;
+import org.apache.rocketmq.remoting.exception.RemotingCommandException;
+import org.apache.rocketmq.remoting.netty.NettyRequestProcessor;
+import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+
+public class QueryAssignmentProcessor implements NettyRequestProcessor {
+ private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME);
+
+ private final BrokerController brokerController;
+
+ private final ConcurrentHashMap name2LoadStrategy = new ConcurrentHashMap();
+
+ private MessageRequestModeManager messageRequestModeManager;
+
+ public QueryAssignmentProcessor(final BrokerController brokerController) {
+ this.brokerController = brokerController;
+
+ //register strategy
+ //NOTE: init with broker's log instead of init with ClientLogger.getLog();
+ AllocateMessageQueueAveragely allocateMessageQueueAveragely = new AllocateMessageQueueAveragely(log);
+ name2LoadStrategy.put(allocateMessageQueueAveragely.getName(), allocateMessageQueueAveragely);
+ AllocateMessageQueueAveragelyByCircle allocateMessageQueueAveragelyByCircle = new AllocateMessageQueueAveragelyByCircle(log);
+ name2LoadStrategy.put(allocateMessageQueueAveragelyByCircle.getName(), allocateMessageQueueAveragelyByCircle);
+
+ this.messageRequestModeManager = new MessageRequestModeManager(brokerController);
+ this.messageRequestModeManager.load();
+ }
+
+ @Override
+ public RemotingCommand processRequest(ChannelHandlerContext ctx,
+ RemotingCommand request) throws RemotingCommandException {
+ switch (request.getCode()) {
+ case RequestCode.QUERY_ASSIGNMENT:
+ return this.queryAssignment(ctx, request);
+ case RequestCode.SET_MESSAGE_REQUEST_MODE:
+ return this.setMessageRequestMode(ctx, request);
+ default:
+ break;
+ }
+ return null;
+ }
+
+ @Override
+ public boolean rejectRequest() {
+ return false;
+ }
+
+ /**
+ *
+ */
+ private RemotingCommand queryAssignment(ChannelHandlerContext ctx, RemotingCommand request)
+ throws RemotingCommandException {
+ final QueryAssignmentRequestBody requestBody = QueryAssignmentRequestBody.decode(request.getBody(), QueryAssignmentRequestBody.class);
+ final String topic = requestBody.getTopic();
+ final String consumerGroup = requestBody.getConsumerGroup();
+ final String clientId = requestBody.getClientId();
+ final MessageModel messageModel = requestBody.getMessageModel();
+ final String strategyName = requestBody.getStrategyName();
+
+ final RemotingCommand response = RemotingCommand.createResponseCommand(null);
+ final QueryAssignmentResponseBody responseBody = new QueryAssignmentResponseBody();
+
+ SetMessageRequestModeRequestBody setMessageRequestModeRequestBody = this.messageRequestModeManager.getMessageRequestMode(topic, consumerGroup);
+
+ if (setMessageRequestModeRequestBody == null) {
+ setMessageRequestModeRequestBody = new SetMessageRequestModeRequestBody();
+ setMessageRequestModeRequestBody.setTopic(topic);
+ setMessageRequestModeRequestBody.setConsumerGroup(consumerGroup);
+
+ if (topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
+ // retry topic must be pull mode
+ setMessageRequestModeRequestBody.setMode(MessageRequestMode.PULL);
+ } else {
+ setMessageRequestModeRequestBody.setMode(brokerController.getBrokerConfig().getDefaultMessageRequestMode());
+ }
+
+ if (setMessageRequestModeRequestBody.getMode() == MessageRequestMode.POP) {
+ setMessageRequestModeRequestBody.setPopShareQueueNum(brokerController.getBrokerConfig().getDefaultPopShareQueueNum());
+ }
+ }
+
+ Set messageQueues = doLoadBalance(topic, consumerGroup, clientId, messageModel, strategyName, setMessageRequestModeRequestBody, ctx);
+
+ Set assignments = null;
+ if (messageQueues != null) {
+ assignments = new HashSet();
+ for (MessageQueue messageQueue : messageQueues) {
+ MessageQueueAssignment messageQueueAssignment = new MessageQueueAssignment();
+ messageQueueAssignment.setMessageQueue(messageQueue);
+ if (setMessageRequestModeRequestBody != null) {
+ messageQueueAssignment.setMode(setMessageRequestModeRequestBody.getMode());
+ }
+ assignments.add(messageQueueAssignment);
+ }
+ }
+
+ responseBody.setMessageQueueAssignments(assignments);
+ response.setBody(responseBody.encode());
+ response.setCode(ResponseCode.SUCCESS);
+ response.setRemark(null);
+ return response;
+ }
+
+ /**
+ * Returns empty set means the client should clear all load assigned to it before, null means invalid result and the
+ * client should skip the update logic
+ *
+ * @param topic
+ * @param consumerGroup
+ * @param clientId
+ * @param messageModel
+ * @param strategyName
+ * @return the MessageQueues assigned to this client
+ */
+ private Set doLoadBalance(final String topic, final String consumerGroup, final String clientId,
+ final MessageModel messageModel, final String strategyName,
+ SetMessageRequestModeRequestBody setMessageRequestModeRequestBody, final ChannelHandlerContext ctx) {
+ Set assignedQueueSet = null;
+ AssignmentManager assignmentManager = brokerController.getAssignmentManager();
+
+ switch (messageModel) {
+ case BROADCASTING: {
+ assignedQueueSet = assignmentManager.getTopicSubscribeInfo(topic);
+ if (assignedQueueSet == null) {
+ log.warn("QueryLoad: no assignment for group[{}], the topic[{}] does not exist.", consumerGroup, topic);
+ }
+ break;
+ }
+ case CLUSTERING: {
+ Set mqSet = assignmentManager.getTopicSubscribeInfo(topic);
+ if (null == mqSet) {
+ if (!topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
+ log.warn("QueryLoad: no assignment for group[{}], the topic[{}] does not exist.", consumerGroup, topic);
+ }
+ return null;
+ }
+
+ if (!brokerController.getBrokerConfig().isServerLoadBalancerEnabled()) {
+ return mqSet;
+ }
+
+ List cidAll = null;
+ ConsumerGroupInfo consumerGroupInfo = this.brokerController.getConsumerManager().getConsumerGroupInfo(consumerGroup);
+ if (consumerGroupInfo != null) {
+ cidAll = consumerGroupInfo.getAllClientId();
+ }
+ if (null == cidAll) {
+ log.warn("QueryLoad: no assignment for group[{}] topic[{}], get consumer id list failed", consumerGroup, topic);
+ return null;
+ }
+
+ List mqAll = new ArrayList();
+ mqAll.addAll(mqSet);
+ Collections.sort(mqAll);
+ Collections.sort(cidAll);
+ List allocateResult = null;
+
+ try {
+ AllocateMessageQueueStrategy allocateMessageQueueStrategy = name2LoadStrategy.get(strategyName);
+ if (null == allocateMessageQueueStrategy) {
+ log.warn("QueryLoad: unsupported strategy [{}], {}", consumerGroup, RemotingHelper.parseChannelRemoteAddr(ctx.channel()));
+ return null;
+ }
+
+ if (setMessageRequestModeRequestBody != null && setMessageRequestModeRequestBody.getMode() == MessageRequestMode.POP) {
+ if (setMessageRequestModeRequestBody.getPopShareQueueNum() <= 0) {
+ //each client pop all messagequeue
+ allocateResult = new ArrayList<>(mqAll.size());
+ for (MessageQueue mq : mqAll) {
+ //must create new MessageQueue in case of change cache in AssignmentManager
+ MessageQueue newMq = new MessageQueue(mq.getTopic(), mq.getBrokerName(), -1);
+ allocateResult.add(newMq);
+ }
+
+ } else {
+ if (cidAll.size() <= mqAll.size()) {
+ //consumer working in pop mode could share the MessageQueues assigned to the N (N = popWorkGroupSize) consumer following it in the cid list
+ allocateResult = allocateMessageQueueStrategy.allocate(consumerGroup, clientId, mqAll, cidAll);
+ int index = cidAll.indexOf(clientId);
+ if (index >= 0) {
+ for (int i = 1; i <= setMessageRequestModeRequestBody.getPopShareQueueNum(); i++) {
+ index++;
+ index = index % cidAll.size();
+ List tmp = allocateMessageQueueStrategy.allocate(consumerGroup, cidAll.get(index), mqAll, cidAll);
+ allocateResult.addAll(tmp);
+ }
+ }
+ } else {
+ //make sure each cid is assigned
+ allocateResult = allocate(consumerGroup, clientId, mqAll, cidAll);
+ }
+ }
+
+ } else {
+ allocateResult = allocateMessageQueueStrategy.allocate(consumerGroup, clientId, mqAll, cidAll);
+ }
+ } catch (Throwable e) {
+ log.error("QueryLoad: no assignment for group[{}] topic[{}], allocate message queue exception. strategy name: {}, ex: {}", consumerGroup, topic, strategyName, e);
+ return null;
+ }
+
+ assignedQueueSet = new HashSet();
+ if (allocateResult != null) {
+ assignedQueueSet.addAll(allocateResult);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ return assignedQueueSet;
+ }
+
+ private List allocate(String consumerGroup, String currentCID, List mqAll,
+ List cidAll) {
+ if (currentCID == null || currentCID.length() < 1) {
+ throw new IllegalArgumentException("currentCID is empty");
+ }
+ if (mqAll == null || mqAll.isEmpty()) {
+ throw new IllegalArgumentException("mqAll is null or mqAll empty");
+ }
+ if (cidAll == null || cidAll.isEmpty()) {
+ throw new IllegalArgumentException("cidAll is null or cidAll empty");
+ }
+
+ List result = new ArrayList();
+ if (!cidAll.contains(currentCID)) {
+ log.info("[BUG] ConsumerGroup: {} The consumerId: {} not in cidAll: {}",
+ consumerGroup,
+ currentCID,
+ cidAll);
+ return result;
+ }
+
+ int index = cidAll.indexOf(currentCID);
+ result.add(mqAll.get(index % mqAll.size()));
+ return result;
+ }
+
+ private RemotingCommand setMessageRequestMode(ChannelHandlerContext ctx,
+ RemotingCommand request) throws RemotingCommandException {
+ final RemotingCommand response = RemotingCommand.createResponseCommand(null);
+ final SetMessageRequestModeRequestBody requestBody = SetMessageRequestModeRequestBody.decode(request.getBody(), SetMessageRequestModeRequestBody.class);
+
+ final String topic = requestBody.getTopic();
+ if (topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
+ response.setCode(ResponseCode.NO_PERMISSION);
+ response.setRemark("retry topic is not allowed to set mode");
+ return response;
+ }
+
+ final String consumerGroup = requestBody.getConsumerGroup();
+
+ this.messageRequestModeManager.setMessageRequestMode(topic, consumerGroup, requestBody);
+ this.messageRequestModeManager.persist();
+
+ response.setCode(ResponseCode.SUCCESS);
+ response.setRemark(null);
+ return response;
+ }
+
+}
diff --git a/broker/src/main/java/org/apache/rocketmq/broker/util/MsgUtil.java b/broker/src/main/java/org/apache/rocketmq/broker/util/MsgUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..80254c96adb26d5e43ed8f7aee4b342e08b920cf
--- /dev/null
+++ b/broker/src/main/java/org/apache/rocketmq/broker/util/MsgUtil.java
@@ -0,0 +1,34 @@
+/*
+ * 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.util;
+
+import org.apache.rocketmq.broker.BrokerController;
+import org.apache.rocketmq.common.message.Message;
+import org.apache.rocketmq.common.message.MessageExt;
+
+public final class MsgUtil {
+ private MsgUtil() {
+ }
+
+ public static void setMessageDeliverTime(BrokerController brokerController, Message msgInner, long timeMillis) {
+ msgInner.setDelayTimeLevel(brokerController.getMessageStore().getScheduleMessageService().computeDelayLevel(timeMillis));
+ }
+
+ public static long getMessageDeliverTime(BrokerController brokerController, MessageExt msgInner) {
+ return brokerController.getMessageStore().getScheduleMessageService().computeDeliverTimestamp(msgInner.getDelayTimeLevel(), msgInner.getStoreTimestamp());
+ }
+}
diff --git a/broker/src/test/java/org/apache/rocketmq/broker/processor/AckMessageProcessorTest.java b/broker/src/test/java/org/apache/rocketmq/broker/processor/AckMessageProcessorTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..c269523007adcfa9efe95e239683686c11701f5c
--- /dev/null
+++ b/broker/src/test/java/org/apache/rocketmq/broker/processor/AckMessageProcessorTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.processor;
+
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import java.lang.reflect.Field;
+import org.apache.rocketmq.broker.BrokerController;
+import org.apache.rocketmq.broker.client.ClientChannelInfo;
+import org.apache.rocketmq.broker.client.net.Broker2Client;
+import org.apache.rocketmq.common.BrokerConfig;
+import org.apache.rocketmq.common.TopicConfig;
+import org.apache.rocketmq.common.message.MessageConst;
+import org.apache.rocketmq.common.protocol.RequestCode;
+import org.apache.rocketmq.common.protocol.ResponseCode;
+import org.apache.rocketmq.common.protocol.header.AckMessageRequestHeader;
+import org.apache.rocketmq.common.protocol.header.ExtraInfoUtil;
+import org.apache.rocketmq.common.protocol.heartbeat.ConsumerData;
+import org.apache.rocketmq.remoting.exception.RemotingCommandException;
+import org.apache.rocketmq.remoting.exception.RemotingSendRequestException;
+import org.apache.rocketmq.remoting.exception.RemotingTimeoutException;
+import org.apache.rocketmq.remoting.netty.NettyClientConfig;
+import org.apache.rocketmq.remoting.netty.NettyServerConfig;
+import org.apache.rocketmq.remoting.protocol.LanguageCode;
+import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+import org.apache.rocketmq.store.AppendMessageResult;
+import org.apache.rocketmq.store.AppendMessageStatus;
+import org.apache.rocketmq.store.DefaultMessageStore;
+import org.apache.rocketmq.store.MessageExtBrokerInner;
+import org.apache.rocketmq.store.PutMessageResult;
+import org.apache.rocketmq.store.PutMessageStatus;
+import org.apache.rocketmq.store.config.MessageStoreConfig;
+import org.apache.rocketmq.store.schedule.ScheduleMessageService;
+import org.junit.Before;
+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.apache.rocketmq.broker.processor.PullMessageProcessorTest.createConsumerData;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class AckMessageProcessorTest {
+ private AckMessageProcessor ackMessageProcessor;
+ @Spy
+ private BrokerController brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), new MessageStoreConfig());
+ @Mock
+ private ChannelHandlerContext handlerContext;
+ @Mock
+ private DefaultMessageStore messageStore;
+ @Mock
+ private Channel channel;
+
+ private String topic = "FooBar";
+ private String group = "FooBarGroup";
+ private ClientChannelInfo clientInfo;
+ @Mock
+ private Broker2Client broker2Client;
+
+ @Before
+ public void init() throws IllegalAccessException, NoSuchFieldException {
+ clientInfo = new ClientChannelInfo(channel, "127.0.0.1", LanguageCode.JAVA, 0);
+ brokerController.setMessageStore(messageStore);
+ Field field = BrokerController.class.getDeclaredField("broker2Client");
+ field.setAccessible(true);
+ field.set(brokerController, broker2Client);
+ ScheduleMessageService scheduleMessageService = new ScheduleMessageService(messageStore);
+ MessageStoreConfig messageStoreConfig = new MessageStoreConfig();
+ messageStoreConfig.setMessageDelayLevel("5s 10s");
+ when(messageStore.getMessageStoreConfig()).thenReturn(messageStoreConfig);
+ scheduleMessageService.parseDelayLevel();
+ when(messageStore.getScheduleMessageService()).thenReturn(scheduleMessageService);
+ Channel mockChannel = mock(Channel.class);
+ when(handlerContext.channel()).thenReturn(mockChannel);
+ brokerController.getTopicConfigManager().getTopicConfigTable().put(topic, new TopicConfig());
+ ConsumerData consumerData = createConsumerData(group, topic);
+ brokerController.getConsumerManager().registerConsumer(
+ consumerData.getGroupName(),
+ clientInfo,
+ consumerData.getConsumeType(),
+ consumerData.getMessageModel(),
+ consumerData.getConsumeFromWhere(),
+ consumerData.getSubscriptionDataSet(),
+ false);
+ ackMessageProcessor = new AckMessageProcessor(brokerController);
+ }
+
+ @Test
+ public void testProcessRequest_Success() throws RemotingCommandException, InterruptedException, RemotingTimeoutException, RemotingSendRequestException {
+ when(messageStore.putMessage(any(MessageExtBrokerInner.class))).thenReturn(new PutMessageResult(PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK)));
+
+ int queueId = 0;
+ long queueOffset = 0;
+ long popTime = System.currentTimeMillis() - 1_000;
+ long invisibleTime = 30_000;
+ int reviveQid = 0;
+ String brokerName = "test_broker";
+ String extraInfo = ExtraInfoUtil.buildExtraInfo(queueOffset, popTime, invisibleTime, reviveQid,
+ topic, brokerName, queueId) + MessageConst.KEY_SEPARATOR + queueOffset;
+ AckMessageRequestHeader requestHeader = new AckMessageRequestHeader();
+ requestHeader.setTopic(topic);
+ requestHeader.setQueueId(0);
+ requestHeader.setOffset(0L);
+ requestHeader.setConsumerGroup(group);
+ requestHeader.setExtraInfo(extraInfo);
+
+ RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.ACK_MESSAGE, requestHeader);
+ request.makeCustomHeaderToNet();
+ RemotingCommand responseToReturn = ackMessageProcessor.processRequest(handlerContext, request);
+ assertThat(responseToReturn.getCode()).isEqualTo(ResponseCode.SUCCESS);
+ assertThat(responseToReturn.getOpaque()).isEqualTo(request.getOpaque());
+ }
+}
\ No newline at end of file
diff --git a/broker/src/test/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessorTest.java b/broker/src/test/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessorTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..f963c231ffb567bc4b89552c1484509b9e7a3a86
--- /dev/null
+++ b/broker/src/test/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessorTest.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.broker.processor;
+
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import java.lang.reflect.Field;
+import org.apache.rocketmq.broker.BrokerController;
+import org.apache.rocketmq.broker.client.ClientChannelInfo;
+import org.apache.rocketmq.broker.client.net.Broker2Client;
+import org.apache.rocketmq.common.BrokerConfig;
+import org.apache.rocketmq.common.TopicConfig;
+import org.apache.rocketmq.common.message.MessageConst;
+import org.apache.rocketmq.common.protocol.RequestCode;
+import org.apache.rocketmq.common.protocol.ResponseCode;
+import org.apache.rocketmq.common.protocol.header.ChangeInvisibleTimeRequestHeader;
+import org.apache.rocketmq.common.protocol.header.ExtraInfoUtil;
+import org.apache.rocketmq.common.protocol.heartbeat.ConsumerData;
+import org.apache.rocketmq.remoting.exception.RemotingCommandException;
+import org.apache.rocketmq.remoting.exception.RemotingSendRequestException;
+import org.apache.rocketmq.remoting.exception.RemotingTimeoutException;
+import org.apache.rocketmq.remoting.netty.NettyClientConfig;
+import org.apache.rocketmq.remoting.netty.NettyServerConfig;
+import org.apache.rocketmq.remoting.protocol.LanguageCode;
+import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+import org.apache.rocketmq.store.AppendMessageResult;
+import org.apache.rocketmq.store.AppendMessageStatus;
+import org.apache.rocketmq.store.DefaultMessageStore;
+import org.apache.rocketmq.store.MessageExtBrokerInner;
+import org.apache.rocketmq.store.PutMessageResult;
+import org.apache.rocketmq.store.PutMessageStatus;
+import org.apache.rocketmq.store.config.MessageStoreConfig;
+import org.apache.rocketmq.store.schedule.ScheduleMessageService;
+import org.junit.Before;
+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.apache.rocketmq.broker.processor.PullMessageProcessorTest.createConsumerData;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ChangeInvisibleTimeProcessorTest {
+ private ChangeInvisibleTimeProcessor changeInvisibleTimeProcessor;
+ @Spy
+ private BrokerController brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), new MessageStoreConfig());
+ @Mock
+ private ChannelHandlerContext handlerContext;
+ @Mock
+ private DefaultMessageStore messageStore;
+ @Mock
+ private Channel channel;
+
+ private String topic = "FooBar";
+ private String group = "FooBarGroup";
+ private ClientChannelInfo clientInfo;
+ @Mock
+ private Broker2Client broker2Client;
+
+ @Before
+ public void init() throws IllegalAccessException, NoSuchFieldException {
+ brokerController.setMessageStore(messageStore);
+ Field field = BrokerController.class.getDeclaredField("broker2Client");
+ field.setAccessible(true);
+ field.set(brokerController, broker2Client);
+ ScheduleMessageService scheduleMessageService = new ScheduleMessageService(messageStore);
+ MessageStoreConfig messageStoreConfig = new MessageStoreConfig();
+ messageStoreConfig.setMessageDelayLevel("5s 10s");
+ when(messageStore.getMessageStoreConfig()).thenReturn(messageStoreConfig);
+ scheduleMessageService.parseDelayLevel();
+ when(messageStore.getScheduleMessageService()).thenReturn(scheduleMessageService);
+ Channel mockChannel = mock(Channel.class);
+ when(handlerContext.channel()).thenReturn(mockChannel);
+ brokerController.getTopicConfigManager().getTopicConfigTable().put(topic, new TopicConfig());
+ ConsumerData consumerData = createConsumerData(group, topic);
+ clientInfo = new ClientChannelInfo(channel, "127.0.0.1", LanguageCode.JAVA, 0);
+ brokerController.getConsumerManager().registerConsumer(
+ consumerData.getGroupName(),
+ clientInfo,
+ consumerData.getConsumeType(),
+ consumerData.getMessageModel(),
+ consumerData.getConsumeFromWhere(),
+ consumerData.getSubscriptionDataSet(),
+ false);
+ changeInvisibleTimeProcessor = new ChangeInvisibleTimeProcessor(brokerController);
+ }
+
+ @Test
+ public void testProcessRequest_Success() throws RemotingCommandException, InterruptedException, RemotingTimeoutException, RemotingSendRequestException {
+ when(messageStore.putMessage(any(MessageExtBrokerInner.class))).thenReturn(new PutMessageResult(PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK)));
+ int queueId = 0;
+ long queueOffset = 0;
+ long popTime = System.currentTimeMillis() - 1_000;
+ long invisibleTime = 30_000;
+ int reviveQid = 0;
+ String brokerName = "test_broker";
+ String extraInfo = ExtraInfoUtil.buildExtraInfo(queueOffset, popTime, invisibleTime, reviveQid,
+ topic, brokerName, queueId) + MessageConst.KEY_SEPARATOR + queueOffset;
+
+ ChangeInvisibleTimeRequestHeader requestHeader = new ChangeInvisibleTimeRequestHeader();
+ requestHeader.setTopic(topic);
+ requestHeader.setQueueId(queueId);
+ requestHeader.setOffset(queueOffset);
+ requestHeader.setConsumerGroup(group);
+ requestHeader.setExtraInfo(extraInfo);
+ requestHeader.setInvisibleTime(invisibleTime);
+
+ final RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.CHANGE_MESSAGE_INVISIBLETIME, requestHeader);
+ request.makeCustomHeaderToNet();
+ RemotingCommand responseToReturn = changeInvisibleTimeProcessor.processRequest(handlerContext, request);
+ assertThat(responseToReturn.getCode()).isEqualTo(ResponseCode.SUCCESS);
+ assertThat(responseToReturn.getOpaque()).isEqualTo(request.getOpaque());
+ }
+}
\ No newline at end of file
diff --git a/broker/src/test/java/org/apache/rocketmq/broker/processor/PopBufferMergeServiceTest.java b/broker/src/test/java/org/apache/rocketmq/broker/processor/PopBufferMergeServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..4d643cd50fe204c188c4b8d30fc1a3110f1f9dd6
--- /dev/null
+++ b/broker/src/test/java/org/apache/rocketmq/broker/processor/PopBufferMergeServiceTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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.processor;
+
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import org.apache.commons.lang3.reflect.FieldUtils;
+import org.apache.rocketmq.broker.BrokerController;
+import org.apache.rocketmq.broker.client.ClientChannelInfo;
+import org.apache.rocketmq.common.BrokerConfig;
+import org.apache.rocketmq.common.TopicConfig;
+import org.apache.rocketmq.common.protocol.heartbeat.ConsumerData;
+import org.apache.rocketmq.remoting.netty.NettyClientConfig;
+import org.apache.rocketmq.remoting.netty.NettyServerConfig;
+import org.apache.rocketmq.store.DefaultMessageStore;
+import org.apache.rocketmq.store.config.MessageStoreConfig;
+import org.apache.rocketmq.store.pop.AckMsg;
+import org.apache.rocketmq.store.pop.PopCheckPoint;
+import org.apache.rocketmq.store.schedule.ScheduleMessageService;
+import org.junit.Before;
+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.apache.rocketmq.broker.processor.PullMessageProcessorTest.createConsumerData;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class PopBufferMergeServiceTest {
+ @Spy
+ private BrokerController brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), new MessageStoreConfig());
+ @Mock
+ private PopMessageProcessor popMessageProcessor;
+ @Mock
+ private ChannelHandlerContext handlerContext;
+ @Mock
+ private DefaultMessageStore messageStore;
+ private ScheduleMessageService scheduleMessageService;
+ private ClientChannelInfo clientChannelInfo;
+ private String group = "FooBarGroup";
+ private String topic = "FooBar";
+
+ @Before
+ public void init() throws Exception {
+ FieldUtils.writeField(brokerController.getBrokerConfig(), "enablePopBufferMerge", true, true);
+ brokerController.setMessageStore(messageStore);
+ popMessageProcessor = new PopMessageProcessor(brokerController);
+ scheduleMessageService = new ScheduleMessageService(messageStore);
+ MessageStoreConfig messageStoreConfig = new MessageStoreConfig();
+ messageStoreConfig.setMessageDelayLevel("5s 10s");
+ when(messageStore.getMessageStoreConfig()).thenReturn(messageStoreConfig);
+ scheduleMessageService.parseDelayLevel();
+ Channel mockChannel = mock(Channel.class);
+ brokerController.getTopicConfigManager().getTopicConfigTable().put(topic, new TopicConfig());
+ clientChannelInfo = new ClientChannelInfo(mockChannel);
+ ConsumerData consumerData = createConsumerData(group, topic);
+ brokerController.getConsumerManager().registerConsumer(
+ consumerData.getGroupName(),
+ clientChannelInfo,
+ consumerData.getConsumeType(),
+ consumerData.getMessageModel(),
+ consumerData.getConsumeFromWhere(),
+ consumerData.getSubscriptionDataSet(),
+ false);
+ }
+
+ @Test(timeout = 10_000)
+ public void testBasic() throws Exception {
+ PopBufferMergeService popBufferMergeService = new PopBufferMergeService(brokerController, popMessageProcessor);
+ popBufferMergeService.start();
+ PopCheckPoint ck = new PopCheckPoint();
+ ck.setBitMap(0);
+ int msgCnt = 1;
+ ck.setNum((byte) msgCnt);
+ long popTime = System.currentTimeMillis() - 1000;
+ ck.setPopTime(popTime);
+ int invisibleTime = 30_000;
+ ck.setInvisibleTime(invisibleTime);
+ int offset = 100;
+ ck.setStartOffset(offset);
+ ck.setCId(group);
+ ck.setTopic(topic);
+ int queueId = 0;
+ ck.setQueueId((byte) queueId);
+
+ int reviveQid = 0;
+ long nextBeginOffset = 101L;
+ long ackOffset = offset;
+ AckMsg ackMsg = new AckMsg();
+ ackMsg.setAckOffset(ackOffset);
+ ackMsg.setStartOffset(offset);
+ ackMsg.setConsumerGroup(group);
+ ackMsg.setTopic(topic);
+ ackMsg.setQueueId(queueId);
+ ackMsg.setPopTime(popTime);
+ try {
+ assertThat(popBufferMergeService.addCk(ck, reviveQid, ackOffset, nextBeginOffset)).isTrue();
+ assertThat(popBufferMergeService.getLatestOffset(topic, group, queueId)).isEqualTo(nextBeginOffset);
+ Thread.sleep(1000); // wait background threads of PopBufferMergeService run for some time
+ assertThat(popBufferMergeService.addAk(reviveQid, ackMsg)).isTrue();
+ assertThat(popBufferMergeService.getLatestOffset(topic, group, queueId)).isEqualTo(nextBeginOffset);
+ } finally {
+ popBufferMergeService.shutdown(true);
+ }
+ }
+}
\ No newline at end of file
diff --git a/broker/src/test/java/org/apache/rocketmq/broker/processor/PopMessageProcessorTest.java b/broker/src/test/java/org/apache/rocketmq/broker/processor/PopMessageProcessorTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..87d96b2fb99b8188bc70b9ed298c165627a9bf65
--- /dev/null
+++ b/broker/src/test/java/org/apache/rocketmq/broker/processor/PopMessageProcessorTest.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.broker.processor;
+
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import org.apache.rocketmq.broker.BrokerController;
+import org.apache.rocketmq.broker.client.ClientChannelInfo;
+import org.apache.rocketmq.common.BrokerConfig;
+import org.apache.rocketmq.common.TopicConfig;
+import org.apache.rocketmq.common.constant.ConsumeInitMode;
+import org.apache.rocketmq.common.message.MessageDecoder;
+import org.apache.rocketmq.common.protocol.RequestCode;
+import org.apache.rocketmq.common.protocol.ResponseCode;
+import org.apache.rocketmq.common.protocol.header.PopMessageRequestHeader;
+import org.apache.rocketmq.common.protocol.heartbeat.ConsumerData;
+import org.apache.rocketmq.remoting.exception.RemotingCommandException;
+import org.apache.rocketmq.remoting.netty.NettyClientConfig;
+import org.apache.rocketmq.remoting.netty.NettyServerConfig;
+import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+import org.apache.rocketmq.store.AppendMessageResult;
+import org.apache.rocketmq.store.AppendMessageStatus;
+import org.apache.rocketmq.store.DefaultMessageStore;
+import org.apache.rocketmq.store.GetMessageResult;
+import org.apache.rocketmq.store.GetMessageStatus;
+import org.apache.rocketmq.store.MappedFile;
+import org.apache.rocketmq.store.PutMessageResult;
+import org.apache.rocketmq.store.PutMessageStatus;
+import org.apache.rocketmq.store.SelectMappedBufferResult;
+import org.apache.rocketmq.store.config.MessageStoreConfig;
+import org.apache.rocketmq.store.schedule.ScheduleMessageService;
+import org.junit.Before;
+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.apache.rocketmq.broker.processor.PullMessageProcessorTest.createConsumerData;
+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.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class PopMessageProcessorTest {
+ private PopMessageProcessor popMessageProcessor;
+
+ @Spy
+ private BrokerController brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), new MessageStoreConfig());
+ @Mock
+ private ChannelHandlerContext handlerContext;
+ @Mock
+ private DefaultMessageStore messageStore;
+ private ScheduleMessageService scheduleMessageService;
+ private ClientChannelInfo clientChannelInfo;
+ private String group = "FooBarGroup";
+ private String topic = "FooBar";
+
+ @Before
+ public void init() {
+ brokerController.setMessageStore(messageStore);
+ popMessageProcessor = new PopMessageProcessor(brokerController);
+ scheduleMessageService = new ScheduleMessageService(messageStore);
+ MessageStoreConfig messageStoreConfig = new MessageStoreConfig();
+ messageStoreConfig.setMessageDelayLevel("5s 10s");
+ when(messageStore.getMessageStoreConfig()).thenReturn(messageStoreConfig);
+ scheduleMessageService.parseDelayLevel();
+ when(messageStore.getScheduleMessageService()).thenReturn(scheduleMessageService);
+ when(messageStore.putMessage(any())).thenReturn(new PutMessageResult(PutMessageStatus.PUT_OK, new AppendMessageResult(AppendMessageStatus.PUT_OK)));
+ Channel mockChannel = mock(Channel.class);
+ when(mockChannel.remoteAddress()).thenReturn(new InetSocketAddress(1024));
+ when(handlerContext.channel()).thenReturn(mockChannel);
+ brokerController.getTopicConfigManager().getTopicConfigTable().put(topic, new TopicConfig());
+ clientChannelInfo = new ClientChannelInfo(mockChannel);
+ ConsumerData consumerData = createConsumerData(group, topic);
+ brokerController.getConsumerManager().registerConsumer(
+ consumerData.getGroupName(),
+ clientChannelInfo,
+ consumerData.getConsumeType(),
+ consumerData.getMessageModel(),
+ consumerData.getConsumeFromWhere(),
+ consumerData.getSubscriptionDataSet(),
+ false);
+ }
+
+ @Test
+ public void testProcessRequest_TopicNotExist() throws RemotingCommandException {
+ brokerController.getTopicConfigManager().getTopicConfigTable().remove(topic);
+ final RemotingCommand request = createPopMsgCommand();
+ RemotingCommand response = popMessageProcessor.processRequest(handlerContext, request);
+ assertThat(response).isNotNull();
+ assertThat(response.getCode()).isEqualTo(ResponseCode.TOPIC_NOT_EXIST);
+ assertThat(response.getRemark()).contains("topic[" + topic + "] not exist");
+ }
+
+ @Test
+ public void testProcessRequest_SubNotExist() throws RemotingCommandException {
+ brokerController.getConsumerManager().unregisterConsumer(group, clientChannelInfo, false);
+ final RemotingCommand request = createPopMsgCommand();
+ RemotingCommand response = popMessageProcessor.processRequest(handlerContext, request);
+ assertThat(response).isNotNull();
+ assertThat(response.getCode()).isEqualTo(ResponseCode.SUBSCRIPTION_NOT_EXIST);
+ assertThat(response.getRemark()).contains("consumer's group info not exist");
+ }
+
+ @Test
+ public void testProcessRequest_Found() throws RemotingCommandException {
+ GetMessageResult getMessageResult = createGetMessageResult(1);
+ when(messageStore.getMessage(anyString(), anyString(), anyInt(), anyLong(), anyInt(), any())).thenReturn(getMessageResult);
+
+ final RemotingCommand request = createPopMsgCommand();
+ RemotingCommand response = popMessageProcessor.processRequest(handlerContext, request);
+ assertThat(response).isNotNull();
+ assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS);
+ }
+
+ @Test
+ public void testProcessRequest_MsgWasRemoving() throws RemotingCommandException {
+ GetMessageResult getMessageResult = createGetMessageResult(1);
+ getMessageResult.setStatus(GetMessageStatus.MESSAGE_WAS_REMOVING);
+ when(messageStore.getMessage(anyString(), anyString(), anyInt(), anyLong(), anyInt(), any())).thenReturn(getMessageResult);
+
+ final RemotingCommand request = createPopMsgCommand();
+ RemotingCommand response = popMessageProcessor.processRequest(handlerContext, request);
+ assertThat(response).isNotNull();
+ assertThat(response.getCode()).isEqualTo(ResponseCode.SUCCESS);
+ }
+
+ @Test
+ public void testProcessRequest_NoMsgInQueue() throws RemotingCommandException {
+ GetMessageResult getMessageResult = createGetMessageResult(0);
+ getMessageResult.setStatus(GetMessageStatus.NO_MESSAGE_IN_QUEUE);
+ when(messageStore.getMessage(anyString(), anyString(), anyInt(), anyLong(), anyInt(), any())).thenReturn(getMessageResult);
+
+ final RemotingCommand request = createPopMsgCommand();
+ RemotingCommand response = popMessageProcessor.processRequest(handlerContext, request);
+ assertThat(response).isNull();
+ }
+
+
+ private RemotingCommand createPopMsgCommand() {
+ PopMessageRequestHeader requestHeader = new PopMessageRequestHeader();
+ requestHeader.setConsumerGroup(group);
+ requestHeader.setMaxMsgNums(30);
+ requestHeader.setQueueId(-1);
+ requestHeader.setTopic(topic);
+ requestHeader.setInvisibleTime(10_000);
+ requestHeader.setInitMode(ConsumeInitMode.MAX);
+ requestHeader.setOrder(false);
+ requestHeader.setPollTime(15_000);
+ requestHeader.setBornTime(System.currentTimeMillis());
+ RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.POP_MESSAGE, requestHeader);
+ request.makeCustomHeaderToNet();
+ return request;
+ }
+
+
+ private GetMessageResult createGetMessageResult(int msgCnt) {
+ GetMessageResult getMessageResult = new GetMessageResult();
+ getMessageResult.setStatus(GetMessageStatus.FOUND);
+ getMessageResult.setMinOffset(100);
+ getMessageResult.setMaxOffset(1024);
+ getMessageResult.setNextBeginOffset(516);
+ for (int i = 0; i < msgCnt; i++) {
+ ByteBuffer bb = ByteBuffer.allocate(64);
+ bb.putLong(MessageDecoder.MESSAGE_STORE_TIMESTAMP_POSITION, System.currentTimeMillis());
+ getMessageResult.addMessage(new SelectMappedBufferResult(200, bb, 64, new MappedFile()));
+ }
+ return getMessageResult;
+ }
+}
\ No newline at end of file
diff --git a/broker/src/test/java/org/apache/rocketmq/broker/processor/QueryAssignmentProcessorTest.java b/broker/src/test/java/org/apache/rocketmq/broker/processor/QueryAssignmentProcessorTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..681fcc30c53a98f6b3392235bde4a46875c90bcb
--- /dev/null
+++ b/broker/src/test/java/org/apache/rocketmq/broker/processor/QueryAssignmentProcessorTest.java
@@ -0,0 +1,153 @@
+/*
+ * 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.processor;
+
+import com.google.common.collect.ImmutableSet;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import org.apache.rocketmq.broker.BrokerController;
+import org.apache.rocketmq.broker.client.ClientChannelInfo;
+import org.apache.rocketmq.broker.loadbalance.AssignmentManager;
+import org.apache.rocketmq.common.BrokerConfig;
+import org.apache.rocketmq.common.MixAll;
+import org.apache.rocketmq.common.TopicConfig;
+import org.apache.rocketmq.common.message.MessageQueue;
+import org.apache.rocketmq.common.message.MessageRequestMode;
+import org.apache.rocketmq.common.protocol.RequestCode;
+import org.apache.rocketmq.common.protocol.ResponseCode;
+import org.apache.rocketmq.common.protocol.body.QueryAssignmentRequestBody;
+import org.apache.rocketmq.common.protocol.body.QueryAssignmentResponseBody;
+import org.apache.rocketmq.common.protocol.body.SetMessageRequestModeRequestBody;
+import org.apache.rocketmq.common.protocol.heartbeat.ConsumerData;
+import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;
+import org.apache.rocketmq.remoting.netty.NettyClientConfig;
+import org.apache.rocketmq.remoting.netty.NettyServerConfig;
+import org.apache.rocketmq.remoting.protocol.LanguageCode;
+import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+import org.apache.rocketmq.store.MessageStore;
+import org.apache.rocketmq.store.config.MessageStoreConfig;
+import org.junit.Before;
+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.apache.rocketmq.broker.processor.PullMessageProcessorTest.createConsumerData;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class QueryAssignmentProcessorTest {
+ private QueryAssignmentProcessor queryAssignmentProcessor;
+ @Spy
+ private BrokerController brokerController = new BrokerController(new BrokerConfig(), new NettyServerConfig(), new NettyClientConfig(), new MessageStoreConfig());
+
+ @Mock
+ private AssignmentManager assignmentManager;
+ @Mock
+ private ChannelHandlerContext handlerContext;
+ @Mock
+ private MessageStore messageStore;
+ @Mock
+ private Channel channel;
+
+ private String topic = "FooBar";
+ private String group = "FooBarGroup";
+ private String clientId = "127.0.0.1";
+ private ClientChannelInfo clientInfo;
+
+ @Before
+ public void init() throws IllegalAccessException, NoSuchFieldException {
+ clientInfo = new ClientChannelInfo(channel, "127.0.0.1", LanguageCode.JAVA, 0);
+ brokerController.setMessageStore(messageStore);
+ doReturn(assignmentManager).when(brokerController).getAssignmentManager();
+ when(assignmentManager.getTopicSubscribeInfo(topic)).thenReturn(ImmutableSet.of(new MessageQueue(topic, "broker-1", 0), new MessageQueue(topic, "broker-2", 1)));
+ queryAssignmentProcessor = new QueryAssignmentProcessor(brokerController);
+ brokerController.getTopicConfigManager().getTopicConfigTable().put(topic, new TopicConfig());
+ ConsumerData consumerData = createConsumerData(group, topic);
+ brokerController.getConsumerManager().registerConsumer(
+ consumerData.getGroupName(),
+ clientInfo,
+ consumerData.getConsumeType(),
+ consumerData.getMessageModel(),
+ consumerData.getConsumeFromWhere(),
+ consumerData.getSubscriptionDataSet(),
+ false);
+ }
+
+ @Test
+ public void testQueryAssignment() throws Exception {
+ brokerController.getProducerManager().registerProducer(group, clientInfo);
+ final RemotingCommand request = createQueryAssignmentRequest();
+ RemotingCommand responseToReturn = queryAssignmentProcessor.processRequest(handlerContext, request);
+ assertThat(responseToReturn.getCode()).isEqualTo(ResponseCode.SUCCESS);
+ assertThat(responseToReturn.getBody()).isNotNull();
+ QueryAssignmentResponseBody responseBody = QueryAssignmentResponseBody.decode(responseToReturn.getBody(), QueryAssignmentResponseBody.class);
+ assertThat(responseBody.getMessageQueueAssignments()).size().isEqualTo(2);
+ }
+
+ @Test
+ public void testSetMessageRequestMode_Success() throws Exception {
+ brokerController.getProducerManager().registerProducer(group, clientInfo);
+ final RemotingCommand request = createSetMessageRequestModeRequest(topic);
+ RemotingCommand responseToReturn = queryAssignmentProcessor.processRequest(handlerContext, request);
+ assertThat(responseToReturn.getCode()).isEqualTo(ResponseCode.SUCCESS);
+ }
+
+ @Test
+ public void testSetMessageRequestMode_RetryTopic() throws Exception {
+ brokerController.getProducerManager().registerProducer(group, clientInfo);
+ final RemotingCommand request = createSetMessageRequestModeRequest(MixAll.RETRY_GROUP_TOPIC_PREFIX + topic);
+ RemotingCommand responseToReturn = queryAssignmentProcessor.processRequest(handlerContext, request);
+ assertThat(responseToReturn.getCode()).isEqualTo(ResponseCode.NO_PERMISSION);
+ }
+
+ private RemotingCommand createQueryAssignmentRequest() {
+ QueryAssignmentRequestBody requestBody = new QueryAssignmentRequestBody();
+ requestBody.setTopic(topic);
+ requestBody.setConsumerGroup(group);
+ requestBody.setClientId(clientId);
+ requestBody.setMessageModel(MessageModel.CLUSTERING);
+ requestBody.setStrategyName("AVG");
+
+ RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.QUERY_ASSIGNMENT, null);
+ request.setBody(requestBody.encode());
+ return request;
+ }
+
+ private RemotingCommand createSetMessageRequestModeRequest(String topic) {
+ RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.SET_MESSAGE_REQUEST_MODE, null);
+
+ SetMessageRequestModeRequestBody requestBody = new SetMessageRequestModeRequestBody();
+ requestBody.setTopic(topic);
+ requestBody.setConsumerGroup(group);
+ requestBody.setMode(MessageRequestMode.POP);
+ requestBody.setPopShareQueueNum(0);
+ request.setBody(requestBody.encode());
+
+ return request;
+ }
+
+ private RemotingCommand createResponse(int code, RemotingCommand request) {
+ RemotingCommand response = RemotingCommand.createResponseCommand(null);
+ response.setCode(code);
+ response.setOpaque(request.getOpaque());
+ return response;
+ }
+}
\ No newline at end of file
diff --git a/client/src/main/java/org/apache/rocketmq/client/consumer/AckCallback.java b/client/src/main/java/org/apache/rocketmq/client/consumer/AckCallback.java
new file mode 100644
index 0000000000000000000000000000000000000000..99a261c9de14b1fbf69d62eb440037174fbe0c4e
--- /dev/null
+++ b/client/src/main/java/org/apache/rocketmq/client/consumer/AckCallback.java
@@ -0,0 +1,23 @@
+/*
+ * 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.consumer;
+
+public interface AckCallback {
+ void onSuccess(final AckResult ackResult);
+
+ void onException(final Throwable e);
+}
diff --git a/client/src/main/java/org/apache/rocketmq/client/consumer/AckResult.java b/client/src/main/java/org/apache/rocketmq/client/consumer/AckResult.java
new file mode 100644
index 0000000000000000000000000000000000000000..06cb59a293c44e34665aca4a5c060f843a845432
--- /dev/null
+++ b/client/src/main/java/org/apache/rocketmq/client/consumer/AckResult.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.client.consumer;
+
+
+public class AckResult {
+ private AckStatus status;
+ private String extraInfo;
+ private long popTime;
+
+ public void setPopTime(long popTime) {
+ this.popTime = popTime;
+ }
+
+ public long getPopTime() {
+ return popTime;
+ }
+
+ public AckStatus getStatus() {
+ return status;
+ }
+
+ public void setStatus(AckStatus status) {
+ this.status = status;
+ }
+
+ public void setExtraInfo(String extraInfo) {
+ this.extraInfo = extraInfo;
+ }
+
+ public String getExtraInfo() {
+ return extraInfo;
+ }
+
+ @Override
+ public String toString() {
+ return "AckResult [AckStatus=" + status + ",extraInfo=" + extraInfo + "]";
+ }
+}
diff --git a/client/src/main/java/org/apache/rocketmq/client/consumer/AckStatus.java b/client/src/main/java/org/apache/rocketmq/client/consumer/AckStatus.java
new file mode 100644
index 0000000000000000000000000000000000000000..b144f8f454c32a11a7d1c63e59c900e65c81c79d
--- /dev/null
+++ b/client/src/main/java/org/apache/rocketmq/client/consumer/AckStatus.java
@@ -0,0 +1,28 @@
+/*
+ * 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.consumer;
+
+public enum AckStatus {
+ /**
+ * ack success
+ */
+ OK,
+ /**
+ * msg not exist
+ */
+ NO_EXIST,
+}
diff --git a/client/src/main/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumer.java b/client/src/main/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumer.java
index 9011117a79fc247800c3c9aa06d58c5c97bb01a8..f32215ab83df48350bd7cadd61d43b92fda4dc60 100644
--- a/client/src/main/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumer.java
+++ b/client/src/main/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumer.java
@@ -179,6 +179,12 @@ public class DefaultMQPushConsumer extends ClientConfig implements MQPushConsume
*/
private int pullThresholdForQueue = 1000;
+ /**
+ * Flow control threshold on queue level, means max num of messages waiting to ack.
+ * in contrast with pull threshold, once a message is popped, it's considered the beginning of consumption.
+ */
+ private int popThresholdForQueue = 96;
+
/**
* Limit the cached message size on queue level, each message queue will cache at most 100 MiB messages by default,
* Consider the {@code pullBatchSize}, the instantaneous value may exceed the limit
@@ -254,6 +260,16 @@ public class DefaultMQPushConsumer extends ClientConfig implements MQPushConsume
*/
private long consumeTimeout = 15;
+ /**
+ * Maximum amount of invisible time in millisecond of a message, rang is [5000, 300000]
+ */
+ private long popInvisibleTime = 60000;
+
+ /**
+ * Batch pop size. range is [1, 32]
+ */
+ private int popBatchNums = 32;
+
/**
* Maximum time to await message consuming when shutdown consumer, 0 indicates no await.
*/
@@ -264,6 +280,9 @@ public class DefaultMQPushConsumer extends ClientConfig implements MQPushConsume
*/
private TraceDispatcher traceDispatcher = null;
+ // force to use client rebalance
+ private boolean clientRebalance = true;
+
/**
* Default constructor.
*/
@@ -598,6 +617,14 @@ public class DefaultMQPushConsumer extends ClientConfig implements MQPushConsume
this.pullThresholdForQueue = pullThresholdForQueue;
}
+ public int getPopThresholdForQueue() {
+ return popThresholdForQueue;
+ }
+
+ public void setPopThresholdForQueue(int popThresholdForQueue) {
+ this.popThresholdForQueue = popThresholdForQueue;
+ }
+
public int getPullThresholdForTopic() {
return pullThresholdForTopic;
}
@@ -891,6 +918,14 @@ public class DefaultMQPushConsumer extends ClientConfig implements MQPushConsume
this.consumeTimeout = consumeTimeout;
}
+ public long getPopInvisibleTime() {
+ return popInvisibleTime;
+ }
+
+ public void setPopInvisibleTime(long popInvisibleTime) {
+ this.popInvisibleTime = popInvisibleTime;
+ }
+
public long getAwaitTerminationMillisWhenShutdown() {
return awaitTerminationMillisWhenShutdown;
}
@@ -902,4 +937,20 @@ public class DefaultMQPushConsumer extends ClientConfig implements MQPushConsume
public TraceDispatcher getTraceDispatcher() {
return traceDispatcher;
}
+
+ public int getPopBatchNums() {
+ return popBatchNums;
+ }
+
+ public void setPopBatchNums(int popBatchNums) {
+ this.popBatchNums = popBatchNums;
+ }
+
+ public boolean isClientRebalance() {
+ return clientRebalance;
+ }
+
+ public void setClientRebalance(boolean clientRebalance) {
+ this.clientRebalance = clientRebalance;
+ }
}
diff --git a/client/src/main/java/org/apache/rocketmq/client/consumer/PopCallback.java b/client/src/main/java/org/apache/rocketmq/client/consumer/PopCallback.java
new file mode 100644
index 0000000000000000000000000000000000000000..4932e7485ba750d28a56fb5108bc8b2c82aba6a4
--- /dev/null
+++ b/client/src/main/java/org/apache/rocketmq/client/consumer/PopCallback.java
@@ -0,0 +1,26 @@
+/*
+ * 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.consumer;
+
+/**
+ * Async message pop interface
+ */
+public interface PopCallback {
+ void onSuccess(final PopResult popResult);
+
+ void onException(final Throwable e);
+}
diff --git a/client/src/main/java/org/apache/rocketmq/client/consumer/PopResult.java b/client/src/main/java/org/apache/rocketmq/client/consumer/PopResult.java
new file mode 100644
index 0000000000000000000000000000000000000000..6423e90e4917d5b9695d922bfa9ab81a417f874a
--- /dev/null
+++ b/client/src/main/java/org/apache/rocketmq/client/consumer/PopResult.java
@@ -0,0 +1,82 @@
+/*
+ * 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.consumer;
+
+import java.util.List;
+import org.apache.rocketmq.common.message.MessageExt;
+
+public class PopResult {
+ private List msgFoundList;
+ private PopStatus popStatus;
+ private long popTime;
+ private long invisibleTime;
+ private long restNum;
+
+ public PopResult(PopStatus popStatus, List msgFoundList) {
+ this.popStatus = popStatus;
+ this.msgFoundList = msgFoundList;
+ }
+
+ public long getPopTime() {
+ return popTime;
+ }
+
+
+ public void setPopTime(long popTime) {
+ this.popTime = popTime;
+ }
+
+ public long getRestNum() {
+ return restNum;
+ }
+
+ public void setRestNum(long restNum) {
+ this.restNum = restNum;
+ }
+
+ public long getInvisibleTime() {
+ return invisibleTime;
+ }
+
+
+ public void setInvisibleTime(long invisibleTime) {
+ this.invisibleTime = invisibleTime;
+ }
+
+
+ public void setPopStatus(PopStatus popStatus) {
+ this.popStatus = popStatus;
+ }
+
+ public PopStatus getPopStatus() {
+ return popStatus;
+ }
+
+ public List getMsgFoundList() {
+ return msgFoundList;
+ }
+
+ public void setMsgFoundList(List msgFoundList) {
+ this.msgFoundList = msgFoundList;
+ }
+
+ @Override
+ public String toString() {
+ return "PopResult [popStatus=" + popStatus + ",msgFoundList="
+ + (msgFoundList == null ? 0 : msgFoundList.size()) + ",restNum=" + restNum + "]";
+ }
+}
diff --git a/client/src/main/java/org/apache/rocketmq/client/consumer/PopStatus.java b/client/src/main/java/org/apache/rocketmq/client/consumer/PopStatus.java
new file mode 100644
index 0000000000000000000000000000000000000000..17dda9a2001d6e173d4a4973532e3796c7de1b80
--- /dev/null
+++ b/client/src/main/java/org/apache/rocketmq/client/consumer/PopStatus.java
@@ -0,0 +1,37 @@
+/*
+ * 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.consumer;
+
+public enum PopStatus {
+ /**
+ * Founded
+ */
+ FOUND,
+ /**
+ * No new message can be pull after polling time out
+ * delete after next realease
+ */
+ NO_NEW_MSG,
+ /**
+ * polling pool is full, do not try again immediately.
+ */
+ POLLING_FULL,
+ /**
+ * polling time out but no message find
+ */
+ POLLING_NOT_FOUND
+}
diff --git a/client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueAveragely.java b/client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueAveragely.java
index 155e692ad0bef0d7e351b6cc1a73f707067bcf0b..4c31041e7caa4b91b14eb33635b373229efba2fa 100644
--- a/client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueAveragely.java
+++ b/client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueAveragely.java
@@ -20,14 +20,22 @@ import java.util.ArrayList;
import java.util.List;
import org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy;
import org.apache.rocketmq.client.log.ClientLogger;
-import org.apache.rocketmq.logging.InternalLogger;
import org.apache.rocketmq.common.message.MessageQueue;
+import org.apache.rocketmq.logging.InternalLogger;
/**
* Average Hashing queue algorithm
*/
public class AllocateMessageQueueAveragely implements AllocateMessageQueueStrategy {
- private final InternalLogger log = ClientLogger.getLog();
+ private InternalLogger log;
+
+ public AllocateMessageQueueAveragely() {
+ log = ClientLogger.getLog();
+ }
+
+ public AllocateMessageQueueAveragely(InternalLogger log) {
+ this.log = log;
+ }
@Override
public List allocate(String consumerGroup, String currentCID, List mqAll,
diff --git a/client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueAveragelyByCircle.java b/client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueAveragelyByCircle.java
index fe78f0a6bbf8f3e698c1c3a52ac2b6307b6a8ace..bd03e1ff584344c4e89381e9bc9359b72363e546 100644
--- a/client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueAveragelyByCircle.java
+++ b/client/src/main/java/org/apache/rocketmq/client/consumer/rebalance/AllocateMessageQueueAveragelyByCircle.java
@@ -20,14 +20,22 @@ import java.util.ArrayList;
import java.util.List;
import org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy;
import org.apache.rocketmq.client.log.ClientLogger;
-import org.apache.rocketmq.logging.InternalLogger;
import org.apache.rocketmq.common.message.MessageQueue;
+import org.apache.rocketmq.logging.InternalLogger;
/**
* Cycle average Hashing queue algorithm
*/
public class AllocateMessageQueueAveragelyByCircle implements AllocateMessageQueueStrategy {
- private final InternalLogger log = ClientLogger.getLog();
+ private InternalLogger log;
+
+ public AllocateMessageQueueAveragelyByCircle() {
+ log = ClientLogger.getLog();
+ }
+
+ public AllocateMessageQueueAveragelyByCircle(InternalLogger log) {
+ this.log = log;
+ }
@Override
public List allocate(String consumerGroup, String currentCID, List mqAll,
diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/BaseInvokeCallback.java b/client/src/main/java/org/apache/rocketmq/client/impl/BaseInvokeCallback.java
new file mode 100644
index 0000000000000000000000000000000000000000..baf6f17f7a58b5f342be46050be4c1c0b254f296
--- /dev/null
+++ b/client/src/main/java/org/apache/rocketmq/client/impl/BaseInvokeCallback.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.client.impl;
+
+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;
+ }
+
+ @Override
+ public void operationComplete(final ResponseFuture responseFuture) {
+ onComplete(responseFuture);
+ }
+
+ public abstract void onComplete(final ResponseFuture responseFuture);
+}
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 63b2045d1d5d5665217ac80c106cd703205c53eb..ef57bde5e1e27d68ace6fd3c670e4588d00e7968 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
@@ -18,6 +18,7 @@ package org.apache.rocketmq.client.impl;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
@@ -29,6 +30,12 @@ import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.client.ClientConfig;
+import org.apache.rocketmq.client.consumer.AckCallback;
+import org.apache.rocketmq.client.consumer.AckResult;
+import org.apache.rocketmq.client.consumer.AckStatus;
+import org.apache.rocketmq.client.consumer.PopCallback;
+import org.apache.rocketmq.client.consumer.PopResult;
+import org.apache.rocketmq.client.consumer.PopStatus;
import org.apache.rocketmq.client.consumer.PullCallback;
import org.apache.rocketmq.client.consumer.PullResult;
import org.apache.rocketmq.client.consumer.PullStatus;
@@ -59,6 +66,8 @@ import org.apache.rocketmq.common.message.MessageConst;
import org.apache.rocketmq.common.message.MessageDecoder;
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.message.MessageRequestMode;
import org.apache.rocketmq.common.namesrv.TopAddressing;
import org.apache.rocketmq.common.protocol.NamespaceUtil;
import org.apache.rocketmq.common.protocol.RequestCode;
@@ -77,15 +86,21 @@ import org.apache.rocketmq.common.protocol.body.KVTable;
import org.apache.rocketmq.common.protocol.body.LockBatchRequestBody;
import org.apache.rocketmq.common.protocol.body.LockBatchResponseBody;
import org.apache.rocketmq.common.protocol.body.ProducerConnection;
+import org.apache.rocketmq.common.protocol.body.QueryAssignmentRequestBody;
+import org.apache.rocketmq.common.protocol.body.QueryAssignmentResponseBody;
import org.apache.rocketmq.common.protocol.body.QueryConsumeQueueResponseBody;
import org.apache.rocketmq.common.protocol.body.QueryConsumeTimeSpanBody;
import org.apache.rocketmq.common.protocol.body.QueryCorrectionOffsetBody;
import org.apache.rocketmq.common.protocol.body.QueueTimeSpan;
import org.apache.rocketmq.common.protocol.body.ResetOffsetBody;
+import org.apache.rocketmq.common.protocol.body.SetMessageRequestModeRequestBody;
import org.apache.rocketmq.common.protocol.body.SubscriptionGroupWrapper;
import org.apache.rocketmq.common.protocol.body.TopicConfigSerializeWrapper;
import org.apache.rocketmq.common.protocol.body.TopicList;
import org.apache.rocketmq.common.protocol.body.UnlockBatchRequestBody;
+import org.apache.rocketmq.common.protocol.header.AckMessageRequestHeader;
+import org.apache.rocketmq.common.protocol.header.ChangeInvisibleTimeRequestHeader;
+import org.apache.rocketmq.common.protocol.header.ChangeInvisibleTimeResponseHeader;
import org.apache.rocketmq.common.protocol.header.CloneGroupOffsetRequestHeader;
import org.apache.rocketmq.common.protocol.header.ConsumeMessageDirectlyResultRequestHeader;
import org.apache.rocketmq.common.protocol.header.ConsumerSendMsgBackRequestHeader;
@@ -95,6 +110,7 @@ import org.apache.rocketmq.common.protocol.header.DeleteAccessConfigRequestHeade
import org.apache.rocketmq.common.protocol.header.DeleteSubscriptionGroupRequestHeader;
import org.apache.rocketmq.common.protocol.header.DeleteTopicRequestHeader;
import org.apache.rocketmq.common.protocol.header.EndTransactionRequestHeader;
+import org.apache.rocketmq.common.protocol.header.ExtraInfoUtil;
import org.apache.rocketmq.common.protocol.header.GetBrokerAclConfigResponseHeader;
import org.apache.rocketmq.common.protocol.header.GetBrokerClusterAclConfigResponseBody;
import org.apache.rocketmq.common.protocol.header.GetConsumeStatsInBrokerHeader;
@@ -113,6 +129,8 @@ import org.apache.rocketmq.common.protocol.header.GetMinOffsetResponseHeader;
import org.apache.rocketmq.common.protocol.header.GetProducerConnectionListRequestHeader;
import org.apache.rocketmq.common.protocol.header.GetTopicStatsInfoRequestHeader;
import org.apache.rocketmq.common.protocol.header.GetTopicsByClusterRequestHeader;
+import org.apache.rocketmq.common.protocol.header.PopMessageRequestHeader;
+import org.apache.rocketmq.common.protocol.header.PopMessageResponseHeader;
import org.apache.rocketmq.common.protocol.header.PullMessageRequestHeader;
import org.apache.rocketmq.common.protocol.header.PullMessageResponseHeader;
import org.apache.rocketmq.common.protocol.header.QueryConsumeQueueRequestHeader;
@@ -144,10 +162,12 @@ import org.apache.rocketmq.common.protocol.header.namesrv.PutKVConfigRequestHead
import org.apache.rocketmq.common.protocol.header.namesrv.WipeWritePermOfBrokerRequestHeader;
import org.apache.rocketmq.common.protocol.header.namesrv.WipeWritePermOfBrokerResponseHeader;
import org.apache.rocketmq.common.protocol.heartbeat.HeartbeatData;
+import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;
import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData;
import org.apache.rocketmq.common.protocol.route.TopicRouteData;
import org.apache.rocketmq.common.subscription.SubscriptionGroupConfig;
import org.apache.rocketmq.logging.InternalLogger;
+import org.apache.rocketmq.remoting.CommandCustomHeader;
import org.apache.rocketmq.remoting.InvokeCallback;
import org.apache.rocketmq.remoting.RPCHook;
import org.apache.rocketmq.remoting.RemotingClient;
@@ -243,6 +263,34 @@ public class MQClientAPIImpl {
this.remotingClient.shutdown();
}
+ public Set queryAssignment(final String addr, final String topic,
+ final String consumerGroup, final String clientId, final String strategyName,
+ final MessageModel messageModel, final long timeoutMillis)
+ throws RemotingException, MQBrokerException, InterruptedException {
+ QueryAssignmentRequestBody requestBody = new QueryAssignmentRequestBody();
+ requestBody.setTopic(topic);
+ requestBody.setConsumerGroup(consumerGroup);
+ requestBody.setClientId(clientId);
+ requestBody.setMessageModel(messageModel);
+ requestBody.setStrategyName(strategyName);
+
+ RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.QUERY_ASSIGNMENT, null);
+ request.setBody(requestBody.encode());
+
+ RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), addr),
+ request, timeoutMillis);
+ switch (response.getCode()) {
+ case ResponseCode.SUCCESS: {
+ QueryAssignmentResponseBody queryAssignmentResponseBody = QueryAssignmentResponseBody.decode(response.getBody(), QueryAssignmentResponseBody.class);
+ return queryAssignmentResponseBody.getMessageQueueAssignments();
+ }
+ default:
+ break;
+ }
+
+ throw new MQBrokerException(response.getCode(), response.getRemark());
+ }
+
public void createSubscriptionGroup(final String addr, final SubscriptionGroupConfig config,
final long timeoutMillis)
throws RemotingException, MQBrokerException, InterruptedException, MQClientException {
@@ -394,7 +442,8 @@ public class MQClientAPIImpl {
}
- public AclConfig getBrokerClusterConfig(final String addr, final long timeoutMillis) throws RemotingCommandException, InterruptedException, RemotingTimeoutException,
+ public AclConfig getBrokerClusterConfig(final String addr,
+ final long timeoutMillis) throws RemotingCommandException, InterruptedException, RemotingTimeoutException,
RemotingSendRequestException, RemotingConnectException, MQBrokerException {
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_BROKER_CLUSTER_ACL_CONFIG, null);
@@ -404,7 +453,7 @@ public class MQClientAPIImpl {
case ResponseCode.SUCCESS: {
if (response.getBody() != null) {
GetBrokerClusterAclConfigResponseBody body =
- GetBrokerClusterAclConfigResponseBody.decode(response.getBody(), GetBrokerClusterAclConfigResponseBody.class);
+ GetBrokerClusterAclConfigResponseBody.decode(response.getBody(), GetBrokerClusterAclConfigResponseBody.class);
AclConfig aclConfig = new AclConfig();
aclConfig.setGlobalWhiteAddrs(body.getGlobalWhiteAddrs());
aclConfig.setPlainAccessConfigs(body.getPlainAccessConfigs());
@@ -502,7 +551,7 @@ public class MQClientAPIImpl {
) throws RemotingException, MQBrokerException, InterruptedException {
RemotingCommand response = this.remotingClient.invokeSync(addr, request, timeoutMillis);
assert response != null;
- return this.processSendResponse(brokerName, msg, response,addr);
+ return this.processSendResponse(brokerName, msg, response, addr);
}
private void sendMessageAsync(
@@ -668,7 +717,7 @@ public class MQClientAPIImpl {
}
SendMessageResponseHeader responseHeader =
- (SendMessageResponseHeader) response.decodeCommandCustomHeader(SendMessageResponseHeader.class);
+ (SendMessageResponseHeader) response.decodeCommandCustomHeader(SendMessageResponseHeader.class);
//If namespace not null , reset Topic without namespace.
String topic = msg.getTopic();
@@ -687,8 +736,8 @@ public class MQClientAPIImpl {
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);
@@ -730,6 +779,123 @@ public class MQClientAPIImpl {
return null;
}
+ public void popMessageAsync(
+ final String brokerName, final String addr, final PopMessageRequestHeader requestHeader,
+ 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) {
+ @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("send request failed to " + addr + ". Request: " + request, responseFuture.getCause()));
+ } else if (responseFuture.isTimeout()) {
+ popCallback.onException(new MQClientException("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 ackMessageAsync(
+ final String addr,
+ final long timeOut,
+ final AckCallback ackCallback,
+ final AckMessageRequestHeader requestHeader //
+ ) throws RemotingException, MQBrokerException, InterruptedException {
+ final RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.ACK_MESSAGE, requestHeader);
+ this.remotingClient.invokeAsync(addr, request, timeOut, new BaseInvokeCallback(MQClientAPIImpl.this) {
+
+ @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);
+ }
+ assert ackResult != null;
+ ackCallback.onSuccess(ackResult);
+ } catch (Exception e) {
+ ackCallback.onException(e);
+ }
+ } else {
+ if (!responseFuture.isSendRequestOK()) {
+ ackCallback.onException(new MQClientException("send request failed to " + addr + ". Request: " + request, responseFuture.getCause()));
+ } else if (responseFuture.isTimeout()) {
+ ackCallback.onException(new MQClientException("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()));
+ }
+ }
+
+ }
+ });
+ }
+
+ public void changeInvisibleTimeAsync(//
+ final String brokerName,
+ final String addr, //
+ final ChangeInvisibleTimeRequestHeader requestHeader,//
+ final long timeoutMillis,
+ 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) {
+ @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);
+ }
+ assert ackResult != null;
+ ackCallback.onSuccess(ackResult);
+ } catch (Exception e) {
+ ackCallback.onException(e);
+ }
+ } else {
+ if (!responseFuture.isSendRequestOK()) {
+ ackCallback.onException(new MQClientException("send request failed to " + addr + ". Request: " + request, responseFuture.getCause()));
+ } else if (responseFuture.isTimeout()) {
+ ackCallback.onException(new MQClientException("wait response from " + addr + " timeout :" + responseFuture.getTimeoutMillis() + "ms" + ". Request: " + request,
+ responseFuture.getCause()));
+ } else {
+ ackCallback.onException(new MQClientException("unknown reason. addr: " + addr + ", timeoutMillis: " + timeoutMillis + ". Request: " + request, responseFuture.getCause()));
+ }
+ }
+ }
+ });
+ }
+
private void pullMessageAsync(
final String addr,
final RemotingCommand request,
@@ -801,6 +967,94 @@ public class MQClientAPIImpl {
responseHeader.getMaxOffset(), null, responseHeader.getSuggestWhichBrokerId(), response.getBody());
}
+ private PopResult processPopResponse(final String brokerName, final RemotingCommand response, String topic,
+ CommandCustomHeader requestHeader) throws MQBrokerException, RemotingCommandException {
+ PopStatus popStatus = PopStatus.NO_NEW_MSG;
+ List msgFoundList = null;
+ switch (response.getCode()) {
+ case ResponseCode.SUCCESS:
+ popStatus = PopStatus.FOUND;
+ ByteBuffer byteBuffer = ByteBuffer.wrap(response.getBody());
+ msgFoundList = MessageDecoder.decodes(byteBuffer);
+ break;
+ case ResponseCode.POLLING_FULL:
+ popStatus = PopStatus.POLLING_FULL;
+ break;
+ case ResponseCode.POLLING_TIMEOUT:
+ popStatus = PopStatus.POLLING_NOT_FOUND;
+ break;
+ case ResponseCode.PULL_NOT_FOUND:
+ popStatus = PopStatus.POLLING_NOT_FOUND;
+ break;
+ default:
+ throw new MQBrokerException(response.getCode(), response.getRemark());
+ }
+
+ PopResult popResult = new PopResult(popStatus, msgFoundList);
+ PopMessageResponseHeader responseHeader = (PopMessageResponseHeader) response.decodeCommandCustomHeader(PopMessageResponseHeader.class);
+ popResult.setRestNum(responseHeader.getRestNum());
+ // it is a pop command if pop time greater than 0, we should set the check point info to extraInfo field
+ if (popStatus == PopStatus.FOUND) {
+ Map startOffsetInfo = null;
+ Map> msgOffsetInfo = null;
+ Map orderCountInfo = null;
+ if (requestHeader instanceof PopMessageRequestHeader) {
+ popResult.setInvisibleTime(responseHeader.getInvisibleTime());
+ popResult.setPopTime(responseHeader.getPopTime());
+ startOffsetInfo = ExtraInfoUtil.parseStartOffsetInfo(responseHeader.getStartOffsetInfo());
+ msgOffsetInfo = ExtraInfoUtil.parseMsgOffsetInfo(responseHeader.getMsgOffsetInfo());
+ orderCountInfo = ExtraInfoUtil.parseOrderCountInfo(responseHeader.getOrderCountInfo());
+ }
+ Map/*msg queueOffset*/> sortMap = new HashMap>(16);
+ for (MessageExt messageExt : msgFoundList) {
+ String key = ExtraInfoUtil.getStartOffsetInfoMapKey(messageExt.getTopic(), messageExt.getQueueId());
+ if (!sortMap.containsKey(key)) {
+ sortMap.put(key, new ArrayList(4));
+ }
+ sortMap.get(key).add(messageExt.getQueueOffset());
+ }
+ Map map = new HashMap(5);
+ for (MessageExt messageExt : msgFoundList) {
+ if (requestHeader instanceof PopMessageRequestHeader) {
+ if (startOffsetInfo == null) {
+ // we should set the check point info to extraInfo field , if the command is popMsg
+ // find pop ck offset
+ String key = messageExt.getTopic() + messageExt.getQueueId();
+ if (!map.containsKey(messageExt.getTopic() + messageExt.getQueueId())) {
+ map.put(key, ExtraInfoUtil.buildExtraInfo(messageExt.getQueueOffset(), responseHeader.getPopTime(), responseHeader.getInvisibleTime(), responseHeader.getReviveQid(),
+ messageExt.getTopic(), brokerName, messageExt.getQueueId()));
+
+ }
+ messageExt.getProperties().put(MessageConst.PROPERTY_POP_CK, map.get(key) + MessageConst.KEY_SEPARATOR + messageExt.getQueueOffset());
+ } else {
+ String key = ExtraInfoUtil.getStartOffsetInfoMapKey(messageExt.getTopic(), messageExt.getQueueId());
+ int index = sortMap.get(key).indexOf(messageExt.getQueueOffset());
+ Long msgQueueOffset = msgOffsetInfo.get(key).get(index);
+ if (msgQueueOffset != messageExt.getQueueOffset()) {
+ log.warn("Queue offset[%d] of msg is strange, not equal to the stored in msg, %s", msgQueueOffset, messageExt);
+ }
+
+ messageExt.getProperties().put(MessageConst.PROPERTY_POP_CK,
+ ExtraInfoUtil.buildExtraInfo(startOffsetInfo.get(key).longValue(), responseHeader.getPopTime(), responseHeader.getInvisibleTime(),
+ responseHeader.getReviveQid(), messageExt.getTopic(), brokerName, messageExt.getQueueId(), msgQueueOffset.longValue())
+ );
+ if (((PopMessageRequestHeader) requestHeader).isOrder() && orderCountInfo != null) {
+ Integer count = orderCountInfo.get(key);
+ if (count != null && count > 0) {
+ messageExt.setReconsumeTimes(count);
+ }
+ }
+ }
+ if (messageExt.getProperties().get(MessageConst.PROPERTY_FIRST_POP_TIME) == null) {
+ messageExt.getProperties().put(MessageConst.PROPERTY_FIRST_POP_TIME, String.valueOf(responseHeader.getPopTime()));
+ }
+ }
+ messageExt.setTopic(NamespaceUtil.withoutNamespace(topic, this.clientConfig.getNamespace()));
+ }
+ }
+ return popResult;
+ }
+
public MessageExt viewMessage(final String addr, final long phyoffset, final long timeoutMillis)
throws RemotingException, MQBrokerException, InterruptedException {
ViewMessageRequestHeader requestHeader = new ViewMessageRequestHeader();
@@ -2263,4 +2517,24 @@ public class MQClientAPIImpl {
return false;
}
}
+
+ public void setMessageRequestMode(final String brokerAddr, final String topic, final String consumerGroup,
+ final MessageRequestMode mode, final int popShareQueueNum, final long timeoutMillis)
+ throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException,
+ RemotingConnectException, MQClientException {
+ RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.SET_MESSAGE_REQUEST_MODE, null);
+
+ SetMessageRequestModeRequestBody requestBody = new SetMessageRequestModeRequestBody();
+ requestBody.setTopic(topic);
+ requestBody.setConsumerGroup(consumerGroup);
+ requestBody.setMode(mode);
+ requestBody.setPopShareQueueNum(popShareQueueNum);
+ request.setBody(requestBody.encode());
+
+ RemotingCommand response = this.remotingClient.invokeSync(MixAll.brokerVIPChannel(this.clientConfig.isVipChannelEnabled(), brokerAddr), request, timeoutMillis);
+ assert response != null;
+ if (ResponseCode.SUCCESS != response.getCode()) {
+ throw new MQClientException(response.getCode(), response.getRemark());
+ }
+ }
}
diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageConcurrentlyService.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageConcurrentlyService.java
index b37f8a635983573c28d42a6708e0ed1ffee91b92..35102b4b28aef5e549bf5daf32765e2a21d64af2 100644
--- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageConcurrentlyService.java
+++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageConcurrentlyService.java
@@ -29,7 +29,6 @@ import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
-
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
@@ -237,6 +236,12 @@ public class ConsumeMessageConcurrentlyService implements ConsumeMessageService
}
}
+ @Override
+ public void submitPopConsumeRequest(final List msgs,
+ final PopProcessQueue processQueue,
+ final MessageQueue messageQueue) {
+ throw new UnsupportedOperationException();
+ }
private void cleanExpireMsg() {
Iterator> it =
@@ -386,6 +391,7 @@ public class ConsumeMessageConcurrentlyService implements ConsumeMessageService
MessageListenerConcurrently listener = ConsumeMessageConcurrentlyService.this.messageListener;
ConsumeConcurrentlyContext context = new ConsumeConcurrentlyContext(messageQueue);
ConsumeConcurrentlyStatus status = null;
+ defaultMQPushConsumerImpl.tryResetPopRetryTopic(msgs, consumerGroup);
defaultMQPushConsumerImpl.resetRetryAndNamespace(msgs, defaultMQPushConsumer.getConsumerGroup());
ConsumeMessageContext consumeMessageContext = null;
diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageOrderlyService.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageOrderlyService.java
index 130effad9e55f08ede275f6870ba9976f11f5f56..ecb3017b5913ec63c3f6b9c7ac793efe897283b2 100644
--- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageOrderlyService.java
+++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageOrderlyService.java
@@ -26,7 +26,6 @@ import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
-
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext;
@@ -39,17 +38,17 @@ import org.apache.rocketmq.client.stat.ConsumerStatsManager;
import org.apache.rocketmq.common.MixAll;
import org.apache.rocketmq.common.ThreadFactoryImpl;
import org.apache.rocketmq.common.UtilAll;
-import org.apache.rocketmq.common.protocol.NamespaceUtil;
-import org.apache.rocketmq.common.utils.ThreadUtils;
-import org.apache.rocketmq.logging.InternalLogger;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageAccessor;
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.common.protocol.NamespaceUtil;
import org.apache.rocketmq.common.protocol.body.CMResult;
import org.apache.rocketmq.common.protocol.body.ConsumeMessageDirectlyResult;
import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;
+import org.apache.rocketmq.common.utils.ThreadUtils;
+import org.apache.rocketmq.logging.InternalLogger;
import org.apache.rocketmq.remoting.common.RemotingHelper;
public class ConsumeMessageOrderlyService implements ConsumeMessageService {
@@ -205,6 +204,13 @@ public class ConsumeMessageOrderlyService implements ConsumeMessageService {
}
}
+ @Override
+ public void submitPopConsumeRequest(final List msgs,
+ final PopProcessQueue processQueue,
+ final MessageQueue messageQueue) {
+ throw new UnsupportedOperationException();
+ }
+
public synchronized void lockMQPeriodically() {
if (!this.stopped) {
this.defaultMQPushConsumerImpl.getRebalanceImpl().lockAll();
diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessagePopConcurrentlyService.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessagePopConcurrentlyService.java
new file mode 100644
index 0000000000000000000000000000000000000000..910f5926ff27e3975d96d80105ce3a7ad3fb49f4
--- /dev/null
+++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessagePopConcurrentlyService.java
@@ -0,0 +1,480 @@
+/*
+ * 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.impl.consumer;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import org.apache.rocketmq.client.consumer.AckCallback;
+import org.apache.rocketmq.client.consumer.AckResult;
+import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
+import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
+import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
+import org.apache.rocketmq.client.consumer.listener.ConsumeReturnType;
+import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
+import org.apache.rocketmq.client.hook.ConsumeMessageContext;
+import org.apache.rocketmq.client.log.ClientLogger;
+import org.apache.rocketmq.client.stat.ConsumerStatsManager;
+import org.apache.rocketmq.common.MixAll;
+import org.apache.rocketmq.common.ThreadFactoryImpl;
+import org.apache.rocketmq.common.message.MessageAccessor;
+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.common.protocol.body.CMResult;
+import org.apache.rocketmq.common.protocol.body.ConsumeMessageDirectlyResult;
+import org.apache.rocketmq.common.protocol.header.ExtraInfoUtil;
+import org.apache.rocketmq.common.utils.ThreadUtils;
+import org.apache.rocketmq.logging.InternalLogger;
+import org.apache.rocketmq.remoting.common.RemotingHelper;
+
+public class ConsumeMessagePopConcurrentlyService implements ConsumeMessageService {
+ private static final InternalLogger log = ClientLogger.getLog();
+ private final DefaultMQPushConsumerImpl defaultMQPushConsumerImpl;
+ private final DefaultMQPushConsumer defaultMQPushConsumer;
+ private final MessageListenerConcurrently messageListener;
+ private final BlockingQueue consumeRequestQueue;
+ private final ThreadPoolExecutor consumeExecutor;
+ private final String consumerGroup;
+
+ private final ScheduledExecutorService scheduledExecutorService;
+
+ public ConsumeMessagePopConcurrentlyService(DefaultMQPushConsumerImpl defaultMQPushConsumerImpl,
+ MessageListenerConcurrently messageListener) {
+ this.defaultMQPushConsumerImpl = defaultMQPushConsumerImpl;
+ this.messageListener = messageListener;
+
+ this.defaultMQPushConsumer = this.defaultMQPushConsumerImpl.getDefaultMQPushConsumer();
+ this.consumerGroup = this.defaultMQPushConsumer.getConsumerGroup();
+ this.consumeRequestQueue = new LinkedBlockingQueue();
+
+ this.consumeExecutor = new ThreadPoolExecutor(
+ this.defaultMQPushConsumer.getConsumeThreadMin(),
+ this.defaultMQPushConsumer.getConsumeThreadMax(),
+ 1000 * 60,
+ TimeUnit.MILLISECONDS,
+ this.consumeRequestQueue,
+ new ThreadFactoryImpl("ConsumeMessageThread_"));
+
+ this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("ConsumeMessageScheduledThread_"));
+ }
+
+ public void start() {
+ }
+
+ public void shutdown(long awaitTerminateMillis) {
+ this.scheduledExecutorService.shutdown();
+ ThreadUtils.shutdownGracefully(this.consumeExecutor, awaitTerminateMillis, TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public void updateCorePoolSize(int corePoolSize) {
+ if (corePoolSize > 0
+ && corePoolSize <= Short.MAX_VALUE
+ && corePoolSize < this.defaultMQPushConsumer.getConsumeThreadMax()) {
+ this.consumeExecutor.setCorePoolSize(corePoolSize);
+ }
+ }
+
+ @Override
+ public void incCorePoolSize() {
+ }
+
+ @Override
+ public void decCorePoolSize() {
+ }
+
+ @Override
+ public int getCorePoolSize() {
+ return this.consumeExecutor.getCorePoolSize();
+ }
+
+
+ @Override
+ public ConsumeMessageDirectlyResult consumeMessageDirectly(MessageExt msg, String brokerName) {
+ ConsumeMessageDirectlyResult result = new ConsumeMessageDirectlyResult();
+ result.setOrder(false);
+ result.setAutoCommit(true);
+
+ List msgs = new ArrayList();
+ msgs.add(msg);
+ MessageQueue mq = new MessageQueue();
+ mq.setBrokerName(brokerName);
+ mq.setTopic(msg.getTopic());
+ mq.setQueueId(msg.getQueueId());
+
+ ConsumeConcurrentlyContext context = new ConsumeConcurrentlyContext(mq);
+
+ this.defaultMQPushConsumerImpl.resetRetryAndNamespace(msgs, this.consumerGroup);
+
+ final long beginTime = System.currentTimeMillis();
+
+ log.info("consumeMessageDirectly receive new message: {}", msg);
+
+ try {
+ ConsumeConcurrentlyStatus status = this.messageListener.consumeMessage(msgs, context);
+ if (status != null) {
+ switch (status) {
+ case CONSUME_SUCCESS:
+ result.setConsumeResult(CMResult.CR_SUCCESS);
+ break;
+ case RECONSUME_LATER:
+ result.setConsumeResult(CMResult.CR_LATER);
+ break;
+ default:
+ break;
+ }
+ } else {
+ result.setConsumeResult(CMResult.CR_RETURN_NULL);
+ }
+ } catch (Throwable e) {
+ result.setConsumeResult(CMResult.CR_THROW_EXCEPTION);
+ result.setRemark(RemotingHelper.exceptionSimpleDesc(e));
+
+ log.warn(String.format("consumeMessageDirectly exception: %s Group: %s Msgs: %s MQ: %s",
+ RemotingHelper.exceptionSimpleDesc(e),
+ ConsumeMessagePopConcurrentlyService.this.consumerGroup,
+ msgs,
+ mq), e);
+ }
+
+ result.setSpentTimeMills(System.currentTimeMillis() - beginTime);
+
+ log.info("consumeMessageDirectly Result: {}", result);
+
+ return result;
+ }
+
+ @Override
+ public void submitConsumeRequest(List msgs, ProcessQueue processQueue,
+ MessageQueue messageQueue, boolean dispathToConsume) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void submitPopConsumeRequest(
+ final List msgs,
+ final PopProcessQueue processQueue,
+ final MessageQueue messageQueue) {
+ final int consumeBatchSize = this.defaultMQPushConsumer.getConsumeMessageBatchMaxSize();
+ if (msgs.size() <= consumeBatchSize) {
+ ConsumeRequest consumeRequest = new ConsumeRequest(msgs, processQueue, messageQueue);
+ try {
+ this.consumeExecutor.submit(consumeRequest);
+ } catch (RejectedExecutionException e) {
+ this.submitConsumeRequestLater(consumeRequest);
+ }
+ } else {
+ for (int total = 0; total < msgs.size(); ) {
+ List msgThis = new ArrayList(consumeBatchSize);
+ for (int i = 0; i < consumeBatchSize; i++, total++) {
+ if (total < msgs.size()) {
+ msgThis.add(msgs.get(total));
+ } else {
+ break;
+ }
+ }
+
+ ConsumeRequest consumeRequest = new ConsumeRequest(msgThis, processQueue, messageQueue);
+ try {
+ this.consumeExecutor.submit(consumeRequest);
+ } catch (RejectedExecutionException e) {
+ for (; total < msgs.size(); total++) {
+ msgThis.add(msgs.get(total));
+ }
+
+ this.submitConsumeRequestLater(consumeRequest);
+ }
+ }
+ }
+ }
+
+ public void processConsumeResult(
+ final ConsumeConcurrentlyStatus status,
+ final ConsumeConcurrentlyContext context,
+ final ConsumeRequest consumeRequest) {
+
+ if (consumeRequest.getMsgs().isEmpty()) {
+ return;
+ }
+
+ int ackIndex = context.getAckIndex();
+ String topic = consumeRequest.getMessageQueue().getTopic();
+
+ switch (status) {
+ case CONSUME_SUCCESS:
+ if (ackIndex >= consumeRequest.getMsgs().size()) {
+ ackIndex = consumeRequest.getMsgs().size() - 1;
+ }
+ int ok = ackIndex + 1;
+ int failed = consumeRequest.getMsgs().size() - ok;
+ this.getConsumerStatsManager().incConsumeOKTPS(consumerGroup, topic, ok);
+ this.getConsumerStatsManager().incConsumeFailedTPS(consumerGroup, topic, failed);
+ break;
+ case RECONSUME_LATER:
+ ackIndex = -1;
+ this.getConsumerStatsManager().incConsumeFailedTPS(consumerGroup, topic,
+ consumeRequest.getMsgs().size());
+ break;
+ default:
+ break;
+ }
+
+ //ack if consume success
+ for (int i = 0; i <= ackIndex; i++) {
+ this.defaultMQPushConsumerImpl.ackAsync(consumeRequest.getMsgs().get(i), consumerGroup);
+ consumeRequest.getPopProcessQueue().ack();
+ }
+
+ //consume later if consume fail
+ for (int i = ackIndex + 1; i < consumeRequest.getMsgs().size(); i++) {
+ MessageExt msgExt = consumeRequest.getMsgs().get(i);
+ consumeRequest.getPopProcessQueue().ack();
+ if (msgExt.getReconsumeTimes() >= this.defaultMQPushConsumerImpl.getMaxReconsumeTimes()) {
+ checkNeedAckOrDelay(msgExt);
+ continue;
+ }
+
+ int delayLevel = context.getDelayLevelWhenNextConsume();
+ changePopInvisibleTime(consumeRequest.getMsgs().get(i), consumerGroup, delayLevel);
+ }
+ }
+
+ private void checkNeedAckOrDelay(MessageExt msgExt) {
+ int[] delayLevelTable = this.defaultMQPushConsumerImpl.getPopDelayLevel();
+
+ long msgDelaytime = System.currentTimeMillis() - msgExt.getBornTimestamp();
+ if (msgDelaytime > delayLevelTable[delayLevelTable.length - 1] * 1000 * 2) {
+ log.warn("Consume too many times, ack message async. message {}", msgExt.toString());
+ this.defaultMQPushConsumerImpl.ackAsync(msgExt, consumerGroup);
+ } else {
+ int delayLevel = delayLevelTable.length - 1;
+ for (; delayLevel >= 0; delayLevel--) {
+ if (msgDelaytime >= delayLevelTable[delayLevel] * 1000) {
+ delayLevel++;
+ break;
+ }
+ }
+
+ changePopInvisibleTime(msgExt, consumerGroup, delayLevel);
+ log.warn("Consume too many times, but delay time {} not enough. changePopInvisibleTime to delayLevel {} . message key:{}",
+ msgDelaytime, delayLevel, msgExt.getKeys());
+ }
+ }
+
+ private void changePopInvisibleTime(final MessageExt msg, String consumerGroup, int delayLevel) {
+ if (0 == delayLevel) {
+ delayLevel = 3 + msg.getReconsumeTimes();
+ }
+
+ int[] delayLevelTable = this.defaultMQPushConsumerImpl.getPopDelayLevel();
+ int delaySecond = delayLevel >= delayLevelTable.length ? delayLevelTable[delayLevelTable.length - 1] : delayLevelTable[delayLevel];
+ String extraInfo = msg.getProperty(MessageConst.PROPERTY_POP_CK);
+
+ try {
+ this.defaultMQPushConsumerImpl.changePopInvisibleTimeAsync(msg.getTopic(), consumerGroup, extraInfo,
+ delaySecond * 1000L, new AckCallback() {
+ @Override
+ public void onSuccess(AckResult ackResult) {
+ }
+
+
+ @Override
+ public void onException(Throwable e) {
+ log.error("changePopInvisibleTimeAsync fail. msg:{} error info: {}", msg.toString(), e.toString());
+ }
+ });
+ } catch (Throwable t) {
+ log.error("changePopInvisibleTimeAsync fail, group:{} msg:{} errorInfo:{}", consumerGroup, msg.toString(), t.toString());
+ }
+ }
+
+ public ConsumerStatsManager getConsumerStatsManager() {
+ return this.defaultMQPushConsumerImpl.getConsumerStatsManager();
+ }
+
+ private void submitConsumeRequestLater(
+ final List msgs,
+ final PopProcessQueue processQueue,
+ final MessageQueue messageQueue
+ ) {
+
+ this.scheduledExecutorService.schedule(new Runnable() {
+
+ @Override
+ public void run() {
+ ConsumeMessagePopConcurrentlyService.this.submitPopConsumeRequest(msgs, processQueue, messageQueue);
+ }
+ }, 5000, TimeUnit.MILLISECONDS);
+ }
+
+ private void submitConsumeRequestLater(final ConsumeRequest consumeRequest
+ ) {
+
+ this.scheduledExecutorService.schedule(new Runnable() {
+
+ @Override
+ public void run() {
+ ConsumeMessagePopConcurrentlyService.this.consumeExecutor.submit(consumeRequest);
+ }
+ }, 5000, TimeUnit.MILLISECONDS);
+ }
+
+ class ConsumeRequest implements Runnable {
+ private final List msgs;
+ private final PopProcessQueue processQueue;
+ private final MessageQueue messageQueue;
+ private long popTime = 0;
+ private long invisibleTime = 0;
+
+ public ConsumeRequest(List msgs, PopProcessQueue processQueue, MessageQueue messageQueue) {
+ this.msgs = msgs;
+ this.processQueue = processQueue;
+ this.messageQueue = messageQueue;
+
+ try {
+ String extraInfo = msgs.get(0).getProperty(MessageConst.PROPERTY_POP_CK);
+ String[] extraInfoStrs = ExtraInfoUtil.split(extraInfo);
+ popTime = ExtraInfoUtil.getPopTime(extraInfoStrs);
+ invisibleTime = ExtraInfoUtil.getInvisibleTime(extraInfoStrs);
+ } catch (Throwable t) {
+ log.error("parse extra info error. msg:" + msgs.get(0), t);
+ }
+ }
+
+ public boolean isPopTimeout() {
+ if (msgs.size() == 0 || popTime <= 0 || invisibleTime <= 0) {
+ return true;
+ }
+
+ long current = System.currentTimeMillis();
+ return current - popTime >= invisibleTime;
+ }
+
+ public List getMsgs() {
+ return msgs;
+ }
+
+ public PopProcessQueue getPopProcessQueue() {
+ return processQueue;
+ }
+
+ @Override
+ public void run() {
+ if (this.processQueue.isDropped()) {
+ log.info("the message queue not be able to consume, because it's dropped(pop). group={} {}", ConsumeMessagePopConcurrentlyService.this.consumerGroup, this.messageQueue);
+ return;
+ }
+
+ if (isPopTimeout()) {
+ log.info("the pop message time out so abort consume. popTime={} invisibleTime={}, group={} {}",
+ popTime, invisibleTime, ConsumeMessagePopConcurrentlyService.this.consumerGroup, this.messageQueue);
+ processQueue.decFoundMsg(-msgs.size());
+ return;
+ }
+
+ MessageListenerConcurrently listener = ConsumeMessagePopConcurrentlyService.this.messageListener;
+ ConsumeConcurrentlyContext context = new ConsumeConcurrentlyContext(messageQueue);
+ ConsumeConcurrentlyStatus status = null;
+ defaultMQPushConsumerImpl.resetRetryAndNamespace(msgs, defaultMQPushConsumer.getConsumerGroup());
+
+ ConsumeMessageContext consumeMessageContext = null;
+ if (ConsumeMessagePopConcurrentlyService.this.defaultMQPushConsumerImpl.hasHook()) {
+ consumeMessageContext = new ConsumeMessageContext();
+ consumeMessageContext.setNamespace(defaultMQPushConsumer.getNamespace());
+ consumeMessageContext.setConsumerGroup(defaultMQPushConsumer.getConsumerGroup());
+ consumeMessageContext.setProps(new HashMap());
+ consumeMessageContext.setMq(messageQueue);
+ consumeMessageContext.setMsgList(msgs);
+ consumeMessageContext.setSuccess(false);
+ ConsumeMessagePopConcurrentlyService.this.defaultMQPushConsumerImpl.executeHookBefore(consumeMessageContext);
+ }
+
+ long beginTimestamp = System.currentTimeMillis();
+ boolean hasException = false;
+ ConsumeReturnType returnType = ConsumeReturnType.SUCCESS;
+ try {
+ if (msgs != null && !msgs.isEmpty()) {
+ for (MessageExt msg : msgs) {
+ MessageAccessor.setConsumeStartTimeStamp(msg, String.valueOf(System.currentTimeMillis()));
+ }
+ }
+ status = listener.consumeMessage(Collections.unmodifiableList(msgs), context);
+ } catch (Throwable e) {
+ log.warn("consumeMessage exception: {} Group: {} Msgs: {} MQ: {}",
+ RemotingHelper.exceptionSimpleDesc(e),
+ ConsumeMessagePopConcurrentlyService.this.consumerGroup,
+ msgs,
+ messageQueue);
+ hasException = true;
+ }
+ long consumeRT = System.currentTimeMillis() - beginTimestamp;
+ if (null == status) {
+ if (hasException) {
+ returnType = ConsumeReturnType.EXCEPTION;
+ } else {
+ returnType = ConsumeReturnType.RETURNNULL;
+ }
+ } else if (consumeRT >= invisibleTime * 1000) {
+ returnType = ConsumeReturnType.TIME_OUT;
+ } else if (ConsumeConcurrentlyStatus.RECONSUME_LATER == status) {
+ returnType = ConsumeReturnType.FAILED;
+ } else if (ConsumeConcurrentlyStatus.CONSUME_SUCCESS == status) {
+ returnType = ConsumeReturnType.SUCCESS;
+ }
+
+ if (null == status) {
+ log.warn("consumeMessage return null, Group: {} Msgs: {} MQ: {}",
+ ConsumeMessagePopConcurrentlyService.this.consumerGroup,
+ msgs,
+ messageQueue);
+ status = ConsumeConcurrentlyStatus.RECONSUME_LATER;
+ }
+
+ if (ConsumeMessagePopConcurrentlyService.this.defaultMQPushConsumerImpl.hasHook()) {
+ consumeMessageContext.getProps().put(MixAll.CONSUME_CONTEXT_TYPE, returnType.name());
+ }
+
+ ConsumeMessagePopConcurrentlyService.this.getConsumerStatsManager()
+ .incConsumeRT(ConsumeMessagePopConcurrentlyService.this.consumerGroup, messageQueue.getTopic(), consumeRT);
+
+ if (!processQueue.isDropped() && !isPopTimeout()) {
+ ConsumeMessagePopConcurrentlyService.this.processConsumeResult(status, context, this);
+ } else {
+ if (msgs != null) {
+ processQueue.decFoundMsg(-msgs.size());
+ }
+
+ log.warn("processQueue invalid. isDropped={}, isPopTimeout={}, messageQueue={}, msgs={}",
+ processQueue.isDropped(), isPopTimeout(), messageQueue, msgs);
+ }
+ }
+
+ public MessageQueue getMessageQueue() {
+ return messageQueue;
+ }
+
+ }
+}
diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessagePopOrderlyService.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessagePopOrderlyService.java
new file mode 100644
index 0000000000000000000000000000000000000000..48e033674c60a083301b838b7a03371788348088
--- /dev/null
+++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessagePopOrderlyService.java
@@ -0,0 +1,408 @@
+/*
+ * 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.impl.consumer;
+
+import io.netty.util.internal.ConcurrentSet;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+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.commons.lang3.StringUtils;
+import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
+import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext;
+import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus;
+import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly;
+import org.apache.rocketmq.client.log.ClientLogger;
+import org.apache.rocketmq.client.stat.ConsumerStatsManager;
+import org.apache.rocketmq.common.MixAll;
+import org.apache.rocketmq.common.ThreadFactoryImpl;
+import org.apache.rocketmq.common.UtilAll;
+import org.apache.rocketmq.common.message.Message;
+import org.apache.rocketmq.common.message.MessageAccessor;
+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.common.protocol.NamespaceUtil;
+import org.apache.rocketmq.common.protocol.body.CMResult;
+import org.apache.rocketmq.common.protocol.body.ConsumeMessageDirectlyResult;
+import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;
+import org.apache.rocketmq.common.utils.ThreadUtils;
+import org.apache.rocketmq.logging.InternalLogger;
+import org.apache.rocketmq.remoting.common.RemotingHelper;
+
+public class ConsumeMessagePopOrderlyService implements ConsumeMessageService {
+ private static final InternalLogger log = ClientLogger.getLog();
+ private final DefaultMQPushConsumerImpl defaultMQPushConsumerImpl;
+ private final DefaultMQPushConsumer defaultMQPushConsumer;
+ private final MessageListenerOrderly messageListener;
+ private final BlockingQueue consumeRequestQueue;
+ private final ConcurrentSet consumeRequestSet = new ConcurrentSet();
+ private final ThreadPoolExecutor consumeExecutor;
+ private final String consumerGroup;
+ private final MessageQueueLock messageQueueLock = new MessageQueueLock();
+ private final MessageQueueLock consumeRequestLock = new MessageQueueLock();
+ private final ScheduledExecutorService scheduledExecutorService;
+ private volatile boolean stopped = false;
+
+ public ConsumeMessagePopOrderlyService(DefaultMQPushConsumerImpl defaultMQPushConsumerImpl,
+ MessageListenerOrderly messageListener) {
+ this.defaultMQPushConsumerImpl = defaultMQPushConsumerImpl;
+ this.messageListener = messageListener;
+
+ this.defaultMQPushConsumer = this.defaultMQPushConsumerImpl.getDefaultMQPushConsumer();
+ this.consumerGroup = this.defaultMQPushConsumer.getConsumerGroup();
+ this.consumeRequestQueue = new LinkedBlockingQueue();
+
+ this.consumeExecutor = new ThreadPoolExecutor(
+ this.defaultMQPushConsumer.getConsumeThreadMin(),
+ this.defaultMQPushConsumer.getConsumeThreadMax(),
+ 1000 * 60,
+ TimeUnit.MILLISECONDS,
+ this.consumeRequestQueue,
+ new ThreadFactoryImpl("ConsumeMessageThread_"));
+
+ this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("ConsumeMessageScheduledThread_"));
+ }
+
+ @Override
+ public void start() {
+ if (MessageModel.CLUSTERING.equals(ConsumeMessagePopOrderlyService.this.defaultMQPushConsumerImpl.messageModel())) {
+ this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
+ @Override
+ public void run() {
+ ConsumeMessagePopOrderlyService.this.lockMQPeriodically();
+ }
+ }, 1000 * 1, ProcessQueue.REBALANCE_LOCK_INTERVAL, TimeUnit.MILLISECONDS);
+ }
+ }
+
+ @Override
+ public void shutdown(long awaitTerminateMillis) {
+ this.stopped = true;
+ this.scheduledExecutorService.shutdown();
+ ThreadUtils.shutdownGracefully(this.consumeExecutor, awaitTerminateMillis, TimeUnit.MILLISECONDS);
+ if (MessageModel.CLUSTERING.equals(this.defaultMQPushConsumerImpl.messageModel())) {
+ this.unlockAllMessageQueues();
+ }
+ }
+
+ public synchronized void unlockAllMessageQueues() {
+ this.defaultMQPushConsumerImpl.getRebalanceImpl().unlockAll(false);
+ }
+
+ @Override
+ public void updateCorePoolSize(int corePoolSize) {
+ if (corePoolSize > 0
+ && corePoolSize <= Short.MAX_VALUE
+ && corePoolSize < this.defaultMQPushConsumer.getConsumeThreadMax()) {
+ this.consumeExecutor.setCorePoolSize(corePoolSize);
+ }
+ }
+
+ @Override
+ public void incCorePoolSize() {
+ }
+
+ @Override
+ public void decCorePoolSize() {
+ }
+
+ @Override
+ public int getCorePoolSize() {
+ return this.consumeExecutor.getCorePoolSize();
+ }
+
+ @Override
+ public ConsumeMessageDirectlyResult consumeMessageDirectly(MessageExt msg, String brokerName) {
+ ConsumeMessageDirectlyResult result = new ConsumeMessageDirectlyResult();
+ result.setOrder(true);
+
+ List msgs = new ArrayList();
+ msgs.add(msg);
+ MessageQueue mq = new MessageQueue();
+ mq.setBrokerName(brokerName);
+ mq.setTopic(msg.getTopic());
+ mq.setQueueId(msg.getQueueId());
+
+ ConsumeOrderlyContext context = new ConsumeOrderlyContext(mq);
+
+ this.defaultMQPushConsumerImpl.resetRetryAndNamespace(msgs, this.consumerGroup);
+
+ final long beginTime = System.currentTimeMillis();
+
+ log.info("consumeMessageDirectly receive new message: {}", msg);
+
+ try {
+ ConsumeOrderlyStatus status = this.messageListener.consumeMessage(msgs, context);
+ if (status != null) {
+ switch (status) {
+ case COMMIT:
+ result.setConsumeResult(CMResult.CR_COMMIT);
+ break;
+ case ROLLBACK:
+ result.setConsumeResult(CMResult.CR_ROLLBACK);
+ break;
+ case SUCCESS:
+ result.setConsumeResult(CMResult.CR_SUCCESS);
+ break;
+ case SUSPEND_CURRENT_QUEUE_A_MOMENT:
+ result.setConsumeResult(CMResult.CR_LATER);
+ break;
+ default:
+ break;
+ }
+ } else {
+ result.setConsumeResult(CMResult.CR_RETURN_NULL);
+ }
+ } catch (Throwable e) {
+ result.setConsumeResult(CMResult.CR_THROW_EXCEPTION);
+ result.setRemark(RemotingHelper.exceptionSimpleDesc(e));
+
+ log.warn(String.format("consumeMessageDirectly exception: %s Group: %s Msgs: %s MQ: %s",
+ RemotingHelper.exceptionSimpleDesc(e),
+ ConsumeMessagePopOrderlyService.this.consumerGroup,
+ msgs,
+ mq), e);
+ }
+
+ result.setAutoCommit(context.isAutoCommit());
+ result.setSpentTimeMills(System.currentTimeMillis() - beginTime);
+
+ log.info("consumeMessageDirectly Result: {}", result);
+
+ return result;
+ }
+
+ @Override
+ public void submitConsumeRequest(List msgs, ProcessQueue processQueue,
+ MessageQueue messageQueue, boolean dispathToConsume) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void submitPopConsumeRequest(final List msgs,
+ final PopProcessQueue processQueue,
+ final MessageQueue messageQueue) {
+ ConsumeRequest req = new ConsumeRequest(processQueue, messageQueue);
+ submitConsumeRequest(req, false);
+ }
+
+ public synchronized void lockMQPeriodically() {
+ if (!this.stopped) {
+ this.defaultMQPushConsumerImpl.getRebalanceImpl().lockAll();
+ }
+ }
+
+ private void removeConsumeRequest(final ConsumeRequest consumeRequest) {
+ consumeRequestSet.remove(consumeRequest);
+ }
+
+ private void submitConsumeRequest(final ConsumeRequest consumeRequest, boolean force) {
+ Object lock = consumeRequestLock.fetchLockObject(consumeRequest.getMessageQueue(), consumeRequest.shardingKeyIndex);
+ synchronized (lock) {
+ boolean isNewReq = consumeRequestSet.add(consumeRequest);
+ if (force || isNewReq) {
+ try {
+ consumeExecutor.submit(consumeRequest);
+ } catch (Exception e) {
+ log.error("error submit consume request: {}, mq: {}, shardingKeyIndex: {}",
+ e.toString(), consumeRequest.getMessageQueue(), consumeRequest.getShardingKeyIndex());
+ }
+ }
+ }
+ }
+
+ private void submitConsumeRequestLater(final ConsumeRequest consumeRequest, final long suspendTimeMillis) {
+ long timeMillis = suspendTimeMillis;
+ if (timeMillis == -1) {
+ timeMillis = this.defaultMQPushConsumer.getSuspendCurrentQueueTimeMillis();
+ }
+
+ if (timeMillis < 10) {
+ timeMillis = 10;
+ } else if (timeMillis > 30000) {
+ timeMillis = 30000;
+ }
+
+ this.scheduledExecutorService.schedule(new Runnable() {
+
+ @Override
+ public void run() {
+ submitConsumeRequest(consumeRequest, true);
+ }
+ }, timeMillis, TimeUnit.MILLISECONDS);
+ }
+
+ public boolean processConsumeResult(
+ final List msgs,
+ final ConsumeOrderlyStatus status,
+ final ConsumeOrderlyContext context,
+ final ConsumeRequest consumeRequest
+ ) {
+ return true;
+ }
+
+ public ConsumerStatsManager getConsumerStatsManager() {
+ return this.defaultMQPushConsumerImpl.getConsumerStatsManager();
+ }
+
+ private int getMaxReconsumeTimes() {
+ // default reconsume times: Integer.MAX_VALUE
+ if (this.defaultMQPushConsumer.getMaxReconsumeTimes() == -1) {
+ return Integer.MAX_VALUE;
+ } else {
+ return this.defaultMQPushConsumer.getMaxReconsumeTimes();
+ }
+ }
+
+ private boolean checkReconsumeTimes(List msgs) {
+ boolean suspend = false;
+ if (msgs != null && !msgs.isEmpty()) {
+ for (MessageExt msg : msgs) {
+ if (msg.getReconsumeTimes() >= getMaxReconsumeTimes()) {
+ MessageAccessor.setReconsumeTime(msg, String.valueOf(msg.getReconsumeTimes()));
+ if (!sendMessageBack(msg)) {
+ suspend = true;
+ msg.setReconsumeTimes(msg.getReconsumeTimes() + 1);
+ }
+ } else {
+ suspend = true;
+ msg.setReconsumeTimes(msg.getReconsumeTimes() + 1);
+ }
+ }
+ }
+ return suspend;
+ }
+
+ public boolean sendMessageBack(final MessageExt msg) {
+ try {
+ // max reconsume times exceeded then send to dead letter queue.
+ Message newMsg = new Message(MixAll.getRetryTopic(this.defaultMQPushConsumer.getConsumerGroup()), msg.getBody());
+ String originMsgId = MessageAccessor.getOriginMessageId(msg);
+ MessageAccessor.setOriginMessageId(newMsg, UtilAll.isBlank(originMsgId) ? msg.getMsgId() : originMsgId);
+ newMsg.setFlag(msg.getFlag());
+ MessageAccessor.setProperties(newMsg, msg.getProperties());
+ MessageAccessor.putProperty(newMsg, MessageConst.PROPERTY_RETRY_TOPIC, msg.getTopic());
+ MessageAccessor.setReconsumeTime(newMsg, String.valueOf(msg.getReconsumeTimes()));
+ MessageAccessor.setMaxReconsumeTimes(newMsg, String.valueOf(getMaxReconsumeTimes()));
+ newMsg.setDelayTimeLevel(3 + msg.getReconsumeTimes());
+
+ this.defaultMQPushConsumer.getDefaultMQPushConsumerImpl().getmQClientFactory().getDefaultMQProducer().send(newMsg);
+ return true;
+ } catch (Exception e) {
+ log.error("sendMessageBack exception, group: " + this.consumerGroup + " msg: " + msg.toString(), e);
+ }
+
+ return false;
+ }
+
+ public void resetNamespace(final List msgs) {
+ for (MessageExt msg : msgs) {
+ if (StringUtils.isNotEmpty(this.defaultMQPushConsumer.getNamespace())) {
+ msg.setTopic(NamespaceUtil.withoutNamespace(msg.getTopic(), this.defaultMQPushConsumer.getNamespace()));
+ }
+ }
+ }
+
+ class ConsumeRequest implements Runnable {
+ private final PopProcessQueue processQueue;
+ private final MessageQueue messageQueue;
+ private int shardingKeyIndex = 0;
+
+ public ConsumeRequest(PopProcessQueue processQueue, MessageQueue messageQueue) {
+ this.processQueue = processQueue;
+ this.messageQueue = messageQueue;
+ this.shardingKeyIndex = 0;
+ }
+
+ public ConsumeRequest(PopProcessQueue processQueue, MessageQueue messageQueue, int shardingKeyIndex) {
+ this.processQueue = processQueue;
+ this.messageQueue = messageQueue;
+ this.shardingKeyIndex = shardingKeyIndex;
+ }
+
+ public PopProcessQueue getProcessQueue() {
+ return processQueue;
+ }
+
+ public MessageQueue getMessageQueue() {
+ return messageQueue;
+ }
+
+ public int getShardingKeyIndex() {
+ return shardingKeyIndex;
+ }
+
+ @Override
+ public void run() {
+ if (this.processQueue.isDropped()) {
+ log.warn("run, message queue not be able to consume, because it's dropped. {}", this.messageQueue);
+ ConsumeMessagePopOrderlyService.this.removeConsumeRequest(this);
+ return;
+ }
+
+ // lock on sharding key index
+ final Object objLock = messageQueueLock.fetchLockObject(this.messageQueue, shardingKeyIndex);
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = shardingKeyIndex;
+ if (processQueue != null) {
+ hash += processQueue.hashCode() * 31;
+ }
+ if (messageQueue != null) {
+ hash += messageQueue.hashCode() * 31;
+ }
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+
+ ConsumeRequest other = (ConsumeRequest) obj;
+ if (shardingKeyIndex != other.shardingKeyIndex) {
+ return false;
+ }
+
+ if (processQueue != other.processQueue) {
+ return false;
+ }
+
+ if (messageQueue == other.messageQueue) {
+ return true;
+ }
+ if (messageQueue != null && messageQueue.equals(other.messageQueue)) {
+ return true;
+ }
+ return false;
+ }
+ }
+}
diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageService.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageService.java
index 5078c97883502f198d1d2f395b42b0f4ee6262bb..bdde6ff6e90c59ecd2a9fa67883b77002a97a1c7 100644
--- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageService.java
+++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageService.java
@@ -41,4 +41,9 @@ public interface ConsumeMessageService {
final ProcessQueue processQueue,
final MessageQueue messageQueue,
final boolean dispathToConsume);
+
+ void submitPopConsumeRequest(
+ final List msgs,
+ final PopProcessQueue processQueue,
+ final MessageQueue messageQueue);
}
diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultLitePullConsumerImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultLitePullConsumerImpl.java
index 4e139c44ce0c25d0eee8b01bc2f87804a036c8b9..2e73f1a5170278729b034f5972f1f6a568a93836 100644
--- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultLitePullConsumerImpl.java
+++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultLitePullConsumerImpl.java
@@ -149,6 +149,9 @@ public class DefaultLitePullConsumerImpl implements MQConsumerInner {
private final ArrayList consumeMessageHookList = new ArrayList<>();
+ // only for test purpose, will be modified by reflection in unit test.
+ @SuppressWarnings("FieldMayBeFinal") private static boolean doNotUpdateTopicSubscribeInfoWhenSubscriptionChanged = false;
+
public DefaultLitePullConsumerImpl(final DefaultLitePullConsumer defaultLitePullConsumer, final RPCHook rpcHook) {
this.defaultLitePullConsumer = defaultLitePullConsumer;
this.rpcHook = rpcHook;
@@ -453,6 +456,9 @@ public class DefaultLitePullConsumerImpl implements MQConsumerInner {
}
private void updateTopicSubscribeInfoWhenSubscriptionChanged() {
+ if (doNotUpdateTopicSubscribeInfoWhenSubscriptionChanged) {
+ return;
+ }
Map subTable = rebalanceImpl.getSubscriptionInner();
if (subTable != null) {
for (final Map.Entry entry : subTable.entrySet()) {
@@ -797,7 +803,7 @@ public class DefaultLitePullConsumerImpl implements MQConsumerInner {
String topic = this.messageQueue.getTopic();
subscriptionData = FilterAPI.buildSubscriptionData(topic, SubscriptionData.SUB_ALL);
}
-
+
PullResult pullResult = pull(messageQueue, subscriptionData, offset, defaultLitePullConsumer.getPullBatchSize());
if (this.isCancelled() || processQueue.isDropped()) {
return;
diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPushConsumerImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPushConsumerImpl.java
index bb0b7f10436ad7ef0844bad9ec09849c72b5940c..b478cb10932287da8ff8a8b001f5257979c51ca4 100644
--- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPushConsumerImpl.java
+++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPushConsumerImpl.java
@@ -27,12 +27,17 @@ import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
-
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.client.QueryResult;
import org.apache.rocketmq.client.Validators;
+import org.apache.rocketmq.client.consumer.AckCallback;
+import org.apache.rocketmq.client.consumer.AckResult;
+import org.apache.rocketmq.client.consumer.AckStatus;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.MessageSelector;
+import org.apache.rocketmq.client.consumer.PopCallback;
+import org.apache.rocketmq.client.consumer.PopResult;
+import org.apache.rocketmq.client.consumer.PopStatus;
import org.apache.rocketmq.client.consumer.PullCallback;
import org.apache.rocketmq.client.consumer.PullResult;
import org.apache.rocketmq.client.consumer.listener.MessageListener;
@@ -46,39 +51,47 @@ import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.hook.ConsumeMessageContext;
import org.apache.rocketmq.client.hook.ConsumeMessageHook;
+import org.apache.rocketmq.client.hook.FilterMessageContext;
import org.apache.rocketmq.client.hook.FilterMessageHook;
import org.apache.rocketmq.client.impl.CommunicationMode;
+import org.apache.rocketmq.client.impl.FindBrokerResult;
import org.apache.rocketmq.client.impl.MQClientManager;
import org.apache.rocketmq.client.impl.factory.MQClientInstance;
import org.apache.rocketmq.client.log.ClientLogger;
import org.apache.rocketmq.client.stat.ConsumerStatsManager;
+import org.apache.rocketmq.common.KeyBuilder;
import org.apache.rocketmq.common.MixAll;
import org.apache.rocketmq.common.ServiceState;
import org.apache.rocketmq.common.UtilAll;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.filter.FilterAPI;
import org.apache.rocketmq.common.help.FAQUrl;
-import org.apache.rocketmq.common.protocol.NamespaceUtil;
-import org.apache.rocketmq.logging.InternalLogger;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageAccessor;
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.common.protocol.NamespaceUtil;
import org.apache.rocketmq.common.protocol.body.ConsumeStatus;
import org.apache.rocketmq.common.protocol.body.ConsumerRunningInfo;
+import org.apache.rocketmq.common.protocol.body.PopProcessQueueInfo;
import org.apache.rocketmq.common.protocol.body.ProcessQueueInfo;
import org.apache.rocketmq.common.protocol.body.QueueTimeSpan;
+import org.apache.rocketmq.common.protocol.header.AckMessageRequestHeader;
+import org.apache.rocketmq.common.protocol.header.ChangeInvisibleTimeRequestHeader;
+import org.apache.rocketmq.common.protocol.header.ExtraInfoUtil;
import org.apache.rocketmq.common.protocol.heartbeat.ConsumeType;
import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;
import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData;
import org.apache.rocketmq.common.protocol.route.BrokerData;
import org.apache.rocketmq.common.protocol.route.TopicRouteData;
import org.apache.rocketmq.common.sysflag.PullSysFlag;
+import org.apache.rocketmq.logging.InternalLogger;
import org.apache.rocketmq.remoting.RPCHook;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.apache.rocketmq.remoting.exception.RemotingException;
+
public class DefaultMQPushConsumerImpl implements MQConsumerInner {
/**
* Delay some time when exception occur
@@ -109,9 +122,20 @@ public class DefaultMQPushConsumerImpl implements MQConsumerInner {
private MessageListener messageListenerInner;
private OffsetStore offsetStore;
private ConsumeMessageService consumeMessageService;
+ private ConsumeMessageService consumeMessagePopService;
private long queueFlowControlTimes = 0;
private long queueMaxSpanFlowControlTimes = 0;
+ //10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
+ private int[] popDelayLevel = new int[] {10, 30, 60, 120, 180, 240, 300, 360, 420, 480, 540, 600, 1200, 1800, 3600, 7200};
+
+ private static final int MAX_POP_INVISIBLE_TIME = 300000;
+ private static final int MIN_POP_INVISIBLE_TIME = 5000;
+ private static final int ASYNC_TIMEOUT = 3000;
+
+ // only for test purpose, will be modified by reflection in unit test.
+ @SuppressWarnings("FieldMayBeFinal") private static boolean doNotUpdateTopicSubscribeInfoWhenSubscriptionChanged = false;
+
public DefaultMQPushConsumerImpl(DefaultMQPushConsumer defaultMQPushConsumer, RPCHook rpcHook) {
this.defaultMQPushConsumer = defaultMQPushConsumer;
this.rpcHook = rpcHook;
@@ -450,6 +474,169 @@ public class DefaultMQPushConsumerImpl implements MQConsumerInner {
}
}
+ void popMessage(final PopRequest popRequest) {
+ final PopProcessQueue processQueue = popRequest.getPopProcessQueue();
+ if (processQueue.isDropped()) {
+ log.info("the pop request[{}] is dropped.", popRequest.toString());
+ return;
+ }
+
+ processQueue.setLastPopTimestamp(System.currentTimeMillis());
+
+ try {
+ this.makeSureStateOK();
+ } catch (MQClientException e) {
+ log.warn("pullMessage exception, consumer state not ok", e);
+ this.executePopPullRequestLater(popRequest, pullTimeDelayMillsWhenException);
+ return;
+ }
+
+ if (this.isPause()) {
+ log.warn("consumer was paused, execute pull request later. instanceName={}, group={}", this.defaultMQPushConsumer.getInstanceName(), this.defaultMQPushConsumer.getConsumerGroup());
+ this.executePopPullRequestLater(popRequest, PULL_TIME_DELAY_MILLS_WHEN_SUSPEND);
+ return;
+ }
+
+ if (processQueue.getWaiAckMsgCount() > this.defaultMQPushConsumer.getPopThresholdForQueue()) {
+ this.executePopPullRequestLater(popRequest, PULL_TIME_DELAY_MILLS_WHEN_FLOW_CONTROL);
+ if ((queueFlowControlTimes++ % 1000) == 0) {
+ log.warn("the messages waiting to ack exceeds the threshold {}, so do flow control, popRequest={}, flowControlTimes={}, wait count={}",
+ this.defaultMQPushConsumer.getPopThresholdForQueue(), popRequest, queueFlowControlTimes, processQueue.getWaiAckMsgCount());
+ }
+ return;
+ }
+
+ //POPTODO think of pop mode orderly implementation later.
+ final SubscriptionData subscriptionData = this.rebalanceImpl.getSubscriptionInner().get(popRequest.getMessageQueue().getTopic());
+ if (null == subscriptionData) {
+ this.executePopPullRequestLater(popRequest, pullTimeDelayMillsWhenException);
+ log.warn("find the consumer's subscription failed, {}", popRequest);
+ return;
+ }
+
+ final long beginTimestamp = System.currentTimeMillis();
+
+ PopCallback popCallback = new PopCallback() {
+ @Override
+ public void onSuccess(PopResult popResult) {
+ if (popResult == null) {
+ log.error("pop callback popResult is null");
+ DefaultMQPushConsumerImpl.this.executePopPullRequestImmediately(popRequest);
+ return;
+ }
+
+ processPopResult(popResult, subscriptionData);
+
+ switch (popResult.getPopStatus()) {
+ case FOUND:
+ long pullRT = System.currentTimeMillis() - beginTimestamp;
+ DefaultMQPushConsumerImpl.this.getConsumerStatsManager().incPullRT(popRequest.getConsumerGroup(),
+ popRequest.getMessageQueue().getTopic(), pullRT);
+ if (popResult.getMsgFoundList() == null || popResult.getMsgFoundList().isEmpty()) {
+ DefaultMQPushConsumerImpl.this.executePopPullRequestImmediately(popRequest);
+ } else {
+ DefaultMQPushConsumerImpl.this.getConsumerStatsManager().incPullTPS(popRequest.getConsumerGroup(),
+ popRequest.getMessageQueue().getTopic(), popResult.getMsgFoundList().size());
+ popRequest.getPopProcessQueue().incFoundMsg(popResult.getMsgFoundList().size());
+
+ DefaultMQPushConsumerImpl.this.consumeMessagePopService.submitPopConsumeRequest(
+ popResult.getMsgFoundList(),
+ processQueue,
+ popRequest.getMessageQueue());
+
+ if (DefaultMQPushConsumerImpl.this.defaultMQPushConsumer.getPullInterval() > 0) {
+ DefaultMQPushConsumerImpl.this.executePopPullRequestLater(popRequest,
+ DefaultMQPushConsumerImpl.this.defaultMQPushConsumer.getPullInterval());
+ } else {
+ DefaultMQPushConsumerImpl.this.executePopPullRequestImmediately(popRequest);
+ }
+ }
+ break;
+ case NO_NEW_MSG:
+ case POLLING_NOT_FOUND:
+ DefaultMQPushConsumerImpl.this.executePopPullRequestImmediately(popRequest);
+ break;
+ case POLLING_FULL:
+ DefaultMQPushConsumerImpl.this.executePopPullRequestLater(popRequest, pullTimeDelayMillsWhenException);
+ break;
+ default:
+ DefaultMQPushConsumerImpl.this.executePopPullRequestLater(popRequest, pullTimeDelayMillsWhenException);
+ break;
+ }
+
+ }
+
+ @Override
+ public void onException(Throwable e) {
+ if (!popRequest.getMessageQueue().getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
+ log.warn("execute the pull request exception: {}", e);
+ }
+
+ DefaultMQPushConsumerImpl.this.executePopPullRequestLater(popRequest, pullTimeDelayMillsWhenException);
+ }
+ };
+
+
+ try {
+
+ long invisibleTime = this.defaultMQPushConsumer.getPopInvisibleTime();
+ if (invisibleTime < MIN_POP_INVISIBLE_TIME || invisibleTime > MAX_POP_INVISIBLE_TIME) {
+ invisibleTime = 60000;
+ }
+ this.pullAPIWrapper.popAsync(popRequest.getMessageQueue(), invisibleTime, this.defaultMQPushConsumer.getPopBatchNums(),
+ popRequest.getConsumerGroup(), BROKER_SUSPEND_MAX_TIME_MILLIS, popCallback, true, popRequest.getInitMode(),
+ false, subscriptionData.getExpressionType(), subscriptionData.getSubString());
+ } catch (Exception e) {
+ log.error("popAsync exception", e);
+ this.executePopPullRequestLater(popRequest, pullTimeDelayMillsWhenException);
+ }
+ }
+
+ private PopResult processPopResult(final PopResult popResult, final SubscriptionData subscriptionData) {
+ if (PopStatus.FOUND == popResult.getPopStatus()) {
+ List msgFoundList = popResult.getMsgFoundList();
+ List msgListFilterAgain = msgFoundList;
+ if (!subscriptionData.getTagsSet().isEmpty() && !subscriptionData.isClassFilterMode()
+ && popResult.getMsgFoundList().size() > 0) {
+ msgListFilterAgain = new ArrayList(popResult.getMsgFoundList().size());
+ for (MessageExt msg : popResult.getMsgFoundList()) {
+ if (msg.getTags() != null) {
+ if (subscriptionData.getTagsSet().contains(msg.getTags())) {
+ msgListFilterAgain.add(msg);
+ }
+ }
+ }
+ }
+
+ if (!this.filterMessageHookList.isEmpty()) {
+ FilterMessageContext filterMessageContext = new FilterMessageContext();
+ filterMessageContext.setUnitMode(this.defaultMQPushConsumer.isUnitMode());
+ filterMessageContext.setMsgList(msgListFilterAgain);
+ if (!this.filterMessageHookList.isEmpty()) {
+ for (FilterMessageHook hook : this.filterMessageHookList) {
+ try {
+ hook.filterMessage(filterMessageContext);
+ } catch (Throwable e) {
+ log.error("execute hook error. hookName={}", hook.hookName());
+ }
+ }
+ }
+ }
+
+ if (msgFoundList.size() != msgListFilterAgain.size()) {
+ for (MessageExt msg : msgFoundList) {
+ if (!msgListFilterAgain.contains(msg)) {
+ ackAsync(msg, this.groupName());
+ }
+ }
+ }
+
+ popResult.setMsgFoundList(msgListFilterAgain);
+ }
+
+ return popResult;
+ }
+
private void makeSureStateOK() throws MQClientException {
if (this.serviceState != ServiceState.RUNNING) {
throw new MQClientException("The consumer service state not OK, "
@@ -459,7 +646,7 @@ public class DefaultMQPushConsumerImpl implements MQConsumerInner {
}
}
- private void executePullRequestLater(final PullRequest pullRequest, final long timeDelay) {
+ void executePullRequestLater(final PullRequest pullRequest, final long timeDelay) {
this.mQClientFactory.getPullMessageService().executePullRequestLater(pullRequest, timeDelay);
}
@@ -479,6 +666,14 @@ public class DefaultMQPushConsumerImpl implements MQConsumerInner {
this.mQClientFactory.getPullMessageService().executePullRequestImmediately(pullRequest);
}
+ void executePopPullRequestLater(final PopRequest pullRequest, final long timeDelay) {
+ this.mQClientFactory.getPullMessageService().executePopPullRequestLater(pullRequest, timeDelay);
+ }
+
+ void executePopPullRequestImmediately(final PopRequest pullRequest) {
+ this.mQClientFactory.getPullMessageService().executePopPullRequestImmediately(pullRequest);
+ }
+
private void correctTagsOffset(final PullRequest pullRequest) {
if (0L == pullRequest.getProcessQueue().getMsgCount().get()) {
this.offsetStore.updateOffset(pullRequest.getMessageQueue(), pullRequest.getNextOffset(), true);
@@ -538,7 +733,78 @@ public class DefaultMQPushConsumerImpl implements MQConsumerInner {
}
}
- private int getMaxReconsumeTimes() {
+ void ackAsync(MessageExt message, String consumerGroup) {
+ final String extraInfo = message.getProperty(MessageConst.PROPERTY_POP_CK);
+
+ try {
+ String[] extraInfoStrs = ExtraInfoUtil.split(extraInfo);
+ String brokerName = ExtraInfoUtil.getBrokerName(extraInfoStrs);
+ int queueId = ExtraInfoUtil.getQueueId(extraInfoStrs);
+ long queueOffset = ExtraInfoUtil.getQueueOffset(extraInfoStrs);
+ String topic = message.getTopic();
+
+ FindBrokerResult
+ findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(brokerName, MixAll.MASTER_ID, true);
+ if (null == findBrokerResult) {
+ this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic);
+ findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(brokerName, MixAll.MASTER_ID, true);
+ }
+
+ if (findBrokerResult == null) {
+ log.error("The broker[" + brokerName + "] not exist");
+ return;
+ }
+
+ AckMessageRequestHeader requestHeader = new AckMessageRequestHeader();
+ requestHeader.setTopic(ExtraInfoUtil.getRealTopic(extraInfoStrs, topic, consumerGroup));
+ requestHeader.setQueueId(queueId);
+ requestHeader.setOffset(queueOffset);
+ requestHeader.setConsumerGroup(consumerGroup);
+ requestHeader.setExtraInfo(extraInfo);
+ this.mQClientFactory.getMQClientAPIImpl().ackMessageAsync(findBrokerResult.getBrokerAddr(), ASYNC_TIMEOUT, new AckCallback() {
+ @Override
+ public void onSuccess(AckResult ackResult) {
+ if (ackResult != null && !AckStatus.OK.equals(ackResult.getStatus())) {
+ log.info("Ack message fail. ackResult: {}, extraInfo: {}", ackResult, extraInfo);
+ }
+ }
+ @Override
+ public void onException(Throwable e) {
+ log.info("Ack message fail. extraInfo: {} error message: {}", extraInfo, e.toString());
+ }
+ }, requestHeader);
+
+ } catch (Throwable t) {
+ log.error("ack async error.", t);
+ }
+ }
+
+ void changePopInvisibleTimeAsync(String topic, String consumerGroup, String extraInfo, long invisibleTime, AckCallback callback)
+ throws MQClientException, RemotingException, InterruptedException, MQBrokerException {
+ String[] extraInfoStrs = ExtraInfoUtil.split(extraInfo);
+ String brokerName = ExtraInfoUtil.getBrokerName(extraInfoStrs);
+ int queueId = ExtraInfoUtil.getQueueId(extraInfoStrs);
+ FindBrokerResult
+ findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(brokerName, MixAll.MASTER_ID, true);
+ if (null == findBrokerResult) {
+ this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic);
+ findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(brokerName, MixAll.MASTER_ID, true);
+ }
+ if (findBrokerResult != null) {
+ ChangeInvisibleTimeRequestHeader requestHeader = new ChangeInvisibleTimeRequestHeader();
+ requestHeader.setTopic(ExtraInfoUtil.getRealTopic(extraInfoStrs, topic, consumerGroup));
+ requestHeader.setQueueId(queueId);
+ requestHeader.setOffset(ExtraInfoUtil.getQueueOffset(extraInfoStrs));
+ requestHeader.setConsumerGroup(consumerGroup);
+ requestHeader.setExtraInfo(extraInfo);
+ requestHeader.setInvisibleTime(invisibleTime);
+ this.mQClientFactory.getMQClientAPIImpl().changeInvisibleTimeAsync(brokerName, findBrokerResult.getBrokerAddr(), requestHeader, ASYNC_TIMEOUT, callback);
+ return;
+ }
+ throw new MQClientException("The broker[" + brokerName + "] not exist", null);
+ }
+
+ public int getMaxReconsumeTimes() {
// default reconsume times: 16
if (this.defaultMQPushConsumer.getMaxReconsumeTimes() == -1) {
return 16;
@@ -619,13 +885,20 @@ public class DefaultMQPushConsumerImpl implements MQConsumerInner {
this.consumeOrderly = true;
this.consumeMessageService =
new ConsumeMessageOrderlyService(this, (MessageListenerOrderly) this.getMessageListenerInner());
+ //POPTODO reuse Executor ?
+ this.consumeMessagePopService = new ConsumeMessagePopOrderlyService(this, (MessageListenerOrderly) this.getMessageListenerInner());
} else if (this.getMessageListenerInner() instanceof MessageListenerConcurrently) {
this.consumeOrderly = false;
this.consumeMessageService =
new ConsumeMessageConcurrentlyService(this, (MessageListenerConcurrently) this.getMessageListenerInner());
+ //POPTODO reuse Executor ?
+ this.consumeMessagePopService =
+ new ConsumeMessagePopConcurrentlyService(this, (MessageListenerConcurrently) this.getMessageListenerInner());
}
this.consumeMessageService.start();
+ // POPTODO
+ this.consumeMessagePopService.start();
boolean registerOK = mQClientFactory.registerConsumer(this.defaultMQPushConsumer.getConsumerGroup(), this);
if (!registerOK) {
@@ -825,6 +1098,23 @@ public class DefaultMQPushConsumerImpl implements MQConsumerInner {
+ FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL),
null);
}
+
+ // popInvisibleTime
+ if (this.defaultMQPushConsumer.getPopInvisibleTime() < MIN_POP_INVISIBLE_TIME
+ || this.defaultMQPushConsumer.getPopInvisibleTime() > MAX_POP_INVISIBLE_TIME) {
+ throw new MQClientException(
+ "popInvisibleTime Out of range [" + MIN_POP_INVISIBLE_TIME + ", " + MAX_POP_INVISIBLE_TIME + "]"
+ + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL),
+ null);
+ }
+
+ // popBatchNums
+ if (this.defaultMQPushConsumer.getPopBatchNums() <= 0 || this.defaultMQPushConsumer.getPopBatchNums() > 32) {
+ throw new MQClientException(
+ "popBatchNums Out of range [1, 32]"
+ + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL),
+ null);
+ }
}
private void copySubscription() throws MQClientException {
@@ -864,6 +1154,9 @@ public class DefaultMQPushConsumerImpl implements MQConsumerInner {
}
private void updateTopicSubscribeInfoWhenSubscriptionChanged() {
+ if (doNotUpdateTopicSubscribeInfoWhenSubscriptionChanged) {
+ return;
+ }
Map subTable = this.getSubscriptionInner();
if (subTable != null) {
for (final Map.Entry entry : subTable.entrySet()) {
@@ -1081,6 +1374,17 @@ public class DefaultMQPushConsumerImpl implements MQConsumerInner {
info.getMqTable().put(mq, pqinfo);
}
+ Iterator> popIt = this.rebalanceImpl.getPopProcessQueueTable().entrySet().iterator();
+ while (popIt.hasNext()) {
+ Entry next = popIt.next();
+ MessageQueue mq = next.getKey();
+ PopProcessQueue pq = next.getValue();
+
+ PopProcessQueueInfo pqinfo = new PopProcessQueueInfo();
+ pq.fillPopProcessQueueInfo(pqinfo);
+ info.getMqPopTable().put(mq, pqinfo);
+ }
+
for (SubscriptionData sd : subSet) {
ConsumeStatus consumeStatus = this.mQClientFactory.getConsumerStatsManager().consumeStatus(this.groupName(), sd.getTopic());
info.getStatusTable().put(sd.getTopic(), consumeStatus);
@@ -1149,6 +1453,19 @@ public class DefaultMQPushConsumerImpl implements MQConsumerInner {
return queueTimeSpan;
}
+ public void tryResetPopRetryTopic(final List msgs, String consumerGroup) {
+ String popRetryPrefix = MixAll.RETRY_GROUP_TOPIC_PREFIX + consumerGroup + "_";
+ for (MessageExt msg : msgs) {
+ if (msg.getTopic().startsWith(popRetryPrefix)) {
+ String normalTopic = KeyBuilder.parseNormalTopic(msg.getTopic(), consumerGroup);
+ if (normalTopic != null && !normalTopic.isEmpty()) {
+ msg.setTopic(normalTopic);
+ }
+ }
+ }
+ }
+
+
public void resetRetryAndNamespace(final List msgs, String consumerGroup) {
final String groupTopic = MixAll.getRetryTopic(consumerGroup);
for (MessageExt msg : msgs) {
@@ -1175,4 +1492,8 @@ public class DefaultMQPushConsumerImpl implements MQConsumerInner {
public void setPullTimeDelayMillsWhenException(long pullTimeDelayMillsWhenException) {
this.pullTimeDelayMillsWhenException = pullTimeDelayMillsWhenException;
}
+
+ int[] getPopDelayLevel() {
+ return popDelayLevel;
+ }
}
diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/MessageQueueLock.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/MessageQueueLock.java
index a02f1b6ef6e7bc9571ba728cf1509056ad78d5de..73453b0ed6f3d19ed16c2968f0df81f7815a21f3 100644
--- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/MessageQueueLock.java
+++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/MessageQueueLock.java
@@ -24,19 +24,32 @@ import org.apache.rocketmq.common.message.MessageQueue;
* Message lock,strictly ensure the single queue only one thread at a time consuming
*/
public class MessageQueueLock {
- private ConcurrentMap mqLockTable =
- new ConcurrentHashMap();
+ private ConcurrentMap> mqLockTable =
+ new ConcurrentHashMap>(32);
public Object fetchLockObject(final MessageQueue mq) {
- Object objLock = this.mqLockTable.get(mq);
- if (null == objLock) {
- objLock = new Object();
- Object prevLock = this.mqLockTable.putIfAbsent(mq, objLock);
+ return fetchLockObject(mq, -1);
+ }
+
+ public Object fetchLockObject(final MessageQueue mq, final int shardingKeyIndex) {
+ ConcurrentMap objMap = this.mqLockTable.get(mq);
+ if (null == objMap) {
+ objMap = new ConcurrentHashMap(32);
+ ConcurrentMap prevObjMap = this.mqLockTable.putIfAbsent(mq, objMap);
+ if (prevObjMap != null) {
+ objMap = prevObjMap;
+ }
+ }
+
+ Object lock = objMap.get(shardingKeyIndex);
+ if (null == lock) {
+ lock = new Object();
+ Object prevLock = objMap.putIfAbsent(shardingKeyIndex, lock);
if (prevLock != null) {
- objLock = prevLock;
+ lock = prevLock;
}
}
- return objLock;
+ return lock;
}
}
diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/MessageRequest.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/MessageRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..a808538b0195c8019264c1ab94ec03c7bf9e850e
--- /dev/null
+++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/MessageRequest.java
@@ -0,0 +1,23 @@
+/*
+ * 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.impl.consumer;
+
+import org.apache.rocketmq.common.message.MessageRequestMode;
+
+public interface MessageRequest {
+ MessageRequestMode getMessageRequestMode();
+}
diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PopProcessQueue.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PopProcessQueue.java
new file mode 100644
index 0000000000000000000000000000000000000000..0883a771323cbe6ff0c7d62e5e7daff2016c8987
--- /dev/null
+++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PopProcessQueue.java
@@ -0,0 +1,84 @@
+/*
+ * 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.impl.consumer;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import org.apache.rocketmq.common.protocol.body.PopProcessQueueInfo;
+
+/**
+ * Queue consumption snapshot
+ */
+public class PopProcessQueue {
+
+ private final static long PULL_MAX_IDLE_TIME = Long.parseLong(System.getProperty("rocketmq.client.pull.pullMaxIdleTime", "120000"));
+
+ private long lastPopTimestamp;
+ private AtomicInteger waitAckCounter = new AtomicInteger(0);
+ private volatile boolean dropped = false;
+
+ public long getLastPopTimestamp() {
+ return lastPopTimestamp;
+ }
+
+ public void setLastPopTimestamp(long lastPopTimestamp) {
+ this.lastPopTimestamp = lastPopTimestamp;
+ }
+
+ public void incFoundMsg(int count) {
+ this.waitAckCounter.getAndAdd(count);
+ }
+
+ /**
+ * @return the value before decrement.
+ */
+ public int ack() {
+ return this.waitAckCounter.getAndDecrement();
+ }
+
+ public void decFoundMsg(int count) {
+ this.waitAckCounter.addAndGet(count);
+ }
+
+ public int getWaiAckMsgCount() {
+ return this.waitAckCounter.get();
+ }
+
+ public boolean isDropped() {
+ return dropped;
+ }
+
+ public void setDropped(boolean dropped) {
+ this.dropped = dropped;
+ }
+
+ public void fillPopProcessQueueInfo(final PopProcessQueueInfo info) {
+ info.setWaitAckCount(getWaiAckMsgCount());
+ info.setDroped(isDropped());
+ info.setLastPopTimestamp(getLastPopTimestamp());
+ }
+
+ public boolean isPullExpired() {
+ return (System.currentTimeMillis() - this.lastPopTimestamp) > PULL_MAX_IDLE_TIME;
+ }
+
+ @Override
+ public String toString() {
+ return "PopProcessQueue[waitAckCounter:" + this.waitAckCounter.get()
+ + ", lastPopTimestamp:" + getLastPopTimestamp()
+ + ", drop:" + dropped + "]";
+ }
+}
diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PopRequest.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PopRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..c47f2d020e7da2bc30992b56b0ca358c06ba794f
--- /dev/null
+++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PopRequest.java
@@ -0,0 +1,131 @@
+/*
+ * 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.impl.consumer;
+
+import org.apache.rocketmq.common.constant.ConsumeInitMode;
+import org.apache.rocketmq.common.message.MessageQueue;
+import org.apache.rocketmq.common.message.MessageRequestMode;
+
+public class PopRequest implements MessageRequest {
+ private String topic;
+ private String consumerGroup;
+ private MessageQueue messageQueue;
+ private PopProcessQueue popProcessQueue;
+ private boolean lockedFirst = false;
+ private int initMode = ConsumeInitMode.MAX;
+
+ public boolean isLockedFirst() {
+ return lockedFirst;
+ }
+
+ public void setLockedFirst(boolean lockedFirst) {
+ this.lockedFirst = lockedFirst;
+ }
+
+ public String getConsumerGroup() {
+ return consumerGroup;
+ }
+
+ public void setConsumerGroup(String consumerGroup) {
+ this.consumerGroup = consumerGroup;
+ }
+
+ public MessageQueue getMessageQueue() {
+ return messageQueue;
+ }
+
+ public void setMessageQueue(MessageQueue messageQueue) {
+ this.messageQueue = messageQueue;
+ }
+
+ public String getTopic() {
+ return topic;
+ }
+
+ public void setTopic(String topic) {
+ this.topic = topic;
+ }
+
+ public PopProcessQueue getPopProcessQueue() {
+ return popProcessQueue;
+ }
+
+ public void setPopProcessQueue(PopProcessQueue popProcessQueue) {
+ this.popProcessQueue = popProcessQueue;
+ }
+
+ public int getInitMode() {
+ return initMode;
+ }
+
+ public void setInitMode(int initMode) {
+ this.initMode = initMode;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((topic == null) ? 0 : topic.hashCode());
+ result = prime * result + ((consumerGroup == null) ? 0 : consumerGroup.hashCode());
+ result = prime * result + ((messageQueue == null) ? 0 : messageQueue.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+
+ PopRequest other = (PopRequest) obj;
+
+ if (topic == null) {
+ if (other.topic != null)
+ return false;
+ } else if (!topic.equals(other.topic)) {
+ return false;
+ }
+
+ if (consumerGroup == null) {
+ if (other.consumerGroup != null)
+ return false;
+ } else if (!consumerGroup.equals(other.consumerGroup))
+ return false;
+
+ if (messageQueue == null) {
+ if (other.messageQueue != null)
+ return false;
+ } else if (!messageQueue.equals(other.messageQueue)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "PopRequest [topic=" + topic + ", consumerGroup=" + consumerGroup + ", messageQueue=" + messageQueue + "]";
+ }
+
+ @Override
+ public MessageRequestMode getMessageRequestMode() {
+ return MessageRequestMode.POP;
+ }
+}
diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullAPIWrapper.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullAPIWrapper.java
index cc42a9e830ee9dde11869423dd879cefdd9d967a..95b609e439a78a29ac781cf381fabea8f454f8ff 100644
--- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullAPIWrapper.java
+++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullAPIWrapper.java
@@ -23,6 +23,7 @@ import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
+import org.apache.rocketmq.client.consumer.PopCallback;
import org.apache.rocketmq.client.consumer.PullCallback;
import org.apache.rocketmq.client.consumer.PullResult;
import org.apache.rocketmq.client.consumer.PullStatus;
@@ -37,16 +38,17 @@ import org.apache.rocketmq.client.log.ClientLogger;
import org.apache.rocketmq.common.MQVersion;
import org.apache.rocketmq.common.MixAll;
import org.apache.rocketmq.common.filter.ExpressionType;
-import org.apache.rocketmq.logging.InternalLogger;
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.MessageExt;
import org.apache.rocketmq.common.message.MessageQueue;
+import org.apache.rocketmq.common.protocol.header.PopMessageRequestHeader;
import org.apache.rocketmq.common.protocol.header.PullMessageRequestHeader;
import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData;
import org.apache.rocketmq.common.protocol.route.TopicRouteData;
import org.apache.rocketmq.common.sysflag.PullSysFlag;
+import org.apache.rocketmq.logging.InternalLogger;
import org.apache.rocketmq.remoting.exception.RemotingException;
public class PullAPIWrapper {
@@ -269,4 +271,55 @@ public class PullAPIWrapper {
public void setDefaultBrokerId(long defaultBrokerId) {
this.defaultBrokerId = defaultBrokerId;
}
+
+
+ /**
+ *
+ * @param mq
+ * @param invisibleTime
+ * @param maxNums
+ * @param consumerGroup
+ * @param timeout
+ * @param popCallback
+ * @param poll
+ * @param initMode
+ // * @param expressionType
+ // * @param expression
+ * @param order
+ * @throws MQClientException
+ * @throws RemotingException
+ * @throws InterruptedException
+ */
+ public void popAsync(MessageQueue mq, long invisibleTime, int maxNums, String consumerGroup,
+ long timeout, PopCallback popCallback, boolean poll, int initMode, boolean order, String expressionType, String expression)
+ throws MQClientException, RemotingException, InterruptedException {
+ FindBrokerResult findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(), MixAll.MASTER_ID, true);
+ if (null == findBrokerResult) {
+ this.mQClientFactory.updateTopicRouteInfoFromNameServer(mq.getTopic());
+ findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(), MixAll.MASTER_ID, true);
+ }
+ if (findBrokerResult != null) {
+ PopMessageRequestHeader requestHeader = new PopMessageRequestHeader();
+ requestHeader.setConsumerGroup(consumerGroup);
+ requestHeader.setTopic(mq.getTopic());
+ requestHeader.setQueueId(mq.getQueueId());
+ requestHeader.setMaxMsgNums(maxNums);
+ requestHeader.setInvisibleTime(invisibleTime);
+ requestHeader.setInitMode(initMode);
+ requestHeader.setExpType(expressionType);
+ requestHeader.setExp(expression);
+ requestHeader.setOrder(order);
+ //give 1000 ms for server response
+ if (poll) {
+ requestHeader.setPollTime(timeout);
+ requestHeader.setBornTime(System.currentTimeMillis());
+ // timeout + 10s, fix the too earlier timeout of client when long polling.
+ timeout += 10 * 1000;
+ }
+ String brokerAddr = findBrokerResult.getBrokerAddr();
+ this.mQClientFactory.getMQClientAPIImpl().popMessageAsync(mq.getBrokerName(), brokerAddr, requestHeader, timeout, popCallback);
+ return;
+ }
+ throw new MQClientException("The broker[" + mq.getBrokerName() + "] not exist", null);
+ }
}
diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullMessageService.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullMessageService.java
index bd46a58859acf59714eeba794ad5fa4ac247bebf..9665c6d22affb3530b657adbb891a89876bc409c 100644
--- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullMessageService.java
+++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullMessageService.java
@@ -24,12 +24,14 @@ import java.util.concurrent.TimeUnit;
import org.apache.rocketmq.client.impl.factory.MQClientInstance;
import org.apache.rocketmq.client.log.ClientLogger;
import org.apache.rocketmq.common.ServiceThread;
-import org.apache.rocketmq.logging.InternalLogger;
+import org.apache.rocketmq.common.message.MessageRequestMode;
import org.apache.rocketmq.common.utils.ThreadUtils;
+import org.apache.rocketmq.logging.InternalLogger;
public class PullMessageService extends ServiceThread {
private final InternalLogger log = ClientLogger.getLog();
- private final LinkedBlockingQueue pullRequestQueue = new LinkedBlockingQueue();
+ private final LinkedBlockingQueue messageRequestQueue = new LinkedBlockingQueue();
+
private final MQClientInstance mQClientFactory;
private final ScheduledExecutorService scheduledExecutorService = Executors
.newSingleThreadScheduledExecutor(new ThreadFactory() {
@@ -58,7 +60,28 @@ public class PullMessageService extends ServiceThread {
public void executePullRequestImmediately(final PullRequest pullRequest) {
try {
- this.pullRequestQueue.put(pullRequest);
+ this.messageRequestQueue.put(pullRequest);
+ } catch (InterruptedException e) {
+ log.error("executePullRequestImmediately pullRequestQueue.put", e);
+ }
+ }
+
+ public void executePopPullRequestLater(final PopRequest pullRequest, final long timeDelay) {
+ if (!isStopped()) {
+ this.scheduledExecutorService.schedule(new Runnable() {
+ @Override
+ public void run() {
+ PullMessageService.this.executePopPullRequestImmediately(pullRequest);
+ }
+ }, timeDelay, TimeUnit.MILLISECONDS);
+ } else {
+ log.warn("PullMessageServiceScheduledThread has shutdown");
+ }
+ }
+
+ public void executePopPullRequestImmediately(final PopRequest pullRequest) {
+ try {
+ this.messageRequestQueue.put(pullRequest);
} catch (InterruptedException e) {
log.error("executePullRequestImmediately pullRequestQueue.put", e);
}
@@ -86,14 +109,28 @@ public class PullMessageService extends ServiceThread {
}
}
+ private void popMessage(final PopRequest popRequest) {
+ final MQConsumerInner consumer = this.mQClientFactory.selectConsumer(popRequest.getConsumerGroup());
+ if (consumer != null) {
+ DefaultMQPushConsumerImpl impl = (DefaultMQPushConsumerImpl) consumer;
+ impl.popMessage(popRequest);
+ } else {
+ log.warn("No matched consumer for the PopRequest {}, drop it", popRequest);
+ }
+ }
+
@Override
public void run() {
log.info(this.getServiceName() + " service started");
while (!this.isStopped()) {
try {
- PullRequest pullRequest = this.pullRequestQueue.take();
- this.pullMessage(pullRequest);
+ MessageRequest messageRequest = this.messageRequestQueue.take();
+ if (messageRequest.getMessageRequestMode() == MessageRequestMode.POP) {
+ this.popMessage((PopRequest)messageRequest);
+ } else {
+ this.pullMessage((PullRequest)messageRequest);
+ }
} catch (InterruptedException ignored) {
} catch (Exception e) {
log.error("Pull Message Service Run Method exception", e);
diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullRequest.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullRequest.java
index bf03ec38c3a68d9a789504f3291b9b66d4c833cf..b90192b99257f804801bd78cb46c2eef2e96ff10 100644
--- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullRequest.java
+++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullRequest.java
@@ -17,8 +17,9 @@
package org.apache.rocketmq.client.impl.consumer;
import org.apache.rocketmq.common.message.MessageQueue;
+import org.apache.rocketmq.common.message.MessageRequestMode;
-public class PullRequest {
+public class PullRequest implements MessageRequest {
private String consumerGroup;
private MessageQueue messageQueue;
private ProcessQueue processQueue;
@@ -101,4 +102,9 @@ public class PullRequest {
public void setProcessQueue(ProcessQueue processQueue) {
this.processQueue = processQueue;
}
+
+ @Override
+ public MessageRequestMode getMessageRequestMode() {
+ return MessageRequestMode.PULL;
+ }
}
diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalanceImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalanceImpl.java
index 833d465a4703184be6e9a17c20682fdd5d81b240..7a457c152bd0eaa76c728eaab8fb40f21979b6b7 100644
--- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalanceImpl.java
+++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalanceImpl.java
@@ -32,18 +32,26 @@ import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.impl.FindBrokerResult;
import org.apache.rocketmq.client.impl.factory.MQClientInstance;
import org.apache.rocketmq.client.log.ClientLogger;
+import org.apache.rocketmq.common.KeyBuilder;
import org.apache.rocketmq.common.MixAll;
-import org.apache.rocketmq.logging.InternalLogger;
+import org.apache.rocketmq.common.filter.FilterAPI;
import org.apache.rocketmq.common.message.MessageQueue;
+import org.apache.rocketmq.common.message.MessageQueueAssignment;
+import org.apache.rocketmq.common.message.MessageRequestMode;
import org.apache.rocketmq.common.protocol.body.LockBatchRequestBody;
import org.apache.rocketmq.common.protocol.body.UnlockBatchRequestBody;
import org.apache.rocketmq.common.protocol.heartbeat.ConsumeType;
import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;
import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData;
+import org.apache.rocketmq.logging.InternalLogger;
+import org.apache.rocketmq.remoting.exception.RemotingTimeoutException;
public abstract class RebalanceImpl {
protected static final InternalLogger log = ClientLogger.getLog();
+
protected final ConcurrentMap processQueueTable = new ConcurrentHashMap(64);
+ protected final ConcurrentMap popProcessQueueTable = new ConcurrentHashMap(64);
+
protected final ConcurrentMap> topicSubscribeInfoTable =
new ConcurrentHashMap>();
protected final ConcurrentMap subscriptionInner =
@@ -52,6 +60,11 @@ public abstract class RebalanceImpl {
protected MessageModel messageModel;
protected AllocateMessageQueueStrategy allocateMessageQueueStrategy;
protected MQClientInstance mQClientFactory;
+ private static final int TIMEOUT_CHECK_TIMES = 3;
+ private static final int QUERY_ASSIGNMENT_TIMEOUT = 3000;
+
+ private Map topicBrokerRebalance = new ConcurrentHashMap();
+ private Map topicClientRebalance = new ConcurrentHashMap();
public RebalanceImpl(String consumerGroup, MessageModel messageModel,
AllocateMessageQueueStrategy allocateMessageQueueStrategy,
@@ -89,8 +102,9 @@ public abstract class RebalanceImpl {
final String brokerName = entry.getKey();
final Set mqs = entry.getValue();
- if (mqs.isEmpty())
+ if (mqs.isEmpty()) {
continue;
+ }
FindBrokerResult findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(brokerName, MixAll.MASTER_ID, true);
if (findBrokerResult != null) {
@@ -118,7 +132,15 @@ public abstract class RebalanceImpl {
private HashMap> buildProcessQueueTableByBrokerName() {
HashMap> result = new HashMap>();
- for (MessageQueue mq : this.processQueueTable.keySet()) {
+
+ for (Map.Entry entry : this.processQueueTable.entrySet()) {
+ MessageQueue mq = entry.getKey();
+ ProcessQueue pq = entry.getValue();
+
+ if (pq.isDropped()) {
+ continue;
+ }
+
Set mqs = result.get(mq.getBrokerName());
if (null == mqs) {
mqs = new HashSet();
@@ -151,10 +173,7 @@ public abstract class RebalanceImpl {
}
boolean lockOK = lockedMq.contains(mq);
- log.info("the message queue lock {}, {} {}",
- lockOK ? "OK" : "Failed",
- this.consumerGroup,
- mq);
+ log.info("message queue lock {}, {} {}", lockOK ? "OK" : "Failed", this.consumerGroup, mq);
return lockOK;
} catch (Exception e) {
log.error("lockBatchMQ exception, " + mq, e);
@@ -173,8 +192,9 @@ public abstract class RebalanceImpl {
final String brokerName = entry.getKey();
final Set mqs = entry.getValue();
- if (mqs.isEmpty())
+ if (mqs.isEmpty()) {
continue;
+ }
FindBrokerResult findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(brokerName, MixAll.MASTER_ID, true);
if (findBrokerResult != null) {
@@ -214,29 +234,86 @@ public abstract class RebalanceImpl {
}
}
- public void doRebalance(final boolean isOrder) {
+ public boolean clientRebalance(String topic) {
+ return true;
+ }
+
+ public boolean doRebalance(final boolean isOrder) {
+ boolean balanced = true;
Map subTable = this.getSubscriptionInner();
if (subTable != null) {
for (final Map.Entry entry : subTable.entrySet()) {
final String topic = entry.getKey();
try {
- this.rebalanceByTopic(topic, isOrder);
+ if (!clientRebalance(topic) && tryQueryAssignment(topic)) {
+ balanced = this.getRebalanceResultFromBroker(topic, isOrder);
+ } else {
+ balanced = this.rebalanceByTopic(topic, isOrder);
+ }
} catch (Throwable e) {
if (!topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
- log.warn("rebalanceByTopic Exception", e);
+ log.warn("rebalance Exception", e);
+ balanced = false;
}
}
}
}
this.truncateMessageQueueNotMyTopic();
+
+ return balanced;
+ }
+
+ private boolean tryQueryAssignment(String topic) {
+ if (topicClientRebalance.containsKey(topic)) {
+ return false;
+ }
+
+ if (topicBrokerRebalance.containsKey(topic)) {
+ return true;
+ }
+
+ String strategyName = allocateMessageQueueStrategy != null ? allocateMessageQueueStrategy.getName() : null;
+
+ boolean success = false;
+ int i = 0;
+ int timeOut = 0;
+ while (i++ < TIMEOUT_CHECK_TIMES) {
+ try {
+ Set resultSet = mQClientFactory.queryAssignment(topic, consumerGroup,
+ strategyName, messageModel, QUERY_ASSIGNMENT_TIMEOUT / TIMEOUT_CHECK_TIMES * i);
+ success = true;
+ break;
+ } catch (Throwable t) {
+ if (t instanceof RemotingTimeoutException) {
+ timeOut++;
+ } else {
+ log.error("tryQueryAssignment error.", t);
+ break;
+ }
+ }
+ }
+
+ if (success) {
+ topicBrokerRebalance.put(topic, topic);
+ return true;
+ } else {
+ if (timeOut >= TIMEOUT_CHECK_TIMES) {
+ // if never success before and timeout exceed TIMEOUT_CHECK_TIMES, force client rebalance
+ topicClientRebalance.put(topic, topic);
+ return false;
+ } else {
+ return true;
+ }
+ }
}
public ConcurrentMap getSubscriptionInner() {
return subscriptionInner;
}
- private void rebalanceByTopic(final String topic, final boolean isOrder) {
+ private boolean rebalanceByTopic(final String topic, final boolean isOrder) {
+ boolean balanced = true;
switch (messageModel) {
case BROADCASTING: {
Set mqSet = this.topicSubscribeInfoTable.get(topic);
@@ -244,13 +321,12 @@ public abstract class RebalanceImpl {
boolean changed = this.updateProcessQueueTableInRebalance(topic, mqSet, isOrder);
if (changed) {
this.messageQueueChanged(topic, mqSet, mqSet);
- log.info("messageQueueChanged {} {} {} {}",
- consumerGroup,
- topic,
- mqSet,
- mqSet);
+ log.info("messageQueueChanged {} {} {} {}", consumerGroup, topic, mqSet, mqSet);
}
+
+ balanced = mqSet.equals(getWorkingMessageQueue(topic));
} else {
+ this.messageQueueChanged(topic, Collections.emptySet(), Collections.emptySet());
log.warn("doRebalance, {}, but the topic[{}] not exist.", consumerGroup, topic);
}
break;
@@ -260,6 +336,7 @@ public abstract class RebalanceImpl {
List cidAll = this.mQClientFactory.findConsumerIdList(topic, consumerGroup);
if (null == mqSet) {
if (!topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
+ this.messageQueueChanged(topic, Collections.emptySet(), Collections.emptySet());
log.warn("doRebalance, {}, but the topic[{}] not exist.", consumerGroup, topic);
}
}
@@ -285,9 +362,8 @@ public abstract class RebalanceImpl {
mqAll,
cidAll);
} catch (Throwable e) {
- log.error("AllocateMessageQueueStrategy.allocate Exception. allocateMessageQueueStrategyName={}", strategy.getName(),
- e);
- return;
+ log.error("allocate message queue exception. strategy name: {}, ex: {}", strategy.getName(), e);
+ return false;
}
Set allocateResultSet = new HashSet();
@@ -298,17 +374,76 @@ public abstract class RebalanceImpl {
boolean changed = this.updateProcessQueueTableInRebalance(topic, allocateResultSet, isOrder);
if (changed) {
log.info(
- "rebalanced result changed. allocateMessageQueueStrategyName={}, group={}, topic={}, clientId={}, mqAllSize={}, cidAllSize={}, rebalanceResultSize={}, rebalanceResultSet={}",
+ "client rebalanced result changed. allocateMessageQueueStrategyName={}, group={}, topic={}, clientId={}, mqAllSize={}, cidAllSize={}, rebalanceResultSize={}, rebalanceResultSet={}",
strategy.getName(), consumerGroup, topic, this.mQClientFactory.getClientId(), mqSet.size(), cidAll.size(),
allocateResultSet.size(), allocateResultSet);
this.messageQueueChanged(topic, mqSet, allocateResultSet);
}
+
+ balanced = allocateResultSet.equals(getWorkingMessageQueue(topic));
}
break;
}
default:
break;
}
+
+ return balanced;
+ }
+
+ private boolean getRebalanceResultFromBroker(final String topic, final boolean isOrder) {
+ String strategyName = this.allocateMessageQueueStrategy.getName();
+ Set messageQueueAssignments;
+ try {
+ messageQueueAssignments = this.mQClientFactory.queryAssignment(topic, consumerGroup,
+ strategyName, messageModel, QUERY_ASSIGNMENT_TIMEOUT);
+ } catch (Exception e) {
+ log.error("allocate message queue exception. strategy name: {}, ex: {}", strategyName, e);
+ return false;
+ }
+
+ // null means invalid result, we should skip the update logic
+ if (messageQueueAssignments == null) {
+ return false;
+ }
+ Set mqSet = new HashSet();
+ for (MessageQueueAssignment messageQueueAssignment : messageQueueAssignments) {
+ if (messageQueueAssignment.getMessageQueue() != null) {
+ mqSet.add(messageQueueAssignment.getMessageQueue());
+ }
+ }
+ Set mqAll = null;
+ boolean changed = this.updateMessageQueueAssignment(topic, messageQueueAssignments, isOrder);
+ if (changed) {
+ log.info("broker rebalanced result changed. allocateMessageQueueStrategyName={}, group={}, topic={}, clientId={}, assignmentSet={}",
+ strategyName, consumerGroup, topic, this.mQClientFactory.getClientId(), messageQueueAssignments);
+ this.messageQueueChanged(topic, mqAll, mqSet);
+ }
+
+ return mqSet.equals(getWorkingMessageQueue(topic));
+ }
+
+ private Set getWorkingMessageQueue(String topic) {
+ Set queueSet = new HashSet();
+ for (Entry entry : this.processQueueTable.entrySet()) {
+ MessageQueue mq = entry.getKey();
+ ProcessQueue pq = entry.getValue();
+
+ if (mq.getTopic().equals(topic) && !pq.isDropped()) {
+ queueSet.add(mq);
+ }
+ }
+
+ for (Entry entry : this.popProcessQueueTable.entrySet()) {
+ MessageQueue mq = entry.getKey();
+ PopProcessQueue pq = entry.getValue();
+
+ if (mq.getTopic().equals(topic) && !pq.isDropped()) {
+ queueSet.add(mq);
+ }
+ }
+
+ return queueSet;
}
private void truncateMessageQueueNotMyTopic() {
@@ -324,12 +459,40 @@ public abstract class RebalanceImpl {
}
}
}
+
+ for (MessageQueue mq : this.popProcessQueueTable.keySet()) {
+ if (!subTable.containsKey(mq.getTopic())) {
+
+ PopProcessQueue pq = this.popProcessQueueTable.remove(mq);
+ if (pq != null) {
+ pq.setDropped(true);
+ log.info("doRebalance, {}, truncateMessageQueueNotMyTopic remove unnecessary pop mq, {}", consumerGroup, mq);
+ }
+ }
+ }
+
+ Iterator> clientIter = topicClientRebalance.entrySet().iterator();
+ while (clientIter.hasNext()) {
+ if (!subTable.containsKey(clientIter.next().getKey())) {
+ clientIter.remove();
+ }
+ }
+
+ Iterator> brokerIter = topicBrokerRebalance.entrySet().iterator();
+ while (brokerIter.hasNext()) {
+ if (!subTable.containsKey(brokerIter.next().getKey())) {
+ brokerIter.remove();
+ }
+ }
}
private boolean updateProcessQueueTableInRebalance(final String topic, final Set mqSet,
final boolean isOrder) {
boolean changed = false;
+ Map upgradeMqTable = new HashMap();
+ // drop process queues no longer belong me
+ HashMap removeQueueMap = new HashMap