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(this.processQueueTable.size()); Iterator> it = this.processQueueTable.entrySet().iterator(); while (it.hasNext()) { Entry next = it.next(); @@ -339,50 +502,43 @@ public abstract class RebalanceImpl { if (mq.getTopic().equals(topic)) { if (!mqSet.contains(mq)) { pq.setDropped(true); - if (this.removeUnnecessaryMessageQueue(mq, pq)) { - it.remove(); - changed = true; - log.info("doRebalance, {}, remove unnecessary mq, {}", consumerGroup, mq); - } - } else if (pq.isPullExpired()) { - switch (this.consumeType()) { - case CONSUME_ACTIVELY: - break; - case CONSUME_PASSIVELY: - pq.setDropped(true); - if (this.removeUnnecessaryMessageQueue(mq, pq)) { - it.remove(); - changed = true; - log.error("[BUG]doRebalance, {}, remove unnecessary mq, {}, because pull is pause, so try to fixed it", - consumerGroup, mq); - } - break; - default: - break; - } + removeQueueMap.put(mq, pq); + } else if (pq.isPullExpired() && this.consumeType() == ConsumeType.CONSUME_PASSIVELY) { + pq.setDropped(true); + removeQueueMap.put(mq, pq); + log.error("[BUG]doRebalance, {}, try remove unnecessary mq, {}, because pull is pause, so try to fixed it", + consumerGroup, mq); } } } + // remove message queues no longer belong me + for (Entry entry : removeQueueMap.entrySet()) { + MessageQueue mq = entry.getKey(); + ProcessQueue pq = entry.getValue(); + + if (this.removeUnnecessaryMessageQueue(mq, pq)) { + this.processQueueTable.remove(mq); + changed = true; + log.info("doRebalance, {}, remove unnecessary mq, {}", consumerGroup, mq); + } + } + + // add new message queue + boolean allMQLocked = true; List pullRequestList = new ArrayList(); for (MessageQueue mq : mqSet) { if (!this.processQueueTable.containsKey(mq)) { if (isOrder && !this.lock(mq)) { log.warn("doRebalance, {}, add a new mq failed, {}, because lock failed", consumerGroup, mq); + allMQLocked = false; continue; } this.removeDirtyOffset(mq); - ProcessQueue pq = new ProcessQueue(); - - long nextOffset = -1L; - try { - nextOffset = this.computePullFromWhereWithException(mq); - } catch (MQClientException e) { - log.info("doRebalance, {}, compute offset failed, {}", consumerGroup, mq); - continue; - } - + ProcessQueue pq = createProcessQueue(topic); + pq.setLocked(true); + long nextOffset = this.computePullFromWhere(mq); if (nextOffset >= 0) { ProcessQueue pre = this.processQueueTable.putIfAbsent(mq, pq); if (pre != null) { @@ -401,9 +557,201 @@ public abstract class RebalanceImpl { log.warn("doRebalance, {}, add new mq failed, {}", consumerGroup, mq); } } + } - this.dispatchPullRequest(pullRequestList); + if (!allMQLocked) { + mQClientFactory.rebalanceLater(500); + } + + this.dispatchPullRequest(pullRequestList, 500); + + return changed; + } + + private boolean updateMessageQueueAssignment(final String topic, final Set assignments, + final boolean isOrder) { + boolean changed = false; + + Map mq2PushAssignment = new HashMap(); + Map mq2PopAssignment = new HashMap(); + for (MessageQueueAssignment assignment : assignments) { + MessageQueue messageQueue = assignment.getMessageQueue(); + if (messageQueue == null) { + continue; + } + if (MessageRequestMode.POP == assignment.getMode()) { + mq2PopAssignment.put(messageQueue, assignment); + } else { + mq2PushAssignment.put(messageQueue, assignment); + } + } + + if (!topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { + if (mq2PopAssignment.isEmpty() && !mq2PushAssignment.isEmpty()) { + //pop switch to push + //subscribe pop retry topic + try { + final String retryTopic = KeyBuilder.buildPopRetryTopic(topic, getConsumerGroup()); + SubscriptionData subscriptionData = FilterAPI.buildSubscriptionData(retryTopic, SubscriptionData.SUB_ALL); + getSubscriptionInner().put(retryTopic, subscriptionData); + } catch (Exception ignored) { + } + + } else if (!mq2PopAssignment.isEmpty() && mq2PushAssignment.isEmpty()) { + //push switch to pop + //unsubscribe pop retry topic + try { + final String retryTopic = KeyBuilder.buildPopRetryTopic(topic, getConsumerGroup()); + getSubscriptionInner().remove(retryTopic); + } catch (Exception ignored) { + } + + } + } + + { + // drop process queues no longer belong me + HashMap removeQueueMap = new HashMap(this.processQueueTable.size()); + Iterator> it = this.processQueueTable.entrySet().iterator(); + while (it.hasNext()) { + Entry next = it.next(); + MessageQueue mq = next.getKey(); + ProcessQueue pq = next.getValue(); + + if (mq.getTopic().equals(topic)) { + if (!mq2PushAssignment.containsKey(mq)) { + pq.setDropped(true); + removeQueueMap.put(mq, pq); + } else if (pq.isPullExpired() && this.consumeType() == ConsumeType.CONSUME_PASSIVELY) { + pq.setDropped(true); + removeQueueMap.put(mq, pq); + log.error("[BUG]doRebalance, {}, try remove unnecessary mq, {}, because pull is pause, so try to fixed it", + consumerGroup, mq); + } + } + } + // remove message queues no longer belong me + for (Entry entry : removeQueueMap.entrySet()) { + MessageQueue mq = entry.getKey(); + ProcessQueue pq = entry.getValue(); + + if (this.removeUnnecessaryMessageQueue(mq, pq)) { + this.processQueueTable.remove(mq); + changed = true; + log.info("doRebalance, {}, remove unnecessary mq, {}", consumerGroup, mq); + } + } + } + + { + HashMap removeQueueMap = new HashMap(this.popProcessQueueTable.size()); + Iterator> it = this.popProcessQueueTable.entrySet().iterator(); + while (it.hasNext()) { + Entry next = it.next(); + MessageQueue mq = next.getKey(); + PopProcessQueue pq = next.getValue(); + + if (mq.getTopic().equals(topic)) { + if (!mq2PopAssignment.containsKey(mq)) { + //the queue is no longer your assignment + pq.setDropped(true); + removeQueueMap.put(mq, pq); + } else if (pq.isPullExpired() && this.consumeType() == ConsumeType.CONSUME_PASSIVELY) { + pq.setDropped(true); + removeQueueMap.put(mq, pq); + log.error("[BUG]doRebalance, {}, try remove unnecessary pop mq, {}, because pop is pause, so try to fixed it", + consumerGroup, mq); + } + } + } + // remove message queues no longer belong me + for (Entry entry : removeQueueMap.entrySet()) { + MessageQueue mq = entry.getKey(); + PopProcessQueue pq = entry.getValue(); + + if (this.removeUnnecessaryPopMessageQueue(mq, pq)) { + this.popProcessQueueTable.remove(mq); + changed = true; + log.info("doRebalance, {}, remove unnecessary pop mq, {}", consumerGroup, mq); + } + } + } + + { + // add new message queue + boolean allMQLocked = true; + List pullRequestList = new ArrayList(); + for (MessageQueue mq : mq2PushAssignment.keySet()) { + if (!this.processQueueTable.containsKey(mq)) { + if (isOrder && !this.lock(mq)) { + log.warn("doRebalance, {}, add a new mq failed, {}, because lock failed", consumerGroup, mq); + allMQLocked = false; + continue; + } + + this.removeDirtyOffset(mq); + ProcessQueue pq = createProcessQueue(); + pq.setLocked(true); + long nextOffset = -1L; + try { + nextOffset = this.computePullFromWhereWithException(mq); + } catch (MQClientException e) { + log.info("doRebalance, {}, compute offset failed, {}", consumerGroup, mq); + continue; + } + + if (nextOffset >= 0) { + ProcessQueue pre = this.processQueueTable.putIfAbsent(mq, pq); + if (pre != null) { + log.info("doRebalance, {}, mq already exists, {}", consumerGroup, mq); + } else { + log.info("doRebalance, {}, add a new mq, {}", consumerGroup, mq); + PullRequest pullRequest = new PullRequest(); + pullRequest.setConsumerGroup(consumerGroup); + pullRequest.setNextOffset(nextOffset); + pullRequest.setMessageQueue(mq); + pullRequest.setProcessQueue(pq); + pullRequestList.add(pullRequest); + changed = true; + } + } else { + log.warn("doRebalance, {}, add new mq failed, {}", consumerGroup, mq); + } + } + } + + if (!allMQLocked) { + mQClientFactory.rebalanceLater(500); + } + this.dispatchPullRequest(pullRequestList, 500); + } + + { + // add new message queue + List popRequestList = new ArrayList(); + for (MessageQueue mq : mq2PopAssignment.keySet()) { + if (!this.popProcessQueueTable.containsKey(mq)) { + PopProcessQueue pq = createPopProcessQueue(); + PopProcessQueue pre = this.popProcessQueueTable.putIfAbsent(mq, pq); + if (pre != null) { + log.info("doRebalance, {}, mq pop already exists, {}", consumerGroup, mq); + } else { + log.info("doRebalance, {}, add a new pop mq, {}", consumerGroup, mq); + PopRequest popRequest = new PopRequest(); + popRequest.setTopic(topic); + popRequest.setConsumerGroup(consumerGroup); + popRequest.setMessageQueue(mq); + popRequest.setPopProcessQueue(pq); + popRequest.setInitMode(getConsumeInitMode()); + popRequestList.add(popRequest); + changed = true; + } + } + } + + this.dispatchPopPullRequest(popRequestList, 500); + } return changed; } @@ -413,6 +761,10 @@ public abstract class RebalanceImpl { public abstract boolean removeUnnecessaryMessageQueue(final MessageQueue mq, final ProcessQueue pq); + public boolean removeUnnecessaryPopMessageQueue(final MessageQueue mq, final PopProcessQueue pq) { + return true; + } + public abstract ConsumeType consumeType(); public abstract void removeDirtyOffset(final MessageQueue mq); @@ -428,7 +780,17 @@ public abstract class RebalanceImpl { public abstract long computePullFromWhereWithException(final MessageQueue mq) throws MQClientException; - public abstract void dispatchPullRequest(final List pullRequestList); + public abstract int getConsumeInitMode(); + + public abstract void dispatchPullRequest(final List pullRequestList, final long delay); + + public abstract void dispatchPopPullRequest(final List pullRequestList, final long delay); + + public abstract ProcessQueue createProcessQueue(); + + public abstract PopProcessQueue createPopProcessQueue(); + + public abstract ProcessQueue createProcessQueue(String topicName); public void removeProcessQueue(final MessageQueue mq) { ProcessQueue prev = this.processQueueTable.remove(mq); @@ -444,6 +806,10 @@ public abstract class RebalanceImpl { return processQueueTable; } + public ConcurrentMap getPopProcessQueueTable() { + return popProcessQueueTable; + } + public ConcurrentMap> getTopicSubscribeInfoTable() { return topicSubscribeInfoTable; } @@ -488,5 +854,13 @@ public abstract class RebalanceImpl { } this.processQueueTable.clear(); + + Iterator> popIt = this.popProcessQueueTable.entrySet().iterator(); + while (popIt.hasNext()) { + Entry next = popIt.next(); + next.getValue().setDropped(true); + } + this.popProcessQueueTable.clear(); } + } diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalanceLitePullImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalanceLitePullImpl.java index 286c684e43a52dc3759c0f3b9393e0ee75851ef4..4d347113c7cdda822308e0e42dc026268d0ddfb8 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalanceLitePullImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalanceLitePullImpl.java @@ -153,7 +153,30 @@ public class RebalanceLitePullImpl extends RebalanceImpl { } @Override - public void dispatchPullRequest(List pullRequestList) { + public int getConsumeInitMode() { + throw new UnsupportedOperationException("no initMode for Pull"); } + @Override + public void dispatchPullRequest(final List pullRequestList, final long delay) { + } + + @Override + public void dispatchPopPullRequest(List pullRequestList, long delay) { + + } + + @Override + public ProcessQueue createProcessQueue() { + return new ProcessQueue(); + } + + @Override + public PopProcessQueue createPopProcessQueue() { + return null; + } + + public ProcessQueue createProcessQueue(String topicName) { + return createProcessQueue(); + } } diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalancePullImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalancePullImpl.java index 6df7eb7f00cb3141bda3cef444f6a88c7abeeadd..e1c67926a9918568858bb29a21cc658bd5ad9ff0 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalancePullImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalancePullImpl.java @@ -81,6 +81,30 @@ public class RebalancePullImpl extends RebalanceImpl { } @Override - public void dispatchPullRequest(List pullRequestList) { + public int getConsumeInitMode() { + throw new UnsupportedOperationException("no initMode for Pull"); } + + @Override + public void dispatchPullRequest(final List pullRequestList, final long delay) { + } + + @Override + public void dispatchPopPullRequest(final List pullRequestList, final long delay) { + } + + @Override + public ProcessQueue createProcessQueue() { + return new ProcessQueue(); + } + + @Override + public PopProcessQueue createPopProcessQueue() { + return null; + } + + public ProcessQueue createProcessQueue(String topicName) { + return createProcessQueue(); + } + } diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalancePushImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalancePushImpl.java index 666b696ffa89a631243e2e6533cfa82a52ff383f..09d1521654830b73e019c0a910e1f7a361fdf492 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalancePushImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalancePushImpl.java @@ -26,6 +26,7 @@ import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.UtilAll; +import org.apache.rocketmq.common.constant.ConsumeInitMode; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.protocol.heartbeat.ConsumeType; @@ -36,6 +37,7 @@ public class RebalancePushImpl extends RebalanceImpl { private final static long UNLOCK_DELAY_TIME_MILLS = Long.parseLong(System.getProperty("rocketmq.client.unlockDelayTimeMills", "20000")); private final DefaultMQPushConsumerImpl defaultMQPushConsumerImpl; + public RebalancePushImpl(DefaultMQPushConsumerImpl defaultMQPushConsumerImpl) { this(null, null, null, null, defaultMQPushConsumerImpl); } @@ -110,6 +112,16 @@ public class RebalancePushImpl extends RebalanceImpl { return true; } + @Override + public boolean clientRebalance(String topic) { + // POPTODO order pop consume not implement yet + return defaultMQPushConsumerImpl.getDefaultMQPushConsumer().isClientRebalance() || defaultMQPushConsumerImpl.isConsumeOrderly() || MessageModel.BROADCASTING.equals(messageModel); + } + + public boolean removeUnnecessaryPopMessageQueue(final MessageQueue mq, final PopProcessQueue pq) { + return true; + } + private boolean unlockDelay(final MessageQueue mq, final ProcessQueue pq) { if (pq.hasTempMessage()) { @@ -227,10 +239,48 @@ public class RebalancePushImpl extends RebalanceImpl { } @Override - public void dispatchPullRequest(List pullRequestList) { + public int getConsumeInitMode() { + final ConsumeFromWhere consumeFromWhere = this.defaultMQPushConsumerImpl.getDefaultMQPushConsumer().getConsumeFromWhere(); + if (ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET == consumeFromWhere) { + return ConsumeInitMode.MIN; + } else { + return ConsumeInitMode.MAX; + } + } + + @Override + public void dispatchPullRequest(final List pullRequestList, final long delay) { for (PullRequest pullRequest : pullRequestList) { - this.defaultMQPushConsumerImpl.executePullRequestImmediately(pullRequest); - log.info("doRebalance, {}, add a new pull request {}", consumerGroup, pullRequest); + if (delay <= 0) { + this.defaultMQPushConsumerImpl.executePullRequestImmediately(pullRequest); + } else { + this.defaultMQPushConsumerImpl.executePullRequestLater(pullRequest, delay); + } } } + + @Override + public void dispatchPopPullRequest(final List pullRequestList, final long delay) { + for (PopRequest pullRequest : pullRequestList) { + if (delay <= 0) { + this.defaultMQPushConsumerImpl.executePopPullRequestImmediately(pullRequest); + } else { + this.defaultMQPushConsumerImpl.executePopPullRequestLater(pullRequest, delay); + } + } + } + + @Override + public ProcessQueue createProcessQueue() { + return new ProcessQueue(); + } + + @Override public ProcessQueue createProcessQueue(String topicName) { + return createProcessQueue(); + } + + @Override + public PopProcessQueue createPopProcessQueue() { + return new PopProcessQueue(); + } } diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/factory/MQClientInstance.java b/client/src/main/java/org/apache/rocketmq/client/impl/factory/MQClientInstance.java index 81e6d8468ebf1d303330cd4ff21b91f79be58b8a..619cfc204ef947b3aee285885c3e3321322cb136 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/factory/MQClientInstance.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/factory/MQClientInstance.java @@ -35,7 +35,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; - import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.admin.MQAdminExtInner; @@ -64,26 +63,29 @@ import org.apache.rocketmq.common.ServiceState; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.constant.PermName; import org.apache.rocketmq.common.filter.ExpressionType; -import org.apache.rocketmq.common.protocol.NamespaceUtil; -import org.apache.rocketmq.logging.InternalLogger; 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.protocol.NamespaceUtil; import org.apache.rocketmq.common.protocol.body.ConsumeMessageDirectlyResult; import org.apache.rocketmq.common.protocol.body.ConsumerRunningInfo; import org.apache.rocketmq.common.protocol.heartbeat.ConsumeType; import org.apache.rocketmq.common.protocol.heartbeat.ConsumerData; import org.apache.rocketmq.common.protocol.heartbeat.HeartbeatData; +import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; import org.apache.rocketmq.common.protocol.heartbeat.ProducerData; import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; import org.apache.rocketmq.common.protocol.route.BrokerData; import org.apache.rocketmq.common.protocol.route.QueueData; import org.apache.rocketmq.common.protocol.route.TopicRouteData; +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; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.protocol.RemotingCommand; + public class MQClientInstance { private final static long LOCK_TIMEOUT_MILLIS = 3000; private final InternalLogger log = ClientLogger.getLog(); @@ -964,6 +966,19 @@ public class MQClientInstance { this.adminExtTable.remove(group); } + public void rebalanceLater(long delayMillis) { + if (delayMillis <= 0) { + this.rebalanceService.wakeup(); + } else { + this.scheduledExecutorService.schedule(new Runnable() { + @Override + public void run() { + MQClientInstance.this.rebalanceService.wakeup(); + } + }, delayMillis, TimeUnit.MILLISECONDS); + } + } + public void rebalanceImmediately() { this.rebalanceService.wakeup(); } @@ -1091,6 +1106,22 @@ public class MQClientInstance { return null; } + public Set queryAssignment(final String topic, final String consumerGroup, final String strategyName, final MessageModel messageModel, int timeout) + throws RemotingException, InterruptedException, MQBrokerException { + String brokerAddr = this.findBrokerAddrByTopic(topic); + if (null == brokerAddr) { + this.updateTopicRouteInfoFromNameServer(topic); + brokerAddr = this.findBrokerAddrByTopic(topic); + } + + if (null != brokerAddr) { + return this.mQClientAPIImpl.queryAssignment(brokerAddr, topic, consumerGroup, clientId, strategyName, + messageModel, timeout); + } + + return null; + } + public String findBrokerAddrByTopic(final String topic) { TopicRouteData topicRouteData = this.topicRouteTable.get(topic); if (topicRouteData != null) { diff --git a/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultLitePullConsumerTest.java b/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultLitePullConsumerTest.java index 04b760eec2281561a70c7723208f10d1125d457f..ba2a6a27d457e052211efa0aa8440fcf2ebc9ae7 100644 --- a/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultLitePullConsumerTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultLitePullConsumerTest.java @@ -104,6 +104,10 @@ public class DefaultLitePullConsumerTest { field = RebalanceService.class.getDeclaredField("waitInterval"); field.setAccessible(true); field.set(rebalanceService, 100); + + field = DefaultLitePullConsumerImpl.class.getDeclaredField("doNotUpdateTopicSubscribeInfoWhenSubscriptionChanged"); + field.setAccessible(true); + field.set(null, true); } @Test @@ -188,6 +192,7 @@ public class DefaultLitePullConsumerTest { List messageQueues = Collections.singletonList(messageQueue); litePullConsumer.assign(messageQueues); litePullConsumer.pause(messageQueues); + litePullConsumer.pause(Collections.singletonList(messageQueue)); long offset = litePullConsumer.committed(messageQueue); litePullConsumer.seek(messageQueue, offset); Field field = DefaultLitePullConsumerImpl.class.getDeclaredField("assignedMessageQueue"); @@ -206,6 +211,7 @@ public class DefaultLitePullConsumerTest { List messageQueues = Collections.singletonList(messageQueue); litePullConsumer.assign(messageQueues); litePullConsumer.pause(messageQueues); + litePullConsumer.pause(Collections.singletonList(messageQueue)); litePullConsumer.seekToBegin(messageQueue); Field field = DefaultLitePullConsumerImpl.class.getDeclaredField("assignedMessageQueue"); field.setAccessible(true); @@ -223,6 +229,7 @@ public class DefaultLitePullConsumerTest { List messageQueues = Collections.singletonList(messageQueue); litePullConsumer.assign(messageQueues); litePullConsumer.pause(messageQueues); + litePullConsumer.pause(Collections.singletonList(messageQueue)); litePullConsumer.seekToEnd(messageQueue); Field field = DefaultLitePullConsumerImpl.class.getDeclaredField("assignedMessageQueue"); field.setAccessible(true); @@ -240,6 +247,7 @@ public class DefaultLitePullConsumerTest { List messageQueues = Collections.singletonList(messageQueue); litePullConsumer.assign(messageQueues); litePullConsumer.pause(messageQueues); + litePullConsumer.pause(Collections.singletonList(messageQueue)); try { litePullConsumer.seek(messageQueue, -1); failBecauseExceptionWasNotThrown(MQClientException.class); diff --git a/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java b/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java index 6c8463542fa61e00e7f84fa980c5588dd827ceee..7c3c501e0549b9dbb8694d980b014fb3dd0bd88b 100644 --- a/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java @@ -17,6 +17,7 @@ package org.apache.rocketmq.client.consumer; import java.io.ByteArrayOutputStream; +import java.lang.reflect.Field; import java.net.InetSocketAddress; import java.util.Collections; import java.util.HashSet; @@ -26,6 +27,7 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; @@ -44,17 +46,18 @@ import org.apache.rocketmq.client.impl.consumer.ConsumeMessageConcurrentlyServic import org.apache.rocketmq.client.impl.consumer.ConsumeMessageOrderlyService; import org.apache.rocketmq.client.impl.consumer.DefaultMQPushConsumerImpl; import org.apache.rocketmq.client.impl.consumer.ProcessQueue; +import org.apache.rocketmq.client.impl.consumer.PullAPIWrapper; import org.apache.rocketmq.client.impl.consumer.PullMessageService; import org.apache.rocketmq.client.impl.consumer.PullRequest; import org.apache.rocketmq.client.impl.consumer.PullResultExt; import org.apache.rocketmq.client.impl.consumer.RebalanceImpl; -import org.apache.rocketmq.client.impl.consumer.RebalancePushImpl; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.common.message.MessageClientExt; 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.PullMessageRequestHeader; +import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.exception.RemotingException; import org.junit.After; @@ -71,26 +74,28 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Fail.failBecauseExceptionWasNotThrown; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; -@RunWith(MockitoJUnitRunner.class) +@RunWith(MockitoJUnitRunner.Silent.class) public class DefaultMQPushConsumerTest { private String consumerGroup; private String topic = "FooBar"; private String brokerName = "BrokerA"; private MQClientInstance mQClientFactory; + private final byte[] msgBody = Long.toString(System.currentTimeMillis()).getBytes(); @Mock private MQClientAPIImpl mQClientAPIImpl; + private PullAPIWrapper pullAPIWrapper; private RebalanceImpl rebalanceImpl; - private RebalancePushImpl rebalancePushImpl; private DefaultMQPushConsumer pushConsumer; + private AtomicLong queueOffset = new AtomicLong(1024);; @Before public void init() throws Exception { @@ -98,31 +103,11 @@ public class DefaultMQPushConsumerTest { factoryTable.forEach((s, instance) -> instance.shutdown()); factoryTable.clear(); - when(mQClientAPIImpl.pullMessage(anyString(), any(PullMessageRequestHeader.class), - anyLong(), any(CommunicationMode.class), nullable(PullCallback.class))) - .thenAnswer(new Answer() { - @Override - public PullResult answer(InvocationOnMock mock) throws Throwable { - PullMessageRequestHeader requestHeader = mock.getArgument(1); - MessageClientExt messageClientExt = new MessageClientExt(); - messageClientExt.setTopic(topic); - messageClientExt.setQueueId(0); - messageClientExt.setMsgId("123"); - messageClientExt.setBody(new byte[] {'a'}); - messageClientExt.setOffsetMsgId("234"); - messageClientExt.setBornHost(new InetSocketAddress(8080)); - messageClientExt.setStoreHost(new InetSocketAddress(8080)); - PullResult pullResult = createPullResult(requestHeader, PullStatus.FOUND, Collections.singletonList(messageClientExt)); - ((PullCallback) mock.getArgument(4)).onSuccess(pullResult); - return pullResult; - } - }); - - consumerGroup = "FooBarGroup" + System.currentTimeMillis(); pushConsumer = new DefaultMQPushConsumer(consumerGroup); pushConsumer.setNamesrvAddr("127.0.0.1:9876"); pushConsumer.setPullInterval(60 * 1000); + pushConsumer.setClientRebalance(false); pushConsumer.registerMessageListener(new MessageListenerConcurrently() { @Override @@ -133,28 +118,67 @@ public class DefaultMQPushConsumerTest { }); DefaultMQPushConsumerImpl pushConsumerImpl = pushConsumer.getDefaultMQPushConsumerImpl(); - rebalancePushImpl = spy(new RebalancePushImpl(pushConsumer.getDefaultMQPushConsumerImpl())); // suppress updateTopicRouteInfoFromNameServer pushConsumer.changeInstanceNameToPID(); - mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(pushConsumer, (RPCHook) FieldUtils.readDeclaredField(pushConsumerImpl, "rpcHook", true)); - FieldUtils.writeDeclaredField(mQClientFactory, "mQClientAPIImpl", mQClientAPIImpl, true); - mQClientFactory = spy(mQClientFactory); + mQClientFactory = spy(MQClientManager.getInstance().getOrCreateMQClientInstance(pushConsumer, (RPCHook) FieldUtils.readDeclaredField(pushConsumerImpl, "rpcHook", true))); factoryTable.put(pushConsumer.buildMQClientId(), mQClientFactory); doReturn(false).when(mQClientFactory).updateTopicRouteInfoFromNameServer(anyString()); + doReturn(null).when(mQClientFactory).queryAssignment(anyString(), anyString(), anyString(), any(MessageModel.class), anyInt()); - doReturn(new FindBrokerResult("127.0.0.1:10911", false)).when(mQClientFactory).findBrokerAddressInSubscribe(anyString(), anyLong(), anyBoolean()); - - rebalanceImpl = spy(pushConsumerImpl.getRebalanceImpl()); + rebalanceImpl = spy(pushConsumer.getDefaultMQPushConsumerImpl().getRebalanceImpl()); doReturn(123L).when(rebalanceImpl).computePullFromWhereWithException(any(MessageQueue.class)); - FieldUtils.writeDeclaredField(pushConsumerImpl, "rebalanceImpl", rebalanceImpl, true); + Field field = DefaultMQPushConsumerImpl.class.getDeclaredField("rebalanceImpl"); + field.setAccessible(true); + field.set(pushConsumerImpl, rebalanceImpl); - Set messageQueueSet = new HashSet(); - messageQueueSet.add(createPullRequest().getMessageQueue()); - pushConsumerImpl.updateTopicSubscribeInfo(topic, messageQueueSet); + field = DefaultMQPushConsumerImpl.class.getDeclaredField("doNotUpdateTopicSubscribeInfoWhenSubscriptionChanged"); + field.setAccessible(true); + field.set(null, true); pushConsumer.subscribe(topic, "*"); pushConsumer.start(); + + field = DefaultMQPushConsumerImpl.class.getDeclaredField("mQClientFactory"); + field.setAccessible(true); + field.set(pushConsumerImpl, mQClientFactory); + + field = MQClientInstance.class.getDeclaredField("mQClientAPIImpl"); + field.setAccessible(true); + field.set(mQClientFactory, mQClientAPIImpl); + + pullAPIWrapper = spy(new PullAPIWrapper(mQClientFactory, consumerGroup, false)); + field = DefaultMQPushConsumerImpl.class.getDeclaredField("pullAPIWrapper"); + field.setAccessible(true); + field.set(pushConsumerImpl, pullAPIWrapper); + + mQClientFactory.registerConsumer(consumerGroup, pushConsumerImpl); + + when(mQClientFactory.getMQClientAPIImpl().pullMessage(anyString(), any(PullMessageRequestHeader.class), + anyLong(), any(CommunicationMode.class), nullable(PullCallback.class))) + .thenAnswer(new Answer() { + @Override + public PullResult answer(InvocationOnMock mock) throws Throwable { + PullMessageRequestHeader requestHeader = mock.getArgument(1); + MessageClientExt messageClientExt = new MessageClientExt(); + messageClientExt.setTopic(topic); + messageClientExt.setQueueId(0); + messageClientExt.setQueueOffset(queueOffset.getAndIncrement()); + messageClientExt.setMsgId("1024"); + messageClientExt.setBody(msgBody); + messageClientExt.setOffsetMsgId("234"); + messageClientExt.setBornHost(new InetSocketAddress(8080)); + messageClientExt.setStoreHost(new InetSocketAddress(8080)); + PullResult pullResult = createPullResult(requestHeader, PullStatus.FOUND, Collections.singletonList(messageClientExt)); + ((PullCallback) mock.getArgument(4)).onSuccess(pullResult); + return pullResult; + } + }); + + doReturn(new FindBrokerResult("127.0.0.1:10911", false)).when(mQClientFactory).findBrokerAddressInSubscribe(anyString(), anyLong(), anyBoolean()); + Set messageQueueSet = new HashSet(); + messageQueueSet.add(createPullRequest().getMessageQueue()); + pushConsumer.getDefaultMQPushConsumerImpl().updateTopicSubscribeInfo(topic, messageQueueSet); } @After @@ -187,10 +211,10 @@ public class DefaultMQPushConsumerTest { MessageExt msg = messageAtomic.get(); assertThat(msg).isNotNull(); assertThat(msg.getTopic()).isEqualTo(topic); - assertThat(msg.getBody()).isEqualTo(new byte[] {'a'}); + assertThat(msg.getBody()).isEqualTo(msgBody); } - @Test + @Test(timeout = 20000) public void testPullMessage_SuccessWithOrderlyService() throws Exception { final CountDownLatch countDownLatch = new CountDownLatch(1); final AtomicReference messageAtomic = new AtomicReference<>(); @@ -210,11 +234,11 @@ public class DefaultMQPushConsumerTest { PullMessageService pullMessageService = mQClientFactory.getPullMessageService(); pullMessageService.executePullRequestLater(createPullRequest(), 100); - countDownLatch.await(10, TimeUnit.SECONDS); + countDownLatch.await(); MessageExt msg = messageAtomic.get(); assertThat(msg).isNotNull(); assertThat(msg.getTopic()).isEqualTo(topic); - assertThat(msg.getBody()).isEqualTo(new byte[] {'a'}); + assertThat(msg.getBody()).isEqualTo(msgBody); } @Test @@ -258,7 +282,7 @@ public class DefaultMQPushConsumerTest { } } - @Test + @Test(timeout = 20000) public void testGracefulShutdown() throws InterruptedException, RemotingException, MQBrokerException, MQClientException { final CountDownLatch countDownLatch = new CountDownLatch(1); pushConsumer.setAwaitTerminationMillisWhenShutdown(2000); @@ -267,6 +291,7 @@ public class DefaultMQPushConsumerTest { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { + assertThat(msgs.get(0).getBody()).isEqualTo(msgBody); countDownLatch.countDown(); try { Thread.sleep(1000); @@ -280,7 +305,7 @@ public class DefaultMQPushConsumerTest { PullMessageService pullMessageService = mQClientFactory.getPullMessageService(); pullMessageService.executePullRequestImmediately(createPullRequest()); - assertThat(countDownLatch.await(10, TimeUnit.SECONDS)).isTrue(); + assertThat(countDownLatch.await(30, TimeUnit.SECONDS)).isTrue(); pushConsumer.shutdown(); assertThat(messageConsumedFlag.get()).isTrue(); @@ -301,7 +326,7 @@ public class DefaultMQPushConsumerTest { private PullRequest createPullRequest() { PullRequest pullRequest = new PullRequest(); pullRequest.setConsumerGroup(consumerGroup); - pullRequest.setNextOffset(1024); + pullRequest.setNextOffset(queueOffset.get()); MessageQueue messageQueue = new MessageQueue(); messageQueue.setBrokerName(brokerName); @@ -330,11 +355,11 @@ public class DefaultMQPushConsumerTest { final CountDownLatch countDownLatch = new CountDownLatch(1); final MessageExt[] messageExts = new MessageExt[1]; pushConsumer.getDefaultMQPushConsumerImpl().setConsumeMessageService( - new ConsumeMessageConcurrentlyService(pushConsumer.getDefaultMQPushConsumerImpl(), - (msgs, context) -> { - messageExts[0] = msgs.get(0); - return null; - })); + new ConsumeMessageConcurrentlyService(pushConsumer.getDefaultMQPushConsumerImpl(), + (msgs, context) -> { + messageExts[0] = msgs.get(0); + return null; + })); pushConsumer.getDefaultMQPushConsumerImpl().setConsumeOrderly(true); PullMessageService pullMessageService = mQClientFactory.getPullMessageService(); diff --git a/client/src/test/java/org/apache/rocketmq/client/impl/MQClientAPIImplTest.java b/client/src/test/java/org/apache/rocketmq/client/impl/MQClientAPIImplTest.java index 3f00d9e4030473f395c9b2a037e50b7e6fde24f0..c91d55aa585ba53b7c0dfea20ca42a1445286acd 100644 --- a/client/src/test/java/org/apache/rocketmq/client/impl/MQClientAPIImplTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/impl/MQClientAPIImplTest.java @@ -17,7 +17,18 @@ package org.apache.rocketmq.client.impl; import java.lang.reflect.Field; +import java.net.InetSocketAddress; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CountDownLatch; 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.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.hook.SendMessageContext; @@ -26,12 +37,42 @@ import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendCallback; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.SendStatus; +import org.apache.rocketmq.common.AclConfig; +import org.apache.rocketmq.common.DataVersion; import org.apache.rocketmq.common.PlainAccessConfig; +import org.apache.rocketmq.common.TopicConfig; import org.apache.rocketmq.common.message.Message; 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.MessageQueueAssignment; +import org.apache.rocketmq.common.message.MessageRequestMode; import org.apache.rocketmq.common.protocol.ResponseCode; +import org.apache.rocketmq.common.protocol.body.ClusterAclVersionInfo; +import org.apache.rocketmq.common.protocol.body.QueryAssignmentResponseBody; +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.ExtraInfoUtil; +import org.apache.rocketmq.common.protocol.header.GetBrokerAclConfigResponseHeader; +import org.apache.rocketmq.common.protocol.header.GetBrokerClusterAclConfigResponseBody; +import org.apache.rocketmq.common.protocol.header.GetBrokerClusterAclConfigResponseHeader; +import org.apache.rocketmq.common.protocol.header.GetConsumerListByGroupResponseBody; +import org.apache.rocketmq.common.protocol.header.GetConsumerListByGroupResponseHeader; +import org.apache.rocketmq.common.protocol.header.GetEarliestMsgStoretimeResponseHeader; +import org.apache.rocketmq.common.protocol.header.GetMaxOffsetResponseHeader; +import org.apache.rocketmq.common.protocol.header.GetMinOffsetResponseHeader; +import org.apache.rocketmq.common.protocol.header.PopMessageRequestHeader; +import org.apache.rocketmq.common.protocol.header.PopMessageResponseHeader; +import org.apache.rocketmq.common.protocol.header.QueryConsumerOffsetRequestHeader; +import org.apache.rocketmq.common.protocol.header.QueryConsumerOffsetResponseHeader; +import org.apache.rocketmq.common.protocol.header.SearchOffsetResponseHeader; import org.apache.rocketmq.common.protocol.header.SendMessageRequestHeader; import org.apache.rocketmq.common.protocol.header.SendMessageResponseHeader; +import org.apache.rocketmq.common.protocol.header.UpdateConsumerOffsetRequestHeader; +import org.apache.rocketmq.common.protocol.header.UpdateConsumerOffsetResponseHeader; +import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; +import org.apache.rocketmq.common.subscription.SubscriptionGroupConfig; import org.apache.rocketmq.remoting.InvokeCallback; import org.apache.rocketmq.remoting.RemotingClient; import org.apache.rocketmq.remoting.exception.RemotingException; @@ -39,6 +80,7 @@ import org.apache.rocketmq.remoting.exception.RemotingTimeoutException; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.ResponseFuture; import org.apache.rocketmq.remoting.protocol.RemotingCommand; +import org.assertj.core.api.Assertions; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -67,9 +109,11 @@ public class MQClientAPIImplTest { private String brokerAddr = "127.0.0.1"; private String brokerName = "DefaultBroker"; + private String clusterName = "DefaultCluster"; private static String group = "FooBarGroup"; private static String topic = "FooBar"; private Message msg = new Message("FooBar", new byte[] {}); + private static String clientId = "127.0.0.2@UnitTest"; @Before public void init() throws Exception { @@ -113,7 +157,7 @@ public class MQClientAPIImplTest { @Override public Object answer(InvocationOnMock mock) throws Throwable { RemotingCommand request = mock.getArgument(1); - return createSuccessResponse(request); + return createSendMessageSuccessResponse(request); } }).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong()); @@ -166,7 +210,7 @@ public class MQClientAPIImplTest { InvokeCallback callback = mock.getArgument(3); RemotingCommand request = mock.getArgument(1); ResponseFuture responseFuture = new ResponseFuture(null, request.getOpaque(), 3 * 1000, null, null); - responseFuture.setResponseCommand(createSuccessResponse(request)); + responseFuture.setResponseCommand(createSendMessageSuccessResponse(request)); callback.operationComplete(responseFuture); return null; } @@ -332,7 +376,7 @@ public class MQClientAPIImplTest { InvokeCallback callback = mock.getArgument(3); RemotingCommand request = mock.getArgument(1); ResponseFuture responseFuture = new ResponseFuture(null, request.getOpaque(), 3 * 1000, null, null); - responseFuture.setResponseCommand(createSuccessResponse(request)); + responseFuture.setResponseCommand(createSendMessageSuccessResponse(request)); callback.operationComplete(responseFuture); return null; } @@ -356,6 +400,444 @@ public class MQClientAPIImplTest { }, null, null, 0, sendMessageContext, defaultMQProducerImpl); } + @Test + public void testQueryAssignment_Success() throws Exception { + doAnswer(new Answer() { + @Override + public RemotingCommand answer(InvocationOnMock mock) { + RemotingCommand request = mock.getArgument(1); + + RemotingCommand response = RemotingCommand.createResponseCommand(null); + response.setCode(ResponseCode.SUCCESS); + response.setOpaque(request.getOpaque()); + QueryAssignmentResponseBody b = new QueryAssignmentResponseBody(); + b.setMessageQueueAssignments(Collections.singleton(new MessageQueueAssignment())); + response.setBody(b.encode()); + return response; + } + }).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong()); + Set assignments = mqClientAPI.queryAssignment(brokerAddr, topic, group, clientId, null, MessageModel.CLUSTERING, 10 * 1000); + assertThat(assignments).size().isEqualTo(1); + } + + @Test + public void testPopMessageAsync_Success() throws Exception { + final long popTime = System.currentTimeMillis(); + final int invisibleTime = 10 * 1000; + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock mock) throws Throwable { + InvokeCallback callback = mock.getArgument(3); + RemotingCommand request = mock.getArgument(1); + ResponseFuture responseFuture = new ResponseFuture(null, request.getOpaque(), 3 * 1000, null, null); + RemotingCommand response = RemotingCommand.createResponseCommand(PopMessageResponseHeader.class); + response.setCode(ResponseCode.SUCCESS); + response.setOpaque(request.getOpaque()); + + PopMessageResponseHeader responseHeader = (PopMessageResponseHeader) response.readCustomHeader(); + responseHeader.setInvisibleTime(invisibleTime); + responseHeader.setPopTime(popTime); + responseHeader.setReviveQid(0); + responseHeader.setRestNum(1); + StringBuilder startOffsetInfo = new StringBuilder(64); + ExtraInfoUtil.buildStartOffsetInfo(startOffsetInfo, false, 0, 0L); + responseHeader.setStartOffsetInfo(startOffsetInfo.toString()); + StringBuilder msgOffsetInfo = new StringBuilder(64); + ExtraInfoUtil.buildMsgOffsetInfo(msgOffsetInfo, false, 0, Collections.singletonList(0L)); + responseHeader.setMsgOffsetInfo(msgOffsetInfo.toString()); + response.setRemark("FOUND"); + response.makeCustomHeaderToNet(); + + MessageExt message = new MessageExt(); + message.setQueueId(0); + message.setFlag(12); + message.setQueueOffset(0L); + message.setCommitLogOffset(100L); + message.setSysFlag(0); + message.setBornTimestamp(System.currentTimeMillis()); + message.setBornHost(new InetSocketAddress("127.0.0.1", 10)); + message.setStoreTimestamp(System.currentTimeMillis()); + message.setStoreHost(new InetSocketAddress("127.0.0.1", 11)); + message.setBody("body".getBytes()); + message.setTopic(topic); + message.putUserProperty("key", "value"); + response.setBody(MessageDecoder.encode(message, false)); + responseFuture.setResponseCommand(response); + callback.operationComplete(responseFuture); + return null; + } + }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any(InvokeCallback.class)); + final CountDownLatch done = new CountDownLatch(1); + mqClientAPI.popMessageAsync(brokerName, brokerAddr, new PopMessageRequestHeader(), 10 * 1000, new PopCallback() { + @Override public void onSuccess(PopResult popResult) { + assertThat(popResult.getPopStatus()).isEqualTo(PopStatus.FOUND); + assertThat(popResult.getRestNum()).isEqualTo(1); + assertThat(popResult.getInvisibleTime()).isEqualTo(invisibleTime); + assertThat(popResult.getPopTime()).isEqualTo(popTime); + assertThat(popResult.getMsgFoundList()).size().isEqualTo(1); + done.countDown(); + } + + @Override public void onException(Throwable e) { + Assertions.fail("want no exception but got one", e); + done.countDown(); + } + }); + done.await(); + } + + @Test + public void testAckMessageAsync_Success() throws Exception { + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock mock) throws Throwable { + InvokeCallback callback = mock.getArgument(3); + RemotingCommand request = mock.getArgument(1); + ResponseFuture responseFuture = new ResponseFuture(null, request.getOpaque(), 3 * 1000, null, null); + RemotingCommand response = RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, null); + response.setOpaque(request.getOpaque()); + response.setCode(ResponseCode.SUCCESS); + responseFuture.setResponseCommand(response); + callback.operationComplete(responseFuture); + return null; + } + }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any(InvokeCallback.class)); + + final CountDownLatch done = new CountDownLatch(1); + mqClientAPI.ackMessageAsync(brokerAddr, 10 * 1000, new AckCallback() { + @Override public void onSuccess(AckResult ackResult) { + assertThat(ackResult.getStatus()).isEqualTo(AckStatus.OK); + done.countDown(); + } + + @Override public void onException(Throwable e) { + Assertions.fail("want no exception but got one", e); + done.countDown(); + } + }, new AckMessageRequestHeader()); + done.await(); + } + + @Test + public void testChangeInvisibleTimeAsync_Success() throws Exception { + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock mock) throws Throwable { + InvokeCallback callback = mock.getArgument(3); + RemotingCommand request = mock.getArgument(1); + ResponseFuture responseFuture = new ResponseFuture(null, request.getOpaque(), 3 * 1000, null, null); + RemotingCommand response = RemotingCommand.createResponseCommand(ChangeInvisibleTimeResponseHeader.class); + response.setOpaque(request.getOpaque()); + response.setCode(ResponseCode.SUCCESS); + ChangeInvisibleTimeResponseHeader responseHeader = (ChangeInvisibleTimeResponseHeader) response.readCustomHeader(); + responseHeader.setPopTime(System.currentTimeMillis()); + responseHeader.setInvisibleTime(10 * 1000L); + responseFuture.setResponseCommand(response); + callback.operationComplete(responseFuture); + return null; + } + }).when(remotingClient).invokeAsync(anyString(), any(RemotingCommand.class), anyLong(), any(InvokeCallback.class)); + + final CountDownLatch done = new CountDownLatch(1); + ChangeInvisibleTimeRequestHeader requestHeader = new ChangeInvisibleTimeRequestHeader(); + requestHeader.setTopic(topic); + requestHeader.setQueueId(0); + requestHeader.setOffset(0L); + requestHeader.setInvisibleTime(10 * 1000L); + mqClientAPI.changeInvisibleTimeAsync(brokerName, brokerAddr, requestHeader, 10 * 1000, new AckCallback() { + @Override public void onSuccess(AckResult ackResult) { + assertThat(ackResult.getStatus()).isEqualTo(AckStatus.OK); + done.countDown(); + } + + @Override public void onException(Throwable e) { + Assertions.fail("want no exception but got one", e); + done.countDown(); + } + }); + done.await(); + } + + @Test + public void testSetMessageRequestMode_Success() throws Exception { + doAnswer(new Answer() { + @Override + public RemotingCommand answer(InvocationOnMock mock) { + RemotingCommand request = mock.getArgument(1); + + RemotingCommand response = RemotingCommand.createResponseCommand(null); + response.setCode(ResponseCode.SUCCESS); + response.setOpaque(request.getOpaque()); + return response; + } + }).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong()); + + mqClientAPI.setMessageRequestMode(brokerAddr, topic, group, MessageRequestMode.POP, 8, 10 * 1000L); + } + + @Test + public void testCreateSubscriptionGroup_Success() throws Exception { + doAnswer(new Answer() { + @Override + public RemotingCommand answer(InvocationOnMock mock) { + RemotingCommand request = mock.getArgument(1); + + RemotingCommand response = RemotingCommand.createResponseCommand(null); + response.setCode(ResponseCode.SUCCESS); + response.setOpaque(request.getOpaque()); + return response; + } + }).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong()); + + mqClientAPI.createSubscriptionGroup(brokerAddr, new SubscriptionGroupConfig(), 10000); + } + + @Test + public void testCreateTopic_Success() throws Exception { + doAnswer(new Answer() { + @Override + public RemotingCommand answer(InvocationOnMock mock) { + RemotingCommand request = mock.getArgument(1); + + RemotingCommand response = RemotingCommand.createResponseCommand(null); + response.setCode(ResponseCode.SUCCESS); + response.setOpaque(request.getOpaque()); + return response; + } + }).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong()); + + mqClientAPI.createTopic(brokerAddr, topic, new TopicConfig(), 10000); + } + + @Test + public void testGetBrokerClusterAclInfo() throws Exception { + doAnswer(new Answer() { + @Override + public RemotingCommand answer(InvocationOnMock mock) { + RemotingCommand request = mock.getArgument(1); + + RemotingCommand response = RemotingCommand.createResponseCommand(GetBrokerAclConfigResponseHeader.class); + GetBrokerAclConfigResponseHeader responseHeader = (GetBrokerAclConfigResponseHeader) response.readCustomHeader(); + responseHeader.setVersion(new DataVersion().toJson()); + responseHeader.setBrokerAddr(brokerAddr); + responseHeader.setBrokerName(brokerName); + responseHeader.setClusterName(clusterName); + response.makeCustomHeaderToNet(); + response.setCode(ResponseCode.SUCCESS); + response.setOpaque(request.getOpaque()); + return response; + } + }).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong()); + + ClusterAclVersionInfo info = mqClientAPI.getBrokerClusterAclInfo(brokerAddr, 10000); + assertThat(info.getAclConfigDataVersion().getTimestamp()).isGreaterThan(0); + } + + @Test + public void testGetBrokerClusterConfig() throws Exception { + doAnswer(new Answer() { + @Override + public RemotingCommand answer(InvocationOnMock mock) { + RemotingCommand request = mock.getArgument(1); + + RemotingCommand response = RemotingCommand.createResponseCommand(GetBrokerClusterAclConfigResponseHeader.class); + GetBrokerClusterAclConfigResponseBody body = new GetBrokerClusterAclConfigResponseBody(); + body.setGlobalWhiteAddrs(Collections.singletonList("1.1.1.1")); + body.setPlainAccessConfigs(Collections.singletonList(new PlainAccessConfig())); + response.setBody(body.encode()); + response.makeCustomHeaderToNet(); + response.setCode(ResponseCode.SUCCESS); + response.setOpaque(request.getOpaque()); + return response; + } + }).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong()); + + AclConfig aclConfig = mqClientAPI.getBrokerClusterConfig(brokerAddr, 10000); + assertThat(aclConfig.getPlainAccessConfigs()).size().isGreaterThan(0); + assertThat(aclConfig.getGlobalWhiteAddrs()).size().isGreaterThan(0); + } + + @Test + public void testViewMessage() throws Exception { + doAnswer(new Answer() { + @Override + public RemotingCommand answer(InvocationOnMock mock) throws Exception { + RemotingCommand request = mock.getArgument(1); + + RemotingCommand response = RemotingCommand.createResponseCommand(null); + MessageExt message = new MessageExt(); + message.setQueueId(0); + message.setFlag(12); + message.setQueueOffset(0L); + message.setCommitLogOffset(100L); + message.setSysFlag(0); + message.setBornTimestamp(System.currentTimeMillis()); + message.setBornHost(new InetSocketAddress("127.0.0.1", 10)); + message.setStoreTimestamp(System.currentTimeMillis()); + message.setStoreHost(new InetSocketAddress("127.0.0.1", 11)); + message.setBody("body".getBytes()); + message.setTopic(topic); + message.putUserProperty("key", "value"); + response.setBody(MessageDecoder.encode(message, false)); + response.makeCustomHeaderToNet(); + response.setCode(ResponseCode.SUCCESS); + response.setOpaque(request.getOpaque()); + return response; + } + }).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong()); + + MessageExt messageExt = mqClientAPI.viewMessage(brokerAddr, 100L, 10000); + assertThat(messageExt.getTopic()).isEqualTo(topic); + } + + @Test + public void testSearchOffset() throws Exception { + doAnswer(new Answer() { + @Override + public RemotingCommand answer(InvocationOnMock mock) { + RemotingCommand request = mock.getArgument(1); + + final RemotingCommand response = RemotingCommand.createResponseCommand(SearchOffsetResponseHeader.class); + final SearchOffsetResponseHeader responseHeader = (SearchOffsetResponseHeader) response.readCustomHeader(); + responseHeader.setOffset(100L); + response.makeCustomHeaderToNet(); + response.setCode(ResponseCode.SUCCESS); + response.setOpaque(request.getOpaque()); + return response; + } + }).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong()); + + long offset = mqClientAPI.searchOffset(brokerAddr, topic, 0, System.currentTimeMillis() - 1000, 10000); + assertThat(offset).isEqualTo(100L); + } + + @Test + public void testGetMaxOffset() throws Exception { + doAnswer(new Answer() { + @Override + public RemotingCommand answer(InvocationOnMock mock) { + RemotingCommand request = mock.getArgument(1); + + final RemotingCommand response = RemotingCommand.createResponseCommand(GetMaxOffsetResponseHeader.class); + final GetMaxOffsetResponseHeader responseHeader = (GetMaxOffsetResponseHeader) response.readCustomHeader(); + responseHeader.setOffset(100L); + response.makeCustomHeaderToNet(); + response.setCode(ResponseCode.SUCCESS); + response.setOpaque(request.getOpaque()); + return response; + } + }).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong()); + + long offset = mqClientAPI.getMaxOffset(brokerAddr, topic, 0, 10000); + assertThat(offset).isEqualTo(100L); + } + + @Test + public void testGetMinOffset() throws Exception { + doAnswer(new Answer() { + @Override + public RemotingCommand answer(InvocationOnMock mock) { + RemotingCommand request = mock.getArgument(1); + + final RemotingCommand response = RemotingCommand.createResponseCommand(GetMinOffsetResponseHeader.class); + final GetMinOffsetResponseHeader responseHeader = (GetMinOffsetResponseHeader) response.readCustomHeader(); + responseHeader.setOffset(100L); + response.makeCustomHeaderToNet(); + response.setCode(ResponseCode.SUCCESS); + response.setOpaque(request.getOpaque()); + return response; + } + }).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong()); + + long offset = mqClientAPI.getMinOffset(brokerAddr, topic, 0, 10000); + assertThat(offset).isEqualTo(100L); + } + + @Test + public void testGetEarliestMsgStoretime() throws Exception { + doAnswer(new Answer() { + @Override + public RemotingCommand answer(InvocationOnMock mock) { + RemotingCommand request = mock.getArgument(1); + + final RemotingCommand response = RemotingCommand.createResponseCommand(GetEarliestMsgStoretimeResponseHeader.class); + final GetEarliestMsgStoretimeResponseHeader responseHeader = (GetEarliestMsgStoretimeResponseHeader) response.readCustomHeader(); + responseHeader.setTimestamp(100L); + response.makeCustomHeaderToNet(); + response.setCode(ResponseCode.SUCCESS); + response.setOpaque(request.getOpaque()); + return response; + } + }).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong()); + + long t = mqClientAPI.getEarliestMsgStoretime(brokerAddr, topic, 0, 10000); + assertThat(t).isEqualTo(100L); + } + + @Test + public void testQueryConsumerOffset() throws Exception { + doAnswer(new Answer() { + @Override + public RemotingCommand answer(InvocationOnMock mock) { + RemotingCommand request = mock.getArgument(1); + + final RemotingCommand response = + RemotingCommand.createResponseCommand(QueryConsumerOffsetResponseHeader.class); + final QueryConsumerOffsetResponseHeader responseHeader = + (QueryConsumerOffsetResponseHeader) response.readCustomHeader(); + responseHeader.setOffset(100L); + response.makeCustomHeaderToNet(); + response.setCode(ResponseCode.SUCCESS); + response.setOpaque(request.getOpaque()); + return response; + } + }).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong()); + + long t = mqClientAPI.queryConsumerOffset(brokerAddr, new QueryConsumerOffsetRequestHeader(), 1000); + assertThat(t).isEqualTo(100L); + } + + @Test + public void testUpdateConsumerOffset() throws Exception { + doAnswer(new Answer() { + @Override + public RemotingCommand answer(InvocationOnMock mock) { + RemotingCommand request = mock.getArgument(1); + + final RemotingCommand response = + RemotingCommand.createResponseCommand(UpdateConsumerOffsetResponseHeader.class); + response.makeCustomHeaderToNet(); + response.setCode(ResponseCode.SUCCESS); + response.setOpaque(request.getOpaque()); + return response; + } + }).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong()); + + mqClientAPI.updateConsumerOffset(brokerAddr, new UpdateConsumerOffsetRequestHeader(), 1000); + } + + @Test + public void testGetConsumerIdListByGroup() throws Exception { + doAnswer(new Answer() { + @Override + public RemotingCommand answer(InvocationOnMock mock) { + RemotingCommand request = mock.getArgument(1); + + final RemotingCommand response = + RemotingCommand.createResponseCommand(GetConsumerListByGroupResponseHeader.class); + GetConsumerListByGroupResponseBody body = new GetConsumerListByGroupResponseBody(); + body.setConsumerIdList(Collections.singletonList("consumer1")); + response.setBody(body.encode()); + response.makeCustomHeaderToNet(); + response.setCode(ResponseCode.SUCCESS); + response.setOpaque(request.getOpaque()); + return response; + } + }).when(remotingClient).invokeSync(anyString(), any(RemotingCommand.class), anyLong()); + List consumerIdList = mqClientAPI.getConsumerIdListByGroup(brokerAddr, group, 10000); + assertThat(consumerIdList).size().isGreaterThan(0); + } + private RemotingCommand createResumeSuccessResponse(RemotingCommand request) { RemotingCommand response = RemotingCommand.createResponseCommand(null); response.setCode(ResponseCode.SUCCESS); @@ -363,7 +845,7 @@ public class MQClientAPIImplTest { return response; } - private RemotingCommand createSuccessResponse(RemotingCommand request) { + private RemotingCommand createSendMessageSuccessResponse(RemotingCommand request) { RemotingCommand response = RemotingCommand.createResponseCommand(SendMessageResponseHeader.class); response.setCode(ResponseCode.SUCCESS); response.setOpaque(request.getOpaque()); diff --git a/client/src/test/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPushConsumerImplTest.java b/client/src/test/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPushConsumerImplTest.java index d4f581231f0d4d25eb7a403f9200911ce90549da..ae8d6a991a3942429e10a07788779ab6595a5766 100644 --- a/client/src/test/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPushConsumerImplTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPushConsumerImplTest.java @@ -18,21 +18,42 @@ package org.apache.rocketmq.client.impl.consumer; import java.util.List; +import org.apache.rocketmq.client.ClientConfig; 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.MessageListenerConcurrently; +import org.apache.rocketmq.client.consumer.rebalance.AllocateMessageQueueAveragely; 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.common.UtilAll; +import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.MessageExt; +import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; +import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) public class DefaultMQPushConsumerImplTest { + @Mock + private DefaultMQPushConsumer defaultMQPushConsumer; @Rule public ExpectedException thrown = ExpectedException.none(); + @Test public void checkConfigTest() throws MQClientException { @@ -58,4 +79,52 @@ public class DefaultMQPushConsumerImplTest { DefaultMQPushConsumerImpl defaultMQPushConsumerImpl = new DefaultMQPushConsumerImpl(consumer, null); defaultMQPushConsumerImpl.start(); } + + @Test + public void testHook() throws Exception { + DefaultMQPushConsumerImpl defaultMQPushConsumerImpl = new DefaultMQPushConsumerImpl(defaultMQPushConsumer, null); + defaultMQPushConsumerImpl.registerConsumeMessageHook(new ConsumeMessageHook() { + @Override public String hookName() { + return "consumerHook"; + } + + @Override public void consumeMessageBefore(ConsumeMessageContext context) { + assertThat(context).isNotNull(); + } + + @Override public void consumeMessageAfter(ConsumeMessageContext context) { + assertThat(context).isNotNull(); + } + }); + defaultMQPushConsumerImpl.registerFilterMessageHook(new FilterMessageHook() { + @Override public String hookName() { + return "filterHook"; + } + + @Override public void filterMessage(FilterMessageContext context) { + assertThat(context).isNotNull(); + } + }); + defaultMQPushConsumerImpl.executeHookBefore(new ConsumeMessageContext()); + defaultMQPushConsumerImpl.executeHookAfter(new ConsumeMessageContext()); + } + + @Ignore + @Test + public void testPush() throws Exception { + when(defaultMQPushConsumer.getMessageListener()).thenReturn(new MessageListenerConcurrently() { + @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, + ConsumeConcurrentlyContext context) { + assertThat(msgs).size().isGreaterThan(0); + assertThat(context).isNotNull(); + return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; + } + }); + DefaultMQPushConsumerImpl defaultMQPushConsumerImpl = new DefaultMQPushConsumerImpl(defaultMQPushConsumer, null); + try { + defaultMQPushConsumerImpl.start(); + } finally { + defaultMQPushConsumerImpl.shutdown(); + } + } } diff --git a/client/src/test/java/org/apache/rocketmq/client/impl/consumer/RebalancePushImplTest.java b/client/src/test/java/org/apache/rocketmq/client/impl/consumer/RebalancePushImplTest.java index 796a3943087247c9e5fc75aa5b8fc1cc47c35fb0..17b48eba51d939a0982b5181aeda9a6ef8e2cec0 100644 --- a/client/src/test/java/org/apache/rocketmq/client/impl/consumer/RebalancePushImplTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/impl/consumer/RebalancePushImplTest.java @@ -22,23 +22,29 @@ import java.util.Set; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.rebalance.AllocateMessageQueueAveragely; import org.apache.rocketmq.client.consumer.store.OffsetStore; +import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.impl.factory.MQClientInstance; 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.heartbeat.MessageModel; import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData; +import org.apache.rocketmq.remoting.exception.RemotingException; +import org.apache.rocketmq.remoting.exception.RemotingTimeoutException; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Spy; -import org.mockito.invocation.InvocationOnMock; import org.mockito.junit.MockitoJUnitRunner; -import org.mockito.stubbing.Answer; 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.doAnswer; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) @@ -51,6 +57,12 @@ public class RebalancePushImplTest { private OffsetStore offsetStore; private String consumerGroup = "CID_RebalancePushImplTest"; private String topic = "TopicA"; + private final String brokerName = "BrokerA"; + + @Before + public void before() { + defaultMQPushConsumer.getDefaultMQPushConsumer().setClientRebalance(false); + } @Test public void testMessageQueueChanged_CountThreshold() { @@ -88,16 +100,15 @@ public class RebalancePushImplTest { rebalancePush.subscriptionInner.putIfAbsent(topic, new SubscriptionData()); + try { + when(mqClientInstance.queryAssignment(anyString(), anyString(), anyString(), any(MessageModel.class), anyInt())).thenThrow(new RemotingTimeoutException("unsupported")); + } catch (RemotingException ignored) { + } catch (InterruptedException ignored) { + } catch (MQBrokerException ignored) { + } when(mqClientInstance.findConsumerIdList(anyString(), anyString())).thenReturn(Collections.singletonList(consumerGroup)); when(mqClientInstance.getClientId()).thenReturn(consumerGroup); when(defaultMQPushConsumer.getOffsetStore()).thenReturn(offsetStore); - - doAnswer(new Answer() { - @Override - public Object answer(final InvocationOnMock invocation) throws Throwable { - return null; - } - }).when(defaultMQPushConsumer).executePullRequestImmediately(any(PullRequest.class)); } @Test @@ -134,8 +145,8 @@ public class RebalancePushImplTest { defaultMQPushConsumer.getDefaultMQPushConsumer().setPullThresholdSizeForQueue(1024); defaultMQPushConsumer.getDefaultMQPushConsumer().setPullThresholdForQueue(1024); Set allocateResultSet = new HashSet(); - allocateResultSet.add(new MessageQueue(topic, "BrokerA", 0)); - allocateResultSet.add(new MessageQueue(topic, "BrokerA", 1)); + allocateResultSet.add(new MessageQueue(topic, brokerName, 0)); + allocateResultSet.add(new MessageQueue(topic, brokerName, 1)); doRebalanceForcibly(rebalancePush, allocateResultSet); defaultMQPushConsumer.setConsumeMessageService(new ConsumeMessageConcurrentlyService(defaultMQPushConsumer, null)); @@ -160,4 +171,22 @@ public class RebalancePushImplTest { assertThat(defaultMQPushConsumer.consumerRunningInfo().getProperties().get("pullThresholdSizeForTopic")).isEqualTo("1024"); assertThat(defaultMQPushConsumer.consumerRunningInfo().getProperties().get("pullThresholdForTopic")).isEqualTo("1024"); } + + @Test + public void testDoRebalancePull() throws Exception { + RebalancePushImpl rebalancePush = new RebalancePushImpl(consumerGroup, MessageModel.CLUSTERING, + new AllocateMessageQueueAveragely(), mqClientInstance, defaultMQPushConsumer); + rebalancePush.getSubscriptionInner().putIfAbsent(topic, new SubscriptionData()); + rebalancePush.subscriptionInner.putIfAbsent(topic, new SubscriptionData()); + + when(mqClientInstance.getClientId()).thenReturn(consumerGroup); + when(defaultMQPushConsumer.getOffsetStore()).thenReturn(offsetStore); + doNothing().when(defaultMQPushConsumer).executePullRequestLater(any(PullRequest.class), anyLong()); + MessageQueueAssignment queueAssignment = new MessageQueueAssignment(); + queueAssignment.setMode(MessageRequestMode.PULL); + queueAssignment.setMessageQueue(new MessageQueue(topic, brokerName, 0)); + when(mqClientInstance.queryAssignment(anyString(), anyString(), anyString(), any(MessageModel.class), anyInt())).thenReturn(Collections.singleton(queueAssignment)); + + assertThat(rebalancePush.doRebalance(false)).isTrue(); + } } \ No newline at end of file diff --git a/client/src/test/java/org/apache/rocketmq/client/impl/factory/MQClientInstanceTest.java b/client/src/test/java/org/apache/rocketmq/client/impl/factory/MQClientInstanceTest.java index d37844ca9631ed801779571fd193bcee6135a20b..f7af69b7e710ba506843dc6bdc725476119bde84 100644 --- a/client/src/test/java/org/apache/rocketmq/client/impl/factory/MQClientInstanceTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/impl/factory/MQClientInstanceTest.java @@ -180,4 +180,5 @@ public class MQClientInstanceTest { flag = mqClientInstance.registerAdminExt(group, mock(MQAdminExtInner.class)); assertThat(flag).isTrue(); } + } \ No newline at end of file diff --git a/client/src/test/java/org/apache/rocketmq/client/producer/DefaultMQProducerTest.java b/client/src/test/java/org/apache/rocketmq/client/producer/DefaultMQProducerTest.java index 5f29fe113c5690cde47833638612ab2201f0ada4..6d507269659e5630906de784c351ed1003517d05 100644 --- a/client/src/test/java/org/apache/rocketmq/client/producer/DefaultMQProducerTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/producer/DefaultMQProducerTest.java @@ -48,7 +48,6 @@ import org.apache.rocketmq.common.protocol.route.BrokerData; import org.apache.rocketmq.common.protocol.route.QueueData; import org.apache.rocketmq.common.protocol.route.TopicRouteData; import org.apache.rocketmq.remoting.exception.RemotingException; -import org.apache.rocketmq.remoting.exception.RemotingSendRequestException; import org.apache.rocketmq.remoting.netty.NettyRemotingClient; import org.junit.After; import org.junit.Before; @@ -56,9 +55,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Spy; -import org.mockito.invocation.InvocationOnMock; import org.mockito.junit.MockitoJUnitRunner; -import org.mockito.stubbing.Answer; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Fail.failBecauseExceptionWasNotThrown; @@ -260,7 +257,7 @@ public class DefaultMQProducerTest { } }; - List msgs = new ArrayList<>(); + List msgs = new ArrayList(); for (int i = 0; i < 5; i++) { Message message = new Message(); message.setTopic("test"); diff --git a/client/src/test/resources/org/powermock/extensions/configuration.properties b/client/src/test/resources/org/powermock/extensions/configuration.properties new file mode 100644 index 0000000000000000000000000000000000000000..6389eff4af9f7e701d0321073dc853fe103ba9ca --- /dev/null +++ b/client/src/test/resources/org/powermock/extensions/configuration.properties @@ -0,0 +1,16 @@ +# 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. + +powermock.global-ignore=javax.management.* \ No newline at end of file diff --git a/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java b/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java index d80b3d2169b13e9c8a5a439b02bd726763a20283..488f2132daf457706201b849abea5a9da3de0635 100644 --- a/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java +++ b/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java @@ -21,6 +21,7 @@ import java.net.UnknownHostException; import org.apache.rocketmq.common.annotation.ImportantField; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.constant.PermName; +import org.apache.rocketmq.common.message.MessageRequestMode; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.logging.InternalLogger; import org.apache.rocketmq.logging.InternalLoggerFactory; @@ -62,12 +63,14 @@ public class BrokerConfig { */ private int sendMessageThreadPoolNums = 1; //16 + Runtime.getRuntime().availableProcessors() * 4; private int pullMessageThreadPoolNums = 16 + Runtime.getRuntime().availableProcessors() * 2; + private int ackMessageThreadPoolNums = 3; private int processReplyMessageThreadPoolNums = 16 + Runtime.getRuntime().availableProcessors() * 2; private int queryMessageThreadPoolNums = 8 + Runtime.getRuntime().availableProcessors(); private int adminBrokerThreadPoolNums = 16; private int clientManageThreadPoolNums = 32; private int consumerManageThreadPoolNums = 32; + private int loadBalanceProcessorThreadPoolNums = 32; private int heartbeatThreadPoolNums = Math.min(32, Runtime.getRuntime().availableProcessors()); /** @@ -85,6 +88,7 @@ public class BrokerConfig { private boolean fetchNamesrvAddrByAddressServer = false; private int sendThreadPoolQueueCapacity = 10000; private int pullThreadPoolQueueCapacity = 100000; + private int ackThreadPoolQueueCapacity = 100000; private int replyThreadPoolQueueCapacity = 10000; private int queryThreadPoolQueueCapacity = 20000; private int clientManagerThreadPoolQueueCapacity = 1000000; @@ -158,6 +162,37 @@ public class BrokerConfig { */ private int registerNameServerPeriod = 1000 * 30; + private int popPollingSize = 1024; + private int popPollingMapSize = 100000; + // 20w cost 200M heap memory. + private long maxPopPollingSize = 100000; + private int reviveQueueNum = 8; + private long reviveInterval = 1000; + private long reviveMaxSlow = 3; + private long reviveScanTime = 10000; + private boolean enablePopLog = true; + private boolean enablePopBufferMerge = false; + private int popCkStayBufferTime = 10 * 1000; + private int popCkStayBufferTimeOut = 3 * 1000; + private int popCkMaxBufferSize = 200000; + private int popCkOffsetMaxQueueSize = 20000; + + /** + * the interval of pulling topic information from the named server + */ + private long loadBalancePollNameServerInterval = 1000 * 30; + + /** + * the interval of cleaning + */ + private int cleanOfflineBrokerInterval = 1000 * 30; + + private boolean serverLoadBalancerEnabled = true; + + private MessageRequestMode defaultMessageRequestMode = MessageRequestMode.PULL; + + private int defaultPopShareQueueNum = -1; + /** * The minimum time of the transactional message to be checked firstly, one message only exceed this time interval * that can be checked. @@ -197,6 +232,58 @@ public class BrokerConfig { return "DEFAULT_BROKER"; } + public long getMaxPopPollingSize() { + return maxPopPollingSize; + } + + public int getReviveQueueNum() { + return reviveQueueNum; + } + + public long getReviveInterval() { + return reviveInterval; + } + + public int getPopCkStayBufferTime() { + return popCkStayBufferTime; + } + + public int getPopCkStayBufferTimeOut() { + return popCkStayBufferTimeOut; + } + + public int getPopPollingMapSize() { + return popPollingMapSize; + } + + public long getReviveScanTime() { + return reviveScanTime; + } + + public long getReviveMaxSlow() { + return reviveMaxSlow; + } + + public int getPopPollingSize() { + return popPollingSize; + } + + public boolean isEnablePopBufferMerge() { + return enablePopBufferMerge; + } + + public int getPopCkMaxBufferSize() { + return popCkMaxBufferSize; + } + + public int getPopCkOffsetMaxQueueSize() { + return popCkOffsetMaxQueueSize; + } + + public boolean isEnablePopLog() { + return enablePopLog; + } + public boolean isTraceOn() { return traceOn; } @@ -381,6 +468,14 @@ public class BrokerConfig { this.pullMessageThreadPoolNums = pullMessageThreadPoolNums; } + public int getAckMessageThreadPoolNums() { + return ackMessageThreadPoolNums; + } + + public void setAckMessageThreadPoolNums(int ackMessageThreadPoolNums) { + this.ackMessageThreadPoolNums = ackMessageThreadPoolNums; + } + public int getProcessReplyMessageThreadPoolNums() { return processReplyMessageThreadPoolNums; } @@ -485,6 +580,14 @@ public class BrokerConfig { this.pullThreadPoolQueueCapacity = pullThreadPoolQueueCapacity; } + public int getAckThreadPoolQueueCapacity() { + return ackThreadPoolQueueCapacity; + } + + public void setAckThreadPoolQueueCapacity(int ackThreadPoolQueueCapacity) { + this.ackThreadPoolQueueCapacity = ackThreadPoolQueueCapacity; + } + public int getReplyThreadPoolQueueCapacity() { return replyThreadPoolQueueCapacity; } @@ -804,4 +907,55 @@ public class BrokerConfig { public void setAutoDeleteUnusedStats(boolean autoDeleteUnusedStats) { this.autoDeleteUnusedStats = autoDeleteUnusedStats; } + + + public long getLoadBalancePollNameServerInterval() { + return loadBalancePollNameServerInterval; + } + + public void setLoadBalancePollNameServerInterval(long loadBalancePollNameServerInterval) { + this.loadBalancePollNameServerInterval = loadBalancePollNameServerInterval; + } + + public int getCleanOfflineBrokerInterval() { + return cleanOfflineBrokerInterval; + } + + public void setCleanOfflineBrokerInterval(int cleanOfflineBrokerInterval) { + this.cleanOfflineBrokerInterval = cleanOfflineBrokerInterval; + } + + public int getLoadBalanceProcessorThreadPoolNums() { + return loadBalanceProcessorThreadPoolNums; + } + + public void setLoadBalanceProcessorThreadPoolNums(int loadBalanceProcessorThreadPoolNums) { + this.loadBalanceProcessorThreadPoolNums = loadBalanceProcessorThreadPoolNums; + } + + public boolean isServerLoadBalancerEnabled() { + return serverLoadBalancerEnabled; + } + + public void setServerLoadBalancerEnabled(boolean serverLoadBalancerEnabled) { + this.serverLoadBalancerEnabled = serverLoadBalancerEnabled; + } + + public MessageRequestMode getDefaultMessageRequestMode() { + return defaultMessageRequestMode; + } + + public void setDefaultMessageRequestMode(String defaultMessageRequestMode) { + this.defaultMessageRequestMode = MessageRequestMode.valueOf(defaultMessageRequestMode); + } + + + public int getDefaultPopShareQueueNum() { + return defaultPopShareQueueNum; + } + + + public void setDefaultPopShareQueueNum(int defaultPopShareQueueNum) { + this.defaultPopShareQueueNum = defaultPopShareQueueNum; + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/KeyBuilder.java b/common/src/main/java/org/apache/rocketmq/common/KeyBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..d30789f3dd85166a5daefd4a2183e19747ce257e --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/KeyBuilder.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.common; + +public class KeyBuilder { + public static final int POP_ORDER_REVIVE_QUEUE = 999; + + public static String buildPopRetryTopic(String topic, String cid) { + return MixAll.RETRY_GROUP_TOPIC_PREFIX + cid + "_" + topic; + } + + public static String parseNormalTopic(String topic, String cid) { + if (topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { + return topic.substring((MixAll.RETRY_GROUP_TOPIC_PREFIX + cid + "_").length()); + } else { + return topic; + } + } + + public static String buildPollingKey(String topic, String cid, int queueId) { + return topic + PopAckConstants.SPLIT + cid + PopAckConstants.SPLIT + queueId; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/PopAckConstants.java b/common/src/main/java/org/apache/rocketmq/common/PopAckConstants.java new file mode 100644 index 0000000000000000000000000000000000000000..839f94759831ff078262717b4d0b2011ad028197 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/PopAckConstants.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.common; + +import org.apache.rocketmq.common.topic.TopicValidator; + +public class PopAckConstants { + public static long ackTimeInterval = 1000; + public static final long SECOND = 1000; + + public static long lockTime = 5000; + public static int retryQueueNum = 1; + + public static final String REVIVE_GROUP = MixAll.CID_RMQ_SYS_PREFIX + "REVIVE_GROUP"; + public static final String LOCAL_HOST = "127.0.0.1"; + public static final String REVIVE_TOPIC = TopicValidator.SYSTEM_TOPIC_PREFIX + "REVIVE_LOG_"; + public static final String CK_TAG = "ck"; + public static final String ACK_TAG = "ack"; + public static final String SPLIT = "@"; + +} diff --git a/common/src/main/java/org/apache/rocketmq/common/constant/ConsumeInitMode.java b/common/src/main/java/org/apache/rocketmq/common/constant/ConsumeInitMode.java new file mode 100644 index 0000000000000000000000000000000000000000..b7091fa2767605172aa320fb929f092b830c9b3e --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/constant/ConsumeInitMode.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.common.constant; + +public class ConsumeInitMode { + public static final int MIN = 0; + public static final int MAX = 1; +} diff --git a/common/src/main/java/org/apache/rocketmq/common/constant/LoggerName.java b/common/src/main/java/org/apache/rocketmq/common/constant/LoggerName.java index fe0ae9f17137fd22a13f80d18cfddd0497b0601a..589200b20943ec06e64b213f9185aae2a4060bee 100644 --- a/common/src/main/java/org/apache/rocketmq/common/constant/LoggerName.java +++ b/common/src/main/java/org/apache/rocketmq/common/constant/LoggerName.java @@ -37,4 +37,5 @@ public class LoggerName { public static final String PROTECTION_LOGGER_NAME = "RocketmqProtection"; public static final String WATER_MARK_LOGGER_NAME = "RocketmqWaterMark"; public static final String FILTER_LOGGER_NAME = "RocketmqFilter"; + public static final String ROCKETMQ_POP_LOGGER_NAME = "RocketmqPop"; } diff --git a/common/src/main/java/org/apache/rocketmq/common/message/MessageConst.java b/common/src/main/java/org/apache/rocketmq/common/message/MessageConst.java index 5bdc846562dc6d294f3a4e5cecc4d4bfd5a83099..0922c5f675902e644c4267e581057e3d1f150ce2 100644 --- a/common/src/main/java/org/apache/rocketmq/common/message/MessageConst.java +++ b/common/src/main/java/org/apache/rocketmq/common/message/MessageConst.java @@ -52,6 +52,8 @@ public class MessageConst { public static final String PROPERTY_PUSH_REPLY_TIME = "PUSH_REPLY_TIME"; public static final String PROPERTY_CLUSTER = "CLUSTER"; public static final String PROPERTY_MESSAGE_TYPE = "MSG_TYPE"; + public static final String PROPERTY_POP_CK = "POP_CK"; + public static final String PROPERTY_FIRST_POP_TIME = "1ST_POP_TIME"; public static final String KEY_SEPARATOR = " "; @@ -80,6 +82,8 @@ public class MessageConst { STRING_HASH_SET.add(PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX); STRING_HASH_SET.add(PROPERTY_MAX_RECONSUME_TIMES); STRING_HASH_SET.add(PROPERTY_CONSUME_START_TIMESTAMP); + STRING_HASH_SET.add(PROPERTY_POP_CK); + STRING_HASH_SET.add(PROPERTY_FIRST_POP_TIME); STRING_HASH_SET.add(PROPERTY_INSTANCE_ID); STRING_HASH_SET.add(PROPERTY_CORRELATION_ID); STRING_HASH_SET.add(PROPERTY_MESSAGE_REPLY_TO_CLIENT); diff --git a/common/src/main/java/org/apache/rocketmq/common/message/MessageDecoder.java b/common/src/main/java/org/apache/rocketmq/common/message/MessageDecoder.java index 2936c18eac9d45ff6c34cfa394d150701ed8cee3..9d38be18abf7865a0127a51e65940f48c38f3838 100644 --- a/common/src/main/java/org/apache/rocketmq/common/message/MessageDecoder.java +++ b/common/src/main/java/org/apache/rocketmq/common/message/MessageDecoder.java @@ -37,7 +37,7 @@ public class MessageDecoder { public final static int MESSAGE_MAGIC_CODE_POSTION = 4; public final static int MESSAGE_FLAG_POSTION = 16; public final static int MESSAGE_PHYSIC_OFFSET_POSTION = 28; - // public final static int MESSAGE_STORE_TIMESTAMP_POSTION = 56; + public final static int MESSAGE_STORE_TIMESTAMP_POSITION = 56; public final static int MESSAGE_MAGIC_CODE = -626843481; public static final char NAME_VALUE_SEPARATOR = 1; public static final char PROPERTY_SEPARATOR = 2; diff --git a/common/src/main/java/org/apache/rocketmq/common/message/MessageQueueAssignment.java b/common/src/main/java/org/apache/rocketmq/common/message/MessageQueueAssignment.java new file mode 100644 index 0000000000000000000000000000000000000000..fcd9f5802e245df4ae6ebc191cf7cb144f59c8c4 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/message/MessageQueueAssignment.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.common.message; + +import java.io.Serializable; +import java.util.Map; + +public class MessageQueueAssignment implements Serializable { + + private static final long serialVersionUID = 8092600270527861645L; + + private MessageQueue messageQueue; + + private MessageRequestMode mode = MessageRequestMode.PULL; + + private Map attachments; + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((messageQueue == null) ? 0 : messageQueue.hashCode()); + result = prime * result + ((mode == null) ? 0 : mode.hashCode()); + result = prime * result + ((attachments == null) ? 0 : attachments.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; + MessageQueueAssignment other = (MessageQueueAssignment) obj; + return messageQueue.equals(other.messageQueue); + } + + @Override + public String toString() { + return "MessageQueueAssignment [MessageQueue=" + messageQueue + ", Mode=" + mode + "]"; + } + + public MessageQueue getMessageQueue() { + return messageQueue; + } + + public void setMessageQueue(MessageQueue messageQueue) { + this.messageQueue = messageQueue; + } + + public MessageRequestMode getMode() { + return mode; + } + + public void setMode(MessageRequestMode mode) { + this.mode = mode; + } + + public Map getAttachments() { + return attachments; + } + + public void setAttachments(Map attachments) { + this.attachments = attachments; + } + +} diff --git a/common/src/main/java/org/apache/rocketmq/common/message/MessageRequestMode.java b/common/src/main/java/org/apache/rocketmq/common/message/MessageRequestMode.java new file mode 100644 index 0000000000000000000000000000000000000000..35a166a67617ec3e97a58f58b114d3383d1c3958 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/message/MessageRequestMode.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.common.message; + +/** + * Message Request Mode + */ +public enum MessageRequestMode { + + /** + * pull + */ + PULL("PULL"), + + /** + * pop, consumer working in pop mode could share MessageQueue + */ + POP("POP"); + + private String name; + + MessageRequestMode(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/RequestCode.java b/common/src/main/java/org/apache/rocketmq/common/protocol/RequestCode.java index 75ceff38cfb9b8bbd086007be095012c0d076f8d..9446caa0735f5438ceab518b0c77de279dfd28e1 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/RequestCode.java +++ b/common/src/main/java/org/apache/rocketmq/common/protocol/RequestCode.java @@ -80,6 +80,10 @@ public class RequestCode { public static final int GET_BROKER_CLUSTER_ACL_CONFIG = 54; + public static final int POP_MESSAGE = 200050; + public static final int ACK_MESSAGE = 200051; + public static final int CHANGE_MESSAGE_INVISIBLETIME = 200053; + public static final int PUT_KV_CONFIG = 100; public static final int GET_KV_CONFIG = 101; @@ -188,4 +192,7 @@ public class RequestCode { public static final int SEND_REPLY_MESSAGE_V2 = 325; public static final int PUSH_REPLY_MESSAGE_TO_CLIENT = 326; + + public static final int QUERY_ASSIGNMENT = 400; + public static final int SET_MESSAGE_REQUEST_MODE = 401; } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/ResponseCode.java b/common/src/main/java/org/apache/rocketmq/common/protocol/ResponseCode.java index dc744448f6cf6999f1b8cb79860c8d916de88610..df0ccbe95f9ab40ec84fde547ac9ee2ad8944a88 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/ResponseCode.java +++ b/common/src/main/java/org/apache/rocketmq/common/protocol/ResponseCode.java @@ -80,4 +80,7 @@ public class ResponseCode extends RemotingSysResponseCode { public static final int UPDATE_GLOBAL_WHITE_ADDRS_CONFIG_FAILED = 211; + public static final int POLLING_FULL = 209; + + public static final int POLLING_TIMEOUT = 210; } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/ConsumerRunningInfo.java b/common/src/main/java/org/apache/rocketmq/common/protocol/body/ConsumerRunningInfo.java index d7942eb4a0a26c697abb96d1228b6a8e13655cfb..10d6f4d1612ef26f89ca136bab7f766ca3e4bd4f 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/body/ConsumerRunningInfo.java +++ b/common/src/main/java/org/apache/rocketmq/common/protocol/body/ConsumerRunningInfo.java @@ -41,6 +41,8 @@ public class ConsumerRunningInfo extends RemotingSerializable { private TreeMap mqTable = new TreeMap(); + private TreeMap mqPopTable = new TreeMap(); + private TreeMap statusTable = new TreeMap(); private String jstack; @@ -265,6 +267,28 @@ public class ConsumerRunningInfo extends RemotingSerializable { } } + { + sb.append("\n\n#Consumer Pop Detail#\n"); + sb.append(String.format("%-32s %-32s %-4s %-20s%n", + "#Topic", + "#Broker Name", + "#QID", + "#ProcessQueueInfo" + )); + + Iterator> it = this.mqPopTable.entrySet().iterator(); + while (it.hasNext()) { + Entry next = it.next(); + String item = String.format("%-32s %-32s %-4d %s%n", + next.getKey().getTopic(), + next.getKey().getBrokerName(), + next.getKey().getQueueId(), + next.getValue().toString()); + + sb.append(item); + } + } + { sb.append("\n\n#Consumer RT&TPS#\n"); sb.append(String.format("%-32s %14s %14s %14s %14s %18s %25s%n", @@ -310,4 +334,12 @@ public class ConsumerRunningInfo extends RemotingSerializable { this.jstack = jstack; } + public TreeMap getMqPopTable() { + return mqPopTable; + } + + public void setMqPopTable( + TreeMap mqPopTable) { + this.mqPopTable = mqPopTable; + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/PopProcessQueueInfo.java b/common/src/main/java/org/apache/rocketmq/common/protocol/body/PopProcessQueueInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..b8811bb4e6e723d354dcfa204ae6399a1d870b35 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/protocol/body/PopProcessQueueInfo.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.common.protocol.body; + +public class PopProcessQueueInfo { + private int waitAckCount; + private boolean droped; + private long lastPopTimestamp; + + + public int getWaitAckCount() { + return waitAckCount; + } + + + public void setWaitAckCount(int waitAckCount) { + this.waitAckCount = waitAckCount; + } + + + public boolean isDroped() { + return droped; + } + + + public void setDroped(boolean droped) { + this.droped = droped; + } + + + public long getLastPopTimestamp() { + return lastPopTimestamp; + } + + + public void setLastPopTimestamp(long lastPopTimestamp) { + this.lastPopTimestamp = lastPopTimestamp; + } + + @Override + public String toString() { + return "PopProcessQueueInfo [waitAckCount:" + waitAckCount + + ", droped:" + droped + ", lastPopTimestamp:" + lastPopTimestamp + "]"; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/QueryAssignmentRequestBody.java b/common/src/main/java/org/apache/rocketmq/common/protocol/body/QueryAssignmentRequestBody.java new file mode 100644 index 0000000000000000000000000000000000000000..6d0285b18aef30aaa6ce686ef970da7c3d7caed2 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/protocol/body/QueryAssignmentRequestBody.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.rocketmq.common.protocol.body; + +import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; +import org.apache.rocketmq.remoting.protocol.RemotingSerializable; + +public class QueryAssignmentRequestBody extends RemotingSerializable { + + private String topic; + + private String consumerGroup; + + private String clientId; + + private String strategyName; + + private MessageModel messageModel; + + public String getTopic() { + return topic; + } + + public void setTopic(String topic) { + this.topic = topic; + } + + public String getConsumerGroup() { + return consumerGroup; + } + + public void setConsumerGroup(String consumerGroup) { + this.consumerGroup = consumerGroup; + } + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public String getStrategyName() { + return strategyName; + } + + public void setStrategyName(String strategyName) { + this.strategyName = strategyName; + } + + public MessageModel getMessageModel() { + return messageModel; + } + + public void setMessageModel(MessageModel messageModel) { + this.messageModel = messageModel; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/QueryAssignmentResponseBody.java b/common/src/main/java/org/apache/rocketmq/common/protocol/body/QueryAssignmentResponseBody.java new file mode 100644 index 0000000000000000000000000000000000000000..688737d1ab096681847d8e06af346aea3201282f --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/protocol/body/QueryAssignmentResponseBody.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.common.protocol.body; + +import java.util.Set; +import org.apache.rocketmq.common.message.MessageQueueAssignment; +import org.apache.rocketmq.remoting.protocol.RemotingSerializable; + +public class QueryAssignmentResponseBody extends RemotingSerializable { + + private Set messageQueueAssignments; + + public Set getMessageQueueAssignments() { + return messageQueueAssignments; + } + + public void setMessageQueueAssignments( + Set messageQueueAssignments) { + this.messageQueueAssignments = messageQueueAssignments; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/body/SetMessageRequestModeRequestBody.java b/common/src/main/java/org/apache/rocketmq/common/protocol/body/SetMessageRequestModeRequestBody.java new file mode 100644 index 0000000000000000000000000000000000000000..309f7ae309688cacc07e9bb0fd7156ee0475945f --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/protocol/body/SetMessageRequestModeRequestBody.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.rocketmq.common.protocol.body; + +import org.apache.rocketmq.common.message.MessageRequestMode; +import org.apache.rocketmq.remoting.protocol.RemotingSerializable; + +public class SetMessageRequestModeRequestBody extends RemotingSerializable { + + private String topic; + + private String consumerGroup; + + private MessageRequestMode mode = MessageRequestMode.PULL; + + /* + consumer working in pop mode could share the MessageQueues assigned to the N (N = popShareQueueNum) consumers following it in the cid list + */ + private int popShareQueueNum = 0; + + public SetMessageRequestModeRequestBody() { + } + + public String getTopic() { + return topic; + } + + public void setTopic(String topic) { + this.topic = topic; + } + + public String getConsumerGroup() { + return consumerGroup; + } + + public void setConsumerGroup(String consumerGroup) { + this.consumerGroup = consumerGroup; + } + + public MessageRequestMode getMode() { + return mode; + } + + public void setMode(MessageRequestMode mode) { + this.mode = mode; + } + + public int getPopShareQueueNum() { + return popShareQueueNum; + } + + public void setPopShareQueueNum(int popShareQueueNum) { + this.popShareQueueNum = popShareQueueNum; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/AckMessageRequestHeader.java b/common/src/main/java/org/apache/rocketmq/common/protocol/header/AckMessageRequestHeader.java new file mode 100644 index 0000000000000000000000000000000000000000..02e388ba4cac4031847380d97bc2f3c06134d1a4 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/protocol/header/AckMessageRequestHeader.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.common.protocol.header; + +import org.apache.rocketmq.remoting.CommandCustomHeader; +import org.apache.rocketmq.remoting.annotation.CFNotNull; +import org.apache.rocketmq.remoting.exception.RemotingCommandException; + +public class AckMessageRequestHeader implements CommandCustomHeader { + @CFNotNull + private String consumerGroup; + @CFNotNull + private String topic; + @CFNotNull + private Integer queueId; + @CFNotNull + private String extraInfo; + + @CFNotNull + private Long offset; + + + @Override + public void checkFields() throws RemotingCommandException { + } + + public void setOffset(Long offset) { + this.offset = offset; + } + + public Long getOffset() { + return offset; + } + + public String getConsumerGroup() { + return consumerGroup; + } + + public void setExtraInfo(String extraInfo) { + this.extraInfo = extraInfo; + } + + public String getExtraInfo() { + return extraInfo; + } + + public void setConsumerGroup(String consumerGroup) { + this.consumerGroup = consumerGroup; + } + + public String getTopic() { + return topic; + } + + public void setTopic(String topic) { + this.topic = topic; + } + + public Integer getQueueId() { + return queueId; + } + + public void setQueueId(Integer queueId) { + this.queueId = queueId; + } + + @Override + public String toString() { + return topic + "," + this.consumerGroup + "," + this.queueId + "," + this.offset + "," + this.extraInfo; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/ChangeInvisibleTimeRequestHeader.java b/common/src/main/java/org/apache/rocketmq/common/protocol/header/ChangeInvisibleTimeRequestHeader.java new file mode 100644 index 0000000000000000000000000000000000000000..a586e490cf28745344ef272385fa76662786d59d --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/protocol/header/ChangeInvisibleTimeRequestHeader.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.common.protocol.header; + +import org.apache.rocketmq.remoting.CommandCustomHeader; +import org.apache.rocketmq.remoting.annotation.CFNotNull; +import org.apache.rocketmq.remoting.exception.RemotingCommandException; + +public class ChangeInvisibleTimeRequestHeader implements CommandCustomHeader { + @CFNotNull + private String consumerGroup; + @CFNotNull + private String topic; + @CFNotNull + private Integer queueId; + /** + * startOffset popTime invisibleTime queueId + */ + @CFNotNull + private String extraInfo; + + @CFNotNull + private Long offset; + + @CFNotNull + private Long invisibleTime; + + @Override + public void checkFields() throws RemotingCommandException { + } + + public void setOffset(Long offset) { + this.offset = offset; + } + + public Long getOffset() { + return offset; + } + + public Long getInvisibleTime() { + return invisibleTime; + } + + public void setInvisibleTime(Long invisibleTime) { + this.invisibleTime = invisibleTime; + } + + public String getConsumerGroup() { + return consumerGroup; + } + + public void setExtraInfo(String extraInfo) { + this.extraInfo = extraInfo; + } + + /** + * startOffset popTime invisibleTime queueId + */ + public String getExtraInfo() { + return extraInfo; + } + + public void setConsumerGroup(String consumerGroup) { + this.consumerGroup = consumerGroup; + } + + public String getTopic() { + return topic; + } + + public void setTopic(String topic) { + this.topic = topic; + } + + public Integer getQueueId() { + return queueId; + } + + public void setQueueId(Integer queueId) { + this.queueId = queueId; + } + +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/ChangeInvisibleTimeResponseHeader.java b/common/src/main/java/org/apache/rocketmq/common/protocol/header/ChangeInvisibleTimeResponseHeader.java new file mode 100644 index 0000000000000000000000000000000000000000..2ebabb7672921624e017e27d13ed8004f395c078 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/protocol/header/ChangeInvisibleTimeResponseHeader.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.common.protocol.header; + +import org.apache.rocketmq.remoting.CommandCustomHeader; +import org.apache.rocketmq.remoting.annotation.CFNotNull; +import org.apache.rocketmq.remoting.exception.RemotingCommandException; + +public class ChangeInvisibleTimeResponseHeader implements CommandCustomHeader { + + + @CFNotNull + private long popTime; + @CFNotNull + private long invisibleTime; + + @CFNotNull + private int reviveQid; + + @Override + public void checkFields() throws RemotingCommandException { + } + + public long getPopTime() { + return popTime; + } + + public void setPopTime(long popTime) { + this.popTime = popTime; + } + + public long getInvisibleTime() { + return invisibleTime; + } + + public void setInvisibleTime(long invisibleTime) { + this.invisibleTime = invisibleTime; + } + + public int getReviveQid() { + return reviveQid; + } + + public void setReviveQid(int reviveQid) { + this.reviveQid = reviveQid; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/ExtraInfoUtil.java b/common/src/main/java/org/apache/rocketmq/common/protocol/header/ExtraInfoUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..19f37f6cd1849dad18caf57f69407a8947349cee --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/protocol/header/ExtraInfoUtil.java @@ -0,0 +1,258 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.common.protocol.header; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.rocketmq.common.KeyBuilder; +import org.apache.rocketmq.common.MixAll; +import org.apache.rocketmq.common.message.MessageConst; + +public class ExtraInfoUtil { + private static final String NORMAL_TOPIC = "0"; + private static final String RETRY_TOPIC = "1"; + + public static String[] split(String extraInfo) { + if (extraInfo == null) { + throw new IllegalArgumentException("split extraInfo is null"); + } + return extraInfo.split(MessageConst.KEY_SEPARATOR); + } + + public static Long getCkQueueOffset(String[] extraInfoStrs) { + if (extraInfoStrs == null || extraInfoStrs.length < 1) { + throw new IllegalArgumentException("getCkQueueOffset fail, extraInfoStrs length " + (extraInfoStrs == null ? 0 : extraInfoStrs.length)); + } + return Long.valueOf(extraInfoStrs[0]); + } + + public static Long getPopTime(String[] extraInfoStrs) { + if (extraInfoStrs == null || extraInfoStrs.length < 2) { + throw new IllegalArgumentException("getPopTime fail, extraInfoStrs length " + (extraInfoStrs == null ? 0 : extraInfoStrs.length)); + } + return Long.valueOf(extraInfoStrs[1]); + } + + public static Long getInvisibleTime(String[] extraInfoStrs) { + if (extraInfoStrs == null || extraInfoStrs.length < 3) { + throw new IllegalArgumentException("getInvisibleTime fail, extraInfoStrs length " + (extraInfoStrs == null ? 0 : extraInfoStrs.length)); + } + return Long.valueOf(extraInfoStrs[2]); + } + + public static int getReviveQid(String[] extraInfoStrs) { + if (extraInfoStrs == null || extraInfoStrs.length < 4) { + throw new IllegalArgumentException("getReviveQid fail, extraInfoStrs length " + (extraInfoStrs == null ? 0 : extraInfoStrs.length)); + } + return Integer.valueOf(extraInfoStrs[3]); + } + + public static String getRealTopic(String[] extraInfoStrs, String topic, String cid) { + if (extraInfoStrs == null || extraInfoStrs.length < 5) { + throw new IllegalArgumentException("getRealTopic fail, extraInfoStrs length " + (extraInfoStrs == null ? 0 : extraInfoStrs.length)); + } + if (RETRY_TOPIC.equals(extraInfoStrs[4])) { + return KeyBuilder.buildPopRetryTopic(topic, cid); + } else { + return topic; + } + } + + public static String getBrokerName(String[] extraInfoStrs) { + if (extraInfoStrs == null || extraInfoStrs.length < 6) { + throw new IllegalArgumentException("getBrokerName fail, extraInfoStrs length " + (extraInfoStrs == null ? 0 : extraInfoStrs.length)); + } + return extraInfoStrs[5]; + } + + public static int getQueueId(String[] extraInfoStrs) { + if (extraInfoStrs == null || extraInfoStrs.length < 7) { + throw new IllegalArgumentException("getQueueId fail, extraInfoStrs length " + (extraInfoStrs == null ? 0 : extraInfoStrs.length)); + } + return Integer.valueOf(extraInfoStrs[6]); + } + + public static long getQueueOffset(String[] extraInfoStrs) { + if (extraInfoStrs == null || extraInfoStrs.length < 8) { + throw new IllegalArgumentException("getQueueOffset fail, extraInfoStrs length " + (extraInfoStrs == null ? 0 : extraInfoStrs.length)); + } + return Long.valueOf(extraInfoStrs[7]); + } + + public static String buildExtraInfo(long ckQueueOffset, long popTime, long invisibleTime, int reviveQid, String topic, String brokerName, int queueId) { + String t = NORMAL_TOPIC; + if (topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { + t = RETRY_TOPIC; + } + return ckQueueOffset + MessageConst.KEY_SEPARATOR + popTime + MessageConst.KEY_SEPARATOR + invisibleTime + MessageConst.KEY_SEPARATOR + reviveQid + MessageConst.KEY_SEPARATOR + t + + MessageConst.KEY_SEPARATOR + brokerName + MessageConst.KEY_SEPARATOR + queueId; + } + + public static String buildExtraInfo(long ckQueueOffset, long popTime, long invisibleTime, int reviveQid, String topic, String brokerName, int queueId, + long msgQueueOffset) { + String t = NORMAL_TOPIC; + if (topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { + t = RETRY_TOPIC; + } + return ckQueueOffset + + MessageConst.KEY_SEPARATOR + popTime + MessageConst.KEY_SEPARATOR + invisibleTime + + MessageConst.KEY_SEPARATOR + reviveQid + MessageConst.KEY_SEPARATOR + t + + MessageConst.KEY_SEPARATOR + brokerName + MessageConst.KEY_SEPARATOR + queueId + + MessageConst.KEY_SEPARATOR + msgQueueOffset; + } + + public static void buildStartOffsetInfo(StringBuilder stringBuilder, boolean retry, int queueId, long startOffset) { + if (stringBuilder == null) { + stringBuilder = new StringBuilder(64); + } + + if (stringBuilder.length() > 0) { + stringBuilder.append(";"); + } + + stringBuilder.append(retry ? RETRY_TOPIC : NORMAL_TOPIC) + .append(MessageConst.KEY_SEPARATOR).append(queueId) + .append(MessageConst.KEY_SEPARATOR).append(startOffset); + } + + public static void buildOrderCountInfo(StringBuilder stringBuilder, boolean retry, int queueId, int orderCount) { + if (stringBuilder == null) { + stringBuilder = new StringBuilder(64); + } + + if (stringBuilder.length() > 0) { + stringBuilder.append(";"); + } + + stringBuilder.append(retry ? RETRY_TOPIC : NORMAL_TOPIC) + .append(MessageConst.KEY_SEPARATOR).append(queueId) + .append(MessageConst.KEY_SEPARATOR).append(orderCount); + } + + public static void buildMsgOffsetInfo(StringBuilder stringBuilder, boolean retry, int queueId, List msgOffsets) { + if (stringBuilder == null) { + stringBuilder = new StringBuilder(64); + } + + if (stringBuilder.length() > 0) { + stringBuilder.append(";"); + } + + stringBuilder.append(retry ? RETRY_TOPIC : NORMAL_TOPIC) + .append(MessageConst.KEY_SEPARATOR).append(queueId) + .append(MessageConst.KEY_SEPARATOR); + + for (int i = 0; i < msgOffsets.size(); i++) { + stringBuilder.append(msgOffsets.get(i)); + if (i < msgOffsets.size() - 1) { + stringBuilder.append(","); + } + } + } + + public static Map> parseMsgOffsetInfo(String msgOffsetInfo) { + if (msgOffsetInfo == null || msgOffsetInfo.length() == 0) { + return null; + } + + Map> msgOffsetMap = new HashMap>(4); + String[] array; + if (msgOffsetInfo.indexOf(";") < 0) { + array = new String[]{msgOffsetInfo}; + } else { + array = msgOffsetInfo.split(";"); + } + + for (String one : array) { + String[] split = one.split(MessageConst.KEY_SEPARATOR); + if (split.length != 3) { + throw new IllegalArgumentException("parse msgOffsetMap error, " + msgOffsetMap); + } + String key = split[0] + "@" + split[1]; + if (msgOffsetMap.containsKey(key)) { + throw new IllegalArgumentException("parse msgOffsetMap error, duplicate, " + msgOffsetMap); + } + msgOffsetMap.put(key, new ArrayList(8)); + String[] msgOffsets = split[2].split(","); + for (String msgOffset : msgOffsets) { + msgOffsetMap.get(key).add(Long.valueOf(msgOffset)); + } + } + + return msgOffsetMap; + } + + public static Map parseStartOffsetInfo(String startOffsetInfo) { + if (startOffsetInfo == null || startOffsetInfo.length() == 0) { + return null; + } + Map startOffsetMap = new HashMap(4); + String[] array; + if (startOffsetInfo.indexOf(";") < 0) { + array = new String[]{startOffsetInfo}; + } else { + array = startOffsetInfo.split(";"); + } + + for (String one : array) { + String[] split = one.split(MessageConst.KEY_SEPARATOR); + if (split.length != 3) { + throw new IllegalArgumentException("parse startOffsetInfo error, " + startOffsetInfo); + } + String key = split[0] + "@" + split[1]; + if (startOffsetMap.containsKey(key)) { + throw new IllegalArgumentException("parse startOffsetInfo error, duplicate, " + startOffsetInfo); + } + startOffsetMap.put(key, Long.valueOf(split[2])); + } + + return startOffsetMap; + } + + public static Map parseOrderCountInfo(String orderCountInfo) { + if (orderCountInfo == null || orderCountInfo.length() == 0) { + return null; + } + Map startOffsetMap = new HashMap(4); + String[] array; + if (orderCountInfo.indexOf(";") < 0) { + array = new String[]{orderCountInfo}; + } else { + array = orderCountInfo.split(";"); + } + + for (String one : array) { + String[] split = one.split(MessageConst.KEY_SEPARATOR); + if (split.length != 3) { + throw new IllegalArgumentException("parse orderCountInfo error, " + orderCountInfo); + } + String key = split[0] + "@" + split[1]; + if (startOffsetMap.containsKey(key)) { + throw new IllegalArgumentException("parse orderCountInfo error, duplicate, " + orderCountInfo); + } + startOffsetMap.put(key, Integer.valueOf(split[2])); + } + + return startOffsetMap; + } + + public static String getStartOffsetInfoMapKey(String topic, int queueId) { + return (topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX) ? RETRY_TOPIC : NORMAL_TOPIC) + "@" + queueId; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/PopMessageRequestHeader.java b/common/src/main/java/org/apache/rocketmq/common/protocol/header/PopMessageRequestHeader.java new file mode 100644 index 0000000000000000000000000000000000000000..4d151a23e05c978eb20a13002759888099e489ce --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/protocol/header/PopMessageRequestHeader.java @@ -0,0 +1,155 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.common.protocol.header; + +import org.apache.rocketmq.remoting.CommandCustomHeader; +import org.apache.rocketmq.remoting.annotation.CFNotNull; +import org.apache.rocketmq.remoting.exception.RemotingCommandException; + +public class PopMessageRequestHeader implements CommandCustomHeader { + @CFNotNull + private String consumerGroup; + @CFNotNull + private String topic; + @CFNotNull + private int queueId; + @CFNotNull + private int maxMsgNums; + @CFNotNull + private long invisibleTime; + @CFNotNull + private long pollTime; + @CFNotNull + private long bornTime; + @CFNotNull + private int initMode; + + private String expType; + private String exp; + + /** + * marked as order consume, if true + * 1. not commit offset + * 2. not pop retry, because no retry + * 3. not append check point, because no retry + */ + private Boolean order = Boolean.FALSE; + + @Override + public void checkFields() throws RemotingCommandException { + } + + public void setInitMode(int initMode) { + this.initMode = initMode; + } + + public int getInitMode() { + return initMode; + } + + public long getInvisibleTime() { + return invisibleTime; + } + + public void setInvisibleTime(long invisibleTime) { + this.invisibleTime = invisibleTime; + } + + public long getPollTime() { + return pollTime; + } + + public void setPollTime(long pollTime) { + this.pollTime = pollTime; + } + + public String getConsumerGroup() { + return consumerGroup; + } + + public void setConsumerGroup(String consumerGroup) { + this.consumerGroup = consumerGroup; + } + + public long getBornTime() { + return bornTime; + } + + public void setBornTime(long bornTime) { + this.bornTime = bornTime; + } + + public String getTopic() { + return topic; + } + + public void setTopic(String topic) { + this.topic = topic; + } + + public int getQueueId() { + if (queueId < 0) { + return -1; + } + return queueId; + } + + public void setQueueId(int queueId) { + this.queueId = queueId; + } + + + public int getMaxMsgNums() { + return maxMsgNums; + } + + public void setMaxMsgNums(int maxMsgNums) { + this.maxMsgNums = maxMsgNums; + } + + public boolean isTimeoutTooMuch() { + return System.currentTimeMillis() - bornTime - pollTime > 500; + } + + public String getExpType() { + return expType; + } + + public void setExpType(String expType) { + this.expType = expType; + } + + public String getExp() { + return exp; + } + + public void setExp(String exp) { + this.exp = exp; + } + + public Boolean getOrder() { + return order; + } + + public void setOrder(Boolean order) { + this.order = order; + } + + public boolean isOrder() { + return this.order != null && this.order.booleanValue(); + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/PopMessageResponseHeader.java b/common/src/main/java/org/apache/rocketmq/common/protocol/header/PopMessageResponseHeader.java new file mode 100644 index 0000000000000000000000000000000000000000..09867f3e1a323ca2e80c9b474b2ab90064cc4ea6 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/protocol/header/PopMessageResponseHeader.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.common.protocol.header; + +import org.apache.rocketmq.remoting.CommandCustomHeader; +import org.apache.rocketmq.remoting.annotation.CFNotNull; +import org.apache.rocketmq.remoting.exception.RemotingCommandException; + +public class PopMessageResponseHeader implements CommandCustomHeader { + + + @CFNotNull + private long popTime; + @CFNotNull + private long invisibleTime; + + @CFNotNull + private int reviveQid; + /** + * the rest num in queue + */ + @CFNotNull + private long restNum; + + private String startOffsetInfo; + private String msgOffsetInfo; + private String orderCountInfo; + + @Override + public void checkFields() throws RemotingCommandException { + } + + public long getPopTime() { + return popTime; + } + + public void setPopTime(long popTime) { + this.popTime = popTime; + } + + public long getInvisibleTime() { + return invisibleTime; + } + + public long getRestNum() { + return restNum; + } + + public void setRestNum(long restNum) { + this.restNum = restNum; + } + + public void setInvisibleTime(long invisibleTime) { + this.invisibleTime = invisibleTime; + } + + public int getReviveQid() { + return reviveQid; + } + + public void setReviveQid(int reviveQid) { + this.reviveQid = reviveQid; + } + + public String getStartOffsetInfo() { + return startOffsetInfo; + } + + public void setStartOffsetInfo(String startOffsetInfo) { + this.startOffsetInfo = startOffsetInfo; + } + + public String getMsgOffsetInfo() { + return msgOffsetInfo; + } + + public void setMsgOffsetInfo(String msgOffsetInfo) { + this.msgOffsetInfo = msgOffsetInfo; + } + + public String getOrderCountInfo() { + return orderCountInfo; + } + + public void setOrderCountInfo(String orderCountInfo) { + this.orderCountInfo = orderCountInfo; + } +} diff --git a/common/src/main/java/org/apache/rocketmq/common/utils/DataConverter.java b/common/src/main/java/org/apache/rocketmq/common/utils/DataConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..8b50de12be854aea1566d04b99fa05f41e9d6703 --- /dev/null +++ b/common/src/main/java/org/apache/rocketmq/common/utils/DataConverter.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.common.utils; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; + +public class DataConverter { + public static Charset charset = Charset.forName("UTF-8"); + + public static byte[] Long2Byte(Long v) { + ByteBuffer tmp = ByteBuffer.allocate(8); + tmp.putLong(v); + return tmp.array(); + } + + public static int setBit(int value, int index, boolean flag) { + if (flag) { + return (int) (value | (1L << index)); + } else { + return (int) (value & ~(1L << index)); + } + } + + public static boolean getBit(int value, int index) { + return (value & (1L << index)) != 0; + } +} diff --git a/distribution/conf/logback_broker.xml b/distribution/conf/logback_broker.xml index 9d1a6b17618da6644c7b16a55c03e86bc2e3d109..2e4f9c668bf76299de732c304d5619ca73ac4b51 100644 --- a/distribution/conf/logback_broker.xml +++ b/distribution/conf/logback_broker.xml @@ -257,6 +257,30 @@ + + ${user.home}/logs/rocketmqlogs/pop.log + true + + ${user.home}/logs/rocketmqlogs/otherdays/pop.%i.log + + 1 + 20 + + + 128MB + + + %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n + UTF-8 + + + + + + + true @@ -330,6 +354,11 @@ + + + + + diff --git a/docs/en/Feature.md b/docs/en/Feature.md index 5945588de4db6920eb1a34a6f95a49a283be4c03..8d3f4a639f9e1bc430181a53f2c4d88edb976b04 100644 --- a/docs/en/Feature.md +++ b/docs/en/Feature.md @@ -83,4 +83,9 @@ The result of consumer flow control is to reduce the pull frequency. ## 12 Dead Letter Queue Dead letter queue is used to deal messages that cannot be consumed normally. When a message is consumed failed at first time, the message queue will automatically resend the message. If the consumption still fails after the maximum number retry, it indicates that the consumer cannot properly consume the message under normal circumstances. At this time, the message queue will not immediately abandon the message, but send it to the special queue corresponding to the consumer. -RocketMQ defines the messages that could not be consumed under normal circumstances as Dead-Letter Messages, and the special queue in which the Dead-Letter Messages are saved as Dead-Letter Queues. In RocketMQ, the consumer instance can consume again by resending messages in the Dead-Letter Queue using console. \ No newline at end of file +RocketMQ defines the messages that could not be consumed under normal circumstances as Dead-Letter Messages, and the special queue in which the Dead-Letter Messages are saved as Dead-Letter Queues. In RocketMQ, the consumer instance can consume again by resending messages in the Dead-Letter Queue using console. + +## 13 Pop Consuming + +Pop consuming refers to that broker fetches messages from queues owned by same broker and returns to clients, which ensures one queue will be consumed by multiple clients. The whole behavior is like a queue +pop process. By invoking `setConsumeMode` sub command of mqadmin, one consumer group can be switch to POP consuming instead of classical PULL consuming without changing a single code line. The new pop consuming will help to mitigate the impact for one queue consuming of an abnormal behaving client. \ No newline at end of file diff --git a/example/pom.xml b/example/pom.xml index 81f7515dbe332d8bbb6798bc2beb28f4f0e647b6..b392d9a10c1b1cd80d8f1d93808c8b4b044808d4 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -44,6 +44,10 @@ ${project.groupId} rocketmq-acl + + ${project.groupId} + rocketmq-tools + ch.qos.logback logback-classic diff --git a/example/src/main/java/org/apache/rocketmq/example/simple/PopPushConsumer.java b/example/src/main/java/org/apache/rocketmq/example/simple/PopPushConsumer.java new file mode 100644 index 0000000000000000000000000000000000000000..d7a2c70afb1f99cb09c742d0e7bd7e9edb7192d3 --- /dev/null +++ b/example/src/main/java/org/apache/rocketmq/example/simple/PopPushConsumer.java @@ -0,0 +1,62 @@ +/* + * 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.example.simple; + +import java.util.Set; +import java.util.stream.Collectors; +import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; +import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; +import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; +import org.apache.rocketmq.common.consumer.ConsumeFromWhere; +import org.apache.rocketmq.common.message.MessageRequestMode; +import org.apache.rocketmq.common.protocol.body.ClusterInfo; +import org.apache.rocketmq.common.protocol.route.BrokerData; +import org.apache.rocketmq.tools.admin.DefaultMQAdminExt; + +public class PopPushConsumer { + + public static final String CONSUMER_GROUP = "CID_JODIE_1"; + public static final String TOPIC = "TopicTest"; + + // Or use AdminTools directly: mqadmin setConsumeMode -c cluster -t topic -g group -m POP -n 8 + private static void switchPop() throws Exception { + DefaultMQAdminExt mqAdminExt = new DefaultMQAdminExt(); + mqAdminExt.start(); + + ClusterInfo clusterInfo = mqAdminExt.examineBrokerClusterInfo(); + Set brokerAddrs = clusterInfo.getBrokerAddrTable().values().stream().map(BrokerData::selectBrokerAddr).collect(Collectors.toSet()); + + for (String brokerAddr : brokerAddrs) { + mqAdminExt.setMessageRequestMode(brokerAddr, TOPIC, CONSUMER_GROUP, MessageRequestMode.POP, 8, 3_000); + } + } + + public static void main(String[] args) throws Exception { + switchPop(); + + DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(CONSUMER_GROUP); + consumer.subscribe(TOPIC, "*"); + consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET); + consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { + System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs); + return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; + }); + consumer.setClientRebalance(false); + consumer.start(); + System.out.printf("Consumer Started.%n"); + } +} diff --git a/pom.xml b/pom.xml index b6f1770e2d85c6295c02f5077926e158d1d8f872..c6e039e583c874465e192147d6420d9c013cef00 100644 --- a/pom.xml +++ b/pom.xml @@ -564,6 +564,11 @@ guava 19.0 + + com.googlecode.concurrentlinkedhashmap + concurrentlinkedhashmap-lru + 1.4.2 + io.openmessaging openmessaging-api diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/common/RemotingHelper.java b/remoting/src/main/java/org/apache/rocketmq/remoting/common/RemotingHelper.java index f244bf4c853551ad8b4810f95a78b039ae6d239b..34f8b76a097518dc679940db72041e7186fff213 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/common/RemotingHelper.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/common/RemotingHelper.java @@ -172,11 +172,18 @@ public class RemotingHelper { public static String parseSocketAddressAddr(SocketAddress socketAddress) { if (socketAddress != null) { + // Default toString of InetSocketAddress is "hostName/IP:port" final String addr = socketAddress.toString(); - if (addr.length() > 0) { + if (addr.contains("/")) { + String[] segments = addr.split("/"); + if (segments.length > 1) { + return segments[1]; + } + } return addr.substring(1); } + return addr; } return ""; } diff --git a/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java b/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java index b8ecdee8cc63141876bf6b8f0b2efb3f811479b1..c25a1147328644bf2ea23c69755c49b0036a7028 100644 --- a/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java +++ b/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java @@ -672,7 +672,7 @@ public class DefaultMessageStore implements MessageStore { } this.storeStatsService.getGetMessageTransferedMsgCount().incrementAndGet(); - getResult.addMessage(selectResult); + getResult.addMessage(selectResult, offset + (i / ConsumeQueue.CQ_STORE_UNIT_SIZE)); status = GetMessageStatus.FOUND; nextPhyFileStartOffset = Long.MIN_VALUE; } @@ -1496,6 +1496,7 @@ public class DefaultMessageStore implements MessageStore { return haService; } + @Override public ScheduleMessageService getScheduleMessageService() { return scheduleMessageService; } diff --git a/store/src/main/java/org/apache/rocketmq/store/GetMessageResult.java b/store/src/main/java/org/apache/rocketmq/store/GetMessageResult.java index 996e24d8058cf9b88ec18c2c456e4af11e755fcd..6fcb3101a139ac8d5b1ef96866288b8f513801ca 100644 --- a/store/src/main/java/org/apache/rocketmq/store/GetMessageResult.java +++ b/store/src/main/java/org/apache/rocketmq/store/GetMessageResult.java @@ -27,6 +27,7 @@ public class GetMessageResult { new ArrayList(100); private final List messageBufferList = new ArrayList(100); + private final List messageQueueOffset = new ArrayList<>(100); private GetMessageStatus status; private long nextBeginOffset; @@ -90,6 +91,11 @@ public class GetMessageResult { mapedBuffer.getSize() / BrokerStatsManager.SIZE_PER_COUNT); } + public void addMessage(final SelectMappedBufferResult mapedBuffer, final long queueOffset) { + addMessage(mapedBuffer); + this.messageQueueOffset.add(queueOffset); + } + public void release() { for (SelectMappedBufferResult select : this.messageMapedList) { select.release(); @@ -124,6 +130,10 @@ public class GetMessageResult { this.msgCount4Commercial = msgCount4Commercial; } + public List getMessageQueueOffset() { + return messageQueueOffset; + } + @Override public String toString() { return "GetMessageResult [status=" + status + ", nextBeginOffset=" + nextBeginOffset + ", minOffset=" diff --git a/store/src/main/java/org/apache/rocketmq/store/MessageStore.java b/store/src/main/java/org/apache/rocketmq/store/MessageStore.java index 64eb5250de6e59f7ce3ef19033cd40b5d5f0254f..0cea607677d4200e77885710168018357c7608f0 100644 --- a/store/src/main/java/org/apache/rocketmq/store/MessageStore.java +++ b/store/src/main/java/org/apache/rocketmq/store/MessageStore.java @@ -20,10 +20,10 @@ import java.util.HashMap; import java.util.LinkedList; import java.util.Set; import java.util.concurrent.CompletableFuture; - import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBatch; import org.apache.rocketmq.store.config.BrokerRole; +import org.apache.rocketmq.store.schedule.ScheduleMessageService; import org.apache.rocketmq.store.stats.BrokerStatsManager; /** @@ -383,6 +383,8 @@ public interface MessageStore { */ ConsumeQueue getConsumeQueue(String topic, int queueId); + ScheduleMessageService getScheduleMessageService(); + /** * Get BrokerStatsManager of the messageStore. * diff --git a/store/src/main/java/org/apache/rocketmq/store/pop/AckMsg.java b/store/src/main/java/org/apache/rocketmq/store/pop/AckMsg.java new file mode 100644 index 0000000000000000000000000000000000000000..ab017a951cd8871ded56035287c63cab72dd64de --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/pop/AckMsg.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.store.pop; + +public class AckMsg { + private long ackOffset; + private long startOffset; + private String consumerGroup; + private String topic; + private int queueId; + private long popTime; + + public long getPopTime() { + return popTime; + } + + public void setPopTime(long popTime) { + this.popTime = popTime; + } + + public void setQueueId(int queueId) { + this.queueId = queueId; + } + + public int getQueueId() { + return queueId; + } + + public void setTopic(String topic) { + this.topic = topic; + } + + public String getTopic() { + return topic; + } + + public long getAckOffset() { + return ackOffset; + } + + public String getConsumerGroup() { + return consumerGroup; + } + + public void setConsumerGroup(String consumerGroup) { + this.consumerGroup = consumerGroup; + } + + public void setAckOffset(long ackOffset) { + this.ackOffset = ackOffset; + } + + public long getStartOffset() { + return startOffset; + } + + public void setStartOffset(long startOffset) { + this.startOffset = startOffset; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("AckMsg{"); + sb.append("ackOffset=").append(ackOffset); + sb.append(", startOffset=").append(startOffset); + sb.append(", consumerGroup='").append(consumerGroup).append('\''); + sb.append(", topic='").append(topic).append('\''); + sb.append(", queueId=").append(queueId); + sb.append(", popTime=").append(popTime); + sb.append('}'); + return sb.toString(); + } +} diff --git a/store/src/main/java/org/apache/rocketmq/store/pop/PopCheckPoint.java b/store/src/main/java/org/apache/rocketmq/store/pop/PopCheckPoint.java new file mode 100644 index 0000000000000000000000000000000000000000..a4a3aac0057ef3a8422498f6eaa23ab499ea3b7e --- /dev/null +++ b/store/src/main/java/org/apache/rocketmq/store/pop/PopCheckPoint.java @@ -0,0 +1,174 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.store.pop; + +import com.alibaba.fastjson.annotation.JSONField; +import java.util.ArrayList; +import java.util.List; + +public class PopCheckPoint { + @JSONField(name = "so") + private long startOffset; + @JSONField(name = "pt") + private long popTime; + @JSONField(name = "it") + private long invisibleTime; + @JSONField(name = "bm") + private int bitMap; + @JSONField(name = "n") + private byte num; + @JSONField(name = "q") + private byte queueId; + @JSONField(name = "t") + private String topic; + @JSONField(name = "c") + private String cid; + @JSONField(name = "ro") + private long reviveOffset; + @JSONField(name = "d") + private List queueOffsetDiff; + + public long getReviveOffset() { + return reviveOffset; + } + + public void setReviveOffset(long reviveOffset) { + this.reviveOffset = reviveOffset; + } + + public long getStartOffset() { + return startOffset; + } + + public void setStartOffset(long startOffset) { + this.startOffset = startOffset; + } + + public void getStartOffset(long startOffset) { + this.startOffset = startOffset; + } + + public void setPopTime(long popTime) { + this.popTime = popTime; + } + + public void setInvisibleTime(long invisibleTime) { + this.invisibleTime = invisibleTime; + } + + public long getPopTime() { + return popTime; + } + + public long getInvisibleTime() { + return invisibleTime; + } + + public long getReviveTime() { + return popTime + invisibleTime; + } + + public int getBitMap() { + return bitMap; + } + + public void setBitMap(int bitMap) { + this.bitMap = bitMap; + } + + public byte getNum() { + return num; + } + + public void setNum(byte num) { + this.num = num; + } + + public byte getQueueId() { + return queueId; + } + + public void setQueueId(byte queueId) { + this.queueId = queueId; + } + + public String getTopic() { + return topic; + } + + public void setTopic(String topic) { + this.topic = topic; + } + + public String getCId() { + return cid; + } + + public void setCId(String cid) { + this.cid = cid; + } + + public List getQueueOffsetDiff() { + return queueOffsetDiff; + } + + public void setQueueOffsetDiff(List queueOffsetDiff) { + this.queueOffsetDiff = queueOffsetDiff; + } + + public void addDiff(int diff) { + if (this.queueOffsetDiff == null) { + this.queueOffsetDiff = new ArrayList<>(8); + } + this.queueOffsetDiff.add(diff); + } + + public int indexOfAck(long ackOffset) { + if (ackOffset < startOffset) { + return -1; + } + + // old version of checkpoint + if (queueOffsetDiff == null || queueOffsetDiff.isEmpty()) { + + if (ackOffset - startOffset < num) { + return (int) (ackOffset - startOffset); + } + + return -1; + } + + // new version of checkpoint + return queueOffsetDiff.indexOf((int) (ackOffset - startOffset)); + } + + public long ackOffsetByIndex(byte index) { + // old version of checkpoint + if (queueOffsetDiff == null || queueOffsetDiff.isEmpty()) { + return startOffset + index; + } + + return startOffset + queueOffsetDiff.get(index); + } + + @Override + public String toString() { + return "PopCheckPoint [topic=" + topic + ", cid=" + cid + ", queueId=" + queueId + ", startOffset=" + startOffset + ", bitMap=" + bitMap + ", num=" + num + ", reviveTime=" + getReviveTime() + + ", reviveOffset=" + reviveOffset + ", diff=" + queueOffsetDiff + "]"; + } + +} diff --git a/store/src/main/java/org/apache/rocketmq/store/schedule/ScheduleMessageService.java b/store/src/main/java/org/apache/rocketmq/store/schedule/ScheduleMessageService.java index bacae1e80bcfb6d7455a88ed493efb270cd595d1..9057ebefdfe49b870f46b4cd593f87c9f885d085 100644 --- a/store/src/main/java/org/apache/rocketmq/store/schedule/ScheduleMessageService.java +++ b/store/src/main/java/org/apache/rocketmq/store/schedule/ScheduleMessageService.java @@ -16,25 +16,28 @@ */ package org.apache.rocketmq.store.schedule; +import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; import org.apache.rocketmq.common.ConfigManager; import org.apache.rocketmq.common.TopicFilterType; import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.common.topic.TopicValidator; -import org.apache.rocketmq.logging.InternalLogger; -import org.apache.rocketmq.logging.InternalLoggerFactory; 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.running.RunningStats; +import org.apache.rocketmq.common.topic.TopicValidator; +import org.apache.rocketmq.logging.InternalLogger; +import org.apache.rocketmq.logging.InternalLoggerFactory; import org.apache.rocketmq.store.ConsumeQueue; import org.apache.rocketmq.store.ConsumeQueueExt; import org.apache.rocketmq.store.DefaultMessageStore; @@ -222,6 +225,17 @@ public class ScheduleMessageService extends ConfigManager { return true; } + public int computeDelayLevel(long timeMillis) { + long intervalMillis = timeMillis - System.currentTimeMillis(); + List> sortedLevels = delayLevelTable.entrySet().stream().sorted(Comparator.comparingLong(Map.Entry::getValue)).collect(Collectors.toList()); + for (Map.Entry entry : sortedLevels) { + if (entry.getValue() > intervalMillis) { + return entry.getKey(); + } + } + return sortedLevels.get(sortedLevels.size() - 1).getKey(); + } + class DeliverDelayedMessageTimerTask extends TimerTask { private final int delayLevel; private final long offset; diff --git a/test/src/main/java/org/apache/rocketmq/test/client/rmq/RMQPopConsumer.java b/test/src/main/java/org/apache/rocketmq/test/client/rmq/RMQPopConsumer.java new file mode 100644 index 0000000000000000000000000000000000000000..036f60e1db1285f9783c6ec6b50a5e24ccdfa51f --- /dev/null +++ b/test/src/main/java/org/apache/rocketmq/test/client/rmq/RMQPopConsumer.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.rocketmq.test.client.rmq; + +import org.apache.rocketmq.test.listener.AbstractListener; + +public class RMQPopConsumer extends RMQNormalConsumer { + public RMQPopConsumer(String nsAddr, String topic, String subExpression, + String consumerGroup, AbstractListener listner) { + super(nsAddr, topic, subExpression, consumerGroup, listner); + } + + @Override + public void create() { + super.create(); + consumer.setClientRebalance(false); + } +} diff --git a/test/src/main/java/org/apache/rocketmq/test/factory/ConsumerFactory.java b/test/src/main/java/org/apache/rocketmq/test/factory/ConsumerFactory.java index 48508462668e8f523d5e5ab6e389c93d9a3e49ef..d530db98b0fff59679ea277b251335162f1d5366 100644 --- a/test/src/main/java/org/apache/rocketmq/test/factory/ConsumerFactory.java +++ b/test/src/main/java/org/apache/rocketmq/test/factory/ConsumerFactory.java @@ -22,6 +22,7 @@ import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer; import org.apache.rocketmq.client.consumer.MessageSelector; import org.apache.rocketmq.test.client.rmq.RMQBroadCastConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; +import org.apache.rocketmq.test.client.rmq.RMQPopConsumer; import org.apache.rocketmq.test.client.rmq.RMQSqlConsumer; import org.apache.rocketmq.test.listener.AbstractListener; @@ -62,6 +63,15 @@ public class ConsumerFactory { consumer.start(); return consumer; } + public static RMQPopConsumer getRMQPopConsumer(String nsAddr, String consumerGroup, + String topic, String subExpression, + AbstractListener listener) { + RMQPopConsumer consumer = new RMQPopConsumer(nsAddr, topic, subExpression, + consumerGroup, listener); + consumer.create(); + consumer.start(); + return consumer; + } public static DefaultMQPullConsumer getRMQPullConsumer(String nsAddr, String consumerGroup) throws Exception { DefaultMQPullConsumer defaultMQPullConsumer = new DefaultMQPullConsumer(consumerGroup); diff --git a/test/src/test/java/org/apache/rocketmq/test/base/IntegrationTestBase.java b/test/src/test/java/org/apache/rocketmq/test/base/IntegrationTestBase.java index c484e87c11b5380fdfb20d4e3cd7275440c26253..82420105e07079796e1b7e3a6a046fedcce74fd2 100644 --- a/test/src/test/java/org/apache/rocketmq/test/base/IntegrationTestBase.java +++ b/test/src/test/java/org/apache/rocketmq/test/base/IntegrationTestBase.java @@ -17,6 +17,7 @@ package org.apache.rocketmq.test.base; +import com.google.common.truth.Truth; import java.io.File; import java.util.ArrayList; import java.util.List; @@ -26,16 +27,15 @@ import java.util.concurrent.atomic.AtomicInteger; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.UtilAll; +import org.apache.rocketmq.common.namesrv.NamesrvConfig; import org.apache.rocketmq.logging.InternalLogger; import org.apache.rocketmq.logging.InternalLoggerFactory; -import org.apache.rocketmq.common.namesrv.NamesrvConfig; import org.apache.rocketmq.namesrv.NamesrvController; import org.apache.rocketmq.remoting.netty.NettyClientConfig; import org.apache.rocketmq.remoting.netty.NettyServerConfig; import org.apache.rocketmq.store.config.MessageStoreConfig; import org.apache.rocketmq.test.util.MQAdmin; import org.apache.rocketmq.test.util.TestUtils; -import org.junit.Assert; public class IntegrationTestBase { public static InternalLogger logger = InternalLoggerFactory.getLogger(IntegrationTestBase.class); @@ -113,7 +113,7 @@ public class IntegrationTestBase { nameServerNettyServerConfig.setListenPort(nextPort()); NamesrvController namesrvController = new NamesrvController(namesrvConfig, nameServerNettyServerConfig); try { - Assert.assertTrue(namesrvController.initialize()); + Truth.assertThat(namesrvController.initialize()).isTrue(); logger.info("Name Server Start:{}", nameServerNettyServerConfig.getListenPort()); namesrvController.start(); } catch (Exception e) { @@ -133,6 +133,7 @@ public class IntegrationTestBase { brokerConfig.setBrokerIP1("127.0.0.1"); brokerConfig.setNamesrvAddr(nsAddr); brokerConfig.setEnablePropertyFilter(true); + brokerConfig.setLoadBalancePollNameServerInterval(500); storeConfig.setStorePathRootDir(baseDir); storeConfig.setStorePathCommitLog(baseDir + SEP + "commitlog"); storeConfig.setMappedFileSizeCommitLog(COMMIT_LOG_SIZE); @@ -149,7 +150,7 @@ public class IntegrationTestBase { storeConfig.setHaListenPort(nextPort()); BrokerController brokerController = new BrokerController(brokerConfig, nettyServerConfig, nettyClientConfig, storeConfig); try { - Assert.assertTrue(brokerController.initialize()); + Truth.assertThat(brokerController.initialize()).isTrue(); logger.info("Broker Start name:{} addr:{}", brokerConfig.getBrokerName(), brokerController.getBrokerAddr()); brokerController.start(); } catch (Throwable t) { @@ -169,8 +170,8 @@ public class IntegrationTestBase { if (createResult) { break; } else if (System.currentTimeMillis() - startTime > topicCreateTime) { - Assert.fail(String.format("topic[%s] is created failed after:%d ms", topic, - System.currentTimeMillis() - startTime)); + Truth.assertWithMessage(String.format("topic[%s] is created failed after:%d ms", topic, + System.currentTimeMillis() - startTime)).fail(); break; } else { TestUtils.waitForMoment(500); diff --git a/test/src/test/java/org/apache/rocketmq/test/client/consumer/balance/NormalMsgStaticBalanceIT.java b/test/src/test/java/org/apache/rocketmq/test/client/consumer/balance/NormalMsgStaticBalanceIT.java index 4c31f6a6cef7fb55c1db6b7a3a6520be1f56974e..7b2f09ed029a53c70a02b0c575f47b26cd88f9a0 100644 --- a/test/src/test/java/org/apache/rocketmq/test/client/consumer/balance/NormalMsgStaticBalanceIT.java +++ b/test/src/test/java/org/apache/rocketmq/test/client/consumer/balance/NormalMsgStaticBalanceIT.java @@ -17,7 +17,6 @@ package org.apache.rocketmq.test.client.consumer.balance; -import org.apache.log4j.Logger; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; @@ -29,11 +28,13 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import static com.google.common.truth.Truth.assertThat; public class NormalMsgStaticBalanceIT extends BaseConf { - private static Logger logger = Logger.getLogger(NormalMsgStaticBalanceIT.class); + private static Logger logger = LoggerFactory.getLogger(NormalMsgStaticBalanceIT.class); private RMQNormalProducer producer = null; private String topic = null; @@ -75,13 +76,12 @@ public class NormalMsgStaticBalanceIT extends BaseConf { @Test public void testFourConsumersBalance() { int msgSize = 600; - RMQNormalConsumer consumer1 = getConsumer(nsAddr, topic, "*", new RMQNormalListener()); - RMQNormalConsumer consumer2 = getConsumer(nsAddr, consumer1.getConsumerGroup(), topic, - "*", new RMQNormalListener()); - RMQNormalConsumer consumer3 = getConsumer(nsAddr, consumer1.getConsumerGroup(), topic, - "*", new RMQNormalListener()); - RMQNormalConsumer consumer4 = getConsumer(nsAddr, consumer1.getConsumerGroup(), topic, - "*", new RMQNormalListener()); + String consumerGroup = initConsumerGroup(); + logger.info("use group: {}", consumerGroup); + RMQNormalConsumer consumer1 = getConsumer(nsAddr, consumerGroup, topic, "*", new RMQNormalListener()); + RMQNormalConsumer consumer2 = getConsumer(nsAddr, consumerGroup, topic, "*", new RMQNormalListener()); + RMQNormalConsumer consumer3 = getConsumer(nsAddr, consumerGroup, topic, "*", new RMQNormalListener()); + RMQNormalConsumer consumer4 = getConsumer(nsAddr, consumerGroup, topic, "*", new RMQNormalListener()); TestUtils.waitForSeconds(waitTime); producer.send(msgSize); diff --git a/test/src/test/java/org/apache/rocketmq/test/client/consumer/pop/PopSubCheckIT.java b/test/src/test/java/org/apache/rocketmq/test/client/consumer/pop/PopSubCheckIT.java new file mode 100644 index 0000000000000000000000000000000000000000..df020c1121efbc012fe96b7545e58cd334938362 --- /dev/null +++ b/test/src/test/java/org/apache/rocketmq/test/client/consumer/pop/PopSubCheckIT.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.rocketmq.test.client.consumer.pop; + +import org.apache.rocketmq.common.message.MessageClientExt; +import org.apache.rocketmq.common.message.MessageConst; +import org.apache.rocketmq.common.message.MessageRequestMode; +import org.apache.rocketmq.logging.inner.Logger; +import org.apache.rocketmq.test.base.BaseConf; +import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; +import org.apache.rocketmq.test.client.rmq.RMQPopConsumer; +import org.apache.rocketmq.test.factory.ConsumerFactory; +import org.apache.rocketmq.test.listener.rmq.concurrent.RMQNormalListener; +import org.apache.rocketmq.test.util.RandomUtil; +import org.apache.rocketmq.test.util.VerifyUtils; +import org.apache.rocketmq.tools.admin.DefaultMQAdminExt; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import static com.google.common.truth.Truth.assertThat; + +public class PopSubCheckIT extends BaseConf { + private static Logger logger = Logger.getLogger(PopSubCheckIT.class); + private String group; + + private DefaultMQAdminExt defaultMQAdminExt; + + @Before + public void setUp() throws Exception { + group = initConsumerGroup(); + + defaultMQAdminExt = new DefaultMQAdminExt(); + defaultMQAdminExt.setInstanceName(RandomUtil.getStringByUUID()); + defaultMQAdminExt.start(); + } + + @After + public void tearDown() { + defaultMQAdminExt.shutdown(); + super.shutdown(); + } + + @Test + public void testNormalPopAck() throws Exception { + String topic = initTopic(); + logger.info(String.format("use topic: %s; group: %s !", topic, group)); + + RMQNormalProducer producer = getProducer(nsAddr, topic); + producer.getProducer().setCompressMsgBodyOverHowmuch(Integer.MAX_VALUE); + + for (String brokerAddr : new String[]{brokerController1.getBrokerAddr(), brokerController2.getBrokerAddr()}) { + defaultMQAdminExt.setMessageRequestMode(brokerAddr, topic, group, MessageRequestMode.POP, 8, 60_000); + } + + RMQPopConsumer consumer = ConsumerFactory.getRMQPopConsumer(nsAddr, group, + topic, "*", new RMQNormalListener()); + mqClients.add(consumer); + + int msgNum = 1; + producer.send(msgNum); + Assert.assertEquals("Not all sent succeeded", msgNum, producer.getAllUndupMsgBody().size()); + logger.info(producer.getFirstMsg()); + + consumer.getListener().waitForMessageConsume(msgNum, 30_000); + assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) + .containsExactlyElementsIn(producer.getAllMsgBody()); + for (Object o : consumer.getListener().getAllOriginMsg()) { + MessageClientExt msg = (MessageClientExt) o; + assertThat(msg.getProperty(MessageConst.PROPERTY_POP_CK)).named("check pop meta").isNotEmpty(); + } + + consumer.getListener().waitForMessageConsume(msgNum, 3_000 * 9); + assertThat(consumer.getListener().getAllOriginMsg().size()).isEqualTo(msgNum); + } +} diff --git a/test/src/test/java/org/apache/rocketmq/test/smoke/NormalMessageSendAndRecvIT.java b/test/src/test/java/org/apache/rocketmq/test/smoke/NormalMessageSendAndRecvIT.java index c7886554a705df0b26adb90981df091abea289db..81dc864d1701cd144b4940e1404d17a029d19c9a 100644 --- a/test/src/test/java/org/apache/rocketmq/test/smoke/NormalMessageSendAndRecvIT.java +++ b/test/src/test/java/org/apache/rocketmq/test/smoke/NormalMessageSendAndRecvIT.java @@ -18,6 +18,8 @@ package org.apache.rocketmq.test.smoke; import org.apache.log4j.Logger; +import org.apache.rocketmq.common.message.MessageClientExt; +import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.test.base.BaseConf; import org.apache.rocketmq.test.client.rmq.RMQNormalConsumer; import org.apache.rocketmq.test.client.rmq.RMQNormalProducer; @@ -58,5 +60,9 @@ public class NormalMessageSendAndRecvIT extends BaseConf { assertThat(VerifyUtils.getFilterdMessage(producer.getAllMsgBody(), consumer.getListener().getAllMsgBody())) .containsExactlyElementsIn(producer.getAllMsgBody()); + for (Object o : consumer.getListener().getAllOriginMsg()) { + MessageClientExt msg = (MessageClientExt) o; + assertThat(msg.getProperty(MessageConst.PROPERTY_POP_CK)).isNull(); + } } } diff --git a/tools/src/main/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExt.java b/tools/src/main/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExt.java index 6592639035f922a362f7503342d34f7ff06ac11b..c27c85c66de2ae226038de951308e0251ed06f18 100644 --- a/tools/src/main/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExt.java +++ b/tools/src/main/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExt.java @@ -33,6 +33,7 @@ import org.apache.rocketmq.common.admin.RollbackStats; import org.apache.rocketmq.common.admin.TopicStatsTable; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; +import org.apache.rocketmq.common.message.MessageRequestMode; import org.apache.rocketmq.common.protocol.body.BrokerStatsData; import org.apache.rocketmq.common.protocol.body.ClusterAclVersionInfo; import org.apache.rocketmq.common.protocol.body.ClusterInfo; @@ -559,14 +560,22 @@ public class DefaultMQAdminExt extends ClientConfig implements MQAdminExt { @Override public boolean resumeCheckHalfMessage(String msgId) - throws RemotingException, MQClientException, InterruptedException, MQBrokerException { + throws RemotingException, MQClientException, InterruptedException, MQBrokerException { return this.defaultMQAdminExtImpl.resumeCheckHalfMessage(msgId); } @Override public boolean resumeCheckHalfMessage(String topic, - String msgId) - throws RemotingException, MQClientException, InterruptedException, MQBrokerException { + String msgId) + throws RemotingException, MQClientException, InterruptedException, MQBrokerException { return this.defaultMQAdminExtImpl.resumeCheckHalfMessage(topic, msgId); } + + @Override + 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 { + this.defaultMQAdminExtImpl.setMessageRequestMode(brokerAddr, topic, consumerGroup, mode, popShareQueueNum, timeoutMillis); + } } diff --git a/tools/src/main/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExtImpl.java b/tools/src/main/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExtImpl.java index 8930bbe49d207ab2dc361caaee6d88f75762cbea..8ae68cdecbedf40865f2c49f2c07b37689cd18b7 100644 --- a/tools/src/main/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExtImpl.java +++ b/tools/src/main/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExtImpl.java @@ -37,11 +37,11 @@ import org.apache.rocketmq.client.exception.MQClientException; 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.common.AclConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.PlainAccessConfig; import org.apache.rocketmq.common.ServiceState; import org.apache.rocketmq.common.TopicConfig; -import org.apache.rocketmq.common.AclConfig; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.admin.ConsumeStats; import org.apache.rocketmq.common.admin.OffsetWrapper; @@ -49,16 +49,16 @@ import org.apache.rocketmq.common.admin.RollbackStats; import org.apache.rocketmq.common.admin.TopicOffset; import org.apache.rocketmq.common.admin.TopicStatsTable; import org.apache.rocketmq.common.help.FAQUrl; -import org.apache.rocketmq.common.protocol.body.ClusterAclVersionInfo; -import org.apache.rocketmq.logging.InternalLogger; import org.apache.rocketmq.common.message.MessageClientExt; 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.MessageRequestMode; import org.apache.rocketmq.common.namesrv.NamesrvUtil; import org.apache.rocketmq.common.protocol.ResponseCode; import org.apache.rocketmq.common.protocol.body.BrokerStatsData; +import org.apache.rocketmq.common.protocol.body.ClusterAclVersionInfo; import org.apache.rocketmq.common.protocol.body.ClusterInfo; import org.apache.rocketmq.common.protocol.body.ConsumeMessageDirectlyResult; import org.apache.rocketmq.common.protocol.body.ConsumeStatsList; @@ -78,6 +78,7 @@ import org.apache.rocketmq.common.protocol.route.BrokerData; import org.apache.rocketmq.common.protocol.route.QueueData; 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.RPCHook; import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.common.RemotingUtil; @@ -1048,14 +1049,15 @@ public class DefaultMQAdminExtImpl implements MQAdminExt, MQAdminExtInner { @Override public boolean resumeCheckHalfMessage(String msgId) - throws RemotingException, MQClientException, InterruptedException, MQBrokerException { + throws RemotingException, MQClientException, InterruptedException, MQBrokerException { MessageExt msg = this.viewMessage(msgId); return this.mqClientInstance.getMQClientAPIImpl().resumeCheckHalfMessage(RemotingUtil.socketAddress2String(msg.getStoreHost()), msgId, timeoutMillis); } @Override - public boolean resumeCheckHalfMessage(final String topic, final String msgId) throws RemotingException, MQClientException, InterruptedException, MQBrokerException { + public boolean resumeCheckHalfMessage(final String topic, + final String msgId) throws RemotingException, MQClientException, InterruptedException, MQBrokerException { MessageExt msg = this.viewMessage(topic, msgId); if (msg.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX) == null) { return this.mqClientInstance.getMQClientAPIImpl().resumeCheckHalfMessage(RemotingUtil.socketAddress2String(msg.getStoreHost()), msgId, timeoutMillis); @@ -1064,4 +1066,12 @@ public class DefaultMQAdminExtImpl implements MQAdminExt, MQAdminExtInner { return this.mqClientInstance.getMQClientAPIImpl().resumeCheckHalfMessage(RemotingUtil.socketAddress2String(msg.getStoreHost()), msgClient.getOffsetMsgId(), timeoutMillis); } } + + @Override + 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 { + this.mqClientInstance.getMQClientAPIImpl().setMessageRequestMode(brokerAddr, topic, consumerGroup, mode, popShareQueueNum, timeoutMillis); + } } diff --git a/tools/src/main/java/org/apache/rocketmq/tools/admin/MQAdminExt.java b/tools/src/main/java/org/apache/rocketmq/tools/admin/MQAdminExt.java index d5462cb04e5a41873690c10cf67b880e21ad6bd1..5ce6db18997dae5f9aff531c52511ee7952b680e 100644 --- a/tools/src/main/java/org/apache/rocketmq/tools/admin/MQAdminExt.java +++ b/tools/src/main/java/org/apache/rocketmq/tools/admin/MQAdminExt.java @@ -32,6 +32,7 @@ import org.apache.rocketmq.common.admin.RollbackStats; import org.apache.rocketmq.common.admin.TopicStatsTable; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; +import org.apache.rocketmq.common.message.MessageRequestMode; import org.apache.rocketmq.common.protocol.body.BrokerStatsData; import org.apache.rocketmq.common.protocol.body.ClusterAclVersionInfo; import org.apache.rocketmq.common.protocol.body.ClusterInfo; @@ -71,16 +72,19 @@ public interface MQAdminExt extends MQAdmin { final TopicConfig config) throws RemotingException, MQBrokerException, InterruptedException, MQClientException; - void createAndUpdatePlainAccessConfig(final String addr, final PlainAccessConfig plainAccessConfig) throws RemotingException, MQBrokerException, + void createAndUpdatePlainAccessConfig(final String addr, + final PlainAccessConfig plainAccessConfig) throws RemotingException, MQBrokerException, InterruptedException, MQClientException; void deletePlainAccessConfig(final String addr, final String accessKey) throws RemotingException, MQBrokerException, InterruptedException, MQClientException; - void updateGlobalWhiteAddrConfig(final String addr, final String globalWhiteAddrs)throws RemotingException, MQBrokerException, + void updateGlobalWhiteAddrConfig(final String addr, + final String globalWhiteAddrs) throws RemotingException, MQBrokerException, InterruptedException, MQClientException; - ClusterAclVersionInfo examineBrokerClusterAclVersionInfo(final String addr) throws RemotingException, MQBrokerException, + ClusterAclVersionInfo examineBrokerClusterAclVersionInfo( + final String addr) throws RemotingException, MQBrokerException, InterruptedException, MQClientException; AclConfig examineBrokerClusterAclConfig(final String addr) throws RemotingException, MQBrokerException, @@ -269,11 +273,11 @@ public interface MQAdminExt extends MQAdmin { /** * query consume queue data * - * @param brokerAddr broker ip address - * @param topic topic - * @param queueId id of queue - * @param index start offset - * @param count how many + * @param brokerAddr broker ip address + * @param topic topic + * @param queueId id of queue + * @param index start offset + * @param count how many * @param consumerGroup group */ QueryConsumeQueueResponseBody queryConsumeQueue(final String brokerAddr, @@ -282,7 +286,13 @@ public interface MQAdminExt extends MQAdmin { throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException, MQClientException; boolean resumeCheckHalfMessage(String msgId) - throws RemotingException, MQClientException, InterruptedException, MQBrokerException; + throws RemotingException, MQClientException, InterruptedException, MQBrokerException; - boolean resumeCheckHalfMessage(final String topic, final String msgId) throws RemotingException, MQClientException, InterruptedException, MQBrokerException; + boolean resumeCheckHalfMessage(final String topic, + final String msgId) throws RemotingException, MQClientException, InterruptedException, MQBrokerException; + + void setMessageRequestMode(final String brokerAddr, final String topic, final String consumerGroup, final + MessageRequestMode mode, final int popWorkGroupSize, final long timeoutMillis) + throws InterruptedException, RemotingTimeoutException, RemotingSendRequestException, + RemotingConnectException, MQClientException; } diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/MQAdminStartup.java b/tools/src/main/java/org/apache/rocketmq/tools/command/MQAdminStartup.java index f9477445bea3d895dd9f52a0c7f7a1127ee95d67..6e075f79595fe042b4863be15baa23cafd74edf4 100644 --- a/tools/src/main/java/org/apache/rocketmq/tools/command/MQAdminStartup.java +++ b/tools/src/main/java/org/apache/rocketmq/tools/command/MQAdminStartup.java @@ -31,8 +31,8 @@ import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.protocol.RemotingCommand; import org.apache.rocketmq.srvutil.ServerUtil; import org.apache.rocketmq.tools.command.acl.ClusterAclConfigVersionListSubCommand; -import org.apache.rocketmq.tools.command.acl.GetAccessConfigSubCommand; import org.apache.rocketmq.tools.command.acl.DeleteAccessConfigSubCommand; +import org.apache.rocketmq.tools.command.acl.GetAccessConfigSubCommand; import org.apache.rocketmq.tools.command.acl.UpdateAccessConfigSubCommand; import org.apache.rocketmq.tools.command.acl.UpdateGlobalWhiteAddrSubCommand; import org.apache.rocketmq.tools.command.broker.BrokerConsumeStatsSubCommad; @@ -49,6 +49,7 @@ import org.apache.rocketmq.tools.command.connection.ProducerConnectionSubCommand import org.apache.rocketmq.tools.command.consumer.ConsumerProgressSubCommand; import org.apache.rocketmq.tools.command.consumer.ConsumerStatusSubCommand; import org.apache.rocketmq.tools.command.consumer.DeleteSubscriptionGroupCommand; +import org.apache.rocketmq.tools.command.consumer.SetConsumeModeSubCommand; import org.apache.rocketmq.tools.command.consumer.StartMonitoringSubCommand; import org.apache.rocketmq.tools.command.consumer.UpdateSubGroupSubCommand; import org.apache.rocketmq.tools.command.message.CheckMsgSendRTCommand; @@ -152,6 +153,7 @@ public class MQAdminStartup { initCommand(new UpdateTopicSubCommand()); initCommand(new DeleteTopicSubCommand()); initCommand(new UpdateSubGroupSubCommand()); + initCommand(new SetConsumeModeSubCommand()); initCommand(new DeleteSubscriptionGroupCommand()); initCommand(new UpdateBrokerConfigSubCommand()); initCommand(new UpdateTopicPermSubCommand()); diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/consumer/SetConsumeModeSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/consumer/SetConsumeModeSubCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..9bf5551158e2eb23598ce8a55e783316790546c4 --- /dev/null +++ b/tools/src/main/java/org/apache/rocketmq/tools/command/consumer/SetConsumeModeSubCommand.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.tools.command.consumer; + +import java.util.Set; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.rocketmq.common.message.MessageRequestMode; +import org.apache.rocketmq.remoting.RPCHook; +import org.apache.rocketmq.srvutil.ServerUtil; +import org.apache.rocketmq.tools.admin.DefaultMQAdminExt; +import org.apache.rocketmq.tools.command.CommandUtil; +import org.apache.rocketmq.tools.command.SubCommand; +import org.apache.rocketmq.tools.command.SubCommandException; + + +public class SetConsumeModeSubCommand implements SubCommand { + @Override + public String commandName() { + return "setConsumeMode"; + } + + + @Override + public String commandDesc() { + return "set consume message mode. pull/pop etc."; + } + + + @Override + public Options buildCommandlineOptions(Options options) { + Option opt = new Option("b", "brokerAddr", true, "create subscription group to which broker"); + opt.setRequired(false); + options.addOption(opt); + + opt = new Option("c", "clusterName", true, "create subscription group to which cluster"); + opt.setRequired(false); + options.addOption(opt); + + opt = new Option("t", "topicName", true, "topic name"); + opt.setRequired(true); + options.addOption(opt); + + opt = new Option("g", "groupName", true, "consumer group name"); + opt.setRequired(true); + options.addOption(opt); + + opt = new Option("m", "mode", true, "consume mode. PULL/POP"); + opt.setRequired(true); + options.addOption(opt); + + opt = new Option("n", "popShareQueueNum", true, "num fo queue which share in pop mode"); + opt.setRequired(false); + options.addOption(opt); + + return options; + } + + + @Override + public void execute(CommandLine commandLine, Options options, RPCHook rpcHook) + throws SubCommandException { + DefaultMQAdminExt defaultMQAdminExt = new DefaultMQAdminExt(rpcHook); + + defaultMQAdminExt.setInstanceName(Long.toString(System.currentTimeMillis())); + defaultMQAdminExt.setVipChannelEnabled(false); + + try { + + String topicName = commandLine.getOptionValue('t').trim(); + String groupName = commandLine.getOptionValue('g').trim(); + + MessageRequestMode mode = MessageRequestMode.valueOf(commandLine.getOptionValue('m').trim()); + + + int popShareQueueNum = 0; + if (commandLine.hasOption('n')) { + popShareQueueNum = Integer.parseInt(commandLine.getOptionValue('n') + .trim()); + } + + + if (commandLine.hasOption('b')) { + String addr = commandLine.getOptionValue('b').trim(); + defaultMQAdminExt.start(); + + defaultMQAdminExt.setMessageRequestMode(addr, topicName, groupName, mode, popShareQueueNum, 5000); + System.out.printf("set consume mode to %s success.%n", addr); + System.out.printf("topic[%s] group[%s] consume mode[%s] popShareQueueNum[%d]", + topicName, groupName, mode.toString(), popShareQueueNum); + return; + + } else if (commandLine.hasOption('c')) { + String clusterName = commandLine.getOptionValue('c').trim(); + + defaultMQAdminExt.start(); + Set masterSet = + CommandUtil.fetchMasterAddrByClusterName(defaultMQAdminExt, clusterName); + for (String addr : masterSet) { + try { + defaultMQAdminExt.setMessageRequestMode(addr, topicName, groupName, mode, popShareQueueNum, 5000); + System.out.printf("set consume mode to %s success.%n", addr); + } catch (Exception e) { + e.printStackTrace(); + Thread.sleep(1000 * 1); + } + } + System.out.printf("topic[%s] group[%s] consume mode[%s] popShareQueueNum[%d]", + topicName, groupName, mode.toString(), popShareQueueNum); + return; + } + + ServerUtil.printCommandLineHelp("mqadmin " + this.commandName(), options); + } catch (Exception e) { + throw new SubCommandException(this.getClass().getSimpleName() + " command failed", e); + } finally { + defaultMQAdminExt.shutdown(); + } + } +}