subscriptionDataList = (Collection) args[0];
this.brokerController.getConsumerFilterManager().register(group, subscriptionDataList);
break;
+ case CLIENT_REGISTER:
+ case CLIENT_UNREGISTER:
+ break;
default:
throw new RuntimeException("Unknown event " + event);
}
diff --git a/broker/src/main/java/org/apache/rocketmq/broker/client/ProducerChangeListener.java b/broker/src/main/java/org/apache/rocketmq/broker/client/ProducerChangeListener.java
new file mode 100644
index 0000000000..f8183d33fa
--- /dev/null
+++ b/broker/src/main/java/org/apache/rocketmq/broker/client/ProducerChangeListener.java
@@ -0,0 +1,27 @@
+/*
+ * 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.client;
+
+/**
+ * producer manager will call this listener when something happen
+ *
+ * event type: {@link ProducerGroupEvent}
+ */
+public interface ProducerChangeListener {
+
+ void handle(ProducerGroupEvent event, String group, ClientChannelInfo clientChannelInfo);
+}
diff --git a/broker/src/main/java/org/apache/rocketmq/broker/client/ProducerGroupEvent.java b/broker/src/main/java/org/apache/rocketmq/broker/client/ProducerGroupEvent.java
new file mode 100644
index 0000000000..cbf27ce61e
--- /dev/null
+++ b/broker/src/main/java/org/apache/rocketmq/broker/client/ProducerGroupEvent.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.broker.client;
+
+public enum ProducerGroupEvent {
+ /**
+ * The group of producer is unregistered.
+ */
+ GROUP_UNREGISTER,
+ /**
+ * The client of this producer is unregistered.
+ */
+ CLIENT_UNREGISTER
+}
diff --git a/broker/src/main/java/org/apache/rocketmq/broker/client/ProducerManager.java b/broker/src/main/java/org/apache/rocketmq/broker/client/ProducerManager.java
index c7a52b176b..2589ca1938 100644
--- a/broker/src/main/java/org/apache/rocketmq/broker/client/ProducerManager.java
+++ b/broker/src/main/java/org/apache/rocketmq/broker/client/ProducerManager.java
@@ -25,6 +25,7 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.rocketmq.broker.util.PositiveAtomicCounter;
import org.apache.rocketmq.common.constant.LoggerName;
import org.apache.rocketmq.common.protocol.body.ProducerInfo;
@@ -44,6 +45,7 @@ public class ProducerManager {
private final ConcurrentHashMap clientChannelTable = new ConcurrentHashMap<>();
protected final BrokerStatsManager brokerStatsManager;
private PositiveAtomicCounter positiveAtomicCounter = new PositiveAtomicCounter();
+ private final List producerChangeListenerList = new CopyOnWriteArrayList<>();
public ProducerManager() {
this.brokerStatsManager = null;
@@ -94,8 +96,11 @@ public class ProducerManager {
}
public void scanNotActiveChannel() {
- for (final Map.Entry> entry : this.groupChannelTable
- .entrySet()) {
+ Iterator>> iterator = this.groupChannelTable.entrySet().iterator();
+
+ while (iterator.hasNext()) {
+ Map.Entry> entry = iterator.next();
+
final String group = entry.getKey();
final ConcurrentHashMap chlMap = entry.getValue();
@@ -112,9 +117,16 @@ public class ProducerManager {
log.warn(
"ProducerManager#scanNotActiveChannel: remove expired channel[{}] from ProducerManager groupChannelTable, producer group name: {}",
RemotingHelper.parseChannelRemoteAddr(info.getChannel()), group);
+ callProducerChangeListener(ProducerGroupEvent.CLIENT_UNREGISTER, group, info);
RemotingUtil.closeChannel(info.getChannel());
}
}
+
+ if (chlMap.isEmpty()) {
+ log.warn("SCAN: remove expired channel from ProducerManager groupChannelTable, all clear, group={}", group);
+ iterator.remove();
+ callProducerChangeListener(ProducerGroupEvent.GROUP_UNREGISTER, group, null);
+ }
}
}
@@ -134,6 +146,14 @@ public class ProducerManager {
log.info(
"NETTY EVENT: remove channel[{}][{}] from ProducerManager groupChannelTable, producer group: {}",
clientChannelInfo.toString(), remoteAddr, group);
+ callProducerChangeListener(ProducerGroupEvent.CLIENT_UNREGISTER, group, clientChannelInfo);
+ if (clientChannelInfoTable.isEmpty()) {
+ ConcurrentHashMap oldGroupTable = this.groupChannelTable.remove(group);
+ if (oldGroupTable != null) {
+ log.info("unregister a producer group[{}] from groupChannelTable", group);
+ callProducerChangeListener(ProducerGroupEvent.GROUP_UNREGISTER, group, null);
+ }
+ }
}
}
@@ -172,10 +192,12 @@ public class ProducerManager {
if (old != null) {
log.info("unregister a producer[{}] from groupChannelTable {}", group,
clientChannelInfo.toString());
+ callProducerChangeListener(ProducerGroupEvent.CLIENT_UNREGISTER, group, clientChannelInfo);
}
if (channelTable.isEmpty()) {
this.groupChannelTable.remove(group);
+ callProducerChangeListener(ProducerGroupEvent.GROUP_UNREGISTER, group, null);
log.info("unregister a producer group[{}] from groupChannelTable", group);
}
}
@@ -224,4 +246,19 @@ public class ProducerManager {
public Channel findChannel(String clientId) {
return clientChannelTable.get(clientId);
}
+
+ private void callProducerChangeListener(ProducerGroupEvent event, String group,
+ ClientChannelInfo clientChannelInfo) {
+ for (ProducerChangeListener listener : producerChangeListenerList) {
+ try {
+ listener.handle(event, group, clientChannelInfo);
+ } catch (Throwable t) {
+ log.error("err when call producerChangeListener", t);
+ }
+ }
+ }
+
+ public void appendProducerChangeListener(ProducerChangeListener producerChangeListener) {
+ producerChangeListenerList.add(producerChangeListener);
+ }
}
diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/AbstractSendMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/AbstractSendMessageProcessor.java
index 3d12678184..5c235714a6 100644
--- a/broker/src/main/java/org/apache/rocketmq/broker/processor/AbstractSendMessageProcessor.java
+++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/AbstractSendMessageProcessor.java
@@ -18,7 +18,6 @@ package org.apache.rocketmq.broker.processor;
import io.netty.channel.ChannelHandlerContext;
import java.net.SocketAddress;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
@@ -32,15 +31,10 @@ import org.apache.rocketmq.broker.mqtrace.SendMessageContext;
import org.apache.rocketmq.broker.mqtrace.SendMessageHook;
import org.apache.rocketmq.common.BrokerConfig;
import org.apache.rocketmq.common.MQVersion;
-import org.apache.rocketmq.common.UtilAll;
-import org.apache.rocketmq.common.message.MessageExt;
-import org.apache.rocketmq.common.message.MessageType;
-import org.apache.rocketmq.common.protocol.header.ConsumerSendMsgBackRequestHeader;
-import org.apache.rocketmq.common.subscription.SubscriptionGroupConfig;
-import org.apache.rocketmq.common.topic.TopicValidator;
import org.apache.rocketmq.common.MixAll;
import org.apache.rocketmq.common.TopicConfig;
import org.apache.rocketmq.common.TopicFilterType;
+import org.apache.rocketmq.common.UtilAll;
import org.apache.rocketmq.common.constant.DBMsgConstants;
import org.apache.rocketmq.common.constant.LoggerName;
import org.apache.rocketmq.common.constant.PermName;
@@ -48,21 +42,24 @@ import org.apache.rocketmq.common.help.FAQUrl;
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.MessageExtBrokerInner;
+import org.apache.rocketmq.common.message.MessageType;
import org.apache.rocketmq.common.protocol.NamespaceUtil;
-import org.apache.rocketmq.common.protocol.RequestCode;
import org.apache.rocketmq.common.protocol.ResponseCode;
+import org.apache.rocketmq.common.protocol.header.ConsumerSendMsgBackRequestHeader;
import org.apache.rocketmq.common.protocol.header.SendMessageRequestHeader;
-import org.apache.rocketmq.common.protocol.header.SendMessageRequestHeaderV2;
import org.apache.rocketmq.common.protocol.header.SendMessageResponseHeader;
+import org.apache.rocketmq.common.subscription.SubscriptionGroupConfig;
import org.apache.rocketmq.common.sysflag.MessageSysFlag;
import org.apache.rocketmq.common.sysflag.TopicSysFlag;
+import org.apache.rocketmq.common.topic.TopicValidator;
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.common.message.MessageExtBrokerInner;
import org.apache.rocketmq.store.PutMessageResult;
import org.apache.rocketmq.store.stats.BrokerStatsManager;
@@ -574,102 +571,8 @@ public abstract class AbstractSendMessageProcessor implements NettyRequestProces
}
}
- protected SendMessageRequestHeader parseRequestHeader(RemotingCommand request)
- throws RemotingCommandException {
-
- SendMessageRequestHeaderV2 requestHeaderV2 = null;
- SendMessageRequestHeader requestHeader = null;
- switch (request.getCode()) {
- case RequestCode.SEND_BATCH_MESSAGE:
- case RequestCode.SEND_MESSAGE_V2:
- requestHeaderV2 =
- (SendMessageRequestHeaderV2) request
- .decodeCommandCustomHeader(SendMessageRequestHeaderV2.class);
- case RequestCode.SEND_MESSAGE:
- if (null == requestHeaderV2) {
- requestHeader =
- (SendMessageRequestHeader) request
- .decodeCommandCustomHeader(SendMessageRequestHeader.class);
- } else {
- requestHeader = SendMessageRequestHeaderV2.createSendMessageRequestHeaderV1(requestHeaderV2);
- }
- default:
- break;
- }
- return requestHeader;
- }
-
- static SendMessageRequestHeaderV2 decodeSendMessageHeaderV2(RemotingCommand request)
- throws RemotingCommandException {
- SendMessageRequestHeaderV2 r = new SendMessageRequestHeaderV2();
- HashMap fields = request.getExtFields();
- if (fields == null) {
- throw new RemotingCommandException("the ext fields is null");
- }
-
- String s = fields.get("a");
- checkNotNull(s, "the custom field is null");
- r.setA(s);
-
- s = fields.get("b");
- checkNotNull(s, "the custom field is null");
- r.setB(s);
-
- s = fields.get("c");
- checkNotNull(s, "the custom field is null");
- r.setC(s);
-
- s = fields.get("d");
- checkNotNull(s, "the custom field is null");
- r.setD(Integer.parseInt(s));
-
- s = fields.get("e");
- checkNotNull(s, "the custom field is null");
- r.setE(Integer.parseInt(s));
-
- s = fields.get("f");
- checkNotNull(s, "the custom field is null");
- r.setF(Integer.parseInt(s));
-
- s = fields.get("g");
- checkNotNull(s, "the custom field is null");
- r.setG(Long.parseLong(s));
-
- s = fields.get("h");
- checkNotNull(s, "the custom field is null");
- r.setH(Integer.parseInt(s));
-
- s = fields.get("i");
- if (s != null) {
- r.setI(s);
- }
-
- s = fields.get("j");
- if (s != null) {
- r.setJ(Integer.parseInt(s));
- }
-
- s = fields.get("k");
- if (s != null) {
- r.setK(Boolean.parseBoolean(s));
- }
-
- s = fields.get("l");
- if (s != null) {
- r.setL(Integer.parseInt(s));
- }
-
- s = fields.get("m");
- if (s != null) {
- r.setM(Boolean.parseBoolean(s));
- }
- return r;
- }
-
- private static void checkNotNull(String s, String msg) throws RemotingCommandException {
- if (s == null) {
- throw new RemotingCommandException(msg);
- }
+ protected SendMessageRequestHeader parseRequestHeader(RemotingCommand request) throws RemotingCommandException {
+ return SendMessageRequestHeader.parseRequestHeader(request);
}
protected int randomQueueId(int writeQueueNums) {
diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java
index 991a7058d9..5ab1647e45 100644
--- a/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java
+++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/ChangeInvisibleTimeProcessor.java
@@ -179,6 +179,7 @@ public class ChangeInvisibleTimeProcessor implements NettyRequestProcessor {
ck.setTopic(requestHeader.getTopic());
ck.setQueueId((byte) queueId);
ck.addDiff(0);
+ ck.setBrokerName(brokerName);
msgInner.setBody(JSON.toJSONString(ck).getBytes(DataConverter.charset));
msgInner.setQueueId(reviveQid);
diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java
index 333b20cecd..8ac4a6a54f 100644
--- a/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java
+++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/PopMessageProcessor.java
@@ -33,7 +33,6 @@ 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;
@@ -251,7 +250,7 @@ public class PopMessageProcessor implements NettyRequestProcessor {
}
if (requestHeader.isTimeoutTooMuch()) {
- response.setCode(POLLING_TIMEOUT);
+ response.setCode(ResponseCode.POLLING_TIMEOUT);
response.setRemark(String.format("the broker[%s] poping message is timeout too much",
this.brokerController.getBrokerConfig().getBrokerIP1()));
return response;
@@ -304,14 +303,6 @@ public class PopMessageProcessor implements NettyRequestProcessor {
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);
@@ -463,6 +454,8 @@ public class PopMessageProcessor implements NettyRequestProcessor {
response = null;
}
break;
+ case ResponseCode.POLLING_TIMEOUT:
+ return response;
default:
assert false;
}
diff --git a/broker/src/main/java/org/apache/rocketmq/broker/transaction/TransactionalMessageCheckService.java b/broker/src/main/java/org/apache/rocketmq/broker/transaction/TransactionalMessageCheckService.java
index 143889a19c..6a3c2d2b29 100644
--- a/broker/src/main/java/org/apache/rocketmq/broker/transaction/TransactionalMessageCheckService.java
+++ b/broker/src/main/java/org/apache/rocketmq/broker/transaction/TransactionalMessageCheckService.java
@@ -42,8 +42,8 @@ public class TransactionalMessageCheckService extends ServiceThread {
@Override
public void run() {
log.info("Start transaction check service thread!");
- long checkInterval = brokerController.getBrokerConfig().getTransactionCheckInterval();
while (!this.isStopped()) {
+ long checkInterval = brokerController.getBrokerConfig().getTransactionCheckInterval();
this.waitForRunning(checkInterval);
}
log.info("End transaction check service thread!");
diff --git a/broker/src/test/java/org/apache/rocketmq/broker/client/ConsumerManagerScannerTest.java b/broker/src/test/java/org/apache/rocketmq/broker/client/ConsumerManagerScannerTest.java
new file mode 100644
index 0000000000..45a39996ad
--- /dev/null
+++ b/broker/src/test/java/org/apache/rocketmq/broker/client/ConsumerManagerScannerTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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.client;
+
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
+import org.apache.rocketmq.common.protocol.heartbeat.ConsumeType;
+import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;
+import org.apache.rocketmq.remoting.protocol.LanguageCode;
+import org.junit.Before;
+import org.junit.Test;
+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.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ConsumerManagerScannerTest {
+ private ConsumerManager consumerManager;
+ private String group = "FooBar";
+ private String clientId = "clientId";
+ private ClientChannelInfo clientInfo;
+ private Map> groupEventListMap = new HashMap<>();
+
+ @Mock
+ private Channel channel;
+
+ @Before
+ public void init() {
+ clientInfo = new ClientChannelInfo(channel, clientId, LanguageCode.JAVA, 0);
+
+ consumerManager = new ConsumerManager(new ConsumerIdsChangeListener() {
+ @Override
+ public void handle(ConsumerGroupEvent event, String group, Object... args) {
+ groupEventListMap.compute(event, (eventKey, dataListVal) -> {
+ if (dataListVal == null) {
+ dataListVal = new ArrayList<>();
+ }
+ dataListVal.add(new ConsumerIdsChangeListenerData(event, group, args));
+ return dataListVal;
+ });
+ }
+
+ @Override
+ public void shutdown() {
+
+ }
+ });
+ }
+
+ private static class ConsumerIdsChangeListenerData {
+ private ConsumerGroupEvent event;
+ private String group;
+ private Object[] args;
+
+ public ConsumerIdsChangeListenerData(ConsumerGroupEvent event, String group, Object[] args) {
+ this.event = event;
+ this.group = group;
+ this.args = args;
+ }
+ }
+
+ @Test
+ public void testClientUnregisterEventInDoChannelCloseEvent() {
+ assertThat(consumerManager.registerConsumer(
+ group,
+ clientInfo,
+ ConsumeType.CONSUME_PASSIVELY,
+ MessageModel.CLUSTERING,
+ ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET,
+ new HashSet<>(),
+ false
+ )).isTrue();
+
+ consumerManager.doChannelCloseEvent("remoteAddr", channel);
+
+ assertThat(groupEventListMap.get(ConsumerGroupEvent.CLIENT_UNREGISTER).size()).isEqualTo(1);
+ assertThat(groupEventListMap.get(ConsumerGroupEvent.CLIENT_UNREGISTER).get(0).args[0]).isInstanceOf(ClientChannelInfo.class);
+ ClientChannelInfo clientChannelInfo = (ClientChannelInfo) groupEventListMap.get(ConsumerGroupEvent.CLIENT_UNREGISTER).get(0).args[0];
+ assertThat(clientChannelInfo).isSameAs(clientInfo);
+ }
+
+ @Test
+ public void testClientUnregisterEventInUnregisterConsumer() {
+ assertThat(consumerManager.registerConsumer(
+ group,
+ clientInfo,
+ ConsumeType.CONSUME_PASSIVELY,
+ MessageModel.CLUSTERING,
+ ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET,
+ new HashSet<>(),
+ false
+ )).isTrue();
+
+ consumerManager.unregisterConsumer(group, clientInfo, false);
+
+ assertThat(groupEventListMap.get(ConsumerGroupEvent.CLIENT_UNREGISTER).size()).isEqualTo(1);
+ assertThat(groupEventListMap.get(ConsumerGroupEvent.CLIENT_UNREGISTER).get(0).args[0]).isInstanceOf(ClientChannelInfo.class);
+ ClientChannelInfo clientChannelInfo = (ClientChannelInfo) groupEventListMap.get(ConsumerGroupEvent.CLIENT_UNREGISTER).get(0).args[0];
+ assertThat(clientChannelInfo).isSameAs(clientInfo);
+ }
+
+ @Test
+ public void testClientUnregisterEventInScanNotActiveChannel() {
+ assertThat(consumerManager.registerConsumer(
+ group,
+ clientInfo,
+ ConsumeType.CONSUME_PASSIVELY,
+ MessageModel.CLUSTERING,
+ ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET,
+ new HashSet<>(),
+ false
+ )).isTrue();
+ clientInfo.setLastUpdateTimestamp(0);
+ when(channel.close()).thenReturn(mock(ChannelFuture.class));
+
+ consumerManager.scanNotActiveChannel();
+ assertThat(groupEventListMap.get(ConsumerGroupEvent.CLIENT_UNREGISTER).size()).isEqualTo(1);
+ assertThat(groupEventListMap.get(ConsumerGroupEvent.CLIENT_UNREGISTER).get(0).args[0]).isInstanceOf(ClientChannelInfo.class);
+ ClientChannelInfo clientChannelInfo = (ClientChannelInfo) groupEventListMap.get(ConsumerGroupEvent.CLIENT_UNREGISTER).get(0).args[0];
+ assertThat(clientChannelInfo).isSameAs(clientInfo);
+ }
+}
\ No newline at end of file
diff --git a/broker/src/test/java/org/apache/rocketmq/broker/client/ProducerManagerTest.java b/broker/src/test/java/org/apache/rocketmq/broker/client/ProducerManagerTest.java
index 6c794ac5d3..fd76312941 100644
--- a/broker/src/test/java/org/apache/rocketmq/broker/client/ProducerManagerTest.java
+++ b/broker/src/test/java/org/apache/rocketmq/broker/client/ProducerManagerTest.java
@@ -21,6 +21,7 @@ import io.netty.channel.ChannelFuture;
import java.lang.reflect.Field;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
import org.apache.rocketmq.remoting.protocol.LanguageCode;
import org.junit.Before;
import org.junit.Test;
@@ -50,6 +51,20 @@ public class ProducerManagerTest {
@Test
public void scanNotActiveChannel() throws Exception {
producerManager.registerProducer(group, clientInfo);
+ AtomicReference groupRef = new AtomicReference<>();
+ AtomicReference clientChannelInfoRef = new AtomicReference<>();
+ producerManager.appendProducerChangeListener((event, group, clientChannelInfo) -> {
+ switch (event) {
+ case GROUP_UNREGISTER:
+ groupRef.set(group);
+ break;
+ case CLIENT_UNREGISTER:
+ clientChannelInfoRef.set(clientChannelInfo);
+ break;
+ default:
+ break;
+ }
+ });
assertThat(producerManager.getGroupChannelTable().get(group).get(channel)).isNotNull();
assertThat(producerManager.findChannel("clientId")).isNotNull();
Field field = ProducerManager.class.getDeclaredField("CHANNEL_EXPIRED_TIMEOUT");
@@ -58,17 +73,35 @@ public class ProducerManagerTest {
clientInfo.setLastUpdateTimestamp(System.currentTimeMillis() - CHANNEL_EXPIRED_TIMEOUT - 10);
when(channel.close()).thenReturn(mock(ChannelFuture.class));
producerManager.scanNotActiveChannel();
- assertThat(producerManager.getGroupChannelTable().get(group).get(channel)).isNull();
+ assertThat(producerManager.getGroupChannelTable().get(group)).isNull();
+ assertThat(groupRef.get()).isEqualTo(group);
+ assertThat(clientChannelInfoRef.get()).isSameAs(clientInfo);
assertThat(producerManager.findChannel("clientId")).isNull();
}
@Test
public void doChannelCloseEvent() throws Exception {
producerManager.registerProducer(group, clientInfo);
+ AtomicReference groupRef = new AtomicReference<>();
+ AtomicReference clientChannelInfoRef = new AtomicReference<>();
+ producerManager.appendProducerChangeListener((event, group, clientChannelInfo) -> {
+ switch (event) {
+ case GROUP_UNREGISTER:
+ groupRef.set(group);
+ break;
+ case CLIENT_UNREGISTER:
+ clientChannelInfoRef.set(clientChannelInfo);
+ break;
+ default:
+ break;
+ }
+ });
assertThat(producerManager.getGroupChannelTable().get(group).get(channel)).isNotNull();
assertThat(producerManager.findChannel("clientId")).isNotNull();
producerManager.doChannelCloseEvent("127.0.0.1", channel);
- assertThat(producerManager.getGroupChannelTable().get(group).get(channel)).isNull();
+ assertThat(producerManager.getGroupChannelTable().get(group)).isNull();
+ assertThat(groupRef.get()).isEqualTo(group);
+ assertThat(clientChannelInfoRef.get()).isSameAs(clientInfo);
assertThat(producerManager.findChannel("clientId")).isNull();
}
@@ -86,6 +119,20 @@ public class ProducerManagerTest {
@Test
public void unregisterProducer() throws Exception {
producerManager.registerProducer(group, clientInfo);
+ AtomicReference groupRef = new AtomicReference<>();
+ AtomicReference clientChannelInfoRef = new AtomicReference<>();
+ producerManager.appendProducerChangeListener((event, group, clientChannelInfo) -> {
+ switch (event) {
+ case GROUP_UNREGISTER:
+ groupRef.set(group);
+ break;
+ case CLIENT_UNREGISTER:
+ clientChannelInfoRef.set(clientChannelInfo);
+ break;
+ default:
+ break;
+ }
+ });
Map channelMap = producerManager.getGroupChannelTable().get(group);
assertThat(channelMap).isNotNull();
assertThat(channelMap.get(channel)).isEqualTo(clientInfo);
@@ -95,6 +142,8 @@ public class ProducerManagerTest {
producerManager.unregisterProducer(group, clientInfo);
channelMap = producerManager.getGroupChannelTable().get(group);
channel1 = producerManager.findChannel("clientId");
+ assertThat(groupRef.get()).isEqualTo(group);
+ assertThat(clientChannelInfoRef.get()).isSameAs(clientInfo);
assertThat(channelMap).isNull();
assertThat(channel1).isNull();
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
index 67b4fb96cb..85582fcff9 100644
--- a/broker/src/test/java/org/apache/rocketmq/broker/processor/PopMessageProcessorTest.java
+++ b/broker/src/test/java/org/apache/rocketmq/broker/processor/PopMessageProcessorTest.java
@@ -110,16 +110,6 @@ public class PopMessageProcessorTest {
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);
diff --git a/client/src/main/java/org/apache/rocketmq/client/ClientConfig.java b/client/src/main/java/org/apache/rocketmq/client/ClientConfig.java
index eeb8826730..02f5efac2e 100644
--- a/client/src/main/java/org/apache/rocketmq/client/ClientConfig.java
+++ b/client/src/main/java/org/apache/rocketmq/client/ClientConfig.java
@@ -35,6 +35,8 @@ import org.apache.rocketmq.remoting.protocol.RequestType;
*/
public class ClientConfig {
public static final String SEND_MESSAGE_WITH_VIP_CHANNEL_PROPERTY = "com.rocketmq.sendMessageWithVIPChannel";
+ public static final String DECODE_READ_BODY = "com.rocketmq.read.body";
+ public static final String DECODE_DECOMPRESS_BODY = "com.rocketmq.decompress.body";
private String namesrvAddr = NameServerAddressUtils.getNameServerAddresses();
private String clientIP = RemotingUtil.getLocalAddress();
private String instanceName = System.getProperty("rocketmq.client.name", "DEFAULT");
@@ -58,6 +60,8 @@ public class ClientConfig {
private long pullTimeDelayMillsWhenException = 1000;
private boolean unitMode = false;
private String unitName;
+ private boolean decodeReadBody = Boolean.parseBoolean(System.getProperty(DECODE_READ_BODY, "true"));
+ private boolean decodeDecompressBody = Boolean.parseBoolean(System.getProperty(DECODE_DECOMPRESS_BODY, "true"));
private boolean vipChannelEnabled = Boolean.parseBoolean(System.getProperty(SEND_MESSAGE_WITH_VIP_CHANNEL_PROPERTY, "false"));
private boolean useTLS = TlsSystemConfig.tlsEnable;
@@ -172,6 +176,8 @@ public class ClientConfig {
this.namespace = cc.namespace;
this.language = cc.language;
this.mqClientApiTimeout = cc.mqClientApiTimeout;
+ this.decodeReadBody = cc.decodeReadBody;
+ this.decodeDecompressBody = cc.decodeDecompressBody;
this.enableStreamRequestType = cc.enableStreamRequestType;
}
@@ -192,6 +198,8 @@ public class ClientConfig {
cc.namespace = namespace;
cc.language = language;
cc.mqClientApiTimeout = mqClientApiTimeout;
+ cc.decodeReadBody = decodeReadBody;
+ cc.decodeDecompressBody = decodeDecompressBody;
cc.enableStreamRequestType = enableStreamRequestType;
return cc;
}
@@ -293,6 +301,22 @@ public class ClientConfig {
this.language = language;
}
+ public boolean isDecodeReadBody() {
+ return decodeReadBody;
+ }
+
+ public void setDecodeReadBody(boolean decodeReadBody) {
+ this.decodeReadBody = decodeReadBody;
+ }
+
+ public boolean isDecodeDecompressBody() {
+ return decodeDecompressBody;
+ }
+
+ public void setDecodeDecompressBody(boolean decodeDecompressBody) {
+ this.decodeDecompressBody = decodeDecompressBody;
+ }
+
public String getNamespace() {
if (namespaceInitialized) {
return namespace;
@@ -347,6 +371,7 @@ public class ClientConfig {
+ ", heartbeatBrokerInterval=" + heartbeatBrokerInterval + ", persistConsumerOffsetInterval=" + persistConsumerOffsetInterval
+ ", pullTimeDelayMillsWhenException=" + pullTimeDelayMillsWhenException + ", unitMode=" + unitMode + ", unitName=" + unitName + ", vipChannelEnabled="
+ vipChannelEnabled + ", useTLS=" + useTLS + ", language=" + language.name() + ", namespace=" + namespace + ", mqClientApiTimeout=" + mqClientApiTimeout
+ + ", decodeReadBody=" + decodeReadBody + ", decodeDecompressBody=" + decodeDecompressBody
+ ", enableStreamRequestType=" + enableStreamRequestType + "]";
}
}
diff --git a/client/src/main/java/org/apache/rocketmq/client/exception/MQClientException.java b/client/src/main/java/org/apache/rocketmq/client/exception/MQClientException.java
index f4534742d5..9bbcce2178 100644
--- a/client/src/main/java/org/apache/rocketmq/client/exception/MQClientException.java
+++ b/client/src/main/java/org/apache/rocketmq/client/exception/MQClientException.java
@@ -37,6 +37,13 @@ public class MQClientException extends Exception {
this.errorMessage = errorMessage;
}
+ public MQClientException(int responseCode, String errorMessage, Throwable cause) {
+ super(FAQUrl.attachDefaultURL("CODE: " + UtilAll.responseCode2String(responseCode) + " DESC: "
+ + errorMessage), cause);
+ this.responseCode = responseCode;
+ this.errorMessage = errorMessage;
+ }
+
public int getResponseCode() {
return responseCode;
}
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 528f5646e2..b0371784ec 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
@@ -31,6 +31,7 @@ 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.common.ClientErrorCode;
import org.apache.rocketmq.client.consumer.AckCallback;
import org.apache.rocketmq.client.consumer.AckResult;
import org.apache.rocketmq.client.consumer.AckStatus;
@@ -735,7 +736,7 @@ public class MQClientAPIImpl implements NameServerUpdateCallback {
}
}
- private SendResult processSendResponse(
+ protected SendResult processSendResponse(
final String brokerName,
final Message msg,
final RemotingCommand response,
@@ -853,9 +854,9 @@ public class MQClientAPIImpl implements NameServerUpdateCallback {
}
} else {
if (!responseFuture.isSendRequestOK()) {
- popCallback.onException(new MQClientException("send request failed to " + addr + ". Request: " + request, responseFuture.getCause()));
+ popCallback.onException(new MQClientException(ClientErrorCode.CONNECT_BROKER_EXCEPTION, "send request failed to " + addr + ". Request: " + request, responseFuture.getCause()));
} else if (responseFuture.isTimeout()) {
- popCallback.onException(new MQClientException("wait response from " + addr + " timeout :" + responseFuture.getTimeoutMillis() + "ms" + ". Request: " + request,
+ popCallback.onException(new MQClientException(ClientErrorCode.ACCESS_BROKER_TIMEOUT, "wait response from " + addr + " timeout :" + responseFuture.getTimeoutMillis() + "ms" + ". Request: " + request,
responseFuture.getCause()));
} else {
popCallback.onException(new MQClientException("unknown reason. addr: " + addr + ", timeoutMillis: " + timeoutMillis + ". Request: " + request, responseFuture.getCause()));
@@ -892,9 +893,9 @@ public class MQClientAPIImpl implements NameServerUpdateCallback {
}
} else {
if (!responseFuture.isSendRequestOK()) {
- ackCallback.onException(new MQClientException("send request failed to " + addr + ". Request: " + request, responseFuture.getCause()));
+ ackCallback.onException(new MQClientException(ClientErrorCode.CONNECT_BROKER_EXCEPTION, "send request failed to " + addr + ". Request: " + request, responseFuture.getCause()));
} else if (responseFuture.isTimeout()) {
- ackCallback.onException(new MQClientException("wait response from " + addr + " timeout :" + responseFuture.getTimeoutMillis() + "ms" + ". Request: " + request,
+ ackCallback.onException(new MQClientException(ClientErrorCode.ACCESS_BROKER_TIMEOUT, "wait response from " + addr + " timeout :" + responseFuture.getTimeoutMillis() + "ms" + ". Request: " + request,
responseFuture.getCause()));
} else {
ackCallback.onException(new MQClientException("unknown reason. addr: " + addr + ", timeoutMillis: " + timeOut + ". Request: " + request, responseFuture.getCause()));
@@ -938,9 +939,9 @@ public class MQClientAPIImpl implements NameServerUpdateCallback {
}
} else {
if (!responseFuture.isSendRequestOK()) {
- ackCallback.onException(new MQClientException("send request failed to " + addr + ". Request: " + request, responseFuture.getCause()));
+ ackCallback.onException(new MQClientException(ClientErrorCode.CONNECT_BROKER_EXCEPTION, "send request failed to " + addr + ". Request: " + request, responseFuture.getCause()));
} else if (responseFuture.isTimeout()) {
- ackCallback.onException(new MQClientException("wait response from " + addr + " timeout :" + responseFuture.getTimeoutMillis() + "ms" + ". Request: " + request,
+ ackCallback.onException(new MQClientException(ClientErrorCode.ACCESS_BROKER_TIMEOUT, "wait response from " + addr + " timeout :" + responseFuture.getTimeoutMillis() + "ms" + ". Request: " + request,
responseFuture.getCause()));
} else {
ackCallback.onException(new MQClientException("unknown reason. addr: " + addr + ", timeoutMillis: " + timeoutMillis + ". Request: " + request, responseFuture.getCause()));
@@ -970,9 +971,9 @@ public class MQClientAPIImpl implements NameServerUpdateCallback {
}
} else {
if (!responseFuture.isSendRequestOK()) {
- pullCallback.onException(new MQClientException("send request failed to " + addr + ". Request: " + request, responseFuture.getCause()));
+ pullCallback.onException(new MQClientException(ClientErrorCode.CONNECT_BROKER_EXCEPTION, "send request failed to " + addr + ". Request: " + request, responseFuture.getCause()));
} else if (responseFuture.isTimeout()) {
- pullCallback.onException(new MQClientException("wait response from " + addr + " timeout :" + responseFuture.getTimeoutMillis() + "ms" + ". Request: " + request,
+ pullCallback.onException(new MQClientException(ClientErrorCode.ACCESS_BROKER_TIMEOUT, "wait response from " + addr + " timeout :" + responseFuture.getTimeoutMillis() + "ms" + ". Request: " + request,
responseFuture.getCause()));
} else {
pullCallback.onException(new MQClientException("unknown reason. addr: " + addr + ", timeoutMillis: " + timeoutMillis + ". Request: " + request, responseFuture.getCause()));
@@ -1029,7 +1030,11 @@ public class MQClientAPIImpl implements NameServerUpdateCallback {
case ResponseCode.SUCCESS:
popStatus = PopStatus.FOUND;
ByteBuffer byteBuffer = ByteBuffer.wrap(response.getBody());
- msgFoundList = MessageDecoder.decodes(byteBuffer);
+ msgFoundList = MessageDecoder.decodesBatch(
+ byteBuffer,
+ clientConfig.isDecodeReadBody(),
+ clientConfig.isDecodeDecompressBody(),
+ true);
break;
case ResponseCode.POLLING_FULL:
popStatus = PopStatus.POLLING_FULL;
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 187b2573dc..689cbc8ee2 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
@@ -78,7 +78,12 @@ public class PullAPIWrapper {
this.updatePullFromWhichNode(mq, pullResultExt.getSuggestWhichBrokerId());
if (PullStatus.FOUND == pullResult.getPullStatus()) {
ByteBuffer byteBuffer = ByteBuffer.wrap(pullResultExt.getMessageBinary());
- List msgList = MessageDecoder.decodes(byteBuffer);
+ List msgList = MessageDecoder.decodesBatch(
+ byteBuffer,
+ this.mQClientFactory.getClientConfig().isDecodeReadBody(),
+ this.mQClientFactory.getClientConfig().isDecodeDecompressBody(),
+ true
+ );
boolean needDecodeInnerMessage = false;
for (MessageExt messageExt: msgList) {
diff --git a/common/pom.xml b/common/pom.xml
index 16fe95fcd3..fc810f26cd 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -52,5 +52,14 @@
com.google.guava
guava
+
+ org.slf4j
+ slf4j-api
+ 1.7.7
+
+
+ commons-codec
+ commons-codec
+
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 a77d5c2d21..b8ba059ea3 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
@@ -44,4 +44,6 @@ public class LoggerName {
public static final String ROCKETMQ_POP_LOGGER_NAME = "RocketmqPop";
public static final String FAILOVER_LOGGER_NAME = "RocketmqFailover";
public static final String STDOUT_LOGGER_NAME = "STDOUT";
+ public static final String PROXY_LOGGER_NAME = "RocketmqProxy";
+ public static final String PROXY_WATER_MARK_LOGGER_NAME = "RocketmqProxyWatermark";
}
diff --git a/common/src/main/java/org/apache/rocketmq/common/consumer/ReceiptHandle.java b/common/src/main/java/org/apache/rocketmq/common/consumer/ReceiptHandle.java
new file mode 100644
index 0000000000..392a3ae339
--- /dev/null
+++ b/common/src/main/java/org/apache/rocketmq/common/consumer/ReceiptHandle.java
@@ -0,0 +1,232 @@
+/*
+ * 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.consumer;
+
+import java.util.Arrays;
+import java.util.List;
+import org.apache.rocketmq.common.KeyBuilder;
+import org.apache.rocketmq.common.message.MessageConst;
+
+public class ReceiptHandle {
+ private static final String SEPARATOR = MessageConst.KEY_SEPARATOR;
+ public static final String NORMAL_TOPIC = "0";
+ public static final String RETRY_TOPIC = "1";
+ private final long startOffset;
+ private final long retrieveTime;
+ private final long invisibleTime;
+ private final long nextVisibleTime;
+ private final int reviveQueueId;
+ private final String topicType;
+ private final String brokerName;
+ private final int queueId;
+ private final long offset;
+ private final long commitLogOffset;
+ private final String receiptHandle;
+
+ public String encode() {
+ return startOffset + SEPARATOR + retrieveTime + SEPARATOR + invisibleTime + SEPARATOR + reviveQueueId
+ + SEPARATOR + topicType + SEPARATOR + brokerName + SEPARATOR + queueId + SEPARATOR + offset + SEPARATOR
+ + commitLogOffset;
+ }
+
+ public boolean isExpired() {
+ return nextVisibleTime <= System.currentTimeMillis();
+ }
+
+ public static ReceiptHandle decode(String receiptHandle) {
+ List dataList = Arrays.asList(receiptHandle.split(SEPARATOR));
+ if (dataList.size() < 8) {
+ throw new IllegalArgumentException("Parse failed, dataList size " + dataList.size());
+ }
+ long startOffset = Long.parseLong(dataList.get(0));
+ long retrieveTime = Long.parseLong(dataList.get(1));
+ long invisibleTime = Long.parseLong(dataList.get(2));
+ int reviveQueueId = Integer.parseInt(dataList.get(3));
+ String topicType = dataList.get(4);
+ String brokerName = dataList.get(5);
+ int queueId = Integer.parseInt(dataList.get(6));
+ long offset = Long.parseLong(dataList.get(7));
+ long commitLogOffset = -1L;
+ if (dataList.size() >= 9) {
+ commitLogOffset = Long.parseLong(dataList.get(8));
+ }
+
+ return new ReceiptHandleBuilder()
+ .startOffset(startOffset)
+ .retrieveTime(retrieveTime)
+ .invisibleTime(invisibleTime)
+ .reviveQueueId(reviveQueueId)
+ .topicType(topicType)
+ .brokerName(brokerName)
+ .queueId(queueId)
+ .offset(offset)
+ .commitLogOffset(commitLogOffset)
+ .receiptHandle(receiptHandle).build();
+ }
+
+ ReceiptHandle(final long startOffset, final long retrieveTime, final long invisibleTime, final long nextVisibleTime,
+ final int reviveQueueId, final String topicType, final String brokerName, final int queueId, final long offset,
+ final long commitLogOffset, final String receiptHandle) {
+ this.startOffset = startOffset;
+ this.retrieveTime = retrieveTime;
+ this.invisibleTime = invisibleTime;
+ this.nextVisibleTime = nextVisibleTime;
+ this.reviveQueueId = reviveQueueId;
+ this.topicType = topicType;
+ this.brokerName = brokerName;
+ this.queueId = queueId;
+ this.offset = offset;
+ this.commitLogOffset = commitLogOffset;
+ this.receiptHandle = receiptHandle;
+ }
+
+ public static class ReceiptHandleBuilder {
+ private long startOffset;
+ private long retrieveTime;
+ private long invisibleTime;
+ private int reviveQueueId;
+ private String topicType;
+ private String brokerName;
+ private int queueId;
+ private long offset;
+ private long commitLogOffset;
+ private String receiptHandle;
+
+ ReceiptHandleBuilder() {
+ }
+
+ public ReceiptHandle.ReceiptHandleBuilder startOffset(final long startOffset) {
+ this.startOffset = startOffset;
+ return this;
+ }
+
+ public ReceiptHandle.ReceiptHandleBuilder retrieveTime(final long retrieveTime) {
+ this.retrieveTime = retrieveTime;
+ return this;
+ }
+
+ public ReceiptHandle.ReceiptHandleBuilder invisibleTime(final long invisibleTime) {
+ this.invisibleTime = invisibleTime;
+ return this;
+ }
+
+ public ReceiptHandle.ReceiptHandleBuilder reviveQueueId(final int reviveQueueId) {
+ this.reviveQueueId = reviveQueueId;
+ return this;
+ }
+
+ public ReceiptHandle.ReceiptHandleBuilder topicType(final String topicType) {
+ this.topicType = topicType;
+ return this;
+ }
+
+ public ReceiptHandle.ReceiptHandleBuilder brokerName(final String brokerName) {
+ this.brokerName = brokerName;
+ return this;
+ }
+
+ public ReceiptHandle.ReceiptHandleBuilder queueId(final int queueId) {
+ this.queueId = queueId;
+ return this;
+ }
+
+ public ReceiptHandle.ReceiptHandleBuilder offset(final long offset) {
+ this.offset = offset;
+ return this;
+ }
+
+ public ReceiptHandle.ReceiptHandleBuilder commitLogOffset(final long commitLogOffset) {
+ this.commitLogOffset = commitLogOffset;
+ return this;
+ }
+
+ public ReceiptHandle.ReceiptHandleBuilder receiptHandle(final String receiptHandle) {
+ this.receiptHandle = receiptHandle;
+ return this;
+ }
+
+ public ReceiptHandle build() {
+ return new ReceiptHandle(this.startOffset, this.retrieveTime, this.invisibleTime, this.retrieveTime + this.invisibleTime,
+ this.reviveQueueId, this.topicType, this.brokerName, this.queueId, this.offset, this.commitLogOffset, this.receiptHandle);
+ }
+
+ @Override
+ public String toString() {
+ return "ReceiptHandle.ReceiptHandleBuilder(startOffset=" + this.startOffset + ", retrieveTime=" + this.retrieveTime + ", invisibleTime=" + this.invisibleTime + ", reviveQueueId=" + this.reviveQueueId + ", topic=" + this.topicType + ", brokerName=" + this.brokerName + ", queueId=" + this.queueId + ", offset=" + this.offset + ", commitLogOffset=" + this.commitLogOffset + ", receiptHandle=" + this.receiptHandle + ")";
+ }
+ }
+
+ public static ReceiptHandle.ReceiptHandleBuilder builder() {
+ return new ReceiptHandle.ReceiptHandleBuilder();
+ }
+
+ public long getStartOffset() {
+ return this.startOffset;
+ }
+
+ public long getRetrieveTime() {
+ return this.retrieveTime;
+ }
+
+ public long getInvisibleTime() {
+ return this.invisibleTime;
+ }
+
+ public long getNextVisibleTime() {
+ return this.nextVisibleTime;
+ }
+
+ public int getReviveQueueId() {
+ return this.reviveQueueId;
+ }
+
+ public String getTopicType() {
+ return this.topicType;
+ }
+
+ public String getBrokerName() {
+ return this.brokerName;
+ }
+
+ public int getQueueId() {
+ return this.queueId;
+ }
+
+ public long getOffset() {
+ return this.offset;
+ }
+
+ public long getCommitLogOffset() {
+ return commitLogOffset;
+ }
+
+ public String getReceiptHandle() {
+ return this.receiptHandle;
+ }
+
+ public boolean isRetryTopic() {
+ return RETRY_TOPIC.equals(topicType);
+ }
+
+ public String getRealTopic(String topic, String groupName) {
+ if (isRetryTopic()) {
+ return KeyBuilder.buildPopRetryTopic(topic, groupName);
+ }
+ return topic;
+ }
+}
diff --git a/common/src/main/java/org/apache/rocketmq/common/message/MessageBatch.java b/common/src/main/java/org/apache/rocketmq/common/message/MessageBatch.java
index a6b801edab..e3104f1656 100644
--- a/common/src/main/java/org/apache/rocketmq/common/message/MessageBatch.java
+++ b/common/src/main/java/org/apache/rocketmq/common/message/MessageBatch.java
@@ -39,7 +39,7 @@ public class MessageBatch extends Message implements Iterable {
return messages.iterator();
}
- public static MessageBatch generateFromList(Collection messages) {
+ public static MessageBatch generateFromList(Collection extends Message> messages) {
assert messages != null;
assert messages.size() > 0;
List messageList = new ArrayList(messages.size());
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 a823466415..0193fddd7e 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
@@ -65,6 +65,10 @@ public class MessageConst {
public static final String PROPERTY_REDIRECT = "REDIRECT";
public static final String PROPERTY_INNER_MULTI_DISPATCH = "INNER_MULTI_DISPATCH";
public static final String PROPERTY_INNER_MULTI_QUEUE_OFFSET = "INNER_MULTI_QUEUE_OFFSET";
+ public static final String PROPERTY_TRACE_CONTEXT = "TRACE_CONTEXT";
+ public static final String PROPERTY_TIMER_DELAY_SEC = "TIMER_DELAY_SEC";
+ public static final String PROPERTY_TIMER_DELIVER_MS = "TIMER_DELIVER_MS";
+ public static final String PROPERTY_BORN_HOST = "__BORNHOST";
/**
* property which name starts with "__RMQ.TRANSIENT." is called transient one that will not stored in broker disks.
@@ -123,5 +127,6 @@ public class MessageConst {
STRING_HASH_SET.add(PROPERTY_CLUSTER);
STRING_HASH_SET.add(PROPERTY_MESSAGE_TYPE);
STRING_HASH_SET.add(PROPERTY_INNER_MULTI_QUEUE_OFFSET);
+ STRING_HASH_SET.add(PROPERTY_BORN_HOST);
}
}
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
index 02e388ba4c..a8fea34d94 100644
--- 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
@@ -16,6 +16,7 @@
*/
package org.apache.rocketmq.common.protocol.header;
+import com.google.common.base.MoreObjects;
import org.apache.rocketmq.remoting.CommandCustomHeader;
import org.apache.rocketmq.remoting.annotation.CFNotNull;
import org.apache.rocketmq.remoting.exception.RemotingCommandException;
@@ -80,6 +81,12 @@ public class AckMessageRequestHeader implements CommandCustomHeader {
@Override
public String toString() {
- return topic + "," + this.consumerGroup + "," + this.queueId + "," + this.offset + "," + this.extraInfo;
+ return MoreObjects.toStringHelper(this)
+ .add("consumerGroup", consumerGroup)
+ .add("topic", topic)
+ .add("queueId", queueId)
+ .add("extraInfo", extraInfo)
+ .add("offset", offset)
+ .toString();
}
}
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
index a586e490cf..918a2304b7 100644
--- 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
@@ -16,6 +16,7 @@
*/
package org.apache.rocketmq.common.protocol.header;
+import com.google.common.base.MoreObjects;
import org.apache.rocketmq.remoting.CommandCustomHeader;
import org.apache.rocketmq.remoting.annotation.CFNotNull;
import org.apache.rocketmq.remoting.exception.RemotingCommandException;
@@ -94,4 +95,14 @@ public class ChangeInvisibleTimeRequestHeader implements CommandCustomHeader {
this.queueId = queueId;
}
+ @Override public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("consumerGroup", consumerGroup)
+ .add("topic", topic)
+ .add("queueId", queueId)
+ .add("extraInfo", extraInfo)
+ .add("offset", offset)
+ .add("invisibleTime", invisibleTime)
+ .toString();
+ }
}
diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/CheckTransactionStateRequestHeader.java b/common/src/main/java/org/apache/rocketmq/common/protocol/header/CheckTransactionStateRequestHeader.java
index 6cba71c7e9..d62802c06a 100644
--- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/CheckTransactionStateRequestHeader.java
+++ b/common/src/main/java/org/apache/rocketmq/common/protocol/header/CheckTransactionStateRequestHeader.java
@@ -20,6 +20,7 @@
*/
package org.apache.rocketmq.common.protocol.header;
+import com.google.common.base.MoreObjects;
import org.apache.rocketmq.remoting.CommandCustomHeader;
import org.apache.rocketmq.remoting.annotation.CFNotNull;
import org.apache.rocketmq.remoting.exception.RemotingCommandException;
@@ -76,4 +77,15 @@ public class CheckTransactionStateRequestHeader implements CommandCustomHeader {
public void setOffsetMsgId(String offsetMsgId) {
this.offsetMsgId = offsetMsgId;
}
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("tranStateTableOffset", tranStateTableOffset)
+ .add("commitLogOffset", commitLogOffset)
+ .add("msgId", msgId)
+ .add("transactionId", transactionId)
+ .add("offsetMsgId", offsetMsgId)
+ .toString();
+ }
}
diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/CloneGroupOffsetRequestHeader.java b/common/src/main/java/org/apache/rocketmq/common/protocol/header/CloneGroupOffsetRequestHeader.java
index afc017b2a6..3b478f8a11 100644
--- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/CloneGroupOffsetRequestHeader.java
+++ b/common/src/main/java/org/apache/rocketmq/common/protocol/header/CloneGroupOffsetRequestHeader.java
@@ -20,6 +20,7 @@
*/
package org.apache.rocketmq.common.protocol.header;
+import com.google.common.base.MoreObjects;
import org.apache.rocketmq.remoting.CommandCustomHeader;
import org.apache.rocketmq.remoting.annotation.CFNotNull;
import org.apache.rocketmq.remoting.exception.RemotingCommandException;
@@ -68,4 +69,14 @@ public class CloneGroupOffsetRequestHeader implements CommandCustomHeader {
public void setOffline(boolean offline) {
this.offline = offline;
}
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("srcGroup", srcGroup)
+ .add("destGroup", destGroup)
+ .add("topic", topic)
+ .add("offline", offline)
+ .toString();
+ }
}
diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/ConsumeMessageDirectlyResultRequestHeader.java b/common/src/main/java/org/apache/rocketmq/common/protocol/header/ConsumeMessageDirectlyResultRequestHeader.java
index 7bad63985d..a7dc28e256 100644
--- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/ConsumeMessageDirectlyResultRequestHeader.java
+++ b/common/src/main/java/org/apache/rocketmq/common/protocol/header/ConsumeMessageDirectlyResultRequestHeader.java
@@ -17,6 +17,7 @@
package org.apache.rocketmq.common.protocol.header;
+import com.google.common.base.MoreObjects;
import org.apache.rocketmq.remoting.CommandCustomHeader;
import org.apache.rocketmq.remoting.annotation.CFNotNull;
import org.apache.rocketmq.remoting.annotation.CFNullable;
@@ -97,4 +98,17 @@ public class ConsumeMessageDirectlyResultRequestHeader implements CommandCustomH
public void setGroupSysFlag(Integer groupSysFlag) {
this.groupSysFlag = groupSysFlag;
}
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("consumerGroup", consumerGroup)
+ .add("clientId", clientId)
+ .add("msgId", msgId)
+ .add("brokerName", brokerName)
+ .add("topic", topic)
+ .add("topicSysFlag", topicSysFlag)
+ .add("groupSysFlag", groupSysFlag)
+ .toString();
+ }
}
diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/ConsumerSendMsgBackRequestHeader.java b/common/src/main/java/org/apache/rocketmq/common/protocol/header/ConsumerSendMsgBackRequestHeader.java
index bd8fbb44ca..3d65f23921 100644
--- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/ConsumerSendMsgBackRequestHeader.java
+++ b/common/src/main/java/org/apache/rocketmq/common/protocol/header/ConsumerSendMsgBackRequestHeader.java
@@ -17,6 +17,7 @@
package org.apache.rocketmq.common.protocol.header;
+import com.google.common.base.MoreObjects;
import org.apache.rocketmq.remoting.CommandCustomHeader;
import org.apache.rocketmq.remoting.annotation.CFNotNull;
import org.apache.rocketmq.remoting.annotation.CFNullable;
@@ -98,7 +99,14 @@ public class ConsumerSendMsgBackRequestHeader implements CommandCustomHeader {
@Override
public String toString() {
- return "ConsumerSendMsgBackRequestHeader [group=" + group + ", originTopic=" + originTopic + ", originMsgId=" + originMsgId
- + ", delayLevel=" + delayLevel + ", unitMode=" + unitMode + ", maxReconsumeTimes=" + maxReconsumeTimes + "]";
+ return MoreObjects.toStringHelper(this)
+ .add("offset", offset)
+ .add("group", group)
+ .add("delayLevel", delayLevel)
+ .add("originMsgId", originMsgId)
+ .add("originTopic", originTopic)
+ .add("unitMode", unitMode)
+ .add("maxReconsumeTimes", maxReconsumeTimes)
+ .toString();
}
}
diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/CreateAccessConfigRequestHeader.java b/common/src/main/java/org/apache/rocketmq/common/protocol/header/CreateAccessConfigRequestHeader.java
index 36990fcf64..09a2a0c667 100644
--- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/CreateAccessConfigRequestHeader.java
+++ b/common/src/main/java/org/apache/rocketmq/common/protocol/header/CreateAccessConfigRequestHeader.java
@@ -17,6 +17,7 @@
package org.apache.rocketmq.common.protocol.header;
+import com.google.common.base.MoreObjects;
import org.apache.rocketmq.remoting.CommandCustomHeader;
import org.apache.rocketmq.remoting.annotation.CFNotNull;
import org.apache.rocketmq.remoting.exception.RemotingCommandException;
@@ -110,4 +111,18 @@ public class CreateAccessConfigRequestHeader implements CommandCustomHeader {
public void setGroupPerms(String groupPerms) {
this.groupPerms = groupPerms;
}
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("accessKey", accessKey)
+ .add("secretKey", secretKey)
+ .add("whiteRemoteAddress", whiteRemoteAddress)
+ .add("admin", admin)
+ .add("defaultTopicPerm", defaultTopicPerm)
+ .add("defaultGroupPerm", defaultGroupPerm)
+ .add("topicPerms", topicPerms)
+ .add("groupPerms", groupPerms)
+ .toString();
+ }
}
diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/CreateTopicRequestHeader.java b/common/src/main/java/org/apache/rocketmq/common/protocol/header/CreateTopicRequestHeader.java
index c3c59d4950..43859410ae 100644
--- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/CreateTopicRequestHeader.java
+++ b/common/src/main/java/org/apache/rocketmq/common/protocol/header/CreateTopicRequestHeader.java
@@ -20,6 +20,7 @@
*/
package org.apache.rocketmq.common.protocol.header;
+import com.google.common.base.MoreObjects;
import org.apache.rocketmq.common.TopicFilterType;
import org.apache.rocketmq.remoting.CommandCustomHeader;
import org.apache.rocketmq.remoting.annotation.CFNotNull;
@@ -139,4 +140,20 @@ public class CreateTopicRequestHeader implements CommandCustomHeader {
public void setAttributes(String attributes) {
this.attributes = attributes;
}
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("topic", topic)
+ .add("defaultTopic", defaultTopic)
+ .add("readQueueNums", readQueueNums)
+ .add("writeQueueNums", writeQueueNums)
+ .add("perm", perm)
+ .add("topicFilterType", topicFilterType)
+ .add("topicSysFlag", topicSysFlag)
+ .add("order", order)
+ .add("attributes", attributes)
+ .add("force", force)
+ .toString();
+ }
}
diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/EndTransactionRequestHeader.java b/common/src/main/java/org/apache/rocketmq/common/protocol/header/EndTransactionRequestHeader.java
index 87661c320a..80fdc3d4a6 100644
--- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/EndTransactionRequestHeader.java
+++ b/common/src/main/java/org/apache/rocketmq/common/protocol/header/EndTransactionRequestHeader.java
@@ -17,6 +17,7 @@
package org.apache.rocketmq.common.protocol.header;
+import com.google.common.base.MoreObjects;
import org.apache.rocketmq.common.sysflag.MessageSysFlag;
import org.apache.rocketmq.remoting.CommandCustomHeader;
import org.apache.rocketmq.remoting.annotation.CFNotNull;
@@ -118,14 +119,14 @@ public class EndTransactionRequestHeader implements CommandCustomHeader {
@Override
public String toString() {
- return "EndTransactionRequestHeader{" +
- "producerGroup='" + producerGroup + '\'' +
- ", tranStateTableOffset=" + tranStateTableOffset +
- ", commitLogOffset=" + commitLogOffset +
- ", commitOrRollback=" + commitOrRollback +
- ", fromTransactionCheck=" + fromTransactionCheck +
- ", msgId='" + msgId + '\'' +
- ", transactionId='" + transactionId + '\'' +
- '}';
+ return MoreObjects.toStringHelper(this)
+ .add("producerGroup", producerGroup)
+ .add("tranStateTableOffset", tranStateTableOffset)
+ .add("commitLogOffset", commitLogOffset)
+ .add("commitOrRollback", commitOrRollback)
+ .add("fromTransactionCheck", fromTransactionCheck)
+ .add("msgId", msgId)
+ .add("transactionId", transactionId)
+ .toString();
}
}
diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumeStatsRequestHeader.java b/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumeStatsRequestHeader.java
index 6ba069e1ff..69a2fc60d0 100644
--- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumeStatsRequestHeader.java
+++ b/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumeStatsRequestHeader.java
@@ -16,6 +16,7 @@
*/
package org.apache.rocketmq.common.protocol.header;
+import com.google.common.base.MoreObjects;
import org.apache.rocketmq.remoting.CommandCustomHeader;
import org.apache.rocketmq.remoting.annotation.CFNotNull;
import org.apache.rocketmq.remoting.exception.RemotingCommandException;
@@ -44,4 +45,12 @@ public class GetConsumeStatsRequestHeader implements CommandCustomHeader {
public void setTopic(String topic) {
this.topic = topic;
}
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("consumerGroup", consumerGroup)
+ .add("topic", topic)
+ .toString();
+ }
}
diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumerListByGroupRequestHeader.java b/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumerListByGroupRequestHeader.java
index 3523a52cae..ecab653150 100644
--- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumerListByGroupRequestHeader.java
+++ b/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumerListByGroupRequestHeader.java
@@ -17,6 +17,7 @@
package org.apache.rocketmq.common.protocol.header;
+import com.google.common.base.MoreObjects;
import org.apache.rocketmq.remoting.CommandCustomHeader;
import org.apache.rocketmq.remoting.annotation.CFNotNull;
import org.apache.rocketmq.remoting.exception.RemotingCommandException;
@@ -36,4 +37,11 @@ public class GetConsumerListByGroupRequestHeader implements CommandCustomHeader
public void setConsumerGroup(String consumerGroup) {
this.consumerGroup = consumerGroup;
}
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("consumerGroup", consumerGroup)
+ .toString();
+ }
}
diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumerRunningInfoRequestHeader.java b/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumerRunningInfoRequestHeader.java
index 1bbbd900c5..840716f5a3 100644
--- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumerRunningInfoRequestHeader.java
+++ b/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumerRunningInfoRequestHeader.java
@@ -17,6 +17,7 @@
package org.apache.rocketmq.common.protocol.header;
+import com.google.common.base.MoreObjects;
import org.apache.rocketmq.remoting.CommandCustomHeader;
import org.apache.rocketmq.remoting.annotation.CFNotNull;
import org.apache.rocketmq.remoting.annotation.CFNullable;
@@ -57,4 +58,13 @@ public class GetConsumerRunningInfoRequestHeader implements CommandCustomHeader
public void setJstackEnable(boolean jstackEnable) {
this.jstackEnable = jstackEnable;
}
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("consumerGroup", consumerGroup)
+ .add("clientId", clientId)
+ .add("jstackEnable", jstackEnable)
+ .toString();
+ }
}
diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumerStatusRequestHeader.java b/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumerStatusRequestHeader.java
index ca26a869c6..0a983fecf6 100644
--- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumerStatusRequestHeader.java
+++ b/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetConsumerStatusRequestHeader.java
@@ -17,6 +17,7 @@
package org.apache.rocketmq.common.protocol.header;
+import com.google.common.base.MoreObjects;
import org.apache.rocketmq.remoting.CommandCustomHeader;
import org.apache.rocketmq.remoting.annotation.CFNotNull;
import org.apache.rocketmq.remoting.annotation.CFNullable;
@@ -57,4 +58,13 @@ public class GetConsumerStatusRequestHeader implements CommandCustomHeader {
public void setClientAddr(String clientAddr) {
this.clientAddr = clientAddr;
}
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("topic", topic)
+ .add("group", group)
+ .add("clientAddr", clientAddr)
+ .toString();
+ }
}
diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetMaxOffsetRequestHeader.java b/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetMaxOffsetRequestHeader.java
index f58e050da7..f98e8500dd 100644
--- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetMaxOffsetRequestHeader.java
+++ b/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetMaxOffsetRequestHeader.java
@@ -20,6 +20,7 @@
*/
package org.apache.rocketmq.common.protocol.header;
+import com.google.common.base.MoreObjects;
import org.apache.rocketmq.common.rpc.TopicQueueRequestHeader;
import org.apache.rocketmq.remoting.annotation.CFNotNull;
import org.apache.rocketmq.remoting.annotation.CFNullable;
@@ -71,4 +72,13 @@ public class GetMaxOffsetRequestHeader extends TopicQueueRequestHeader {
public void setCommitted(final boolean committed) {
this.committed = committed;
}
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("topic", topic)
+ .add("queueId", queueId)
+ .add("committed", committed)
+ .toString();
+ }
}
diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetMinOffsetRequestHeader.java b/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetMinOffsetRequestHeader.java
index 70189b74b5..d54c4aa41c 100644
--- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetMinOffsetRequestHeader.java
+++ b/common/src/main/java/org/apache/rocketmq/common/protocol/header/GetMinOffsetRequestHeader.java
@@ -20,6 +20,7 @@
*/
package org.apache.rocketmq.common.protocol.header;
+import com.google.common.base.MoreObjects;
import org.apache.rocketmq.common.rpc.TopicQueueRequestHeader;
import org.apache.rocketmq.remoting.annotation.CFNotNull;
import org.apache.rocketmq.remoting.exception.RemotingCommandException;
@@ -53,4 +54,12 @@ public class GetMinOffsetRequestHeader extends TopicQueueRequestHeader {
public void setQueueId(Integer queueId) {
this.queueId = queueId;
}
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("topic", topic)
+ .add("queueId", queueId)
+ .toString();
+ }
}
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
index 4d151a23e0..a3a186a917 100644
--- 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
@@ -16,6 +16,7 @@
*/
package org.apache.rocketmq.common.protocol.header;
+import com.google.common.base.MoreObjects;
import org.apache.rocketmq.remoting.CommandCustomHeader;
import org.apache.rocketmq.remoting.annotation.CFNotNull;
import org.apache.rocketmq.remoting.exception.RemotingCommandException;
@@ -152,4 +153,21 @@ public class PopMessageRequestHeader implements CommandCustomHeader {
public boolean isOrder() {
return this.order != null && this.order.booleanValue();
}
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("consumerGroup", consumerGroup)
+ .add("topic", topic)
+ .add("queueId", queueId)
+ .add("maxMsgNums", maxMsgNums)
+ .add("invisibleTime", invisibleTime)
+ .add("pollTime", pollTime)
+ .add("bornTime", bornTime)
+ .add("initMode", initMode)
+ .add("expType", expType)
+ .add("exp", exp)
+ .add("order", order)
+ .toString();
+ }
}
diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/PullMessageRequestHeader.java b/common/src/main/java/org/apache/rocketmq/common/protocol/header/PullMessageRequestHeader.java
index 486efdfb55..317dc5f4e6 100644
--- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/PullMessageRequestHeader.java
+++ b/common/src/main/java/org/apache/rocketmq/common/protocol/header/PullMessageRequestHeader.java
@@ -20,6 +20,7 @@
*/
package org.apache.rocketmq.common.protocol.header;
+import com.google.common.base.MoreObjects;
import java.util.HashMap;
import org.apache.rocketmq.common.rpc.TopicQueueRequestHeader;
import org.apache.rocketmq.remoting.annotation.CFNotNull;
@@ -267,4 +268,22 @@ public class PullMessageRequestHeader extends TopicQueueRequestHeader implements
public void setMaxMsgBytes(Integer maxMsgBytes) {
this.maxMsgBytes = maxMsgBytes;
}
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("consumerGroup", consumerGroup)
+ .add("topic", topic)
+ .add("queueId", queueId)
+ .add("queueOffset", queueOffset)
+ .add("maxMsgBytes", maxMsgBytes)
+ .add("maxMsgNums", maxMsgNums)
+ .add("sysFlag", sysFlag)
+ .add("commitOffset", commitOffset)
+ .add("suspendTimeoutMillis", suspendTimeoutMillis)
+ .add("subscription", subscription)
+ .add("subVersion", subVersion)
+ .add("expressionType", expressionType)
+ .toString();
+ }
}
diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/SearchOffsetRequestHeader.java b/common/src/main/java/org/apache/rocketmq/common/protocol/header/SearchOffsetRequestHeader.java
index c8291d2665..3753e062b7 100644
--- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/SearchOffsetRequestHeader.java
+++ b/common/src/main/java/org/apache/rocketmq/common/protocol/header/SearchOffsetRequestHeader.java
@@ -20,6 +20,7 @@
*/
package org.apache.rocketmq.common.protocol.header;
+import com.google.common.base.MoreObjects;
import org.apache.rocketmq.common.rpc.TopicQueueRequestHeader;
import org.apache.rocketmq.remoting.annotation.CFNotNull;
import org.apache.rocketmq.remoting.exception.RemotingCommandException;
@@ -65,4 +66,12 @@ public class SearchOffsetRequestHeader extends TopicQueueRequestHeader {
this.timestamp = timestamp;
}
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("topic", topic)
+ .add("queueId", queueId)
+ .add("timestamp", timestamp)
+ .toString();
+ }
}
diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/SendMessageRequestHeader.java b/common/src/main/java/org/apache/rocketmq/common/protocol/header/SendMessageRequestHeader.java
index 808bc2d3d5..4fece199df 100644
--- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/SendMessageRequestHeader.java
+++ b/common/src/main/java/org/apache/rocketmq/common/protocol/header/SendMessageRequestHeader.java
@@ -20,10 +20,14 @@
*/
package org.apache.rocketmq.common.protocol.header;
+import com.google.common.base.MoreObjects;
+import java.util.HashMap;
+import org.apache.rocketmq.common.protocol.RequestCode;
import org.apache.rocketmq.common.rpc.TopicQueueRequestHeader;
import org.apache.rocketmq.remoting.annotation.CFNotNull;
import org.apache.rocketmq.remoting.annotation.CFNullable;
import org.apache.rocketmq.remoting.exception.RemotingCommandException;
+import org.apache.rocketmq.remoting.protocol.RemotingCommand;
public class SendMessageRequestHeader extends TopicQueueRequestHeader {
@CFNotNull
@@ -163,4 +167,119 @@ public class SendMessageRequestHeader extends TopicQueueRequestHeader {
public void setBatch(boolean batch) {
this.batch = batch;
}
+
+ public static SendMessageRequestHeader parseRequestHeader(RemotingCommand request) throws RemotingCommandException {
+ SendMessageRequestHeaderV2 requestHeaderV2 = null;
+ SendMessageRequestHeader requestHeader = null;
+ switch (request.getCode()) {
+ case RequestCode.SEND_BATCH_MESSAGE:
+ case RequestCode.SEND_MESSAGE_V2:
+ requestHeaderV2 =
+ (SendMessageRequestHeaderV2) request
+ .decodeCommandCustomHeader(SendMessageRequestHeaderV2.class);
+ case RequestCode.SEND_MESSAGE:
+ if (null == requestHeaderV2) {
+ requestHeader =
+ (SendMessageRequestHeader) request
+ .decodeCommandCustomHeader(SendMessageRequestHeader.class);
+ } else {
+ requestHeader = SendMessageRequestHeaderV2.createSendMessageRequestHeaderV1(requestHeaderV2);
+ }
+ default:
+ break;
+ }
+ return requestHeader;
+ }
+
+ public static SendMessageRequestHeaderV2 decodeSendMessageHeaderV2(RemotingCommand request)
+ throws RemotingCommandException {
+ SendMessageRequestHeaderV2 r = new SendMessageRequestHeaderV2();
+ HashMap fields = request.getExtFields();
+ if (fields == null) {
+ throw new RemotingCommandException("the ext fields is null");
+ }
+
+ String s = fields.get("a");
+ checkNotNull(s, "the custom field is null");
+ r.setA(s);
+
+ s = fields.get("b");
+ checkNotNull(s, "the custom field is null");
+ r.setB(s);
+
+ s = fields.get("c");
+ checkNotNull(s, "the custom field is null");
+ r.setC(s);
+
+ s = fields.get("d");
+ checkNotNull(s, "the custom field is null");
+ r.setD(Integer.parseInt(s));
+
+ s = fields.get("e");
+ checkNotNull(s, "the custom field is null");
+ r.setE(Integer.parseInt(s));
+
+ s = fields.get("f");
+ checkNotNull(s, "the custom field is null");
+ r.setF(Integer.parseInt(s));
+
+ s = fields.get("g");
+ checkNotNull(s, "the custom field is null");
+ r.setG(Long.parseLong(s));
+
+ s = fields.get("h");
+ checkNotNull(s, "the custom field is null");
+ r.setH(Integer.parseInt(s));
+
+ s = fields.get("i");
+ if (s != null) {
+ r.setI(s);
+ }
+
+ s = fields.get("j");
+ if (s != null) {
+ r.setJ(Integer.parseInt(s));
+ }
+
+ s = fields.get("k");
+ if (s != null) {
+ r.setK(Boolean.parseBoolean(s));
+ }
+
+ s = fields.get("l");
+ if (s != null) {
+ r.setL(Integer.parseInt(s));
+ }
+
+ s = fields.get("m");
+ if (s != null) {
+ r.setM(Boolean.parseBoolean(s));
+ }
+ return r;
+ }
+
+ private static void checkNotNull(String s, String msg) throws RemotingCommandException {
+ if (s == null) {
+ throw new RemotingCommandException(msg);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("producerGroup", producerGroup)
+ .add("topic", topic)
+ .add("defaultTopic", defaultTopic)
+ .add("defaultTopicQueueNums", defaultTopicQueueNums)
+ .add("queueId", queueId)
+ .add("sysFlag", sysFlag)
+ .add("bornTimestamp", bornTimestamp)
+ .add("flag", flag)
+ .add("properties", properties)
+ .add("reconsumeTimes", reconsumeTimes)
+ .add("unitMode", unitMode)
+ .add("batch", batch)
+ .add("maxReconsumeTimes", maxReconsumeTimes)
+ .toString();
+ }
}
diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/SendMessageRequestHeaderV2.java b/common/src/main/java/org/apache/rocketmq/common/protocol/header/SendMessageRequestHeaderV2.java
index ff9457e283..f4771252eb 100644
--- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/SendMessageRequestHeaderV2.java
+++ b/common/src/main/java/org/apache/rocketmq/common/protocol/header/SendMessageRequestHeaderV2.java
@@ -20,6 +20,7 @@ package org.apache.rocketmq.common.protocol.header;
import java.util.HashMap;
import org.apache.rocketmq.remoting.protocol.FastCodesHeader;
+import com.google.common.base.MoreObjects;
import org.apache.rocketmq.remoting.CommandCustomHeader;
import org.apache.rocketmq.remoting.annotation.CFNotNull;
import org.apache.rocketmq.remoting.annotation.CFNullable;
@@ -288,4 +289,23 @@ public class SendMessageRequestHeaderV2 implements CommandCustomHeader, FastCode
public void setM(boolean m) {
this.m = m;
}
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("a", a)
+ .add("b", b)
+ .add("c", c)
+ .add("d", d)
+ .add("e", e)
+ .add("f", f)
+ .add("g", g)
+ .add("h", h)
+ .add("i", i)
+ .add("j", j)
+ .add("k", k)
+ .add("l", l)
+ .add("m", m)
+ .toString();
+ }
}
\ No newline at end of file
diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/header/UpdateConsumerOffsetRequestHeader.java b/common/src/main/java/org/apache/rocketmq/common/protocol/header/UpdateConsumerOffsetRequestHeader.java
index 11eccd5c1e..77af812184 100644
--- a/common/src/main/java/org/apache/rocketmq/common/protocol/header/UpdateConsumerOffsetRequestHeader.java
+++ b/common/src/main/java/org/apache/rocketmq/common/protocol/header/UpdateConsumerOffsetRequestHeader.java
@@ -20,6 +20,7 @@
*/
package org.apache.rocketmq.common.protocol.header;
+import com.google.common.base.MoreObjects;
import org.apache.rocketmq.common.rpc.TopicQueueRequestHeader;
import org.apache.rocketmq.remoting.annotation.CFNotNull;
import org.apache.rocketmq.remoting.exception.RemotingCommandException;
@@ -73,4 +74,14 @@ public class UpdateConsumerOffsetRequestHeader extends TopicQueueRequestHeader {
public void setCommitOffset(Long commitOffset) {
this.commitOffset = commitOffset;
}
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("consumerGroup", consumerGroup)
+ .add("topic", topic)
+ .add("queueId", queueId)
+ .add("commitOffset", commitOffset)
+ .toString();
+ }
}
diff --git a/common/src/main/java/org/apache/rocketmq/common/subscription/CustomizedRetryPolicy.java b/common/src/main/java/org/apache/rocketmq/common/subscription/CustomizedRetryPolicy.java
index 1fe1983f32..c15e16c546 100644
--- a/common/src/main/java/org/apache/rocketmq/common/subscription/CustomizedRetryPolicy.java
+++ b/common/src/main/java/org/apache/rocketmq/common/subscription/CustomizedRetryPolicy.java
@@ -48,6 +48,13 @@ public class CustomizedRetryPolicy implements RetryPolicy {
TimeUnit.HOURS.toMillis(2)
};
+ public CustomizedRetryPolicy() {
+ }
+
+ public CustomizedRetryPolicy(long[] next) {
+ this.next = next;
+ }
+
public long[] getNext() {
return next;
}
diff --git a/common/src/main/java/org/apache/rocketmq/common/subscription/ExponentialRetryPolicy.java b/common/src/main/java/org/apache/rocketmq/common/subscription/ExponentialRetryPolicy.java
index f1c15e2967..6f212b591e 100644
--- a/common/src/main/java/org/apache/rocketmq/common/subscription/ExponentialRetryPolicy.java
+++ b/common/src/main/java/org/apache/rocketmq/common/subscription/ExponentialRetryPolicy.java
@@ -20,14 +20,20 @@ package org.apache.rocketmq.common.subscription;
import com.google.common.base.MoreObjects;
import java.util.concurrent.TimeUnit;
-/**
- * next delay time = min(max, initial * multiplier^reconsumeTimes)
- */
public class ExponentialRetryPolicy implements RetryPolicy {
private long initial = TimeUnit.SECONDS.toMillis(5);
private long max = TimeUnit.HOURS.toMillis(2);
private long multiplier = 2;
+ public ExponentialRetryPolicy() {
+ }
+
+ public ExponentialRetryPolicy(long initial, long max, long multiplier) {
+ this.initial = initial;
+ this.max = max;
+ this.multiplier = multiplier;
+ }
+
public long getInitial() {
return initial;
}
diff --git a/common/src/main/java/org/apache/rocketmq/common/thread/ThreadPoolMonitor.java b/common/src/main/java/org/apache/rocketmq/common/thread/ThreadPoolMonitor.java
new file mode 100644
index 0000000000..e5bb6a394c
--- /dev/null
+++ b/common/src/main/java/org/apache/rocketmq/common/thread/ThreadPoolMonitor.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.common.thread;
+
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import org.apache.rocketmq.common.UtilAll;
+import org.apache.rocketmq.logging.InternalLogger;
+import org.apache.rocketmq.logging.InternalLoggerFactory;
+
+public class ThreadPoolMonitor {
+ private static InternalLogger jstackLogger = InternalLoggerFactory.getLogger(ThreadPoolMonitor.class);
+ private static InternalLogger waterMarkLogger = InternalLoggerFactory.getLogger(ThreadPoolMonitor.class);
+
+ private static final List MONITOR_EXECUTOR = new CopyOnWriteArrayList<>();
+ private static final ScheduledExecutorService MONITOR_SCHEDULED = Executors.newSingleThreadScheduledExecutor(
+ new ThreadFactoryBuilder().setNameFormat("ThreadPoolMonitor-%d").build()
+ );
+
+ private static volatile long threadPoolStatusPeriodTime = TimeUnit.SECONDS.toMillis(3);
+ private static volatile boolean enablePrintJstack = true;
+ private static volatile long jstackPeriodTime = 60000;
+ private static volatile long jstackTime = System.currentTimeMillis();
+
+ public static void config(InternalLogger jstackLoggerConfig, InternalLogger waterMarkLoggerConfig,
+ boolean enablePrintJstack, long jstackPeriodTimeConfig, long threadPoolStatusPeriodTimeConfig) {
+ jstackLogger = jstackLoggerConfig;
+ waterMarkLogger = waterMarkLoggerConfig;
+ threadPoolStatusPeriodTime = threadPoolStatusPeriodTimeConfig;
+ ThreadPoolMonitor.enablePrintJstack = enablePrintJstack;
+ jstackPeriodTime = jstackPeriodTimeConfig;
+ }
+
+ public static ThreadPoolExecutor createAndMonitor(int corePoolSize,
+ int maximumPoolSize,
+ long keepAliveTime,
+ TimeUnit unit,
+ String name,
+ int queueCapacity) {
+ return createAndMonitor(corePoolSize, maximumPoolSize, keepAliveTime, unit, name, queueCapacity, Collections.emptyList());
+ }
+
+ public static ThreadPoolExecutor createAndMonitor(int corePoolSize,
+ int maximumPoolSize,
+ long keepAliveTime,
+ TimeUnit unit,
+ String name,
+ int queueCapacity,
+ ThreadPoolStatusMonitor... threadPoolStatusMonitors) {
+ return createAndMonitor(corePoolSize, maximumPoolSize, keepAliveTime, unit, name, queueCapacity,
+ Lists.newArrayList(threadPoolStatusMonitors));
+ }
+
+ public static ThreadPoolExecutor createAndMonitor(int corePoolSize,
+ int maximumPoolSize,
+ long keepAliveTime,
+ TimeUnit unit,
+ String name,
+ int queueCapacity,
+ List threadPoolStatusMonitors) {
+ ThreadPoolExecutor executor = new ThreadPoolExecutor(
+ corePoolSize,
+ maximumPoolSize,
+ keepAliveTime,
+ unit,
+ new LinkedBlockingQueue<>(queueCapacity),
+ new ThreadFactoryBuilder().setNameFormat(name + "-%d").build(),
+ new ThreadPoolExecutor.DiscardOldestPolicy());
+ List printers = Lists.newArrayList(new ThreadPoolQueueSizeMonitor(queueCapacity));
+ printers.addAll(threadPoolStatusMonitors);
+
+ MONITOR_EXECUTOR.add(ThreadPoolWrapper.builder()
+ .name(name)
+ .threadPoolExecutor(executor)
+ .statusPrinters(printers)
+ .build());
+ return executor;
+ }
+
+ public static void logThreadPoolStatus() {
+ for (ThreadPoolWrapper threadPoolWrapper : MONITOR_EXECUTOR) {
+ List monitors = threadPoolWrapper.getStatusPrinters();
+ for (ThreadPoolStatusMonitor monitor : monitors) {
+ double value = monitor.value(threadPoolWrapper.getThreadPoolExecutor());
+ waterMarkLogger.info("\t{}\t{}\t{}", threadPoolWrapper.getName(),
+ monitor.describe(),
+ value);
+
+ if (enablePrintJstack) {
+ if (monitor.needPrintJstack(threadPoolWrapper.getThreadPoolExecutor(), value) &&
+ System.currentTimeMillis() - jstackTime > jstackPeriodTime) {
+ jstackTime = System.currentTimeMillis();
+ jstackLogger.warn("jstack start\n{}", UtilAll.jstack());
+ }
+ }
+ }
+ }
+ }
+
+ public static void init() {
+ MONITOR_SCHEDULED.scheduleAtFixedRate(ThreadPoolMonitor::logThreadPoolStatus, 20,
+ threadPoolStatusPeriodTime, TimeUnit.MILLISECONDS);
+ }
+
+ public static void shutdown() {
+ MONITOR_SCHEDULED.shutdown();
+ }
+}
\ No newline at end of file
diff --git a/common/src/main/java/org/apache/rocketmq/common/thread/ThreadPoolQueueSizeMonitor.java b/common/src/main/java/org/apache/rocketmq/common/thread/ThreadPoolQueueSizeMonitor.java
new file mode 100644
index 0000000000..9e2e2f675c
--- /dev/null
+++ b/common/src/main/java/org/apache/rocketmq/common/thread/ThreadPoolQueueSizeMonitor.java
@@ -0,0 +1,44 @@
+/*
+ * 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.thread;
+
+import java.util.concurrent.ThreadPoolExecutor;
+
+public class ThreadPoolQueueSizeMonitor implements ThreadPoolStatusMonitor {
+
+ private final int maxQueueCapacity;
+
+ public ThreadPoolQueueSizeMonitor(int maxQueueCapacity) {
+ this.maxQueueCapacity = maxQueueCapacity;
+ }
+
+ @Override
+ public String describe() {
+ return "queueSize";
+ }
+
+ @Override
+ public double value(ThreadPoolExecutor executor) {
+ return executor.getQueue().size();
+ }
+
+ @Override
+ public boolean needPrintJstack(ThreadPoolExecutor executor, double value) {
+ return value > maxQueueCapacity * 0.85;
+ }
+}
diff --git a/common/src/main/java/org/apache/rocketmq/common/thread/ThreadPoolStatusMonitor.java b/common/src/main/java/org/apache/rocketmq/common/thread/ThreadPoolStatusMonitor.java
new file mode 100644
index 0000000000..548fec52ec
--- /dev/null
+++ b/common/src/main/java/org/apache/rocketmq/common/thread/ThreadPoolStatusMonitor.java
@@ -0,0 +1,29 @@
+/*
+ * 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.thread;
+
+import java.util.concurrent.ThreadPoolExecutor;
+
+public interface ThreadPoolStatusMonitor {
+
+ String describe();
+
+ double value(ThreadPoolExecutor executor);
+
+ boolean needPrintJstack(ThreadPoolExecutor executor, double value);
+}
\ No newline at end of file
diff --git a/common/src/main/java/org/apache/rocketmq/common/thread/ThreadPoolWrapper.java b/common/src/main/java/org/apache/rocketmq/common/thread/ThreadPoolWrapper.java
new file mode 100644
index 0000000000..3e5bbfe574
--- /dev/null
+++ b/common/src/main/java/org/apache/rocketmq/common/thread/ThreadPoolWrapper.java
@@ -0,0 +1,120 @@
+/*
+ * 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.thread;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+import java.util.List;
+import java.util.concurrent.ThreadPoolExecutor;
+
+public class ThreadPoolWrapper {
+ private String name;
+ private ThreadPoolExecutor threadPoolExecutor;
+ private List statusPrinters;
+
+ ThreadPoolWrapper(final String name, final ThreadPoolExecutor threadPoolExecutor,
+ final List statusPrinters) {
+ this.name = name;
+ this.threadPoolExecutor = threadPoolExecutor;
+ this.statusPrinters = statusPrinters;
+ }
+
+ public static class ThreadPoolWrapperBuilder {
+ private String name;
+ private ThreadPoolExecutor threadPoolExecutor;
+ private List statusPrinters;
+
+ ThreadPoolWrapperBuilder() {
+ }
+
+ public ThreadPoolWrapper.ThreadPoolWrapperBuilder name(final String name) {
+ this.name = name;
+ return this;
+ }
+
+ public ThreadPoolWrapper.ThreadPoolWrapperBuilder threadPoolExecutor(
+ final ThreadPoolExecutor threadPoolExecutor) {
+ this.threadPoolExecutor = threadPoolExecutor;
+ return this;
+ }
+
+ public ThreadPoolWrapper.ThreadPoolWrapperBuilder statusPrinters(
+ final List statusPrinters) {
+ this.statusPrinters = statusPrinters;
+ return this;
+ }
+
+ public ThreadPoolWrapper build() {
+ return new ThreadPoolWrapper(this.name, this.threadPoolExecutor, this.statusPrinters);
+ }
+
+ @java.lang.Override
+ public java.lang.String toString() {
+ return "ThreadPoolWrapper.ThreadPoolWrapperBuilder(name=" + this.name + ", threadPoolExecutor=" + this.threadPoolExecutor + ", statusPrinters=" + this.statusPrinters + ")";
+ }
+ }
+
+ public static ThreadPoolWrapper.ThreadPoolWrapperBuilder builder() {
+ return new ThreadPoolWrapper.ThreadPoolWrapperBuilder();
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public ThreadPoolExecutor getThreadPoolExecutor() {
+ return this.threadPoolExecutor;
+ }
+
+ public List getStatusPrinters() {
+ return this.statusPrinters;
+ }
+
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ public void setThreadPoolExecutor(final ThreadPoolExecutor threadPoolExecutor) {
+ this.threadPoolExecutor = threadPoolExecutor;
+ }
+
+ public void setStatusPrinters(final List statusPrinters) {
+ this.statusPrinters = statusPrinters;
+ }
+
+ @Override public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ ThreadPoolWrapper wrapper = (ThreadPoolWrapper) o;
+ return Objects.equal(name, wrapper.name) && Objects.equal(threadPoolExecutor, wrapper.threadPoolExecutor) && Objects.equal(statusPrinters, wrapper.statusPrinters);
+ }
+
+ @Override public int hashCode() {
+ return Objects.hashCode(name, threadPoolExecutor, statusPrinters);
+ }
+
+ @Override public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("name", name)
+ .add("threadPoolExecutor", threadPoolExecutor)
+ .add("statusPrinters", statusPrinters)
+ .toString();
+ }
+}
diff --git a/common/src/main/java/org/apache/rocketmq/common/utils/BinaryUtil.java b/common/src/main/java/org/apache/rocketmq/common/utils/BinaryUtil.java
new file mode 100644
index 0000000000..421adaca4d
--- /dev/null
+++ b/common/src/main/java/org/apache/rocketmq/common/utils/BinaryUtil.java
@@ -0,0 +1,46 @@
+/*
+ * 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.charset.Charset;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import org.apache.commons.codec.binary.Hex;
+
+public class BinaryUtil {
+ public static byte[] calculateMd5(byte[] binaryData) {
+ MessageDigest messageDigest = null;
+ try {
+ messageDigest = MessageDigest.getInstance("MD5");
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException("MD5 algorithm not found.");
+ }
+ messageDigest.update(binaryData);
+ return messageDigest.digest();
+ }
+
+ public static String generateMd5(String bodyStr) {
+ byte[] bytes = calculateMd5(bodyStr.getBytes(Charset.forName("UTF-8")));
+ return Hex.encodeHexString(bytes, false);
+ }
+
+ public static String generateMd5(byte[] content) {
+ byte[] bytes = calculateMd5(content);
+ return Hex.encodeHexString(bytes, false);
+ }
+}
\ No newline at end of file
diff --git a/distribution/bin/mqproxy b/distribution/bin/mqproxy
new file mode 100644
index 0000000000..9f0cb84ea0
--- /dev/null
+++ b/distribution/bin/mqproxy
@@ -0,0 +1,45 @@
+#!/bin/sh
+
+# 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.
+
+if [ -z "$ROCKETMQ_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ ROCKETMQ_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ ROCKETMQ_HOME=`cd "$ROCKETMQ_HOME" && pwd`
+
+ cd "$saveddir"
+fi
+
+export ROCKETMQ_HOME
+
+sh ${ROCKETMQ_HOME}/bin/runserver.sh org.apache.rocketmq.proxy.ProxyStartup $@
diff --git a/distribution/bin/mqproxy.cmd b/distribution/bin/mqproxy.cmd
new file mode 100644
index 0000000000..d5f58e4de3
--- /dev/null
+++ b/distribution/bin/mqproxy.cmd
@@ -0,0 +1,23 @@
+@echo off
+rem Licensed to the Apache Software Foundation (ASF) under one or more
+rem contributor license agreements. See the NOTICE file distributed with
+rem this work for additional information regarding copyright ownership.
+rem The ASF licenses this file to You under the Apache License, Version 2.0
+rem (the "License"); you may not use this file except in compliance with
+rem the License. You may obtain a copy of the License at
+rem
+rem http://www.apache.org/licenses/LICENSE-2.0
+rem
+rem Unless required by applicable law or agreed to in writing, software
+rem distributed under the License is distributed on an "AS IS" BASIS,
+rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+rem See the License for the specific language governing permissions and
+rem limitations under the License.
+
+if not exist "%ROCKETMQ_HOME%\bin\runserver.cmd" echo Please set the ROCKETMQ_HOME variable in your environment! & EXIT /B 1
+
+call "%ROCKETMQ_HOME%\bin\runserver.cmd" org.apache.rocketmq.proxy.ProxyStartup %*
+
+IF %ERRORLEVEL% EQU 0 (
+ ECHO "Proxy starts OK"
+)
\ No newline at end of file
diff --git a/distribution/bin/mqshutdown b/distribution/bin/mqshutdown
index d91fce9c5b..7ea6048e15 100644
--- a/distribution/bin/mqshutdown
+++ b/distribution/bin/mqshutdown
@@ -58,6 +58,20 @@ case $1 in
echo "Send shutdown request to mqnamesrv(${pid}) OK"
;;
+ proxy)
+
+ pid=`ps ax | grep -i 'org.apache.rocketmq.proxy.ProxyStartup' |grep java | grep -v grep | awk '{print $1}'`
+ if [ -z "$pid" ] ; then
+ echo "No mqproxy running."
+ exit -1;
+ fi
+
+ echo "The mqproxy(${pid}) is running..."
+
+ kill ${pid}
+
+ echo "Send shutdown request to mqproxy(${pid}) OK"
+ ;;
*)
- echo "Useage: mqshutdown broker | namesrv"
+ echo "Useage: mqshutdown broker | namesrv | proxy"
esac
diff --git a/distribution/conf/logback_proxy.xml b/distribution/conf/logback_proxy.xml
new file mode 100644
index 0000000000..ad862d53c7
--- /dev/null
+++ b/distribution/conf/logback_proxy.xml
@@ -0,0 +1,420 @@
+
+
+
+
+
+
+ ${user.home}/logs/rocketmqlogs/proxy.log
+ true
+
+ ${user.home}/logs/rocketmqlogs/otherdays/proxy.%i.log.gz
+ 1
+ 10
+
+
+ 128MB
+
+
+ %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n
+ UTF-8
+
+
+
+
+
+
+
+ ${user.home}/logs/rocketmqlogs/proxy_watermark.log
+ true
+
+ ${user.home}/logs/rocketmqlogs/otherdays/proxy_watermark.%i.log.gz
+ 1
+ 10
+
+
+ 128MB
+
+
+ %d{yyy-MM-dd HH:mm:ss,GMT+8}%m%n
+ UTF-8
+
+
+
+
+
+
+
+
+ ${user.home}/logs/rocketmqlogs/${brokerLogDir}/broker_default.log
+ true
+
+ ${user.home}/logs/rocketmqlogs/otherdays/${brokerLogDir}/broker_default.%i.log.gz
+ 1
+ 10
+
+
+ 100MB
+
+
+ %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n
+ UTF-8
+
+
+
+
+ ${user.home}/logs/rocketmqlogs/${brokerLogDir}/broker.log
+ true
+
+ ${user.home}/logs/rocketmqlogs/otherdays/${brokerLogDir}/broker.%i.log.gz
+ 1
+ 20
+
+
+ 128MB
+
+
+ %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n
+ UTF-8
+
+
+
+
+
+
+
+ ${user.home}/logs/rocketmqlogs/${brokerLogDir}/protection.log
+ true
+
+ ${user.home}/logs/rocketmqlogs/otherdays/${brokerLogDir}/protection.%i.log.gz
+ 1
+ 10
+
+
+ 100MB
+
+
+ %d{yyy-MM-dd HH:mm:ss,GMT+8} - %m%n
+ UTF-8
+
+
+
+
+
+
+
+ ${user.home}/logs/rocketmqlogs/${brokerLogDir}/watermark.log
+ true
+
+ ${user.home}/logs/rocketmqlogs/otherdays/${brokerLogDir}/watermark.%i.log.gz
+ 1
+ 10
+
+
+ 100MB
+
+
+ %d{yyy-MM-dd HH:mm:ss,GMT+8} - %m%n
+ UTF-8
+
+
+
+
+
+
+
+ ${user.home}/logs/rocketmqlogs/${brokerLogDir}/store.log
+ true
+
+ ${user.home}/logs/rocketmqlogs/otherdays/${brokerLogDir}/store.%i.log.gz
+ 1
+ 10
+
+
+ 128MB
+
+
+ %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n
+ UTF-8
+
+
+
+
+
+
+
+ ${user.home}/logs/rocketmqlogs/${brokerLogDir}/remoting.log
+ true
+
+ ${user.home}/logs/rocketmqlogs/otherdays/${brokerLogDir}/remoting.%i.log.gz
+ 1
+ 10
+
+
+ 100MB
+
+
+ %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n
+ UTF-8
+
+
+
+
+
+
+
+ ${user.home}/logs/rocketmqlogs/${brokerLogDir}/storeerror.log
+ true
+
+ ${user.home}/logs/rocketmqlogs/otherdays/${brokerLogDir}/storeerror.%i.log.gz
+ 1
+ 10
+
+
+ 100MB
+
+
+ %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n
+ UTF-8
+
+
+
+
+
+
+
+
+ ${user.home}/logs/rocketmqlogs/${brokerLogDir}/transaction.log
+ true
+
+ ${user.home}/logs/rocketmqlogs/otherdays/${brokerLogDir}/transaction.%i.log.gz
+ 1
+ 10
+
+
+ 100MB
+
+
+ %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n
+ UTF-8
+
+
+
+
+
+
+
+ ${user.home}/logs/rocketmqlogs/${brokerLogDir}/lock.log
+ true
+
+ ${user.home}/logs/rocketmqlogs/otherdays/${brokerLogDir}/lock.%i.log.gz
+ 1
+ 5
+
+
+ 100MB
+
+
+ %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n
+ UTF-8
+
+
+
+
+
+
+
+ ${user.home}/logs/rocketmqlogs/${brokerLogDir}/filter.log
+ true
+
+ ${user.home}/logs/rocketmqlogs/otherdays/${brokerLogDir}/filter.%i.log.gz
+ 1
+ 10
+
+
+ 100MB
+
+
+ %d{yyy-MM-dd HH:mm:ss,GMT+8} %p %t - %m%n
+ UTF-8
+
+
+
+
+
+
+
+ ${user.home}/logs/rocketmqlogs/${brokerLogDir}/stats.log
+ true
+
+ ${user.home}/logs/rocketmqlogs/otherdays/${brokerLogDir}/stats.%i.log.gz
+ 1
+ 5
+
+
+ 100MB
+
+
+ %d{yyy-MM-dd HH:mm:ss,GMT+8} %p - %m%n
+ UTF-8
+
+
+
+
+ ${user.home}/logs/rocketmqlogs/${brokerLogDir}/commercial.log
+ true
+
+ ${user.home}/logs/rocketmqlogs/otherdays/${brokerLogDir}/commercial.%i.log.gz
+ 1
+ 10
+
+
+ 500MB
+
+
+
+
+ ${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
+
+ %d{yyy-MM-dd HH\:mm\:ss,GMT+8} %p %t - %m%n
+ UTF-8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/distribution/conf/rmq-proxy.json b/distribution/conf/rmq-proxy.json
new file mode 100644
index 0000000000..077404aaa4
--- /dev/null
+++ b/distribution/conf/rmq-proxy.json
@@ -0,0 +1,3 @@
+{
+
+}
\ No newline at end of file
diff --git a/distribution/pom.xml b/distribution/pom.xml
index 0dfda399ad..ce27e43dea 100644
--- a/distribution/pom.xml
+++ b/distribution/pom.xml
@@ -38,6 +38,10 @@
org.apache.rocketmq
rocketmq-broker
+
+ org.apache.rocketmq
+ rocketmq-proxy
+
org.apache.rocketmq
rocketmq-client
diff --git a/docs/en/README.md b/docs/en/README.md
index 97aef18a2d..e1e569e5a2 100644
--- a/docs/en/README.md
+++ b/docs/en/README.md
@@ -33,6 +33,9 @@
- [Cluster Deployment](dledger/deploy_guide.md):introduce how to deploy Dledger in cluster.
+- [Proxy Deployment](proxy/deploy_guide.md)
+ Introduce how to deploy proxy (both `Local` mode and `Cluster` mode).
+
### 5. Operation and maintenance management
- [Operation](operation.md):introduce RocketMQ's deployment modes that including single-master mode, multi-master mode, multi-master multi-slave mode and so on, as well as the usage of operation tool mqadmin.
diff --git a/docs/en/images/rocketmq_proxy_cluster_mode.png b/docs/en/images/rocketmq_proxy_cluster_mode.png
new file mode 100644
index 0000000000..1b4eb5eb31
Binary files /dev/null and b/docs/en/images/rocketmq_proxy_cluster_mode.png differ
diff --git a/docs/en/images/rocketmq_proxy_local_mode.png b/docs/en/images/rocketmq_proxy_local_mode.png
new file mode 100644
index 0000000000..12e6354a8e
Binary files /dev/null and b/docs/en/images/rocketmq_proxy_local_mode.png differ
diff --git a/docs/en/proxy/deploy_guide.md b/docs/en/proxy/deploy_guide.md
new file mode 100644
index 0000000000..84e5a3c171
--- /dev/null
+++ b/docs/en/proxy/deploy_guide.md
@@ -0,0 +1,37 @@
+# RocketMQ Proxy Deployment Guide
+
+## Overview
+
+RocketMQ Proxy supports two deployment modes, `Local` mode and `Cluster` mode.
+
+## Configuration
+
+The configuration applies to both the `Cluster` mode and `Local` mode, whose default path is
+distribution/conf/rmq-proxy.json directory.
+
+## `Cluster` mode
+
+* Set configuration field `nameSrvAddr`.
+* Set configuration field `proxyMode` to `cluster` (case insensitive).
+
+Run the command below.
+
+```shell
+nohup sh mqproxy &
+```
+
+The command will only run `Proxy` itself. It requires `Namesrv` and `Broker` components running.
+
+## `Local` mode
+
+* Set configuration field `nameSrvAddr`.
+* Set configuration field `proxyMode` to `local` (case insensitive).
+
+Run the command below.
+
+```shell
+nohup sh mqproxy &
+```
+
+The command will not only run `Proxy`, but also run `Broker`. It requires `Namesrv` only and there's no need for
+extra `Broker`.
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index f416700e31..abffd0a279 100644
--- a/pom.xml
+++ b/pom.xml
@@ -113,7 +113,7 @@
0.3.1-alpha
1.2.17
1.30
- 1.9
+ 1.13
2.17.1
1.7
1.5.2-2
@@ -124,10 +124,13 @@
6.0.53
1.0-beta-4
1.4.2
+ 2.0.0
+ 1.45.0
+ 3.20.1
4.13.2
- 2.6.0
+ 3.22.0
3.10.0
4.1.0
0.30
@@ -156,7 +159,6 @@
${project.basedir}/../test/target/jacoco-it.exec
file:**/generated-sources/**,**/test/**
-
@@ -176,6 +178,7 @@
acl
example
container
+ proxy
@@ -284,9 +287,11 @@
.gitignore
.travis.yml
+ README.md
CONTRIBUTING.md
bin/README.md
.github/**
+ src/test/resources/**
src/test/resources/certs/*
src/test/**/*.log
src/test/resources/META-INF/service/*
@@ -295,6 +300,7 @@
*/*.iml
docs/**
localbin/**
+ conf/rmq-proxy.json
@@ -476,6 +482,11 @@
+
+ ${project.groupId}
+ rocketmq-proto
+ ${rocketmq-proto.version}
+
${project.groupId}
rocketmq-client
@@ -551,6 +562,11 @@
rocketmq-example
${project.version}
+
+ ${project.groupId}
+ rocketmq-proxy
+ ${project.version}
+
org.slf4j
slf4j-api
@@ -712,13 +728,43 @@
org.awaitility
awaitility
${awaitility.version}
- test
com.google.truth
truth
${truth.version}
+
+ io.grpc
+ grpc-netty-shaded
+ ${grpc.version}
+
+
+ io.grpc
+ grpc-protobuf
+ ${grpc.version}
+
+
+ io.grpc
+ grpc-stub
+ ${grpc.version}
+
+
+ io.grpc
+ grpc-services
+ ${grpc.version}
+
+
+ io.grpc
+ grpc-testing
+ ${grpc.version}
+ test
+
+
+ com.google.protobuf
+ protobuf-java-util
+ ${protobuf-java-util.version}
+
@@ -727,16 +773,19 @@
junit
junit
${junit.version}
+ test
org.assertj
assertj-core
${assertj-core.version}
+ test
org.mockito
mockito-core
${mockito-core.version}
+ test
org.awaitility
diff --git a/proxy/README.md b/proxy/README.md
new file mode 100644
index 0000000000..936bd024b0
--- /dev/null
+++ b/proxy/README.md
@@ -0,0 +1,60 @@
+rocketmq-proxy
+--------
+
+## Introduction
+
+`RocketMQ Proxy` is a stateless component that makes full use of the newly introduced `pop` consumption mechanism to
+achieve stateless consumption behavior. `gRPC` protocol is supported by `Proxy` now and all the message types
+including `normal`, `fifo`, `transaction` and `delay` are supported via `pop` consumption mode. `Proxy` will translate
+incoming traffic into customized `Remoting` protocol to access `Broker` and `Namesrv`.
+
+`Proxy` also handles SSL, authorization/authentication and logging/tracing/metrics and is in charge of connection
+management and traffic governance.
+
+### Multi-language support.
+
+`gRPC` combined with `Protocol Buffer` makes it easy to implement clients with both `java` and other programming
+languages while the server side doesn't need extra work to support different programming languages.
+See [rocketmq-clients](https://github.com/apache/rocketmq-clients) for more information.
+
+### Multi-protocol support.
+
+With `Proxy` served as a traffic interface, it's convenient to implement multiple protocols upon proxy. `gRPC` protocol
+is implemented first and the customized `Remoting` protocol will be implemented later. HTTP/1.1 will also be taken into
+consideration.
+
+## Architecture
+
+`RocketMQ Proxy` has two deployment modes: `Cluster` mode and `Local` mode. With both modes, `Pop` mode is natively
+supported in `Proxy`.
+
+### `Cluster` mode
+
+While in `Cluster` mode, `Proxy` is an independent cluster that communicates with `Broker` with remote procedure call.
+In this scenario, `Proxy` acts as a stateless computing component while `Broker` is a stateful component with local
+storage. This form of deployment introduces the architecture of separation of computing and storage for RocketMQ.
+
+Due to the separation of computing and storage, `RocketMQ Proxy` can be scaled out indefinitely in `Cluster` mode to
+handle traffic peak while `Broker` can focus on storage engine and high availability.
+
+
+
+### `Local` mode
+
+`Proxy` in `Local` mode has more similarity with `RocketMQ` 4.x version, which is easily deployed or upgraded for
+current RocketMQ users. With `Local` mode, `Proxy` deployed with `Broker` in the same process with inter-process
+communication so the network overhead is reduced compared to `Cluster` mode.
+
+
+
+## Deploy guide
+
+See [Proxy Deployment](../docs/en/proxy/deploy_guide.md)
+
+## Related
+
+* [rocketmq-apis](https://github.com/apache/rocketmq-apis): Common communication protocol between server and client.
+* [rocketmq-clients](https://github.com/apache/rocketmq-clients): Collection of Polyglot Clients for Apache RocketMQ.
+* [RIP-37: New and Unified APIs](https://shimo.im/docs/m5kv92OeRRU8olqX): RocketMQ proposal of new and unified APIs
+ crossing different languages.
+* [RIP-39: Support gRPC protocol](https://shimo.im/docs/gXqmeEPYgdUw5bqo): RocketMQ proposal of gRPC protocol support.
\ No newline at end of file
diff --git a/proxy/pom.xml b/proxy/pom.xml
new file mode 100644
index 0000000000..4511bc7fa7
--- /dev/null
+++ b/proxy/pom.xml
@@ -0,0 +1,89 @@
+
+
+
+
+
+ rocketmq-all
+ org.apache.rocketmq
+ 5.0.0-SNAPSHOT
+
+
+ 4.0.0
+ jar
+ rocketmq-proxy
+ rocketmq-proxy ${project.version}
+
+
+ 8
+ 8
+
+
+
+
+ org.apache.rocketmq
+ rocketmq-proto
+
+
+ org.apache.rocketmq
+ rocketmq-broker
+
+
+ org.apache.rocketmq
+ rocketmq-common
+
+
+ org.apache.rocketmq
+ rocketmq-client
+
+
+ io.grpc
+ grpc-netty-shaded
+
+
+ io.grpc
+ grpc-protobuf
+
+
+ io.grpc
+ grpc-stub
+
+
+ io.grpc
+ grpc-services
+
+
+ com.google.protobuf
+ protobuf-java-util
+
+
+ org.apache.commons
+ commons-lang3
+
+
+ org.slf4j
+ slf4j-api
+
+
+ ch.qos.logback
+ logback-classic
+
+
+
+
\ No newline at end of file
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/ProxyMode.java b/proxy/src/main/java/org/apache/rocketmq/proxy/ProxyMode.java
new file mode 100644
index 0000000000..3cc36425b0
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/ProxyMode.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.proxy;
+
+public enum ProxyMode {
+ LOCAL("LOCAL"),
+ CLUSTER("CLUSTER");
+
+ private final String mode;
+
+ ProxyMode(String mode) {
+ this.mode = mode;
+ }
+
+ public static boolean isClusterMode(String mode) {
+ if (mode == null) {
+ return false;
+ }
+ return CLUSTER.mode.equals(mode.toUpperCase());
+ }
+
+ public static boolean isClusterMode(ProxyMode mode) {
+ if (mode == null) {
+ return false;
+ }
+ return CLUSTER.equals(mode);
+ }
+
+ public static boolean isLocalMode(String mode) {
+ if (mode == null) {
+ return false;
+ }
+ return LOCAL.mode.equals(mode.toUpperCase());
+ }
+
+ public static boolean isLocalMode(ProxyMode mode) {
+ if (mode == null) {
+ return false;
+ }
+ return LOCAL.equals(mode);
+ }
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/ProxyStartup.java b/proxy/src/main/java/org/apache/rocketmq/proxy/ProxyStartup.java
new file mode 100644
index 0000000000..383a99a5bc
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/ProxyStartup.java
@@ -0,0 +1,173 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.proxy;
+
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.joran.JoranConfigurator;
+import ch.qos.logback.core.joran.spi.JoranException;
+import java.util.Date;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import org.apache.rocketmq.broker.BrokerController;
+import org.apache.rocketmq.broker.BrokerStartup;
+import org.apache.rocketmq.client.log.ClientLogger;
+import org.apache.rocketmq.common.constant.LoggerName;
+import org.apache.rocketmq.common.thread.ThreadPoolMonitor;
+import org.apache.rocketmq.logging.InternalLogger;
+import org.apache.rocketmq.logging.InternalLoggerFactory;
+import org.apache.rocketmq.proxy.common.AbstractStartAndShutdown;
+import org.apache.rocketmq.proxy.common.StartAndShutdown;
+import org.apache.rocketmq.proxy.config.ConfigurationManager;
+import org.apache.rocketmq.proxy.config.ProxyConfig;
+import org.apache.rocketmq.proxy.grpc.GrpcServer;
+import org.apache.rocketmq.proxy.grpc.GrpcServerBuilder;
+import org.apache.rocketmq.proxy.grpc.v2.GrpcMessagingApplication;
+import org.apache.rocketmq.proxy.processor.DefaultMessagingProcessor;
+import org.apache.rocketmq.proxy.processor.MessagingProcessor;
+import org.slf4j.LoggerFactory;
+
+public class ProxyStartup {
+ private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME);
+ private static final ProxyStartAndShutdown PROXY_START_AND_SHUTDOWN = new ProxyStartAndShutdown();
+
+ private static class ProxyStartAndShutdown extends AbstractStartAndShutdown {
+ @Override
+ public void appendStartAndShutdown(StartAndShutdown startAndShutdown) {
+ super.appendStartAndShutdown(startAndShutdown);
+ }
+ }
+
+ public static void main(String[] args) {
+ try {
+ ConfigurationManager.initEnv();
+ initLogger();
+ ConfigurationManager.intConfig();
+
+ // init thread pool monitor for proxy.
+ initThreadPoolMonitor();
+
+ ThreadPoolExecutor executor = createServerExecutor();
+
+ MessagingProcessor messagingProcessor = createMessagingProcessor();
+
+ // create grpcServer
+ GrpcServer grpcServer = GrpcServerBuilder.newBuilder(executor, ConfigurationManager.getProxyConfig().getGrpcServerPort())
+ .addService(createServiceProcessor(messagingProcessor))
+ .configInterceptor()
+ .build();
+ PROXY_START_AND_SHUTDOWN.appendStartAndShutdown(grpcServer);
+
+ // start servers one by one.
+ PROXY_START_AND_SHUTDOWN.start();
+
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+ log.info("try to shutdown server");
+ try {
+ PROXY_START_AND_SHUTDOWN.shutdown();
+ } catch (Exception e) {
+ log.error("err when shutdown rocketmq-proxy", e);
+ }
+ }));
+ } catch (Exception e) {
+ System.err.println("find an unexpect err." + e);
+ e.printStackTrace();
+ log.error("find an unexpect err.", e);
+ System.exit(1);
+ }
+
+ System.out.printf("%s%n", new Date() + " rocketmq-proxy startup successfully");
+ log.info(new Date() + " rocketmq-proxy startup successfully");
+ }
+
+ private static MessagingProcessor createMessagingProcessor() {
+ String proxyModeStr = ConfigurationManager.getProxyConfig().getProxyMode();
+ MessagingProcessor messagingProcessor;
+
+ if (ProxyMode.isClusterMode(proxyModeStr)) {
+ messagingProcessor = DefaultMessagingProcessor.createForClusterMode();
+ } else if (ProxyMode.isLocalMode(proxyModeStr)) {
+ BrokerController brokerController = createBrokerController();
+ StartAndShutdown brokerControllerWrapper = new StartAndShutdown() {
+ @Override
+ public void start() throws Exception {
+ brokerController.start();
+ }
+
+ @Override
+ public void shutdown() throws Exception {
+ brokerController.shutdown();
+ }
+ };
+ PROXY_START_AND_SHUTDOWN.appendStartAndShutdown(brokerControllerWrapper);
+ messagingProcessor = DefaultMessagingProcessor.createForLocalMode(brokerController);
+ } else {
+ throw new IllegalArgumentException("try to start grpc server with wrong mode, use 'local' or 'cluster'");
+ }
+ PROXY_START_AND_SHUTDOWN.appendStartAndShutdown(messagingProcessor);
+ return messagingProcessor;
+ }
+
+ private static GrpcMessagingApplication createServiceProcessor(MessagingProcessor messagingProcessor) {
+ GrpcMessagingApplication application = GrpcMessagingApplication.create(messagingProcessor);
+ PROXY_START_AND_SHUTDOWN.appendStartAndShutdown(application);
+ return application;
+ }
+
+ private static BrokerController createBrokerController() {
+ String[] brokerStartupArgs = new String[] {"-c", ConfigurationManager.getProxyConfig().getBrokerConfigPath()};
+ return BrokerStartup.createBrokerController(brokerStartupArgs);
+ }
+
+ public static ThreadPoolExecutor createServerExecutor() {
+ ProxyConfig config = ConfigurationManager.getProxyConfig();
+ int threadPoolNums = config.getGrpcThreadPoolNums();
+ int threadPoolQueueCapacity = config.getGrpcThreadPoolQueueCapacity();
+ ThreadPoolExecutor executor = ThreadPoolMonitor.createAndMonitor(
+ threadPoolNums,
+ threadPoolNums,
+ 1, TimeUnit.MINUTES,
+ "GrpcRequestExecutorThread",
+ threadPoolQueueCapacity
+ );
+ PROXY_START_AND_SHUTDOWN.appendShutdown(executor::shutdown);
+ return executor;
+ }
+
+ public static void initThreadPoolMonitor() {
+ ProxyConfig config = ConfigurationManager.getProxyConfig();
+ ThreadPoolMonitor.config(
+ InternalLoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME),
+ InternalLoggerFactory.getLogger(LoggerName.PROXY_WATER_MARK_LOGGER_NAME),
+ config.isEnablePrintJstack(), config.getPrintJstackInMillis(),
+ config.getPrintThreadPoolStatusInMillis());
+ ThreadPoolMonitor.init();
+ }
+
+ public static void initLogger() throws JoranException {
+ System.setProperty("brokerLogDir", "");
+ System.setProperty(ClientLogger.CLIENT_LOG_USESLF4J, "true");
+
+ LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
+ JoranConfigurator configurator = new JoranConfigurator();
+ configurator.setContext(lc);
+ lc.reset();
+ //https://logback.qos.ch/manual/configuration.html
+ lc.setPackagingDataEnabled(false);
+ configurator.doConfigure(ConfigurationManager.getProxyHome() + "/conf/logback_proxy.xml");
+ }
+}
\ No newline at end of file
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/common/AbstractCacheLoader.java b/proxy/src/main/java/org/apache/rocketmq/proxy/common/AbstractCacheLoader.java
new file mode 100644
index 0000000000..581caffdbd
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/common/AbstractCacheLoader.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.proxy.common;
+
+import com.google.common.cache.CacheLoader;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListenableFutureTask;
+import java.util.concurrent.ThreadPoolExecutor;
+import javax.annotation.Nonnull;
+
+public abstract class AbstractCacheLoader extends CacheLoader {
+ private final ThreadPoolExecutor cacheRefreshExecutor;
+
+ public AbstractCacheLoader(ThreadPoolExecutor cacheRefreshExecutor) {
+ this.cacheRefreshExecutor = cacheRefreshExecutor;
+ }
+
+ @Override
+ public ListenableFuture reload(@Nonnull K key, @Nonnull V oldValue) throws Exception {
+ ListenableFutureTask task = ListenableFutureTask.create(() -> {
+ try {
+ return getDirectly(key);
+ } catch (Exception e) {
+ onErr(key, e);
+ return oldValue;
+ }
+ });
+ cacheRefreshExecutor.execute(task);
+ return task;
+ }
+
+ @Override
+ public V load(@Nonnull K key) throws Exception {
+ return getDirectly(key);
+ }
+
+ protected abstract V getDirectly(K key) throws Exception;
+
+ protected abstract void onErr(K key, Exception e);
+}
\ No newline at end of file
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/common/AbstractStartAndShutdown.java b/proxy/src/main/java/org/apache/rocketmq/proxy/common/AbstractStartAndShutdown.java
new file mode 100644
index 0000000000..c59f18c4cf
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/common/AbstractStartAndShutdown.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.proxy.common;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public abstract class AbstractStartAndShutdown implements StartAndShutdown {
+
+ protected List startAndShutdownList = new CopyOnWriteArrayList<>();
+
+ protected void appendStartAndShutdown(StartAndShutdown startAndShutdown) {
+ this.startAndShutdownList.add(startAndShutdown);
+ }
+
+ @Override
+ public void start() throws Exception {
+ for (StartAndShutdown startAndShutdown : startAndShutdownList) {
+ startAndShutdown.start();
+ }
+ }
+
+ @Override
+ public void shutdown() throws Exception {
+ int index = startAndShutdownList.size() - 1;
+ for (; index >= 0; index--) {
+ startAndShutdownList.get(index).shutdown();
+ }
+ }
+
+ public void appendStart(Start start) {
+ this.appendStartAndShutdown(new StartAndShutdown() {
+ @Override
+ public void shutdown() throws Exception {
+
+ }
+
+ @Override
+ public void start() throws Exception {
+ start.start();
+ }
+ });
+ }
+
+ public void appendShutdown(Shutdown shutdown) {
+ this.appendStartAndShutdown(new StartAndShutdown() {
+ @Override
+ public void shutdown() throws Exception {
+ shutdown.shutdown();
+ }
+
+ @Override
+ public void start() throws Exception {
+
+ }
+ });
+ }
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/common/Address.java b/proxy/src/main/java/org/apache/rocketmq/proxy/common/Address.java
new file mode 100644
index 0000000000..2fc1dab40e
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/common/Address.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.proxy.common;
+
+import com.google.common.net.HostAndPort;
+import java.util.Objects;
+
+public class Address {
+
+ public enum AddressScheme {
+ IPv4,
+ IPv6,
+ DOMAIN_NAME,
+ UNRECOGNIZED
+ }
+
+ private AddressScheme addressScheme;
+ private HostAndPort hostAndPort;
+
+ public Address(AddressScheme addressScheme, HostAndPort hostAndPort) {
+ this.addressScheme = addressScheme;
+ this.hostAndPort = hostAndPort;
+ }
+
+ public AddressScheme getAddressScheme() {
+ return addressScheme;
+ }
+
+ public void setAddressScheme(AddressScheme addressScheme) {
+ this.addressScheme = addressScheme;
+ }
+
+ public HostAndPort getHostAndPort() {
+ return hostAndPort;
+ }
+
+ public void setHostAndPort(HostAndPort hostAndPort) {
+ this.hostAndPort = hostAndPort;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Address address = (Address) o;
+ return addressScheme == address.addressScheme && Objects.equals(hostAndPort, address.hostAndPort);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(addressScheme, hostAndPort);
+ }
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/common/ContextVariable.java b/proxy/src/main/java/org/apache/rocketmq/proxy/common/ContextVariable.java
new file mode 100644
index 0000000000..dcfc529090
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/common/ContextVariable.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.proxy.common;
+
+public class ContextVariable {
+ public static final String REMOTE_ADDRESS = "remote-address";
+ public static final String LOCAL_ADDRESS = "local-address";
+ public static final String CLIENT_ID = "client-id";
+ public static final String LANGUAGE = "language";
+ public static final String CLIENT_VERSION = "client-version";
+ public static final String REMAINING_MS = "remaining-ms";
+ public static final String ACTION = "action";
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/common/MessageReceiptHandle.java b/proxy/src/main/java/org/apache/rocketmq/proxy/common/MessageReceiptHandle.java
new file mode 100644
index 0000000000..64e7a122ab
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/common/MessageReceiptHandle.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.proxy.common;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+
+public class MessageReceiptHandle {
+ private final String group;
+ private final String topic;
+ private final int queueId;
+ private final String messageId;
+ private final long queueOffset;
+ private final String originalReceiptHandle;
+ private final long timestamp;
+ private final int reconsumeTimes;
+ private final long expectInvisibleTime;
+
+ private String receiptHandle;
+
+ public MessageReceiptHandle(String group, String topic, int queueId, String receiptHandle, String messageId,
+ long queueOffset, int reconsumeTimes, long expectInvisibleTime) {
+ this.group = group;
+ this.topic = topic;
+ this.queueId = queueId;
+ this.receiptHandle = receiptHandle;
+ this.originalReceiptHandle = receiptHandle;
+ this.messageId = messageId;
+ this.queueOffset = queueOffset;
+ this.reconsumeTimes = reconsumeTimes;
+ this.expectInvisibleTime = expectInvisibleTime;
+ this.timestamp = System.currentTimeMillis();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ MessageReceiptHandle handle = (MessageReceiptHandle) o;
+ return queueId == handle.queueId && queueOffset == handle.queueOffset && timestamp == handle.timestamp
+ && reconsumeTimes == handle.reconsumeTimes && expectInvisibleTime == handle.expectInvisibleTime
+ && Objects.equal(group, handle.group) && Objects.equal(topic, handle.topic)
+ && Objects.equal(messageId, handle.messageId) && Objects.equal(originalReceiptHandle, handle.originalReceiptHandle)
+ && Objects.equal(receiptHandle, handle.receiptHandle);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(group, topic, queueId, messageId, queueOffset, originalReceiptHandle, timestamp,
+ reconsumeTimes, expectInvisibleTime, receiptHandle);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("group", group)
+ .add("topic", topic)
+ .add("queueId", queueId)
+ .add("messageId", messageId)
+ .add("queueOffset", queueOffset)
+ .add("originalReceiptHandle", originalReceiptHandle)
+ .add("timestamp", timestamp)
+ .add("reconsumeTimes", reconsumeTimes)
+ .add("expectInvisibleTime", expectInvisibleTime)
+ .add("receiptHandle", receiptHandle)
+ .toString();
+ }
+
+ public String getGroup() {
+ return group;
+ }
+
+ public String getTopic() {
+ return topic;
+ }
+
+ public int getQueueId() {
+ return queueId;
+ }
+
+ public String getReceiptHandle() {
+ return receiptHandle;
+ }
+
+ public String getOriginalReceiptHandle() {
+ return originalReceiptHandle;
+ }
+
+ public String getMessageId() {
+ return messageId;
+ }
+
+ public long getQueueOffset() {
+ return queueOffset;
+ }
+
+ public int getReconsumeTimes() {
+ return reconsumeTimes;
+ }
+
+ public long getTimestamp() {
+ return timestamp;
+ }
+
+ public long getExpectInvisibleTime() {
+ return expectInvisibleTime;
+ }
+
+ public void update(String receiptHandle) {
+ this.receiptHandle = receiptHandle;
+ }
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/common/ProxyContext.java b/proxy/src/main/java/org/apache/rocketmq/proxy/common/ProxyContext.java
new file mode 100644
index 0000000000..6a35993fec
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/common/ProxyContext.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.proxy.common;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ProxyContext {
+ public static final String INNER_ACTION_PREFIX = "Inner";
+ private final Map value = new HashMap<>();
+
+ public static ProxyContext create() {
+ return new ProxyContext();
+ }
+
+ public static ProxyContext createForInner(String actionName) {
+ return create().setAction(INNER_ACTION_PREFIX + actionName);
+ }
+
+ public static ProxyContext createForInner(Class> clazz) {
+ return createForInner(clazz.getSimpleName());
+ }
+
+ public Map getValue() {
+ return this.value;
+ }
+
+ public ProxyContext withVal(String key, Object val) {
+ this.value.put(key, val);
+ return this;
+ }
+
+ public T getVal(String key) {
+ return (T) this.value.get(key);
+ }
+
+ public ProxyContext setLocalAddress(String localAddress) {
+ this.withVal(ContextVariable.LOCAL_ADDRESS, localAddress);
+ return this;
+ }
+
+ public String getLocalAddress() {
+ return this.getVal(ContextVariable.LOCAL_ADDRESS);
+ }
+
+ public ProxyContext setRemoteAddress(String remoteAddress) {
+ this.withVal(ContextVariable.REMOTE_ADDRESS, remoteAddress);
+ return this;
+ }
+
+ public String getRemoteAddress() {
+ return this.getVal(ContextVariable.REMOTE_ADDRESS);
+ }
+
+ public ProxyContext setClientID(String clientID) {
+ this.withVal(ContextVariable.CLIENT_ID, clientID);
+ return this;
+ }
+
+ public String getClientID() {
+ return this.getVal(ContextVariable.CLIENT_ID);
+ }
+
+ public ProxyContext setLanguage(String language) {
+ this.withVal(ContextVariable.LANGUAGE, language);
+ return this;
+ }
+
+ public String getLanguage() {
+ return this.getVal(ContextVariable.LANGUAGE);
+ }
+
+ public ProxyContext setClientVersion(String clientVersion) {
+ this.withVal(ContextVariable.CLIENT_VERSION, clientVersion);
+ return this;
+ }
+
+ public String getClientVersion() {
+ return this.getVal(ContextVariable.CLIENT_VERSION);
+ }
+
+ public ProxyContext setRemainingMs(Long remainingMs) {
+ this.withVal(ContextVariable.REMAINING_MS, remainingMs);
+ return this;
+ }
+
+ public Long getRemainingMs() {
+ return this.getVal(ContextVariable.REMAINING_MS);
+ }
+
+ public ProxyContext setAction(String action) {
+ this.withVal(ContextVariable.ACTION, action);
+ return this;
+ }
+
+ public String getAction() {
+ return this.getVal(ContextVariable.ACTION);
+ }
+
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/common/ProxyException.java b/proxy/src/main/java/org/apache/rocketmq/proxy/common/ProxyException.java
new file mode 100644
index 0000000000..af528329fd
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/common/ProxyException.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.proxy.common;
+
+public class ProxyException extends RuntimeException {
+
+ private final ProxyExceptionCode code;
+
+ public ProxyException(ProxyExceptionCode code, String message) {
+ super(message);
+ this.code = code;
+ }
+
+ public ProxyException(ProxyExceptionCode code, String message, Throwable cause) {
+ super(message, cause);
+ this.code = code;
+ }
+
+ public ProxyExceptionCode getCode() {
+ return code;
+ }
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/common/ProxyExceptionCode.java b/proxy/src/main/java/org/apache/rocketmq/proxy/common/ProxyExceptionCode.java
new file mode 100644
index 0000000000..4f91388215
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/common/ProxyExceptionCode.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.proxy.common;
+
+public enum ProxyExceptionCode {
+ INVALID_BROKER_NAME,
+ TRANSACTION_DATA_NOT_FOUND,
+ FORBIDDEN,
+ MESSAGE_PROPERTY_CONFLICT_WITH_TYPE,
+ INVALID_RECEIPT_HANDLE,
+ INTERNAL_SERVER_ERROR,
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/common/ReceiptHandleGroup.java b/proxy/src/main/java/org/apache/rocketmq/proxy/common/ReceiptHandleGroup.java
new file mode 100644
index 0000000000..ce68fb2db9
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/common/ReceiptHandleGroup.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.proxy.common;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class ReceiptHandleGroup {
+ private final Map> receiptHandleMap = new ConcurrentHashMap<>();
+
+ public void put(String msgID, String handle, MessageReceiptHandle value) {
+ Map handleMap = receiptHandleMap.computeIfAbsent(msgID, msgIDKey -> new ConcurrentHashMap<>());
+ handleMap.put(handle, value);
+ }
+
+ public boolean isEmpty() {
+ return this.receiptHandleMap.isEmpty();
+ }
+
+ public MessageReceiptHandle remove(String msgID, String handle) {
+ AtomicReference resRef = new AtomicReference<>();
+ receiptHandleMap.computeIfPresent(msgID, (msgIDKey, handleMap) -> {
+ resRef.set(handleMap.remove(handle));
+ if (handleMap.isEmpty()) {
+ return null;
+ }
+ return handleMap;
+ });
+ return resRef.get();
+ }
+
+ public MessageReceiptHandle removeOne(String msgID) {
+ AtomicReference resRef = new AtomicReference<>();
+ receiptHandleMap.computeIfPresent(msgID, (msgIDKey, handleMap) -> {
+ if (handleMap.isEmpty()) {
+ return null;
+ }
+ Optional handleKey = handleMap.keySet().stream().findAny();
+ resRef.set(handleMap.remove(handleKey.get()));
+ if (handleMap.isEmpty()) {
+ return null;
+ }
+ return handleMap;
+ });
+ return resRef.get();
+ }
+
+ public interface DataScanner {
+ void onData(String msgID, String handle, MessageReceiptHandle receiptHandle);
+ }
+
+ public void scan(DataScanner scanner) {
+ this.receiptHandleMap.forEach((msgID, handleMap) -> {
+ handleMap.forEach((handleStr, v) -> {
+ scanner.onData(msgID, handleStr, v);
+ });
+ });
+ }
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/common/Shutdown.java b/proxy/src/main/java/org/apache/rocketmq/proxy/common/Shutdown.java
new file mode 100644
index 0000000000..28f4f92f54
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/common/Shutdown.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.proxy.common;
+
+public interface Shutdown {
+ void shutdown() throws Exception;
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/common/Start.java b/proxy/src/main/java/org/apache/rocketmq/proxy/common/Start.java
new file mode 100644
index 0000000000..3cf74d47d2
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/common/Start.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.proxy.common;
+
+public interface Start {
+ void start() throws Exception;
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/common/StartAndShutdown.java b/proxy/src/main/java/org/apache/rocketmq/proxy/common/StartAndShutdown.java
new file mode 100644
index 0000000000..565e92c25c
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/common/StartAndShutdown.java
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.proxy.common;
+
+public interface StartAndShutdown extends Start, Shutdown {
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/common/utils/ExceptionUtils.java b/proxy/src/main/java/org/apache/rocketmq/proxy/common/utils/ExceptionUtils.java
new file mode 100644
index 0000000000..e85360a5da
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/common/utils/ExceptionUtils.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.proxy.common.utils;
+
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ExecutionException;
+
+public class ExceptionUtils {
+
+ public static Throwable getRealException(Throwable throwable) {
+ if (throwable instanceof CompletionException || throwable instanceof ExecutionException) {
+ if (throwable.getCause() != null) {
+ throwable = throwable.getCause();
+ }
+ }
+ return throwable;
+ }
+
+ public static String getErrorDetailMessage(Throwable t) {
+ if (t == null) {
+ return null;
+ }
+ StringBuilder sb = new StringBuilder();
+ sb.append(t.getMessage()).append(". ").append(t.getClass().getSimpleName());
+
+ if (t.getStackTrace().length > 0) {
+ sb.append(". ").append(t.getStackTrace()[0]);
+ }
+ return sb.toString();
+ }
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/common/utils/FilterUtils.java b/proxy/src/main/java/org/apache/rocketmq/proxy/common/utils/FilterUtils.java
new file mode 100644
index 0000000000..23eb1e1536
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/common/utils/FilterUtils.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.proxy.common.utils;
+
+import java.util.Set;
+import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData;
+
+public class FilterUtils {
+ /**
+ * Whether the message's tag matches consumerGroup's SubscriptionData
+ *
+ * @param tagsSet, tagSet in {@link SubscriptionData}, tagSet empty means SubscriptionData.SUB_ALL(*)
+ * @param tags, message's tags, null means not tag attached to the message.
+ */
+ public static boolean isTagMatched(Set tagsSet, String tags) {
+ if (tagsSet.isEmpty()) {
+ return true;
+ }
+
+ if (tags == null) {
+ return false;
+ }
+
+ return tagsSet.contains(tags);
+ }
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/common/utils/FutureUtils.java b/proxy/src/main/java/org/apache/rocketmq/proxy/common/utils/FutureUtils.java
new file mode 100644
index 0000000000..2e194a8cbe
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/common/utils/FutureUtils.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.proxy.common.utils;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+
+public class FutureUtils {
+
+ public static CompletableFuture appendNextFuture(CompletableFuture future,
+ CompletableFuture nextFuture, ExecutorService executor) {
+ future.whenCompleteAsync((t, throwable) -> {
+ if (throwable != null) {
+ nextFuture.completeExceptionally(throwable);
+ } else {
+ nextFuture.complete(t);
+ }
+ }, executor);
+ return nextFuture;
+ }
+
+ public static CompletableFuture addExecutor(CompletableFuture future, ExecutorService executor) {
+ return appendNextFuture(future, new CompletableFuture<>(), executor);
+ }
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/common/utils/ProxyUtils.java b/proxy/src/main/java/org/apache/rocketmq/proxy/common/utils/ProxyUtils.java
new file mode 100644
index 0000000000..7e82a49613
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/common/utils/ProxyUtils.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.proxy.common.utils;
+
+public class ProxyUtils {
+
+ public static final int MAX_MSG_NUMS_FOR_POP_REQUEST = 32;
+
+ public static final String BROKER_ADDR = "brokerAddr";
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/config/ConfigFile.java b/proxy/src/main/java/org/apache/rocketmq/proxy/config/ConfigFile.java
new file mode 100644
index 0000000000..37757f8d63
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/config/ConfigFile.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.proxy.config;
+
+public interface ConfigFile {
+
+ void initData();
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/config/Configuration.java b/proxy/src/main/java/org/apache/rocketmq/proxy/config/Configuration.java
new file mode 100644
index 0000000000..cf0b715936
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/config/Configuration.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.proxy.config;
+
+import com.alibaba.fastjson.JSON;
+import java.io.File;
+import java.nio.file.Files;
+import java.util.concurrent.atomic.AtomicReference;
+import org.apache.rocketmq.common.constant.LoggerName;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Configuration {
+ private final static Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME);
+ private final AtomicReference proxyConfigReference = new AtomicReference<>();
+
+ public void init() throws Exception {
+ String proxyConfigData = loadJsonConfig(ProxyConfig.CONFIG_FILE_NAME);
+ if (null == proxyConfigData) {
+ throw new RuntimeException(String.format("load configuration from file: %s error.", ProxyConfig.CONFIG_FILE_NAME));
+ }
+
+ ProxyConfig proxyConfig = JSON.parseObject(proxyConfigData, ProxyConfig.class);
+ proxyConfig.initData();
+ setProxyConfig(proxyConfig);
+ }
+
+ public static String loadJsonConfig(String configFileName) throws Exception {
+ String filePath = new File(ConfigurationManager.getProxyHome() + File.separator + "conf", configFileName).toString();
+
+ File file = new File(filePath);
+ if (!file.exists()) {
+ log.warn("the config file {} not exist", filePath);
+ return null;
+ }
+ long fileLength = file.length();
+ if (fileLength <= 0) {
+ log.warn("the config file {} length is zero", filePath);
+ return null;
+ }
+
+ return new String(Files.readAllBytes(file.toPath()));
+ }
+
+ public ProxyConfig getProxyConfig() {
+ return proxyConfigReference.get();
+ }
+
+ public void setProxyConfig(ProxyConfig proxyConfig) {
+ proxyConfigReference.set(proxyConfig);
+ }
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/config/ConfigurationManager.java b/proxy/src/main/java/org/apache/rocketmq/proxy/config/ConfigurationManager.java
new file mode 100644
index 0000000000..61e4498962
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/config/ConfigurationManager.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.proxy.config;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.rocketmq.common.MixAll;
+
+public class ConfigurationManager {
+ public static final String RMQ_PROXY_HOME = "RMQ_PROXY_HOME";
+ protected static final String DEFAULT_RMQ_PROXY_HOME = System.getenv(MixAll.ROCKETMQ_HOME_ENV);
+ protected static String proxyHome;
+ protected static Configuration configuration;
+
+ public static void initEnv() {
+ proxyHome = System.getenv(RMQ_PROXY_HOME);
+ if (StringUtils.isEmpty(proxyHome)) {
+ proxyHome = System.getProperty(RMQ_PROXY_HOME, DEFAULT_RMQ_PROXY_HOME);
+ }
+ }
+
+ public static void intConfig() throws Exception {
+ configuration = new Configuration();
+ configuration.init();
+ }
+
+ public static String getProxyHome() {
+ return proxyHome;
+ }
+
+ public static ProxyConfig getProxyConfig() {
+ return configuration.getProxyConfig();
+ }
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/config/MetricCollectorMode.java b/proxy/src/main/java/org/apache/rocketmq/proxy/config/MetricCollectorMode.java
new file mode 100644
index 0000000000..305ca8d28a
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/config/MetricCollectorMode.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.proxy.config;
+
+public enum MetricCollectorMode {
+ /**
+ * Do not collect the metric from clients.
+ */
+ OFF(0),
+ /**
+ * Collect the metric from clients to the given address.
+ */
+ ON(1),
+ /**
+ * Collect the metric by the proxy itself.
+ */
+ PROXY(2);
+ private final int ordinal;
+
+ MetricCollectorMode(int ordinal) {
+ this.ordinal = ordinal;
+ }
+
+ public int getOrdinal() {
+ return ordinal;
+ }
+
+ public static MetricCollectorMode getEnumByOrdinal(int ordinal) {
+ for (MetricCollectorMode mode : MetricCollectorMode.values()) {
+ if (mode.ordinal == ordinal) {
+ return mode;
+ }
+ }
+ return OFF;
+ }
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java b/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java
new file mode 100644
index 0000000000..bc7c58b6a0
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java
@@ -0,0 +1,858 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.proxy.config;
+
+import java.time.Duration;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import org.apache.rocketmq.common.constant.LoggerName;
+import org.apache.rocketmq.proxy.ProxyMode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ProxyConfig implements ConfigFile {
+ private final static Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME);
+ public final static String CONFIG_FILE_NAME = "rmq-proxy.json";
+ private static final int PROCESSOR_NUMBER = Runtime.getRuntime().availableProcessors();
+
+ private String rocketMQClusterName = "";
+
+ /**
+ * configuration for ThreadPoolMonitor
+ */
+ private boolean enablePrintJstack = true;
+ private long printJstackInMillis = Duration.ofSeconds(60).toMillis();
+ private long printThreadPoolStatusInMillis = Duration.ofSeconds(3).toMillis();
+
+ private String nameSrvAddr = "";
+ private String nameSrvDomain = "";
+ private String nameSrvDomainSubgroup = "";
+ /**
+ * gRPC
+ */
+ private String proxyMode = ProxyMode.CLUSTER.name();
+ private Integer grpcServerPort = 8081;
+ private boolean grpcTlsTestModeEnable = true;
+ private String grpcTlsKeyPath = ConfigurationManager.getProxyHome() + "/conf/tls/rocketmq.key";
+ private String grpcTlsCertPath = ConfigurationManager.getProxyHome() + "/conf/tls/rocketmq.crt";
+ private int grpcBossLoopNum = 1;
+ private int grpcWorkerLoopNum = PROCESSOR_NUMBER * 2;
+ private boolean enableGrpcEpoll = false;
+ private int grpcThreadPoolNums = 16 + PROCESSOR_NUMBER * 2;
+ private int grpcThreadPoolQueueCapacity = 100000;
+ private String brokerConfigPath = ConfigurationManager.getProxyHome() + "/conf/broker.conf";
+ /**
+ * gRPC max message size
+ * 130M = 4M * 32 messages + 2M attributes
+ */
+ private int grpcMaxInboundMessageSize = 130 * 1024 * 1024;
+ /**
+ * max message body size, 0 or negative number means no limit for proxy
+ */
+ private int maxMessageSize = 4 * 1024 * 1024;
+ /**
+ * max user property size, 0 or negative number means no limit for proxy
+ */
+ private int maxUserPropertySize = 16 * 1024;
+ private int userPropertyMaxNum = 128;
+ /**
+ * max message group size, 0 or negative number means no limit for proxy
+ */
+ private int maxMessageGroupSize = 64;
+ private long minInvisibleTimeMillsForRecv = Duration.ofSeconds(10).toMillis();
+ private long maxInvisibleTimeMills = Duration.ofHours(12).toMillis();
+ private long maxDelayTimeMills = Duration.ofDays(1).toMillis();
+ private long maxTransactionRecoverySecond = Duration.ofHours(1).getSeconds();
+ private boolean enableTopicMessageTypeCheck = true;
+
+ private int grpcClientProducerMaxAttempts = 3;
+ private long grpcClientProducerBackoffInitialMillis = 10;
+ private long grpcClientProducerBackoffMaxMillis = 1000;
+ private int grpcClientProducerBackoffMultiplier = 2;
+ private long grpcClientConsumerLongPollingTimeoutMillis = Duration.ofSeconds(30).toMillis();
+ private int grpcClientConsumerLongPollingBatchSize = 32;
+
+ private int channelExpiredInSeconds = 60;
+ private int contextExpiredInSeconds = 30;
+
+ private int rocketmqMQClientNum = 6;
+
+ private long grpcProxyRelayRequestTimeoutInSeconds = 5;
+ private int grpcProducerThreadPoolNums = PROCESSOR_NUMBER;
+ private int grpcProducerThreadQueueCapacity = 10000;
+ private int grpcConsumerThreadPoolNums = PROCESSOR_NUMBER;
+ private int grpcConsumerThreadQueueCapacity = 10000;
+ private int grpcRouteThreadPoolNums = PROCESSOR_NUMBER;
+ private int grpcRouteThreadQueueCapacity = 10000;
+ private int grpcClientManagerThreadPoolNums = PROCESSOR_NUMBER;
+ private int grpcClientManagerThreadQueueCapacity = 10000;
+ private int grpcTransactionThreadPoolNums = PROCESSOR_NUMBER;
+ private int grpcTransactionThreadQueueCapacity = 10000;
+
+ private int producerProcessorThreadPoolNums = PROCESSOR_NUMBER;
+ private int producerProcessorThreadPoolQueueCapacity = 10000;
+ private int consumerProcessorThreadPoolNums = PROCESSOR_NUMBER;
+ private int consumerProcessorThreadPoolQueueCapacity = 10000;
+
+ private int topicRouteServiceCacheExpiredInSeconds = 20;
+ private int topicRouteServiceCacheMaxNum = 20000;
+ private int topicRouteServiceThreadPoolNums = PROCESSOR_NUMBER;
+ private int topicRouteServiceThreadPoolQueueCapacity = 5000;
+
+ private int topicConfigCacheExpiredInSeconds = 20;
+ private int topicConfigCacheMaxNum = 20000;
+ private int subscriptionGroupConfigCacheExpiredInSeconds = 20;
+ private int subscriptionGroupConfigCacheMaxNum = 20000;
+ private int metadataThreadPoolNums = 3;
+ private int metadataThreadPoolQueueCapacity = 1000;
+
+ private int transactionHeartbeatThreadPoolNums = 20;
+ private int transactionHeartbeatThreadPoolQueueCapacity = 200;
+ private int transactionHeartbeatPeriodSecond = 20;
+ private int transactionHeartbeatBatchNum = 100;
+ private long transactionDataExpireScanPeriodMillis = Duration.ofSeconds(10).toMillis();
+ private long transactionDataMaxWaitClearMillis = Duration.ofSeconds(30).toMillis();
+ private long transactionDataExpireMillis = Duration.ofSeconds(30).toMillis();
+ private int transactionDataMaxNum = 15;
+
+ private long longPollingReserveTimeInMillis = 100;
+
+ private long invisibleTimeMillisWhenClear = 1000L;
+ private boolean enableProxyAutoRenew = true;
+ private long renewAheadTimeMillis = TimeUnit.SECONDS.toMillis(10);
+ private long renewSliceTimeMillis = TimeUnit.SECONDS.toMillis(60);
+ private long renewMaxTimeMillis = TimeUnit.HOURS.toMillis(3);
+ private long renewSchedulePeriodMillis = TimeUnit.SECONDS.toMillis(5);
+
+ private boolean enableACL = false;
+
+ private boolean useDelayLevel = true;
+ private String messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h";
+ private transient Map delayLevelTable = new ConcurrentHashMap<>();
+
+ private int metricCollectorMode = MetricCollectorMode.OFF.getOrdinal();
+ // Example address: 127.0.0.1:1234
+ private String metricCollectorAddress = "";
+
+ @Override
+ public void initData() {
+ parseDelayLevel();
+ }
+
+ 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();
+ }
+
+ public void parseDelayLevel() {
+ this.delayLevelTable = new ConcurrentHashMap<>();
+ Map timeUnitTable = new HashMap<>();
+ timeUnitTable.put("s", 1000L);
+ timeUnitTable.put("m", 1000L * 60);
+ timeUnitTable.put("h", 1000L * 60 * 60);
+ timeUnitTable.put("d", 1000L * 60 * 60 * 24);
+
+ String levelString = this.getMessageDelayLevel();
+ try {
+ String[] levelArray = levelString.split(" ");
+ for (int i = 0; i < levelArray.length; i++) {
+ String value = levelArray[i];
+ String ch = value.substring(value.length() - 1);
+ Long tu = timeUnitTable.get(ch);
+
+ int level = i + 1;
+ long num = Long.parseLong(value.substring(0, value.length() - 1));
+ long delayTimeMillis = tu * num;
+ this.delayLevelTable.put(level, delayTimeMillis);
+ }
+ } catch (Exception e) {
+ log.error("parse delay level failed. messageDelayLevel:{}", messageDelayLevel, e);
+ }
+ }
+
+ public String getRocketMQClusterName() {
+ return rocketMQClusterName;
+ }
+
+ public void setRocketMQClusterName(String rocketMQClusterName) {
+ this.rocketMQClusterName = rocketMQClusterName;
+ }
+
+ public boolean isEnablePrintJstack() {
+ return enablePrintJstack;
+ }
+
+ public void setEnablePrintJstack(boolean enablePrintJstack) {
+ this.enablePrintJstack = enablePrintJstack;
+ }
+
+ public long getPrintJstackInMillis() {
+ return printJstackInMillis;
+ }
+
+ public void setPrintJstackInMillis(long printJstackInMillis) {
+ this.printJstackInMillis = printJstackInMillis;
+ }
+
+ public long getPrintThreadPoolStatusInMillis() {
+ return printThreadPoolStatusInMillis;
+ }
+
+ public void setPrintThreadPoolStatusInMillis(long printThreadPoolStatusInMillis) {
+ this.printThreadPoolStatusInMillis = printThreadPoolStatusInMillis;
+ }
+
+ public String getNameSrvAddr() {
+ return nameSrvAddr;
+ }
+
+ public void setNameSrvAddr(String nameSrvAddr) {
+ this.nameSrvAddr = nameSrvAddr;
+ }
+
+ public String getNameSrvDomain() {
+ return nameSrvDomain;
+ }
+
+ public void setNameSrvDomain(String nameSrvDomain) {
+ this.nameSrvDomain = nameSrvDomain;
+ }
+
+ public String getNameSrvDomainSubgroup() {
+ return nameSrvDomainSubgroup;
+ }
+
+ public void setNameSrvDomainSubgroup(String nameSrvDomainSubgroup) {
+ this.nameSrvDomainSubgroup = nameSrvDomainSubgroup;
+ }
+
+ public String getProxyMode() {
+ return proxyMode;
+ }
+
+ public void setProxyMode(String proxyMode) {
+ this.proxyMode = proxyMode;
+ }
+
+ public Integer getGrpcServerPort() {
+ return grpcServerPort;
+ }
+
+ public void setGrpcServerPort(Integer grpcServerPort) {
+ this.grpcServerPort = grpcServerPort;
+ }
+
+ public boolean isGrpcTlsTestModeEnable() {
+ return grpcTlsTestModeEnable;
+ }
+
+ public void setGrpcTlsTestModeEnable(boolean grpcTlsTestModeEnable) {
+ this.grpcTlsTestModeEnable = grpcTlsTestModeEnable;
+ }
+
+ public String getGrpcTlsKeyPath() {
+ return grpcTlsKeyPath;
+ }
+
+ public void setGrpcTlsKeyPath(String grpcTlsKeyPath) {
+ this.grpcTlsKeyPath = grpcTlsKeyPath;
+ }
+
+ public String getGrpcTlsCertPath() {
+ return grpcTlsCertPath;
+ }
+
+ public void setGrpcTlsCertPath(String grpcTlsCertPath) {
+ this.grpcTlsCertPath = grpcTlsCertPath;
+ }
+
+ public int getGrpcBossLoopNum() {
+ return grpcBossLoopNum;
+ }
+
+ public void setGrpcBossLoopNum(int grpcBossLoopNum) {
+ this.grpcBossLoopNum = grpcBossLoopNum;
+ }
+
+ public int getGrpcWorkerLoopNum() {
+ return grpcWorkerLoopNum;
+ }
+
+ public void setGrpcWorkerLoopNum(int grpcWorkerLoopNum) {
+ this.grpcWorkerLoopNum = grpcWorkerLoopNum;
+ }
+
+ public boolean isEnableGrpcEpoll() {
+ return enableGrpcEpoll;
+ }
+
+ public void setEnableGrpcEpoll(boolean enableGrpcEpoll) {
+ this.enableGrpcEpoll = enableGrpcEpoll;
+ }
+
+ public int getGrpcThreadPoolNums() {
+ return grpcThreadPoolNums;
+ }
+
+ public void setGrpcThreadPoolNums(int grpcThreadPoolNums) {
+ this.grpcThreadPoolNums = grpcThreadPoolNums;
+ }
+
+ public int getGrpcThreadPoolQueueCapacity() {
+ return grpcThreadPoolQueueCapacity;
+ }
+
+ public void setGrpcThreadPoolQueueCapacity(int grpcThreadPoolQueueCapacity) {
+ this.grpcThreadPoolQueueCapacity = grpcThreadPoolQueueCapacity;
+ }
+
+ public String getBrokerConfigPath() {
+ return brokerConfigPath;
+ }
+
+ public void setBrokerConfigPath(String brokerConfigPath) {
+ this.brokerConfigPath = brokerConfigPath;
+ }
+
+ public int getGrpcMaxInboundMessageSize() {
+ return grpcMaxInboundMessageSize;
+ }
+
+ public void setGrpcMaxInboundMessageSize(int grpcMaxInboundMessageSize) {
+ this.grpcMaxInboundMessageSize = grpcMaxInboundMessageSize;
+ }
+
+ public int getMaxMessageSize() {
+ return maxMessageSize;
+ }
+
+ public void setMaxMessageSize(int maxMessageSize) {
+ this.maxMessageSize = maxMessageSize;
+ }
+
+ public int getMaxUserPropertySize() {
+ return maxUserPropertySize;
+ }
+
+ public void setMaxUserPropertySize(int maxUserPropertySize) {
+ this.maxUserPropertySize = maxUserPropertySize;
+ }
+
+ public int getUserPropertyMaxNum() {
+ return userPropertyMaxNum;
+ }
+
+ public void setUserPropertyMaxNum(int userPropertyMaxNum) {
+ this.userPropertyMaxNum = userPropertyMaxNum;
+ }
+
+ public int getMaxMessageGroupSize() {
+ return maxMessageGroupSize;
+ }
+
+ public void setMaxMessageGroupSize(int maxMessageGroupSize) {
+ this.maxMessageGroupSize = maxMessageGroupSize;
+ }
+
+ public long getMinInvisibleTimeMillsForRecv() {
+ return minInvisibleTimeMillsForRecv;
+ }
+
+ public void setMinInvisibleTimeMillsForRecv(long minInvisibleTimeMillsForRecv) {
+ this.minInvisibleTimeMillsForRecv = minInvisibleTimeMillsForRecv;
+ }
+
+ public long getMaxInvisibleTimeMills() {
+ return maxInvisibleTimeMills;
+ }
+
+ public void setMaxInvisibleTimeMills(long maxInvisibleTimeMills) {
+ this.maxInvisibleTimeMills = maxInvisibleTimeMills;
+ }
+
+ public long getMaxDelayTimeMills() {
+ return maxDelayTimeMills;
+ }
+
+ public void setMaxDelayTimeMills(long maxDelayTimeMills) {
+ this.maxDelayTimeMills = maxDelayTimeMills;
+ }
+
+ public long getMaxTransactionRecoverySecond() {
+ return maxTransactionRecoverySecond;
+ }
+
+ public void setMaxTransactionRecoverySecond(long maxTransactionRecoverySecond) {
+ this.maxTransactionRecoverySecond = maxTransactionRecoverySecond;
+ }
+
+ public int getGrpcClientProducerMaxAttempts() {
+ return grpcClientProducerMaxAttempts;
+ }
+
+ public void setGrpcClientProducerMaxAttempts(int grpcClientProducerMaxAttempts) {
+ this.grpcClientProducerMaxAttempts = grpcClientProducerMaxAttempts;
+ }
+
+ public long getGrpcClientProducerBackoffInitialMillis() {
+ return grpcClientProducerBackoffInitialMillis;
+ }
+
+ public void setGrpcClientProducerBackoffInitialMillis(long grpcClientProducerBackoffInitialMillis) {
+ this.grpcClientProducerBackoffInitialMillis = grpcClientProducerBackoffInitialMillis;
+ }
+
+ public long getGrpcClientProducerBackoffMaxMillis() {
+ return grpcClientProducerBackoffMaxMillis;
+ }
+
+ public void setGrpcClientProducerBackoffMaxMillis(long grpcClientProducerBackoffMaxMillis) {
+ this.grpcClientProducerBackoffMaxMillis = grpcClientProducerBackoffMaxMillis;
+ }
+
+ public int getGrpcClientProducerBackoffMultiplier() {
+ return grpcClientProducerBackoffMultiplier;
+ }
+
+ public void setGrpcClientProducerBackoffMultiplier(int grpcClientProducerBackoffMultiplier) {
+ this.grpcClientProducerBackoffMultiplier = grpcClientProducerBackoffMultiplier;
+ }
+
+ public long getGrpcClientConsumerLongPollingTimeoutMillis() {
+ return grpcClientConsumerLongPollingTimeoutMillis;
+ }
+
+ public void setGrpcClientConsumerLongPollingTimeoutMillis(long grpcClientConsumerLongPollingTimeoutMillis) {
+ this.grpcClientConsumerLongPollingTimeoutMillis = grpcClientConsumerLongPollingTimeoutMillis;
+ }
+
+ public int getGrpcClientConsumerLongPollingBatchSize() {
+ return grpcClientConsumerLongPollingBatchSize;
+ }
+
+ public void setGrpcClientConsumerLongPollingBatchSize(int grpcClientConsumerLongPollingBatchSize) {
+ this.grpcClientConsumerLongPollingBatchSize = grpcClientConsumerLongPollingBatchSize;
+ }
+
+ public int getChannelExpiredInSeconds() {
+ return channelExpiredInSeconds;
+ }
+
+ public void setChannelExpiredInSeconds(int channelExpiredInSeconds) {
+ this.channelExpiredInSeconds = channelExpiredInSeconds;
+ }
+
+ public int getContextExpiredInSeconds() {
+ return contextExpiredInSeconds;
+ }
+
+ public void setContextExpiredInSeconds(int contextExpiredInSeconds) {
+ this.contextExpiredInSeconds = contextExpiredInSeconds;
+ }
+
+ public int getRocketmqMQClientNum() {
+ return rocketmqMQClientNum;
+ }
+
+ public void setRocketmqMQClientNum(int rocketmqMQClientNum) {
+ this.rocketmqMQClientNum = rocketmqMQClientNum;
+ }
+
+ public long getGrpcProxyRelayRequestTimeoutInSeconds() {
+ return grpcProxyRelayRequestTimeoutInSeconds;
+ }
+
+ public void setGrpcProxyRelayRequestTimeoutInSeconds(long grpcProxyRelayRequestTimeoutInSeconds) {
+ this.grpcProxyRelayRequestTimeoutInSeconds = grpcProxyRelayRequestTimeoutInSeconds;
+ }
+
+ public int getGrpcProducerThreadPoolNums() {
+ return grpcProducerThreadPoolNums;
+ }
+
+ public void setGrpcProducerThreadPoolNums(int grpcProducerThreadPoolNums) {
+ this.grpcProducerThreadPoolNums = grpcProducerThreadPoolNums;
+ }
+
+ public int getGrpcProducerThreadQueueCapacity() {
+ return grpcProducerThreadQueueCapacity;
+ }
+
+ public void setGrpcProducerThreadQueueCapacity(int grpcProducerThreadQueueCapacity) {
+ this.grpcProducerThreadQueueCapacity = grpcProducerThreadQueueCapacity;
+ }
+
+ public int getGrpcConsumerThreadPoolNums() {
+ return grpcConsumerThreadPoolNums;
+ }
+
+ public void setGrpcConsumerThreadPoolNums(int grpcConsumerThreadPoolNums) {
+ this.grpcConsumerThreadPoolNums = grpcConsumerThreadPoolNums;
+ }
+
+ public int getGrpcConsumerThreadQueueCapacity() {
+ return grpcConsumerThreadQueueCapacity;
+ }
+
+ public void setGrpcConsumerThreadQueueCapacity(int grpcConsumerThreadQueueCapacity) {
+ this.grpcConsumerThreadQueueCapacity = grpcConsumerThreadQueueCapacity;
+ }
+
+ public int getGrpcRouteThreadPoolNums() {
+ return grpcRouteThreadPoolNums;
+ }
+
+ public void setGrpcRouteThreadPoolNums(int grpcRouteThreadPoolNums) {
+ this.grpcRouteThreadPoolNums = grpcRouteThreadPoolNums;
+ }
+
+ public int getGrpcRouteThreadQueueCapacity() {
+ return grpcRouteThreadQueueCapacity;
+ }
+
+ public void setGrpcRouteThreadQueueCapacity(int grpcRouteThreadQueueCapacity) {
+ this.grpcRouteThreadQueueCapacity = grpcRouteThreadQueueCapacity;
+ }
+
+ public int getGrpcClientManagerThreadPoolNums() {
+ return grpcClientManagerThreadPoolNums;
+ }
+
+ public void setGrpcClientManagerThreadPoolNums(int grpcClientManagerThreadPoolNums) {
+ this.grpcClientManagerThreadPoolNums = grpcClientManagerThreadPoolNums;
+ }
+
+ public int getGrpcClientManagerThreadQueueCapacity() {
+ return grpcClientManagerThreadQueueCapacity;
+ }
+
+ public void setGrpcClientManagerThreadQueueCapacity(int grpcClientManagerThreadQueueCapacity) {
+ this.grpcClientManagerThreadQueueCapacity = grpcClientManagerThreadQueueCapacity;
+ }
+
+ public int getGrpcTransactionThreadPoolNums() {
+ return grpcTransactionThreadPoolNums;
+ }
+
+ public void setGrpcTransactionThreadPoolNums(int grpcTransactionThreadPoolNums) {
+ this.grpcTransactionThreadPoolNums = grpcTransactionThreadPoolNums;
+ }
+
+ public int getGrpcTransactionThreadQueueCapacity() {
+ return grpcTransactionThreadQueueCapacity;
+ }
+
+ public void setGrpcTransactionThreadQueueCapacity(int grpcTransactionThreadQueueCapacity) {
+ this.grpcTransactionThreadQueueCapacity = grpcTransactionThreadQueueCapacity;
+ }
+
+ public int getProducerProcessorThreadPoolNums() {
+ return producerProcessorThreadPoolNums;
+ }
+
+ public void setProducerProcessorThreadPoolNums(int producerProcessorThreadPoolNums) {
+ this.producerProcessorThreadPoolNums = producerProcessorThreadPoolNums;
+ }
+
+ public int getProducerProcessorThreadPoolQueueCapacity() {
+ return producerProcessorThreadPoolQueueCapacity;
+ }
+
+ public void setProducerProcessorThreadPoolQueueCapacity(int producerProcessorThreadPoolQueueCapacity) {
+ this.producerProcessorThreadPoolQueueCapacity = producerProcessorThreadPoolQueueCapacity;
+ }
+
+ public int getConsumerProcessorThreadPoolNums() {
+ return consumerProcessorThreadPoolNums;
+ }
+
+ public void setConsumerProcessorThreadPoolNums(int consumerProcessorThreadPoolNums) {
+ this.consumerProcessorThreadPoolNums = consumerProcessorThreadPoolNums;
+ }
+
+ public int getConsumerProcessorThreadPoolQueueCapacity() {
+ return consumerProcessorThreadPoolQueueCapacity;
+ }
+
+ public void setConsumerProcessorThreadPoolQueueCapacity(int consumerProcessorThreadPoolQueueCapacity) {
+ this.consumerProcessorThreadPoolQueueCapacity = consumerProcessorThreadPoolQueueCapacity;
+ }
+
+ public int getTopicRouteServiceCacheExpiredInSeconds() {
+ return topicRouteServiceCacheExpiredInSeconds;
+ }
+
+ public void setTopicRouteServiceCacheExpiredInSeconds(int topicRouteServiceCacheExpiredInSeconds) {
+ this.topicRouteServiceCacheExpiredInSeconds = topicRouteServiceCacheExpiredInSeconds;
+ }
+
+ public int getTopicRouteServiceCacheMaxNum() {
+ return topicRouteServiceCacheMaxNum;
+ }
+
+ public void setTopicRouteServiceCacheMaxNum(int topicRouteServiceCacheMaxNum) {
+ this.topicRouteServiceCacheMaxNum = topicRouteServiceCacheMaxNum;
+ }
+
+ public int getTopicRouteServiceThreadPoolNums() {
+ return topicRouteServiceThreadPoolNums;
+ }
+
+ public void setTopicRouteServiceThreadPoolNums(int topicRouteServiceThreadPoolNums) {
+ this.topicRouteServiceThreadPoolNums = topicRouteServiceThreadPoolNums;
+ }
+
+ public int getTopicRouteServiceThreadPoolQueueCapacity() {
+ return topicRouteServiceThreadPoolQueueCapacity;
+ }
+
+ public void setTopicRouteServiceThreadPoolQueueCapacity(int topicRouteServiceThreadPoolQueueCapacity) {
+ this.topicRouteServiceThreadPoolQueueCapacity = topicRouteServiceThreadPoolQueueCapacity;
+ }
+
+ public int getTopicConfigCacheExpiredInSeconds() {
+ return topicConfigCacheExpiredInSeconds;
+ }
+
+ public void setTopicConfigCacheExpiredInSeconds(int topicConfigCacheExpiredInSeconds) {
+ this.topicConfigCacheExpiredInSeconds = topicConfigCacheExpiredInSeconds;
+ }
+
+ public int getTopicConfigCacheMaxNum() {
+ return topicConfigCacheMaxNum;
+ }
+
+ public void setTopicConfigCacheMaxNum(int topicConfigCacheMaxNum) {
+ this.topicConfigCacheMaxNum = topicConfigCacheMaxNum;
+ }
+
+ public int getSubscriptionGroupConfigCacheExpiredInSeconds() {
+ return subscriptionGroupConfigCacheExpiredInSeconds;
+ }
+
+ public void setSubscriptionGroupConfigCacheExpiredInSeconds(int subscriptionGroupConfigCacheExpiredInSeconds) {
+ this.subscriptionGroupConfigCacheExpiredInSeconds = subscriptionGroupConfigCacheExpiredInSeconds;
+ }
+
+ public int getSubscriptionGroupConfigCacheMaxNum() {
+ return subscriptionGroupConfigCacheMaxNum;
+ }
+
+ public void setSubscriptionGroupConfigCacheMaxNum(int subscriptionGroupConfigCacheMaxNum) {
+ this.subscriptionGroupConfigCacheMaxNum = subscriptionGroupConfigCacheMaxNum;
+ }
+
+ public int getMetadataThreadPoolNums() {
+ return metadataThreadPoolNums;
+ }
+
+ public void setMetadataThreadPoolNums(int metadataThreadPoolNums) {
+ this.metadataThreadPoolNums = metadataThreadPoolNums;
+ }
+
+ public int getMetadataThreadPoolQueueCapacity() {
+ return metadataThreadPoolQueueCapacity;
+ }
+
+ public void setMetadataThreadPoolQueueCapacity(int metadataThreadPoolQueueCapacity) {
+ this.metadataThreadPoolQueueCapacity = metadataThreadPoolQueueCapacity;
+ }
+
+ public int getTransactionHeartbeatThreadPoolNums() {
+ return transactionHeartbeatThreadPoolNums;
+ }
+
+ public void setTransactionHeartbeatThreadPoolNums(int transactionHeartbeatThreadPoolNums) {
+ this.transactionHeartbeatThreadPoolNums = transactionHeartbeatThreadPoolNums;
+ }
+
+ public int getTransactionHeartbeatThreadPoolQueueCapacity() {
+ return transactionHeartbeatThreadPoolQueueCapacity;
+ }
+
+ public void setTransactionHeartbeatThreadPoolQueueCapacity(int transactionHeartbeatThreadPoolQueueCapacity) {
+ this.transactionHeartbeatThreadPoolQueueCapacity = transactionHeartbeatThreadPoolQueueCapacity;
+ }
+
+ public int getTransactionHeartbeatPeriodSecond() {
+ return transactionHeartbeatPeriodSecond;
+ }
+
+ public void setTransactionHeartbeatPeriodSecond(int transactionHeartbeatPeriodSecond) {
+ this.transactionHeartbeatPeriodSecond = transactionHeartbeatPeriodSecond;
+ }
+
+ public int getTransactionHeartbeatBatchNum() {
+ return transactionHeartbeatBatchNum;
+ }
+
+ public void setTransactionHeartbeatBatchNum(int transactionHeartbeatBatchNum) {
+ this.transactionHeartbeatBatchNum = transactionHeartbeatBatchNum;
+ }
+
+ public long getTransactionDataExpireScanPeriodMillis() {
+ return transactionDataExpireScanPeriodMillis;
+ }
+
+ public void setTransactionDataExpireScanPeriodMillis(long transactionDataExpireScanPeriodMillis) {
+ this.transactionDataExpireScanPeriodMillis = transactionDataExpireScanPeriodMillis;
+ }
+
+ public long getTransactionDataMaxWaitClearMillis() {
+ return transactionDataMaxWaitClearMillis;
+ }
+
+ public void setTransactionDataMaxWaitClearMillis(long transactionDataMaxWaitClearMillis) {
+ this.transactionDataMaxWaitClearMillis = transactionDataMaxWaitClearMillis;
+ }
+
+ public long getTransactionDataExpireMillis() {
+ return transactionDataExpireMillis;
+ }
+
+ public void setTransactionDataExpireMillis(long transactionDataExpireMillis) {
+ this.transactionDataExpireMillis = transactionDataExpireMillis;
+ }
+
+ public int getTransactionDataMaxNum() {
+ return transactionDataMaxNum;
+ }
+
+ public void setTransactionDataMaxNum(int transactionDataMaxNum) {
+ this.transactionDataMaxNum = transactionDataMaxNum;
+ }
+
+ public long getLongPollingReserveTimeInMillis() {
+ return longPollingReserveTimeInMillis;
+ }
+
+ public void setLongPollingReserveTimeInMillis(long longPollingReserveTimeInMillis) {
+ this.longPollingReserveTimeInMillis = longPollingReserveTimeInMillis;
+ }
+
+ public boolean isEnableACL() {
+ return enableACL;
+ }
+
+ public void setEnableACL(boolean enableACL) {
+ this.enableACL = enableACL;
+ }
+
+ public boolean isEnableTopicMessageTypeCheck() {
+ return enableTopicMessageTypeCheck;
+ }
+
+ public void setEnableTopicMessageTypeCheck(boolean enableTopicMessageTypeCheck) {
+ this.enableTopicMessageTypeCheck = enableTopicMessageTypeCheck;
+ }
+
+ public long getInvisibleTimeMillisWhenClear() {
+ return invisibleTimeMillisWhenClear;
+ }
+
+ public void setInvisibleTimeMillisWhenClear(long invisibleTimeMillisWhenClear) {
+ this.invisibleTimeMillisWhenClear = invisibleTimeMillisWhenClear;
+ }
+
+ public boolean isEnableProxyAutoRenew() {
+ return enableProxyAutoRenew;
+ }
+
+ public void setEnableProxyAutoRenew(boolean enableProxyAutoRenew) {
+ this.enableProxyAutoRenew = enableProxyAutoRenew;
+ }
+
+ public long getRenewAheadTimeMillis() {
+ return renewAheadTimeMillis;
+ }
+
+ public void setRenewAheadTimeMillis(long renewAheadTimeMillis) {
+ this.renewAheadTimeMillis = renewAheadTimeMillis;
+ }
+
+ public long getRenewSliceTimeMillis() {
+ return renewSliceTimeMillis;
+ }
+
+ public void setRenewSliceTimeMillis(long renewSliceTimeMillis) {
+ this.renewSliceTimeMillis = renewSliceTimeMillis;
+ }
+
+ public long getRenewMaxTimeMillis() {
+ return renewMaxTimeMillis;
+ }
+
+ public void setRenewMaxTimeMillis(long renewMaxTimeMillis) {
+ this.renewMaxTimeMillis = renewMaxTimeMillis;
+ }
+
+ public long getRenewSchedulePeriodMillis() {
+ return renewSchedulePeriodMillis;
+ }
+
+ public void setRenewSchedulePeriodMillis(long renewSchedulePeriodMillis) {
+ this.renewSchedulePeriodMillis = renewSchedulePeriodMillis;
+ }
+
+ public int getMetricCollectorMode() {
+ return metricCollectorMode;
+ }
+
+ public void setMetricCollectorMode(int metricCollectorMode) {
+ this.metricCollectorMode = metricCollectorMode;
+ }
+
+ public String getMetricCollectorAddress() {
+ return metricCollectorAddress;
+ }
+
+ public void setMetricCollectorAddress(String metricCollectorAddress) {
+ this.metricCollectorAddress = metricCollectorAddress;
+ }
+
+ public boolean isUseDelayLevel() {
+ return useDelayLevel;
+ }
+
+ public void setUseDelayLevel(boolean useDelayLevel) {
+ this.useDelayLevel = useDelayLevel;
+ }
+
+ public String getMessageDelayLevel() {
+ return messageDelayLevel;
+ }
+
+ public void setMessageDelayLevel(String messageDelayLevel) {
+ this.messageDelayLevel = messageDelayLevel;
+ }
+
+ public Map getDelayLevelTable() {
+ return delayLevelTable;
+ }
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/GrpcServer.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/GrpcServer.java
new file mode 100644
index 0000000000..d663a88f6d
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/GrpcServer.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.proxy.grpc;
+
+import java.util.concurrent.TimeUnit;
+import org.apache.rocketmq.common.constant.LoggerName;
+import org.apache.rocketmq.logging.InternalLogger;
+import org.apache.rocketmq.logging.InternalLoggerFactory;
+import org.apache.rocketmq.proxy.common.StartAndShutdown;
+
+public class GrpcServer implements StartAndShutdown {
+ private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME);
+
+ private final io.grpc.Server server;
+
+ protected GrpcServer(io.grpc.Server server) {
+ this.server = server;
+ }
+
+ public void start() throws Exception {
+ this.server.start();
+ log.info("grpc server start successfully.");
+ }
+
+ public void shutdown() {
+ try {
+ this.server.shutdown().awaitTermination(30, TimeUnit.SECONDS);
+ log.info("grpc server shutdown successfully.");
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
\ No newline at end of file
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/GrpcServerBuilder.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/GrpcServerBuilder.java
new file mode 100644
index 0000000000..024766bf48
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/GrpcServerBuilder.java
@@ -0,0 +1,157 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.proxy.grpc;
+
+import io.grpc.BindableService;
+import io.grpc.ServerInterceptor;
+import io.grpc.ServerServiceDefinition;
+import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts;
+import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder;
+import io.grpc.netty.shaded.io.netty.channel.epoll.EpollEventLoopGroup;
+import io.grpc.netty.shaded.io.netty.channel.epoll.EpollServerSocketChannel;
+import io.grpc.netty.shaded.io.netty.channel.nio.NioEventLoopGroup;
+import io.grpc.netty.shaded.io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.grpc.netty.shaded.io.netty.handler.ssl.ClientAuth;
+import io.grpc.netty.shaded.io.netty.handler.ssl.util.InsecureTrustManagerFactory;
+import io.grpc.netty.shaded.io.netty.handler.ssl.util.SelfSignedCertificate;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.cert.CertificateException;
+import java.util.List;
+import java.util.concurrent.ThreadPoolExecutor;
+import javax.net.ssl.SSLException;
+import org.apache.rocketmq.acl.AccessValidator;
+import org.apache.rocketmq.common.constant.LoggerName;
+import org.apache.rocketmq.common.utils.ServiceProvider;
+import org.apache.rocketmq.logging.InternalLogger;
+import org.apache.rocketmq.logging.InternalLoggerFactory;
+import org.apache.rocketmq.proxy.config.ConfigurationManager;
+import org.apache.rocketmq.proxy.config.ProxyConfig;
+import org.apache.rocketmq.proxy.grpc.interceptor.AuthenticationInterceptor;
+import org.apache.rocketmq.proxy.grpc.interceptor.ContextInterceptor;
+import org.apache.rocketmq.proxy.grpc.interceptor.GlobalExceptionInterceptor;
+import org.apache.rocketmq.proxy.grpc.interceptor.HeaderInterceptor;
+
+public class GrpcServerBuilder {
+ private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME);
+ protected NettyServerBuilder serverBuilder;
+
+ public static GrpcServerBuilder newBuilder(ThreadPoolExecutor executor, int port) {
+ return new GrpcServerBuilder(executor, port);
+ }
+
+ protected GrpcServerBuilder(ThreadPoolExecutor executor, int port) {
+ serverBuilder = NettyServerBuilder.forPort(port);
+
+ try {
+ configSslContext(serverBuilder);
+ } catch (Exception e) {
+ log.error("grpc tls set failed. msg: {}, e:", e.getMessage(), e);
+ throw new RuntimeException("grpc tls set failed: " + e.getMessage());
+ }
+
+ // build server
+ int bossLoopNum = ConfigurationManager.getProxyConfig().getGrpcBossLoopNum();
+ int workerLoopNum = ConfigurationManager.getProxyConfig().getGrpcWorkerLoopNum();
+ int maxInboundMessageSize = ConfigurationManager.getProxyConfig().getGrpcMaxInboundMessageSize();
+
+ if (ConfigurationManager.getProxyConfig().isEnableGrpcEpoll()) {
+ serverBuilder.maxInboundMessageSize(maxInboundMessageSize)
+ .bossEventLoopGroup(new EpollEventLoopGroup(bossLoopNum))
+ .workerEventLoopGroup(new EpollEventLoopGroup(workerLoopNum))
+ .channelType(EpollServerSocketChannel.class)
+ .executor(executor);
+ } else {
+ serverBuilder.maxInboundMessageSize(maxInboundMessageSize)
+ .bossEventLoopGroup(new NioEventLoopGroup(bossLoopNum))
+ .workerEventLoopGroup(new NioEventLoopGroup(workerLoopNum))
+ .channelType(NioServerSocketChannel.class)
+ .executor(executor);
+ }
+
+ log.info(
+ "grpc server has built. port: {}, tlsKeyPath: {}, tlsCertPath: {}, threadPool: {}, queueCapacity: {}, "
+ + "boosLoop: {}, workerLoop: {}, maxInboundMessageSize: {}",
+ port, bossLoopNum, workerLoopNum, maxInboundMessageSize);
+ }
+
+ public GrpcServerBuilder addService(BindableService service) {
+ this.serverBuilder.addService(service);
+ return this;
+ }
+
+ public GrpcServerBuilder addService(ServerServiceDefinition service) {
+ this.serverBuilder.addService(service);
+ return this;
+ }
+
+ public GrpcServerBuilder appendInterceptor(ServerInterceptor interceptor) {
+ this.serverBuilder.intercept(interceptor);
+ return this;
+ }
+
+ public GrpcServer build() {
+ return new GrpcServer(this.serverBuilder.build());
+ }
+
+ protected void configSslContext(NettyServerBuilder serverBuilder) throws SSLException, CertificateException {
+ if (null == serverBuilder) {
+ return;
+ }
+ ProxyConfig proxyConfig = ConfigurationManager.getProxyConfig();
+ boolean tlsTestModeEnable = proxyConfig.isGrpcTlsTestModeEnable();
+ if (tlsTestModeEnable) {
+ SelfSignedCertificate selfSignedCertificate = new SelfSignedCertificate();
+ serverBuilder.sslContext(GrpcSslContexts.forServer(selfSignedCertificate.certificate(), selfSignedCertificate.privateKey())
+ .trustManager(InsecureTrustManagerFactory.INSTANCE)
+ .clientAuth(ClientAuth.NONE)
+ .build());
+ return;
+ }
+
+ String tlsKeyPath = ConfigurationManager.getProxyConfig().getGrpcTlsKeyPath();
+ String tlsCertPath = ConfigurationManager.getProxyConfig().getGrpcTlsCertPath();
+ try (InputStream serverKeyInputStream = Files.newInputStream(Paths.get(tlsKeyPath));
+ InputStream serverCertificateStream = Files.newInputStream(Paths.get(tlsCertPath))) {
+ serverBuilder.sslContext(GrpcSslContexts.forServer(serverCertificateStream, serverKeyInputStream)
+ .trustManager(InsecureTrustManagerFactory.INSTANCE)
+ .clientAuth(ClientAuth.NONE)
+ .build());
+ log.info("TLS configured OK");
+ } catch (IOException e) {
+ log.error("Failed to load Server key/certificate", e);
+ }
+ }
+
+ public GrpcServerBuilder configInterceptor() {
+ // grpc interceptors, including acl, logging etc.
+ List accessValidators = ServiceProvider.load(ServiceProvider.ACL_VALIDATOR_ID, AccessValidator.class);
+ if (!accessValidators.isEmpty()) {
+ this.serverBuilder.intercept(new AuthenticationInterceptor(accessValidators));
+ }
+
+ this.serverBuilder
+ .intercept(new GlobalExceptionInterceptor())
+ .intercept(new ContextInterceptor())
+ .intercept(new HeaderInterceptor());
+
+ return this;
+ }
+
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/AuthenticationInterceptor.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/AuthenticationInterceptor.java
new file mode 100644
index 0000000000..5aa009e733
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/AuthenticationInterceptor.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.proxy.grpc.interceptor;
+
+import com.google.protobuf.GeneratedMessageV3;
+import io.grpc.Context;
+import io.grpc.ForwardingServerCallListener;
+import io.grpc.Metadata;
+import io.grpc.ServerCall;
+import io.grpc.ServerCallHandler;
+import io.grpc.ServerInterceptor;
+import io.grpc.Status;
+import io.grpc.StatusRuntimeException;
+import java.util.List;
+import org.apache.rocketmq.acl.AccessResource;
+import org.apache.rocketmq.acl.AccessValidator;
+import org.apache.rocketmq.acl.common.AclException;
+import org.apache.rocketmq.acl.common.AuthenticationHeader;
+import org.apache.rocketmq.acl.plain.PlainAccessResource;
+import org.apache.rocketmq.proxy.config.ConfigurationManager;
+
+public class AuthenticationInterceptor implements ServerInterceptor {
+ protected final List accessValidatorList;
+
+ public AuthenticationInterceptor(List accessValidatorList) {
+ this.accessValidatorList = accessValidatorList;
+ }
+
+ @Override
+ public ServerCall.Listener interceptCall(ServerCall call, Metadata headers,
+ ServerCallHandler next) {
+ return new ForwardingServerCallListener.SimpleForwardingServerCallListener(next.startCall(call, headers)) {
+ @Override
+ public void onMessage(R message) {
+ GeneratedMessageV3 messageV3 = (GeneratedMessageV3) message;
+ headers.put(InterceptorConstants.RPC_NAME, messageV3.getDescriptorForType().getFullName());
+ if (ConfigurationManager.getProxyConfig().isEnableACL()) {
+ try {
+ AuthenticationHeader authenticationHeader = AuthenticationHeader.builder()
+ .remoteAddress(InterceptorConstants.METADATA.get(Context.current()).get(InterceptorConstants.REMOTE_ADDRESS))
+ .namespace(InterceptorConstants.METADATA.get(Context.current()).get(InterceptorConstants.NAMESPACE_ID))
+ .authorization(InterceptorConstants.METADATA.get(Context.current()).get(InterceptorConstants.AUTHORIZATION))
+ .datetime(InterceptorConstants.METADATA.get(Context.current()).get(InterceptorConstants.DATE_TIME))
+ .sessionToken(InterceptorConstants.METADATA.get(Context.current()).get(InterceptorConstants.SESSION_TOKEN))
+ .requestId(InterceptorConstants.METADATA.get(Context.current()).get(InterceptorConstants.REQUEST_ID))
+ .language(InterceptorConstants.METADATA.get(Context.current()).get(InterceptorConstants.LANGUAGE))
+ .clientVersion(InterceptorConstants.METADATA.get(Context.current()).get(InterceptorConstants.CLIENT_VERSION))
+ .protocol(InterceptorConstants.METADATA.get(Context.current()).get(InterceptorConstants.PROTOCOL_VERSION))
+ .requestCode(RequestMapping.map(messageV3.getDescriptorForType().getFullName()))
+ .build();
+
+ validate(authenticationHeader, headers, messageV3);
+ super.onMessage(message);
+ } catch (AclException aclException) {
+ throw new StatusRuntimeException(Status.PERMISSION_DENIED, headers);
+ }
+ } else {
+ super.onMessage(message);
+ }
+ }
+ };
+ }
+
+ protected void validate(AuthenticationHeader authenticationHeader, Metadata headers, GeneratedMessageV3 messageV3) {
+ for (AccessValidator accessValidator : accessValidatorList) {
+ AccessResource accessResource = accessValidator.parse(messageV3, authenticationHeader);
+ accessValidator.validate(accessResource);
+
+ if (accessResource instanceof PlainAccessResource) {
+ PlainAccessResource plainAccessResource = (PlainAccessResource) accessResource;
+ headers.put(InterceptorConstants.AUTHORIZATION_AK, plainAccessResource.getAccessKey());
+ }
+ }
+ }
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/ContextInterceptor.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/ContextInterceptor.java
new file mode 100644
index 0000000000..07d7ab9bf3
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/ContextInterceptor.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.proxy.grpc.interceptor;
+
+import io.grpc.Context;
+import io.grpc.Contexts;
+import io.grpc.Metadata;
+import io.grpc.ServerCall;
+import io.grpc.ServerCallHandler;
+import io.grpc.ServerInterceptor;
+
+public class ContextInterceptor implements ServerInterceptor {
+
+ @Override
+ public ServerCall.Listener interceptCall(
+ ServerCall call,
+ Metadata headers,
+ ServerCallHandler next
+ ) {
+ Context context = Context.current().withValue(InterceptorConstants.METADATA, headers);
+ return Contexts.interceptCall(context, call, headers, next);
+ }
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/GlobalExceptionInterceptor.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/GlobalExceptionInterceptor.java
new file mode 100644
index 0000000000..0c34b15743
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/GlobalExceptionInterceptor.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.proxy.grpc.interceptor;
+
+import io.grpc.ForwardingServerCall;
+import io.grpc.ForwardingServerCallListener;
+import io.grpc.Metadata;
+import io.grpc.ServerCall;
+import io.grpc.ServerCallHandler;
+import io.grpc.ServerInterceptor;
+import io.grpc.Status;
+import io.grpc.StatusRuntimeException;
+import org.apache.rocketmq.common.constant.LoggerName;
+import org.apache.rocketmq.logging.InternalLogger;
+import org.apache.rocketmq.logging.InternalLoggerFactory;
+
+public class GlobalExceptionInterceptor implements ServerInterceptor {
+ private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME);
+
+ @Override
+ public ServerCall.Listener interceptCall(
+ ServerCall call,
+ Metadata headers,
+ ServerCallHandler next
+ ) {
+ final ServerCall serverCall = new ClosableServerCall<>(call);
+ ServerCall.Listener delegate = next.startCall(serverCall, headers);
+ return new ForwardingServerCallListener.SimpleForwardingServerCallListener(delegate) {
+ @Override
+ public void onMessage(R message) {
+ try {
+ super.onMessage(message);
+ } catch (Throwable e) {
+ closeWithException(e);
+ }
+ }
+
+ @Override
+ public void onHalfClose() {
+ try {
+ super.onHalfClose();
+ } catch (Throwable e) {
+ closeWithException(e);
+ }
+ }
+
+ @Override
+ public void onCancel() {
+ try {
+ super.onCancel();
+ } catch (Throwable e) {
+ closeWithException(e);
+ }
+ }
+
+ @Override
+ public void onComplete() {
+ try {
+ super.onComplete();
+ } catch (Throwable e) {
+ closeWithException(e);
+ }
+ }
+
+ @Override
+ public void onReady() {
+ try {
+ super.onReady();
+ } catch (Throwable e) {
+ closeWithException(e);
+ }
+ }
+
+ private void closeWithException(Throwable t) {
+ Metadata trailers = new Metadata();
+ Status status = Status.INTERNAL.withDescription(t.getMessage());
+ boolean printLog = true;
+
+ if (t instanceof StatusRuntimeException) {
+ trailers = ((StatusRuntimeException) t).getTrailers();
+ status = ((StatusRuntimeException) t).getStatus();
+ // no error stack for permission denied.
+ if (status.getCode().value() == Status.PERMISSION_DENIED.getCode().value()) {
+ printLog = false;
+ }
+ }
+
+ if (printLog) {
+ log.error("grpc server has exception. errorMsg:{}, e:", t.getMessage(), t);
+ }
+
+ serverCall.close(status, trailers);
+ }
+ };
+ }
+
+ private static class ClosableServerCall extends
+ ForwardingServerCall.SimpleForwardingServerCall {
+ private boolean closeCalled = false;
+
+ ClosableServerCall(ServerCall delegate) {
+ super(delegate);
+ }
+
+ @Override
+ public synchronized void close(final Status status, final Metadata trailers) {
+ if (!closeCalled) {
+ closeCalled = true;
+ ClosableServerCall.super.close(status, trailers);
+ }
+ }
+ }
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/HeaderInterceptor.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/HeaderInterceptor.java
new file mode 100644
index 0000000000..1cbb003610
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/HeaderInterceptor.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.proxy.grpc.interceptor;
+
+import com.google.common.net.HostAndPort;
+import io.grpc.Grpc;
+import io.grpc.Metadata;
+import io.grpc.ServerCall;
+import io.grpc.ServerCallHandler;
+import io.grpc.ServerInterceptor;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+
+public class HeaderInterceptor implements ServerInterceptor {
+ @Override
+ public ServerCall.Listener interceptCall(
+ ServerCall call,
+ Metadata headers,
+ ServerCallHandler next
+ ) {
+ SocketAddress remoteSocketAddress = call.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR);
+ String remoteAddress = parseSocketAddress(remoteSocketAddress);
+ headers.put(InterceptorConstants.REMOTE_ADDRESS, remoteAddress);
+
+ SocketAddress localSocketAddress = call.getAttributes().get(Grpc.TRANSPORT_ATTR_LOCAL_ADDR);
+ String localAddress = parseSocketAddress(localSocketAddress);
+ headers.put(InterceptorConstants.LOCAL_ADDRESS, localAddress);
+ return next.startCall(call, headers);
+ }
+
+ private String parseSocketAddress(SocketAddress socketAddress) {
+ if (socketAddress instanceof InetSocketAddress) {
+ InetSocketAddress inetSocketAddress = (InetSocketAddress) socketAddress;
+ return HostAndPort.fromParts(
+ inetSocketAddress.getAddress()
+ .getHostAddress(),
+ inetSocketAddress.getPort()
+ ).toString();
+ }
+
+ return "";
+ }
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/InterceptorConstants.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/InterceptorConstants.java
new file mode 100644
index 0000000000..c8aa39959e
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/InterceptorConstants.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.proxy.grpc.interceptor;
+
+import io.grpc.Context;
+import io.grpc.Metadata;
+
+public class InterceptorConstants {
+ public static final Context.Key METADATA = Context.key("rpc-metadata");
+
+ /**
+ * Remote address key in attributes of call
+ */
+ public static final Metadata.Key REMOTE_ADDRESS
+ = Metadata.Key.of("rpc-remote-address", Metadata.ASCII_STRING_MARSHALLER);
+
+ /**
+ * Local address key in attributes of call
+ */
+ public static final Metadata.Key LOCAL_ADDRESS
+ = Metadata.Key.of("rpc-local-address", Metadata.ASCII_STRING_MARSHALLER);
+
+ public static final Metadata.Key AUTHORIZATION
+ = Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER);
+
+ public static final Metadata.Key NAMESPACE_ID
+ = Metadata.Key.of("x-mq-namespace", Metadata.ASCII_STRING_MARSHALLER);
+
+ public static final Metadata.Key DATE_TIME
+ = Metadata.Key.of("x-mq-date-time", Metadata.ASCII_STRING_MARSHALLER);
+
+ public static final Metadata.Key REQUEST_ID
+ = Metadata.Key.of("x-mq-request-id", Metadata.ASCII_STRING_MARSHALLER);
+
+ public static final Metadata.Key LANGUAGE
+ = Metadata.Key.of("x-mq-language", Metadata.ASCII_STRING_MARSHALLER);
+
+ public static final Metadata.Key CLIENT_VERSION
+ = Metadata.Key.of("x-mq-client-version", Metadata.ASCII_STRING_MARSHALLER);
+
+ public static final Metadata.Key PROTOCOL_VERSION
+ = Metadata.Key.of("x-mq-protocol", Metadata.ASCII_STRING_MARSHALLER);
+
+ public static final Metadata.Key RPC_NAME
+ = Metadata.Key.of("x-mq-rpc-name", Metadata.ASCII_STRING_MARSHALLER);
+
+ public static final Metadata.Key SESSION_TOKEN
+ = Metadata.Key.of("x-mq-session-token", Metadata.ASCII_STRING_MARSHALLER);
+
+ public static final Metadata.Key CLIENT_ID
+ = Metadata.Key.of("x-mq-client-id", Metadata.ASCII_STRING_MARSHALLER);
+
+ public static final Metadata.Key AUTHORIZATION_AK
+ = Metadata.Key.of("x-mq-authorization-ak", Metadata.ASCII_STRING_MARSHALLER);
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/RequestMapping.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/RequestMapping.java
new file mode 100644
index 0000000000..a9674d1837
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/interceptor/RequestMapping.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.proxy.grpc.interceptor;
+
+import apache.rocketmq.v2.AckMessageRequest;
+import apache.rocketmq.v2.ChangeInvisibleDurationRequest;
+import apache.rocketmq.v2.EndTransactionRequest;
+import apache.rocketmq.v2.ForwardMessageToDeadLetterQueueResponse;
+import apache.rocketmq.v2.HeartbeatRequest;
+import apache.rocketmq.v2.NotifyClientTerminationRequest;
+import apache.rocketmq.v2.QueryAssignmentRequest;
+import apache.rocketmq.v2.QueryRouteRequest;
+import apache.rocketmq.v2.ReceiveMessageRequest;
+import apache.rocketmq.v2.SendMessageRequest;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.rocketmq.common.protocol.RequestCode;
+
+public class RequestMapping {
+ private final static Map REQUEST_MAP = new HashMap() {
+ {
+ // v2
+ put(QueryRouteRequest.getDescriptor().getFullName(), RequestCode.GET_ROUTEINFO_BY_TOPIC);
+ put(HeartbeatRequest.getDescriptor().getFullName(), RequestCode.HEART_BEAT);
+ put(SendMessageRequest.getDescriptor().getFullName(), RequestCode.SEND_MESSAGE_V2);
+ put(QueryAssignmentRequest.getDescriptor().getFullName(), RequestCode.GET_ROUTEINFO_BY_TOPIC);
+ put(ReceiveMessageRequest.getDescriptor().getFullName(), RequestCode.PULL_MESSAGE);
+ put(AckMessageRequest.getDescriptor().getFullName(), RequestCode.UPDATE_CONSUMER_OFFSET);
+ put(ForwardMessageToDeadLetterQueueResponse.getDescriptor().getFullName(), RequestCode.CONSUMER_SEND_MSG_BACK);
+ put(EndTransactionRequest.getDescriptor().getFullName(), RequestCode.END_TRANSACTION);
+ put(NotifyClientTerminationRequest.getDescriptor().getFullName(), RequestCode.UNREGISTER_CLIENT);
+ put(ChangeInvisibleDurationRequest.getDescriptor().getFullName(), RequestCode.CONSUMER_SEND_MSG_BACK);
+ }
+ };
+
+ public static int map(String rpcFullName) {
+ if (REQUEST_MAP.containsKey(rpcFullName)) {
+ return REQUEST_MAP.get(rpcFullName);
+ }
+ return RequestCode.HEART_BEAT;
+ }
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/AbstractMessingActivity.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/AbstractMessingActivity.java
new file mode 100644
index 0000000000..13b855768a
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/AbstractMessingActivity.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.proxy.grpc.v2;
+
+import apache.rocketmq.v2.Resource;
+import org.apache.rocketmq.common.constant.LoggerName;
+import org.apache.rocketmq.logging.InternalLogger;
+import org.apache.rocketmq.logging.InternalLoggerFactory;
+import org.apache.rocketmq.proxy.grpc.v2.channel.GrpcChannelManager;
+import org.apache.rocketmq.proxy.grpc.v2.common.GrpcClientSettingsManager;
+import org.apache.rocketmq.proxy.grpc.v2.common.GrpcValidator;
+import org.apache.rocketmq.proxy.processor.MessagingProcessor;
+
+public abstract class AbstractMessingActivity {
+ protected static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME);
+ protected final MessagingProcessor messagingProcessor;
+ protected final GrpcClientSettingsManager grpcClientSettingsManager;
+ protected final GrpcChannelManager grpcChannelManager;
+
+ public AbstractMessingActivity(MessagingProcessor messagingProcessor,
+ GrpcClientSettingsManager grpcClientSettingsManager, GrpcChannelManager grpcChannelManager) {
+ this.messagingProcessor = messagingProcessor;
+ this.grpcClientSettingsManager = grpcClientSettingsManager;
+ this.grpcChannelManager = grpcChannelManager;
+ }
+
+ protected void validateTopic(Resource topic) {
+ GrpcValidator.getInstance().validateTopic(topic);
+ }
+
+ protected void validateConsumerGroup(Resource consumerGroup) {
+ GrpcValidator.getInstance().validateConsumerGroup(consumerGroup);
+ }
+
+ protected void validateTopicAndConsumerGroup(Resource topic, Resource consumerGroup) {
+ GrpcValidator.getInstance().validateTopicAndConsumerGroup(topic, consumerGroup);
+ }
+
+ protected void validateInvisibleTime(long invisibleTime) {
+ GrpcValidator.getInstance().validateInvisibleTime(invisibleTime);
+ }
+
+ protected void validateInvisibleTime(long invisibleTime, long minInvisibleTime) {
+ GrpcValidator.getInstance().validateInvisibleTime(invisibleTime, minInvisibleTime);
+ }
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/DefaultGrpcMessingActivity.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/DefaultGrpcMessingActivity.java
new file mode 100644
index 0000000000..8cac746bf6
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/DefaultGrpcMessingActivity.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.proxy.grpc.v2;
+
+import apache.rocketmq.v2.AckMessageRequest;
+import apache.rocketmq.v2.AckMessageResponse;
+import apache.rocketmq.v2.ChangeInvisibleDurationRequest;
+import apache.rocketmq.v2.ChangeInvisibleDurationResponse;
+import apache.rocketmq.v2.EndTransactionRequest;
+import apache.rocketmq.v2.EndTransactionResponse;
+import apache.rocketmq.v2.ForwardMessageToDeadLetterQueueRequest;
+import apache.rocketmq.v2.ForwardMessageToDeadLetterQueueResponse;
+import apache.rocketmq.v2.HeartbeatRequest;
+import apache.rocketmq.v2.HeartbeatResponse;
+import apache.rocketmq.v2.NotifyClientTerminationRequest;
+import apache.rocketmq.v2.NotifyClientTerminationResponse;
+import apache.rocketmq.v2.QueryAssignmentRequest;
+import apache.rocketmq.v2.QueryAssignmentResponse;
+import apache.rocketmq.v2.QueryRouteRequest;
+import apache.rocketmq.v2.QueryRouteResponse;
+import apache.rocketmq.v2.ReceiveMessageRequest;
+import apache.rocketmq.v2.ReceiveMessageResponse;
+import apache.rocketmq.v2.SendMessageRequest;
+import apache.rocketmq.v2.SendMessageResponse;
+import apache.rocketmq.v2.TelemetryCommand;
+import io.grpc.stub.StreamObserver;
+import java.util.concurrent.CompletableFuture;
+import org.apache.rocketmq.common.constant.LoggerName;
+import org.apache.rocketmq.logging.InternalLogger;
+import org.apache.rocketmq.logging.InternalLoggerFactory;
+import org.apache.rocketmq.proxy.common.AbstractStartAndShutdown;
+import org.apache.rocketmq.proxy.common.ProxyContext;
+import org.apache.rocketmq.proxy.grpc.v2.channel.GrpcChannelManager;
+import org.apache.rocketmq.proxy.grpc.v2.client.ClientActivity;
+import org.apache.rocketmq.proxy.grpc.v2.common.GrpcClientSettingsManager;
+import org.apache.rocketmq.proxy.grpc.v2.consumer.AckMessageActivity;
+import org.apache.rocketmq.proxy.grpc.v2.consumer.ChangeInvisibleDurationActivity;
+import org.apache.rocketmq.proxy.grpc.v2.consumer.ReceiveMessageActivity;
+import org.apache.rocketmq.proxy.grpc.v2.producer.ForwardMessageToDLQActivity;
+import org.apache.rocketmq.proxy.grpc.v2.producer.SendMessageActivity;
+import org.apache.rocketmq.proxy.grpc.v2.route.RouteActivity;
+import org.apache.rocketmq.proxy.grpc.v2.transaction.EndTransactionActivity;
+import org.apache.rocketmq.proxy.processor.MessagingProcessor;
+import org.apache.rocketmq.proxy.processor.ReceiptHandleProcessor;
+
+public class DefaultGrpcMessingActivity extends AbstractStartAndShutdown implements GrpcMessingActivity {
+ private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME);
+
+ protected GrpcClientSettingsManager grpcClientSettingsManager;
+ protected GrpcChannelManager grpcChannelManager;
+ protected ReceiptHandleProcessor receiptHandleProcessor;
+ protected ReceiveMessageActivity receiveMessageActivity;
+ protected AckMessageActivity ackMessageActivity;
+ protected ChangeInvisibleDurationActivity changeInvisibleDurationActivity;
+ protected SendMessageActivity sendMessageActivity;
+ protected ForwardMessageToDLQActivity forwardMessageToDLQActivity;
+ protected EndTransactionActivity endTransactionActivity;
+ protected RouteActivity routeActivity;
+ protected ClientActivity clientActivity;
+
+ protected DefaultGrpcMessingActivity(MessagingProcessor messagingProcessor) {
+ this.grpcClientSettingsManager = new GrpcClientSettingsManager(messagingProcessor);
+ this.grpcChannelManager = new GrpcChannelManager(messagingProcessor.getProxyRelayService());
+ this.receiptHandleProcessor = new ReceiptHandleProcessor(messagingProcessor);
+
+ this.receiveMessageActivity = new ReceiveMessageActivity(messagingProcessor, receiptHandleProcessor, grpcClientSettingsManager, grpcChannelManager);
+ this.ackMessageActivity = new AckMessageActivity(messagingProcessor, receiptHandleProcessor, grpcClientSettingsManager, grpcChannelManager);
+ this.changeInvisibleDurationActivity = new ChangeInvisibleDurationActivity(messagingProcessor, receiptHandleProcessor, grpcClientSettingsManager, grpcChannelManager);
+ this.sendMessageActivity = new SendMessageActivity(messagingProcessor, grpcClientSettingsManager, grpcChannelManager);
+ this.forwardMessageToDLQActivity = new ForwardMessageToDLQActivity(messagingProcessor, grpcClientSettingsManager, grpcChannelManager);
+ this.endTransactionActivity = new EndTransactionActivity(messagingProcessor, grpcClientSettingsManager, grpcChannelManager);
+ this.routeActivity = new RouteActivity(messagingProcessor, grpcClientSettingsManager, grpcChannelManager);
+ this.clientActivity = new ClientActivity(messagingProcessor, grpcClientSettingsManager, grpcChannelManager);
+
+ this.init();
+ }
+
+ protected void init() {
+ this.appendStartAndShutdown(this.receiptHandleProcessor);
+ }
+
+ @Override
+ public CompletableFuture queryRoute(ProxyContext ctx, QueryRouteRequest request) {
+ return this.routeActivity.queryRoute(ctx, request);
+ }
+
+ @Override
+ public CompletableFuture heartbeat(ProxyContext ctx, HeartbeatRequest request) {
+ return this.clientActivity.heartbeat(ctx, request);
+ }
+
+ @Override
+ public CompletableFuture sendMessage(ProxyContext ctx, SendMessageRequest request) {
+ return this.sendMessageActivity.sendMessage(ctx, request);
+ }
+
+ @Override
+ public CompletableFuture queryAssignment(ProxyContext ctx,
+ QueryAssignmentRequest request) {
+ return this.routeActivity.queryAssignment(ctx, request);
+ }
+
+ @Override
+ public void receiveMessage(ProxyContext ctx, ReceiveMessageRequest request,
+ StreamObserver responseObserver) {
+ this.receiveMessageActivity.receiveMessage(ctx, request, responseObserver);
+ }
+
+ @Override
+ public CompletableFuture ackMessage(ProxyContext ctx, AckMessageRequest request) {
+ return this.ackMessageActivity.ackMessage(ctx, request);
+ }
+
+ @Override
+ public CompletableFuture forwardMessageToDeadLetterQueue(ProxyContext ctx,
+ ForwardMessageToDeadLetterQueueRequest request) {
+ return this.forwardMessageToDLQActivity.forwardMessageToDeadLetterQueue(ctx, request);
+ }
+
+ @Override
+ public CompletableFuture endTransaction(ProxyContext ctx, EndTransactionRequest request) {
+ return this.endTransactionActivity.endTransaction(ctx, request);
+ }
+
+ @Override
+ public CompletableFuture notifyClientTermination(ProxyContext ctx,
+ NotifyClientTerminationRequest request) {
+ return this.clientActivity.notifyClientTermination(ctx, request);
+ }
+
+ @Override
+ public CompletableFuture changeInvisibleDuration(ProxyContext ctx,
+ ChangeInvisibleDurationRequest request) {
+ return this.changeInvisibleDurationActivity.changeInvisibleDuration(ctx, request);
+ }
+
+ @Override
+ public StreamObserver telemetry(ProxyContext ctx,
+ StreamObserver responseObserver) {
+ return this.clientActivity.telemetry(ctx, responseObserver);
+ }
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/GrpcMessagingApplication.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/GrpcMessagingApplication.java
new file mode 100644
index 0000000000..9c940dee76
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/GrpcMessagingApplication.java
@@ -0,0 +1,467 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.proxy.grpc.v2;
+
+import apache.rocketmq.v2.AckMessageRequest;
+import apache.rocketmq.v2.AckMessageResponse;
+import apache.rocketmq.v2.ChangeInvisibleDurationRequest;
+import apache.rocketmq.v2.ChangeInvisibleDurationResponse;
+import apache.rocketmq.v2.Code;
+import apache.rocketmq.v2.EndTransactionRequest;
+import apache.rocketmq.v2.EndTransactionResponse;
+import apache.rocketmq.v2.ForwardMessageToDeadLetterQueueRequest;
+import apache.rocketmq.v2.ForwardMessageToDeadLetterQueueResponse;
+import apache.rocketmq.v2.HeartbeatRequest;
+import apache.rocketmq.v2.HeartbeatResponse;
+import apache.rocketmq.v2.MessagingServiceGrpc;
+import apache.rocketmq.v2.NotifyClientTerminationRequest;
+import apache.rocketmq.v2.NotifyClientTerminationResponse;
+import apache.rocketmq.v2.QueryAssignmentRequest;
+import apache.rocketmq.v2.QueryAssignmentResponse;
+import apache.rocketmq.v2.QueryRouteRequest;
+import apache.rocketmq.v2.QueryRouteResponse;
+import apache.rocketmq.v2.ReceiveMessageRequest;
+import apache.rocketmq.v2.ReceiveMessageResponse;
+import apache.rocketmq.v2.SendMessageRequest;
+import apache.rocketmq.v2.SendMessageResponse;
+import apache.rocketmq.v2.Status;
+import apache.rocketmq.v2.TelemetryCommand;
+import io.grpc.Context;
+import io.grpc.Metadata;
+import io.grpc.stub.StreamObserver;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.RejectedExecutionHandler;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.rocketmq.common.constant.LoggerName;
+import org.apache.rocketmq.common.thread.ThreadPoolMonitor;
+import org.apache.rocketmq.proxy.common.ProxyContext;
+import org.apache.rocketmq.proxy.common.StartAndShutdown;
+import org.apache.rocketmq.proxy.config.ConfigurationManager;
+import org.apache.rocketmq.proxy.config.ProxyConfig;
+import org.apache.rocketmq.proxy.grpc.interceptor.InterceptorConstants;
+import org.apache.rocketmq.proxy.grpc.v2.common.GrpcProxyException;
+import org.apache.rocketmq.proxy.grpc.v2.common.ResponseBuilder;
+import org.apache.rocketmq.proxy.grpc.v2.common.ResponseWriter;
+import org.apache.rocketmq.proxy.processor.MessagingProcessor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class GrpcMessagingApplication extends MessagingServiceGrpc.MessagingServiceImplBase implements StartAndShutdown {
+ private final static Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME);
+
+ private final GrpcMessingActivity grpcMessingActivity;
+
+ protected ThreadPoolExecutor routeThreadPoolExecutor;
+ protected ThreadPoolExecutor producerThreadPoolExecutor;
+ protected ThreadPoolExecutor consumerThreadPoolExecutor;
+ protected ThreadPoolExecutor clientManagerThreadPoolExecutor;
+ protected ThreadPoolExecutor transactionThreadPoolExecutor;
+
+ protected GrpcMessagingApplication(GrpcMessingActivity grpcMessingActivity) {
+ this.grpcMessingActivity = grpcMessingActivity;
+
+ ProxyConfig config = ConfigurationManager.getProxyConfig();
+ this.routeThreadPoolExecutor = ThreadPoolMonitor.createAndMonitor(
+ config.getGrpcRouteThreadPoolNums(),
+ config.getGrpcRouteThreadPoolNums(),
+ 1,
+ TimeUnit.MINUTES,
+ "GrpcRouteThreadPool",
+ config.getGrpcRouteThreadQueueCapacity()
+ );
+ this.producerThreadPoolExecutor = ThreadPoolMonitor.createAndMonitor(
+ config.getGrpcProducerThreadPoolNums(),
+ config.getGrpcProducerThreadPoolNums(),
+ 1,
+ TimeUnit.MINUTES,
+ "GrpcProducerThreadPool",
+ config.getGrpcProducerThreadQueueCapacity()
+ );
+ this.consumerThreadPoolExecutor = ThreadPoolMonitor.createAndMonitor(
+ config.getGrpcConsumerThreadPoolNums(),
+ config.getGrpcConsumerThreadPoolNums(),
+ 1,
+ TimeUnit.MINUTES,
+ "GrpcConsumerThreadPool",
+ config.getGrpcConsumerThreadQueueCapacity()
+ );
+ this.clientManagerThreadPoolExecutor = ThreadPoolMonitor.createAndMonitor(
+ config.getGrpcClientManagerThreadPoolNums(),
+ config.getGrpcClientManagerThreadPoolNums(),
+ 1,
+ TimeUnit.MINUTES,
+ "GrpcClientManagerThreadPool",
+ config.getGrpcClientManagerThreadQueueCapacity()
+ );
+ this.transactionThreadPoolExecutor = ThreadPoolMonitor.createAndMonitor(
+ config.getGrpcTransactionThreadPoolNums(),
+ config.getGrpcTransactionThreadPoolNums(),
+ 1,
+ TimeUnit.MINUTES,
+ "GrpcTransactionThreadPool",
+ config.getGrpcTransactionThreadQueueCapacity()
+ );
+
+ this.init();
+ }
+
+ protected void init() {
+ GrpcTaskRejectedExecutionHandler rejectedExecutionHandler = new GrpcTaskRejectedExecutionHandler();
+ this.routeThreadPoolExecutor.setRejectedExecutionHandler(rejectedExecutionHandler);
+ this.routeThreadPoolExecutor.setRejectedExecutionHandler(rejectedExecutionHandler);
+ this.producerThreadPoolExecutor.setRejectedExecutionHandler(rejectedExecutionHandler);
+ this.consumerThreadPoolExecutor.setRejectedExecutionHandler(rejectedExecutionHandler);
+ this.clientManagerThreadPoolExecutor.setRejectedExecutionHandler(rejectedExecutionHandler);
+ this.transactionThreadPoolExecutor.setRejectedExecutionHandler(rejectedExecutionHandler);
+ }
+
+ public static GrpcMessagingApplication create(MessagingProcessor messagingProcessor) {
+ return new GrpcMessagingApplication(new DefaultGrpcMessingActivity(
+ messagingProcessor
+ ));
+ }
+
+ protected Status flowLimitStatus() {
+ return ResponseBuilder.getInstance().buildStatus(Code.TOO_MANY_REQUESTS, "flow limit");
+ }
+
+ protected Status convertExceptionToStatus(Throwable t) {
+ return ResponseBuilder.getInstance().buildStatus(t);
+ }
+
+ protected void addExecutor(ExecutorService executor, ProxyContext context, V request, Runnable runnable,
+ StreamObserver responseObserver, Function statusResponseCreator) {
+ executor.submit(new GrpcTask<>(runnable, context, request, responseObserver, statusResponseCreator.apply(flowLimitStatus())));
+ }
+
+ protected void writeResponse(ProxyContext context, V request, T response, StreamObserver responseObserver,
+ Throwable t, Function errorResponseCreator) {
+ if (t != null) {
+ ResponseWriter.getInstance().write(
+ responseObserver,
+ errorResponseCreator.apply(convertExceptionToStatus(t))
+ );
+ } else {
+ ResponseWriter.getInstance().write(responseObserver, response);
+ }
+ }
+
+ protected ProxyContext createContext() {
+ Context ctx = Context.current();
+ Metadata headers = InterceptorConstants.METADATA.get(ctx);
+ ProxyContext context = ProxyContext.create()
+ .setLocalAddress(getDefaultStringMetadataInfo(headers, InterceptorConstants.LOCAL_ADDRESS))
+ .setRemoteAddress(getDefaultStringMetadataInfo(headers, InterceptorConstants.REMOTE_ADDRESS))
+ .setClientID(getDefaultStringMetadataInfo(headers, InterceptorConstants.CLIENT_ID))
+ .setLanguage(getDefaultStringMetadataInfo(headers, InterceptorConstants.LANGUAGE))
+ .setClientVersion(getDefaultStringMetadataInfo(headers, InterceptorConstants.CLIENT_VERSION))
+ .setAction(getDefaultStringMetadataInfo(headers, InterceptorConstants.RPC_NAME));
+ if (ctx.getDeadline() != null) {
+ context.setRemainingMs(ctx.getDeadline().timeRemaining(TimeUnit.MILLISECONDS));
+ }
+ return context;
+ }
+
+ protected void validateContext(ProxyContext context) {
+ if (StringUtils.isBlank(context.getClientID())) {
+ throw new GrpcProxyException(Code.CLIENT_ID_REQUIRED, "client id cannot be empty");
+ }
+ }
+
+ protected String getDefaultStringMetadataInfo(Metadata headers, Metadata.Key key) {
+ return StringUtils.defaultString(headers.get(key));
+ }
+
+ @Override
+ public void queryRoute(QueryRouteRequest request, StreamObserver responseObserver) {
+ Function statusResponseCreator = status -> QueryRouteResponse.newBuilder().setStatus(status).build();
+ ProxyContext context = createContext();
+ try {
+ validateContext(context);
+ this.addExecutor(this.routeThreadPoolExecutor,
+ context,
+ request,
+ () -> grpcMessingActivity.queryRoute(context, request)
+ .whenComplete((response, throwable) -> writeResponse(context, request, response, responseObserver, throwable, statusResponseCreator)),
+ responseObserver,
+ statusResponseCreator);
+ } catch (Throwable t) {
+ writeResponse(context, request, null, responseObserver, t, statusResponseCreator);
+ }
+ }
+
+ @Override
+ public void heartbeat(HeartbeatRequest request, StreamObserver responseObserver) {
+ Function statusResponseCreator = status -> HeartbeatResponse.newBuilder().setStatus(status).build();
+ ProxyContext context = createContext();
+ try {
+ validateContext(context);
+ this.addExecutor(this.clientManagerThreadPoolExecutor,
+ context,
+ request,
+ () -> grpcMessingActivity.heartbeat(context, request)
+ .whenComplete((response, throwable) -> writeResponse(context, request, response, responseObserver, throwable, statusResponseCreator)),
+ responseObserver,
+ statusResponseCreator);
+ } catch (Throwable t) {
+ writeResponse(context, request, null, responseObserver, t, statusResponseCreator);
+ }
+ }
+
+ @Override
+ public void sendMessage(SendMessageRequest request, StreamObserver responseObserver) {
+ Function statusResponseCreator = status -> SendMessageResponse.newBuilder().setStatus(status).build();
+ ProxyContext context = createContext();
+ try {
+ validateContext(context);
+ this.addExecutor(this.producerThreadPoolExecutor,
+ context,
+ request,
+ () -> grpcMessingActivity.sendMessage(context, request)
+ .whenComplete((response, throwable) -> writeResponse(context, request, response, responseObserver, throwable, statusResponseCreator)),
+ responseObserver,
+ statusResponseCreator);
+ } catch (Throwable t) {
+ writeResponse(context, request, null, responseObserver, t, statusResponseCreator);
+ }
+ }
+
+ @Override
+ public void queryAssignment(QueryAssignmentRequest request,
+ StreamObserver responseObserver) {
+ Function statusResponseCreator = status -> QueryAssignmentResponse.newBuilder().setStatus(status).build();
+ ProxyContext context = createContext();
+ try {
+ validateContext(context);
+ this.addExecutor(this.routeThreadPoolExecutor,
+ context,
+ request,
+ () -> grpcMessingActivity.queryAssignment(context, request)
+ .whenComplete((response, throwable) -> writeResponse(context, request, response, responseObserver, throwable, statusResponseCreator)),
+ responseObserver,
+ statusResponseCreator);
+ } catch (Throwable t) {
+ writeResponse(context, request, null, responseObserver, t, statusResponseCreator);
+ }
+ }
+
+ @Override
+ public void receiveMessage(ReceiveMessageRequest request, StreamObserver responseObserver) {
+ Function statusResponseCreator = status -> ReceiveMessageResponse.newBuilder().setStatus(status).build();
+ ProxyContext context = createContext();
+ try {
+ validateContext(context);
+ this.addExecutor(this.consumerThreadPoolExecutor,
+ context,
+ request,
+ () -> grpcMessingActivity.receiveMessage(context, request, responseObserver),
+ responseObserver,
+ statusResponseCreator);
+ } catch (Throwable t) {
+ writeResponse(context, request, null, responseObserver, t, statusResponseCreator);
+ }
+ }
+
+ @Override
+ public void ackMessage(AckMessageRequest request, StreamObserver responseObserver) {
+ Function statusResponseCreator = status -> AckMessageResponse.newBuilder().setStatus(status).build();
+ ProxyContext context = createContext();
+ try {
+ validateContext(context);
+ this.addExecutor(this.consumerThreadPoolExecutor,
+ context,
+ request,
+ () -> grpcMessingActivity.ackMessage(context, request)
+ .whenComplete((response, throwable) -> writeResponse(context, request, response, responseObserver, throwable, statusResponseCreator)),
+ responseObserver,
+ statusResponseCreator);
+ } catch (Throwable t) {
+ writeResponse(context, request, null, responseObserver, t, statusResponseCreator);
+ }
+ }
+
+ @Override
+ public void forwardMessageToDeadLetterQueue(ForwardMessageToDeadLetterQueueRequest request,
+ StreamObserver responseObserver) {
+ Function statusResponseCreator = status -> ForwardMessageToDeadLetterQueueResponse.newBuilder().setStatus(status).build();
+ ProxyContext context = createContext();
+ try {
+ validateContext(context);
+ this.addExecutor(this.producerThreadPoolExecutor,
+ context,
+ request,
+ () -> grpcMessingActivity.forwardMessageToDeadLetterQueue(context, request)
+ .whenComplete((response, throwable) -> writeResponse(context, request, response, responseObserver, throwable, statusResponseCreator)),
+ responseObserver,
+ statusResponseCreator);
+ } catch (Throwable t) {
+ writeResponse(context, request, null, responseObserver, t, statusResponseCreator);
+ }
+ }
+
+ @Override
+ public void endTransaction(EndTransactionRequest request, StreamObserver responseObserver) {
+ Function statusResponseCreator = status -> EndTransactionResponse.newBuilder().setStatus(status).build();
+ ProxyContext context = createContext();
+ try {
+ validateContext(context);
+ this.addExecutor(this.transactionThreadPoolExecutor,
+ context,
+ request,
+ () -> grpcMessingActivity.endTransaction(context, request)
+ .whenComplete((response, throwable) -> writeResponse(context, request, response, responseObserver, throwable, statusResponseCreator)),
+ responseObserver,
+ statusResponseCreator);
+ } catch (Throwable t) {
+ writeResponse(context, request, null, responseObserver, t, statusResponseCreator);
+ }
+ }
+
+ @Override
+ public void notifyClientTermination(NotifyClientTerminationRequest request,
+ StreamObserver responseObserver) {
+ Function statusResponseCreator = status -> NotifyClientTerminationResponse.newBuilder().setStatus(status).build();
+ ProxyContext context = createContext();
+ try {
+ validateContext(context);
+ this.addExecutor(this.clientManagerThreadPoolExecutor,
+ context,
+ request,
+ () -> grpcMessingActivity.notifyClientTermination(context, request)
+ .whenComplete((response, throwable) -> writeResponse(context, request, response, responseObserver, throwable, statusResponseCreator)),
+ responseObserver,
+ statusResponseCreator);
+ } catch (Throwable t) {
+ writeResponse(context, request, null, responseObserver, t, statusResponseCreator);
+ }
+ }
+
+ @Override
+ public void changeInvisibleDuration(ChangeInvisibleDurationRequest request,
+ StreamObserver responseObserver) {
+ Function statusResponseCreator = status -> ChangeInvisibleDurationResponse.newBuilder().setStatus(status).build();
+ ProxyContext context = createContext();
+ try {
+ validateContext(context);
+ this.addExecutor(this.consumerThreadPoolExecutor,
+ context,
+ request,
+ () -> grpcMessingActivity.changeInvisibleDuration(context, request)
+ .whenComplete((response, throwable) -> writeResponse(context, request, response, responseObserver, throwable, statusResponseCreator)),
+ responseObserver,
+ statusResponseCreator);
+ } catch (Throwable t) {
+ writeResponse(context, request, null, responseObserver, t, statusResponseCreator);
+ }
+ }
+
+ @Override
+ public StreamObserver telemetry(StreamObserver responseObserver) {
+ Function statusResponseCreator = status -> TelemetryCommand.newBuilder().setStatus(status).build();
+ ProxyContext context = createContext();
+ StreamObserver responseTelemetryCommand = grpcMessingActivity.telemetry(context, responseObserver);
+ return new StreamObserver() {
+ @Override
+ public void onNext(TelemetryCommand value) {
+ try {
+ validateContext(context);
+ addExecutor(clientManagerThreadPoolExecutor,
+ context,
+ value,
+ () -> responseTelemetryCommand.onNext(value),
+ responseObserver,
+ statusResponseCreator);
+ } catch (Throwable t) {
+ writeResponse(context, value, null, responseObserver, t, statusResponseCreator);
+ }
+ }
+
+ @Override
+ public void onError(Throwable t) {
+ responseTelemetryCommand.onError(t);
+ }
+
+ @Override
+ public void onCompleted() {
+ responseTelemetryCommand.onCompleted();
+ }
+ };
+ }
+
+ @Override
+ public void shutdown() throws Exception {
+ this.grpcMessingActivity.shutdown();
+
+ this.routeThreadPoolExecutor.shutdown();
+ this.routeThreadPoolExecutor.shutdown();
+ this.producerThreadPoolExecutor.shutdown();
+ this.consumerThreadPoolExecutor.shutdown();
+ this.clientManagerThreadPoolExecutor.shutdown();
+ this.transactionThreadPoolExecutor.shutdown();
+ }
+
+ @Override
+ public void start() throws Exception {
+ this.grpcMessingActivity.start();
+ }
+
+ protected static class GrpcTask implements Runnable {
+
+ protected final Runnable runnable;
+ protected final ProxyContext context;
+ protected final V request;
+ protected final T executeRejectResponse;
+ protected final StreamObserver streamObserver;
+
+ public GrpcTask(Runnable runnable, ProxyContext context, V request, StreamObserver streamObserver,
+ T executeRejectResponse) {
+ this.runnable = runnable;
+ this.context = context;
+ this.streamObserver = streamObserver;
+ this.request = request;
+ this.executeRejectResponse = executeRejectResponse;
+ }
+
+ @Override
+ public void run() {
+ this.runnable.run();
+ }
+ }
+
+ protected class GrpcTaskRejectedExecutionHandler implements RejectedExecutionHandler {
+
+ public GrpcTaskRejectedExecutionHandler() {
+
+ }
+
+ @Override
+ public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
+ if (r instanceof GrpcTask) {
+ try {
+ GrpcTask grpcTask = (GrpcTask) r;
+ writeResponse(grpcTask.context, grpcTask.request, grpcTask.executeRejectResponse, grpcTask.streamObserver, null, null);
+ } catch (Throwable t) {
+ log.warn("write rejected error response failed", t);
+ }
+ }
+ }
+ }
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/GrpcMessingActivity.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/GrpcMessingActivity.java
new file mode 100644
index 0000000000..0f353e94db
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/GrpcMessingActivity.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.proxy.grpc.v2;
+
+import apache.rocketmq.v2.AckMessageRequest;
+import apache.rocketmq.v2.AckMessageResponse;
+import apache.rocketmq.v2.ChangeInvisibleDurationRequest;
+import apache.rocketmq.v2.ChangeInvisibleDurationResponse;
+import apache.rocketmq.v2.EndTransactionRequest;
+import apache.rocketmq.v2.EndTransactionResponse;
+import apache.rocketmq.v2.ForwardMessageToDeadLetterQueueRequest;
+import apache.rocketmq.v2.ForwardMessageToDeadLetterQueueResponse;
+import apache.rocketmq.v2.HeartbeatRequest;
+import apache.rocketmq.v2.HeartbeatResponse;
+import apache.rocketmq.v2.NotifyClientTerminationRequest;
+import apache.rocketmq.v2.NotifyClientTerminationResponse;
+import apache.rocketmq.v2.QueryAssignmentRequest;
+import apache.rocketmq.v2.QueryAssignmentResponse;
+import apache.rocketmq.v2.QueryRouteRequest;
+import apache.rocketmq.v2.QueryRouteResponse;
+import apache.rocketmq.v2.ReceiveMessageRequest;
+import apache.rocketmq.v2.ReceiveMessageResponse;
+import apache.rocketmq.v2.SendMessageRequest;
+import apache.rocketmq.v2.SendMessageResponse;
+import apache.rocketmq.v2.TelemetryCommand;
+import io.grpc.stub.StreamObserver;
+import java.util.concurrent.CompletableFuture;
+import org.apache.rocketmq.proxy.common.ProxyContext;
+import org.apache.rocketmq.proxy.common.StartAndShutdown;
+
+public interface GrpcMessingActivity extends StartAndShutdown {
+
+ CompletableFuture queryRoute(ProxyContext ctx, QueryRouteRequest request);
+
+ CompletableFuture heartbeat(ProxyContext ctx, HeartbeatRequest request);
+
+ CompletableFuture sendMessage(ProxyContext ctx, SendMessageRequest request);
+
+ CompletableFuture queryAssignment(ProxyContext ctx, QueryAssignmentRequest request);
+
+ void receiveMessage(ProxyContext ctx, ReceiveMessageRequest request,
+ StreamObserver responseObserver);
+
+ CompletableFuture ackMessage(ProxyContext ctx, AckMessageRequest request);
+
+ CompletableFuture forwardMessageToDeadLetterQueue(ProxyContext ctx,
+ ForwardMessageToDeadLetterQueueRequest request);
+
+ CompletableFuture endTransaction(ProxyContext ctx, EndTransactionRequest request);
+
+ CompletableFuture notifyClientTermination(ProxyContext ctx,
+ NotifyClientTerminationRequest request);
+
+ CompletableFuture changeInvisibleDuration(ProxyContext ctx,
+ ChangeInvisibleDurationRequest request);
+
+ StreamObserver telemetry(ProxyContext ctx, StreamObserver responseObserver);
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/channel/GrpcChannelManager.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/channel/GrpcChannelManager.java
new file mode 100644
index 0000000000..57a7b1104b
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/channel/GrpcChannelManager.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.proxy.grpc.v2.channel;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+import org.apache.rocketmq.common.ThreadFactoryImpl;
+import org.apache.rocketmq.common.protocol.ResponseCode;
+import org.apache.rocketmq.proxy.common.ProxyContext;
+import org.apache.rocketmq.proxy.common.StartAndShutdown;
+import org.apache.rocketmq.proxy.config.ConfigurationManager;
+import org.apache.rocketmq.proxy.config.ProxyConfig;
+import org.apache.rocketmq.proxy.service.relay.ProxyRelayResult;
+import org.apache.rocketmq.proxy.service.relay.ProxyRelayService;
+
+public class GrpcChannelManager implements StartAndShutdown {
+ private final ProxyRelayService proxyRelayService;
+ protected final ConcurrentMap/* clientId */> groupClientIdChannelMap = new ConcurrentHashMap<>();
+
+ protected final AtomicLong nonceIdGenerator = new AtomicLong(0);
+ protected final ConcurrentMap resultNonceFutureMap = new ConcurrentHashMap<>();
+
+ protected final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(
+ new ThreadFactoryImpl("GrpcChannelManager_")
+ );
+
+ public GrpcChannelManager(ProxyRelayService proxyRelayService) {
+ this.proxyRelayService = proxyRelayService;
+ }
+
+ protected void init() {
+ this.scheduledExecutorService.scheduleAtFixedRate(
+ this::scanExpireResultFuture,
+ 10, 10, TimeUnit.SECONDS
+ );
+ }
+
+ public GrpcClientChannel createChannel(ProxyContext ctx, String group, String clientId) {
+ this.groupClientIdChannelMap.compute(group, (groupKey, clientIdMap) -> {
+ if (clientIdMap == null) {
+ clientIdMap = new ConcurrentHashMap<>();
+ }
+ clientIdMap.computeIfAbsent(clientId, clientIdKey -> new GrpcClientChannel(proxyRelayService, this, ctx, group, clientId));
+ return clientIdMap;
+ });
+ return getChannel(group, clientId);
+ }
+
+ public GrpcClientChannel getChannel(String group, String clientId) {
+ Map clientIdChannelMap = this.groupClientIdChannelMap.get(group);
+ if (clientIdChannelMap == null) {
+ return null;
+ }
+ return clientIdChannelMap.get(clientId);
+ }
+
+ public GrpcClientChannel removeChannel(String group, String clientId) {
+ AtomicReference channelRef = new AtomicReference<>();
+ this.groupClientIdChannelMap.computeIfPresent(group, (groupKey, clientIdMap) -> {
+ channelRef.set(clientIdMap.remove(clientId));
+ if (clientIdMap.isEmpty()) {
+ return null;
+ }
+ return clientIdMap;
+ });
+ return channelRef.get();
+ }
+
+ public String addResponseFuture(CompletableFuture> responseFuture) {
+ String nonce = this.nextNonce();
+ this.resultNonceFutureMap.put(nonce, new ResultFuture<>(responseFuture));
+ return nonce;
+ }
+
+ public CompletableFuture> getAndRemoveResponseFuture(String nonce) {
+ ResultFuture resultFuture = this.resultNonceFutureMap.remove(nonce);
+ if (resultFuture != null) {
+ return resultFuture.future;
+ }
+ return null;
+ }
+
+ protected String nextNonce() {
+ return String.valueOf(this.nonceIdGenerator.getAndIncrement());
+ }
+
+ protected void scanExpireResultFuture() {
+ ProxyConfig proxyConfig = ConfigurationManager.getProxyConfig();
+ long timeOutMs = TimeUnit.SECONDS.toMillis(proxyConfig.getGrpcProxyRelayRequestTimeoutInSeconds());
+
+ Set nonceSet = this.resultNonceFutureMap.keySet();
+ for (String nonce : nonceSet) {
+ ResultFuture> resultFuture = this.resultNonceFutureMap.get(nonce);
+ if (resultFuture == null) {
+ continue;
+ }
+ if (System.currentTimeMillis() - resultFuture.createTime > timeOutMs) {
+ resultFuture = this.resultNonceFutureMap.remove(nonce);
+ if (resultFuture != null) {
+ resultFuture.future.complete(new ProxyRelayResult<>(ResponseCode.SYSTEM_BUSY, "call remote timeout", null));
+ }
+ }
+ }
+ }
+
+ @Override
+ public void shutdown() throws Exception {
+ this.scheduledExecutorService.shutdown();
+ }
+
+ @Override
+ public void start() throws Exception {
+
+ }
+
+ protected static class ResultFuture {
+ public CompletableFuture> future;
+ public long createTime = System.currentTimeMillis();
+
+ public ResultFuture(CompletableFuture> future) {
+ this.future = future;
+ }
+ }
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/channel/GrpcClientChannel.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/channel/GrpcClientChannel.java
new file mode 100644
index 0000000000..d0ef56159a
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/channel/GrpcClientChannel.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.proxy.grpc.v2.channel;
+
+import apache.rocketmq.v2.PrintThreadStackTraceCommand;
+import apache.rocketmq.v2.RecoverOrphanedTransactionCommand;
+import apache.rocketmq.v2.TelemetryCommand;
+import apache.rocketmq.v2.VerifyMessageCommand;
+import com.google.common.collect.ComparisonChain;
+import io.grpc.stub.StreamObserver;
+import io.netty.channel.ChannelId;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicReference;
+import org.apache.rocketmq.common.message.MessageExt;
+import org.apache.rocketmq.common.protocol.body.ConsumeMessageDirectlyResult;
+import org.apache.rocketmq.common.protocol.body.ConsumerRunningInfo;
+import org.apache.rocketmq.common.protocol.header.CheckTransactionStateRequestHeader;
+import org.apache.rocketmq.common.protocol.header.ConsumeMessageDirectlyResultRequestHeader;
+import org.apache.rocketmq.common.protocol.header.GetConsumerRunningInfoRequestHeader;
+import org.apache.rocketmq.proxy.common.ProxyContext;
+import org.apache.rocketmq.proxy.grpc.v2.common.GrpcConverter;
+import org.apache.rocketmq.proxy.service.relay.ProxyChannel;
+import org.apache.rocketmq.proxy.service.relay.ProxyRelayResult;
+import org.apache.rocketmq.proxy.service.relay.ProxyRelayService;
+import org.apache.rocketmq.proxy.service.transaction.TransactionData;
+import org.apache.rocketmq.remoting.protocol.RemotingCommand;
+
+public class GrpcClientChannel extends ProxyChannel {
+
+ protected static final String SEPARATOR = "@";
+
+ private final GrpcChannelManager grpcChannelManager;
+
+ private final AtomicReference> telemetryCommandRef = new AtomicReference<>();
+ private final String group;
+ private final String clientId;
+
+ public GrpcClientChannel(ProxyRelayService proxyRelayService, GrpcChannelManager grpcChannelManager,
+ ProxyContext ctx,
+ String group, String clientId) {
+ super(proxyRelayService, null, new GrpcChannelId(group, clientId),
+ ctx.getRemoteAddress(),
+ ctx.getLocalAddress());
+ this.grpcChannelManager = grpcChannelManager;
+ this.group = group;
+ this.clientId = clientId;
+ }
+
+ protected static class GrpcChannelId implements ChannelId {
+
+ private final String group;
+ private final String clientId;
+
+ public GrpcChannelId(String group, String clientId) {
+ this.group = group;
+ this.clientId = clientId;
+ }
+
+ @Override
+ public String asShortText() {
+ return this.clientId;
+ }
+
+ @Override
+ public String asLongText() {
+ return this.group + SEPARATOR + this.clientId;
+ }
+
+ @Override
+ public int compareTo(ChannelId o) {
+ if (this == o) {
+ return 0;
+ }
+ if (o instanceof GrpcChannelId) {
+ GrpcChannelId other = (GrpcChannelId) o;
+ return ComparisonChain.start()
+ .compare(this.group, other.group)
+ .compare(this.clientId, other.clientId)
+ .result();
+ }
+
+ return asLongText().compareTo(o.asLongText());
+ }
+ }
+
+ public void setClientObserver(StreamObserver future) {
+ this.telemetryCommandRef.set(future);
+ }
+
+ @Override
+ public boolean isOpen() {
+ return this.telemetryCommandRef.get() != null;
+ }
+
+ @Override
+ public boolean isActive() {
+ return this.telemetryCommandRef.get() != null;
+ }
+
+ @Override
+ public boolean isWritable() {
+ return this.telemetryCommandRef.get() != null;
+ }
+
+ @Override
+ protected CompletableFuture processOtherMessage(Object msg) {
+ if (msg instanceof TelemetryCommand) {
+ TelemetryCommand response = (TelemetryCommand) msg;
+ this.getTelemetryCommandStreamObserver().onNext(response);
+ }
+ return CompletableFuture.completedFuture(null);
+ }
+
+ @Override
+ protected CompletableFuture processCheckTransaction(CheckTransactionStateRequestHeader header,
+ MessageExt messageExt, TransactionData transactionData, CompletableFuture> responseFuture) {
+ CompletableFuture writeFuture = new CompletableFuture<>();
+ try {
+ this.getTelemetryCommandStreamObserver().onNext(TelemetryCommand.newBuilder()
+ .setRecoverOrphanedTransactionCommand(RecoverOrphanedTransactionCommand.newBuilder()
+ .setTransactionId(transactionData.getTransactionId())
+ .setMessage(GrpcConverter.getInstance().buildMessage(messageExt))
+ .build())
+ .build());
+ responseFuture.complete(null);
+ writeFuture.complete(null);
+ } catch (Throwable t) {
+ responseFuture.completeExceptionally(t);
+ writeFuture.completeExceptionally(t);
+ }
+ return writeFuture;
+ }
+
+ @Override
+ protected CompletableFuture processGetConsumerRunningInfo(RemotingCommand command,
+ GetConsumerRunningInfoRequestHeader header,
+ CompletableFuture> responseFuture) {
+ if (!header.isJstackEnable()) {
+ return CompletableFuture.completedFuture(null);
+ }
+ this.getTelemetryCommandStreamObserver().onNext(TelemetryCommand.newBuilder()
+ .setPrintThreadStackTraceCommand(PrintThreadStackTraceCommand.newBuilder()
+ .setNonce(this.grpcChannelManager.addResponseFuture(responseFuture))
+ .build())
+ .build());
+ return CompletableFuture.completedFuture(null);
+ }
+
+ @Override
+ protected CompletableFuture processConsumeMessageDirectly(RemotingCommand command,
+ ConsumeMessageDirectlyResultRequestHeader header,
+ MessageExt messageExt, CompletableFuture> responseFuture) {
+ this.getTelemetryCommandStreamObserver().onNext(TelemetryCommand.newBuilder()
+ .setVerifyMessageCommand(VerifyMessageCommand.newBuilder()
+ .setNonce(this.grpcChannelManager.addResponseFuture(responseFuture))
+ .setMessage(GrpcConverter.getInstance().buildMessage(messageExt))
+ .build())
+ .build());
+ return CompletableFuture.completedFuture(null);
+ }
+
+ public String getGroup() {
+ return group;
+ }
+
+ public String getClientId() {
+ return clientId;
+ }
+
+ public String getRemoteAddress() {
+ return remoteAddress;
+ }
+
+ public String getLocalAddress() {
+ return localAddress;
+ }
+
+ public StreamObserver getTelemetryCommandStreamObserver() {
+ return this.telemetryCommandRef.get();
+ }
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/client/ClientActivity.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/client/ClientActivity.java
new file mode 100644
index 0000000000..1f58e70661
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/client/ClientActivity.java
@@ -0,0 +1,404 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.proxy.grpc.v2.client;
+
+import apache.rocketmq.v2.ClientType;
+import apache.rocketmq.v2.Code;
+import apache.rocketmq.v2.FilterExpression;
+import apache.rocketmq.v2.HeartbeatRequest;
+import apache.rocketmq.v2.HeartbeatResponse;
+import apache.rocketmq.v2.NotifyClientTerminationRequest;
+import apache.rocketmq.v2.NotifyClientTerminationResponse;
+import apache.rocketmq.v2.Resource;
+import apache.rocketmq.v2.Settings;
+import apache.rocketmq.v2.Status;
+import apache.rocketmq.v2.SubscriptionEntry;
+import apache.rocketmq.v2.TelemetryCommand;
+import apache.rocketmq.v2.ThreadStackTrace;
+import apache.rocketmq.v2.VerifyMessageResult;
+import io.grpc.stub.StreamObserver;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.rocketmq.broker.client.ClientChannelInfo;
+import org.apache.rocketmq.broker.client.ConsumerGroupEvent;
+import org.apache.rocketmq.broker.client.ConsumerIdsChangeListener;
+import org.apache.rocketmq.broker.client.ProducerChangeListener;
+import org.apache.rocketmq.broker.client.ProducerGroupEvent;
+import org.apache.rocketmq.common.MQVersion;
+import org.apache.rocketmq.common.constant.LoggerName;
+import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
+import org.apache.rocketmq.common.filter.FilterAPI;
+import org.apache.rocketmq.common.protocol.ResponseCode;
+import org.apache.rocketmq.common.protocol.body.CMResult;
+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.MessageModel;
+import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData;
+import org.apache.rocketmq.logging.InternalLogger;
+import org.apache.rocketmq.logging.InternalLoggerFactory;
+import org.apache.rocketmq.proxy.common.ProxyContext;
+import org.apache.rocketmq.proxy.grpc.v2.AbstractMessingActivity;
+import org.apache.rocketmq.proxy.grpc.v2.channel.GrpcChannelManager;
+import org.apache.rocketmq.proxy.grpc.v2.channel.GrpcClientChannel;
+import org.apache.rocketmq.proxy.grpc.v2.common.GrpcClientSettingsManager;
+import org.apache.rocketmq.proxy.grpc.v2.common.GrpcConverter;
+import org.apache.rocketmq.proxy.grpc.v2.common.GrpcProxyException;
+import org.apache.rocketmq.proxy.grpc.v2.common.ResponseBuilder;
+import org.apache.rocketmq.proxy.processor.MessagingProcessor;
+import org.apache.rocketmq.proxy.service.relay.ProxyRelayResult;
+import org.apache.rocketmq.remoting.protocol.LanguageCode;
+
+public class ClientActivity extends AbstractMessingActivity {
+
+ private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME);
+
+ public ClientActivity(MessagingProcessor messagingProcessor,
+ GrpcClientSettingsManager grpcClientSettingsManager,
+ GrpcChannelManager grpcChannelManager) {
+ super(messagingProcessor, grpcClientSettingsManager, grpcChannelManager);
+ this.init();
+ }
+
+ protected void init() {
+ this.messagingProcessor.registerConsumerListener(new ConsumerIdsChangeListenerImpl());
+ this.messagingProcessor.registerProducerListener(new ProducerChangeListenerImpl());
+ }
+
+ public CompletableFuture heartbeat(ProxyContext ctx, HeartbeatRequest request) {
+ CompletableFuture future = new CompletableFuture<>();
+
+ try {
+ Settings clientSettings = grpcClientSettingsManager.getClientSettings(ctx);
+ if (clientSettings == null) {
+ future.complete(HeartbeatResponse.newBuilder()
+ .setStatus(ResponseBuilder.getInstance().buildStatus(Code.UNRECOGNIZED_CLIENT_TYPE, "cannot find client settings for this client"))
+ .build());
+ return future;
+ }
+ switch (clientSettings.getClientType()) {
+ case PRODUCER: {
+ for (Resource topic : clientSettings.getPublishing().getTopicsList()) {
+ String topicName = GrpcConverter.getInstance().wrapResourceWithNamespace(topic);
+ this.registerProducer(ctx, topicName);
+ }
+ break;
+ }
+ case PUSH_CONSUMER:
+ case SIMPLE_CONSUMER: {
+ validateConsumerGroup(request.getGroup());
+ String consumerGroup = GrpcConverter.getInstance().wrapResourceWithNamespace(request.getGroup());
+ this.registerConsumer(ctx, consumerGroup, clientSettings.getClientType(), clientSettings.getSubscription().getSubscriptionsList(), false);
+ break;
+ }
+ default: {
+ future.complete(HeartbeatResponse.newBuilder()
+ .setStatus(ResponseBuilder.getInstance().buildStatus(Code.UNRECOGNIZED_CLIENT_TYPE, clientSettings.getClientType().name()))
+ .build());
+ return future;
+ }
+ }
+ future.complete(HeartbeatResponse.newBuilder()
+ .setStatus(ResponseBuilder.getInstance().buildStatus(Code.OK, Code.OK.name()))
+ .build());
+ return future;
+ } catch (Throwable t) {
+ future.completeExceptionally(t);
+ }
+ return future;
+ }
+
+ public CompletableFuture notifyClientTermination(ProxyContext ctx,
+ NotifyClientTerminationRequest request) {
+ CompletableFuture future = new CompletableFuture<>();
+
+ try {
+ String clientId = ctx.getClientID();
+ LanguageCode languageCode = LanguageCode.valueOf(ctx.getLanguage());
+ Settings clientSettings = grpcClientSettingsManager.removeAndGetClientSettings(ctx);
+
+ switch (clientSettings.getClientType()) {
+ case PRODUCER:
+ for (Resource topic : clientSettings.getPublishing().getTopicsList()) {
+ String topicName = GrpcConverter.getInstance().wrapResourceWithNamespace(topic);
+ // user topic name as producer group
+ GrpcClientChannel channel = this.grpcChannelManager.removeChannel(topicName, clientId);
+ if (channel != null) {
+ ClientChannelInfo clientChannelInfo = new ClientChannelInfo(channel, clientId, languageCode, MQVersion.Version.V5_0_0.ordinal());
+ this.messagingProcessor.unRegisterProducer(ctx, topicName, clientChannelInfo);
+ }
+ }
+ break;
+ case PUSH_CONSUMER:
+ case SIMPLE_CONSUMER:
+ validateConsumerGroup(request.getGroup());
+ String consumerGroup = GrpcConverter.getInstance().wrapResourceWithNamespace(request.getGroup());
+ GrpcClientChannel channel = this.grpcChannelManager.removeChannel(consumerGroup, clientId);
+ if (channel != null) {
+ ClientChannelInfo clientChannelInfo = new ClientChannelInfo(channel, clientId, languageCode, MQVersion.Version.V5_0_0.ordinal());
+ this.messagingProcessor.unRegisterConsumer(ctx, consumerGroup, clientChannelInfo);
+ }
+ break;
+ default:
+ future.complete(NotifyClientTerminationResponse.newBuilder()
+ .setStatus(ResponseBuilder.getInstance().buildStatus(Code.UNRECOGNIZED_CLIENT_TYPE, clientSettings.getClientType().name()))
+ .build());
+ return future;
+ }
+ future.complete(NotifyClientTerminationResponse.newBuilder()
+ .setStatus(ResponseBuilder.getInstance().buildStatus(Code.OK, Code.OK.name()))
+ .build());
+ } catch (Throwable t) {
+ future.completeExceptionally(t);
+ }
+ return future;
+ }
+
+ public StreamObserver telemetry(ProxyContext ctx,
+ StreamObserver responseObserver) {
+ return new StreamObserver() {
+ @Override
+ public void onNext(TelemetryCommand request) {
+ try {
+ switch (request.getCommandCase()) {
+ case SETTINGS: {
+ responseObserver.onNext(processClientSettings(ctx, request, responseObserver));
+ break;
+ }
+ case THREAD_STACK_TRACE: {
+ reportThreadStackTrace(ctx, request.getStatus(), request.getThreadStackTrace());
+ break;
+ }
+ case VERIFY_MESSAGE_RESULT: {
+ reportVerifyMessageResult(ctx, request.getStatus(), request.getVerifyMessageResult());
+ break;
+ }
+ }
+ } catch (Throwable t) {
+ responseObserver.onNext(convertToTelemetryCommand(t));
+ }
+ }
+
+ @Override
+ public void onError(Throwable t) {
+ log.error("telemetry on error", t);
+ }
+
+ @Override
+ public void onCompleted() {
+ responseObserver.onCompleted();
+ }
+ };
+ }
+
+ protected TelemetryCommand convertToTelemetryCommand(Throwable t) {
+ return TelemetryCommand.newBuilder().setStatus(ResponseBuilder.getInstance().buildStatus(t)).build();
+ }
+
+ protected TelemetryCommand processClientSettings(ProxyContext ctx, TelemetryCommand request,
+ StreamObserver responseObserver) {
+ String clientId = ctx.getClientID();
+ Settings settings = request.getSettings();
+ if (settings.hasPublishing()) {
+ for (Resource topic : settings.getPublishing().getTopicsList()) {
+ validateTopic(topic);
+ String topicName = GrpcConverter.getInstance().wrapResourceWithNamespace(topic);
+ GrpcClientChannel producerChannel = registerProducer(ctx, topicName);
+ producerChannel.setClientObserver(responseObserver);
+ }
+ }
+ if (settings.hasSubscription()) {
+ validateConsumerGroup(settings.getSubscription().getGroup());
+ String groupName = GrpcConverter.getInstance().wrapResourceWithNamespace(settings.getSubscription().getGroup());
+ GrpcClientChannel consumerChannel = registerConsumer(ctx, groupName, settings.getClientType(), settings.getSubscription().getSubscriptionsList(), true);
+ consumerChannel.setClientObserver(responseObserver);
+ }
+
+ grpcClientSettingsManager.updateClientSettings(clientId, request.getSettings());
+ settings = grpcClientSettingsManager.getClientSettings(ctx);
+ return TelemetryCommand.newBuilder()
+ .setStatus(ResponseBuilder.getInstance().buildStatus(Code.OK, Code.OK.name()))
+ .setSettings(settings)
+ .build();
+ }
+
+ protected GrpcClientChannel registerProducer(ProxyContext ctx, String topicName) {
+ String clientId = ctx.getClientID();
+ LanguageCode languageCode = LanguageCode.valueOf(ctx.getLanguage());
+
+ GrpcClientChannel channel = this.grpcChannelManager.createChannel(ctx, topicName, clientId);
+ // use topic name as producer group
+ ClientChannelInfo clientChannelInfo = new ClientChannelInfo(channel, clientId, languageCode, parseClientVersion(ctx.getClientVersion()));
+ this.messagingProcessor.registerProducer(ctx, topicName, clientChannelInfo);
+ this.messagingProcessor.addTransactionSubscription(ctx, topicName, topicName);
+ return channel;
+ }
+
+ protected GrpcClientChannel registerConsumer(ProxyContext ctx, String consumerGroup, ClientType clientType, List subscriptionEntryList, boolean updateSubscription) {
+ String clientId = ctx.getClientID();
+ LanguageCode languageCode = LanguageCode.valueOf(ctx.getLanguage());
+
+ GrpcClientChannel channel = this.grpcChannelManager.createChannel(ctx, consumerGroup, clientId);
+ ClientChannelInfo clientChannelInfo = new ClientChannelInfo(channel, clientId, languageCode, parseClientVersion(ctx.getClientVersion()));
+
+ this.messagingProcessor.registerConsumer(
+ ctx,
+ consumerGroup,
+ clientChannelInfo,
+ this.buildConsumeType(clientType),
+ MessageModel.CLUSTERING,
+ ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET,
+ this.buildSubscriptionDataSet(subscriptionEntryList),
+ updateSubscription
+ );
+ return channel;
+ }
+
+ private int parseClientVersion(String clientVersionStr) {
+ int clientVersion = MQVersion.CURRENT_VERSION;
+ if (!StringUtils.isEmpty(clientVersionStr)) {
+ try {
+ String tmp = StringUtils.upperCase(clientVersionStr);
+ clientVersion = MQVersion.Version.valueOf(tmp).ordinal();
+ } catch (Exception ignored) {
+ }
+ }
+ return clientVersion;
+ }
+
+ protected void reportThreadStackTrace(ProxyContext ctx, Status status, ThreadStackTrace request) {
+ String nonce = request.getNonce();
+ String threadStack = request.getThreadStackTrace();
+ CompletableFuture> responseFuture = this.grpcChannelManager.getAndRemoveResponseFuture(nonce);
+ if (responseFuture != null) {
+ try {
+ if (status.getCode().equals(Code.OK)) {
+ ConsumerRunningInfo runningInfo = new ConsumerRunningInfo();
+ runningInfo.setJstack(threadStack);
+ responseFuture.complete(new ProxyRelayResult<>(ResponseCode.SUCCESS, "", runningInfo));
+ } else if (status.getCode().equals(Code.VERIFY_FIFO_MESSAGE_UNSUPPORTED)) {
+ responseFuture.complete(new ProxyRelayResult<>(ResponseCode.NO_PERMISSION, "forbidden to verify message", null));
+ } else {
+ responseFuture.complete(new ProxyRelayResult<>(ResponseCode.SYSTEM_ERROR, "verify message failed", null));
+ }
+ } catch (Throwable t) {
+ responseFuture.completeExceptionally(t);
+ }
+ }
+ }
+
+ protected void reportVerifyMessageResult(ProxyContext ctx, Status status, VerifyMessageResult request) {
+ String nonce = request.getNonce();
+ CompletableFuture> responseFuture = this.grpcChannelManager.getAndRemoveResponseFuture(nonce);
+ if (responseFuture != null) {
+ try {
+ ConsumeMessageDirectlyResult result = this.buildConsumeMessageDirectlyResult(status, request);
+ responseFuture.complete(new ProxyRelayResult<>(ResponseCode.SUCCESS, "", result));
+ } catch (Throwable t) {
+ responseFuture.completeExceptionally(t);
+ }
+ }
+ }
+
+ protected ConsumeMessageDirectlyResult buildConsumeMessageDirectlyResult(Status status,
+ VerifyMessageResult request) {
+ ConsumeMessageDirectlyResult consumeMessageDirectlyResult = new ConsumeMessageDirectlyResult();
+ switch (status.getCode().getNumber()) {
+ case Code.OK_VALUE: {
+ consumeMessageDirectlyResult.setConsumeResult(CMResult.CR_SUCCESS);
+ break;
+ }
+ case Code.FAILED_TO_CONSUME_MESSAGE_VALUE: {
+ consumeMessageDirectlyResult.setConsumeResult(CMResult.CR_LATER);
+ break;
+ }
+ case Code.MESSAGE_CORRUPTED_VALUE: {
+ consumeMessageDirectlyResult.setConsumeResult(CMResult.CR_RETURN_NULL);
+ break;
+ }
+ }
+ consumeMessageDirectlyResult.setRemark("from gRPC client");
+ return consumeMessageDirectlyResult;
+ }
+
+ protected ConsumeType buildConsumeType(ClientType clientType) {
+ switch (clientType) {
+ case SIMPLE_CONSUMER:
+ return ConsumeType.CONSUME_ACTIVELY;
+ case PUSH_CONSUMER:
+ return ConsumeType.CONSUME_PASSIVELY;
+ default:
+ throw new IllegalArgumentException("Client type is not consumer, type: " + clientType);
+ }
+ }
+
+ protected Set buildSubscriptionDataSet(List subscriptionEntryList) {
+ Set subscriptionDataSet = new HashSet<>();
+ for (SubscriptionEntry sub : subscriptionEntryList) {
+ String topicName = GrpcConverter.getInstance().wrapResourceWithNamespace(sub.getTopic());
+ FilterExpression filterExpression = sub.getExpression();
+ subscriptionDataSet.add(buildSubscriptionData(topicName, filterExpression));
+ }
+ return subscriptionDataSet;
+ }
+
+ protected SubscriptionData buildSubscriptionData(String topicName, FilterExpression filterExpression) {
+ String expression = filterExpression.getExpression();
+ String expressionType = GrpcConverter.getInstance().buildExpressionType(filterExpression.getType());
+ try {
+ return FilterAPI.build(topicName, expression, expressionType);
+ } catch (Exception e) {
+ throw new GrpcProxyException(Code.ILLEGAL_FILTER_EXPRESSION, "expression format is not correct", e);
+ }
+ }
+
+ protected class ConsumerIdsChangeListenerImpl implements ConsumerIdsChangeListener {
+
+ @Override
+ public void handle(ConsumerGroupEvent event, String group, Object... args) {
+ if (event == ConsumerGroupEvent.CLIENT_UNREGISTER) {
+ if (args == null || args.length < 1) {
+ return;
+ }
+ if (args[0] instanceof ClientChannelInfo) {
+ ClientChannelInfo clientChannelInfo = (ClientChannelInfo) args[0];
+ grpcChannelManager.removeChannel(group, clientChannelInfo.getClientId());
+ grpcClientSettingsManager.removeClientSettings(clientChannelInfo.getClientId());
+ }
+ }
+ }
+
+ @Override
+ public void shutdown() {
+
+ }
+ }
+
+ protected class ProducerChangeListenerImpl implements ProducerChangeListener {
+
+ @Override
+ public void handle(ProducerGroupEvent event, String group, ClientChannelInfo clientChannelInfo) {
+ if (event == ProducerGroupEvent.CLIENT_UNREGISTER) {
+ grpcChannelManager.removeChannel(group, clientChannelInfo.getClientId());
+ grpcClientSettingsManager.removeClientSettings(clientChannelInfo.getClientId());
+ }
+ }
+ }
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcClientSettingsManager.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcClientSettingsManager.java
new file mode 100644
index 0000000000..548bd5efd2
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcClientSettingsManager.java
@@ -0,0 +1,204 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.proxy.grpc.v2.common;
+
+import apache.rocketmq.v2.Address;
+import apache.rocketmq.v2.AddressScheme;
+import apache.rocketmq.v2.CustomizedBackoff;
+import apache.rocketmq.v2.Endpoints;
+import apache.rocketmq.v2.ExponentialBackoff;
+import apache.rocketmq.v2.Metric;
+import apache.rocketmq.v2.Publishing;
+import apache.rocketmq.v2.RetryPolicy;
+import apache.rocketmq.v2.Settings;
+import apache.rocketmq.v2.Subscription;
+import com.google.protobuf.Duration;
+import com.google.protobuf.util.Durations;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+import org.apache.rocketmq.common.subscription.CustomizedRetryPolicy;
+import org.apache.rocketmq.common.subscription.ExponentialRetryPolicy;
+import org.apache.rocketmq.common.subscription.GroupRetryPolicy;
+import org.apache.rocketmq.common.subscription.GroupRetryPolicyType;
+import org.apache.rocketmq.common.subscription.SubscriptionGroupConfig;
+import org.apache.rocketmq.proxy.common.ProxyContext;
+import org.apache.rocketmq.proxy.config.ConfigurationManager;
+import org.apache.rocketmq.proxy.config.MetricCollectorMode;
+import org.apache.rocketmq.proxy.config.ProxyConfig;
+import org.apache.rocketmq.proxy.processor.MessagingProcessor;
+
+public class GrpcClientSettingsManager {
+
+ protected static final Map CLIENT_SETTINGS_MAP = new ConcurrentHashMap<>();
+
+ private final MessagingProcessor messagingProcessor;
+
+ public GrpcClientSettingsManager(MessagingProcessor messagingProcessor) {
+ this.messagingProcessor = messagingProcessor;
+ }
+
+ public Settings getClientSettings(ProxyContext ctx) {
+ String clientId = ctx.getClientID();
+ Settings settings = CLIENT_SETTINGS_MAP.get(clientId);
+ if (settings == null) {
+ return null;
+ }
+ if (settings.hasSubscription()) {
+ settings = mergeSubscriptionData(ctx, settings,
+ GrpcConverter.getInstance().wrapResourceWithNamespace(settings.getSubscription().getGroup()));
+ }
+ return mergeMetric(settings);
+ }
+
+ private Settings mergeSubscriptionData(ProxyContext ctx, Settings settings, String consumerGroup) {
+ SubscriptionGroupConfig config = this.messagingProcessor.getSubscriptionGroupConfig(ctx, consumerGroup);
+ if (config == null) {
+ return settings;
+ }
+
+ return mergeSubscriptionData(settings, config);
+ }
+
+ private Settings mergeMetric(Settings settings) {
+ // Construct metric according to the proxy config
+ final ProxyConfig proxyConfig = ConfigurationManager.getProxyConfig();
+ final MetricCollectorMode metricCollectorMode =
+ MetricCollectorMode.getEnumByOrdinal(proxyConfig.getMetricCollectorMode());
+ final String metricCollectorAddress = proxyConfig.getMetricCollectorAddress();
+ final Metric.Builder metricBuilder = Metric.newBuilder();
+ switch (metricCollectorMode) {
+ case ON:
+ final String[] split = metricCollectorAddress.split(":");
+ final String host = split[0];
+ final int port = Integer.parseInt(split[1]);
+ Address address = Address.newBuilder().setHost(host).setPort(port).build();
+ final Endpoints endpoints = Endpoints.newBuilder().setScheme(AddressScheme.IPv4)
+ .addAddresses(address).build();
+ metricBuilder.setOn(true).setEndpoints(endpoints);
+ break;
+ case PROXY:
+ metricBuilder.setOn(true).setEndpoints(settings.getAccessPoint());
+ break;
+ case OFF:
+ default:
+ metricBuilder.setOn(false);
+ break;
+ }
+ Metric metric = metricBuilder.build();
+ return settings.toBuilder().setMetric(metric).build();
+ }
+
+ protected static Settings mergeSubscriptionData(Settings settings, SubscriptionGroupConfig config) {
+ Settings.Builder resultSettingsBuilder = settings.toBuilder();
+
+ resultSettingsBuilder.getSubscriptionBuilder().setFifo(config.isConsumeMessageOrderly());
+
+ resultSettingsBuilder.getBackoffPolicyBuilder().setMaxAttempts(config.getRetryMaxTimes() + 1);
+
+ GroupRetryPolicy groupRetryPolicy = config.getGroupRetryPolicy();
+ if (groupRetryPolicy.getType().equals(GroupRetryPolicyType.EXPONENTIAL)) {
+ ExponentialRetryPolicy exponentialRetryPolicy = groupRetryPolicy.getExponentialRetryPolicy();
+ if (exponentialRetryPolicy == null) {
+ exponentialRetryPolicy = new ExponentialRetryPolicy();
+ }
+ resultSettingsBuilder.getBackoffPolicyBuilder().setExponentialBackoff(convertToExponentialBackoff(exponentialRetryPolicy));
+ } else {
+ CustomizedRetryPolicy customizedRetryPolicy = groupRetryPolicy.getCustomizedRetryPolicy();
+ if (customizedRetryPolicy == null) {
+ customizedRetryPolicy = new CustomizedRetryPolicy();
+ }
+ resultSettingsBuilder.getBackoffPolicyBuilder().setCustomizedBackoff(convertToCustomizedRetryPolicy(customizedRetryPolicy));
+ }
+
+ return resultSettingsBuilder.build();
+ }
+
+ protected static ExponentialBackoff convertToExponentialBackoff(ExponentialRetryPolicy retryPolicy) {
+ return ExponentialBackoff.newBuilder()
+ .setInitial(Durations.fromMillis(retryPolicy.getInitial()))
+ .setMax(Durations.fromMillis(retryPolicy.getMax()))
+ .setMultiplier(retryPolicy.getMultiplier())
+ .build();
+ }
+
+ protected static CustomizedBackoff convertToCustomizedRetryPolicy(CustomizedRetryPolicy retryPolicy) {
+ List durationList = Arrays.stream(retryPolicy.getNext())
+ .mapToObj(Durations::fromMillis).collect(Collectors.toList());
+ return CustomizedBackoff.newBuilder()
+ .addAllNext(durationList)
+ .build();
+ }
+
+ public void updateClientSettings(String clientId, Settings settings) {
+ if (settings.hasPublishing()) {
+ settings = createDefaultProducerSettingsBuilder().mergeFrom(settings).build();
+ } else if (settings.hasSubscription()) {
+ settings = createDefaultConsumerSettingsBuilder().mergeFrom(settings).build();
+ }
+ CLIENT_SETTINGS_MAP.put(clientId, settings);
+ }
+
+ protected Settings.Builder createDefaultProducerSettingsBuilder() {
+ ProxyConfig config = ConfigurationManager.getProxyConfig();
+ return Settings.newBuilder()
+ .setBackoffPolicy(RetryPolicy.newBuilder()
+ .setMaxAttempts(config.getGrpcClientProducerMaxAttempts())
+ .setExponentialBackoff(ExponentialBackoff.newBuilder()
+ .setInitial(Durations.fromMillis(config.getGrpcClientProducerBackoffInitialMillis()))
+ .setMax(Durations.fromMillis(config.getGrpcClientProducerBackoffMaxMillis()))
+ .setMultiplier(config.getGrpcClientProducerBackoffMultiplier())
+ .build())
+ .build())
+ .setPublishing(Publishing.newBuilder()
+ .setValidateMessageType(config.isEnableTopicMessageTypeCheck())
+ .setMaxBodySize(config.getMaxMessageSize())
+ .build());
+ }
+
+ protected Settings.Builder createDefaultConsumerSettingsBuilder() {
+ ProxyConfig config = ConfigurationManager.getProxyConfig();
+ return mergeSubscriptionData(Settings.newBuilder()
+ .setSubscription(Subscription.newBuilder()
+ .setReceiveBatchSize(config.getGrpcClientConsumerLongPollingBatchSize())
+ .setLongPollingTimeout(Durations.fromMillis(config.getGrpcClientConsumerLongPollingTimeoutMillis()))
+ .build())
+ .build(), new SubscriptionGroupConfig())
+ .toBuilder();
+ }
+
+ public void removeClientSettings(String clientId) {
+ CLIENT_SETTINGS_MAP.remove(clientId);
+ }
+
+ public Settings removeAndGetClientSettings(ProxyContext ctx) {
+ String clientId = ctx.getClientID();
+ Settings settings = CLIENT_SETTINGS_MAP.remove(clientId);
+ if (settings == null) {
+ return null;
+ }
+ settings = mergeSubscriptionData(ctx, settings,
+ GrpcConverter.getInstance().wrapResourceWithNamespace(settings.getSubscription().getGroup()));
+ if (settings == null) {
+ return null;
+ }
+ return mergeMetric(settings);
+ }
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcConverter.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcConverter.java
new file mode 100644
index 0000000000..cc5a60ca6d
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcConverter.java
@@ -0,0 +1,249 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.proxy.grpc.v2.common;
+
+import apache.rocketmq.v2.Broker;
+import apache.rocketmq.v2.Digest;
+import apache.rocketmq.v2.DigestType;
+import apache.rocketmq.v2.Encoding;
+import apache.rocketmq.v2.FilterType;
+import apache.rocketmq.v2.Message;
+import apache.rocketmq.v2.MessageQueue;
+import apache.rocketmq.v2.MessageType;
+import apache.rocketmq.v2.Resource;
+import apache.rocketmq.v2.SystemProperties;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.util.Timestamps;
+import java.net.SocketAddress;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.rocketmq.common.constant.LoggerName;
+import org.apache.rocketmq.common.filter.ExpressionType;
+import org.apache.rocketmq.common.message.MessageConst;
+import org.apache.rocketmq.common.message.MessageExt;
+import org.apache.rocketmq.common.protocol.NamespaceUtil;
+import org.apache.rocketmq.common.sysflag.MessageSysFlag;
+import org.apache.rocketmq.common.utils.BinaryUtil;
+import org.apache.rocketmq.logging.InternalLogger;
+import org.apache.rocketmq.logging.InternalLoggerFactory;
+import org.apache.rocketmq.remoting.common.RemotingUtil;
+
+public class GrpcConverter {
+ private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME);
+
+ protected static final Object INSTANCE_CREATE_LOCK = new Object();
+ protected static volatile GrpcConverter instance;
+
+ public static GrpcConverter getInstance() {
+ if (instance == null) {
+ synchronized (INSTANCE_CREATE_LOCK) {
+ if (instance == null) {
+ instance = new GrpcConverter();
+ }
+ }
+ }
+ return instance;
+ }
+
+ public String wrapResourceWithNamespace(Resource resource) {
+ return NamespaceUtil.wrapNamespace(resource.getResourceNamespace(), resource.getName());
+ }
+
+ public MessageQueue buildMessageQueue(MessageExt messageExt, String brokerName) {
+ Broker broker = Broker.getDefaultInstance();
+ if (!StringUtils.isEmpty(brokerName)) {
+ broker = Broker.newBuilder()
+ .setName(brokerName)
+ .setId(0)
+ .build();
+ }
+ return MessageQueue.newBuilder()
+ .setId(messageExt.getQueueId())
+ .setTopic(Resource.newBuilder()
+ .setName(NamespaceUtil.withoutNamespace(messageExt.getTopic()))
+ .setResourceNamespace(NamespaceUtil.getNamespaceFromResource(messageExt.getTopic()))
+ .build())
+ .setBroker(broker)
+ .build();
+ }
+
+ public String buildExpressionType(FilterType filterType) {
+ switch (filterType) {
+ case SQL:
+ return ExpressionType.SQL92;
+ case TAG:
+ default:
+ return ExpressionType.TAG;
+ }
+ }
+
+ public Message buildMessage(MessageExt messageExt) {
+ Map userProperties = buildUserAttributes(messageExt);
+ SystemProperties systemProperties = buildSystemProperties(messageExt);
+ Resource topic = buildResource(messageExt.getTopic());
+
+ return Message.newBuilder()
+ .setTopic(topic)
+ .putAllUserProperties(userProperties)
+ .setSystemProperties(systemProperties)
+ .setBody(ByteString.copyFrom(messageExt.getBody()))
+ .build();
+ }
+
+ protected Map buildUserAttributes(MessageExt messageExt) {
+ Map userAttributes = new HashMap<>();
+ Map properties = messageExt.getProperties();
+
+ for (Map.Entry property : properties.entrySet()) {
+ if (!MessageConst.STRING_HASH_SET.contains(property.getKey())) {
+ userAttributes.put(property.getKey(), property.getValue());
+ }
+ }
+
+ return userAttributes;
+ }
+
+ protected SystemProperties buildSystemProperties(MessageExt messageExt) {
+ SystemProperties.Builder systemPropertiesBuilder = SystemProperties.newBuilder();
+
+ // tag
+ String tag = messageExt.getUserProperty(MessageConst.PROPERTY_TAGS);
+ if (tag != null) {
+ systemPropertiesBuilder.setTag(tag);
+ }
+
+ // keys
+ String keys = messageExt.getKeys();
+ if (keys != null) {
+ String[] keysArray = keys.split(MessageConst.KEY_SEPARATOR);
+ systemPropertiesBuilder.addAllKeys(Arrays.asList(keysArray));
+ }
+
+ // message_id
+ String uniqKey = messageExt.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX);
+ if (uniqKey != null) {
+ systemPropertiesBuilder.setMessageId(uniqKey);
+ }
+
+ // body_digest & body_encoding
+ String md5Result = BinaryUtil.generateMd5(messageExt.getBody());
+ Digest digest = Digest.newBuilder()
+ .setType(DigestType.MD5)
+ .setChecksum(md5Result)
+ .build();
+ systemPropertiesBuilder.setBodyDigest(digest);
+
+ if ((messageExt.getSysFlag() & MessageSysFlag.COMPRESSED_FLAG) == MessageSysFlag.COMPRESSED_FLAG) {
+ systemPropertiesBuilder.setBodyEncoding(Encoding.GZIP);
+ } else {
+ systemPropertiesBuilder.setBodyEncoding(Encoding.IDENTITY);
+ }
+
+ // message_type
+ String isTrans = messageExt.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED);
+ String isTransValue = "true";
+ if (isTransValue.equals(isTrans)) {
+ systemPropertiesBuilder.setMessageType(MessageType.TRANSACTION);
+ } else if (messageExt.getProperty(MessageConst.PROPERTY_DELAY_TIME_LEVEL) != null
+ || messageExt.getProperty(MessageConst.PROPERTY_TIMER_DELIVER_MS) != null
+ || messageExt.getProperty(MessageConst.PROPERTY_TIMER_DELAY_SEC) != null) {
+ systemPropertiesBuilder.setMessageType(MessageType.DELAY);
+ } else if (messageExt.getProperty(MessageConst.PROPERTY_SHARDING_KEY) != null) {
+ systemPropertiesBuilder.setMessageType(MessageType.FIFO);
+ } else {
+ systemPropertiesBuilder.setMessageType(MessageType.NORMAL);
+ }
+
+ // born_timestamp (millis)
+ long bornTimestamp = messageExt.getBornTimestamp();
+ systemPropertiesBuilder.setBornTimestamp(Timestamps.fromMillis(bornTimestamp));
+
+ // born_host
+ String bornHostString = messageExt.getProperty(MessageConst.PROPERTY_BORN_HOST);
+ if (StringUtils.isBlank(bornHostString)) {
+ bornHostString = messageExt.getBornHostString();
+ }
+ if (StringUtils.isNotBlank(bornHostString)) {
+ systemPropertiesBuilder.setBornHost(bornHostString);
+ }
+
+ // store_timestamp (millis)
+ long storeTimestamp = messageExt.getStoreTimestamp();
+ systemPropertiesBuilder.setStoreTimestamp(Timestamps.fromMillis(storeTimestamp));
+
+ // store_host
+ SocketAddress storeHost = messageExt.getStoreHost();
+ if (storeHost != null) {
+ systemPropertiesBuilder.setStoreHost(RemotingUtil.socketAddress2String(storeHost));
+ }
+
+ // delivery_timestamp
+ String deliverMsString;
+ long deliverMs;
+ if (messageExt.getProperty(MessageConst.PROPERTY_TIMER_DELAY_SEC) != null) {
+ long delayMs = TimeUnit.SECONDS.toMillis(Long.parseLong(messageExt.getProperty(MessageConst.PROPERTY_TIMER_DELAY_SEC)));
+ deliverMs = System.currentTimeMillis() + delayMs;
+ systemPropertiesBuilder.setDeliveryTimestamp(Timestamps.fromMillis(deliverMs));
+ } else {
+ deliverMsString = messageExt.getProperty(MessageConst.PROPERTY_TIMER_DELIVER_MS);
+ if (deliverMsString != null) {
+ deliverMs = Long.parseLong(deliverMsString);
+ systemPropertiesBuilder.setDeliveryTimestamp(Timestamps.fromMillis(deliverMs));
+ }
+ }
+
+ // sharding key
+ String shardingKey = messageExt.getProperty(MessageConst.PROPERTY_SHARDING_KEY);
+ if (shardingKey != null) {
+ systemPropertiesBuilder.setMessageGroup(shardingKey);
+ }
+
+ // receipt_handle && invisible_period
+ String handle = messageExt.getProperty(MessageConst.PROPERTY_POP_CK);
+ if (handle != null) {
+ systemPropertiesBuilder.setReceiptHandle(handle);
+ }
+
+ // partition_id
+ systemPropertiesBuilder.setQueueId(messageExt.getQueueId());
+
+ // partition_offset
+ systemPropertiesBuilder.setQueueOffset(messageExt.getQueueOffset());
+
+ // delivery_attempt
+ systemPropertiesBuilder.setDeliveryAttempt(messageExt.getReconsumeTimes() + 1);
+
+ // trace context
+ String traceContext = messageExt.getProperty(MessageConst.PROPERTY_TRACE_CONTEXT);
+ if (traceContext != null) {
+ systemPropertiesBuilder.setTraceContext(traceContext);
+ }
+
+ return systemPropertiesBuilder.build();
+ }
+
+ public Resource buildResource(String resourceNameWithNamespace) {
+ return Resource.newBuilder()
+ .setResourceNamespace(NamespaceUtil.getNamespaceFromResource(resourceNameWithNamespace))
+ .setName(NamespaceUtil.withoutNamespace(resourceNameWithNamespace))
+ .build();
+ }
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcProxyException.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcProxyException.java
new file mode 100644
index 0000000000..74e499b4d7
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcProxyException.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.proxy.grpc.v2.common;
+
+import apache.rocketmq.v2.Code;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.rocketmq.proxy.common.ProxyException;
+import org.apache.rocketmq.proxy.common.ProxyExceptionCode;
+
+public class GrpcProxyException extends RuntimeException {
+
+ private ProxyException proxyException;
+ private Code code;
+
+ protected static final Map CODE_MAPPING = new ConcurrentHashMap<>();
+
+ static {
+ CODE_MAPPING.put(ProxyExceptionCode.INVALID_BROKER_NAME, Code.BAD_REQUEST);
+ CODE_MAPPING.put(ProxyExceptionCode.INVALID_RECEIPT_HANDLE, Code.INVALID_RECEIPT_HANDLE);
+ CODE_MAPPING.put(ProxyExceptionCode.FORBIDDEN, Code.FORBIDDEN);
+ CODE_MAPPING.put(ProxyExceptionCode.INTERNAL_SERVER_ERROR, Code.INTERNAL_SERVER_ERROR);
+ CODE_MAPPING.put(ProxyExceptionCode.MESSAGE_PROPERTY_CONFLICT_WITH_TYPE, Code.MESSAGE_PROPERTY_CONFLICT_WITH_TYPE);
+ }
+
+ public GrpcProxyException(Code code, String message) {
+ super(message);
+ this.code = code;
+ }
+
+ public GrpcProxyException(Code code, String message, Throwable t) {
+ super(message, t);
+ this.code = code;
+ }
+
+ public GrpcProxyException(ProxyException proxyException) {
+ super(proxyException);
+ this.proxyException = proxyException;
+ }
+
+ public Code getCode() {
+ if (this.code != null) {
+ return this.code;
+ }
+ if (this.proxyException != null) {
+ return CODE_MAPPING.getOrDefault(this.proxyException.getCode(), Code.INTERNAL_SERVER_ERROR);
+ }
+ return Code.INTERNAL_SERVER_ERROR;
+ }
+
+ public ProxyException getProxyException() {
+ return proxyException;
+ }
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcValidator.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcValidator.java
new file mode 100644
index 0000000000..0ada96b864
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/GrpcValidator.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.proxy.grpc.v2.common;
+
+import apache.rocketmq.v2.Code;
+import apache.rocketmq.v2.Resource;
+import com.google.common.base.CharMatcher;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.rocketmq.client.Validators;
+import org.apache.rocketmq.client.exception.MQClientException;
+import org.apache.rocketmq.common.MixAll;
+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.proxy.config.ConfigurationManager;
+
+public class GrpcValidator {
+ protected static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME);
+
+ protected static final Object INSTANCE_CREATE_LOCK = new Object();
+ protected static volatile GrpcValidator instance;
+
+ public static GrpcValidator getInstance() {
+ if (instance == null) {
+ synchronized (INSTANCE_CREATE_LOCK) {
+ if (instance == null) {
+ instance = new GrpcValidator();
+ }
+ }
+ }
+ return instance;
+ }
+
+ public void validateTopic(Resource topic) {
+ validateTopic(GrpcConverter.getInstance().wrapResourceWithNamespace(topic));
+ }
+
+ public void validateTopic(String topicName) {
+ if (StringUtils.isBlank(topicName)) {
+ throw new GrpcProxyException(Code.ILLEGAL_TOPIC, "topic name cannot be empty");
+ }
+ if (TopicValidator.isSystemTopic(topicName)) {
+ throw new GrpcProxyException(Code.ILLEGAL_TOPIC, "cannot access system topic");
+ }
+ try {
+ Validators.checkTopic(topicName);
+ } catch (MQClientException mqClientException) {
+ throw new GrpcProxyException(Code.ILLEGAL_TOPIC, mqClientException.getErrorMessage());
+ }
+ }
+
+ public void validateConsumerGroup(Resource consumerGroup) {
+ validateConsumerGroup(GrpcConverter.getInstance().wrapResourceWithNamespace(consumerGroup));
+ }
+
+ public void validateConsumerGroup(String consumerGroupName) {
+ if (StringUtils.isBlank(consumerGroupName)) {
+ throw new GrpcProxyException(Code.ILLEGAL_CONSUMER_GROUP, "consumer group cannot be empty");
+ }
+ if (MixAll.isSysConsumerGroup(consumerGroupName)) {
+ throw new GrpcProxyException(Code.ILLEGAL_CONSUMER_GROUP, "cannot use system consumer group");
+ }
+ try {
+ Validators.checkGroup(consumerGroupName);
+ } catch (MQClientException mqClientException) {
+ throw new GrpcProxyException(Code.ILLEGAL_CONSUMER_GROUP, mqClientException.getErrorMessage());
+ }
+ }
+
+ public void validateTopicAndConsumerGroup(Resource topic, Resource consumerGroup) {
+ validateTopic(topic);
+ validateConsumerGroup(consumerGroup);
+ }
+
+ public void validateInvisibleTime(long invisibleTime) {
+ validateInvisibleTime(invisibleTime, 0);
+ }
+
+ public void validateInvisibleTime(long invisibleTime, long minInvisibleTime) {
+ if (invisibleTime < minInvisibleTime) {
+ throw new GrpcProxyException(Code.ILLEGAL_INVISIBLE_TIME, "the invisibleTime is too small. min is " + minInvisibleTime);
+ }
+ long maxInvisibleTime = ConfigurationManager.getProxyConfig().getMaxInvisibleTimeMills();
+ if (maxInvisibleTime <= 0) {
+ return;
+ }
+ if (invisibleTime > maxInvisibleTime) {
+ throw new GrpcProxyException(Code.ILLEGAL_INVISIBLE_TIME, "the invisibleTime is too large. max is " + maxInvisibleTime);
+ }
+ }
+
+ public void validateTag(String tag) {
+ if (StringUtils.isNotEmpty(tag)) {
+ if (StringUtils.isBlank(tag)) {
+ throw new GrpcProxyException(Code.ILLEGAL_MESSAGE_TAG, "tag cannot be the char sequence of whitespace");
+ }
+ if (tag.contains("|")) {
+ throw new GrpcProxyException(Code.ILLEGAL_MESSAGE_TAG, "tag cannot contain '|'");
+ }
+ if (containControlCharacter(tag)) {
+ throw new GrpcProxyException(Code.ILLEGAL_MESSAGE_TAG, "tag cannot contain control character");
+ }
+ }
+ }
+
+ public boolean containControlCharacter(String data) {
+ for (int i = 0; i < data.length(); i++) {
+ if (CharMatcher.javaIsoControl().matches(data.charAt(i))) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/ResponseBuilder.java b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/ResponseBuilder.java
new file mode 100644
index 0000000000..08fa124be7
--- /dev/null
+++ b/proxy/src/main/java/org/apache/rocketmq/proxy/grpc/v2/common/ResponseBuilder.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.proxy.grpc.v2.common;
+
+import apache.rocketmq.v2.Code;
+import apache.rocketmq.v2.Status;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.rocketmq.client.common.ClientErrorCode;
+import org.apache.rocketmq.client.exception.MQBrokerException;
+import org.apache.rocketmq.client.exception.MQClientException;
+import org.apache.rocketmq.common.constant.LoggerName;
+import org.apache.rocketmq.common.protocol.ResponseCode;
+import org.apache.rocketmq.logging.InternalLogger;
+import org.apache.rocketmq.logging.InternalLoggerFactory;
+import org.apache.rocketmq.proxy.common.ProxyException;
+import org.apache.rocketmq.proxy.common.utils.ExceptionUtils;
+import org.apache.rocketmq.proxy.service.route.TopicRouteHelper;
+import org.apache.rocketmq.remoting.exception.RemotingTimeoutException;
+
+public class ResponseBuilder {
+
+ private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME);
+ protected static final Map