From 3401ba5783ae86e9d8d890935d7178aba5849658 Mon Sep 17 00:00:00 2001 From: zhangyang21 Date: Tue, 29 Sep 2020 20:11:04 +0800 Subject: [PATCH 01/65] [ACL] Parameter verification Signed-off-by: zhangyang21 --- .../rocketmq/acl/common/AclConstants.java | 10 +++++++ .../rocketmq/acl/common/Permission.java | 29 +++++++++++++++---- .../acl/plain/PlainPermissionManager.java | 5 +++- .../rocketmq/acl/common/PermissionTest.java | 24 +++++++++++++++ .../acl/plain/PlainAccessValidatorTest.java | 20 +++++++++++++ pom.xml | 2 +- 6 files changed, 83 insertions(+), 7 deletions(-) diff --git a/acl/src/main/java/org/apache/rocketmq/acl/common/AclConstants.java b/acl/src/main/java/org/apache/rocketmq/acl/common/AclConstants.java index bfe96f5303..d129c66d1c 100644 --- a/acl/src/main/java/org/apache/rocketmq/acl/common/AclConstants.java +++ b/acl/src/main/java/org/apache/rocketmq/acl/common/AclConstants.java @@ -44,6 +44,16 @@ public class AclConstants { public static final String CONFIG_TIME_STAMP = "timestamp"; + public static final String PUB = "PUB"; + + public static final String SUB = "SUB"; + + public static final String DENY = "DENY"; + + public static final String PUB_SUB = "PUB|SUB"; + + public static final String SUB_PUB = "SUB|PUB"; + public static final int ACCESS_KEY_MIN_LENGTH = 6; public static final int SECRET_KEY_MIN_LENGTH = 6; diff --git a/acl/src/main/java/org/apache/rocketmq/acl/common/Permission.java b/acl/src/main/java/org/apache/rocketmq/acl/common/Permission.java index 8ceb135fbf..dadcaa304a 100644 --- a/acl/src/main/java/org/apache/rocketmq/acl/common/Permission.java +++ b/acl/src/main/java/org/apache/rocketmq/acl/common/Permission.java @@ -60,14 +60,14 @@ public class Permission { return Permission.DENY; } switch (permString.trim()) { - case "PUB": + case AclConstants.PUB: return Permission.PUB; - case "SUB": + case AclConstants.SUB: return Permission.SUB; - case "PUB|SUB": - case "SUB|PUB": + case AclConstants.PUB_SUB: + case AclConstants.SUB_PUB: return Permission.PUB | Permission.SUB; - case "DENY": + case AclConstants.DENY: return Permission.DENY; default: return Permission.DENY; @@ -89,6 +89,25 @@ public class Permission { } } + public static void checkResourcePerms(List resources) { + if (resources == null || resources.isEmpty()) { + return; + } + + for (String resource : resources) { + String[] items = StringUtils.split(resource, "="); + if (items.length != 2) { + throw new AclException(String.format("Parse Resource format error for %s.\n" + + "The expected resource format is 'Res=Perm'. For example: topicA=SUB", resource)); + } + + if (!AclConstants.DENY.equals(items[1].trim()) && Permission.DENY == Permission.parsePermFromString(items[1].trim())) { + throw new AclException(String.format("Parse resource permission error for %s.\n" + + "The expected permissions are 'SUB' or 'PUB' or 'SUB|PUB' or 'PUB|SUB'.", resource)); + } + } + } + public static boolean needAdminPerm(Integer code) { return ADMIN_CODE.contains(code); } diff --git a/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainPermissionManager.java b/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainPermissionManager.java index c182d7eb6a..6514919776 100644 --- a/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainPermissionManager.java +++ b/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainPermissionManager.java @@ -128,9 +128,12 @@ public class PlainPermissionManager { if (plainAccessConfig == null) { log.error("Parameter value plainAccessConfig is null,Please check your parameter"); - return false; + throw new AclException("Parameter value plainAccessConfig is null, Please check your parameter"); } + Permission.checkResourcePerms(plainAccessConfig.getTopicPerms()); + Permission.checkResourcePerms(plainAccessConfig.getGroupPerms()); + Map aclAccessConfigMap = AclUtils.getYamlDataObject(fileHome + File.separator + fileName, Map.class); diff --git a/acl/src/test/java/org/apache/rocketmq/acl/common/PermissionTest.java b/acl/src/test/java/org/apache/rocketmq/acl/common/PermissionTest.java index 253b5b241e..c824065f7f 100644 --- a/acl/src/test/java/org/apache/rocketmq/acl/common/PermissionTest.java +++ b/acl/src/test/java/org/apache/rocketmq/acl/common/PermissionTest.java @@ -17,6 +17,7 @@ package org.apache.rocketmq.acl.common; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -165,4 +166,27 @@ public class PermissionTest { aclException.setStatus("netaddress examine scope Exception netaddress"); Assert.assertEquals(aclException.getStatus(),"netaddress examine scope Exception netaddress"); } + + @Test + public void checkResourcePermsNormalTest() { + Permission.checkResourcePerms(null); + Permission.checkResourcePerms(new ArrayList<>()); + Permission.checkResourcePerms(Arrays.asList("topicA=PUB")); + Permission.checkResourcePerms(Arrays.asList("topicA=PUB", "topicB=SUB", "topicC=PUB|SUB")); + } + + @Test(expected = AclException.class) + public void checkResourcePermsExceptionTest1() { + Permission.checkResourcePerms(Arrays.asList("topicA")); + } + + @Test(expected = AclException.class) + public void checkResourcePermsExceptionTest2() { + Permission.checkResourcePerms(Arrays.asList("topicA=")); + } + + @Test(expected = AclException.class) + public void checkResourcePermsExceptionTest3() { + Permission.checkResourcePerms(Arrays.asList("topicA=DENY1")); + } } diff --git a/acl/src/test/java/org/apache/rocketmq/acl/plain/PlainAccessValidatorTest.java b/acl/src/test/java/org/apache/rocketmq/acl/plain/PlainAccessValidatorTest.java index 645e522763..c3d41919a7 100644 --- a/acl/src/test/java/org/apache/rocketmq/acl/plain/PlainAccessValidatorTest.java +++ b/acl/src/test/java/org/apache/rocketmq/acl/plain/PlainAccessValidatorTest.java @@ -546,6 +546,26 @@ public class PlainAccessValidatorTest { Assert.assertEquals(plainAccessValidator.updateAccessConfig(plainAccessConfig), false); } + @Test(expected = AclException.class) + public void createAndUpdateAccessAclYamlConfigExceptionTest() { + System.setProperty("rocketmq.home.dir", "src/test/resources"); + System.setProperty("rocketmq.acl.plain.file", "/conf/plain_acl_update_create.yml"); + + PlainAccessConfig plainAccessConfig = new PlainAccessConfig(); + plainAccessConfig.setAccessKey("RocketMQ33"); + plainAccessConfig.setSecretKey("123456789111"); + List topicPerms = new ArrayList(); + topicPerms.add("topicB=PUB"); + plainAccessConfig.setTopicPerms(topicPerms); + List groupPerms = new ArrayList(); + groupPerms.add("groupC=DENY1"); + plainAccessConfig.setGroupPerms(groupPerms); + + PlainAccessValidator plainAccessValidator = new PlainAccessValidator(); + // Create element in the acl access yaml config file + plainAccessValidator.updateAccessConfig(plainAccessConfig); + } + @Test public void updateGlobalWhiteAddrsNormalTest() { System.setProperty("rocketmq.home.dir", "src/test/resources"); diff --git a/pom.xml b/pom.xml index 37ea2ebc0e..9370ecfe57 100644 --- a/pom.xml +++ b/pom.xml @@ -549,7 +549,7 @@ com.alibaba fastjson - 1.2.69 + 1.2.70 org.javassist From 7006d3a8dbaf18ad6c71c941c6b8255dea904c13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=AE=B8=E6=99=93=E4=B8=9C?= Date: Mon, 18 Jan 2021 13:06:48 +0800 Subject: [PATCH 02/65] benchmark consumer isSuffixEnable default value set [false] and update help info --- .../java/org/apache/rocketmq/example/benchmark/Consumer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/src/main/java/org/apache/rocketmq/example/benchmark/Consumer.java b/example/src/main/java/org/apache/rocketmq/example/benchmark/Consumer.java index b6c6ae410f..9cc931dda9 100644 --- a/example/src/main/java/org/apache/rocketmq/example/benchmark/Consumer.java +++ b/example/src/main/java/org/apache/rocketmq/example/benchmark/Consumer.java @@ -54,7 +54,7 @@ public class Consumer { final String topic = commandLine.hasOption('t') ? commandLine.getOptionValue('t').trim() : "BenchmarkTest"; final int threadCount = commandLine.hasOption('w') ? Integer.parseInt(commandLine.getOptionValue('w')) : 20; final String groupPrefix = commandLine.hasOption('g') ? commandLine.getOptionValue('g').trim() : "benchmark_consumer"; - final String isSuffixEnable = commandLine.hasOption('p') ? commandLine.getOptionValue('p').trim() : "true"; + final String isSuffixEnable = commandLine.hasOption('s') ? commandLine.getOptionValue('s').trim() : "false"; final String filterType = commandLine.hasOption('f') ? commandLine.getOptionValue('f').trim() : null; final String expression = commandLine.hasOption('e') ? commandLine.getOptionValue('e').trim() : null; final double failRate = commandLine.hasOption('r') ? Double.parseDouble(commandLine.getOptionValue('r').trim()) : 0.0; @@ -190,7 +190,7 @@ public class Consumer { opt.setRequired(false); options.addOption(opt); - opt = new Option("p", "group prefix enable", true, "Consumer group name, Default: false"); + opt = new Option("s", "group suffix enable", true, "Consumer group suffix enable, Default: false"); opt.setRequired(false); options.addOption(opt); From 32881d466e1a4b379e0addc5faf7372708d48517 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=AE=B8=E6=99=93=E4=B8=9C?= Date: Thu, 21 Jan 2021 21:13:16 +0800 Subject: [PATCH 03/65] add the property of benchmark producer that specifies how many messages to send --- .../rocketmq/example/benchmark/Producer.java | 69 ++++++++++++++++--- 1 file changed, 59 insertions(+), 10 deletions(-) diff --git a/example/src/main/java/org/apache/rocketmq/example/benchmark/Producer.java b/example/src/main/java/org/apache/rocketmq/example/benchmark/Producer.java index d1fa5743ae..f79c017d43 100644 --- a/example/src/main/java/org/apache/rocketmq/example/benchmark/Producer.java +++ b/example/src/main/java/org/apache/rocketmq/example/benchmark/Producer.java @@ -17,12 +17,14 @@ package org.apache.rocketmq.example.benchmark; import java.io.UnsupportedEncodingException; +import java.util.Arrays; import java.util.LinkedList; import java.util.Random; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import org.apache.commons.cli.CommandLine; @@ -58,9 +60,10 @@ public class Producer { final int tagCount = commandLine.hasOption('l') ? Integer.parseInt(commandLine.getOptionValue('l')) : 0; final boolean msgTraceEnable = commandLine.hasOption('m') && Boolean.parseBoolean(commandLine.getOptionValue('m')); final boolean aclEnable = commandLine.hasOption('a') && Boolean.parseBoolean(commandLine.getOptionValue('a')); + final long messageNum = commandLine.hasOption('q') ? Long.parseLong(commandLine.getOptionValue('q')) : 0; - System.out.printf("topic: %s threadCount: %d messageSize: %d keyEnable: %s propertySize: %d tagCount: %d traceEnable: %s aclEnable: %s%n", - topic, threadCount, messageSize, keyEnable, propertySize, tagCount, msgTraceEnable, aclEnable); + System.out.printf("topic: %s threadCount: %d messageSize: %d keyEnable: %s propertySize: %d tagCount: %d traceEnable: %s aclEnable: %s messageQuantity: %d%n", + topic, threadCount, messageSize, keyEnable, propertySize, tagCount, msgTraceEnable, aclEnable, messageNum); final InternalLogger log = ClientLogger.getLog(); @@ -72,6 +75,16 @@ public class Producer { final LinkedList snapshotList = new LinkedList(); + final long[] msgNums = new long[threadCount]; + + if (messageNum > 0) { + Arrays.fill(msgNums, messageNum / threadCount); + long mod = messageNum % threadCount; + if (mod > 0) { + msgNums[0] += mod; + } + } + timer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { @@ -85,14 +98,7 @@ public class Producer { timer.scheduleAtFixedRate(new TimerTask() { private void printStats() { if (snapshotList.size() >= 10) { - Long[] begin = snapshotList.getFirst(); - Long[] end = snapshotList.getLast(); - - final long sendTps = (long) (((end[3] - begin[3]) / (double) (end[0] - begin[0])) * 1000L); - final double averageRT = (end[5] - begin[5]) / (double) (end[3] - begin[3]); - - System.out.printf("Current Time: %s Send TPS: %d Max RT(ms): %d Average RT(ms): %7.3f Send Failed: %d Response Failed: %d%n", - System.currentTimeMillis(), sendTps, statsBenchmark.getSendMessageMaxRT().get(), averageRT, end[2], end[4]); + doPrintStats(snapshotList, statsBenchmark, false); } } @@ -120,9 +126,14 @@ public class Producer { producer.start(); for (int i = 0; i < threadCount; i++) { + final long msgNumLimit = msgNums[i]; + if (messageNum > 0 && msgNumLimit == 0) { + break; + } sendThreadPool.execute(new Runnable() { @Override public void run() { + int num = 0; while (true) { try { final Message msg; @@ -198,10 +209,27 @@ public class Producer { } catch (InterruptedException ignored) { } } + if (messageNum > 0 && ++num >= msgNumLimit) { + break; + } } } }); } + try { + sendThreadPool.shutdown(); + sendThreadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS); + timer.cancel(); + if (snapshotList.size() > 1) { + doPrintStats(snapshotList, statsBenchmark, true); + } else { + System.out.printf("[Complete] Send Total: %d Send Failed: %d Response Failed: %d%n", + statsBenchmark.getSendRequestSuccessCount().get() + statsBenchmark.getSendRequestFailedCount().get(), + statsBenchmark.getSendRequestFailedCount().get(), statsBenchmark.getReceiveResponseFailedCount().get()); + } + producer.shutdown(); + } catch (InterruptedException ignored) { + } } public static Options buildCommandlineOptions(final Options options) { @@ -233,6 +261,10 @@ public class Producer { opt.setRequired(false); options.addOption(opt); + opt = new Option("q", "messageQuantity", true, "Send message quantity, Default: 0, running forever"); + opt.setRequired(false); + options.addOption(opt); + return options; } @@ -249,6 +281,23 @@ public class Producer { return msg; } + + private static void doPrintStats(final LinkedList snapshotList, final StatsBenchmarkProducer statsBenchmark, boolean done) { + Long[] begin = snapshotList.getFirst(); + Long[] end = snapshotList.getLast(); + + final long sendTps = (long) (((end[3] - begin[3]) / (double) (end[0] - begin[0])) * 1000L); + final double averageRT = (end[5] - begin[5]) / (double) (end[3] - begin[3]); + + if (done) { + System.out.printf("[Complete] Send Total: %d Send TPS: %d Max RT(ms): %d Average RT(ms): %7.3f Send Failed: %d Response Failed: %d%n", + statsBenchmark.getSendRequestSuccessCount().get() + statsBenchmark.getSendRequestFailedCount().get(), + sendTps, statsBenchmark.getSendMessageMaxRT().get(), averageRT, end[2], end[4]); + } else { + System.out.printf("Current Time: %s Send TPS: %d Max RT(ms): %d Average RT(ms): %7.3f Send Failed: %d Response Failed: %d%n", + System.currentTimeMillis(), sendTps, statsBenchmark.getSendMessageMaxRT().get(), averageRT, end[2], end[4]); + } + } } class StatsBenchmarkProducer { From 58cbf596abb0daa429d54719454c67d26be40cb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=AE=B8=E6=99=93=E4=B8=9C?= Date: Thu, 21 Jan 2021 21:36:58 +0800 Subject: [PATCH 04/65] recover the property key of group suffix enable --- .../java/org/apache/rocketmq/example/benchmark/Consumer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/src/main/java/org/apache/rocketmq/example/benchmark/Consumer.java b/example/src/main/java/org/apache/rocketmq/example/benchmark/Consumer.java index 9cc931dda9..8a6429b2aa 100644 --- a/example/src/main/java/org/apache/rocketmq/example/benchmark/Consumer.java +++ b/example/src/main/java/org/apache/rocketmq/example/benchmark/Consumer.java @@ -54,7 +54,7 @@ public class Consumer { final String topic = commandLine.hasOption('t') ? commandLine.getOptionValue('t').trim() : "BenchmarkTest"; final int threadCount = commandLine.hasOption('w') ? Integer.parseInt(commandLine.getOptionValue('w')) : 20; final String groupPrefix = commandLine.hasOption('g') ? commandLine.getOptionValue('g').trim() : "benchmark_consumer"; - final String isSuffixEnable = commandLine.hasOption('s') ? commandLine.getOptionValue('s').trim() : "false"; + final String isSuffixEnable = commandLine.hasOption('p') ? commandLine.getOptionValue('p').trim() : "false"; final String filterType = commandLine.hasOption('f') ? commandLine.getOptionValue('f').trim() : null; final String expression = commandLine.hasOption('e') ? commandLine.getOptionValue('e').trim() : null; final double failRate = commandLine.hasOption('r') ? Double.parseDouble(commandLine.getOptionValue('r').trim()) : 0.0; @@ -190,7 +190,7 @@ public class Consumer { opt.setRequired(false); options.addOption(opt); - opt = new Option("s", "group suffix enable", true, "Consumer group suffix enable, Default: false"); + opt = new Option("p", "group suffix enable", true, "Consumer group suffix enable, Default: false"); opt.setRequired(false); options.addOption(opt); From 9eca70f51c9e71970e7e49d8d31d0c453531f7c4 Mon Sep 17 00:00:00 2001 From: zhangxu16 Date: Fri, 19 Feb 2021 20:54:21 +0800 Subject: [PATCH 05/65] Add delay message stats to brokerStatsManager --- .../org/apache/rocketmq/store/DefaultMessageStore.java | 2 +- .../rocketmq/store/schedule/ScheduleMessageService.java | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java b/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java index 89e2cc0284..8c3a2768a9 100644 --- a/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java +++ b/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java @@ -144,7 +144,7 @@ public class DefaultMessageStore implements MessageStore { } this.reputMessageService = new ReputMessageService(); - this.scheduleMessageService = new ScheduleMessageService(this); + this.scheduleMessageService = new ScheduleMessageService(this, brokerStatsManager); this.transientStorePool = new TransientStorePool(messageStoreConfig); diff --git a/store/src/main/java/org/apache/rocketmq/store/schedule/ScheduleMessageService.java b/store/src/main/java/org/apache/rocketmq/store/schedule/ScheduleMessageService.java index 3b19a16fd4..9006ecb0fe 100644 --- a/store/src/main/java/org/apache/rocketmq/store/schedule/ScheduleMessageService.java +++ b/store/src/main/java/org/apache/rocketmq/store/schedule/ScheduleMessageService.java @@ -44,6 +44,7 @@ import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.apache.rocketmq.store.config.StorePathConfigHelper; +import org.apache.rocketmq.store.stats.BrokerStatsManager; public class ScheduleMessageService extends ConfigManager { private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); @@ -58,14 +59,16 @@ public class ScheduleMessageService extends ConfigManager { private final ConcurrentMap offsetTable = new ConcurrentHashMap(32); private final DefaultMessageStore defaultMessageStore; + private final BrokerStatsManager brokerStatsManager; private final AtomicBoolean started = new AtomicBoolean(false); private Timer timer; private MessageStore writeMessageStore; private int maxDelayLevel; - public ScheduleMessageService(final DefaultMessageStore defaultMessageStore) { + public ScheduleMessageService(final DefaultMessageStore defaultMessageStore, final BrokerStatsManager brokerStatsManager) { this.defaultMessageStore = defaultMessageStore; this.writeMessageStore = defaultMessageStore; + this.brokerStatsManager = brokerStatsManager; } public static int queueId2DelayLevel(final int queueId) { @@ -316,6 +319,10 @@ public class ScheduleMessageService extends ConfigManager { if (putMessageResult != null && putMessageResult.getPutMessageStatus() == PutMessageStatus.PUT_OK) { + ScheduleMessageService.this.brokerStatsManager.incTopicPutNums(msgInner.getTopic(), putMessageResult.getAppendMessageResult().getMsgNum(), 1); + ScheduleMessageService.this.brokerStatsManager.incTopicPutSize(msgInner.getTopic(), + putMessageResult.getAppendMessageResult().getWroteBytes()); + ScheduleMessageService.this.brokerStatsManager.incBrokerPutNums(putMessageResult.getAppendMessageResult().getMsgNum()); continue; } else { // XXX: warn and notify me From 2d18e506683f1f2b05839425ac571634ba6cc4a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=AE=B8=E6=99=93=E4=B8=9C?= Date: Tue, 23 Feb 2021 16:35:32 +0800 Subject: [PATCH 06/65] Print InterruptedException message --- .../java/org/apache/rocketmq/example/benchmark/Producer.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/example/src/main/java/org/apache/rocketmq/example/benchmark/Producer.java b/example/src/main/java/org/apache/rocketmq/example/benchmark/Producer.java index f79c017d43..6975ab58b0 100644 --- a/example/src/main/java/org/apache/rocketmq/example/benchmark/Producer.java +++ b/example/src/main/java/org/apache/rocketmq/example/benchmark/Producer.java @@ -228,7 +228,8 @@ public class Producer { statsBenchmark.getSendRequestFailedCount().get(), statsBenchmark.getReceiveResponseFailedCount().get()); } producer.shutdown(); - } catch (InterruptedException ignored) { + } catch (InterruptedException e) { + log.error("[Exit] Thread Interrupted Exception", e); } } From d510dd79b3318cfa28f516ad92edd2bba63290ad Mon Sep 17 00:00:00 2001 From: zhangxu16 Date: Tue, 23 Feb 2021 23:39:14 +0800 Subject: [PATCH 07/65] Add constuctor of default --- .../rocketmq/store/schedule/ScheduleMessageService.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/store/src/main/java/org/apache/rocketmq/store/schedule/ScheduleMessageService.java b/store/src/main/java/org/apache/rocketmq/store/schedule/ScheduleMessageService.java index 9006ecb0fe..bf4082dc6b 100644 --- a/store/src/main/java/org/apache/rocketmq/store/schedule/ScheduleMessageService.java +++ b/store/src/main/java/org/apache/rocketmq/store/schedule/ScheduleMessageService.java @@ -65,6 +65,12 @@ public class ScheduleMessageService extends ConfigManager { private MessageStore writeMessageStore; private int maxDelayLevel; + public ScheduleMessageService(final DefaultMessageStore defaultMessageStore) { + this.defaultMessageStore = defaultMessageStore; + this.writeMessageStore = defaultMessageStore; + this.brokerStatsManager = null; + } + public ScheduleMessageService(final DefaultMessageStore defaultMessageStore, final BrokerStatsManager brokerStatsManager) { this.defaultMessageStore = defaultMessageStore; this.writeMessageStore = defaultMessageStore; From a8606d41819ebf8153b913ee03c0f99519d63959 Mon Sep 17 00:00:00 2001 From: zhangxu16 Date: Wed, 24 Feb 2021 10:08:09 +0800 Subject: [PATCH 08/65] Add check to avoid npe --- .../apache/rocketmq/store/schedule/ScheduleMessageService.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/store/src/main/java/org/apache/rocketmq/store/schedule/ScheduleMessageService.java b/store/src/main/java/org/apache/rocketmq/store/schedule/ScheduleMessageService.java index bf4082dc6b..ef5fac25cd 100644 --- a/store/src/main/java/org/apache/rocketmq/store/schedule/ScheduleMessageService.java +++ b/store/src/main/java/org/apache/rocketmq/store/schedule/ScheduleMessageService.java @@ -325,6 +325,9 @@ public class ScheduleMessageService extends ConfigManager { if (putMessageResult != null && putMessageResult.getPutMessageStatus() == PutMessageStatus.PUT_OK) { + if (ScheduleMessageService.this.brokerStatsManager == null) { + continue; + } ScheduleMessageService.this.brokerStatsManager.incTopicPutNums(msgInner.getTopic(), putMessageResult.getAppendMessageResult().getMsgNum(), 1); ScheduleMessageService.this.brokerStatsManager.incTopicPutSize(msgInner.getTopic(), putMessageResult.getAppendMessageResult().getWroteBytes()); From 19fb42f57c463c7bcf0747ca0d8432567a570662 Mon Sep 17 00:00:00 2001 From: zhangxu16 Date: Thu, 25 Feb 2021 20:54:29 +0800 Subject: [PATCH 09/65] Add config enableScheduleMessageStats --- .../rocketmq/store/DefaultMessageStore.java | 2 +- .../store/config/MessageStoreConfig.java | 10 ++++++++++ .../schedule/ScheduleMessageService.java | 20 +++++-------------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java b/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java index 8c3a2768a9..89e2cc0284 100644 --- a/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java +++ b/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java @@ -144,7 +144,7 @@ public class DefaultMessageStore implements MessageStore { } this.reputMessageService = new ReputMessageService(); - this.scheduleMessageService = new ScheduleMessageService(this, brokerStatsManager); + this.scheduleMessageService = new ScheduleMessageService(this); this.transientStorePool = new TransientStorePool(messageStoreConfig); diff --git a/store/src/main/java/org/apache/rocketmq/store/config/MessageStoreConfig.java b/store/src/main/java/org/apache/rocketmq/store/config/MessageStoreConfig.java index c13ad4cfa2..dd520f4859 100644 --- a/store/src/main/java/org/apache/rocketmq/store/config/MessageStoreConfig.java +++ b/store/src/main/java/org/apache/rocketmq/store/config/MessageStoreConfig.java @@ -152,6 +152,8 @@ public class MessageStoreConfig { private boolean isEnableBatchPush = false; + private boolean enableScheduleMessageStats = true; + public boolean isDebugLockEnable() { return debugLockEnable; } @@ -722,4 +724,12 @@ public class MessageStoreConfig { public void setEnableBatchPush(boolean enableBatchPush) { isEnableBatchPush = enableBatchPush; } + + public boolean isEnableScheduleMessageStats() { + return enableScheduleMessageStats; + } + + public void setEnableScheduleMessageStats(boolean enableScheduleMessageStats) { + this.enableScheduleMessageStats = enableScheduleMessageStats; + } } diff --git a/store/src/main/java/org/apache/rocketmq/store/schedule/ScheduleMessageService.java b/store/src/main/java/org/apache/rocketmq/store/schedule/ScheduleMessageService.java index ef5fac25cd..a78c5f8a82 100644 --- a/store/src/main/java/org/apache/rocketmq/store/schedule/ScheduleMessageService.java +++ b/store/src/main/java/org/apache/rocketmq/store/schedule/ScheduleMessageService.java @@ -44,7 +44,6 @@ import org.apache.rocketmq.store.PutMessageResult; import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.SelectMappedBufferResult; import org.apache.rocketmq.store.config.StorePathConfigHelper; -import org.apache.rocketmq.store.stats.BrokerStatsManager; public class ScheduleMessageService extends ConfigManager { private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); @@ -59,7 +58,6 @@ public class ScheduleMessageService extends ConfigManager { private final ConcurrentMap offsetTable = new ConcurrentHashMap(32); private final DefaultMessageStore defaultMessageStore; - private final BrokerStatsManager brokerStatsManager; private final AtomicBoolean started = new AtomicBoolean(false); private Timer timer; private MessageStore writeMessageStore; @@ -68,13 +66,6 @@ public class ScheduleMessageService extends ConfigManager { public ScheduleMessageService(final DefaultMessageStore defaultMessageStore) { this.defaultMessageStore = defaultMessageStore; this.writeMessageStore = defaultMessageStore; - this.brokerStatsManager = null; - } - - public ScheduleMessageService(final DefaultMessageStore defaultMessageStore, final BrokerStatsManager brokerStatsManager) { - this.defaultMessageStore = defaultMessageStore; - this.writeMessageStore = defaultMessageStore; - this.brokerStatsManager = brokerStatsManager; } public static int queueId2DelayLevel(final int queueId) { @@ -325,13 +316,12 @@ public class ScheduleMessageService extends ConfigManager { if (putMessageResult != null && putMessageResult.getPutMessageStatus() == PutMessageStatus.PUT_OK) { - if (ScheduleMessageService.this.brokerStatsManager == null) { - continue; + if (ScheduleMessageService.this.defaultMessageStore.getMessageStoreConfig().isEnableScheduleMessageStats()) { + ScheduleMessageService.this.defaultMessageStore.getBrokerStatsManager().incTopicPutNums(msgInner.getTopic(), putMessageResult.getAppendMessageResult().getMsgNum(), 1); + ScheduleMessageService.this.defaultMessageStore.getBrokerStatsManager().incTopicPutSize(msgInner.getTopic(), + putMessageResult.getAppendMessageResult().getWroteBytes()); + ScheduleMessageService.this.defaultMessageStore.getBrokerStatsManager().incBrokerPutNums(putMessageResult.getAppendMessageResult().getMsgNum()); } - ScheduleMessageService.this.brokerStatsManager.incTopicPutNums(msgInner.getTopic(), putMessageResult.getAppendMessageResult().getMsgNum(), 1); - ScheduleMessageService.this.brokerStatsManager.incTopicPutSize(msgInner.getTopic(), - putMessageResult.getAppendMessageResult().getWroteBytes()); - ScheduleMessageService.this.brokerStatsManager.incBrokerPutNums(putMessageResult.getAppendMessageResult().getMsgNum()); continue; } else { // XXX: warn and notify me From 11703c3cde4eee6468e8fc89874569532e3a5e16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=9C=E9=98=B3?= Date: Sun, 7 Mar 2021 12:14:51 +0800 Subject: [PATCH 10/65] [ISSUE #2698] TopicRouteSubCommand enhancement --- .../command/topic/TopicRouteSubCommand.java | 54 +++++++++++++++++-- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicRouteSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicRouteSubCommand.java index c770db01d4..8d739f657d 100644 --- a/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicRouteSubCommand.java +++ b/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicRouteSubCommand.java @@ -19,14 +19,23 @@ package org.apache.rocketmq.tools.command.topic; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import org.apache.rocketmq.common.protocol.route.BrokerData; +import org.apache.rocketmq.common.protocol.route.QueueData; import org.apache.rocketmq.common.protocol.route.TopicRouteData; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.tools.admin.DefaultMQAdminExt; import org.apache.rocketmq.tools.command.SubCommand; import org.apache.rocketmq.tools.command.SubCommandException; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + public class TopicRouteSubCommand implements SubCommand { + private static final String FORMAT = "%-45s %-32s %-50s %-10s %-11s %-5s\n"; + @Override public String commandName() { return "topicRoute"; @@ -43,12 +52,15 @@ public class TopicRouteSubCommand implements SubCommand { opt.setRequired(true); options.addOption(opt); + opt = new Option("l", "list", false, "Use list format to print data"); + opt.setRequired(false); + options.addOption(opt); return options; } @Override public void execute(final CommandLine commandLine, final Options options, - RPCHook rpcHook) throws SubCommandException { + RPCHook rpcHook) throws SubCommandException { DefaultMQAdminExt defaultMQAdminExt = new DefaultMQAdminExt(rpcHook); defaultMQAdminExt.setInstanceName(Long.toString(System.currentTimeMillis())); @@ -58,12 +70,46 @@ public class TopicRouteSubCommand implements SubCommand { String topic = commandLine.getOptionValue('t').trim(); TopicRouteData topicRouteData = defaultMQAdminExt.examineTopicRouteInfo(topic); - String json = topicRouteData.toJson(true); - System.out.printf("%s%n", json); + printData(topicRouteData, commandLine.hasOption('l')); } catch (Exception e) { throw new SubCommandException(this.getClass().getSimpleName() + " command failed", e); } finally { defaultMQAdminExt.shutdown(); } } -} + + private void printData(TopicRouteData topicRouteData, boolean useListFormat) { + if (!useListFormat) { + System.out.printf("%s%n", topicRouteData.toJson(true)); + return; + } + + int totalReadQueue = 0, totalWriteQueue = 0; + List queueDataList = topicRouteData.getQueueDatas(); + Map map = new HashMap<>(); + for (QueueData queueData : queueDataList) { + map.put(queueData.getBrokerName(), queueData); + } + queueDataList.sort(Comparator.comparing(QueueData::getBrokerName)); + + List brokerDataList = topicRouteData.getBrokerDatas(); + brokerDataList.sort(Comparator.comparing(BrokerData::getBrokerName)); + + System.out.printf(FORMAT, "#ClusterName", "#BrokerName", "#BrokerAddrs", "#ReadQueue", "#WriteQueue", "#Perm"); + + for (BrokerData brokerData : brokerDataList) { + String brokerName = brokerData.getBrokerName(); + QueueData queueData = map.get(brokerName); + totalReadQueue += queueData.getReadQueueNums(); + totalWriteQueue += queueData.getWriteQueueNums(); + System.out.printf(FORMAT, brokerData.getCluster(), brokerName, brokerData.getBrokerAddrs(), + queueData.getReadQueueNums(), queueData.getWriteQueueNums(), queueData.getPerm()); + } + + for (int i = 0; i < 158; i++) { + System.out.print("-"); + } + System.out.print("\n"); + System.out.printf(FORMAT, "Total:", map.keySet().size(), "", totalReadQueue, totalWriteQueue, ""); + } +} \ No newline at end of file From 48d36bd25f12dde7fa3f8db7c0594e9975a9249e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=9C=E9=98=B3?= Date: Sun, 7 Mar 2021 12:30:22 +0800 Subject: [PATCH 11/65] [ISSUE #2698] TopicRouteSubCommand enhancement --- .../rocketmq/tools/command/topic/TopicRouteSubCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicRouteSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicRouteSubCommand.java index 8d739f657d..b9d76bd341 100644 --- a/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicRouteSubCommand.java +++ b/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicRouteSubCommand.java @@ -60,7 +60,7 @@ public class TopicRouteSubCommand implements SubCommand { @Override public void execute(final CommandLine commandLine, final Options options, - RPCHook rpcHook) throws SubCommandException { + RPCHook rpcHook) throws SubCommandException { DefaultMQAdminExt defaultMQAdminExt = new DefaultMQAdminExt(rpcHook); defaultMQAdminExt.setInstanceName(Long.toString(System.currentTimeMillis())); From 40d4a22e324a2fd4cf71ccc95fe877b01cf2c503 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=9C=E9=98=B3?= Date: Sun, 7 Mar 2021 13:01:04 +0800 Subject: [PATCH 12/65] [ISSUE #2698] TopicRouteSubCommand enhancement --- .../rocketmq/tools/command/topic/TopicRouteSubCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicRouteSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicRouteSubCommand.java index b9d76bd341..2b5e75b8a2 100644 --- a/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicRouteSubCommand.java +++ b/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicRouteSubCommand.java @@ -80,7 +80,7 @@ public class TopicRouteSubCommand implements SubCommand { private void printData(TopicRouteData topicRouteData, boolean useListFormat) { if (!useListFormat) { - System.out.printf("%s%n", topicRouteData.toJson(true)); + System.out.printf("%s\n", topicRouteData.toJson(true)); return; } From 6323754621826fee50b8912f9feae0c46a91766b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=9C=E9=98=B3?= Date: Sun, 7 Mar 2021 13:02:53 +0800 Subject: [PATCH 13/65] [ISSUE #2698] TopicRouteSubCommand enhancement --- .../rocketmq/tools/command/topic/TopicRouteSubCommand.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicRouteSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicRouteSubCommand.java index 2b5e75b8a2..1b6979b8e6 100644 --- a/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicRouteSubCommand.java +++ b/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicRouteSubCommand.java @@ -34,7 +34,7 @@ import java.util.Map; public class TopicRouteSubCommand implements SubCommand { - private static final String FORMAT = "%-45s %-32s %-50s %-10s %-11s %-5s\n"; + private static final String FORMAT = "%-45s %-32s %-50s %-10s %-11s %-5s%n"; @Override public String commandName() { @@ -80,7 +80,7 @@ public class TopicRouteSubCommand implements SubCommand { private void printData(TopicRouteData topicRouteData, boolean useListFormat) { if (!useListFormat) { - System.out.printf("%s\n", topicRouteData.toJson(true)); + System.out.printf("%s%n", topicRouteData.toJson(true)); return; } @@ -109,7 +109,7 @@ public class TopicRouteSubCommand implements SubCommand { for (int i = 0; i < 158; i++) { System.out.print("-"); } - System.out.print("\n"); + System.out.print("%n"); System.out.printf(FORMAT, "Total:", map.keySet().size(), "", totalReadQueue, totalWriteQueue, ""); } } \ No newline at end of file From d444dd2029b0ae7d5e1631c343f1e868b9626cfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=9C=E9=98=B3?= Date: Sun, 7 Mar 2021 13:08:20 +0800 Subject: [PATCH 14/65] [ISSUE #2698] TopicRouteSubCommand enhancement --- .../rocketmq/tools/command/topic/TopicRouteSubCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicRouteSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicRouteSubCommand.java index 1b6979b8e6..a78a4a63ab 100644 --- a/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicRouteSubCommand.java +++ b/tools/src/main/java/org/apache/rocketmq/tools/command/topic/TopicRouteSubCommand.java @@ -109,7 +109,7 @@ public class TopicRouteSubCommand implements SubCommand { for (int i = 0; i < 158; i++) { System.out.print("-"); } - System.out.print("%n"); + System.out.printf("%n"); System.out.printf(FORMAT, "Total:", map.keySet().size(), "", totalReadQueue, totalWriteQueue, ""); } } \ No newline at end of file From 3ae251751586b940b7467284966ec4fe93f86be1 Mon Sep 17 00:00:00 2001 From: von gosling Date: Mon, 8 Mar 2021 14:31:07 +0800 Subject: [PATCH 15/65] Delete README --- docs/cn/README | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 docs/cn/README diff --git a/docs/cn/README b/docs/cn/README deleted file mode 100644 index e69de29bb2..0000000000 From 5f9f75a098e8ecb134b59d860b2699d4fa99fa21 Mon Sep 17 00:00:00 2001 From: zhangxu16 Date: Thu, 11 Mar 2021 15:40:45 +0800 Subject: [PATCH 16/65] Add UT --- .../store/schedule/ScheduleMessageServiceTest.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/store/src/test/java/org/apache/rocketmq/store/schedule/ScheduleMessageServiceTest.java b/store/src/test/java/org/apache/rocketmq/store/schedule/ScheduleMessageServiceTest.java index fd860e6b9d..fa3c6bfcd8 100644 --- a/store/src/test/java/org/apache/rocketmq/store/schedule/ScheduleMessageServiceTest.java +++ b/store/src/test/java/org/apache/rocketmq/store/schedule/ScheduleMessageServiceTest.java @@ -40,6 +40,9 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.TimeUnit; +import static org.apache.rocketmq.store.stats.BrokerStatsManager.BROKER_PUT_NUMS; +import static org.apache.rocketmq.store.stats.BrokerStatsManager.TOPIC_PUT_NUMS; +import static org.apache.rocketmq.store.stats.BrokerStatsManager.TOPIC_PUT_SIZE; import static org.assertj.core.api.Assertions.assertThat; @@ -112,6 +115,10 @@ public class ScheduleMessageServiceTest { @Test public void deliverDelayedMessageTimerTaskTest() throws Exception { + assertThat(messageStore.getMessageStoreConfig().isEnableScheduleMessageStats()).isTrue(); + + assertThat(messageStore.getBrokerStatsManager().getStatsItem(TOPIC_PUT_NUMS, topic)).isNull(); + MessageExtBrokerInner msg = buildMessage(); int realQueueId = msg.getQueueId(); // set delayLevel,and send delay message @@ -141,6 +148,10 @@ public class ScheduleMessageServiceTest { // now,found the message assertThat(messageResult.getStatus()).isEqualTo(GetMessageStatus.FOUND); + // get the stats change + assertThat(messageStore.getBrokerStatsManager().getStatsItem(BROKER_PUT_NUMS, brokerConfig.getBrokerClusterName()).getValue().get()).isEqualTo(1); + assertThat(messageStore.getBrokerStatsManager().getStatsItem(TOPIC_PUT_NUMS, topic).getValue().get()).isEqualTo(1L); + assertThat(messageStore.getBrokerStatsManager().getStatsItem(TOPIC_PUT_SIZE, topic).getValue().get()).isEqualTo(messageResult.getBufferTotalSize()); // get the message body ByteBuffer byteBuffer = ByteBuffer.allocate(messageResult.getBufferTotalSize()); From b4240d5cea8d001c21b9c0d73f5aa700fcd0d568 Mon Sep 17 00:00:00 2001 From: von gosling Date: Wed, 31 Mar 2021 09:33:41 +0800 Subject: [PATCH 17/65] docs(reblance design): modify typographical issues There is still more room for optimization, so one task is reserved here. --- docs/en/Design_LoadBlancing.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/en/Design_LoadBlancing.md b/docs/en/Design_LoadBlancing.md index e6bb66f8ac..971e8b5dd4 100644 --- a/docs/en/Design_LoadBlancing.md +++ b/docs/en/Design_LoadBlancing.md @@ -1,22 +1,24 @@ ## Load Balancing Load balancing in RocketMQ is accomplished on Client side. Specifically, it can be divided into load balancing at Producer side when sending messages and load balancing at Constumer side when subscribing messages. -### 1 Producer Load Balancing +### Producer Load Balancing When the Producer sends a message, it will first find the specified TopicPublishInfo according to Topic. After getting the routing information of TopicPublishInfo, the RocketMQ client will select a queue (MessageQueue) from the messageQueue List in TopicPublishInfo to send the message by default.Specific fault-tolerant strategies are defined in the MQFaultStrategy class. Here is a sendLatencyFaultEnable switch variable, which, if turned on, filters out the Broker agent of not available on the basis of randomly gradually increasing modular arithmetic selection. The so-called "latencyFault Tolerance" refers to a certain period of time to avoid previous failures. For example, if the latency of the last request exceeds 550 Lms, it will evade 3000 Lms; if it exceeds 1000L, it will evade 60000 L; if it is closed, it will choose a queue (MessageQueue) to send messages by randomly gradually increasing modular arithmetic, and the latencyFault Tolerance mechanism is the key to achieve high availability of message sending. -### 2 Consumer Load Balancing +### Consumer Load Balancing In RocketMQ, the two consumption modes (Push/Pull) on the Consumer side are both based on the pull mode to get the message, while in the Push mode it is only a kind of encapsulation of the pull mode, which is essentially implemented as the message pulling thread after pulling a batch of messages from the server. After submitting to the message consuming thread pool, it continues to try again to pull the message to the server. If the message is not pulled, the pull is delayed and continues. In both pull mode based consumption patterns (Push/Pull), the Consumer needs to know which message queue - queue from the Broker side to get the message. Therefore, it is necessary to do load balancing on the Consumer side, that is, which Consumer consumption is allocated to the same ConsumerGroup by more than one MessageQueue on the Broker side. - 1, Heartbeat Packet Sending on Consumer side +#### 1 Heartbeat Packet Sending on Consumer side After Consumer is started, it continuously sends heartbeat packets to all Broker instances in the RocketMQ cluster via timing task (which contains the message consumption group name, subscription relationship collection,Message communication mode and the value of the client id,etc). After receiving the heartbeat message from Consumer, Broker side maintains it in Consumer Manager's local caching variable—consumerTable, At the same time, the encapsulated client network channel information is stored in the local caching variable—channelInfoTable, which can provide metadata information for the later load balancing of Consumer. -2,Core Class for Load Balancing on Consumer side—RebalanceImpl +#### 2 Core Class for Load Balancing on Consumer side—RebalanceImpl Starting the MQClientInstance instance in the startup process of the Consumer instance will complete the start of the load balancing service thread-RebalanceService (executed every 20 s). By looking at the source code, we can find that the run () method of the RebalanceService thread calls the rebalanceByTopic () method of the RebalanceImpl class, which is the core of the Consumer end load balancing. Here, rebalanceByTopic () method will do different logical processing depending on whether the consumer communication type is "broadcast mode" or "cluster mode". Here we mainly look at the main processing flow in cluster mode: -(1) Get the message consumption queue set (mqSet) under the Topic from the local cache variable—topicSubscribeInfoTable of the rebalanceImpl instance. -(2) Call mQClientFactory. findConsumerIdList () method to send a RPC communication request to Broker side to obtain the consumer Id list under the consumer group based on the parameters of topic and consumer group (consumer table constructed by Broker side based on the heartbeat data reported by the front consumer side responds and returns, business request code: GET_CONSUMER_LIST_BY_GROUP); -(3) First, the message consumption queue and the consumer Id under Topic are sorted, then the message queue to be pulled is calculated by using the message queue allocation strategy algorithm (default: the average allocation algorithm of the message queue). The average allocation algorithm here is similar to the paging algorithm. It ranks all MessageQueues like records. It ranks all consumers like pages. It calculates the average size of each page and the range of each page record. Finally, it traverses the whole range and calculates the records that the current consumer should allocate to (MessageQueue here). +##### 1) Get the message consumption queue set (mqSet) under the Topic from the local cache variable—topicSubscribeInfoTable of the rebalanceImpl instance. +##### 2) Call mQClientFactory. findConsumerIdList () method to send a RPC communication request to Broker side to obtain the consumer Id list under the consumer group based on the parameters of topic and consumer group (consumer table constructed by Broker side based on the heartbeat data reported by the front consumer side responds and returns, business request code: GET_CONSUMER_LIST_BY_GROUP); +##### 3) First, the message consumption queue and the consumer Id under Topic are sorted, then the message queue to be pulled is calculated by using the message queue allocation strategy algorithm (default: the average allocation algorithm of the message queue). The average allocation algorithm here is similar to the paging algorithm. It ranks all MessageQueues like records. It ranks all consumers like pages. It calculates the average size of each page and the range of each page record. Finally, it traverses the whole range and calculates the records that the current consumer should allocate to (MessageQueue here). ![Image text](https://github.com/apache/rocketmq/raw/develop/docs/cn/image/rocketmq_design_8.png) -(4) Then, the updateProcessQueueTableInRebalance () method is invoked, which first compares the allocated message queue set (mqSet) with processQueueTable for filtering. + + +##### 4) Then, the updateProcessQueueTableInRebalance () method is invoked, which first compares the allocated message queue set (mqSet) with processQueueTable for filtering. ![Image text](https://github.com/apache/rocketmq/raw/develop/docs/cn/image/rocketmq_design_9.png) - The red part of the processQueueTable annotation in the figure above @@ -37,6 +39,6 @@ Starting the MQClientInstance instance in the startup process of the Consumer in removeUnnecessaryMessageQueue () method to try to remove Entry as above; -Finally, a ProcessQueue object is created for each MessageQueue in the filtered message queue set (mqSet) and stored in the processQueueTable queue of RebalanceImpl (where the computePullFromWhere (MessageQueue mq) method of the RebalanceImpl instance is invoked to obtain the next progress consumption value offset of the MessageQueue object, which is then populated into the attribute of pullRequest object to be created next time.), and create pull request object—pullRequest to add to pull list—pullRequestList, and finally execute dispatchPullRequest () method. PullRequest object of Pull message is put into the blocking queue pullRequestQueue of PullMessageService service thread in turn, and the request of Pull message is sent to Broker end after the service thread takes out. Among them, we can focus on the contrast, RebalancePushImpl and RebalancePullImpl two implementation classes dispatchPullRequest () method is different, the method in RebalancePullImpl class is empty, thus answering the last question in the previous article. +Finally, a ProcessQueue object is created for each MessageQueue in the filtered message queue set (mqSet) and stored in the processQueueTable queue of RebalanceImpl (where the computePullFromWhere (MessageQueue mq) method of the RebalanceImpl instance is invoked to obtain the next progress consumption value offset of the MessageQueue object, which is then populated into the attribute of pullRequest object to be created next time.), and create pull request object—pullRequest to add to pull list—pullRequestList, and finally execute dispatchPullRequest () method. PullRequest object of Pull message is put into the blocking queue pullRequestQueue of PullMessageService service thread in turn, and the request of Pull message is sent to Broker end after the service thread takes out. The core design idea of message consumption queue is that a message consumption queue can only be consumed by one consumer in the same consumer group at the same time, and a message consumer can consume multiple message queues at the same time. From b78326d869a09ed9f37b85dc957b39dff7a4ef87 Mon Sep 17 00:00:00 2001 From: Kingdon1992 Date: Wed, 7 Apr 2021 14:56:53 +0800 Subject: [PATCH 18/65] [ISSUE #2783] fix_bug_in_ThreadLocalIndex --- .../org/apache/rocketmq/client/common/ThreadLocalIndex.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/client/src/main/java/org/apache/rocketmq/client/common/ThreadLocalIndex.java b/client/src/main/java/org/apache/rocketmq/client/common/ThreadLocalIndex.java index c0b04caea1..891c17e3ba 100644 --- a/client/src/main/java/org/apache/rocketmq/client/common/ThreadLocalIndex.java +++ b/client/src/main/java/org/apache/rocketmq/client/common/ThreadLocalIndex.java @@ -30,9 +30,8 @@ public class ThreadLocalIndex { this.threadLocalIndex.set(index); } - index = Math.abs(index + 1); - this.threadLocalIndex.set(index); - return index; + this.threadLocalIndex.set(++index); + return Math.abs(index); } @Override From b219c161b78be6d9c0a7877c914ea81c720e1d12 Mon Sep 17 00:00:00 2001 From: shendong Date: Fri, 14 May 2021 14:25:44 +0800 Subject: [PATCH 19/65] fix the problem of potential NPE in ACL plain --- .../acl/plain/PlainPermissionManager.java | 47 +++++++++++-------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainPermissionManager.java b/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainPermissionManager.java index c182d7eb6a..1f7215bfff 100644 --- a/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainPermissionManager.java +++ b/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainPermissionManager.java @@ -50,9 +50,9 @@ public class PlainPermissionManager { private String fileName = System.getProperty("rocketmq.acl.plain.file", DEFAULT_PLAIN_ACL_FILE); - private Map plainAccessResourceMap = new HashMap<>(); + private Map plainAccessResourceMap = new HashMap<>(); - private List globalWhiteRemoteAddressStrategy = new ArrayList<>(); + private List globalWhiteRemoteAddressStrategy = new ArrayList<>(); private RemoteAddressStrategyFactory remoteAddressStrategyFactory = new RemoteAddressStrategyFactory(); @@ -73,14 +73,14 @@ public class PlainPermissionManager { JSONObject plainAclConfData = AclUtils.getYamlDataObject(fileHome + File.separator + fileName, JSONObject.class); if (plainAclConfData == null || plainAclConfData.isEmpty()) { - throw new AclException(String.format("%s file is not data", fileHome + File.separator + fileName)); + throw new AclException(String.format("%s file is not data", fileHome + File.separator + fileName)); } log.info("Broker plain acl conf data is : ", plainAclConfData.toString()); JSONArray globalWhiteRemoteAddressesList = plainAclConfData.getJSONArray("globalWhiteRemoteAddresses"); if (globalWhiteRemoteAddressesList != null && !globalWhiteRemoteAddressesList.isEmpty()) { for (int i = 0; i < globalWhiteRemoteAddressesList.size(); i++) { globalWhiteRemoteAddressStrategy.add(remoteAddressStrategyFactory. - getRemoteAddressStrategy(globalWhiteRemoteAddressesList.getString(i))); + getRemoteAddressStrategy(globalWhiteRemoteAddressesList.getString(i))); } } @@ -89,7 +89,7 @@ public class PlainPermissionManager { List plainAccessConfigList = accounts.toJavaList(PlainAccessConfig.class); for (PlainAccessConfig plainAccessConfig : plainAccessConfigList) { PlainAccessResource plainAccessResource = buildPlainAccessResource(plainAccessConfig); - plainAccessResourceMap.put(plainAccessResource.getAccessKey(),plainAccessResource); + plainAccessResourceMap.put(plainAccessResource.getAccessKey(), plainAccessResource); } } @@ -133,7 +133,9 @@ public class PlainPermissionManager { Map aclAccessConfigMap = AclUtils.getYamlDataObject(fileHome + File.separator + fileName, Map.class); - + if (aclAccessConfigMap == null || aclAccessConfigMap.isEmpty()) { + throw new AclException(String.format("%s file is not data", fileHome + File.separator + fileName)); + } List> accounts = (List>) aclAccessConfigMap.get(AclConstants.CONFIG_ACCOUNTS); Map updateAccountMap = null; if (accounts != null) { @@ -164,20 +166,21 @@ public class PlainPermissionManager { return false; } - private Map createAclAccessConfigMap(Map existedAccountMap, PlainAccessConfig plainAccessConfig) { - + private Map createAclAccessConfigMap(Map existedAccoutMap, + PlainAccessConfig plainAccessConfig) { + Map newAccountsMap = null; - if (existedAccountMap == null) { + if (existedAccoutMap == null) { newAccountsMap = new LinkedHashMap(); } else { - newAccountsMap = existedAccountMap; + newAccountsMap = existedAccoutMap; } if (StringUtils.isEmpty(plainAccessConfig.getAccessKey()) || plainAccessConfig.getAccessKey().length() <= AclConstants.ACCESS_KEY_MIN_LENGTH) { throw new AclException(String.format( - "The accessKey=%s cannot be null and length should longer than 6", - plainAccessConfig.getAccessKey())); + "The accessKey=%s cannot be null and length should longer than 6", + plainAccessConfig.getAccessKey())); } newAccountsMap.put(AclConstants.CONFIG_ACCESS_KEY, plainAccessConfig.getAccessKey()); @@ -218,8 +221,10 @@ public class PlainPermissionManager { } Map aclAccessConfigMap = AclUtils.getYamlDataObject(fileHome + File.separator + fileName, - Map.class); - + Map.class); + if (aclAccessConfigMap == null || aclAccessConfigMap.isEmpty()) { + throw new AclException(String.format("%s file is not data", fileHome + File.separator + fileName)); + } List> accounts = (List>) aclAccessConfigMap.get("accounts"); if (accounts != null) { Iterator> itemIterator = accounts.iterator(); @@ -251,7 +256,9 @@ public class PlainPermissionManager { Map aclAccessConfigMap = AclUtils.getYamlDataObject(fileHome + File.separator + fileName, Map.class); - + if (aclAccessConfigMap == null || aclAccessConfigMap.isEmpty()) { + throw new AclException(String.format("%s file is not data", fileHome + File.separator + fileName)); + } List globalWhiteRemoteAddrList = (List) aclAccessConfigMap.get(AclConstants.CONFIG_GLOBAL_WHITE_ADDRS); if (globalWhiteRemoteAddrList != null) { @@ -259,7 +266,7 @@ public class PlainPermissionManager { globalWhiteRemoteAddrList.addAll(globalWhiteAddrsList); // Update globalWhiteRemoteAddr element in memeory map firstly - aclAccessConfigMap.put(AclConstants.CONFIG_GLOBAL_WHITE_ADDRS,globalWhiteRemoteAddrList); + aclAccessConfigMap.put(AclConstants.CONFIG_GLOBAL_WHITE_ADDRS, globalWhiteRemoteAddrList); if (AclUtils.writeDataObject(fileHome + File.separator + fileName, updateAclConfigFileVersion(aclAccessConfigMap))) { return true; } @@ -275,9 +282,9 @@ public class PlainPermissionManager { List configs = new ArrayList<>(); List whiteAddrs = new ArrayList<>(); JSONObject plainAclConfData = AclUtils.getYamlDataObject(fileHome + File.separator + fileName, - JSONObject.class); + JSONObject.class); if (plainAclConfData == null || plainAclConfData.isEmpty()) { - throw new AclException(String.format("%s file is not data", fileHome + File.separator + fileName)); + throw new AclException(String.format("%s file is not data", fileHome + File.separator + fileName)); } JSONArray globalWhiteAddrs = plainAclConfData.getJSONArray(AclConstants.CONFIG_GLOBAL_WHITE_ADDRS); if (globalWhiteAddrs != null && !globalWhiteAddrs.isEmpty()) { @@ -359,7 +366,7 @@ public class PlainPermissionManager { || plainAccessConfig.getSecretKey().length() <= AclConstants.SECRET_KEY_MIN_LENGTH) { throw new AclException(String.format( "The accessKey=%s and secretKey=%s cannot be null and length should longer than 6", - plainAccessConfig.getAccessKey(), plainAccessConfig.getSecretKey())); + plainAccessConfig.getAccessKey(), plainAccessConfig.getSecretKey())); } PlainAccessResource plainAccessResource = new PlainAccessResource(); plainAccessResource.setAccessKey(plainAccessConfig.getAccessKey()); @@ -375,7 +382,7 @@ public class PlainPermissionManager { Permission.parseResourcePerms(plainAccessResource, true, plainAccessConfig.getTopicPerms()); plainAccessResource.setRemoteAddressStrategy(remoteAddressStrategyFactory. - getRemoteAddressStrategy(plainAccessResource.getWhiteRemoteAddress())); + getRemoteAddressStrategy(plainAccessResource.getWhiteRemoteAddress())); return plainAccessResource; } From fbb35d3d66bc732f9213e15c85071ad603daa168 Mon Sep 17 00:00:00 2001 From: zhangjidi2016 <1017543663@qq.com> Date: Mon, 17 May 2021 15:16:21 +0800 Subject: [PATCH 20/65] [ISSUE #2898] Resolve rocketmq-example project failed during checkstyle execution --- .../example/tracemessage/OpenTracingTransactionProducer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/src/main/java/org/apache/rocketmq/example/tracemessage/OpenTracingTransactionProducer.java b/example/src/main/java/org/apache/rocketmq/example/tracemessage/OpenTracingTransactionProducer.java index 428640a383..514f3ceb7f 100644 --- a/example/src/main/java/org/apache/rocketmq/example/tracemessage/OpenTracingTransactionProducer.java +++ b/example/src/main/java/org/apache/rocketmq/example/tracemessage/OpenTracingTransactionProducer.java @@ -57,7 +57,7 @@ public class OpenTracingTransactionProducer { try { Message msg = new Message("TopicTest", "Tag", "KEY", - ("Hello RocketMQ").getBytes(RemotingHelper.DEFAULT_CHARSET)); + "Hello RocketMQ".getBytes(RemotingHelper.DEFAULT_CHARSET)); SendResult sendResult = producer.sendMessageInTransaction(msg, null); System.out.printf("%s%n", sendResult); } catch (MQClientException | UnsupportedEncodingException e) { From 16694485a7e111c601c0ec8c2a76a44cdd830067 Mon Sep 17 00:00:00 2001 From: Git_Yang <30995057+Git-Yang@users.noreply.github.com> Date: Wed, 19 May 2021 09:50:41 +0800 Subject: [PATCH 21/65] [ISSUE #2865] Batch message send bug fix (#2866) * [DLedgerCommitLog] Batch message send bug fix Signed-off-by: zhangyang * [DLedgerCommitLog] Batch message send bug fix2 Signed-off-by: zhangyang --- .../store/dledger/DLedgerCommitLog.java | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/store/src/main/java/org/apache/rocketmq/store/dledger/DLedgerCommitLog.java b/store/src/main/java/org/apache/rocketmq/store/dledger/DLedgerCommitLog.java index 9a6e7a78a1..9241ffe423 100644 --- a/store/src/main/java/org/apache/rocketmq/store/dledger/DLedgerCommitLog.java +++ b/store/src/main/java/org/apache/rocketmq/store/dledger/DLedgerCommitLog.java @@ -426,17 +426,18 @@ public class DLedgerCommitLog extends CommitLog { AppendFuture dledgerFuture; EncodeResult encodeResult; + encodeResult = this.messageSerializer.serialize(msg); + if (encodeResult.status != AppendMessageStatus.PUT_OK) { + return new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, new AppendMessageResult(encodeResult.status)); + } + putMessageLock.lock(); //spin or ReentrantLock ,depending on store config long elapsedTimeInLock; long queueOffset; try { beginTimeInDledgerLock = this.defaultMessageStore.getSystemClock().now(); - encodeResult = this.messageSerializer.serialize(msg); queueOffset = getQueueOffsetByKey(encodeResult.queueOffsetKey, tranType); - encodeResult.setQueueOffsetKey(queueOffset); - if (encodeResult.status != AppendMessageStatus.PUT_OK) { - return new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, new AppendMessageResult(encodeResult.status)); - } + encodeResult.setQueueOffsetKey(queueOffset, false); AppendEntryRequest request = new AppendEntryRequest(); request.setGroup(dLedgerConfig.getGroup()); request.setRemoteId(dLedgerServer.getMemberState().getSelfId()); @@ -542,6 +543,12 @@ public class DLedgerCommitLog extends CommitLog { BatchAppendFuture dledgerFuture; EncodeResult encodeResult; + encodeResult = this.messageSerializer.serialize(messageExtBatch); + if (encodeResult.status != AppendMessageStatus.PUT_OK) { + return new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, new AppendMessageResult(encodeResult + .status)); + } + putMessageLock.lock(); //spin or ReentrantLock ,depending on store config msgIdBuilder.setLength(0); long elapsedTimeInLock; @@ -549,12 +556,8 @@ public class DLedgerCommitLog extends CommitLog { long msgNum = 0; try { beginTimeInDledgerLock = this.defaultMessageStore.getSystemClock().now(); - encodeResult = this.messageSerializer.serialize(messageExtBatch); - queueOffset = topicQueueTable.get(encodeResult.queueOffsetKey); - if (encodeResult.status != AppendMessageStatus.PUT_OK) { - return new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, new AppendMessageResult(encodeResult - .status)); - } + queueOffset = getQueueOffsetByKey(encodeResult.queueOffsetKey, tranType); + encodeResult.setQueueOffsetKey(queueOffset, true); BatchAppendEntryRequest request = new BatchAppendEntryRequest(); request.setGroup(dLedgerConfig.getGroup()); request.setRemoteId(dLedgerServer.getMemberState().getSelfId()); @@ -664,7 +667,7 @@ public class DLedgerCommitLog extends CommitLog { try { beginTimeInDledgerLock = this.defaultMessageStore.getSystemClock().now(); queueOffset = getQueueOffsetByKey(encodeResult.queueOffsetKey, tranType); - encodeResult.setQueueOffsetKey(queueOffset); + encodeResult.setQueueOffsetKey(queueOffset, false); AppendEntryRequest request = new AppendEntryRequest(); request.setGroup(dLedgerConfig.getGroup()); request.setRemoteId(dLedgerServer.getMemberState().getSelfId()); @@ -779,7 +782,8 @@ public class DLedgerCommitLog extends CommitLog { long msgNum = 0; try { beginTimeInDledgerLock = this.defaultMessageStore.getSystemClock().now(); - queueOffset = topicQueueTable.get(encodeResult.queueOffsetKey); + queueOffset = getQueueOffsetByKey(encodeResult.queueOffsetKey, tranType); + encodeResult.setQueueOffsetKey(queueOffset, true); BatchAppendEntryRequest request = new BatchAppendEntryRequest(); request.setGroup(dLedgerConfig.getGroup()); request.setRemoteId(dLedgerServer.getMemberState().getSelfId()); @@ -957,8 +961,15 @@ public class DLedgerCommitLog extends CommitLog { this.queueOffsetKey = queueOffsetKey; } - public void setQueueOffsetKey(long offset) { - data.putLong(MessageDecoder.QUEUE_OFFSET_POSITION, offset); + public void setQueueOffsetKey(long offset, boolean isBatch) { + if (!isBatch) { + this.data.putLong(MessageDecoder.QUEUE_OFFSET_POSITION, offset); + return; + } + + for (byte[] data : batchData) { + ByteBuffer.wrap(data).putLong(MessageDecoder.QUEUE_OFFSET_POSITION, offset++); + } } public byte[] getData() { @@ -977,8 +988,6 @@ public class DLedgerCommitLog extends CommitLog { // The maximum length of the message private final int maxMessageSize; - // Build Message Key - private final StringBuilder keyBuilder = new StringBuilder(); MessageSerializer(final int size) { this.maxMessageSize = size; @@ -1079,17 +1088,7 @@ public class DLedgerCommitLog extends CommitLog { } public EncodeResult serialize(final MessageExtBatch messageExtBatch) { - keyBuilder.setLength(0); - keyBuilder.append(messageExtBatch.getTopic()); - keyBuilder.append('-'); - keyBuilder.append(messageExtBatch.getQueueId()); - String key = keyBuilder.toString(); - - Long queueOffset = DLedgerCommitLog.this.topicQueueTable.get(key); - if (null == queueOffset) { - queueOffset = 0L; - DLedgerCommitLog.this.topicQueueTable.put(key, queueOffset); - } + String key = messageExtBatch.getTopic() + "-" + messageExtBatch.getQueueId(); int totalMsgLen = 0; ByteBuffer messagesByteBuff = messageExtBatch.wrap(); @@ -1154,7 +1153,7 @@ public class DLedgerCommitLog extends CommitLog { // 5 FLAG msgStoreItemMemory.putInt(flag); // 6 QUEUEOFFSET - msgStoreItemMemory.putLong(queueOffset++); + msgStoreItemMemory.putLong(0L); // 7 PHYSICALOFFSET msgStoreItemMemory.putLong(0); // 8 SYSFLAG @@ -1210,6 +1209,7 @@ public class DLedgerCommitLog extends CommitLog { this.sbr = sbr; } + @Override public synchronized void release() { super.release(); if (sbr != null) { From f5cf3ccebdb5624153f34da9482185d8fc7d4cdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=97=AD?= Date: Wed, 19 May 2021 12:28:33 +0800 Subject: [PATCH 22/65] Fix ORIGIN_MESSAGE_ID field not write into commitlog (#2869) Co-authored-by: zhangxu16 --- .../apache/rocketmq/broker/processor/SendMessageProcessor.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/SendMessageProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/SendMessageProcessor.java index d8f53e4542..724cf54c81 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/SendMessageProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/SendMessageProcessor.java @@ -218,6 +218,8 @@ public class SendMessageProcessor extends AbstractSendMessageProcessor implement String originMsgId = MessageAccessor.getOriginMessageId(msgExt); MessageAccessor.setOriginMessageId(msgInner, UtilAll.isBlank(originMsgId) ? msgExt.getMsgId() : originMsgId); + msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgExt.getProperties())); + CompletableFuture putMessageResult = this.brokerController.getMessageStore().asyncPutMessage(msgInner); return putMessageResult.thenApply((r) -> { if (r != null) { From 6c279f389024c2c894a51d869d46e82efc8e372a Mon Sep 17 00:00:00 2001 From: von gosling Date: Wed, 19 May 2021 12:31:59 +0800 Subject: [PATCH 23/65] docs(fix typo) Thanks juliuszhang --- docs/cn/design.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cn/design.md b/docs/cn/design.md index dcd39026f2..1c32521ed5 100644 --- a/docs/cn/design.md +++ b/docs/cn/design.md @@ -58,7 +58,7 @@ code |int | 请求操作码,应答方根据不同的请求码进行不同的 language | LanguageCode | 请求方实现的语言 | 应答方实现的语言 version | int | 请求方程序的版本 | 应答方程序的版本 opaque | int |相当于requestId,在同一个连接上的不同请求标识码,与响应消息中的相对应 | 应答不做修改直接返回 -flag | int | 区分是普通RPC还是onewayRPC得标志 | 区分是普通RPC还是onewayRPC得标志 +flag | int | 区分是普通RPC还是onewayRPC的标志 | 区分是普通RPC还是onewayRPC的标志 remark | String | 传输自定义文本信息 | 传输自定义文本信息 extFields | HashMap | 请求自定义扩展信息 | 响应自定义扩展信息 From 8a2554dfe6d97c561ba19ec8051fe25391c7a5af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B3=E5=8A=AD=E6=98=8E?= <42922512+shenshaoming@users.noreply.github.com> Date: Wed, 19 May 2021 12:35:21 +0800 Subject: [PATCH 24/65] [ISSUE #2826] Remove useless code (#2827) --- .../rocketmq/common/constant/PermName.java | 4 ++-- docs/en/Design_LoadBlancing.md | 20 ++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/common/src/main/java/org/apache/rocketmq/common/constant/PermName.java b/common/src/main/java/org/apache/rocketmq/common/constant/PermName.java index 200dec2481..1fe9aa106e 100644 --- a/common/src/main/java/org/apache/rocketmq/common/constant/PermName.java +++ b/common/src/main/java/org/apache/rocketmq/common/constant/PermName.java @@ -20,10 +20,10 @@ public class PermName { public static final int PERM_PRIORITY = 0x1 << 3; public static final int PERM_READ = 0x1 << 2; public static final int PERM_WRITE = 0x1 << 1; - public static final int PERM_INHERIT = 0x1 << 0; + public static final int PERM_INHERIT = 0x1; public static String perm2String(final int perm) { - final StringBuffer sb = new StringBuffer("---"); + final StringBuilder sb = new StringBuilder("---"); if (isReadable(perm)) { sb.replace(0, 1, "R"); } diff --git a/docs/en/Design_LoadBlancing.md b/docs/en/Design_LoadBlancing.md index e6bb66f8ac..971e8b5dd4 100644 --- a/docs/en/Design_LoadBlancing.md +++ b/docs/en/Design_LoadBlancing.md @@ -1,22 +1,24 @@ ## Load Balancing Load balancing in RocketMQ is accomplished on Client side. Specifically, it can be divided into load balancing at Producer side when sending messages and load balancing at Constumer side when subscribing messages. -### 1 Producer Load Balancing +### Producer Load Balancing When the Producer sends a message, it will first find the specified TopicPublishInfo according to Topic. After getting the routing information of TopicPublishInfo, the RocketMQ client will select a queue (MessageQueue) from the messageQueue List in TopicPublishInfo to send the message by default.Specific fault-tolerant strategies are defined in the MQFaultStrategy class. Here is a sendLatencyFaultEnable switch variable, which, if turned on, filters out the Broker agent of not available on the basis of randomly gradually increasing modular arithmetic selection. The so-called "latencyFault Tolerance" refers to a certain period of time to avoid previous failures. For example, if the latency of the last request exceeds 550 Lms, it will evade 3000 Lms; if it exceeds 1000L, it will evade 60000 L; if it is closed, it will choose a queue (MessageQueue) to send messages by randomly gradually increasing modular arithmetic, and the latencyFault Tolerance mechanism is the key to achieve high availability of message sending. -### 2 Consumer Load Balancing +### Consumer Load Balancing In RocketMQ, the two consumption modes (Push/Pull) on the Consumer side are both based on the pull mode to get the message, while in the Push mode it is only a kind of encapsulation of the pull mode, which is essentially implemented as the message pulling thread after pulling a batch of messages from the server. After submitting to the message consuming thread pool, it continues to try again to pull the message to the server. If the message is not pulled, the pull is delayed and continues. In both pull mode based consumption patterns (Push/Pull), the Consumer needs to know which message queue - queue from the Broker side to get the message. Therefore, it is necessary to do load balancing on the Consumer side, that is, which Consumer consumption is allocated to the same ConsumerGroup by more than one MessageQueue on the Broker side. - 1, Heartbeat Packet Sending on Consumer side +#### 1 Heartbeat Packet Sending on Consumer side After Consumer is started, it continuously sends heartbeat packets to all Broker instances in the RocketMQ cluster via timing task (which contains the message consumption group name, subscription relationship collection,Message communication mode and the value of the client id,etc). After receiving the heartbeat message from Consumer, Broker side maintains it in Consumer Manager's local caching variable—consumerTable, At the same time, the encapsulated client network channel information is stored in the local caching variable—channelInfoTable, which can provide metadata information for the later load balancing of Consumer. -2,Core Class for Load Balancing on Consumer side—RebalanceImpl +#### 2 Core Class for Load Balancing on Consumer side—RebalanceImpl Starting the MQClientInstance instance in the startup process of the Consumer instance will complete the start of the load balancing service thread-RebalanceService (executed every 20 s). By looking at the source code, we can find that the run () method of the RebalanceService thread calls the rebalanceByTopic () method of the RebalanceImpl class, which is the core of the Consumer end load balancing. Here, rebalanceByTopic () method will do different logical processing depending on whether the consumer communication type is "broadcast mode" or "cluster mode". Here we mainly look at the main processing flow in cluster mode: -(1) Get the message consumption queue set (mqSet) under the Topic from the local cache variable—topicSubscribeInfoTable of the rebalanceImpl instance. -(2) Call mQClientFactory. findConsumerIdList () method to send a RPC communication request to Broker side to obtain the consumer Id list under the consumer group based on the parameters of topic and consumer group (consumer table constructed by Broker side based on the heartbeat data reported by the front consumer side responds and returns, business request code: GET_CONSUMER_LIST_BY_GROUP); -(3) First, the message consumption queue and the consumer Id under Topic are sorted, then the message queue to be pulled is calculated by using the message queue allocation strategy algorithm (default: the average allocation algorithm of the message queue). The average allocation algorithm here is similar to the paging algorithm. It ranks all MessageQueues like records. It ranks all consumers like pages. It calculates the average size of each page and the range of each page record. Finally, it traverses the whole range and calculates the records that the current consumer should allocate to (MessageQueue here). +##### 1) Get the message consumption queue set (mqSet) under the Topic from the local cache variable—topicSubscribeInfoTable of the rebalanceImpl instance. +##### 2) Call mQClientFactory. findConsumerIdList () method to send a RPC communication request to Broker side to obtain the consumer Id list under the consumer group based on the parameters of topic and consumer group (consumer table constructed by Broker side based on the heartbeat data reported by the front consumer side responds and returns, business request code: GET_CONSUMER_LIST_BY_GROUP); +##### 3) First, the message consumption queue and the consumer Id under Topic are sorted, then the message queue to be pulled is calculated by using the message queue allocation strategy algorithm (default: the average allocation algorithm of the message queue). The average allocation algorithm here is similar to the paging algorithm. It ranks all MessageQueues like records. It ranks all consumers like pages. It calculates the average size of each page and the range of each page record. Finally, it traverses the whole range and calculates the records that the current consumer should allocate to (MessageQueue here). ![Image text](https://github.com/apache/rocketmq/raw/develop/docs/cn/image/rocketmq_design_8.png) -(4) Then, the updateProcessQueueTableInRebalance () method is invoked, which first compares the allocated message queue set (mqSet) with processQueueTable for filtering. + + +##### 4) Then, the updateProcessQueueTableInRebalance () method is invoked, which first compares the allocated message queue set (mqSet) with processQueueTable for filtering. ![Image text](https://github.com/apache/rocketmq/raw/develop/docs/cn/image/rocketmq_design_9.png) - The red part of the processQueueTable annotation in the figure above @@ -37,6 +39,6 @@ Starting the MQClientInstance instance in the startup process of the Consumer in removeUnnecessaryMessageQueue () method to try to remove Entry as above; -Finally, a ProcessQueue object is created for each MessageQueue in the filtered message queue set (mqSet) and stored in the processQueueTable queue of RebalanceImpl (where the computePullFromWhere (MessageQueue mq) method of the RebalanceImpl instance is invoked to obtain the next progress consumption value offset of the MessageQueue object, which is then populated into the attribute of pullRequest object to be created next time.), and create pull request object—pullRequest to add to pull list—pullRequestList, and finally execute dispatchPullRequest () method. PullRequest object of Pull message is put into the blocking queue pullRequestQueue of PullMessageService service thread in turn, and the request of Pull message is sent to Broker end after the service thread takes out. Among them, we can focus on the contrast, RebalancePushImpl and RebalancePullImpl two implementation classes dispatchPullRequest () method is different, the method in RebalancePullImpl class is empty, thus answering the last question in the previous article. +Finally, a ProcessQueue object is created for each MessageQueue in the filtered message queue set (mqSet) and stored in the processQueueTable queue of RebalanceImpl (where the computePullFromWhere (MessageQueue mq) method of the RebalanceImpl instance is invoked to obtain the next progress consumption value offset of the MessageQueue object, which is then populated into the attribute of pullRequest object to be created next time.), and create pull request object—pullRequest to add to pull list—pullRequestList, and finally execute dispatchPullRequest () method. PullRequest object of Pull message is put into the blocking queue pullRequestQueue of PullMessageService service thread in turn, and the request of Pull message is sent to Broker end after the service thread takes out. The core design idea of message consumption queue is that a message consumption queue can only be consumed by one consumer in the same consumer group at the same time, and a message consumer can consume multiple message queues at the same time. From 662404edfdb4f351f8bb2fbae530b97c7a58c50f Mon Sep 17 00:00:00 2001 From: Walker Xia Date: Wed, 19 May 2021 19:38:10 +0800 Subject: [PATCH 25/65] [ISSUE #1057]Filter out docker0 when finding a local address (#1177) * Filter out docker0 when finding a local address * sweep out bridge interface Co-authored-by: yuanyuan.xia --- .../rocketmq/remoting/common/RemotingUtil.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/common/RemotingUtil.java b/remoting/src/main/java/org/apache/rocketmq/remoting/common/RemotingUtil.java index a16940e561..d936c3bf69 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/common/RemotingUtil.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/common/RemotingUtil.java @@ -19,6 +19,7 @@ package org.apache.rocketmq.remoting.common; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; +import java.io.File; import java.io.IOException; import java.lang.reflect.Method; import java.net.Inet6Address; @@ -98,6 +99,10 @@ public class RemotingUtil { ArrayList ipv6Result = new ArrayList(); while (enumeration.hasMoreElements()) { final NetworkInterface networkInterface = enumeration.nextElement(); + if (isBridge(networkInterface)) { + continue; + } + final Enumeration en = networkInterface.getInetAddresses(); while (en.hasMoreElements()) { final InetAddress address = en.nextElement(); @@ -160,6 +165,19 @@ public class RemotingUtil { return sb.toString(); } + private static boolean isBridge(NetworkInterface networkInterface) { + try { + if (isLinuxPlatform()) { + String interfaceName = networkInterface.getName(); + File file = new File("/sys/class/net/" + interfaceName + "/bridge"); + return file.exists(); + } + } catch (SecurityException e) { + //Ignore + } + return false; + } + public static SocketChannel connect(SocketAddress remote) { return connect(remote, 1000 * 5); } From 18dadebef67cba7e0fe3dc59adcb2fc42c22273f Mon Sep 17 00:00:00 2001 From: Braavos <35978114+EricHetti@users.noreply.github.com> Date: Wed, 19 May 2021 12:39:45 +0100 Subject: [PATCH 26/65] Add unit tests for org.apache.rocketmq.common.UtilAll (#1249) These tests were written using Diffblue Cover. Co-authored-by: Eric Hettiaratchi <35978114+Braavos96@users.noreply.github.com> --- .../apache/rocketmq/common/UtilAllTests.java | 381 ++++++++++++++++++ pom.xml | 11 + 2 files changed, 392 insertions(+) create mode 100644 common/src/test/java/org/apache/rocketmq/common/UtilAllTests.java diff --git a/common/src/test/java/org/apache/rocketmq/common/UtilAllTests.java b/common/src/test/java/org/apache/rocketmq/common/UtilAllTests.java new file mode 100644 index 0000000000..d2aa6ef19c --- /dev/null +++ b/common/src/test/java/org/apache/rocketmq/common/UtilAllTests.java @@ -0,0 +1,381 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.common; + +import java.io.File; +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.lang.management.RuntimeMXBean; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.util.Calendar; +import java.util.Date; +import java.util.Enumeration; + +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +@RunWith(PowerMockRunner.class) +@PowerMockIgnore("javax.management.*") +public class UtilAllTests { + + @Rule + private TemporaryFolder folder = new TemporaryFolder(); + + @Rule + private ExpectedException thrown = ExpectedException.none(); + + @PrepareForTest({RuntimeMXBean.class, + UtilAll.class, ManagementFactory.class}) + @Test + public void testGetPidIfNotNull() { + PowerMockito.mockStatic(ManagementFactory.class); + RuntimeMXBean runtimeMXBean = PowerMockito.mock(RuntimeMXBean.class); + PowerMockito.when(runtimeMXBean.getName()) + .thenReturn("40@abc.com"); + PowerMockito.doReturn(runtimeMXBean).when(ManagementFactory.class); + ManagementFactory.getRuntimeMXBean(); + + Assert.assertEquals(40, UtilAll.getPid()); + } + + @PrepareForTest({RuntimeMXBean.class, + UtilAll.class, ManagementFactory.class}) + @Test + public void testGetPidIfNull() { + PowerMockito.mockStatic(ManagementFactory.class); + RuntimeMXBean runtimeMXBean = PowerMockito.mock(RuntimeMXBean.class); + PowerMockito.when(runtimeMXBean.getName()).thenReturn(null); + PowerMockito.doReturn(runtimeMXBean).when(ManagementFactory.class); + ManagementFactory.getRuntimeMXBean(); + + Assert.assertEquals(-1, UtilAll.getPid()); + } + + @PrepareForTest({UtilAll.class}) + @Test + public void testCurrentStackTraceIfNotNull() { + PowerMockito.mockStatic(Thread.class); + StackTraceElement stackTraceElement = + new StackTraceElement("Bar", "foo", "baz", 101); + PowerMockito.when(Thread.currentThread().getStackTrace()) + .thenReturn(new StackTraceElement[]{stackTraceElement}); + + Assert.assertEquals("\n\tBar.foo(baz:101)", + UtilAll.currentStackTrace()); + } + + @Test + public void testOffset2FileNameIfInputNotNull() { + Assert.assertEquals("00000000000000000002", + UtilAll.offset2FileName(2l)); + Assert.assertEquals("00000001558787014000", + UtilAll.offset2FileName(1558787014000l)); + } + + @PrepareForTest({UtilAll.class, System.class}) + @Test + public void testComputeEclipseTimeMillisecondsIfInputNotNull() { + PowerMockito.mockStatic(System.class); + PowerMockito.when(System.currentTimeMillis()) + .thenReturn(1558787014000l); + + Assert.assertEquals(2592000000l, + UtilAll.computeEclipseTimeMilliseconds(1556195014000l)); + } + + @PrepareForTest({Calendar.class, UtilAll.class}) + @Test + public void testIsItTimeToDoIfCurrentSetTime() { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(1558787014000l); + PowerMockito.mockStatic(Calendar.class); + PowerMockito.when(Calendar.getInstance()).thenReturn(calendar); + + Assert.assertTrue(UtilAll.isItTimeToDo("13;12;14")); + + Assert.assertFalse(UtilAll.isItTimeToDo("00;00;00;")); + } + + @PrepareForTest({UtilAll.class, System.class}) + @Test + public void testTimeMillisToHumanStringIfCurrentTimeNotNull() { + PowerMockito.mockStatic(System.class); + PowerMockito.when(System.currentTimeMillis()) + .thenReturn(1558787014000l); + + Assert.assertEquals("20190525132334000", + UtilAll.timeMillisToHumanString()); + } + + @Test + public void testTimeMillisToHumanStringLongIfInputNotNull() { + Assert.assertEquals("20190525132334000", + UtilAll.timeMillisToHumanString(1558787014000l)); + } + + @PrepareForTest({System.class, UtilAll.class}) + @Test + public void testComputNextMorningTimeMillisIfCurrentTimeNotNull() { + PowerMockito.mockStatic(System.class); + PowerMockito.when(System.currentTimeMillis()) + .thenReturn(1515585600000L); + + Assert.assertEquals(1515628800000l, + UtilAll.computNextMorningTimeMillis()); + } + + @PrepareForTest({System.class, UtilAll.class}) + @Test + public void testComputNextMinutesTimeMillisIfCurrentTimeNotNull() { + PowerMockito.mockStatic(System.class); + PowerMockito.when(System.currentTimeMillis()) + .thenReturn(1515585600000L); + + Assert.assertEquals(1515585660000l, + UtilAll.computNextMinutesTimeMillis()); + } + + @PrepareForTest({System.class, UtilAll.class}) + @Test + public void testComputNextHourTimeMillisIfCurrentTimeNotNull() { + PowerMockito.mockStatic(System.class); + PowerMockito.when(System.currentTimeMillis()) + .thenReturn(1515585600000L); + + Assert.assertEquals(1515589200000l, + UtilAll.computNextHourTimeMillis()); + } + + @PrepareForTest({UtilAll.class, System.class}) + @Test + public void testComputNextHalfHourTimeMillisIfCurrentTimeNotNull() { + PowerMockito.mockStatic(System.class); + PowerMockito.when(System.currentTimeMillis()) + .thenReturn(1515585600000L); + + Assert.assertEquals(1515591000000l, + UtilAll.computNextHalfHourTimeMillis()); + } + + @Test + public void testTimeMillisToHumanString2IfInputNotNull() { + Assert.assertEquals("2019-04-25 13:23:34,000", + UtilAll.timeMillisToHumanString2(1556195014000l)); + } + + @Test + public void testTimeMillisToHumanString3IfInputNotNull() { + Assert.assertEquals("20190425132334", + UtilAll.timeMillisToHumanString3(1556195014000l)); + } + + @Test + public void testGetDiskPartitionSpaceUsedPercentIfFileDoesNotExist() { + Assert.assertEquals(-1, + UtilAll.getDiskPartitionSpaceUsedPercent(""), 0); + Assert.assertEquals(-1, + UtilAll.getDiskPartitionSpaceUsedPercent(null), 0); + Assert.assertEquals(-1, + UtilAll.getDiskPartitionSpaceUsedPercent("foo"), 0); + } + + @Test + public void testGetDiskPartitionSpaceUsedPercentIfFileExists() + throws IOException { + File file = folder.newFile("foo.txt"); + try { + Assert.assertNotNull( + UtilAll.getDiskPartitionSpaceUsedPercent(file.getPath())); + } finally { + file.delete(); + folder.delete(); + } + } + + @Test + public void testCrc32IfInputNull() { + Assert.assertEquals(0, UtilAll.crc32(null)); + Assert.assertEquals(0, UtilAll.crc32(new byte[]{})); + } + + @Test + public void testCrc32IfInputNotNull() { + Assert.assertEquals(1438416925, UtilAll.crc32(new byte[]{1, 2, 3})); + } + + @Test + public void testBytes2stringIfInputArrayNotEmpty() { + Assert.assertEquals("010203", + UtilAll.bytes2string(new byte[]{1, 2, 3})); + } + + @Test + public void testBytes2stringIfInputEmptyArray() { + Assert.assertEquals("", UtilAll.bytes2string(new byte[0])); + } + + @Test + public void testString2bytesIfInputNullOrEmptyString() { + Assert.assertNull(UtilAll.string2bytes("")); + Assert.assertNull(UtilAll.string2bytes(null)); + } + + @Test + public void testString2bytesIfInputNotNullOrEmptyString() { + Assert.assertArrayEquals(new byte[]{-1}, UtilAll.string2bytes("foo")); + } + + @Test + public void testUncompressIfInputNotNull() throws IOException { + byte[] by = new byte[]{120, 1, 1, 3, 0, -4, -1, 1, 2, 3, 0, 13, 0, 7}; + Assert.assertArrayEquals(new byte[]{1, 2, 3}, UtilAll.uncompress(by)); + } + + @Test + public void testCompressIfInputNotNull() throws IOException { + byte[] by = new byte[]{120, 1, 1, 3, 0, -4, -1, 1, 2, 3, 0, 13, 0, 7}; + Assert.assertArrayEquals(by, UtilAll.compress(new byte[]{1, 2, 3}, 0)); + } + + @Test + public void testAsIntIfInputNotNull() { + Assert.assertEquals(1, UtilAll.asInt("foo", 1)); + Assert.assertEquals(123, UtilAll.asInt("123", 1)); + } + + @Test + public void testAsLongIfInputNotNull() { + Assert.assertEquals(1l, UtilAll.asLong("foo", 1l)); + Assert.assertEquals(1556195014000l, + UtilAll.asLong("1556195014000", 1l)); + } + + @Test + public void testFormatDateIfInputNotNull() { + Assert.assertEquals("2019-04-25 13:23:34", + UtilAll.formatDate( + new Date(1556195014000l), "yyyy-MM-dd HH:mm:ss")); + } + + @Test + public void testParseDateIfInputNotNull() { + Assert.assertNull(UtilAll.parseDate("2019-04-25 13:23:34", "")); + + Assert.assertEquals(new Date(1556195014000l), + UtilAll.parseDate( + "2019-04-25 13:23:34", "yyyy-MM-dd HH:mm:ss")); + } + + @Test + public void testResponseCode2StringIfInputNotNull() { + Assert.assertEquals("2", UtilAll.responseCode2String(2)); + } + + @Test + public void testFrontStringAtLeastIfInputNotNull() { + Assert.assertEquals("", UtilAll.frontStringAtLeast("", 2)); + Assert.assertEquals("fo", UtilAll.frontStringAtLeast("foo", 2)); + } + + @Test + public void testIsBlankIfInputNullOrEmpty() { + Assert.assertTrue(UtilAll.isBlank("")); + Assert.assertTrue(UtilAll.isBlank(" ")); + Assert.assertTrue(UtilAll.isBlank(null)); + } + + @Test + public void testIsBlankIfInputNotNull() { + Assert.assertFalse(UtilAll.isBlank("foo")); + } + + @Test + public void testJstackIfNotNull() { + Assert.assertNotNull(UtilAll.jstack()); + } + + @Test + public void testIsInternalIPIfInputNotNull() { + Assert.assertFalse(UtilAll.isInternalIP(new byte[]{1, 1, 1, 1})); + + Assert.assertTrue(UtilAll.isInternalIP(new byte[]{10, 1, 2, 3})); + Assert.assertTrue(UtilAll.isInternalIP( + new byte[]{(byte) 172, 16, 2, 3})); + Assert.assertTrue(UtilAll.isInternalIP( + new byte[]{(byte) 192, (byte) 168, 2, 3})); + } + + @Test + public void testIsInternalIPIfLengthLessThan4() { + thrown.expect(RuntimeException.class); + UtilAll.isInternalIP(new byte[0]); + // Method is not expected to return due to exception thrown + } + + @Test + public void testIpToIPv4StrIfInputNotNull() { + Assert.assertNull(UtilAll.ipToIPv4Str(new byte[]{1})); + + Assert.assertEquals("10.1.2.3", + UtilAll.ipToIPv4Str(new byte[]{10, 1, 2, 3})); + Assert.assertEquals("172.16.2.3", + UtilAll.ipToIPv4Str(new byte[]{(byte) 172, 16, 2, 3})); + Assert.assertEquals("192.168.2.3", + UtilAll.ipToIPv4Str( + new byte[]{(byte) 192, (byte) 168, 2, 3})); + } + + @Test + public void testGetIP1IfCurrentIPNotNull() { + InetAddress inetAddress = PowerMockito.mock(InetAddress.class); + PowerMockito.when(inetAddress.getAddress()) + .thenReturn(new byte[]{10, 1, 2, 3}); + + Assert.assertArrayEquals(new byte[]{-84, 17, 0, 1}, UtilAll.getIP()); + } + + @Test + public void testGetIP2IfCurrentIPNotNull() { + InetAddress inetAddress = PowerMockito.mock(InetAddress.class); + PowerMockito.when(inetAddress.getAddress()).thenReturn(new byte[]{1}); + + Assert.assertArrayEquals(new byte[]{-84, 17, 0, 1}, UtilAll.getIP()); + } + + @PrepareForTest({UtilAll.class, Enumeration.class, NetworkInterface.class}) + @Test + public void testGetIPOutputRuntimeException() throws Exception { + PowerMockito.mockStatic(NetworkInterface.class); + Enumeration enumeration = PowerMockito.mock(Enumeration.class); + PowerMockito.when(enumeration.hasMoreElements()).thenReturn(false); + PowerMockito.doReturn(enumeration).when(NetworkInterface.class); + NetworkInterface.getNetworkInterfaces(); + + thrown.expect(RuntimeException.class); + UtilAll.getIP(); + // Method is not expected to return due to exception thrown + } +} diff --git a/pom.xml b/pom.xml index 3714eb344e..ab9e88cfcd 100644 --- a/pom.xml +++ b/pom.xml @@ -439,6 +439,17 @@ 3.10.0 test + + org.powermock + powermock-api-mockito2 + 1.7.0RC2 + + + org.powermock + powermock-module-junit4 + 1.7.0RC2 + test + From f37dc187f244cca1609ae01f81e63343040656aa Mon Sep 17 00:00:00 2001 From: Lunaticf <18210240128@fudan.edu.cn> Date: Wed, 19 May 2021 19:43:42 +0800 Subject: [PATCH 27/65] Fix some code style issue and remove redundant code (#1378) Co-authored-by: center-liu --- .../acl/plain/RemoteAddressStrategyFactory.java | 2 +- .../rocketmq/broker/mqtrace/SendMessageHook.java | 6 +++--- .../org/apache/rocketmq/broker/BrokerOuterAPITest.java | 2 +- .../apache/rocketmq/common/hook/FilterCheckHook.java | 4 ++-- .../rocketmq/filter/parser/SimpleCharStream.java | 8 ++++---- .../java/org/apache/rocketmq/logging/inner/Level.java | 1 - .../java/org/apache/rocketmq/test/util/FileUtil.java | 2 +- .../java/org/apache/rocketmq/test/util/MQWait.java | 2 +- .../java/org/apache/rocketmq/test/util/RandomUtil.java | 10 +++++----- .../org/apache/rocketmq/test/util/RandomUtils.java | 8 ++++---- .../org/apache/rocketmq/test/util/VerifyUtils.java | 2 +- .../rocketmq/test/client/consumer/tag/MulTagSubIT.java | 6 +++--- .../consumer/tag/TagMessageWithMulConsumerIT.java | 6 +++--- .../tools/command/cluster/CLusterSendMsgRTCommand.java | 2 +- 14 files changed, 30 insertions(+), 31 deletions(-) diff --git a/acl/src/main/java/org/apache/rocketmq/acl/plain/RemoteAddressStrategyFactory.java b/acl/src/main/java/org/apache/rocketmq/acl/plain/RemoteAddressStrategyFactory.java index 0c3548eeb5..6ec90ee3fa 100644 --- a/acl/src/main/java/org/apache/rocketmq/acl/plain/RemoteAddressStrategyFactory.java +++ b/acl/src/main/java/org/apache/rocketmq/acl/plain/RemoteAddressStrategyFactory.java @@ -193,7 +193,7 @@ public class RemoteAddressStrategyFactory { throw new AclException(String.format("RangeRemoteAddressStrategy netaddress examine scope Exception start is %s , end is %s", start, end)); } } - return this.end > 0 ? true : false; + return this.end > 0; } private boolean ipv6Analysis(String[] strArray, int index) { diff --git a/broker/src/main/java/org/apache/rocketmq/broker/mqtrace/SendMessageHook.java b/broker/src/main/java/org/apache/rocketmq/broker/mqtrace/SendMessageHook.java index a74b6d66ce..a89bace193 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/mqtrace/SendMessageHook.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/mqtrace/SendMessageHook.java @@ -17,9 +17,9 @@ package org.apache.rocketmq.broker.mqtrace; public interface SendMessageHook { - public String hookName(); + String hookName(); - public void sendMessageBefore(final SendMessageContext context); + void sendMessageBefore(final SendMessageContext context); - public void sendMessageAfter(final SendMessageContext context); + void sendMessageAfter(final SendMessageContext context); } diff --git a/broker/src/test/java/org/apache/rocketmq/broker/BrokerOuterAPITest.java b/broker/src/test/java/org/apache/rocketmq/broker/BrokerOuterAPITest.java index 68d58ef440..9ea1eeee31 100644 --- a/broker/src/test/java/org/apache/rocketmq/broker/BrokerOuterAPITest.java +++ b/broker/src/test/java/org/apache/rocketmq/broker/BrokerOuterAPITest.java @@ -124,7 +124,7 @@ public class BrokerOuterAPITest { boolean success = Iterables.any(booleanList, new Predicate() { public boolean apply(Boolean input) { - return input ? true : false; + return input; } }); diff --git a/common/src/main/java/org/apache/rocketmq/common/hook/FilterCheckHook.java b/common/src/main/java/org/apache/rocketmq/common/hook/FilterCheckHook.java index e72fb82601..f57df26d6f 100644 --- a/common/src/main/java/org/apache/rocketmq/common/hook/FilterCheckHook.java +++ b/common/src/main/java/org/apache/rocketmq/common/hook/FilterCheckHook.java @@ -20,7 +20,7 @@ package org.apache.rocketmq.common.hook; import java.nio.ByteBuffer; public interface FilterCheckHook { - public String hookName(); + String hookName(); - public boolean isFilterMatched(final boolean isUnitMode, final ByteBuffer byteBuffer); + boolean isFilterMatched(final boolean isUnitMode, final ByteBuffer byteBuffer); } diff --git a/filter/src/main/java/org/apache/rocketmq/filter/parser/SimpleCharStream.java b/filter/src/main/java/org/apache/rocketmq/filter/parser/SimpleCharStream.java index 53f7e1c297..c10250e3bb 100644 --- a/filter/src/main/java/org/apache/rocketmq/filter/parser/SimpleCharStream.java +++ b/filter/src/main/java/org/apache/rocketmq/filter/parser/SimpleCharStream.java @@ -36,8 +36,8 @@ public class SimpleCharStream { * Position in buffer. */ public int bufpos = -1; - protected int bufline[]; - protected int bufcolumn[]; + protected int[] bufline; + protected int[] bufcolumn; protected int column = 0; protected int line = 1; @@ -62,8 +62,8 @@ public class SimpleCharStream { protected void ExpandBuff(boolean wrapAround) { char[] newbuffer = new char[bufsize + 2048]; - int newbufline[] = new int[bufsize + 2048]; - int newbufcolumn[] = new int[bufsize + 2048]; + int[] newbufline = new int[bufsize + 2048]; + int[] newbufcolumn = new int[bufsize + 2048]; try { if (wrapAround) { diff --git a/logging/src/main/java/org/apache/rocketmq/logging/inner/Level.java b/logging/src/main/java/org/apache/rocketmq/logging/inner/Level.java index 487682cd44..0dc81d74e6 100755 --- a/logging/src/main/java/org/apache/rocketmq/logging/inner/Level.java +++ b/logging/src/main/java/org/apache/rocketmq/logging/inner/Level.java @@ -116,7 +116,6 @@ public class Level implements Serializable { if (s.equals(OFF_NAME)) { return Level.OFF; } - if (s.equals(INFO_NAME)) { return Level.INFO; } diff --git a/test/src/main/java/org/apache/rocketmq/test/util/FileUtil.java b/test/src/main/java/org/apache/rocketmq/test/util/FileUtil.java index 44db782b5c..e08967eb91 100644 --- a/test/src/main/java/org/apache/rocketmq/test/util/FileUtil.java +++ b/test/src/main/java/org/apache/rocketmq/test/util/FileUtil.java @@ -33,7 +33,7 @@ public class FileUtil { this.fileName = fileName; } - public static void main(String args[]) { + public static void main(String[] args) { String filePath = FileUtil.class.getResource("/").getPath(); String fileName = "test.txt"; FileUtil fileUtil = new FileUtil(filePath, fileName); diff --git a/test/src/main/java/org/apache/rocketmq/test/util/MQWait.java b/test/src/main/java/org/apache/rocketmq/test/util/MQWait.java index 6edeecadb2..0c24427d92 100644 --- a/test/src/main/java/org/apache/rocketmq/test/util/MQWait.java +++ b/test/src/main/java/org/apache/rocketmq/test/util/MQWait.java @@ -76,7 +76,7 @@ public class MQWait { } } - public static void main(String args[]) { + public static void main(String[] args) { long start = System.currentTimeMillis(); MQWait.setCondition(new Condition() { diff --git a/test/src/main/java/org/apache/rocketmq/test/util/RandomUtil.java b/test/src/main/java/org/apache/rocketmq/test/util/RandomUtil.java index 1c2bdac305..41cacb684c 100644 --- a/test/src/main/java/org/apache/rocketmq/test/util/RandomUtil.java +++ b/test/src/main/java/org/apache/rocketmq/test/util/RandomUtil.java @@ -100,14 +100,14 @@ public final class RandomUtil { return n + res % (m - n); } - private static char getChar(int arg[]) { + private static char getChar(int[] arg) { int size = arg.length; int c = rd.nextInt(size / 2); c = c * 2; return (char) (getIntegerBetween(arg[c], arg[c + 1])); } - private static String getString(int n, int arg[]) { + private static String getString(int n, int[] arg) { StringBuilder res = new StringBuilder(); for (int i = 0; i < n; i++) { res.append(getChar(arg)); @@ -116,17 +116,17 @@ public final class RandomUtil { } public static String getStringWithCharacter(int n) { - int arg[] = new int[] {'a', 'z' + 1, 'A', 'Z' + 1}; + int[] arg = new int[] {'a', 'z' + 1, 'A', 'Z' + 1}; return getString(n, arg); } public static String getStringWithNumber(int n) { - int arg[] = new int[] {'0', '9' + 1}; + int[] arg = new int[] {'0', '9' + 1}; return getString(n, arg); } public static String getStringWithNumAndCha(int n) { - int arg[] = new int[] {'a', 'z' + 1, 'A', 'Z' + 1, '0', '9' + 1}; + int[] arg = new int[] {'a', 'z' + 1, 'A', 'Z' + 1, '0', '9' + 1}; return getString(n, arg); } diff --git a/test/src/main/java/org/apache/rocketmq/test/util/RandomUtils.java b/test/src/main/java/org/apache/rocketmq/test/util/RandomUtils.java index 9eca28bbe7..3f71176d6f 100644 --- a/test/src/main/java/org/apache/rocketmq/test/util/RandomUtils.java +++ b/test/src/main/java/org/apache/rocketmq/test/util/RandomUtils.java @@ -45,16 +45,16 @@ public class RandomUtils { } public static String getStringWithNumber(int n) { - int arg[] = new int[] {'0', '9' + 1}; + int[] arg = new int[] {'0', '9' + 1}; return getString(n, arg); } public static String getStringWithCharacter(int n) { - int arg[] = new int[] {'a', 'z' + 1, 'A', 'Z' + 1}; + int[] arg = new int[] {'a', 'z' + 1, 'A', 'Z' + 1}; return getString(n, arg); } - private static String getString(int n, int arg[]) { + private static String getString(int n, int[] arg) { StringBuilder res = new StringBuilder(); for (int i = 0; i < n; i++) { res.append(getChar(arg)); @@ -62,7 +62,7 @@ public class RandomUtils { return res.toString(); } - private static char getChar(int arg[]) { + private static char getChar(int[] arg) { int size = arg.length; int c = rd.nextInt(size / 2); c = c * 2; diff --git a/test/src/main/java/org/apache/rocketmq/test/util/VerifyUtils.java b/test/src/main/java/org/apache/rocketmq/test/util/VerifyUtils.java index 965d2ee670..69bfd8f7d1 100644 --- a/test/src/main/java/org/apache/rocketmq/test/util/VerifyUtils.java +++ b/test/src/main/java/org/apache/rocketmq/test/util/VerifyUtils.java @@ -140,7 +140,7 @@ public class VerifyUtils { return rtExpect; } - public static void main(String args[]) { + public static void main(String[] args) { verifyBalance(400, 0.1f, 230, 190); } } diff --git a/test/src/test/java/org/apache/rocketmq/test/client/consumer/tag/MulTagSubIT.java b/test/src/test/java/org/apache/rocketmq/test/client/consumer/tag/MulTagSubIT.java index 564da5ceaf..0edbdbe2e3 100644 --- a/test/src/test/java/org/apache/rocketmq/test/client/consumer/tag/MulTagSubIT.java +++ b/test/src/test/java/org/apache/rocketmq/test/client/consumer/tag/MulTagSubIT.java @@ -92,7 +92,7 @@ public class MulTagSubIT extends BaseConf { @Test public void testSubTwoTabAndMatchTwo() { - String tags[] = {"jueyin1", "jueyin2"}; + String[] tags = {"jueyin1", "jueyin2"}; String subExpress = String.format("%s||%s", tags[0], tags[1]); int msgSize = 10; @@ -113,7 +113,7 @@ public class MulTagSubIT extends BaseConf { @Test public void testSubThreeTabAndMatchTwo() { - String tags[] = {"jueyin1", "jueyin2", "jueyin3"}; + String[] tags = {"jueyin1", "jueyin2", "jueyin3"}; String subExpress = String.format("%s||%s", tags[0], tags[1]); int msgSize = 10; @@ -135,7 +135,7 @@ public class MulTagSubIT extends BaseConf { @Test public void testNoMatch() { - String tags[] = {"jueyin1", "jueyin2", "jueyin3"}; + String[] tags = {"jueyin1", "jueyin2", "jueyin3"}; String subExpress = "no_match"; int msgSize = 10; diff --git a/test/src/test/java/org/apache/rocketmq/test/client/consumer/tag/TagMessageWithMulConsumerIT.java b/test/src/test/java/org/apache/rocketmq/test/client/consumer/tag/TagMessageWithMulConsumerIT.java index 31321975b5..8de1b7d4e0 100644 --- a/test/src/test/java/org/apache/rocketmq/test/client/consumer/tag/TagMessageWithMulConsumerIT.java +++ b/test/src/test/java/org/apache/rocketmq/test/client/consumer/tag/TagMessageWithMulConsumerIT.java @@ -84,7 +84,7 @@ public class TagMessageWithMulConsumerIT extends BaseConf { @Test public void testSendMessagesWithTwoTag() { - String tags[] = {"jueyin1", "jueyin2"}; + String[] tags = {"jueyin1", "jueyin2"}; int msgSize = 10; TagMessage tagMessage = new TagMessage(tags, topic, msgSize); @@ -113,7 +113,7 @@ public class TagMessageWithMulConsumerIT extends BaseConf { @Test public void testTwoConsumerOneMatchOneOtherMatchAll() { - String tags[] = {"jueyin1", "jueyin2"}; + String[] tags = {"jueyin1", "jueyin2"}; String sub1 = String.format("%s||%s", tags[0], tags[1]); String sub2 = String.format("%s|| noExist", tags[0]); int msgSize = 10; @@ -144,7 +144,7 @@ public class TagMessageWithMulConsumerIT extends BaseConf { @Test public void testSubKindsOf() { - String tags[] = {"jueyin1", "jueyin2"}; + String[] tags = {"jueyin1", "jueyin2"}; String sub1 = String.format("%s||%s", tags[0], tags[1]); String sub2 = String.format("%s|| noExist", tags[0]); String sub3 = tags[0]; diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/cluster/CLusterSendMsgRTCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/cluster/CLusterSendMsgRTCommand.java index 5038123561..872a130d9f 100644 --- a/tools/src/main/java/org/apache/rocketmq/tools/command/cluster/CLusterSendMsgRTCommand.java +++ b/tools/src/main/java/org/apache/rocketmq/tools/command/cluster/CLusterSendMsgRTCommand.java @@ -38,7 +38,7 @@ import org.apache.rocketmq.tools.command.SubCommandException; public class CLusterSendMsgRTCommand implements SubCommand { - public static void main(String args[]) { + public static void main(String[] args) { } @Override From 305b19589dc61beb8ee668d804459800bb71a1b5 Mon Sep 17 00:00:00 2001 From: Chen Long Date: Wed, 19 May 2021 19:46:53 +0800 Subject: [PATCH 28/65] fix(script): windows can not startup when acl enable(#1393) --- distribution/bin/runbroker.cmd | 2 +- distribution/bin/runserver.cmd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/distribution/bin/runbroker.cmd b/distribution/bin/runbroker.cmd index eab7e30f9d..753a11e9a4 100644 --- a/distribution/bin/runbroker.cmd +++ b/distribution/bin/runbroker.cmd @@ -36,7 +36,7 @@ set "JAVA_OPT=%JAVA_OPT% -XX:-OmitStackTraceInFastThrow" set "JAVA_OPT=%JAVA_OPT% -XX:+AlwaysPreTouch" set "JAVA_OPT=%JAVA_OPT% -XX:MaxDirectMemorySize=15g" set "JAVA_OPT=%JAVA_OPT% -XX:-UseLargePages -XX:-UseBiasedLocking" -set "JAVA_OPT=%JAVA_OPT% -Djava.ext.dirs=%BASE_DIR%lib" +set "JAVA_OPT=%JAVA_OPT% -Djava.ext.dirs=%BASE_DIR%lib;%JAVA_HOME%\jre\lib\ext" set "JAVA_OPT=%JAVA_OPT% -cp %CLASSPATH%" "%JAVA%" %JAVA_OPT% %* \ No newline at end of file diff --git a/distribution/bin/runserver.cmd b/distribution/bin/runserver.cmd index 48e32bf2d7..76865f7a16 100644 --- a/distribution/bin/runserver.cmd +++ b/distribution/bin/runserver.cmd @@ -31,7 +31,7 @@ set "JAVA_OPT=%JAVA_OPT% -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollect set "JAVA_OPT=%JAVA_OPT% -verbose:gc -Xloggc:"%USERPROFILE%\rmq_srv_gc.log" -XX:+PrintGCDetails" set "JAVA_OPT=%JAVA_OPT% -XX:-OmitStackTraceInFastThrow" set "JAVA_OPT=%JAVA_OPT% -XX:-UseLargePages" -set "JAVA_OPT=%JAVA_OPT% -Djava.ext.dirs=%BASE_DIR%lib" +set "JAVA_OPT=%JAVA_OPT% -Djava.ext.dirs=%BASE_DIR%lib;%JAVA_HOME%\jre\lib\ext" set "JAVA_OPT=%JAVA_OPT% -cp "%CLASSPATH%"" "%JAVA%" %JAVA_OPT% %* \ No newline at end of file From f799f17dfcabf9c450552b78f81589eae2767c88 Mon Sep 17 00:00:00 2001 From: Heng Du Date: Thu, 20 May 2021 10:34:45 +0800 Subject: [PATCH 29/65] Revert "Add unit tests for org.apache.rocketmq.common.UtilAll (#1249)" This reverts commit 18dadebef67cba7e0fe3dc59adcb2fc42c22273f. --- .../apache/rocketmq/common/UtilAllTests.java | 381 ------------------ pom.xml | 11 - 2 files changed, 392 deletions(-) delete mode 100644 common/src/test/java/org/apache/rocketmq/common/UtilAllTests.java diff --git a/common/src/test/java/org/apache/rocketmq/common/UtilAllTests.java b/common/src/test/java/org/apache/rocketmq/common/UtilAllTests.java deleted file mode 100644 index d2aa6ef19c..0000000000 --- a/common/src/test/java/org/apache/rocketmq/common/UtilAllTests.java +++ /dev/null @@ -1,381 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.rocketmq.common; - -import java.io.File; -import java.io.IOException; -import java.lang.management.ManagementFactory; -import java.lang.management.RuntimeMXBean; -import java.net.InetAddress; -import java.net.NetworkInterface; -import java.util.Calendar; -import java.util.Date; -import java.util.Enumeration; - -import org.junit.Assert; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.powermock.api.mockito.PowerMockito; -import org.powermock.core.classloader.annotations.PowerMockIgnore; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; - -@RunWith(PowerMockRunner.class) -@PowerMockIgnore("javax.management.*") -public class UtilAllTests { - - @Rule - private TemporaryFolder folder = new TemporaryFolder(); - - @Rule - private ExpectedException thrown = ExpectedException.none(); - - @PrepareForTest({RuntimeMXBean.class, - UtilAll.class, ManagementFactory.class}) - @Test - public void testGetPidIfNotNull() { - PowerMockito.mockStatic(ManagementFactory.class); - RuntimeMXBean runtimeMXBean = PowerMockito.mock(RuntimeMXBean.class); - PowerMockito.when(runtimeMXBean.getName()) - .thenReturn("40@abc.com"); - PowerMockito.doReturn(runtimeMXBean).when(ManagementFactory.class); - ManagementFactory.getRuntimeMXBean(); - - Assert.assertEquals(40, UtilAll.getPid()); - } - - @PrepareForTest({RuntimeMXBean.class, - UtilAll.class, ManagementFactory.class}) - @Test - public void testGetPidIfNull() { - PowerMockito.mockStatic(ManagementFactory.class); - RuntimeMXBean runtimeMXBean = PowerMockito.mock(RuntimeMXBean.class); - PowerMockito.when(runtimeMXBean.getName()).thenReturn(null); - PowerMockito.doReturn(runtimeMXBean).when(ManagementFactory.class); - ManagementFactory.getRuntimeMXBean(); - - Assert.assertEquals(-1, UtilAll.getPid()); - } - - @PrepareForTest({UtilAll.class}) - @Test - public void testCurrentStackTraceIfNotNull() { - PowerMockito.mockStatic(Thread.class); - StackTraceElement stackTraceElement = - new StackTraceElement("Bar", "foo", "baz", 101); - PowerMockito.when(Thread.currentThread().getStackTrace()) - .thenReturn(new StackTraceElement[]{stackTraceElement}); - - Assert.assertEquals("\n\tBar.foo(baz:101)", - UtilAll.currentStackTrace()); - } - - @Test - public void testOffset2FileNameIfInputNotNull() { - Assert.assertEquals("00000000000000000002", - UtilAll.offset2FileName(2l)); - Assert.assertEquals("00000001558787014000", - UtilAll.offset2FileName(1558787014000l)); - } - - @PrepareForTest({UtilAll.class, System.class}) - @Test - public void testComputeEclipseTimeMillisecondsIfInputNotNull() { - PowerMockito.mockStatic(System.class); - PowerMockito.when(System.currentTimeMillis()) - .thenReturn(1558787014000l); - - Assert.assertEquals(2592000000l, - UtilAll.computeEclipseTimeMilliseconds(1556195014000l)); - } - - @PrepareForTest({Calendar.class, UtilAll.class}) - @Test - public void testIsItTimeToDoIfCurrentSetTime() { - Calendar calendar = Calendar.getInstance(); - calendar.setTimeInMillis(1558787014000l); - PowerMockito.mockStatic(Calendar.class); - PowerMockito.when(Calendar.getInstance()).thenReturn(calendar); - - Assert.assertTrue(UtilAll.isItTimeToDo("13;12;14")); - - Assert.assertFalse(UtilAll.isItTimeToDo("00;00;00;")); - } - - @PrepareForTest({UtilAll.class, System.class}) - @Test - public void testTimeMillisToHumanStringIfCurrentTimeNotNull() { - PowerMockito.mockStatic(System.class); - PowerMockito.when(System.currentTimeMillis()) - .thenReturn(1558787014000l); - - Assert.assertEquals("20190525132334000", - UtilAll.timeMillisToHumanString()); - } - - @Test - public void testTimeMillisToHumanStringLongIfInputNotNull() { - Assert.assertEquals("20190525132334000", - UtilAll.timeMillisToHumanString(1558787014000l)); - } - - @PrepareForTest({System.class, UtilAll.class}) - @Test - public void testComputNextMorningTimeMillisIfCurrentTimeNotNull() { - PowerMockito.mockStatic(System.class); - PowerMockito.when(System.currentTimeMillis()) - .thenReturn(1515585600000L); - - Assert.assertEquals(1515628800000l, - UtilAll.computNextMorningTimeMillis()); - } - - @PrepareForTest({System.class, UtilAll.class}) - @Test - public void testComputNextMinutesTimeMillisIfCurrentTimeNotNull() { - PowerMockito.mockStatic(System.class); - PowerMockito.when(System.currentTimeMillis()) - .thenReturn(1515585600000L); - - Assert.assertEquals(1515585660000l, - UtilAll.computNextMinutesTimeMillis()); - } - - @PrepareForTest({System.class, UtilAll.class}) - @Test - public void testComputNextHourTimeMillisIfCurrentTimeNotNull() { - PowerMockito.mockStatic(System.class); - PowerMockito.when(System.currentTimeMillis()) - .thenReturn(1515585600000L); - - Assert.assertEquals(1515589200000l, - UtilAll.computNextHourTimeMillis()); - } - - @PrepareForTest({UtilAll.class, System.class}) - @Test - public void testComputNextHalfHourTimeMillisIfCurrentTimeNotNull() { - PowerMockito.mockStatic(System.class); - PowerMockito.when(System.currentTimeMillis()) - .thenReturn(1515585600000L); - - Assert.assertEquals(1515591000000l, - UtilAll.computNextHalfHourTimeMillis()); - } - - @Test - public void testTimeMillisToHumanString2IfInputNotNull() { - Assert.assertEquals("2019-04-25 13:23:34,000", - UtilAll.timeMillisToHumanString2(1556195014000l)); - } - - @Test - public void testTimeMillisToHumanString3IfInputNotNull() { - Assert.assertEquals("20190425132334", - UtilAll.timeMillisToHumanString3(1556195014000l)); - } - - @Test - public void testGetDiskPartitionSpaceUsedPercentIfFileDoesNotExist() { - Assert.assertEquals(-1, - UtilAll.getDiskPartitionSpaceUsedPercent(""), 0); - Assert.assertEquals(-1, - UtilAll.getDiskPartitionSpaceUsedPercent(null), 0); - Assert.assertEquals(-1, - UtilAll.getDiskPartitionSpaceUsedPercent("foo"), 0); - } - - @Test - public void testGetDiskPartitionSpaceUsedPercentIfFileExists() - throws IOException { - File file = folder.newFile("foo.txt"); - try { - Assert.assertNotNull( - UtilAll.getDiskPartitionSpaceUsedPercent(file.getPath())); - } finally { - file.delete(); - folder.delete(); - } - } - - @Test - public void testCrc32IfInputNull() { - Assert.assertEquals(0, UtilAll.crc32(null)); - Assert.assertEquals(0, UtilAll.crc32(new byte[]{})); - } - - @Test - public void testCrc32IfInputNotNull() { - Assert.assertEquals(1438416925, UtilAll.crc32(new byte[]{1, 2, 3})); - } - - @Test - public void testBytes2stringIfInputArrayNotEmpty() { - Assert.assertEquals("010203", - UtilAll.bytes2string(new byte[]{1, 2, 3})); - } - - @Test - public void testBytes2stringIfInputEmptyArray() { - Assert.assertEquals("", UtilAll.bytes2string(new byte[0])); - } - - @Test - public void testString2bytesIfInputNullOrEmptyString() { - Assert.assertNull(UtilAll.string2bytes("")); - Assert.assertNull(UtilAll.string2bytes(null)); - } - - @Test - public void testString2bytesIfInputNotNullOrEmptyString() { - Assert.assertArrayEquals(new byte[]{-1}, UtilAll.string2bytes("foo")); - } - - @Test - public void testUncompressIfInputNotNull() throws IOException { - byte[] by = new byte[]{120, 1, 1, 3, 0, -4, -1, 1, 2, 3, 0, 13, 0, 7}; - Assert.assertArrayEquals(new byte[]{1, 2, 3}, UtilAll.uncompress(by)); - } - - @Test - public void testCompressIfInputNotNull() throws IOException { - byte[] by = new byte[]{120, 1, 1, 3, 0, -4, -1, 1, 2, 3, 0, 13, 0, 7}; - Assert.assertArrayEquals(by, UtilAll.compress(new byte[]{1, 2, 3}, 0)); - } - - @Test - public void testAsIntIfInputNotNull() { - Assert.assertEquals(1, UtilAll.asInt("foo", 1)); - Assert.assertEquals(123, UtilAll.asInt("123", 1)); - } - - @Test - public void testAsLongIfInputNotNull() { - Assert.assertEquals(1l, UtilAll.asLong("foo", 1l)); - Assert.assertEquals(1556195014000l, - UtilAll.asLong("1556195014000", 1l)); - } - - @Test - public void testFormatDateIfInputNotNull() { - Assert.assertEquals("2019-04-25 13:23:34", - UtilAll.formatDate( - new Date(1556195014000l), "yyyy-MM-dd HH:mm:ss")); - } - - @Test - public void testParseDateIfInputNotNull() { - Assert.assertNull(UtilAll.parseDate("2019-04-25 13:23:34", "")); - - Assert.assertEquals(new Date(1556195014000l), - UtilAll.parseDate( - "2019-04-25 13:23:34", "yyyy-MM-dd HH:mm:ss")); - } - - @Test - public void testResponseCode2StringIfInputNotNull() { - Assert.assertEquals("2", UtilAll.responseCode2String(2)); - } - - @Test - public void testFrontStringAtLeastIfInputNotNull() { - Assert.assertEquals("", UtilAll.frontStringAtLeast("", 2)); - Assert.assertEquals("fo", UtilAll.frontStringAtLeast("foo", 2)); - } - - @Test - public void testIsBlankIfInputNullOrEmpty() { - Assert.assertTrue(UtilAll.isBlank("")); - Assert.assertTrue(UtilAll.isBlank(" ")); - Assert.assertTrue(UtilAll.isBlank(null)); - } - - @Test - public void testIsBlankIfInputNotNull() { - Assert.assertFalse(UtilAll.isBlank("foo")); - } - - @Test - public void testJstackIfNotNull() { - Assert.assertNotNull(UtilAll.jstack()); - } - - @Test - public void testIsInternalIPIfInputNotNull() { - Assert.assertFalse(UtilAll.isInternalIP(new byte[]{1, 1, 1, 1})); - - Assert.assertTrue(UtilAll.isInternalIP(new byte[]{10, 1, 2, 3})); - Assert.assertTrue(UtilAll.isInternalIP( - new byte[]{(byte) 172, 16, 2, 3})); - Assert.assertTrue(UtilAll.isInternalIP( - new byte[]{(byte) 192, (byte) 168, 2, 3})); - } - - @Test - public void testIsInternalIPIfLengthLessThan4() { - thrown.expect(RuntimeException.class); - UtilAll.isInternalIP(new byte[0]); - // Method is not expected to return due to exception thrown - } - - @Test - public void testIpToIPv4StrIfInputNotNull() { - Assert.assertNull(UtilAll.ipToIPv4Str(new byte[]{1})); - - Assert.assertEquals("10.1.2.3", - UtilAll.ipToIPv4Str(new byte[]{10, 1, 2, 3})); - Assert.assertEquals("172.16.2.3", - UtilAll.ipToIPv4Str(new byte[]{(byte) 172, 16, 2, 3})); - Assert.assertEquals("192.168.2.3", - UtilAll.ipToIPv4Str( - new byte[]{(byte) 192, (byte) 168, 2, 3})); - } - - @Test - public void testGetIP1IfCurrentIPNotNull() { - InetAddress inetAddress = PowerMockito.mock(InetAddress.class); - PowerMockito.when(inetAddress.getAddress()) - .thenReturn(new byte[]{10, 1, 2, 3}); - - Assert.assertArrayEquals(new byte[]{-84, 17, 0, 1}, UtilAll.getIP()); - } - - @Test - public void testGetIP2IfCurrentIPNotNull() { - InetAddress inetAddress = PowerMockito.mock(InetAddress.class); - PowerMockito.when(inetAddress.getAddress()).thenReturn(new byte[]{1}); - - Assert.assertArrayEquals(new byte[]{-84, 17, 0, 1}, UtilAll.getIP()); - } - - @PrepareForTest({UtilAll.class, Enumeration.class, NetworkInterface.class}) - @Test - public void testGetIPOutputRuntimeException() throws Exception { - PowerMockito.mockStatic(NetworkInterface.class); - Enumeration enumeration = PowerMockito.mock(Enumeration.class); - PowerMockito.when(enumeration.hasMoreElements()).thenReturn(false); - PowerMockito.doReturn(enumeration).when(NetworkInterface.class); - NetworkInterface.getNetworkInterfaces(); - - thrown.expect(RuntimeException.class); - UtilAll.getIP(); - // Method is not expected to return due to exception thrown - } -} diff --git a/pom.xml b/pom.xml index ab9e88cfcd..3714eb344e 100644 --- a/pom.xml +++ b/pom.xml @@ -439,17 +439,6 @@ 3.10.0 test - - org.powermock - powermock-api-mockito2 - 1.7.0RC2 - - - org.powermock - powermock-module-junit4 - 1.7.0RC2 - test - From 129d7e2b387ca62e887b7b34972616e1845ab065 Mon Sep 17 00:00:00 2001 From: von gosling Date: Thu, 20 May 2021 10:34:51 +0800 Subject: [PATCH 30/65] Revert "[ISSUE #2865] Batch message send bug fix (#2866)" (#2912) This reverts commit 16694485a7e111c601c0ec8c2a76a44cdd830067. --- .../store/dledger/DLedgerCommitLog.java | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/store/src/main/java/org/apache/rocketmq/store/dledger/DLedgerCommitLog.java b/store/src/main/java/org/apache/rocketmq/store/dledger/DLedgerCommitLog.java index 9241ffe423..9a6e7a78a1 100644 --- a/store/src/main/java/org/apache/rocketmq/store/dledger/DLedgerCommitLog.java +++ b/store/src/main/java/org/apache/rocketmq/store/dledger/DLedgerCommitLog.java @@ -426,18 +426,17 @@ public class DLedgerCommitLog extends CommitLog { AppendFuture dledgerFuture; EncodeResult encodeResult; - encodeResult = this.messageSerializer.serialize(msg); - if (encodeResult.status != AppendMessageStatus.PUT_OK) { - return new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, new AppendMessageResult(encodeResult.status)); - } - putMessageLock.lock(); //spin or ReentrantLock ,depending on store config long elapsedTimeInLock; long queueOffset; try { beginTimeInDledgerLock = this.defaultMessageStore.getSystemClock().now(); + encodeResult = this.messageSerializer.serialize(msg); queueOffset = getQueueOffsetByKey(encodeResult.queueOffsetKey, tranType); - encodeResult.setQueueOffsetKey(queueOffset, false); + encodeResult.setQueueOffsetKey(queueOffset); + if (encodeResult.status != AppendMessageStatus.PUT_OK) { + return new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, new AppendMessageResult(encodeResult.status)); + } AppendEntryRequest request = new AppendEntryRequest(); request.setGroup(dLedgerConfig.getGroup()); request.setRemoteId(dLedgerServer.getMemberState().getSelfId()); @@ -543,12 +542,6 @@ public class DLedgerCommitLog extends CommitLog { BatchAppendFuture dledgerFuture; EncodeResult encodeResult; - encodeResult = this.messageSerializer.serialize(messageExtBatch); - if (encodeResult.status != AppendMessageStatus.PUT_OK) { - return new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, new AppendMessageResult(encodeResult - .status)); - } - putMessageLock.lock(); //spin or ReentrantLock ,depending on store config msgIdBuilder.setLength(0); long elapsedTimeInLock; @@ -556,8 +549,12 @@ public class DLedgerCommitLog extends CommitLog { long msgNum = 0; try { beginTimeInDledgerLock = this.defaultMessageStore.getSystemClock().now(); - queueOffset = getQueueOffsetByKey(encodeResult.queueOffsetKey, tranType); - encodeResult.setQueueOffsetKey(queueOffset, true); + encodeResult = this.messageSerializer.serialize(messageExtBatch); + queueOffset = topicQueueTable.get(encodeResult.queueOffsetKey); + if (encodeResult.status != AppendMessageStatus.PUT_OK) { + return new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, new AppendMessageResult(encodeResult + .status)); + } BatchAppendEntryRequest request = new BatchAppendEntryRequest(); request.setGroup(dLedgerConfig.getGroup()); request.setRemoteId(dLedgerServer.getMemberState().getSelfId()); @@ -667,7 +664,7 @@ public class DLedgerCommitLog extends CommitLog { try { beginTimeInDledgerLock = this.defaultMessageStore.getSystemClock().now(); queueOffset = getQueueOffsetByKey(encodeResult.queueOffsetKey, tranType); - encodeResult.setQueueOffsetKey(queueOffset, false); + encodeResult.setQueueOffsetKey(queueOffset); AppendEntryRequest request = new AppendEntryRequest(); request.setGroup(dLedgerConfig.getGroup()); request.setRemoteId(dLedgerServer.getMemberState().getSelfId()); @@ -782,8 +779,7 @@ public class DLedgerCommitLog extends CommitLog { long msgNum = 0; try { beginTimeInDledgerLock = this.defaultMessageStore.getSystemClock().now(); - queueOffset = getQueueOffsetByKey(encodeResult.queueOffsetKey, tranType); - encodeResult.setQueueOffsetKey(queueOffset, true); + queueOffset = topicQueueTable.get(encodeResult.queueOffsetKey); BatchAppendEntryRequest request = new BatchAppendEntryRequest(); request.setGroup(dLedgerConfig.getGroup()); request.setRemoteId(dLedgerServer.getMemberState().getSelfId()); @@ -961,15 +957,8 @@ public class DLedgerCommitLog extends CommitLog { this.queueOffsetKey = queueOffsetKey; } - public void setQueueOffsetKey(long offset, boolean isBatch) { - if (!isBatch) { - this.data.putLong(MessageDecoder.QUEUE_OFFSET_POSITION, offset); - return; - } - - for (byte[] data : batchData) { - ByteBuffer.wrap(data).putLong(MessageDecoder.QUEUE_OFFSET_POSITION, offset++); - } + public void setQueueOffsetKey(long offset) { + data.putLong(MessageDecoder.QUEUE_OFFSET_POSITION, offset); } public byte[] getData() { @@ -988,6 +977,8 @@ public class DLedgerCommitLog extends CommitLog { // The maximum length of the message private final int maxMessageSize; + // Build Message Key + private final StringBuilder keyBuilder = new StringBuilder(); MessageSerializer(final int size) { this.maxMessageSize = size; @@ -1088,7 +1079,17 @@ public class DLedgerCommitLog extends CommitLog { } public EncodeResult serialize(final MessageExtBatch messageExtBatch) { - String key = messageExtBatch.getTopic() + "-" + messageExtBatch.getQueueId(); + keyBuilder.setLength(0); + keyBuilder.append(messageExtBatch.getTopic()); + keyBuilder.append('-'); + keyBuilder.append(messageExtBatch.getQueueId()); + String key = keyBuilder.toString(); + + Long queueOffset = DLedgerCommitLog.this.topicQueueTable.get(key); + if (null == queueOffset) { + queueOffset = 0L; + DLedgerCommitLog.this.topicQueueTable.put(key, queueOffset); + } int totalMsgLen = 0; ByteBuffer messagesByteBuff = messageExtBatch.wrap(); @@ -1153,7 +1154,7 @@ public class DLedgerCommitLog extends CommitLog { // 5 FLAG msgStoreItemMemory.putInt(flag); // 6 QUEUEOFFSET - msgStoreItemMemory.putLong(0L); + msgStoreItemMemory.putLong(queueOffset++); // 7 PHYSICALOFFSET msgStoreItemMemory.putLong(0); // 8 SYSFLAG @@ -1209,7 +1210,6 @@ public class DLedgerCommitLog extends CommitLog { this.sbr = sbr; } - @Override public synchronized void release() { super.release(); if (sbr != null) { From 432236e983ede0d40b13b0dd0128932afd09df01 Mon Sep 17 00:00:00 2001 From: von gosling Date: Thu, 20 May 2021 11:01:39 +0800 Subject: [PATCH 31/65] Revert 2912 & 2866 and merge to develop branch(#2915) * Delete README * docs(reblance design): modify typographical issues There is still more room for optimization, so one task is reserved here. * [ISSUE #2865] Batch message send bug fix (#2866) * [DLedgerCommitLog] Batch message send bug fix Signed-off-by: zhangyang * [DLedgerCommitLog] Batch message send bug fix2 Signed-off-by: zhangyang * Revert "[ISSUE #2865] Batch message send bug fix (#2866)" (#2912) This reverts commit 16694485a7e111c601c0ec8c2a76a44cdd830067. * Revert "Revert "[ISSUE #2865] Batch message send bug fix (#2866)" (#2912)" This reverts commit 129d7e2b387ca62e887b7b34972616e1845ab065. Co-authored-by: Git_Yang <30995057+Git-Yang@users.noreply.github.com> --- .../store/dledger/DLedgerCommitLog.java | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/store/src/main/java/org/apache/rocketmq/store/dledger/DLedgerCommitLog.java b/store/src/main/java/org/apache/rocketmq/store/dledger/DLedgerCommitLog.java index 9a6e7a78a1..9241ffe423 100644 --- a/store/src/main/java/org/apache/rocketmq/store/dledger/DLedgerCommitLog.java +++ b/store/src/main/java/org/apache/rocketmq/store/dledger/DLedgerCommitLog.java @@ -426,17 +426,18 @@ public class DLedgerCommitLog extends CommitLog { AppendFuture dledgerFuture; EncodeResult encodeResult; + encodeResult = this.messageSerializer.serialize(msg); + if (encodeResult.status != AppendMessageStatus.PUT_OK) { + return new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, new AppendMessageResult(encodeResult.status)); + } + putMessageLock.lock(); //spin or ReentrantLock ,depending on store config long elapsedTimeInLock; long queueOffset; try { beginTimeInDledgerLock = this.defaultMessageStore.getSystemClock().now(); - encodeResult = this.messageSerializer.serialize(msg); queueOffset = getQueueOffsetByKey(encodeResult.queueOffsetKey, tranType); - encodeResult.setQueueOffsetKey(queueOffset); - if (encodeResult.status != AppendMessageStatus.PUT_OK) { - return new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, new AppendMessageResult(encodeResult.status)); - } + encodeResult.setQueueOffsetKey(queueOffset, false); AppendEntryRequest request = new AppendEntryRequest(); request.setGroup(dLedgerConfig.getGroup()); request.setRemoteId(dLedgerServer.getMemberState().getSelfId()); @@ -542,6 +543,12 @@ public class DLedgerCommitLog extends CommitLog { BatchAppendFuture dledgerFuture; EncodeResult encodeResult; + encodeResult = this.messageSerializer.serialize(messageExtBatch); + if (encodeResult.status != AppendMessageStatus.PUT_OK) { + return new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, new AppendMessageResult(encodeResult + .status)); + } + putMessageLock.lock(); //spin or ReentrantLock ,depending on store config msgIdBuilder.setLength(0); long elapsedTimeInLock; @@ -549,12 +556,8 @@ public class DLedgerCommitLog extends CommitLog { long msgNum = 0; try { beginTimeInDledgerLock = this.defaultMessageStore.getSystemClock().now(); - encodeResult = this.messageSerializer.serialize(messageExtBatch); - queueOffset = topicQueueTable.get(encodeResult.queueOffsetKey); - if (encodeResult.status != AppendMessageStatus.PUT_OK) { - return new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, new AppendMessageResult(encodeResult - .status)); - } + queueOffset = getQueueOffsetByKey(encodeResult.queueOffsetKey, tranType); + encodeResult.setQueueOffsetKey(queueOffset, true); BatchAppendEntryRequest request = new BatchAppendEntryRequest(); request.setGroup(dLedgerConfig.getGroup()); request.setRemoteId(dLedgerServer.getMemberState().getSelfId()); @@ -664,7 +667,7 @@ public class DLedgerCommitLog extends CommitLog { try { beginTimeInDledgerLock = this.defaultMessageStore.getSystemClock().now(); queueOffset = getQueueOffsetByKey(encodeResult.queueOffsetKey, tranType); - encodeResult.setQueueOffsetKey(queueOffset); + encodeResult.setQueueOffsetKey(queueOffset, false); AppendEntryRequest request = new AppendEntryRequest(); request.setGroup(dLedgerConfig.getGroup()); request.setRemoteId(dLedgerServer.getMemberState().getSelfId()); @@ -779,7 +782,8 @@ public class DLedgerCommitLog extends CommitLog { long msgNum = 0; try { beginTimeInDledgerLock = this.defaultMessageStore.getSystemClock().now(); - queueOffset = topicQueueTable.get(encodeResult.queueOffsetKey); + queueOffset = getQueueOffsetByKey(encodeResult.queueOffsetKey, tranType); + encodeResult.setQueueOffsetKey(queueOffset, true); BatchAppendEntryRequest request = new BatchAppendEntryRequest(); request.setGroup(dLedgerConfig.getGroup()); request.setRemoteId(dLedgerServer.getMemberState().getSelfId()); @@ -957,8 +961,15 @@ public class DLedgerCommitLog extends CommitLog { this.queueOffsetKey = queueOffsetKey; } - public void setQueueOffsetKey(long offset) { - data.putLong(MessageDecoder.QUEUE_OFFSET_POSITION, offset); + public void setQueueOffsetKey(long offset, boolean isBatch) { + if (!isBatch) { + this.data.putLong(MessageDecoder.QUEUE_OFFSET_POSITION, offset); + return; + } + + for (byte[] data : batchData) { + ByteBuffer.wrap(data).putLong(MessageDecoder.QUEUE_OFFSET_POSITION, offset++); + } } public byte[] getData() { @@ -977,8 +988,6 @@ public class DLedgerCommitLog extends CommitLog { // The maximum length of the message private final int maxMessageSize; - // Build Message Key - private final StringBuilder keyBuilder = new StringBuilder(); MessageSerializer(final int size) { this.maxMessageSize = size; @@ -1079,17 +1088,7 @@ public class DLedgerCommitLog extends CommitLog { } public EncodeResult serialize(final MessageExtBatch messageExtBatch) { - keyBuilder.setLength(0); - keyBuilder.append(messageExtBatch.getTopic()); - keyBuilder.append('-'); - keyBuilder.append(messageExtBatch.getQueueId()); - String key = keyBuilder.toString(); - - Long queueOffset = DLedgerCommitLog.this.topicQueueTable.get(key); - if (null == queueOffset) { - queueOffset = 0L; - DLedgerCommitLog.this.topicQueueTable.put(key, queueOffset); - } + String key = messageExtBatch.getTopic() + "-" + messageExtBatch.getQueueId(); int totalMsgLen = 0; ByteBuffer messagesByteBuff = messageExtBatch.wrap(); @@ -1154,7 +1153,7 @@ public class DLedgerCommitLog extends CommitLog { // 5 FLAG msgStoreItemMemory.putInt(flag); // 6 QUEUEOFFSET - msgStoreItemMemory.putLong(queueOffset++); + msgStoreItemMemory.putLong(0L); // 7 PHYSICALOFFSET msgStoreItemMemory.putLong(0); // 8 SYSFLAG @@ -1210,6 +1209,7 @@ public class DLedgerCommitLog extends CommitLog { this.sbr = sbr; } + @Override public synchronized void release() { super.release(); if (sbr != null) { From b34e68a74ccf0ae55be868adb3cdb8483f5abe9a Mon Sep 17 00:00:00 2001 From: ayanamist Date: Wed, 19 May 2021 14:44:01 +0800 Subject: [PATCH 32/65] Fix ConcurrentModificationException --- .../test/util/data/collect/impl/ListDataCollectorImpl.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/src/main/java/org/apache/rocketmq/test/util/data/collect/impl/ListDataCollectorImpl.java b/test/src/main/java/org/apache/rocketmq/test/util/data/collect/impl/ListDataCollectorImpl.java index 82ab461aa5..bdd991a335 100644 --- a/test/src/main/java/org/apache/rocketmq/test/util/data/collect/impl/ListDataCollectorImpl.java +++ b/test/src/main/java/org/apache/rocketmq/test/util/data/collect/impl/ListDataCollectorImpl.java @@ -43,7 +43,7 @@ public class ListDataCollectorImpl implements DataCollector { return datas; } - public void resetData() { + public synchronized void resetData() { datas.clear(); unlockIncrement(); } @@ -67,7 +67,7 @@ public class ListDataCollectorImpl implements DataCollector { return Collections.frequency(datas, data) == 1; } - public Collection getAllDataWithoutDuplicate() { + public synchronized Collection getAllDataWithoutDuplicate() { return new HashSet(datas); } @@ -81,7 +81,7 @@ public class ListDataCollectorImpl implements DataCollector { return res; } - public void removeData(Object data) { + public synchronized void removeData(Object data) { datas.remove(data); } From 85ab2201ff8fcb5241f938fe772080365a0c9456 Mon Sep 17 00:00:00 2001 From: ayanamist Date: Wed, 19 May 2021 17:07:47 +0800 Subject: [PATCH 33/65] Fix concurrent issue of test of tracer of OpenTracing --- .../client/trace/DefaultMQConsumerWithOpenTracingTest.java | 4 +++- pom.xml | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQConsumerWithOpenTracingTest.java b/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQConsumerWithOpenTracingTest.java index c173b8ef79..1d8ac85b27 100644 --- a/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQConsumerWithOpenTracingTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQConsumerWithOpenTracingTest.java @@ -75,6 +75,7 @@ import org.mockito.junit.MockitoJUnitRunner; import org.mockito.stubbing.Answer; import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.waitAtMost; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; @@ -209,7 +210,8 @@ public class DefaultMQConsumerWithOpenTracingTest { assertThat(msg.getTopic()).isEqualTo(topic); assertThat(msg.getBody()).isEqualTo(new byte[]{'a'}); - assertThat(tracer.finishedSpans().size()).isEqualTo(1); + // wait until consumeMessageAfter hook of tracer is done surely. + waitAtMost(1, TimeUnit.SECONDS).until(() -> tracer.finishedSpans().size() == 1); MockSpan span = tracer.finishedSpans().get(0); assertThat(span.tags().get(Tags.MESSAGE_BUS_DESTINATION.getKey())).isEqualTo(topic); assertThat(span.tags().get(Tags.SPAN_KIND.getKey())).isEqualTo(Tags.SPAN_KIND_CONSUMER); diff --git a/pom.xml b/pom.xml index 3714eb344e..634a0fbac3 100644 --- a/pom.xml +++ b/pom.xml @@ -439,6 +439,12 @@ 3.10.0 test + + org.awaitility + awaitility + 4.1.0 + test + From 0aaaeb60c377ac513860dc0789420e1d1628aedd Mon Sep 17 00:00:00 2001 From: ayanamist Date: Wed, 19 May 2021 22:21:18 +0800 Subject: [PATCH 34/65] DefaultMQPushConsumerTest/DefaultMQConsumerWithOpenTracingTest mock everything before start --- .../consumer/DefaultMQPushConsumerTest.java | 86 ++++++++----------- .../DefaultMQConsumerWithOpenTracingTest.java | 76 +++++++--------- 2 files changed, 64 insertions(+), 98 deletions(-) diff --git a/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java b/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java index 48d9b3a91a..c2c60824ec 100644 --- a/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java @@ -17,7 +17,6 @@ package org.apache.rocketmq.client.consumer; import java.io.ByteArrayOutputStream; -import java.lang.reflect.Field; import java.net.InetSocketAddress; import java.util.Collections; import java.util.HashSet; @@ -45,7 +44,6 @@ import org.apache.rocketmq.client.impl.consumer.ConsumeMessageConcurrentlyServic import org.apache.rocketmq.client.impl.consumer.ConsumeMessageOrderlyService; import org.apache.rocketmq.client.impl.consumer.DefaultMQPushConsumerImpl; import org.apache.rocketmq.client.impl.consumer.ProcessQueue; -import org.apache.rocketmq.client.impl.consumer.PullAPIWrapper; import org.apache.rocketmq.client.impl.consumer.PullMessageService; import org.apache.rocketmq.client.impl.consumer.PullRequest; import org.apache.rocketmq.client.impl.consumer.PullResultExt; @@ -88,7 +86,6 @@ public class DefaultMQPushConsumerTest { @Mock private MQClientAPIImpl mQClientAPIImpl; - private PullAPIWrapper pullAPIWrapper; private RebalanceImpl rebalanceImpl; private DefaultMQPushConsumer pushConsumer; @@ -98,51 +95,7 @@ public class DefaultMQPushConsumerTest { factoryTable.forEach((s, instance) -> instance.shutdown()); factoryTable.clear(); - consumerGroup = "FooBarGroup" + System.currentTimeMillis(); - pushConsumer = new DefaultMQPushConsumer(consumerGroup); - pushConsumer.setNamesrvAddr("127.0.0.1:9876"); - pushConsumer.setPullInterval(60 * 1000); - - pushConsumer.registerMessageListener(new MessageListenerConcurrently() { - @Override - public ConsumeConcurrentlyStatus consumeMessage(List msgs, - ConsumeConcurrentlyContext context) { - return null; - } - }); - - DefaultMQPushConsumerImpl pushConsumerImpl = pushConsumer.getDefaultMQPushConsumerImpl(); - - // suppress updateTopicRouteInfoFromNameServer - pushConsumer.changeInstanceNameToPID(); - mQClientFactory = spy(MQClientManager.getInstance().getOrCreateMQClientInstance(pushConsumer, (RPCHook) FieldUtils.readDeclaredField(pushConsumerImpl, "rpcHook", true))); - factoryTable.put(pushConsumer.buildMQClientId(), mQClientFactory); - doReturn(false).when(mQClientFactory).updateTopicRouteInfoFromNameServer(anyString()); - - rebalanceImpl = spy(pushConsumer.getDefaultMQPushConsumerImpl().getRebalanceImpl()); - Field field = DefaultMQPushConsumerImpl.class.getDeclaredField("rebalanceImpl"); - field.setAccessible(true); - field.set(pushConsumerImpl, rebalanceImpl); - - pushConsumer.subscribe(topic, "*"); - pushConsumer.start(); - - field = DefaultMQPushConsumerImpl.class.getDeclaredField("mQClientFactory"); - field.setAccessible(true); - field.set(pushConsumerImpl, mQClientFactory); - - field = MQClientInstance.class.getDeclaredField("mQClientAPIImpl"); - field.setAccessible(true); - field.set(mQClientFactory, mQClientAPIImpl); - - pullAPIWrapper = spy(new PullAPIWrapper(mQClientFactory, consumerGroup, false)); - field = DefaultMQPushConsumerImpl.class.getDeclaredField("pullAPIWrapper"); - field.setAccessible(true); - field.set(pushConsumerImpl, pullAPIWrapper); - - mQClientFactory.registerConsumer(consumerGroup, pushConsumerImpl); - - when(mQClientFactory.getMQClientAPIImpl().pullMessage(anyString(), any(PullMessageRequestHeader.class), + when(mQClientAPIImpl.pullMessage(anyString(), any(PullMessageRequestHeader.class), anyLong(), any(CommunicationMode.class), nullable(PullCallback.class))) .thenAnswer(new Answer() { @Override @@ -162,11 +115,42 @@ public class DefaultMQPushConsumerTest { } }); + + consumerGroup = "FooBarGroup" + System.currentTimeMillis(); + pushConsumer = new DefaultMQPushConsumer(consumerGroup); + pushConsumer.setNamesrvAddr("127.0.0.1:9876"); + pushConsumer.setPullInterval(60 * 1000); + + pushConsumer.registerMessageListener(new MessageListenerConcurrently() { + @Override + public ConsumeConcurrentlyStatus consumeMessage(List msgs, + ConsumeConcurrentlyContext context) { + return null; + } + }); + + DefaultMQPushConsumerImpl pushConsumerImpl = pushConsumer.getDefaultMQPushConsumerImpl(); + + // suppress updateTopicRouteInfoFromNameServer + pushConsumer.changeInstanceNameToPID(); + mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(pushConsumer, (RPCHook) FieldUtils.readDeclaredField(pushConsumerImpl, "rpcHook", true)); + FieldUtils.writeDeclaredField(mQClientFactory, "mQClientAPIImpl", mQClientAPIImpl, true); + mQClientFactory = spy(mQClientFactory); + factoryTable.put(pushConsumer.buildMQClientId(), mQClientFactory); + doReturn(false).when(mQClientFactory).updateTopicRouteInfoFromNameServer(anyString()); + doReturn(new FindBrokerResult("127.0.0.1:10911", false)).when(mQClientFactory).findBrokerAddressInSubscribe(anyString(), anyLong(), anyBoolean()); + + rebalanceImpl = spy(pushConsumerImpl.getRebalanceImpl()); + doReturn(123L).when(rebalanceImpl).computePullFromWhere(any(MessageQueue.class)); + FieldUtils.writeDeclaredField(pushConsumerImpl, "rebalanceImpl", rebalanceImpl, true); + Set messageQueueSet = new HashSet(); messageQueueSet.add(createPullRequest().getMessageQueue()); - pushConsumer.getDefaultMQPushConsumerImpl().updateTopicSubscribeInfo(topic, messageQueueSet); - doReturn(123L).when(rebalanceImpl).computePullFromWhere(any(MessageQueue.class)); + pushConsumerImpl.updateTopicSubscribeInfo(topic, messageQueueSet); + + pushConsumer.subscribe(topic, "*"); + pushConsumer.start(); } @After @@ -292,7 +276,7 @@ public class DefaultMQPushConsumerTest { PullMessageService pullMessageService = mQClientFactory.getPullMessageService(); pullMessageService.executePullRequestImmediately(createPullRequest()); - assertThat(countDownLatch.await(30, TimeUnit.SECONDS)).isTrue(); + assertThat(countDownLatch.await(10, TimeUnit.SECONDS)).isTrue(); pushConsumer.shutdown(); assertThat(messageConsumedFlag.get()).isTrue(); diff --git a/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQConsumerWithOpenTracingTest.java b/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQConsumerWithOpenTracingTest.java index 1d8ac85b27..ecf72ae44c 100644 --- a/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQConsumerWithOpenTracingTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQConsumerWithOpenTracingTest.java @@ -21,7 +21,6 @@ import io.opentracing.mock.MockSpan; import io.opentracing.mock.MockTracer; import io.opentracing.tag.Tags; import java.io.ByteArrayOutputStream; -import java.lang.reflect.Field; import java.net.InetSocketAddress; import java.util.Collections; import java.util.HashSet; @@ -106,6 +105,27 @@ public class DefaultMQConsumerWithOpenTracingTest { factoryTable.forEach((s, instance) -> instance.shutdown()); factoryTable.clear(); + when(mQClientAPIImpl.pullMessage(anyString(), any(PullMessageRequestHeader.class), + anyLong(), any(CommunicationMode.class), nullable(PullCallback.class))) + .thenAnswer(new Answer() { + @Override + public PullResult answer(InvocationOnMock mock) throws Throwable { + PullMessageRequestHeader requestHeader = mock.getArgument(1); + MessageClientExt messageClientExt = new MessageClientExt(); + messageClientExt.setTopic(topic); + messageClientExt.setQueueId(0); + messageClientExt.setMsgId("123"); + messageClientExt.setBody(new byte[]{'a'}); + messageClientExt.setOffsetMsgId("234"); + messageClientExt.setBornHost(new InetSocketAddress(8080)); + messageClientExt.setStoreHost(new InetSocketAddress(8080)); + PullResult pullResult = createPullResult(requestHeader, PullStatus.FOUND, Collections.singletonList(messageClientExt)); + ((PullCallback) mock.getArgument(4)).onSuccess(pullResult); + return pullResult; + } + }); + + consumerGroup = "FooBarGroup" + System.currentTimeMillis(); pushConsumer = new DefaultMQPushConsumer(consumerGroup); pushConsumer.getDefaultMQPushConsumerImpl().registerConsumeMessageHook( @@ -129,58 +149,20 @@ public class DefaultMQConsumerWithOpenTracingTest { // suppress updateTopicRouteInfoFromNameServer pushConsumer.changeInstanceNameToPID(); - mQClientFactory = spy(MQClientManager.getInstance().getOrCreateMQClientInstance(pushConsumer, (RPCHook) FieldUtils.readDeclaredField(pushConsumerImpl, "rpcHook", true))); + mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(pushConsumer, (RPCHook) FieldUtils.readDeclaredField(pushConsumerImpl, "rpcHook", true)); + FieldUtils.writeDeclaredField(mQClientFactory, "mQClientAPIImpl", mQClientAPIImpl, true); + mQClientFactory = spy(mQClientFactory); factoryTable.put(pushConsumer.buildMQClientId(), mQClientFactory); doReturn(false).when(mQClientFactory).updateTopicRouteInfoFromNameServer(anyString()); - rebalancePushImpl = spy(new RebalancePushImpl(pushConsumer.getDefaultMQPushConsumerImpl())); - Field field = DefaultMQPushConsumerImpl.class.getDeclaredField("rebalanceImpl"); - field.setAccessible(true); - field.set(pushConsumerImpl, rebalancePushImpl); - pushConsumer.subscribe(topic, "*"); - - pushConsumer.start(); - - field = DefaultMQPushConsumerImpl.class.getDeclaredField("mQClientFactory"); - field.setAccessible(true); - field.set(pushConsumerImpl, mQClientFactory); - - field = MQClientInstance.class.getDeclaredField("mQClientAPIImpl"); - field.setAccessible(true); - field.set(mQClientFactory, mQClientAPIImpl); - - pullAPIWrapper = spy(new PullAPIWrapper(mQClientFactory, consumerGroup, false)); - field = DefaultMQPushConsumerImpl.class.getDeclaredField("pullAPIWrapper"); - field.setAccessible(true); - field.set(pushConsumerImpl, pullAPIWrapper); - - pushConsumer.getDefaultMQPushConsumerImpl().getRebalanceImpl().setmQClientFactory(mQClientFactory); - mQClientFactory.registerConsumer(consumerGroup, pushConsumerImpl); - - when(mQClientAPIImpl.pullMessage(anyString(), any(PullMessageRequestHeader.class), - anyLong(), any(CommunicationMode.class), nullable(PullCallback.class))) - .thenAnswer(new Answer() { - @Override - public PullResult answer(InvocationOnMock mock) throws Throwable { - PullMessageRequestHeader requestHeader = mock.getArgument(1); - MessageClientExt messageClientExt = new MessageClientExt(); - messageClientExt.setTopic(topic); - messageClientExt.setQueueId(0); - messageClientExt.setMsgId("123"); - messageClientExt.setBody(new byte[]{'a'}); - messageClientExt.setOffsetMsgId("234"); - messageClientExt.setBornHost(new InetSocketAddress(8080)); - messageClientExt.setStoreHost(new InetSocketAddress(8080)); - PullResult pullResult = createPullResult(requestHeader, PullStatus.FOUND, Collections.singletonList(messageClientExt)); - ((PullCallback) mock.getArgument(4)).onSuccess(pullResult); - return pullResult; - } - }); - doReturn(new FindBrokerResult("127.0.0.1:10911", false)).when(mQClientFactory).findBrokerAddressInSubscribe(anyString(), anyLong(), anyBoolean()); + Set messageQueueSet = new HashSet(); messageQueueSet.add(createPullRequest().getMessageQueue()); - pushConsumer.getDefaultMQPushConsumerImpl().updateTopicSubscribeInfo(topic, messageQueueSet); + pushConsumerImpl.updateTopicSubscribeInfo(topic, messageQueueSet); + + pushConsumer.subscribe(topic, "*"); + pushConsumer.start(); } @After From 7b7e45bca15500e3f36940180c87f80ceab291eb Mon Sep 17 00:00:00 2001 From: shendong Date: Fri, 14 May 2021 14:25:44 +0800 Subject: [PATCH 35/65] fix the problem of potential NPE in ACL plain --- .../acl/plain/PlainPermissionManager.java | 43 +++++++++++-------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainPermissionManager.java b/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainPermissionManager.java index c182d7eb6a..97bd6bc857 100644 --- a/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainPermissionManager.java +++ b/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainPermissionManager.java @@ -50,9 +50,9 @@ public class PlainPermissionManager { private String fileName = System.getProperty("rocketmq.acl.plain.file", DEFAULT_PLAIN_ACL_FILE); - private Map plainAccessResourceMap = new HashMap<>(); + private Map plainAccessResourceMap = new HashMap<>(); - private List globalWhiteRemoteAddressStrategy = new ArrayList<>(); + private List globalWhiteRemoteAddressStrategy = new ArrayList<>(); private RemoteAddressStrategyFactory remoteAddressStrategyFactory = new RemoteAddressStrategyFactory(); @@ -80,7 +80,7 @@ public class PlainPermissionManager { if (globalWhiteRemoteAddressesList != null && !globalWhiteRemoteAddressesList.isEmpty()) { for (int i = 0; i < globalWhiteRemoteAddressesList.size(); i++) { globalWhiteRemoteAddressStrategy.add(remoteAddressStrategyFactory. - getRemoteAddressStrategy(globalWhiteRemoteAddressesList.getString(i))); + getRemoteAddressStrategy(globalWhiteRemoteAddressesList.getString(i))); } } @@ -89,7 +89,7 @@ public class PlainPermissionManager { List plainAccessConfigList = accounts.toJavaList(PlainAccessConfig.class); for (PlainAccessConfig plainAccessConfig : plainAccessConfigList) { PlainAccessResource plainAccessResource = buildPlainAccessResource(plainAccessConfig); - plainAccessResourceMap.put(plainAccessResource.getAccessKey(),plainAccessResource); + plainAccessResourceMap.put(plainAccessResource.getAccessKey(), plainAccessResource); } } @@ -133,7 +133,9 @@ public class PlainPermissionManager { Map aclAccessConfigMap = AclUtils.getYamlDataObject(fileHome + File.separator + fileName, Map.class); - + if (aclAccessConfigMap == null || aclAccessConfigMap.isEmpty()) { + throw new AclException(String.format("%s file not found or isEmpty", fileHome + File.separator + fileName)); + } List> accounts = (List>) aclAccessConfigMap.get(AclConstants.CONFIG_ACCOUNTS); Map updateAccountMap = null; if (accounts != null) { @@ -164,20 +166,21 @@ public class PlainPermissionManager { return false; } - private Map createAclAccessConfigMap(Map existedAccountMap, PlainAccessConfig plainAccessConfig) { - + private Map createAclAccessConfigMap(Map existedAccoutMap, + PlainAccessConfig plainAccessConfig) { + Map newAccountsMap = null; - if (existedAccountMap == null) { + if (existedAccoutMap == null) { newAccountsMap = new LinkedHashMap(); } else { - newAccountsMap = existedAccountMap; + newAccountsMap = existedAccoutMap; } if (StringUtils.isEmpty(plainAccessConfig.getAccessKey()) || plainAccessConfig.getAccessKey().length() <= AclConstants.ACCESS_KEY_MIN_LENGTH) { throw new AclException(String.format( - "The accessKey=%s cannot be null and length should longer than 6", - plainAccessConfig.getAccessKey())); + "The accessKey=%s cannot be null and length should longer than 6", + plainAccessConfig.getAccessKey())); } newAccountsMap.put(AclConstants.CONFIG_ACCESS_KEY, plainAccessConfig.getAccessKey()); @@ -218,8 +221,10 @@ public class PlainPermissionManager { } Map aclAccessConfigMap = AclUtils.getYamlDataObject(fileHome + File.separator + fileName, - Map.class); - + Map.class); + if (aclAccessConfigMap == null || aclAccessConfigMap.isEmpty()) { + throw new AclException(String.format("%s file not found or isEmpty", fileHome + File.separator + fileName)); + } List> accounts = (List>) aclAccessConfigMap.get("accounts"); if (accounts != null) { Iterator> itemIterator = accounts.iterator(); @@ -251,7 +256,9 @@ public class PlainPermissionManager { Map aclAccessConfigMap = AclUtils.getYamlDataObject(fileHome + File.separator + fileName, Map.class); - + if (aclAccessConfigMap == null || aclAccessConfigMap.isEmpty()) { + throw new AclException(String.format("%s file not found or isEmpty", fileHome + File.separator + fileName)); + } List globalWhiteRemoteAddrList = (List) aclAccessConfigMap.get(AclConstants.CONFIG_GLOBAL_WHITE_ADDRS); if (globalWhiteRemoteAddrList != null) { @@ -259,7 +266,7 @@ public class PlainPermissionManager { globalWhiteRemoteAddrList.addAll(globalWhiteAddrsList); // Update globalWhiteRemoteAddr element in memeory map firstly - aclAccessConfigMap.put(AclConstants.CONFIG_GLOBAL_WHITE_ADDRS,globalWhiteRemoteAddrList); + aclAccessConfigMap.put(AclConstants.CONFIG_GLOBAL_WHITE_ADDRS, globalWhiteRemoteAddrList); if (AclUtils.writeDataObject(fileHome + File.separator + fileName, updateAclConfigFileVersion(aclAccessConfigMap))) { return true; } @@ -275,7 +282,7 @@ public class PlainPermissionManager { List configs = new ArrayList<>(); List whiteAddrs = new ArrayList<>(); JSONObject plainAclConfData = AclUtils.getYamlDataObject(fileHome + File.separator + fileName, - JSONObject.class); + JSONObject.class); if (plainAclConfData == null || plainAclConfData.isEmpty()) { throw new AclException(String.format("%s file is not data", fileHome + File.separator + fileName)); } @@ -359,7 +366,7 @@ public class PlainPermissionManager { || plainAccessConfig.getSecretKey().length() <= AclConstants.SECRET_KEY_MIN_LENGTH) { throw new AclException(String.format( "The accessKey=%s and secretKey=%s cannot be null and length should longer than 6", - plainAccessConfig.getAccessKey(), plainAccessConfig.getSecretKey())); + plainAccessConfig.getAccessKey(), plainAccessConfig.getSecretKey())); } PlainAccessResource plainAccessResource = new PlainAccessResource(); plainAccessResource.setAccessKey(plainAccessConfig.getAccessKey()); @@ -375,7 +382,7 @@ public class PlainPermissionManager { Permission.parseResourcePerms(plainAccessResource, true, plainAccessConfig.getTopicPerms()); plainAccessResource.setRemoteAddressStrategy(remoteAddressStrategyFactory. - getRemoteAddressStrategy(plainAccessResource.getWhiteRemoteAddress())); + getRemoteAddressStrategy(plainAccessResource.getWhiteRemoteAddress())); return plainAccessResource; } From 85168cd2aada89075dc4f5342f8727f564f9c252 Mon Sep 17 00:00:00 2001 From: shendong Date: Fri, 14 May 2021 14:25:44 +0800 Subject: [PATCH 36/65] fix the problem of potential NPE in ACL plain --- .../acl/plain/PlainPermissionManager.java | 43 +++++++++++-------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainPermissionManager.java b/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainPermissionManager.java index c182d7eb6a..97bd6bc857 100644 --- a/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainPermissionManager.java +++ b/acl/src/main/java/org/apache/rocketmq/acl/plain/PlainPermissionManager.java @@ -50,9 +50,9 @@ public class PlainPermissionManager { private String fileName = System.getProperty("rocketmq.acl.plain.file", DEFAULT_PLAIN_ACL_FILE); - private Map plainAccessResourceMap = new HashMap<>(); + private Map plainAccessResourceMap = new HashMap<>(); - private List globalWhiteRemoteAddressStrategy = new ArrayList<>(); + private List globalWhiteRemoteAddressStrategy = new ArrayList<>(); private RemoteAddressStrategyFactory remoteAddressStrategyFactory = new RemoteAddressStrategyFactory(); @@ -80,7 +80,7 @@ public class PlainPermissionManager { if (globalWhiteRemoteAddressesList != null && !globalWhiteRemoteAddressesList.isEmpty()) { for (int i = 0; i < globalWhiteRemoteAddressesList.size(); i++) { globalWhiteRemoteAddressStrategy.add(remoteAddressStrategyFactory. - getRemoteAddressStrategy(globalWhiteRemoteAddressesList.getString(i))); + getRemoteAddressStrategy(globalWhiteRemoteAddressesList.getString(i))); } } @@ -89,7 +89,7 @@ public class PlainPermissionManager { List plainAccessConfigList = accounts.toJavaList(PlainAccessConfig.class); for (PlainAccessConfig plainAccessConfig : plainAccessConfigList) { PlainAccessResource plainAccessResource = buildPlainAccessResource(plainAccessConfig); - plainAccessResourceMap.put(plainAccessResource.getAccessKey(),plainAccessResource); + plainAccessResourceMap.put(plainAccessResource.getAccessKey(), plainAccessResource); } } @@ -133,7 +133,9 @@ public class PlainPermissionManager { Map aclAccessConfigMap = AclUtils.getYamlDataObject(fileHome + File.separator + fileName, Map.class); - + if (aclAccessConfigMap == null || aclAccessConfigMap.isEmpty()) { + throw new AclException(String.format("%s file not found or isEmpty", fileHome + File.separator + fileName)); + } List> accounts = (List>) aclAccessConfigMap.get(AclConstants.CONFIG_ACCOUNTS); Map updateAccountMap = null; if (accounts != null) { @@ -164,20 +166,21 @@ public class PlainPermissionManager { return false; } - private Map createAclAccessConfigMap(Map existedAccountMap, PlainAccessConfig plainAccessConfig) { - + private Map createAclAccessConfigMap(Map existedAccoutMap, + PlainAccessConfig plainAccessConfig) { + Map newAccountsMap = null; - if (existedAccountMap == null) { + if (existedAccoutMap == null) { newAccountsMap = new LinkedHashMap(); } else { - newAccountsMap = existedAccountMap; + newAccountsMap = existedAccoutMap; } if (StringUtils.isEmpty(plainAccessConfig.getAccessKey()) || plainAccessConfig.getAccessKey().length() <= AclConstants.ACCESS_KEY_MIN_LENGTH) { throw new AclException(String.format( - "The accessKey=%s cannot be null and length should longer than 6", - plainAccessConfig.getAccessKey())); + "The accessKey=%s cannot be null and length should longer than 6", + plainAccessConfig.getAccessKey())); } newAccountsMap.put(AclConstants.CONFIG_ACCESS_KEY, plainAccessConfig.getAccessKey()); @@ -218,8 +221,10 @@ public class PlainPermissionManager { } Map aclAccessConfigMap = AclUtils.getYamlDataObject(fileHome + File.separator + fileName, - Map.class); - + Map.class); + if (aclAccessConfigMap == null || aclAccessConfigMap.isEmpty()) { + throw new AclException(String.format("%s file not found or isEmpty", fileHome + File.separator + fileName)); + } List> accounts = (List>) aclAccessConfigMap.get("accounts"); if (accounts != null) { Iterator> itemIterator = accounts.iterator(); @@ -251,7 +256,9 @@ public class PlainPermissionManager { Map aclAccessConfigMap = AclUtils.getYamlDataObject(fileHome + File.separator + fileName, Map.class); - + if (aclAccessConfigMap == null || aclAccessConfigMap.isEmpty()) { + throw new AclException(String.format("%s file not found or isEmpty", fileHome + File.separator + fileName)); + } List globalWhiteRemoteAddrList = (List) aclAccessConfigMap.get(AclConstants.CONFIG_GLOBAL_WHITE_ADDRS); if (globalWhiteRemoteAddrList != null) { @@ -259,7 +266,7 @@ public class PlainPermissionManager { globalWhiteRemoteAddrList.addAll(globalWhiteAddrsList); // Update globalWhiteRemoteAddr element in memeory map firstly - aclAccessConfigMap.put(AclConstants.CONFIG_GLOBAL_WHITE_ADDRS,globalWhiteRemoteAddrList); + aclAccessConfigMap.put(AclConstants.CONFIG_GLOBAL_WHITE_ADDRS, globalWhiteRemoteAddrList); if (AclUtils.writeDataObject(fileHome + File.separator + fileName, updateAclConfigFileVersion(aclAccessConfigMap))) { return true; } @@ -275,7 +282,7 @@ public class PlainPermissionManager { List configs = new ArrayList<>(); List whiteAddrs = new ArrayList<>(); JSONObject plainAclConfData = AclUtils.getYamlDataObject(fileHome + File.separator + fileName, - JSONObject.class); + JSONObject.class); if (plainAclConfData == null || plainAclConfData.isEmpty()) { throw new AclException(String.format("%s file is not data", fileHome + File.separator + fileName)); } @@ -359,7 +366,7 @@ public class PlainPermissionManager { || plainAccessConfig.getSecretKey().length() <= AclConstants.SECRET_KEY_MIN_LENGTH) { throw new AclException(String.format( "The accessKey=%s and secretKey=%s cannot be null and length should longer than 6", - plainAccessConfig.getAccessKey(), plainAccessConfig.getSecretKey())); + plainAccessConfig.getAccessKey(), plainAccessConfig.getSecretKey())); } PlainAccessResource plainAccessResource = new PlainAccessResource(); plainAccessResource.setAccessKey(plainAccessConfig.getAccessKey()); @@ -375,7 +382,7 @@ public class PlainPermissionManager { Permission.parseResourcePerms(plainAccessResource, true, plainAccessConfig.getTopicPerms()); plainAccessResource.setRemoteAddressStrategy(remoteAddressStrategyFactory. - getRemoteAddressStrategy(plainAccessResource.getWhiteRemoteAddress())); + getRemoteAddressStrategy(plainAccessResource.getWhiteRemoteAddress())); return plainAccessResource; } From 908782f056859603fe238984afd9bb530110f583 Mon Sep 17 00:00:00 2001 From: zhangjidi2016 <1017543663@qq.com> Date: Thu, 20 May 2021 16:29:39 +0800 Subject: [PATCH 37/65] [ISSUE #2921]Fix consume msgtrace loss (#2922) * [ISSUE #2898] Resolve rocketmq-example project failed during checkstyle execution * [ISSUE #2921] Fix a bug in trace when consume message. --- .../java/org/apache/rocketmq/client/trace/TraceDataEncoder.java | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/main/java/org/apache/rocketmq/client/trace/TraceDataEncoder.java b/client/src/main/java/org/apache/rocketmq/client/trace/TraceDataEncoder.java index b2b06452f6..2625d353f8 100644 --- a/client/src/main/java/org/apache/rocketmq/client/trace/TraceDataEncoder.java +++ b/client/src/main/java/org/apache/rocketmq/client/trace/TraceDataEncoder.java @@ -198,6 +198,7 @@ public class TraceDataEncoder { } } + break; case EndTransaction: { TraceBean bean = ctx.getTraceBeans().get(0); sb.append(ctx.getTraceType()).append(TraceConstants.CONTENT_SPLITOR)// From 4e73ba0ee374d6a5484eb6184bbc8ff3829a7075 Mon Sep 17 00:00:00 2001 From: Shang Yehua Date: Thu, 20 May 2021 16:47:42 +0800 Subject: [PATCH 38/65] docs(example): change delay start point (#2586) As the test results showed, the message delay period starts from the bornTimestamp, not from storeTimestamp Co-authored-by: Yehua Shang --- docs/cn/RocketMQ_Example.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cn/RocketMQ_Example.md b/docs/cn/RocketMQ_Example.md index fd36fd1c31..d924ce1d7d 100644 --- a/docs/cn/RocketMQ_Example.md +++ b/docs/cn/RocketMQ_Example.md @@ -446,7 +446,7 @@ public class ScheduledMessageConsumer { public ConsumeConcurrentlyStatus consumeMessage(List messages, ConsumeConcurrentlyContext context) { for (MessageExt message : messages) { // Print approximate delay time period - System.out.println("Receive message[msgId=" + message.getMsgId() + "] " + (System.currentTimeMillis() - message.getStoreTimestamp()) + "ms later"); + System.out.println("Receive message[msgId=" + message.getMsgId() + "] " + (System.currentTimeMillis() - message.getBornTimestamp()) + "ms later"); } return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } From 088dd29364405621f9a2fbc1bc31aa44fb786b20 Mon Sep 17 00:00:00 2001 From: baozhang Date: Thu, 20 May 2021 17:29:42 +0800 Subject: [PATCH 39/65] update fastjson version from 1.2.69 to 1.2.76 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 634a0fbac3..39f368538e 100644 --- a/pom.xml +++ b/pom.xml @@ -542,7 +542,7 @@ com.alibaba fastjson - 1.2.69 + 1.2.76 org.javassist From bc1c1b18a9c25275539f9195fff88118b390be01 Mon Sep 17 00:00:00 2001 From: b1n90 <34547665+b1n90@users.noreply.github.com> Date: Fri, 21 May 2021 10:28:54 +0800 Subject: [PATCH 40/65] docs(architecture_cn): fix incorrect description of the Producer module (#2856) * Wrong description in producer module Wrong description in producer module in the picture * Corrected the producer module description Corrected th description of the producer module in this picture --- docs/cn/image/rocketmq_architecture_3.png | Bin 61421 -> 84220 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/cn/image/rocketmq_architecture_3.png b/docs/cn/image/rocketmq_architecture_3.png index aa74ed7ac15120112d45b2a2b3213ca452ff79d7..908220ca835886e5157e6a6419e538d589b4ae3d 100644 GIT binary patch literal 84220 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 z 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 Z%)*t=MpxVut>NI$ZML`TA(zng{{dl#>eT=M literal 61421 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 f{~!HLw_=OvXq Date: Fri, 21 May 2021 10:29:12 +0800 Subject: [PATCH 41/65] Revert "docs(architecture_cn): fix incorrect description of the Producer module (#2856)" (#2928) This reverts commit bc1c1b18a9c25275539f9195fff88118b390be01. --- docs/cn/image/rocketmq_architecture_3.png | Bin 84220 -> 61421 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/cn/image/rocketmq_architecture_3.png b/docs/cn/image/rocketmq_architecture_3.png index 908220ca835886e5157e6a6419e538d589b4ae3d..aa74ed7ac15120112d45b2a2b3213ca452ff79d7 100644 GIT binary patch literal 61421 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 f{~!HLw_=OvXqK#ui6K)6ytKrrnyS`>MJ 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 z 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 Z%)*t=MpxVut>NI$ZML`TA(zng{{dl#>eT=M From f244ee35777328e8beb98f12612a822de503082b Mon Sep 17 00:00:00 2001 From: zhangjidi2016 <1017543663@qq.com> Date: Fri, 21 May 2021 10:31:00 +0800 Subject: [PATCH 42/65] [ISSUE #2556] LitePullConsumer support message trace. --- client/pom.xml | 4 + .../consumer/DefaultLitePullConsumer.java | 77 +++++ .../consumer/DefaultLitePullConsumerImpl.java | 46 +++ .../client/trace/TraceDataEncoder.java | 15 +- .../rocketmq/client/trace/TraceView.java | 12 +- .../hook/ConsumeMessageTraceHookImpl.java | 16 +- .../trace/hook/SendMessageTraceHookImpl.java | 1 - ...efaultMQLitePullConsumerWithTraceTest.java | 302 ++++++++++++++++++ .../client/trace/TraceDataEncoderTest.java | 5 +- .../rocketmq/client/trace/TraceViewTest.java | 40 +-- .../message/QueryMsgTraceByIdSubCommand.java | 54 ++-- 11 files changed, 497 insertions(+), 75 deletions(-) create mode 100644 client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQLitePullConsumerWithTraceTest.java diff --git a/client/pom.xml b/client/pom.xml index 164082c9b2..87dc89426a 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -47,6 +47,10 @@ org.apache.commons commons-lang3 + + commons-codec + commons-codec + io.opentracing opentracing-api diff --git a/client/src/main/java/org/apache/rocketmq/client/consumer/DefaultLitePullConsumer.java b/client/src/main/java/org/apache/rocketmq/client/consumer/DefaultLitePullConsumer.java index 393bda928e..a33652d6b7 100644 --- a/client/src/main/java/org/apache/rocketmq/client/consumer/DefaultLitePullConsumer.java +++ b/client/src/main/java/org/apache/rocketmq/client/consumer/DefaultLitePullConsumer.java @@ -23,6 +23,10 @@ import org.apache.rocketmq.client.consumer.rebalance.AllocateMessageQueueAverage import org.apache.rocketmq.client.consumer.store.OffsetStore; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.impl.consumer.DefaultLitePullConsumerImpl; +import org.apache.rocketmq.client.log.ClientLogger; +import org.apache.rocketmq.client.trace.AsyncTraceDispatcher; +import org.apache.rocketmq.client.trace.TraceDispatcher; +import org.apache.rocketmq.client.trace.hook.ConsumeMessageTraceHookImpl; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.consumer.ConsumeFromWhere; @@ -30,10 +34,13 @@ import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.protocol.NamespaceUtil; import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; +import org.apache.rocketmq.logging.InternalLogger; import org.apache.rocketmq.remoting.RPCHook; public class DefaultLitePullConsumer extends ClientConfig implements LitePullConsumer { + private final InternalLogger log = ClientLogger.getLog(); + private final DefaultLitePullConsumerImpl defaultLitePullConsumerImpl; /** @@ -153,6 +160,11 @@ public class DefaultLitePullConsumer extends ClientConfig implements LitePullCon */ private String consumeTimestamp = UtilAll.timeMillisToHumanString3(System.currentTimeMillis() - (1000 * 60 * 30)); + /** + * Interface of asynchronous transfer data + */ + private TraceDispatcher traceDispatcher = null; + /** * Default constructor. */ @@ -188,6 +200,57 @@ public class DefaultLitePullConsumer extends ClientConfig implements LitePullCon this(null, consumerGroup, rpcHook); } + /** + * Constructor specifying consumer group and enabled msg trace flag. + * + * @param consumerGroup Consumer group. + * @param enableMsgTrace Switch flag instance for message trace. + */ + public DefaultLitePullConsumer(final String consumerGroup, boolean enableMsgTrace) { + this(null, consumerGroup, null, enableMsgTrace, null); + } + + /** + * Constructor specifying consumer group, enabled msg trace flag and customized trace topic name. + * + * @param consumerGroup Consumer group. + * @param enableMsgTrace Switch flag instance for message trace. + * @param customizedTraceTopic The name value of message trace topic.If you don't config,you can use the default + * trace topic name. + */ + public DefaultLitePullConsumer(final String consumerGroup, boolean enableMsgTrace, + final String customizedTraceTopic) { + this(null, consumerGroup, null, enableMsgTrace, customizedTraceTopic); + } + + /** + * Constructor specifying namespace, consumer group, RPC hook, enabled msg trace flag and customized trace topic + * name. + * + * @param namespace Namespace for this MQ Producer instance. + * @param consumerGroup Consume queue. + * @param rpcHook RPC hook to execute before each remoting command. + * @param enableMsgTrace Switch flag instance for message trace. + * @param customizedTraceTopic The name value of message trace topic.If you don't config,you can use the default + * trace topic name. + */ + public DefaultLitePullConsumer(final String namespace, final String consumerGroup, RPCHook rpcHook, + boolean enableMsgTrace, final String customizedTraceTopic) { + this.namespace = namespace; + this.consumerGroup = consumerGroup; + defaultLitePullConsumerImpl = new DefaultLitePullConsumerImpl(this, rpcHook); + if (enableMsgTrace) { + try { + this.traceDispatcher = new AsyncTraceDispatcher(consumerGroup, TraceDispatcher.Type.CONSUME, customizedTraceTopic, rpcHook); + this.defaultLitePullConsumerImpl.registerConsumeMessageHook( + new ConsumeMessageTraceHookImpl(traceDispatcher)); + } catch (Throwable e) { + log.error("system mqtrace hook init failed ,maybe can't send msg trace data"); + } + } + } + + /** * Constructor specifying namespace, consumer group and RPC hook. * @@ -204,11 +267,21 @@ public class DefaultLitePullConsumer extends ClientConfig implements LitePullCon public void start() throws MQClientException { setConsumerGroup(NamespaceUtil.wrapNamespace(this.getNamespace(), this.consumerGroup)); this.defaultLitePullConsumerImpl.start(); + if (null != traceDispatcher) { + try { + traceDispatcher.start(this.getNamesrvAddr(), this.getAccessChannel()); + } catch (MQClientException e) { + log.warn("trace dispatcher start failed ", e); + } + } } @Override public void shutdown() { this.defaultLitePullConsumerImpl.shutdown(); + if (null != traceDispatcher) { + traceDispatcher.shutdown(); + } } @Override @@ -490,4 +563,8 @@ public class DefaultLitePullConsumer extends ClientConfig implements LitePullCon public void setConsumeTimestamp(String consumeTimestamp) { this.consumeTimestamp = consumeTimestamp; } + + public TraceDispatcher getTraceDispatcher() { + return traceDispatcher; + } } diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultLitePullConsumerImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultLitePullConsumerImpl.java index 280de46ff2..621f5c20ca 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultLitePullConsumerImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultLitePullConsumerImpl.java @@ -41,12 +41,15 @@ import org.apache.rocketmq.client.consumer.MessageQueueListener; import org.apache.rocketmq.client.consumer.MessageSelector; import org.apache.rocketmq.client.consumer.PullResult; import org.apache.rocketmq.client.consumer.TopicMessageQueueChangeListener; +import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.store.LocalFileOffsetStore; import org.apache.rocketmq.client.consumer.store.OffsetStore; import org.apache.rocketmq.client.consumer.store.ReadOffsetType; import org.apache.rocketmq.client.consumer.store.RemoteBrokerOffsetStore; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; +import org.apache.rocketmq.client.hook.ConsumeMessageContext; +import org.apache.rocketmq.client.hook.ConsumeMessageHook; import org.apache.rocketmq.client.hook.FilterMessageHook; import org.apache.rocketmq.client.impl.CommunicationMode; import org.apache.rocketmq.client.impl.MQClientManager; @@ -142,6 +145,8 @@ public class DefaultLitePullConsumerImpl implements MQConsumerInner { private final MessageQueueLock messageQueueLock = new MessageQueueLock(); + private final ArrayList consumeMessageHookList = new ArrayList<>(); + public DefaultLitePullConsumerImpl(final DefaultLitePullConsumer defaultLitePullConsumer, final RPCHook rpcHook) { this.defaultLitePullConsumer = defaultLitePullConsumer; this.rpcHook = rpcHook; @@ -158,6 +163,35 @@ public class DefaultLitePullConsumerImpl implements MQConsumerInner { this.pullTimeDelayMillsWhenException = defaultLitePullConsumer.getPullTimeDelayMillsWhenException(); } + public void registerConsumeMessageHook(final ConsumeMessageHook hook) { + this.consumeMessageHookList.add(hook); + log.info("register consumeMessageHook Hook, {}", hook.hookName()); + } + + public void executeHookBefore(final ConsumeMessageContext context) { + if (!this.consumeMessageHookList.isEmpty()) { + for (ConsumeMessageHook hook : this.consumeMessageHookList) { + try { + hook.consumeMessageBefore(context); + } catch (Throwable e) { + log.error("consumeMessageHook {} executeHookBefore exception", hook.hookName(), e); + } + } + } + } + + public void executeHookAfter(final ConsumeMessageContext context) { + if (!this.consumeMessageHookList.isEmpty()) { + for (ConsumeMessageHook hook : this.consumeMessageHookList) { + try { + hook.consumeMessageAfter(context); + } catch (Throwable e) { + log.error("consumeMessageHook {} executeHookAfter exception", hook.hookName(), e); + } + } + } + } + private void checkServiceState() { if (this.serviceState != ServiceState.RUNNING) throw new IllegalStateException(NOT_RUNNING_EXCEPTION_MESSAGE); @@ -848,6 +882,18 @@ public class DefaultLitePullConsumerImpl implements MQConsumerInner { null ); this.pullAPIWrapper.processPullResult(mq, pullResult, subscriptionData); + if (!this.consumeMessageHookList.isEmpty()) { + ConsumeMessageContext consumeMessageContext = new ConsumeMessageContext(); + consumeMessageContext.setNamespace(defaultLitePullConsumer.getNamespace()); + consumeMessageContext.setConsumerGroup(this.groupName()); + consumeMessageContext.setMq(mq); + consumeMessageContext.setMsgList(pullResult.getMsgFoundList()); + consumeMessageContext.setSuccess(false); + this.executeHookBefore(consumeMessageContext); + consumeMessageContext.setStatus(ConsumeConcurrentlyStatus.CONSUME_SUCCESS.toString()); + consumeMessageContext.setSuccess(true); + this.executeHookAfter(consumeMessageContext); + } return pullResult; } diff --git a/client/src/main/java/org/apache/rocketmq/client/trace/TraceDataEncoder.java b/client/src/main/java/org/apache/rocketmq/client/trace/TraceDataEncoder.java index 2625d353f8..b65ba46a39 100644 --- a/client/src/main/java/org/apache/rocketmq/client/trace/TraceDataEncoder.java +++ b/client/src/main/java/org/apache/rocketmq/client/trace/TraceDataEncoder.java @@ -85,7 +85,6 @@ public class TraceDataEncoder { bean.setMsgId(line[5]); bean.setRetryTimes(Integer.parseInt(line[6])); bean.setKeys(line[7]); - bean.setClientHost(line[8]); subBeforeContext.setTraceBeans(new ArrayList(1)); subBeforeContext.getTraceBeans().add(bean); resList.add(subBeforeContext); @@ -123,10 +122,9 @@ public class TraceDataEncoder { bean.setKeys(line[7]); bean.setStoreHost(line[8]); bean.setMsgType(MessageType.values()[Integer.parseInt(line[9])]); - bean.setClientHost(line[10]); - bean.setTransactionId(line[11]); - bean.setTransactionState(LocalTransactionState.valueOf(line[12])); - bean.setFromTransactionCheck(Boolean.parseBoolean(line[13])); + bean.setTransactionId(line[10]); + bean.setTransactionState(LocalTransactionState.valueOf(line[11])); + bean.setFromTransactionCheck(Boolean.parseBoolean(line[12])); endTransactionContext.setTraceBeans(new ArrayList(1)); endTransactionContext.getTraceBeans().add(bean); @@ -166,8 +164,7 @@ public class TraceDataEncoder { .append(ctx.getCostTime()).append(TraceConstants.CONTENT_SPLITOR)// .append(bean.getMsgType().ordinal()).append(TraceConstants.CONTENT_SPLITOR)// .append(bean.getOffsetMsgId()).append(TraceConstants.CONTENT_SPLITOR)// - .append(ctx.isSuccess()).append(TraceConstants.CONTENT_SPLITOR)// - .append(bean.getClientHost()).append(TraceConstants.FIELD_SPLITOR); + .append(ctx.isSuccess()).append(TraceConstants.FIELD_SPLITOR);// } break; case SubBefore: { @@ -179,8 +176,7 @@ public class TraceDataEncoder { .append(ctx.getRequestId()).append(TraceConstants.CONTENT_SPLITOR)// .append(bean.getMsgId()).append(TraceConstants.CONTENT_SPLITOR)// .append(bean.getRetryTimes()).append(TraceConstants.CONTENT_SPLITOR)// - .append(bean.getKeys()).append(TraceConstants.CONTENT_SPLITOR)// - .append(bean.getClientHost()).append(TraceConstants.FIELD_SPLITOR);// + .append(bean.getKeys()).append(TraceConstants.FIELD_SPLITOR);// } } break; @@ -211,7 +207,6 @@ public class TraceDataEncoder { .append(bean.getKeys()).append(TraceConstants.CONTENT_SPLITOR)// .append(bean.getStoreHost()).append(TraceConstants.CONTENT_SPLITOR)// .append(bean.getMsgType().ordinal()).append(TraceConstants.CONTENT_SPLITOR)// - .append(bean.getClientHost()).append(TraceConstants.CONTENT_SPLITOR)// .append(bean.getTransactionId()).append(TraceConstants.CONTENT_SPLITOR)// .append(bean.getTransactionState().name()).append(TraceConstants.CONTENT_SPLITOR)// .append(bean.isFromTransactionCheck()).append(TraceConstants.FIELD_SPLITOR); diff --git a/client/src/main/java/org/apache/rocketmq/client/trace/TraceView.java b/client/src/main/java/org/apache/rocketmq/client/trace/TraceView.java index 14fc360f11..e78d37ab2c 100644 --- a/client/src/main/java/org/apache/rocketmq/client/trace/TraceView.java +++ b/client/src/main/java/org/apache/rocketmq/client/trace/TraceView.java @@ -17,10 +17,10 @@ package org.apache.rocketmq.client.trace; - - import java.util.ArrayList; import java.util.List; +import org.apache.commons.codec.Charsets; +import org.apache.rocketmq.common.message.MessageExt; public class TraceView { @@ -38,8 +38,9 @@ public class TraceView { private String groupName; private String status; - public static List decodeFromTraceTransData(String key, String messageBody) { + public static List decodeFromTraceTransData(String key, MessageExt messageExt) { List messageTraceViewList = new ArrayList(); + String messageBody = new String(messageExt.getBody(), Charsets.UTF_8); if (messageBody == null || messageBody.length() <= 0) { return messageTraceViewList; } @@ -56,8 +57,7 @@ public class TraceView { messageTraceView.setGroupName(context.getGroupName()); if (context.isSuccess()) { messageTraceView.setStatus("success"); - } - else { + } else { messageTraceView.setStatus("failed"); } messageTraceView.setKeys(traceBean.getKeys()); @@ -68,7 +68,7 @@ public class TraceView { messageTraceView.setOffSetMsgId(traceBean.getOffsetMsgId()); messageTraceView.setTimeStamp(context.getTimeStamp()); messageTraceView.setStoreHost(traceBean.getStoreHost()); - messageTraceView.setClientHost(traceBean.getClientHost()); + messageTraceView.setClientHost(messageExt.getBornHostString()); messageTraceViewList.add(messageTraceView); } return messageTraceViewList; diff --git a/client/src/main/java/org/apache/rocketmq/client/trace/hook/ConsumeMessageTraceHookImpl.java b/client/src/main/java/org/apache/rocketmq/client/trace/hook/ConsumeMessageTraceHookImpl.java index 4f6f916ea9..bce613987f 100644 --- a/client/src/main/java/org/apache/rocketmq/client/trace/hook/ConsumeMessageTraceHookImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/trace/hook/ConsumeMessageTraceHookImpl.java @@ -16,10 +16,10 @@ */ package org.apache.rocketmq.client.trace.hook; +import java.util.Map; import org.apache.rocketmq.client.consumer.listener.ConsumeReturnType; import org.apache.rocketmq.client.hook.ConsumeMessageContext; import org.apache.rocketmq.client.hook.ConsumeMessageHook; -import org.apache.rocketmq.client.trace.AsyncTraceDispatcher; import org.apache.rocketmq.client.trace.TraceContext; import org.apache.rocketmq.client.trace.TraceDispatcher; import org.apache.rocketmq.client.trace.TraceBean; @@ -74,7 +74,6 @@ public class ConsumeMessageTraceHookImpl implements ConsumeMessageHook { traceBean.setStoreTime(msg.getStoreTimestamp());// traceBean.setBodyLength(msg.getStoreSize());// traceBean.setRetryTimes(msg.getReconsumeTimes());// - traceBean.setClientHost(((AsyncTraceDispatcher)localDispatcher).getHostConsumer().getmQClientFactory().getClientId()); traceContext.setRegionId(regionId);// beans.add(traceBean); } @@ -93,7 +92,7 @@ public class ConsumeMessageTraceHookImpl implements ConsumeMessageHook { TraceContext subBeforeContext = (TraceContext) context.getMqTraceContext(); if (subBeforeContext.getTraceBeans() == null || subBeforeContext.getTraceBeans().size() < 1) { - // If subbefore bean is null ,skip it + // If subBefore bean is null ,skip it return; } TraceContext subAfterContext = new TraceContext(); @@ -103,13 +102,16 @@ public class ConsumeMessageTraceHookImpl implements ConsumeMessageHook { subAfterContext.setRequestId(subBeforeContext.getRequestId());// subAfterContext.setSuccess(context.isSuccess());// - // Caculate the cost time for processing messages + // Calculate the cost time for processing messages int costTime = (int) ((System.currentTimeMillis() - subBeforeContext.getTimeStamp()) / context.getMsgList().size()); subAfterContext.setCostTime(costTime);// subAfterContext.setTraceBeans(subBeforeContext.getTraceBeans()); - String contextType = context.getProps().get(MixAll.CONSUME_CONTEXT_TYPE); - if (contextType != null) { - subAfterContext.setContextCode(ConsumeReturnType.valueOf(contextType).ordinal()); + Map props = context.getProps(); + if (props != null) { + String contextType = props.get(MixAll.CONSUME_CONTEXT_TYPE); + if (contextType != null) { + subAfterContext.setContextCode(ConsumeReturnType.valueOf(contextType).ordinal()); + } } localDispatcher.append(subAfterContext); } diff --git a/client/src/main/java/org/apache/rocketmq/client/trace/hook/SendMessageTraceHookImpl.java b/client/src/main/java/org/apache/rocketmq/client/trace/hook/SendMessageTraceHookImpl.java index 4feb276284..80c7babdaa 100644 --- a/client/src/main/java/org/apache/rocketmq/client/trace/hook/SendMessageTraceHookImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/trace/hook/SendMessageTraceHookImpl.java @@ -60,7 +60,6 @@ public class SendMessageTraceHookImpl implements SendMessageHook { traceBean.setStoreHost(context.getBrokerAddr()); traceBean.setBodyLength(context.getMessage().getBody().length); traceBean.setMsgType(context.getMsgType()); - traceBean.setClientHost(((AsyncTraceDispatcher)localDispatcher).getHostProducer().getmQClientFactory().getClientId()); tuxeContext.getTraceBeans().add(traceBean); } diff --git a/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQLitePullConsumerWithTraceTest.java b/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQLitePullConsumerWithTraceTest.java new file mode 100644 index 0000000000..d79790d19c --- /dev/null +++ b/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQLitePullConsumerWithTraceTest.java @@ -0,0 +1,302 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.client.trace; + +import java.util.concurrent.ConcurrentMap; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.rocketmq.client.ClientConfig; +import org.apache.rocketmq.client.consumer.DefaultLitePullConsumer; +import org.apache.rocketmq.client.consumer.PullCallback; +import org.apache.rocketmq.client.consumer.PullResult; +import org.apache.rocketmq.client.consumer.PullStatus; +import org.apache.rocketmq.client.consumer.store.OffsetStore; +import org.apache.rocketmq.client.consumer.store.ReadOffsetType; +import org.apache.rocketmq.client.hook.SendMessageContext; +import org.apache.rocketmq.client.impl.CommunicationMode; +import org.apache.rocketmq.client.impl.FindBrokerResult; +import org.apache.rocketmq.client.impl.MQAdminImpl; +import org.apache.rocketmq.client.impl.MQClientAPIImpl; +import org.apache.rocketmq.client.impl.MQClientManager; +import org.apache.rocketmq.client.impl.consumer.DefaultLitePullConsumerImpl; +import org.apache.rocketmq.client.impl.consumer.PullAPIWrapper; +import org.apache.rocketmq.client.impl.consumer.PullResultExt; +import org.apache.rocketmq.client.impl.consumer.RebalanceImpl; +import org.apache.rocketmq.client.impl.consumer.RebalanceService; +import org.apache.rocketmq.client.impl.factory.MQClientInstance; +import org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl; +import org.apache.rocketmq.client.producer.DefaultMQProducer; +import org.apache.rocketmq.client.producer.SendResult; +import org.apache.rocketmq.client.producer.SendStatus; +import org.apache.rocketmq.common.message.MessageClientExt; +import org.apache.rocketmq.common.message.MessageDecoder; +import org.apache.rocketmq.common.message.MessageExt; +import org.apache.rocketmq.common.message.MessageQueue; +import org.apache.rocketmq.common.protocol.header.PullMessageRequestHeader; +import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; +import org.apache.rocketmq.common.protocol.route.BrokerData; +import org.apache.rocketmq.common.protocol.route.QueueData; +import org.apache.rocketmq.common.protocol.route.TopicRouteData; +import org.apache.rocketmq.common.topic.TopicValidator; +import org.apache.rocketmq.remoting.RPCHook; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; + +import java.io.ByteArrayOutputStream; +import java.lang.reflect.Field; +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class DefaultMQLitePullConsumerWithTraceTest { + + @Spy + private MQClientInstance mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(new ClientConfig()); + + @Mock + private MQClientAPIImpl mQClientAPIImpl; + @Mock + private MQAdminImpl mQAdminImpl; + + private AsyncTraceDispatcher asyncTraceDispatcher; + private DefaultMQProducer traceProducer; + private RebalanceImpl rebalanceImpl; + private OffsetStore offsetStore; + private DefaultLitePullConsumerImpl litePullConsumerImpl; + private String consumerGroup = "LitePullConsumerGroup"; + private String topic = "LitePullConsumerTest"; + private String brokerName = "BrokerA"; + private String producerGroupTraceTemp = TopicValidator.RMQ_SYS_TRACE_TOPIC + System.currentTimeMillis(); + + private String customerTraceTopic = "rmq_trace_topic_12345"; + + @Before + public void init() throws Exception { + Field field = MQClientInstance.class.getDeclaredField("rebalanceService"); + field.setAccessible(true); + RebalanceService rebalanceService = (RebalanceService) field.get(mQClientFactory); + field = RebalanceService.class.getDeclaredField("waitInterval"); + field.setAccessible(true); + field.set(rebalanceService, 100); + } + + @Test + public void testSubscribe_PollMessageSuccess_WithDefaultTraceTopic() throws Exception { + DefaultLitePullConsumer litePullConsumer = createLitePullConsumerWithDefaultTraceTopic(); + try { + Set messageQueueSet = new HashSet(); + messageQueueSet.add(createMessageQueue()); + litePullConsumerImpl.updateTopicSubscribeInfo(topic, messageQueueSet); + litePullConsumer.setPollTimeoutMillis(20 * 1000); + List result = litePullConsumer.poll(); + assertThat(result.get(0).getTopic()).isEqualTo(topic); + assertThat(result.get(0).getBody()).isEqualTo(new byte[] {'a'}); + } finally { + litePullConsumer.shutdown(); + } + } + + @Test + public void testSubscribe_PollMessageSuccess_WithCustomizedTraceTopic() throws Exception { + DefaultLitePullConsumer litePullConsumer = createLitePullConsumerWithCustomizedTraceTopic(); + try { + Set messageQueueSet = new HashSet(); + messageQueueSet.add(createMessageQueue()); + litePullConsumerImpl.updateTopicSubscribeInfo(topic, messageQueueSet); + litePullConsumer.setPollTimeoutMillis(20 * 1000); + List result = litePullConsumer.poll(); + assertThat(result.get(0).getTopic()).isEqualTo(topic); + assertThat(result.get(0).getBody()).isEqualTo(new byte[] {'a'}); + } finally { + litePullConsumer.shutdown(); + } + } + + + private DefaultLitePullConsumer createLitePullConsumerWithDefaultTraceTopic() throws Exception { + DefaultLitePullConsumer litePullConsumer = new DefaultLitePullConsumer(consumerGroup + System.currentTimeMillis(), true); + litePullConsumer.setNamesrvAddr("127.0.0.1:9876"); + litePullConsumer.subscribe(topic, "*"); + suppressUpdateTopicRouteInfoFromNameServer(litePullConsumer); + litePullConsumer.start(); + initDefaultLitePullConsumer(litePullConsumer); + return litePullConsumer; + } + + private DefaultLitePullConsumer createLitePullConsumerWithCustomizedTraceTopic() throws Exception { + DefaultLitePullConsumer litePullConsumer = new DefaultLitePullConsumer(consumerGroup + System.currentTimeMillis(), true, customerTraceTopic); + litePullConsumer.setNamesrvAddr("127.0.0.1:9876"); + litePullConsumer.subscribe(topic, "*"); + suppressUpdateTopicRouteInfoFromNameServer(litePullConsumer); + litePullConsumer.start(); + initDefaultLitePullConsumer(litePullConsumer); + return litePullConsumer; + } + + private void initDefaultLitePullConsumer(DefaultLitePullConsumer litePullConsumer) throws Exception { + asyncTraceDispatcher = (AsyncTraceDispatcher) litePullConsumer.getTraceDispatcher(); + traceProducer = asyncTraceDispatcher.getTraceProducer(); + Field field = DefaultLitePullConsumer.class.getDeclaredField("defaultLitePullConsumerImpl"); + field.setAccessible(true); + litePullConsumerImpl = (DefaultLitePullConsumerImpl) field.get(litePullConsumer); + field = DefaultLitePullConsumerImpl.class.getDeclaredField("mQClientFactory"); + field.setAccessible(true); + field.set(litePullConsumerImpl, mQClientFactory); + + PullAPIWrapper pullAPIWrapper = litePullConsumerImpl.getPullAPIWrapper(); + field = PullAPIWrapper.class.getDeclaredField("mQClientFactory"); + field.setAccessible(true); + field.set(pullAPIWrapper, mQClientFactory); + + Field fieldTrace = DefaultMQProducerImpl.class.getDeclaredField("mQClientFactory"); + fieldTrace.setAccessible(true); + fieldTrace.set(traceProducer.getDefaultMQProducerImpl(), mQClientFactory); + + field = MQClientInstance.class.getDeclaredField("mQClientAPIImpl"); + field.setAccessible(true); + field.set(mQClientFactory, mQClientAPIImpl); + + field = MQClientInstance.class.getDeclaredField("mQAdminImpl"); + field.setAccessible(true); + field.set(mQClientFactory, mQAdminImpl); + + field = DefaultLitePullConsumerImpl.class.getDeclaredField("rebalanceImpl"); + field.setAccessible(true); + rebalanceImpl = (RebalanceImpl) field.get(litePullConsumerImpl); + field = RebalanceImpl.class.getDeclaredField("mQClientFactory"); + field.setAccessible(true); + field.set(rebalanceImpl, mQClientFactory); + + offsetStore = spy(litePullConsumerImpl.getOffsetStore()); + field = DefaultLitePullConsumerImpl.class.getDeclaredField("offsetStore"); + field.setAccessible(true); + field.set(litePullConsumerImpl, offsetStore); + + traceProducer.getDefaultMQProducerImpl().getmQClientFactory().registerProducer(producerGroupTraceTemp, traceProducer.getDefaultMQProducerImpl()); + + when(mQClientFactory.getMQClientAPIImpl().pullMessage(anyString(), any(PullMessageRequestHeader.class), + anyLong(), any(CommunicationMode.class), nullable(PullCallback.class))) + .thenAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock mock) throws Throwable { + PullMessageRequestHeader requestHeader = mock.getArgument(1); + MessageClientExt messageClientExt = new MessageClientExt(); + messageClientExt.setTopic(topic); + messageClientExt.setQueueId(0); + messageClientExt.setMsgId("123"); + messageClientExt.setBody(new byte[] {'a'}); + messageClientExt.setOffsetMsgId("234"); + messageClientExt.setBornHost(new InetSocketAddress(8080)); + messageClientExt.setStoreHost(new InetSocketAddress(8080)); + PullResult pullResult = createPullResult(requestHeader, PullStatus.FOUND, Collections.singletonList(messageClientExt)); + return pullResult; + } + }); + + when(mQClientFactory.findBrokerAddressInSubscribe(anyString(), anyLong(), anyBoolean())).thenReturn(new FindBrokerResult("127.0.0.1:10911", false)); + + doReturn(Collections.singletonList(mQClientFactory.getClientId())).when(mQClientFactory).findConsumerIdList(anyString(), anyString()); + + doReturn(123L).when(offsetStore).readOffset(any(MessageQueue.class), any(ReadOffsetType.class)); + + } + + private PullResultExt createPullResult(PullMessageRequestHeader requestHeader, PullStatus pullStatus, + List messageExtList) throws Exception { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + for (MessageExt messageExt : messageExtList) { + outputStream.write(MessageDecoder.encode(messageExt, false)); + } + return new PullResultExt(pullStatus, requestHeader.getQueueOffset() + messageExtList.size(), 123, 2048, messageExtList, 0, outputStream.toByteArray()); + } + + private MessageQueue createMessageQueue() { + MessageQueue messageQueue = new MessageQueue(); + messageQueue.setBrokerName(brokerName); + messageQueue.setQueueId(0); + messageQueue.setTopic(topic); + return messageQueue; + } + + private TopicRouteData createTopicRoute() { + TopicRouteData topicRouteData = new TopicRouteData(); + + topicRouteData.setFilterServerTable(new HashMap>()); + List brokerDataList = new ArrayList(); + BrokerData brokerData = new BrokerData(); + brokerData.setBrokerName("BrokerA"); + brokerData.setCluster("DefaultCluster"); + HashMap brokerAddrs = new HashMap(); + brokerAddrs.put(0L, "127.0.0.1:10911"); + brokerData.setBrokerAddrs(brokerAddrs); + brokerDataList.add(brokerData); + topicRouteData.setBrokerDatas(brokerDataList); + + List queueDataList = new ArrayList(); + QueueData queueData = new QueueData(); + queueData.setBrokerName("BrokerA"); + queueData.setPerm(6); + queueData.setReadQueueNums(3); + queueData.setWriteQueueNums(4); + queueData.setTopicSysFlag(0); + queueDataList.add(queueData); + topicRouteData.setQueueDatas(queueDataList); + return topicRouteData; + } + + private SendResult createSendResult(SendStatus sendStatus) { + SendResult sendResult = new SendResult(); + sendResult.setMsgId("123"); + sendResult.setOffsetMsgId("123"); + sendResult.setQueueOffset(456); + sendResult.setSendStatus(sendStatus); + sendResult.setRegionId("HZ"); + return sendResult; + } + + private static void suppressUpdateTopicRouteInfoFromNameServer(DefaultLitePullConsumer litePullConsumer) throws IllegalAccessException { + DefaultLitePullConsumerImpl defaultLitePullConsumerImpl = (DefaultLitePullConsumerImpl) FieldUtils.readDeclaredField(litePullConsumer, "defaultLitePullConsumerImpl", true); + if (litePullConsumer.getMessageModel() == MessageModel.CLUSTERING) { + litePullConsumer.changeInstanceNameToPID(); + } + MQClientInstance mQClientFactory = spy(MQClientManager.getInstance().getOrCreateMQClientInstance(litePullConsumer, (RPCHook) FieldUtils.readDeclaredField(defaultLitePullConsumerImpl, "rpcHook", true))); + ConcurrentMap factoryTable = (ConcurrentMap) FieldUtils.readDeclaredField(MQClientManager.getInstance(), "factoryTable", true); + factoryTable.put(litePullConsumer.buildMQClientId(), mQClientFactory); + doReturn(false).when(mQClientFactory).updateTopicRouteInfoFromNameServer(anyString()); + } + +} \ No newline at end of file diff --git a/client/src/test/java/org/apache/rocketmq/client/trace/TraceDataEncoderTest.java b/client/src/test/java/org/apache/rocketmq/client/trace/TraceDataEncoderTest.java index bac12ea0ce..af5a705395 100644 --- a/client/src/test/java/org/apache/rocketmq/client/trace/TraceDataEncoderTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/trace/TraceDataEncoderTest.java @@ -18,7 +18,6 @@ package org.apache.rocketmq.client.trace; import org.apache.rocketmq.client.producer.LocalTransactionState; -import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.message.MessageType; import org.junit.Assert; import org.junit.Before; @@ -50,8 +49,7 @@ public class TraceDataEncoderTest { .append(245).append(TraceConstants.CONTENT_SPLITOR) .append(MessageType.Normal_Msg.ordinal()).append(TraceConstants.CONTENT_SPLITOR) .append("0A9A002600002A9F0000000000002329").append(TraceConstants.CONTENT_SPLITOR) - .append(true).append(TraceConstants.CONTENT_SPLITOR) - .append(UtilAll.ipToIPv4Str(UtilAll.getIP())).append(TraceConstants.FIELD_SPLITOR) + .append(true).append(TraceConstants.FIELD_SPLITOR) .toString(); } @@ -104,7 +102,6 @@ public class TraceDataEncoderTest { traceBean.setTags("Tags"); traceBean.setMsgId("AC1415116D1418B4AAC217FE1B4E0000"); traceBean.setStoreHost("127.0.0.1:10911"); - traceBean.setClientHost("127.0.0.1@41700"); traceBean.setMsgType(MessageType.Trans_msg_Commit); traceBean.setTransactionId("transactionId"); traceBean.setTransactionState(LocalTransactionState.COMMIT_MESSAGE); diff --git a/client/src/test/java/org/apache/rocketmq/client/trace/TraceViewTest.java b/client/src/test/java/org/apache/rocketmq/client/trace/TraceViewTest.java index 51a1543ee5..b1fdbaf965 100644 --- a/client/src/test/java/org/apache/rocketmq/client/trace/TraceViewTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/trace/TraceViewTest.java @@ -17,7 +17,8 @@ package org.apache.rocketmq.client.trace; -import org.apache.rocketmq.common.UtilAll; +import org.apache.commons.codec.Charsets; +import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageType; import org.junit.Assert; import org.junit.Test; @@ -29,29 +30,30 @@ public class TraceViewTest { @Test public void testDecodeFromTraceTransData() { String messageBody = new StringBuilder() - .append("Pub").append(TraceConstants.CONTENT_SPLITOR) - .append(System.currentTimeMillis()).append(TraceConstants.CONTENT_SPLITOR) - .append("DefaultRegion").append(TraceConstants.CONTENT_SPLITOR) - .append("PID-test").append(TraceConstants.CONTENT_SPLITOR) - .append("topic-test").append(TraceConstants.CONTENT_SPLITOR) - .append("AC1415116D1418B4AAC217FE1B4E0000").append(TraceConstants.CONTENT_SPLITOR) - .append("Tags").append(TraceConstants.CONTENT_SPLITOR) - .append("Keys").append(TraceConstants.CONTENT_SPLITOR) - .append("127.0.0.1:10911").append(TraceConstants.CONTENT_SPLITOR) - .append(26).append(TraceConstants.CONTENT_SPLITOR) - .append(245).append(TraceConstants.CONTENT_SPLITOR) - .append(MessageType.Normal_Msg.ordinal()).append(TraceConstants.CONTENT_SPLITOR) - .append("0A9A002600002A9F0000000000002329").append(TraceConstants.CONTENT_SPLITOR) - .append(true).append(TraceConstants.CONTENT_SPLITOR) - .append(UtilAll.ipToIPv4Str(UtilAll.getIP())).append(TraceConstants.FIELD_SPLITOR) - .toString(); + .append("Pub").append(TraceConstants.CONTENT_SPLITOR) + .append(System.currentTimeMillis()).append(TraceConstants.CONTENT_SPLITOR) + .append("DefaultRegion").append(TraceConstants.CONTENT_SPLITOR) + .append("PID-test").append(TraceConstants.CONTENT_SPLITOR) + .append("topic-test").append(TraceConstants.CONTENT_SPLITOR) + .append("AC1415116D1418B4AAC217FE1B4E0000").append(TraceConstants.CONTENT_SPLITOR) + .append("Tags").append(TraceConstants.CONTENT_SPLITOR) + .append("Keys").append(TraceConstants.CONTENT_SPLITOR) + .append("127.0.0.1:10911").append(TraceConstants.CONTENT_SPLITOR) + .append(26).append(TraceConstants.CONTENT_SPLITOR) + .append(245).append(TraceConstants.CONTENT_SPLITOR) + .append(MessageType.Normal_Msg.ordinal()).append(TraceConstants.CONTENT_SPLITOR) + .append("0A9A002600002A9F0000000000002329").append(TraceConstants.CONTENT_SPLITOR) + .append(true).append(TraceConstants.FIELD_SPLITOR) + .toString(); + MessageExt message = new MessageExt(); + message.setBody(messageBody.getBytes(Charsets.UTF_8)); String key = "AC1415116D1418B4AAC217FE1B4E0000"; - List traceViews = TraceView.decodeFromTraceTransData(key, messageBody); + List traceViews = TraceView.decodeFromTraceTransData(key, message); Assert.assertEquals(traceViews.size(), 1); Assert.assertEquals(traceViews.get(0).getMsgId(), key); key = "AD4233434334AAC217FEFFD0000"; - traceViews = TraceView.decodeFromTraceTransData(key, messageBody); + traceViews = TraceView.decodeFromTraceTransData(key, message); Assert.assertEquals(traceViews.size(), 0); } } diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgTraceByIdSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgTraceByIdSubCommand.java index bed2763dad..7c2b51fe86 100644 --- a/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgTraceByIdSubCommand.java +++ b/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgTraceByIdSubCommand.java @@ -19,7 +19,6 @@ package org.apache.rocketmq.tools.command.message; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; -import org.apache.commons.codec.Charsets; import org.apache.commons.lang3.time.DateFormatUtils; import org.apache.rocketmq.client.QueryResult; import org.apache.rocketmq.client.exception.MQClientException; @@ -38,7 +37,6 @@ import java.util.Iterator; import java.util.List; import java.util.Map; - public class QueryMsgTraceByIdSubCommand implements SubCommand { @Override @@ -74,13 +72,13 @@ public class QueryMsgTraceByIdSubCommand implements SubCommand { } private void queryTraceByMsgId(final DefaultMQAdminExt admin, String msgId) - throws MQClientException, InterruptedException { + throws MQClientException, InterruptedException { admin.start(); QueryResult queryResult = admin.queryMessage(TopicValidator.RMQ_SYS_TRACE_TOPIC, msgId, 64, 0, System.currentTimeMillis()); List messageList = queryResult.getMessageList(); List traceViews = new ArrayList<>(); for (MessageExt message : messageList) { - List traceView = TraceView.decodeFromTraceTransData(msgId, new String(message.getBody(), Charsets.UTF_8)); + List traceView = TraceView.decodeFromTraceTransData(msgId, message); traceViews.addAll(traceView); } @@ -92,20 +90,20 @@ public class QueryMsgTraceByIdSubCommand implements SubCommand { for (TraceView traceView : traceViews) { if (traceView.getMsgType().equals(TraceType.Pub.name())) { System.out.printf("%-10s %-20s %-20s %-20s %-10s %-10s%n", - "#Type", - "#ProducerGroup", - "#ClientHost", - "#SendTime", - "#CostTimes", - "#Status" + "#Type", + "#ProducerGroup", + "#ClientHost", + "#SendTime", + "#CostTimes", + "#Status" ); System.out.printf("%-10s %-20s %-20s %-20s %-10s %-10s%n", - "Pub", - traceView.getGroupName(), - traceView.getClientHost(), - DateFormatUtils.format(traceView.getTimeStamp(), "yyyy-MM-dd HH:mm:ss"), - traceView.getCostTime() + "ms", - traceView.getStatus() + "Pub", + traceView.getGroupName(), + traceView.getClientHost(), + DateFormatUtils.format(traceView.getTimeStamp(), "yyyy-MM-dd HH:mm:ss"), + traceView.getCostTime() + "ms", + traceView.getStatus() ); System.out.printf("\n"); } @@ -124,22 +122,22 @@ public class QueryMsgTraceByIdSubCommand implements SubCommand { Iterator consumers = consumerTraceMap.keySet().iterator(); while (consumers.hasNext()) { System.out.printf("%-10s %-20s %-20s %-20s %-10s %-10s%n", - "#Type", - "#ConsumerGroup", - "#ClientHost", - "#ConsumerTime", - "#CostTimes", - "#Status" + "#Type", + "#ConsumerGroup", + "#ClientHost", + "#ConsumerTime", + "#CostTimes", + "#Status" ); List consumerTraces = consumerTraceMap.get(consumers.next()); for (TraceView traceView : consumerTraces) { System.out.printf("%-10s %-20s %-20s %-20s %-10s %-10s%n", - "Sub", - traceView.getGroupName(), - traceView.getClientHost(), - DateFormatUtils.format(traceView.getTimeStamp(), "yyyy-MM-dd HH:mm:ss"), - traceView.getCostTime() + "ms", - traceView.getStatus() + "Sub", + traceView.getGroupName(), + traceView.getClientHost(), + DateFormatUtils.format(traceView.getTimeStamp(), "yyyy-MM-dd HH:mm:ss"), + traceView.getCostTime() + "ms", + traceView.getStatus() ); } System.out.printf("\n"); From 5829fbd44965ed6d2559302f7ec5a1dd5b2a9de1 Mon Sep 17 00:00:00 2001 From: yuz10 <845238369@qq.com> Date: Fri, 21 May 2021 23:12:43 +0800 Subject: [PATCH 43/65] #2935 check kv config should not be null --- .../rocketmq/namesrv/processor/DefaultRequestProcessor.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/namesrv/src/main/java/org/apache/rocketmq/namesrv/processor/DefaultRequestProcessor.java b/namesrv/src/main/java/org/apache/rocketmq/namesrv/processor/DefaultRequestProcessor.java index 44a2b6f261..f8bc55e7aa 100644 --- a/namesrv/src/main/java/org/apache/rocketmq/namesrv/processor/DefaultRequestProcessor.java +++ b/namesrv/src/main/java/org/apache/rocketmq/namesrv/processor/DefaultRequestProcessor.java @@ -140,6 +140,11 @@ public class DefaultRequestProcessor extends AsyncNettyRequestProcessor implemen final PutKVConfigRequestHeader requestHeader = (PutKVConfigRequestHeader) request.decodeCommandCustomHeader(PutKVConfigRequestHeader.class); + if (requestHeader.getNamespace() == null || requestHeader.getKey() == null) { + response.setCode(ResponseCode.SYSTEM_ERROR); + response.setRemark("namespace or key is null"); + return response; + } this.namesrvController.getKvConfigManager().putKVConfig( requestHeader.getNamespace(), requestHeader.getKey(), From 952772fa1bc48bb27b5e0ee586b2b086c2ef611a Mon Sep 17 00:00:00 2001 From: rongtong Date: Mon, 24 May 2021 19:47:51 +0800 Subject: [PATCH 44/65] fix(dledger): reload the delay offset when master changed (#2518) --- .../apache/rocketmq/store/schedule/ScheduleMessageService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/store/src/main/java/org/apache/rocketmq/store/schedule/ScheduleMessageService.java b/store/src/main/java/org/apache/rocketmq/store/schedule/ScheduleMessageService.java index a78c5f8a82..bacae1e80b 100644 --- a/store/src/main/java/org/apache/rocketmq/store/schedule/ScheduleMessageService.java +++ b/store/src/main/java/org/apache/rocketmq/store/schedule/ScheduleMessageService.java @@ -112,6 +112,7 @@ public class ScheduleMessageService extends ConfigManager { public void start() { if (started.compareAndSet(false, true)) { + super.load(); this.timer = new Timer("ScheduleMessageTimerThread", true); for (Map.Entry entry : this.delayLevelTable.entrySet()) { Integer level = entry.getKey(); From 99b42f24f02c8a867120dcf7541390ada9cce291 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=9C=E9=98=B3?= Date: Mon, 24 May 2021 23:02:26 +0800 Subject: [PATCH 45/65] [ISSUE #2735] QueryMsgByUniqueKey tool should return all messages with same unique key --- .../rocketmq/client/impl/MQAdminImpl.java | 16 ++- .../tools/admin/DefaultMQAdminExt.java | 12 +- .../tools/admin/DefaultMQAdminExtImpl.java | 12 +- .../QueryMsgByUniqueKeySubCommand.java | 126 +++++++----------- 4 files changed, 75 insertions(+), 91 deletions(-) diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/MQAdminImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/MQAdminImpl.java index ca89d61306..e46527a5e0 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/MQAdminImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/MQAdminImpl.java @@ -265,17 +265,23 @@ public class MQAdminImpl { messageId.getOffset(), timeoutMillis); } - public QueryResult queryMessage(String topic, String key, int maxNum, long begin, - long end) throws MQClientException, - InterruptedException { + public QueryResult queryMessage(String topic, String key, int maxNum, long begin, long end) + throws MQClientException, InterruptedException { + return queryMessage(topic, key, maxNum, begin, end, false); } + public QueryResult queryMessageByUniqKey(String topic, String uniqKey, int maxNum, long begin, long end) + throws MQClientException, InterruptedException { + + return queryMessage(topic, uniqKey, maxNum, begin, end, true); + } + public MessageExt queryMessageByUniqKey(String topic, String uniqKey) throws InterruptedException, MQClientException { - QueryResult qr = this.queryMessage(topic, uniqKey, 32, - MessageClientIDSetter.getNearlyTimeFromID(uniqKey).getTime() - 1000, Long.MAX_VALUE, true); + QueryResult qr = queryMessageByUniqKey(topic, uniqKey, 32, + MessageClientIDSetter.getNearlyTimeFromID(uniqKey).getTime() - 1000, Long.MAX_VALUE); if (qr != null && qr.getMessageList() != null && qr.getMessageList().size() > 0) { return qr.getMessageList().get(0); } else { diff --git a/tools/src/main/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExt.java b/tools/src/main/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExt.java index 1ca3fe4c2f..cab36e7d0b 100644 --- a/tools/src/main/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExt.java +++ b/tools/src/main/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExt.java @@ -128,12 +128,18 @@ public class DefaultMQAdminExt extends ClientConfig implements MQAdminExt { } @Override - public QueryResult queryMessage(String topic, String key, int maxNum, long begin, - long end) throws MQClientException, - InterruptedException { + public QueryResult queryMessage(String topic, String key, int maxNum, long begin, long end) + throws MQClientException, InterruptedException { + return defaultMQAdminExtImpl.queryMessage(topic, key, maxNum, begin, end); } + public QueryResult queryMessageByUniqueKey(String topic, String key, int maxNum, long begin, long end) + throws MQClientException, InterruptedException { + + return defaultMQAdminExtImpl.queryMessageByUniqKey(topic, key, maxNum, begin, end); + } + @Override public void start() throws MQClientException { defaultMQAdminExtImpl.start(); diff --git a/tools/src/main/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExtImpl.java b/tools/src/main/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExtImpl.java index 22d4005ce9..ddd99b28e4 100644 --- a/tools/src/main/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExtImpl.java +++ b/tools/src/main/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExtImpl.java @@ -991,12 +991,18 @@ public class DefaultMQAdminExtImpl implements MQAdminExt, MQAdminExtInner { } @Override - public QueryResult queryMessage(String topic, String key, int maxNum, long begin, - long end) throws MQClientException, - InterruptedException { + public QueryResult queryMessage(String topic, String key, int maxNum, long begin, long end) + throws MQClientException, InterruptedException { + return this.mqClientInstance.getMQAdminImpl().queryMessage(topic, key, maxNum, begin, end); } + public QueryResult queryMessageByUniqKey(String topic, String key, int maxNum, long begin, + long end) throws MQClientException, + InterruptedException { + return this.mqClientInstance.getMQAdminImpl().queryMessageByUniqKey(topic, key, maxNum, begin, end); + } + @Override public void updateConsumeOffset(String brokerAddr, String consumeGroup, MessageQueue mq, long offset) throws RemotingException, InterruptedException, MQBrokerException { diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgByUniqueKeySubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgByUniqueKeySubCommand.java index 9ad075082f..badac11446 100644 --- a/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgByUniqueKeySubCommand.java +++ b/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgByUniqueKeySubCommand.java @@ -24,6 +24,7 @@ import java.util.List; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import org.apache.rocketmq.client.QueryResult; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.UtilAll; @@ -57,82 +58,40 @@ public class QueryMsgByUniqueKeySubCommand implements SubCommand { } } - public static void queryById(final DefaultMQAdminExt admin, final String topic, - final String msgId) throws MQClientException, - RemotingException, MQBrokerException, InterruptedException, IOException { - MessageExt msg = admin.viewMessage(topic, msgId); + public static void queryById(final DefaultMQAdminExt admin, final String topic, final String msgId, + final boolean showAll) throws MQClientException, + RemotingException, MQBrokerException, InterruptedException, IOException { - String bodyTmpFilePath = createBodyFile(msg); + QueryResult queryResult = admin.queryMessageByUniqueKey(topic, msgId, 32, 0, Long.MAX_VALUE); + assert queryResult != null; + List list = queryResult.getMessageList(); + list.sort((o1, o2) -> (int) (o1.getStoreTimestamp() - o2.getStoreTimestamp())); + for (int i = 0; i < (showAll ? list.size() : 1); i++) { + showMessage(admin, list.get(i), i); + } + } - System.out.printf("%-20s %s%n", - "Topic:", - msg.getTopic() - ); + private static void showMessage(final DefaultMQAdminExt admin, MessageExt msg, int index) throws IOException { + String bodyTmpFilePath = createBodyFile(msg, index); - System.out.printf("%-20s %s%n", - "Tags:", - "[" + msg.getTags() + "]" - ); + final String strFormat = "%-20s %s%n"; + final String intFormat = "%-20s %d%n"; - System.out.printf("%-20s %s%n", - "Keys:", - "[" + msg.getKeys() + "]" - ); - - System.out.printf("%-20s %d%n", - "Queue ID:", - msg.getQueueId() - ); - - System.out.printf("%-20s %d%n", - "Queue Offset:", - msg.getQueueOffset() - ); - - System.out.printf("%-20s %d%n", - "CommitLog Offset:", - msg.getCommitLogOffset() - ); - - System.out.printf("%-20s %d%n", - "Reconsume Times:", - msg.getReconsumeTimes() - ); - - System.out.printf("%-20s %s%n", - "Born Timestamp:", - UtilAll.timeMillisToHumanString2(msg.getBornTimestamp()) - ); - - System.out.printf("%-20s %s%n", - "Store Timestamp:", - UtilAll.timeMillisToHumanString2(msg.getStoreTimestamp()) - ); - - System.out.printf("%-20s %s%n", - "Born Host:", - RemotingHelper.parseSocketAddressAddr(msg.getBornHost()) - ); - - System.out.printf("%-20s %s%n", - "Store Host:", - RemotingHelper.parseSocketAddressAddr(msg.getStoreHost()) - ); - - System.out.printf("%-20s %d%n", - "System Flag:", - msg.getSysFlag() - ); - - System.out.printf("%-20s %s%n", - "Properties:", - msg.getProperties() != null ? msg.getProperties().toString() : "" - ); - - System.out.printf("%-20s %s%n", - "Message Body Path:", - bodyTmpFilePath - ); + System.out.printf(strFormat, "Topic:", msg.getTopic()); + System.out.printf(strFormat, "Tags:", "[" + msg.getTags() + "]"); + System.out.printf(strFormat, "Keys:", "[" + msg.getKeys() + "]"); + System.out.printf(intFormat, "Queue ID:", msg.getQueueId()); + System.out.printf(intFormat, "Queue Offset:", msg.getQueueOffset()); + System.out.printf(intFormat, "CommitLog Offset:", msg.getCommitLogOffset()); + System.out.printf(intFormat, "Reconsume Times:", msg.getReconsumeTimes()); + System.out.printf(strFormat, "Born Timestamp:", UtilAll.timeMillisToHumanString2(msg.getBornTimestamp())); + System.out.printf(strFormat, "Store Timestamp:", UtilAll.timeMillisToHumanString2(msg.getStoreTimestamp())); + System.out.printf(strFormat, "Born Host:", RemotingHelper.parseSocketAddressAddr(msg.getBornHost())); + System.out.printf(strFormat, "Store Host:", RemotingHelper.parseSocketAddressAddr(msg.getStoreHost())); + System.out.printf(intFormat, "System Flag:", msg.getSysFlag()); + System.out.printf(strFormat, "Properties:", + msg.getProperties() != null ? msg.getProperties().toString() : ""); + System.out.printf(strFormat, "Message Body Path:", bodyTmpFilePath); try { List mtdList = admin.messageTrackDetail(msg); @@ -149,18 +108,21 @@ public class QueryMsgByUniqueKeySubCommand implements SubCommand { } } - private static String createBodyFile(MessageExt msg) throws IOException { + private static String createBodyFile(MessageExt msg, int index) throws IOException { DataOutputStream dos = null; try { - String bodyTmpFilePath = "/tmp/rocketmq/msgbodys"; - File file = new File(bodyTmpFilePath); + StringBuffer bodyTmpFilePath = new StringBuffer("/tmp/rocketmq/msgbodys"); + File file = new File(bodyTmpFilePath.toString()); if (!file.exists()) { file.mkdirs(); } - bodyTmpFilePath = bodyTmpFilePath + "/" + msg.getMsgId(); - dos = new DataOutputStream(new FileOutputStream(bodyTmpFilePath)); + bodyTmpFilePath.append("/").append(msg.getMsgId()); + if (index > 0) { + bodyTmpFilePath.append("_" + index); + } + dos = new DataOutputStream(new FileOutputStream(bodyTmpFilePath.toString())); dos.write(msg.getBody()); - return bodyTmpFilePath; + return bodyTmpFilePath.toString(); } finally { if (dos != null) dos.close(); @@ -195,6 +157,10 @@ public class QueryMsgByUniqueKeySubCommand implements SubCommand { opt.setRequired(true); options.addOption(opt); + opt = new Option("a", "showAll", false, "Print all message, the limit is 32"); + opt.setRequired(false); + options.addOption(opt); + return options; } @@ -202,11 +168,11 @@ public class QueryMsgByUniqueKeySubCommand implements SubCommand { public void execute(CommandLine commandLine, Options options, RPCHook rpcHook) throws SubCommandException { try { - defaultMQAdminExt = createMQAdminExt(rpcHook); final String msgId = commandLine.getOptionValue('i').trim(); final String topic = commandLine.getOptionValue('t').trim(); + final boolean showAll = commandLine.hasOption('a'); if (commandLine.hasOption('g') && commandLine.hasOption('d')) { final String consumerGroup = commandLine.getOptionValue('g').trim(); final String clientId = commandLine.getOptionValue('d').trim(); @@ -214,7 +180,7 @@ public class QueryMsgByUniqueKeySubCommand implements SubCommand { defaultMQAdminExt.consumeMessageDirectly(consumerGroup, clientId, topic, msgId); System.out.printf("%s", result); } else { - queryById(defaultMQAdminExt, topic, msgId); + queryById(defaultMQAdminExt, topic, msgId, showAll); } } catch (Exception e) { throw new SubCommandException(this.getClass().getSimpleName() + " command failed", e); From 672f581953673e4e47ef7153677937ddbe50e6c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=9C=E9=98=B3?= Date: Mon, 24 May 2021 23:11:14 +0800 Subject: [PATCH 46/65] [ISSUE #2735] QueryMsgByUniqueKey tool should return all messages with same unique key --- .../apache/rocketmq/tools/admin/DefaultMQAdminExtImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/src/main/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExtImpl.java b/tools/src/main/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExtImpl.java index ddd99b28e4..c8ca8e791e 100644 --- a/tools/src/main/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExtImpl.java +++ b/tools/src/main/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExtImpl.java @@ -998,8 +998,8 @@ public class DefaultMQAdminExtImpl implements MQAdminExt, MQAdminExtInner { } public QueryResult queryMessageByUniqKey(String topic, String key, int maxNum, long begin, - long end) throws MQClientException, - InterruptedException { + long end) throws MQClientException, InterruptedException { + return this.mqClientInstance.getMQAdminImpl().queryMessageByUniqKey(topic, key, maxNum, begin, end); } From c34796bd06321560c88deab8bfa7a1ab560ddeb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=9C=E9=98=B3?= Date: Tue, 25 May 2021 21:10:41 +0800 Subject: [PATCH 47/65] [ISSUE #2735] QueryMsgByUniqueKey tool should return all messages with same unique key --- .../org/apache/rocketmq/tools/admin/DefaultMQAdminExt.java | 2 +- .../tools/command/message/QueryMsgByUniqueKeySubCommand.java | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/src/main/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExt.java b/tools/src/main/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExt.java index cab36e7d0b..af2bd715a1 100644 --- a/tools/src/main/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExt.java +++ b/tools/src/main/java/org/apache/rocketmq/tools/admin/DefaultMQAdminExt.java @@ -134,7 +134,7 @@ public class DefaultMQAdminExt extends ClientConfig implements MQAdminExt { return defaultMQAdminExtImpl.queryMessage(topic, key, maxNum, begin, end); } - public QueryResult queryMessageByUniqueKey(String topic, String key, int maxNum, long begin, long end) + public QueryResult queryMessageByUniqKey(String topic, String key, int maxNum, long begin, long end) throws MQClientException, InterruptedException { return defaultMQAdminExtImpl.queryMessageByUniqKey(topic, key, maxNum, begin, end); diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgByUniqueKeySubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgByUniqueKeySubCommand.java index badac11446..8c7bec616d 100644 --- a/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgByUniqueKeySubCommand.java +++ b/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgByUniqueKeySubCommand.java @@ -62,9 +62,12 @@ public class QueryMsgByUniqueKeySubCommand implements SubCommand { final boolean showAll) throws MQClientException, RemotingException, MQBrokerException, InterruptedException, IOException { - QueryResult queryResult = admin.queryMessageByUniqueKey(topic, msgId, 32, 0, Long.MAX_VALUE); + QueryResult queryResult = admin.queryMessageByUniqKey(topic, msgId, 32, 0, Long.MAX_VALUE); assert queryResult != null; List list = queryResult.getMessageList(); + if (list == null || list.size() == 0) { + return; + } list.sort((o1, o2) -> (int) (o1.getStoreTimestamp() - o2.getStoreTimestamp())); for (int i = 0; i < (showAll ? list.size() : 1); i++) { showMessage(admin, list.get(i), i); From 6ab6afd006fcef784b3e3202b497080fcf4d023a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=9C=E9=98=B3?= Date: Tue, 25 May 2021 22:02:43 +0800 Subject: [PATCH 48/65] [ISSUE #2708] Client may submit wrong offset when network instability --- .../consumer/DefaultLitePullConsumerImpl.java | 15 +++++++++---- .../consumer/DefaultMQPushConsumerImpl.java | 7 ++++++- .../client/impl/consumer/RebalanceImpl.java | 20 +++++++++++++++++- .../impl/consumer/RebalanceLitePullImpl.java | 18 ++++++++++++++-- .../impl/consumer/RebalancePullImpl.java | 7 +++++++ .../impl/consumer/RebalancePushImpl.java | 21 ++++++++++++++++--- 6 files changed, 77 insertions(+), 11 deletions(-) diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultLitePullConsumerImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultLitePullConsumerImpl.java index 280de46ff2..32b05d55e2 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultLitePullConsumerImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultLitePullConsumerImpl.java @@ -626,9 +626,9 @@ public class DefaultLitePullConsumerImpl implements MQConsumerInner { } } - private long fetchConsumeOffset(MessageQueue messageQueue) { + private long fetchConsumeOffset(MessageQueue messageQueue) throws MQClientException { checkServiceState(); - long offset = this.rebalanceImpl.computePullFromWhere(messageQueue); + long offset = this.rebalanceImpl.computePullFromWhereWithException(messageQueue); return offset; } @@ -652,7 +652,7 @@ public class DefaultLitePullConsumerImpl implements MQConsumerInner { } } - private long nextPullOffset(MessageQueue messageQueue) { + private long nextPullOffset(MessageQueue messageQueue) throws MQClientException { long offset = -1; long seekOffset = assignedMessageQueue.getSeekOffset(messageQueue); if (seekOffset != -1) { @@ -739,7 +739,14 @@ public class DefaultLitePullConsumerImpl implements MQConsumerInner { return; } - long offset = nextPullOffset(messageQueue); + long offset = 0L; + try { + offset = nextPullOffset(messageQueue); + } catch (MQClientException e) { + log.error("get next pull offset failed, maybe timeout exception"); + return; + } + if (this.isCancelled() || processQueue.isDropped()) { return; } diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPushConsumerImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPushConsumerImpl.java index e08f780030..52cd25fa8a 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPushConsumerImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPushConsumerImpl.java @@ -270,7 +270,12 @@ public class DefaultMQPushConsumerImpl implements MQConsumerInner { } else { if (processQueue.isLocked()) { if (!pullRequest.isLockedFirst()) { - final long offset = this.rebalanceImpl.computePullFromWhere(pullRequest.getMessageQueue()); + long offset = -1L; + try { + offset = this.rebalanceImpl.computePullFromWhereWithException(pullRequest.getMessageQueue()); + } catch (MQClientException e) { + log.error("compute consume offset failed, maybe timeout exception"); + } boolean brokerBusy = offset < pullRequest.getNextOffset(); log.info("the first time to pull message, so fix offset from broker. pullRequest: {} NewOffset: {} brokerBusy: {}", pullRequest, offset, brokerBusy); diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalanceImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalanceImpl.java index b8972a92e8..8d31314b0f 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalanceImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalanceImpl.java @@ -28,6 +28,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy; +import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.impl.FindBrokerResult; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.client.log.ClientLogger; @@ -373,7 +374,15 @@ public abstract class RebalanceImpl { this.removeDirtyOffset(mq); ProcessQueue pq = new ProcessQueue(); - long nextOffset = this.computePullFromWhere(mq); + + long nextOffset = 0L; + try { + nextOffset = this.computePullFromWhereWithException(mq); + } catch (MQClientException e) { + log.info("doRebalance, {}, compute offset failed, {}", consumerGroup, mq); + continue; + } + if (nextOffset >= 0) { ProcessQueue pre = this.processQueueTable.putIfAbsent(mq, pq); if (pre != null) { @@ -408,8 +417,17 @@ public abstract class RebalanceImpl { public abstract void removeDirtyOffset(final MessageQueue mq); + /** + * When the network is unstable, using this interface may return wrong offset. + * It is recommended to use computePullFromWhereWithException instead. + * @param mq + * @return offset + */ + @Deprecated public abstract long computePullFromWhere(final MessageQueue mq); + public abstract long computePullFromWhereWithException(final MessageQueue mq) throws MQClientException; + public abstract void dispatchPullRequest(final List pullRequestList); public void removeProcessQueue(final MessageQueue mq) { diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalanceLitePullImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalanceLitePullImpl.java index 9d1ea7492e..286c684e43 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalanceLitePullImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalanceLitePullImpl.java @@ -74,8 +74,20 @@ public class RebalanceLitePullImpl extends RebalanceImpl { this.litePullConsumerImpl.getOffsetStore().removeOffset(mq); } + @Deprecated @Override public long computePullFromWhere(MessageQueue mq) { + long result = -1L; + try { + result = computePullFromWhereWithException(mq); + } catch (MQClientException e) { + log.warn("Compute consume offset exception, mq={}", mq); + } + return result; + } + + @Override + public long computePullFromWhereWithException(MessageQueue mq) throws MQClientException { ConsumeFromWhere consumeFromWhere = litePullConsumerImpl.getDefaultLitePullConsumer().getConsumeFromWhere(); long result = -1; switch (consumeFromWhere) { @@ -118,7 +130,8 @@ public class RebalanceLitePullImpl extends RebalanceImpl { try { result = this.mQClientFactory.getMQAdminImpl().maxOffset(mq); } catch (MQClientException e) { - result = -1; + log.warn("Compute consume offset from last offset exception, mq={}, exception={}", mq, e); + throw e; } } else { try { @@ -126,7 +139,8 @@ public class RebalanceLitePullImpl extends RebalanceImpl { UtilAll.YYYYMMDDHHMMSS).getTime(); result = this.mQClientFactory.getMQAdminImpl().searchOffset(mq, timestamp); } catch (MQClientException e) { - result = -1; + log.warn("Compute consume offset from last offset exception, mq={}, exception={}", mq, e); + throw e; } } } else { diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalancePullImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalancePullImpl.java index 9dd408c1d1..6df7eb7f00 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalancePullImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalancePullImpl.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Set; import org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy; import org.apache.rocketmq.client.consumer.MessageQueueListener; +import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.protocol.heartbeat.ConsumeType; @@ -68,11 +69,17 @@ public class RebalancePullImpl extends RebalanceImpl { this.defaultMQPullConsumerImpl.getOffsetStore().removeOffset(mq); } + @Deprecated @Override public long computePullFromWhere(MessageQueue mq) { return 0; } + @Override + public long computePullFromWhereWithException(MessageQueue mq) throws MQClientException { + return 0; + } + @Override public void dispatchPullRequest(List pullRequestList) { } diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalancePushImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalancePushImpl.java index 9582391622..666b696ffa 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalancePushImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalancePushImpl.java @@ -137,8 +137,20 @@ public class RebalancePushImpl extends RebalanceImpl { this.defaultMQPushConsumerImpl.getOffsetStore().removeOffset(mq); } + @Deprecated @Override public long computePullFromWhere(MessageQueue mq) { + long result = -1L; + try { + result = computePullFromWhereWithException(mq); + } catch (MQClientException e) { + log.warn("Compute consume offset exception, mq={}", mq); + } + return result; + } + + @Override + public long computePullFromWhereWithException(MessageQueue mq) throws MQClientException { long result = -1; final ConsumeFromWhere consumeFromWhere = this.defaultMQPushConsumerImpl.getDefaultMQPushConsumer().getConsumeFromWhere(); final OffsetStore offsetStore = this.defaultMQPushConsumerImpl.getOffsetStore(); @@ -159,7 +171,8 @@ public class RebalancePushImpl extends RebalanceImpl { try { result = this.mQClientFactory.getMQAdminImpl().maxOffset(mq); } catch (MQClientException e) { - result = -1; + log.warn("Compute consume offset from last offset exception, mq={}, exception={}", mq, e); + throw e; } } } else { @@ -187,7 +200,8 @@ public class RebalancePushImpl extends RebalanceImpl { try { result = this.mQClientFactory.getMQAdminImpl().maxOffset(mq); } catch (MQClientException e) { - result = -1; + log.warn("Compute consume offset from last offset exception, mq={}, exception={}", mq, e); + throw e; } } else { try { @@ -195,7 +209,8 @@ public class RebalancePushImpl extends RebalanceImpl { UtilAll.YYYYMMDDHHMMSS).getTime(); result = this.mQClientFactory.getMQAdminImpl().searchOffset(mq, timestamp); } catch (MQClientException e) { - result = -1; + log.warn("Compute consume offset from last offset exception, mq={}, exception={}", mq, e); + throw e; } } } else { From da589b19755c0c73830718f5d3917d4fdba70e26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=9C=E9=98=B3?= Date: Tue, 25 May 2021 22:13:12 +0800 Subject: [PATCH 49/65] [ISSUE #2708] Client may submit wrong offset when network instability --- .../impl/consumer/DefaultLitePullConsumerImpl.java | 5 ++++- .../impl/consumer/DefaultMQPushConsumerImpl.java | 8 +++++--- .../rocketmq/client/impl/consumer/PullRequest.java | 10 +++++----- .../rocketmq/client/impl/consumer/RebalanceImpl.java | 2 +- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultLitePullConsumerImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultLitePullConsumerImpl.java index 32b05d55e2..1e6b8dc996 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultLitePullConsumerImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultLitePullConsumerImpl.java @@ -115,6 +115,8 @@ public class DefaultLitePullConsumerImpl implements MQConsumerInner { */ private static final long PULL_TIME_DELAY_MILLS_WHEN_PAUSE = 1000; + private static final long PULL_TIME_DELAY_MILLS_ON_EXCEPTION = 3 * 1000; + private DefaultLitePullConsumer defaultLitePullConsumer; private final ConcurrentMap taskTable = @@ -743,7 +745,8 @@ public class DefaultLitePullConsumerImpl implements MQConsumerInner { try { offset = nextPullOffset(messageQueue); } catch (MQClientException e) { - log.error("get next pull offset failed, maybe timeout exception"); + log.error("Failed to get next pull offset", e); + scheduledThreadPoolExecutor.schedule(this, PULL_TIME_DELAY_MILLS_ON_EXCEPTION, TimeUnit.MILLISECONDS); return; } diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPushConsumerImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPushConsumerImpl.java index 52cd25fa8a..e3db9a35a3 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPushConsumerImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPushConsumerImpl.java @@ -269,12 +269,14 @@ public class DefaultMQPushConsumerImpl implements MQConsumerInner { } } else { if (processQueue.isLocked()) { - if (!pullRequest.isLockedFirst()) { + if (!pullRequest.isPreviouslyLocked()) { long offset = -1L; try { offset = this.rebalanceImpl.computePullFromWhereWithException(pullRequest.getMessageQueue()); } catch (MQClientException e) { - log.error("compute consume offset failed, maybe timeout exception"); + this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException); + log.error("Failed to compute pull offset", e); + return; } boolean brokerBusy = offset < pullRequest.getNextOffset(); log.info("the first time to pull message, so fix offset from broker. pullRequest: {} NewOffset: {} brokerBusy: {}", @@ -284,7 +286,7 @@ public class DefaultMQPushConsumerImpl implements MQConsumerInner { pullRequest, offset); } - pullRequest.setLockedFirst(true); + pullRequest.setPreviouslyLocked(true); pullRequest.setNextOffset(offset); } } else { diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullRequest.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullRequest.java index 10aded0762..bf03ec38c3 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullRequest.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/PullRequest.java @@ -23,14 +23,14 @@ public class PullRequest { private MessageQueue messageQueue; private ProcessQueue processQueue; private long nextOffset; - private boolean lockedFirst = false; + private boolean previouslyLocked = false; - public boolean isLockedFirst() { - return lockedFirst; + public boolean isPreviouslyLocked() { + return previouslyLocked; } - public void setLockedFirst(boolean lockedFirst) { - this.lockedFirst = lockedFirst; + public void setPreviouslyLocked(boolean previouslyLocked) { + this.previouslyLocked = previouslyLocked; } public String getConsumerGroup() { diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalanceImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalanceImpl.java index 8d31314b0f..833d465a47 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalanceImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/RebalanceImpl.java @@ -375,7 +375,7 @@ public abstract class RebalanceImpl { this.removeDirtyOffset(mq); ProcessQueue pq = new ProcessQueue(); - long nextOffset = 0L; + long nextOffset = -1L; try { nextOffset = this.computePullFromWhereWithException(mq); } catch (MQClientException e) { From 707d1b85d2443361d3cbc89e6d798a67b7a79b76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=9C=E9=98=B3?= Date: Tue, 25 May 2021 22:13:45 +0800 Subject: [PATCH 50/65] [ISSUE #2708] Client may submit wrong offset when network instability, add more log --- .../client/impl/consumer/DefaultMQPushConsumerImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPushConsumerImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPushConsumerImpl.java index e3db9a35a3..bb0b7f1043 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPushConsumerImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/DefaultMQPushConsumerImpl.java @@ -275,7 +275,7 @@ public class DefaultMQPushConsumerImpl implements MQConsumerInner { offset = this.rebalanceImpl.computePullFromWhereWithException(pullRequest.getMessageQueue()); } catch (MQClientException e) { this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException); - log.error("Failed to compute pull offset", e); + log.error("Failed to compute pull offset, pullResult: {}", pullRequest, e); return; } boolean brokerBusy = offset < pullRequest.getNextOffset(); From 7a6edb7c649948e0223d42e5d9e66f9360031a0f Mon Sep 17 00:00:00 2001 From: yuz10 <845238369@qq.com> Date: Wed, 26 May 2021 09:59:41 +0800 Subject: [PATCH 51/65] [ISSUE #2926] fix fast-try.sh fail to run in ubuntu (#2927) * #2926 fix fast-try.sh fail to run in ubuntu * update fast-try.sh --- distribution/bin/dledger/fast-try.sh | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/distribution/bin/dledger/fast-try.sh b/distribution/bin/dledger/fast-try.sh index ff8a96036d..acdde71b8d 100644 --- a/distribution/bin/dledger/fast-try.sh +++ b/distribution/bin/dledger/fast-try.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env bash # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with @@ -20,25 +20,25 @@ CURRENT_DIR="$(cd "$(dirname "$0")"; pwd)" RMQ_DIR=$CURRENT_DIR/../.. cd $RMQ_DIR -function startNameserver() { +startNameserver() { export JAVA_OPT_EXT=" -Xms512m -Xmx512m " nohup bin/mqnamesrv & } -function startBroker() { +startBroker() { export JAVA_OPT_EXT=" -Xms1g -Xmx1g " conf_name=$1 nohup bin/mqbroker -c $conf_name & } -function stopNameserver() { +stopNameserver() { PIDS=$(ps -ef|grep java|grep NamesrvStartup|grep -v grep|awk '{print $2}') if [ ! -z "$PIDS" ]; then kill -s TERM $PIDS fi } -function stopBroker() { +stopBroker() { conf_name=$1 PIDS=$(ps -ef|grep java|grep BrokerStartup|grep $conf_name|grep -v grep|awk '{print $2}') i=1 @@ -46,7 +46,7 @@ function stopBroker() { do echo "Waiting to kill ..." kill -s TERM $PIDS - ((i=$i+1)) + i=`expr $i + 1` sleep 2 PIDS=$(ps -ef|grep java|grep BrokerStartup|grep $conf_name|grep -v grep|awk '{print $2}') done @@ -56,7 +56,7 @@ function stopBroker() { fi } -function stopAll() { +stopAll() { ps -ef|grep java|grep BrokerStartup|grep -v grep|awk '{print $2}'|xargs kill stopNameserver stopBroker ./conf/dledger/broker-n0.conf @@ -64,18 +64,18 @@ function stopAll() { stopBroker ./conf/dledger/broker-n2.conf } -function startAll() { +startAll() { startNameserver startBroker ./conf/dledger/broker-n0.conf startBroker ./conf/dledger/broker-n1.conf startBroker ./conf/dledger/broker-n2.conf } -function checkConf() { +checkConf() { if [ ! -f ./conf/dledger/broker-n0.conf -o ! -f ./conf/dledger/broker-n1.conf -o ! -f ./conf/dledger/broker-n2.conf ]; then echo "Make sure the ./conf/dledger/broker-n0.conf, ./conf/dledger/broker-n1.conf, ./conf/dledger/broker-n2.conf exists" - exit -1 - fi + exit 1 + fi } @@ -83,7 +83,7 @@ function checkConf() { ## Main if [ $# -lt 1 ]; then echo "Usage: sh $0 start|stop" - exit -1 + exit 1 fi action=$1 checkConf From 4f7e83128fb12d6ab98460029972728ad62d8e0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=9C=E9=98=B3?= Date: Wed, 26 May 2021 10:18:10 +0800 Subject: [PATCH 52/65] [ISSUE #2708] Fix unit test --- .../consumer/DefaultMQPushConsumerTest.java | 96 ++++++++++++------- 1 file changed, 60 insertions(+), 36 deletions(-) diff --git a/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java b/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java index c2c60824ec..20efd66478 100644 --- a/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java @@ -16,17 +16,6 @@ */ package org.apache.rocketmq.client.consumer; -import java.io.ByteArrayOutputStream; -import java.net.InetSocketAddress; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; @@ -48,6 +37,7 @@ import org.apache.rocketmq.client.impl.consumer.PullMessageService; import org.apache.rocketmq.client.impl.consumer.PullRequest; import org.apache.rocketmq.client.impl.consumer.PullResultExt; import org.apache.rocketmq.client.impl.consumer.RebalanceImpl; +import org.apache.rocketmq.client.impl.consumer.RebalancePushImpl; import org.apache.rocketmq.client.impl.factory.MQClientInstance; import org.apache.rocketmq.common.message.MessageClientExt; import org.apache.rocketmq.common.message.MessageDecoder; @@ -66,6 +56,18 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.junit.MockitoJUnitRunner; import org.mockito.stubbing.Answer; +import java.io.ByteArrayOutputStream; +import java.net.InetSocketAddress; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Fail.failBecauseExceptionWasNotThrown; import static org.mockito.ArgumentMatchers.any; @@ -74,6 +76,7 @@ import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @@ -87,6 +90,7 @@ public class DefaultMQPushConsumerTest { @Mock private MQClientAPIImpl mQClientAPIImpl; private RebalanceImpl rebalanceImpl; + private RebalancePushImpl rebalancePushImpl; private DefaultMQPushConsumer pushConsumer; @Before @@ -96,24 +100,24 @@ public class DefaultMQPushConsumerTest { factoryTable.clear(); when(mQClientAPIImpl.pullMessage(anyString(), any(PullMessageRequestHeader.class), - anyLong(), any(CommunicationMode.class), nullable(PullCallback.class))) - .thenAnswer(new Answer() { - @Override - public PullResult answer(InvocationOnMock mock) throws Throwable { - PullMessageRequestHeader requestHeader = mock.getArgument(1); - MessageClientExt messageClientExt = new MessageClientExt(); - messageClientExt.setTopic(topic); - messageClientExt.setQueueId(0); - messageClientExt.setMsgId("123"); - messageClientExt.setBody(new byte[] {'a'}); - messageClientExt.setOffsetMsgId("234"); - messageClientExt.setBornHost(new InetSocketAddress(8080)); - messageClientExt.setStoreHost(new InetSocketAddress(8080)); - PullResult pullResult = createPullResult(requestHeader, PullStatus.FOUND, Collections.singletonList(messageClientExt)); - ((PullCallback) mock.getArgument(4)).onSuccess(pullResult); - return pullResult; - } - }); + anyLong(), any(CommunicationMode.class), nullable(PullCallback.class))) + .thenAnswer(new Answer() { + @Override + public PullResult answer(InvocationOnMock mock) throws Throwable { + PullMessageRequestHeader requestHeader = mock.getArgument(1); + MessageClientExt messageClientExt = new MessageClientExt(); + messageClientExt.setTopic(topic); + messageClientExt.setQueueId(0); + messageClientExt.setMsgId("123"); + messageClientExt.setBody(new byte[]{'a'}); + messageClientExt.setOffsetMsgId("234"); + messageClientExt.setBornHost(new InetSocketAddress(8080)); + messageClientExt.setStoreHost(new InetSocketAddress(8080)); + PullResult pullResult = createPullResult(requestHeader, PullStatus.FOUND, Collections.singletonList(messageClientExt)); + ((PullCallback) mock.getArgument(4)).onSuccess(pullResult); + return pullResult; + } + }); consumerGroup = "FooBarGroup" + System.currentTimeMillis(); @@ -124,12 +128,13 @@ public class DefaultMQPushConsumerTest { pushConsumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, - ConsumeConcurrentlyContext context) { + ConsumeConcurrentlyContext context) { return null; } }); DefaultMQPushConsumerImpl pushConsumerImpl = pushConsumer.getDefaultMQPushConsumerImpl(); + rebalancePushImpl = spy(new RebalancePushImpl(pushConsumer.getDefaultMQPushConsumerImpl())); // suppress updateTopicRouteInfoFromNameServer pushConsumer.changeInstanceNameToPID(); @@ -142,7 +147,8 @@ public class DefaultMQPushConsumerTest { doReturn(new FindBrokerResult("127.0.0.1:10911", false)).when(mQClientFactory).findBrokerAddressInSubscribe(anyString(), anyLong(), anyBoolean()); rebalanceImpl = spy(pushConsumerImpl.getRebalanceImpl()); - doReturn(123L).when(rebalanceImpl).computePullFromWhere(any(MessageQueue.class)); + // doReturn(123L).when(rebalancePushImpl).computePullFromWhere(any(MessageQueue.class)); + doReturn(123L).when(rebalanceImpl).computePullFromWhereWithException(any(MessageQueue.class)); FieldUtils.writeDeclaredField(pushConsumerImpl, "rebalanceImpl", rebalanceImpl, true); Set messageQueueSet = new HashSet(); @@ -170,7 +176,7 @@ public class DefaultMQPushConsumerTest { pushConsumer.getDefaultMQPushConsumerImpl().setConsumeMessageService(new ConsumeMessageConcurrentlyService(pushConsumer.getDefaultMQPushConsumerImpl(), new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, - ConsumeConcurrentlyContext context) { + ConsumeConcurrentlyContext context) { messageAtomic.set(msgs.get(0)); countDownLatch.countDown(); return null; @@ -183,7 +189,7 @@ public class DefaultMQPushConsumerTest { MessageExt msg = messageAtomic.get(); assertThat(msg).isNotNull(); assertThat(msg.getTopic()).isEqualTo(topic); - assertThat(msg.getBody()).isEqualTo(new byte[] {'a'}); + assertThat(msg.getBody()).isEqualTo(new byte[]{'a'}); } @Test @@ -210,7 +216,7 @@ public class DefaultMQPushConsumerTest { MessageExt msg = messageAtomic.get(); assertThat(msg).isNotNull(); assertThat(msg.getTopic()).isEqualTo(topic); - assertThat(msg.getBody()).isEqualTo(new byte[] {'a'}); + assertThat(msg.getBody()).isEqualTo(new byte[]{'a'}); } @Test @@ -287,7 +293,7 @@ public class DefaultMQPushConsumerTest { pushConsumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, - ConsumeConcurrentlyContext context) { + ConsumeConcurrentlyContext context) { return null; } }); @@ -313,11 +319,29 @@ public class DefaultMQPushConsumerTest { } private PullResultExt createPullResult(PullMessageRequestHeader requestHeader, PullStatus pullStatus, - List messageExtList) throws Exception { + List messageExtList) throws Exception { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); for (MessageExt messageExt : messageExtList) { outputStream.write(MessageDecoder.encode(messageExt, false)); } return new PullResultExt(pullStatus, requestHeader.getQueueOffset() + messageExtList.size(), 123, 2048, messageExtList, 0, outputStream.toByteArray()); } + + @Test + public void testPullMessage_ExceptionOccursWhenComputePullFromWhere() throws MQClientException { + doThrow(MQClientException.class).when(rebalancePushImpl).computePullFromWhereWithException(any(MessageQueue.class)); + final CountDownLatch countDownLatch = new CountDownLatch(1); + final MessageExt[] messageExts = new MessageExt[1]; + pushConsumer.getDefaultMQPushConsumerImpl().setConsumeMessageService( + new ConsumeMessageConcurrentlyService(pushConsumer.getDefaultMQPushConsumerImpl(), + (msgs, context) -> { + messageExts[0] = msgs.get(0); + return null; + })); + + pushConsumer.getDefaultMQPushConsumerImpl().setConsumeOrderly(true); + PullMessageService pullMessageService = mQClientFactory.getPullMessageService(); + pullMessageService.executePullRequestImmediately(createPullRequest()); + assertThat(messageExts[0]).isNull(); + } } From 96a4e55a0b8b16813aff2cacc7892b871fe60a61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=9C=E9=98=B3?= Date: Wed, 26 May 2021 10:19:39 +0800 Subject: [PATCH 53/65] [ISSUE #2708] Fix unit test --- .../consumer/DefaultMQPushConsumerTest.java | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java b/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java index 20efd66478..da71d40feb 100644 --- a/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java @@ -100,24 +100,24 @@ public class DefaultMQPushConsumerTest { factoryTable.clear(); when(mQClientAPIImpl.pullMessage(anyString(), any(PullMessageRequestHeader.class), - anyLong(), any(CommunicationMode.class), nullable(PullCallback.class))) - .thenAnswer(new Answer() { - @Override - public PullResult answer(InvocationOnMock mock) throws Throwable { - PullMessageRequestHeader requestHeader = mock.getArgument(1); - MessageClientExt messageClientExt = new MessageClientExt(); - messageClientExt.setTopic(topic); - messageClientExt.setQueueId(0); - messageClientExt.setMsgId("123"); - messageClientExt.setBody(new byte[]{'a'}); - messageClientExt.setOffsetMsgId("234"); - messageClientExt.setBornHost(new InetSocketAddress(8080)); - messageClientExt.setStoreHost(new InetSocketAddress(8080)); - PullResult pullResult = createPullResult(requestHeader, PullStatus.FOUND, Collections.singletonList(messageClientExt)); - ((PullCallback) mock.getArgument(4)).onSuccess(pullResult); - return pullResult; - } - }); + anyLong(), any(CommunicationMode.class), nullable(PullCallback.class))) + .thenAnswer(new Answer() { + @Override + public PullResult answer(InvocationOnMock mock) throws Throwable { + PullMessageRequestHeader requestHeader = mock.getArgument(1); + MessageClientExt messageClientExt = new MessageClientExt(); + messageClientExt.setTopic(topic); + messageClientExt.setQueueId(0); + messageClientExt.setMsgId("123"); + messageClientExt.setBody(new byte[]{'a'}); + messageClientExt.setOffsetMsgId("234"); + messageClientExt.setBornHost(new InetSocketAddress(8080)); + messageClientExt.setStoreHost(new InetSocketAddress(8080)); + PullResult pullResult = createPullResult(requestHeader, PullStatus.FOUND, Collections.singletonList(messageClientExt)); + ((PullCallback) mock.getArgument(4)).onSuccess(pullResult); + return pullResult; + } + }); consumerGroup = "FooBarGroup" + System.currentTimeMillis(); From da286a442b6496e0c84527a800f8194d6fd6ac66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=9C=E9=98=B3?= Date: Wed, 26 May 2021 10:20:20 +0800 Subject: [PATCH 54/65] [ISSUE #2708] Fix unit test --- .../rocketmq/client/consumer/DefaultMQPushConsumerTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java b/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java index da71d40feb..bffbb46523 100644 --- a/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java @@ -128,7 +128,7 @@ public class DefaultMQPushConsumerTest { pushConsumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, - ConsumeConcurrentlyContext context) { + ConsumeConcurrentlyContext context) { return null; } }); @@ -176,7 +176,7 @@ public class DefaultMQPushConsumerTest { pushConsumer.getDefaultMQPushConsumerImpl().setConsumeMessageService(new ConsumeMessageConcurrentlyService(pushConsumer.getDefaultMQPushConsumerImpl(), new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, - ConsumeConcurrentlyContext context) { + ConsumeConcurrentlyContext context) { messageAtomic.set(msgs.get(0)); countDownLatch.countDown(); return null; @@ -268,7 +268,7 @@ public class DefaultMQPushConsumerTest { pushConsumer.getDefaultMQPushConsumerImpl().setConsumeMessageService(new ConsumeMessageConcurrentlyService(pushConsumer.getDefaultMQPushConsumerImpl(), new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, - ConsumeConcurrentlyContext context) { + ConsumeConcurrentlyContext context) { countDownLatch.countDown(); try { Thread.sleep(1000); From 436d72b7070fb01038616f09512adb2f3d61a89e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=9C=E9=98=B3?= Date: Wed, 26 May 2021 10:22:12 +0800 Subject: [PATCH 55/65] [ISSUE #2708] Fix unit test --- .../consumer/DefaultMQPushConsumerTest.java | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java b/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java index bffbb46523..8a03a23f69 100644 --- a/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java @@ -16,6 +16,17 @@ */ package org.apache.rocketmq.client.consumer; +import java.io.ByteArrayOutputStream; +import java.net.InetSocketAddress; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; @@ -56,18 +67,6 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.junit.MockitoJUnitRunner; import org.mockito.stubbing.Answer; -import java.io.ByteArrayOutputStream; -import java.net.InetSocketAddress; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; - import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Fail.failBecauseExceptionWasNotThrown; import static org.mockito.ArgumentMatchers.any; From 9f90a2353287b10ff0b3667f6dd7e388ecb59fa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=9C=E9=98=B3?= Date: Wed, 26 May 2021 10:25:59 +0800 Subject: [PATCH 56/65] [ISSUE #2708] Fix unit test --- .../rocketmq/client/consumer/DefaultMQPushConsumerTest.java | 5 ++--- .../impl/consumer/ConsumeMessageConcurrentlyServiceTest.java | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java b/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java index 8a03a23f69..a60e1c645b 100644 --- a/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java @@ -146,7 +146,6 @@ public class DefaultMQPushConsumerTest { doReturn(new FindBrokerResult("127.0.0.1:10911", false)).when(mQClientFactory).findBrokerAddressInSubscribe(anyString(), anyLong(), anyBoolean()); rebalanceImpl = spy(pushConsumerImpl.getRebalanceImpl()); - // doReturn(123L).when(rebalancePushImpl).computePullFromWhere(any(MessageQueue.class)); doReturn(123L).when(rebalanceImpl).computePullFromWhereWithException(any(MessageQueue.class)); FieldUtils.writeDeclaredField(pushConsumerImpl, "rebalanceImpl", rebalanceImpl, true); @@ -292,7 +291,7 @@ public class DefaultMQPushConsumerTest { pushConsumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List msgs, - ConsumeConcurrentlyContext context) { + ConsumeConcurrentlyContext context) { return null; } }); @@ -318,7 +317,7 @@ public class DefaultMQPushConsumerTest { } private PullResultExt createPullResult(PullMessageRequestHeader requestHeader, PullStatus pullStatus, - List messageExtList) throws Exception { + List messageExtList) throws Exception { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); for (MessageExt messageExt : messageExtList) { outputStream.write(MessageDecoder.encode(messageExt, false)); diff --git a/client/src/test/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageConcurrentlyServiceTest.java b/client/src/test/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageConcurrentlyServiceTest.java index e8feb80dd9..f39be4e2c8 100644 --- a/client/src/test/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageConcurrentlyServiceTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageConcurrentlyServiceTest.java @@ -182,7 +182,7 @@ public class ConsumeMessageConcurrentlyServiceTest { MessageExt msg = messageAtomic.get(); assertThat(msg).isNotNull(); assertThat(msg.getTopic()).isEqualTo(topic); - assertThat(msg.getBody()).isEqualTo(new byte[] {'a'}); + assertThat(msg.getBody()).isEqualTo(new byte[]{'a'}); } @After From 715a6144aaaeee78a8b8718fe1a66728ab56ddaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=9C=E9=98=B3?= Date: Wed, 26 May 2021 10:26:42 +0800 Subject: [PATCH 57/65] [ISSUE #2708] Fix unit test --- .../impl/consumer/ConsumeMessageConcurrentlyServiceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/test/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageConcurrentlyServiceTest.java b/client/src/test/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageConcurrentlyServiceTest.java index f39be4e2c8..64cf630e95 100644 --- a/client/src/test/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageConcurrentlyServiceTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageConcurrentlyServiceTest.java @@ -129,7 +129,7 @@ public class ConsumeMessageConcurrentlyServiceTest { messageClientExt.setTopic(topic); messageClientExt.setQueueId(0); messageClientExt.setMsgId("123"); - messageClientExt.setBody(new byte[] {'a'}); + messageClientExt.setBody(new byte[]{'a'}); messageClientExt.setOffsetMsgId("234"); messageClientExt.setBornHost(new InetSocketAddress(8080)); messageClientExt.setStoreHost(new InetSocketAddress(8080)); From fb34b6486e410842e714305a3987c55ac40ee236 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=9C=E9=98=B3?= Date: Wed, 26 May 2021 10:30:24 +0800 Subject: [PATCH 58/65] [ISSUE #2708] Reformat --- .../rocketmq/client/consumer/DefaultMQPushConsumerTest.java | 4 ++-- .../impl/consumer/ConsumeMessageConcurrentlyServiceTest.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java b/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java index a60e1c645b..ac69a71ff7 100644 --- a/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java @@ -108,7 +108,7 @@ public class DefaultMQPushConsumerTest { messageClientExt.setTopic(topic); messageClientExt.setQueueId(0); messageClientExt.setMsgId("123"); - messageClientExt.setBody(new byte[]{'a'}); + messageClientExt.setBody(new byte[] {'a'}); messageClientExt.setOffsetMsgId("234"); messageClientExt.setBornHost(new InetSocketAddress(8080)); messageClientExt.setStoreHost(new InetSocketAddress(8080)); @@ -187,7 +187,7 @@ public class DefaultMQPushConsumerTest { MessageExt msg = messageAtomic.get(); assertThat(msg).isNotNull(); assertThat(msg.getTopic()).isEqualTo(topic); - assertThat(msg.getBody()).isEqualTo(new byte[]{'a'}); + assertThat(msg.getBody()).isEqualTo(new byte[] {'a'}); } @Test diff --git a/client/src/test/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageConcurrentlyServiceTest.java b/client/src/test/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageConcurrentlyServiceTest.java index 64cf630e95..e8feb80dd9 100644 --- a/client/src/test/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageConcurrentlyServiceTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageConcurrentlyServiceTest.java @@ -129,7 +129,7 @@ public class ConsumeMessageConcurrentlyServiceTest { messageClientExt.setTopic(topic); messageClientExt.setQueueId(0); messageClientExt.setMsgId("123"); - messageClientExt.setBody(new byte[]{'a'}); + messageClientExt.setBody(new byte[] {'a'}); messageClientExt.setOffsetMsgId("234"); messageClientExt.setBornHost(new InetSocketAddress(8080)); messageClientExt.setStoreHost(new InetSocketAddress(8080)); @@ -182,7 +182,7 @@ public class ConsumeMessageConcurrentlyServiceTest { MessageExt msg = messageAtomic.get(); assertThat(msg).isNotNull(); assertThat(msg.getTopic()).isEqualTo(topic); - assertThat(msg.getBody()).isEqualTo(new byte[]{'a'}); + assertThat(msg.getBody()).isEqualTo(new byte[] {'a'}); } @After From c3750b343e80bf86797fb86ed40ae388f5c827ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=9C=E9=98=B3?= Date: Wed, 26 May 2021 11:38:48 +0800 Subject: [PATCH 59/65] [ISSUE #2708] Reformat --- .../rocketmq/client/consumer/DefaultMQPushConsumerTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java b/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java index ac69a71ff7..6c8463542f 100644 --- a/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/consumer/DefaultMQPushConsumerTest.java @@ -214,7 +214,7 @@ public class DefaultMQPushConsumerTest { MessageExt msg = messageAtomic.get(); assertThat(msg).isNotNull(); assertThat(msg.getTopic()).isEqualTo(topic); - assertThat(msg.getBody()).isEqualTo(new byte[]{'a'}); + assertThat(msg.getBody()).isEqualTo(new byte[] {'a'}); } @Test @@ -327,7 +327,6 @@ public class DefaultMQPushConsumerTest { @Test public void testPullMessage_ExceptionOccursWhenComputePullFromWhere() throws MQClientException { - doThrow(MQClientException.class).when(rebalancePushImpl).computePullFromWhereWithException(any(MessageQueue.class)); final CountDownLatch countDownLatch = new CountDownLatch(1); final MessageExt[] messageExts = new MessageExt[1]; pushConsumer.getDefaultMQPushConsumerImpl().setConsumeMessageService( From 2dc75d32099cf12e3caff0b32c7fa5fd1ce66a4c Mon Sep 17 00:00:00 2001 From: zhangjidi2016 Date: Wed, 26 May 2021 21:49:31 +0800 Subject: [PATCH 60/65] The messageTrace related configurations is set as a property --- .../consumer/DefaultLitePullConsumer.java | 90 ++++++++----------- ...efaultMQLitePullConsumerWithTraceTest.java | 7 +- 2 files changed, 44 insertions(+), 53 deletions(-) diff --git a/client/src/main/java/org/apache/rocketmq/client/consumer/DefaultLitePullConsumer.java b/client/src/main/java/org/apache/rocketmq/client/consumer/DefaultLitePullConsumer.java index a33652d6b7..c54399aa5e 100644 --- a/client/src/main/java/org/apache/rocketmq/client/consumer/DefaultLitePullConsumer.java +++ b/client/src/main/java/org/apache/rocketmq/client/consumer/DefaultLitePullConsumer.java @@ -165,6 +165,16 @@ public class DefaultLitePullConsumer extends ClientConfig implements LitePullCon */ private TraceDispatcher traceDispatcher = null; + /** + * The flag for message trace + */ + private boolean enableMsgTrace = false; + + /** + * The name value of message trace topic.If you don't config,you can use the default trace topic name. + */ + private String customizedTraceTopic; + /** * Default constructor. */ @@ -200,57 +210,6 @@ public class DefaultLitePullConsumer extends ClientConfig implements LitePullCon this(null, consumerGroup, rpcHook); } - /** - * Constructor specifying consumer group and enabled msg trace flag. - * - * @param consumerGroup Consumer group. - * @param enableMsgTrace Switch flag instance for message trace. - */ - public DefaultLitePullConsumer(final String consumerGroup, boolean enableMsgTrace) { - this(null, consumerGroup, null, enableMsgTrace, null); - } - - /** - * Constructor specifying consumer group, enabled msg trace flag and customized trace topic name. - * - * @param consumerGroup Consumer group. - * @param enableMsgTrace Switch flag instance for message trace. - * @param customizedTraceTopic The name value of message trace topic.If you don't config,you can use the default - * trace topic name. - */ - public DefaultLitePullConsumer(final String consumerGroup, boolean enableMsgTrace, - final String customizedTraceTopic) { - this(null, consumerGroup, null, enableMsgTrace, customizedTraceTopic); - } - - /** - * Constructor specifying namespace, consumer group, RPC hook, enabled msg trace flag and customized trace topic - * name. - * - * @param namespace Namespace for this MQ Producer instance. - * @param consumerGroup Consume queue. - * @param rpcHook RPC hook to execute before each remoting command. - * @param enableMsgTrace Switch flag instance for message trace. - * @param customizedTraceTopic The name value of message trace topic.If you don't config,you can use the default - * trace topic name. - */ - public DefaultLitePullConsumer(final String namespace, final String consumerGroup, RPCHook rpcHook, - boolean enableMsgTrace, final String customizedTraceTopic) { - this.namespace = namespace; - this.consumerGroup = consumerGroup; - defaultLitePullConsumerImpl = new DefaultLitePullConsumerImpl(this, rpcHook); - if (enableMsgTrace) { - try { - this.traceDispatcher = new AsyncTraceDispatcher(consumerGroup, TraceDispatcher.Type.CONSUME, customizedTraceTopic, rpcHook); - this.defaultLitePullConsumerImpl.registerConsumeMessageHook( - new ConsumeMessageTraceHookImpl(traceDispatcher)); - } catch (Throwable e) { - log.error("system mqtrace hook init failed ,maybe can't send msg trace data"); - } - } - } - - /** * Constructor specifying namespace, consumer group and RPC hook. * @@ -265,6 +224,7 @@ public class DefaultLitePullConsumer extends ClientConfig implements LitePullCon @Override public void start() throws MQClientException { + setTraceDispatcher(); setConsumerGroup(NamespaceUtil.wrapNamespace(this.getNamespace(), this.consumerGroup)); this.defaultLitePullConsumerImpl.start(); if (null != traceDispatcher) { @@ -567,4 +527,32 @@ public class DefaultLitePullConsumer extends ClientConfig implements LitePullCon public TraceDispatcher getTraceDispatcher() { return traceDispatcher; } + + public void setCustomizedTraceTopic(String customizedTraceTopic) { + this.customizedTraceTopic = customizedTraceTopic; + } + + private void setTraceDispatcher() { + if (isEnableMsgTrace()) { + try { + this.traceDispatcher = new AsyncTraceDispatcher(consumerGroup, TraceDispatcher.Type.CONSUME, customizedTraceTopic, null); + this.defaultLitePullConsumerImpl.registerConsumeMessageHook( + new ConsumeMessageTraceHookImpl(traceDispatcher)); + } catch (Throwable e) { + log.error("system mqtrace hook init failed ,maybe can't send msg trace data"); + } + } + } + + public String getCustomizedTraceTopic() { + return customizedTraceTopic; + } + + public boolean isEnableMsgTrace() { + return enableMsgTrace; + } + + public void setEnableMsgTrace(boolean enableMsgTrace) { + this.enableMsgTrace = enableMsgTrace; + } } diff --git a/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQLitePullConsumerWithTraceTest.java b/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQLitePullConsumerWithTraceTest.java index d79790d19c..67ae194b88 100644 --- a/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQLitePullConsumerWithTraceTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/trace/DefaultMQLitePullConsumerWithTraceTest.java @@ -148,7 +148,8 @@ public class DefaultMQLitePullConsumerWithTraceTest { private DefaultLitePullConsumer createLitePullConsumerWithDefaultTraceTopic() throws Exception { - DefaultLitePullConsumer litePullConsumer = new DefaultLitePullConsumer(consumerGroup + System.currentTimeMillis(), true); + DefaultLitePullConsumer litePullConsumer = new DefaultLitePullConsumer(consumerGroup + System.currentTimeMillis()); + litePullConsumer.setEnableMsgTrace(true); litePullConsumer.setNamesrvAddr("127.0.0.1:9876"); litePullConsumer.subscribe(topic, "*"); suppressUpdateTopicRouteInfoFromNameServer(litePullConsumer); @@ -158,7 +159,9 @@ public class DefaultMQLitePullConsumerWithTraceTest { } private DefaultLitePullConsumer createLitePullConsumerWithCustomizedTraceTopic() throws Exception { - DefaultLitePullConsumer litePullConsumer = new DefaultLitePullConsumer(consumerGroup + System.currentTimeMillis(), true, customerTraceTopic); + DefaultLitePullConsumer litePullConsumer = new DefaultLitePullConsumer(consumerGroup + System.currentTimeMillis()); + litePullConsumer.setEnableMsgTrace(true); + litePullConsumer.setCustomizedTraceTopic(customerTraceTopic); litePullConsumer.setNamesrvAddr("127.0.0.1:9876"); litePullConsumer.subscribe(topic, "*"); suppressUpdateTopicRouteInfoFromNameServer(litePullConsumer); From ae394c755acba748c5cd8cf07d6e92f232909879 Mon Sep 17 00:00:00 2001 From: hehejiejie <56462452+hehejiejie@users.noreply.github.com> Date: Thu, 27 May 2021 09:01:54 +0800 Subject: [PATCH 61/65] Update operation.md fix a mistake on operation doc --- docs/cn/operation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cn/operation.md b/docs/cn/operation.md index d8c0bb151d..bc2d38527f 100644 --- a/docs/cn/operation.md +++ b/docs/cn/operation.md @@ -26,7 +26,7 @@ The Name Server boot success... $ nohup sh bin/mqbroker -n localhost:9876 & ### 验证Name Server 是否启动成功,例如Broker的IP为:192.168.1.2,且名称为broker-a -$ tail -f ~/logs/rocketmqlogs/Broker.log +$ tail -f ~/logs/rocketmqlogs/broker.log The broker[broker-a, 192.169.1.2:10911] boot success... ``` From 5515791f669250b481c2a8ad378daea19eae18c8 Mon Sep 17 00:00:00 2001 From: zhangjidi2016 <1017543663@qq.com> Date: Thu, 27 May 2021 10:54:17 +0800 Subject: [PATCH 62/65] [ISSUE #2556] The queryMsgTraceById command supports specifying the customerTraceTopic --- .../message/QueryMsgTraceByIdSubCommand.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgTraceByIdSubCommand.java b/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgTraceByIdSubCommand.java index 7c2b51fe86..7382ff568d 100644 --- a/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgTraceByIdSubCommand.java +++ b/tools/src/main/java/org/apache/rocketmq/tools/command/message/QueryMsgTraceByIdSubCommand.java @@ -44,6 +44,10 @@ public class QueryMsgTraceByIdSubCommand implements SubCommand { Option opt = new Option("i", "msgId", true, "Message Id"); opt.setRequired(true); options.addOption(opt); + + opt = new Option("t", "traceTopic", true, "The name value of message trace topic"); + opt.setRequired(false); + options.addOption(opt); return options; } @@ -63,7 +67,11 @@ public class QueryMsgTraceByIdSubCommand implements SubCommand { defaultMQAdminExt.setInstanceName(Long.toString(System.currentTimeMillis())); try { final String msgId = commandLine.getOptionValue('i').trim(); - this.queryTraceByMsgId(defaultMQAdminExt, msgId); + String traceTopic = TopicValidator.RMQ_SYS_TRACE_TOPIC; + if (commandLine.hasOption('t')) { + traceTopic = commandLine.getOptionValue('t').trim(); + } + this.queryTraceByMsgId(defaultMQAdminExt, traceTopic, msgId); } catch (Exception e) { throw new SubCommandException(this.getClass().getSimpleName() + "command failed", e); } finally { @@ -71,10 +79,10 @@ public class QueryMsgTraceByIdSubCommand implements SubCommand { } } - private void queryTraceByMsgId(final DefaultMQAdminExt admin, String msgId) + private void queryTraceByMsgId(final DefaultMQAdminExt admin, String traceTopic, String msgId) throws MQClientException, InterruptedException { admin.start(); - QueryResult queryResult = admin.queryMessage(TopicValidator.RMQ_SYS_TRACE_TOPIC, msgId, 64, 0, System.currentTimeMillis()); + QueryResult queryResult = admin.queryMessage(traceTopic, msgId, 64, 0, System.currentTimeMillis()); List messageList = queryResult.getMessageList(); List traceViews = new ArrayList<>(); for (MessageExt message : messageList) { From 93974b0dd542c9f478aa8da65909b86ae7de0ca6 Mon Sep 17 00:00:00 2001 From: ShannonDing Date: Fri, 4 Jun 2021 09:49:19 +0800 Subject: [PATCH 63/65] chore(release): prepare to release rocketmq 4.9.0 --- common/src/main/java/org/apache/rocketmq/common/MQVersion.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/org/apache/rocketmq/common/MQVersion.java b/common/src/main/java/org/apache/rocketmq/common/MQVersion.java index 0d4aff0b0f..48cd1582cc 100644 --- a/common/src/main/java/org/apache/rocketmq/common/MQVersion.java +++ b/common/src/main/java/org/apache/rocketmq/common/MQVersion.java @@ -18,7 +18,7 @@ package org.apache.rocketmq.common; public class MQVersion { - public static final int CURRENT_VERSION = Version.V4_8_0.ordinal(); + public static final int CURRENT_VERSION = Version.V4_9_0.ordinal(); public static String getVersionDesc(int value) { int length = Version.values().length; From 5203d52f1bab431ada236ba279c80e62778f8a15 Mon Sep 17 00:00:00 2001 From: zongtanghu Date: Thu, 10 Jun 2021 21:35:41 +0800 Subject: [PATCH 64/65] [maven-release-plugin] prepare release rocketmq-all-4.9.0 --- acl/pom.xml | 2 +- broker/pom.xml | 2 +- client/pom.xml | 2 +- common/pom.xml | 2 +- distribution/pom.xml | 2 +- example/pom.xml | 2 +- filter/pom.xml | 2 +- logappender/pom.xml | 2 +- logging/pom.xml | 2 +- namesrv/pom.xml | 2 +- openmessaging/pom.xml | 2 +- pom.xml | 4 ++-- remoting/pom.xml | 2 +- srvutil/pom.xml | 2 +- store/pom.xml | 2 +- test/pom.xml | 2 +- tools/pom.xml | 2 +- 17 files changed, 18 insertions(+), 18 deletions(-) diff --git a/acl/pom.xml b/acl/pom.xml index 36dae6ad04..5e61c7cbdd 100644 --- a/acl/pom.xml +++ b/acl/pom.xml @@ -13,7 +13,7 @@ org.apache.rocketmq rocketmq-all - 4.8.1-SNAPSHOT + 4.9.0 rocketmq-acl rocketmq-acl ${project.version} diff --git a/broker/pom.xml b/broker/pom.xml index 0137b55678..f31f0af807 100644 --- a/broker/pom.xml +++ b/broker/pom.xml @@ -13,7 +13,7 @@ org.apache.rocketmq rocketmq-all - 4.8.1-SNAPSHOT + 4.9.0 4.0.0 diff --git a/client/pom.xml b/client/pom.xml index 87dc89426a..446c5efaee 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -19,7 +19,7 @@ org.apache.rocketmq rocketmq-all - 4.8.1-SNAPSHOT + 4.9.0 4.0.0 diff --git a/common/pom.xml b/common/pom.xml index 068de26dcb..0e3bf7bfb7 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -19,7 +19,7 @@ org.apache.rocketmq rocketmq-all - 4.8.1-SNAPSHOT + 4.9.0 4.0.0 diff --git a/distribution/pom.xml b/distribution/pom.xml index d5ab667229..1d090dec0c 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -20,7 +20,7 @@ org.apache.rocketmq rocketmq-all - 4.8.1-SNAPSHOT + 4.9.0 rocketmq-distribution rocketmq-distribution ${project.version} diff --git a/example/pom.xml b/example/pom.xml index 568dd80295..1a54df7379 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -19,7 +19,7 @@ rocketmq-all org.apache.rocketmq - 4.8.1-SNAPSHOT + 4.9.0 4.0.0 diff --git a/filter/pom.xml b/filter/pom.xml index c12dd1fb28..f7cccd71f8 100644 --- a/filter/pom.xml +++ b/filter/pom.xml @@ -20,7 +20,7 @@ rocketmq-all org.apache.rocketmq - 4.8.1-SNAPSHOT + 4.9.0 4.0.0 diff --git a/logappender/pom.xml b/logappender/pom.xml index a6e979a83e..181f1bd478 100644 --- a/logappender/pom.xml +++ b/logappender/pom.xml @@ -19,7 +19,7 @@ org.apache.rocketmq rocketmq-all - 4.8.1-SNAPSHOT + 4.9.0 4.0.0 rocketmq-logappender diff --git a/logging/pom.xml b/logging/pom.xml index 77d0f29a97..e9fa2a42f4 100644 --- a/logging/pom.xml +++ b/logging/pom.xml @@ -19,7 +19,7 @@ org.apache.rocketmq rocketmq-all - 4.8.1-SNAPSHOT + 4.9.0 4.0.0 diff --git a/namesrv/pom.xml b/namesrv/pom.xml index c5d6d05bc2..95cc7b2faa 100644 --- a/namesrv/pom.xml +++ b/namesrv/pom.xml @@ -19,7 +19,7 @@ org.apache.rocketmq rocketmq-all - 4.8.1-SNAPSHOT + 4.9.0 4.0.0 diff --git a/openmessaging/pom.xml b/openmessaging/pom.xml index 7f0e937996..069900cb48 100644 --- a/openmessaging/pom.xml +++ b/openmessaging/pom.xml @@ -20,7 +20,7 @@ rocketmq-all org.apache.rocketmq - 4.8.1-SNAPSHOT + 4.9.0 4.0.0 diff --git a/pom.xml b/pom.xml index 39f368538e..ed700c9551 100644 --- a/pom.xml +++ b/pom.xml @@ -29,7 +29,7 @@ 2012 org.apache.rocketmq rocketmq-all - 4.8.1-SNAPSHOT + 4.9.0 pom Apache RocketMQ ${project.version} http://rocketmq.apache.org/ @@ -38,7 +38,7 @@ git@github.com:apache/rocketmq.git scm:git:git@github.com:apache/rocketmq.git scm:git:git@github.com:apache/rocketmq.git - HEAD + rocketmq-all-4.9.0 diff --git a/remoting/pom.xml b/remoting/pom.xml index d9dab9efa6..6df93dc692 100644 --- a/remoting/pom.xml +++ b/remoting/pom.xml @@ -19,7 +19,7 @@ org.apache.rocketmq rocketmq-all - 4.8.1-SNAPSHOT + 4.9.0 4.0.0 diff --git a/srvutil/pom.xml b/srvutil/pom.xml index b95808b7c2..7f1d4c0102 100644 --- a/srvutil/pom.xml +++ b/srvutil/pom.xml @@ -19,7 +19,7 @@ org.apache.rocketmq rocketmq-all - 4.8.1-SNAPSHOT + 4.9.0 4.0.0 diff --git a/store/pom.xml b/store/pom.xml index ed54db1b24..33e8dc9e60 100644 --- a/store/pom.xml +++ b/store/pom.xml @@ -19,7 +19,7 @@ org.apache.rocketmq rocketmq-all - 4.8.1-SNAPSHOT + 4.9.0 4.0.0 diff --git a/test/pom.xml b/test/pom.xml index 851d9ab9aa..02f90101d8 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -20,7 +20,7 @@ rocketmq-all org.apache.rocketmq - 4.8.1-SNAPSHOT + 4.9.0 4.0.0 diff --git a/tools/pom.xml b/tools/pom.xml index eeccae81bf..355464e2ff 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -19,7 +19,7 @@ org.apache.rocketmq rocketmq-all - 4.8.1-SNAPSHOT + 4.9.0 4.0.0 From df1d93fc8859377b92ba87c6947911281656f355 Mon Sep 17 00:00:00 2001 From: zongtanghu Date: Thu, 10 Jun 2021 21:36:05 +0800 Subject: [PATCH 65/65] [maven-release-plugin] prepare for next development iteration --- acl/pom.xml | 2 +- broker/pom.xml | 2 +- client/pom.xml | 2 +- common/pom.xml | 2 +- distribution/pom.xml | 2 +- example/pom.xml | 2 +- filter/pom.xml | 2 +- logappender/pom.xml | 2 +- logging/pom.xml | 2 +- namesrv/pom.xml | 2 +- openmessaging/pom.xml | 2 +- pom.xml | 4 ++-- remoting/pom.xml | 2 +- srvutil/pom.xml | 2 +- store/pom.xml | 2 +- test/pom.xml | 2 +- tools/pom.xml | 2 +- 17 files changed, 18 insertions(+), 18 deletions(-) diff --git a/acl/pom.xml b/acl/pom.xml index 5e61c7cbdd..d624934178 100644 --- a/acl/pom.xml +++ b/acl/pom.xml @@ -13,7 +13,7 @@ org.apache.rocketmq rocketmq-all - 4.9.0 + 4.9.1-SNAPSHOT rocketmq-acl rocketmq-acl ${project.version} diff --git a/broker/pom.xml b/broker/pom.xml index f31f0af807..3a58aa0c71 100644 --- a/broker/pom.xml +++ b/broker/pom.xml @@ -13,7 +13,7 @@ org.apache.rocketmq rocketmq-all - 4.9.0 + 4.9.1-SNAPSHOT 4.0.0 diff --git a/client/pom.xml b/client/pom.xml index 446c5efaee..95ef4617dd 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -19,7 +19,7 @@ org.apache.rocketmq rocketmq-all - 4.9.0 + 4.9.1-SNAPSHOT 4.0.0 diff --git a/common/pom.xml b/common/pom.xml index 0e3bf7bfb7..ac1d086b4c 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -19,7 +19,7 @@ org.apache.rocketmq rocketmq-all - 4.9.0 + 4.9.1-SNAPSHOT 4.0.0 diff --git a/distribution/pom.xml b/distribution/pom.xml index 1d090dec0c..a5698bca1c 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -20,7 +20,7 @@ org.apache.rocketmq rocketmq-all - 4.9.0 + 4.9.1-SNAPSHOT rocketmq-distribution rocketmq-distribution ${project.version} diff --git a/example/pom.xml b/example/pom.xml index 1a54df7379..81f7515dbe 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -19,7 +19,7 @@ rocketmq-all org.apache.rocketmq - 4.9.0 + 4.9.1-SNAPSHOT 4.0.0 diff --git a/filter/pom.xml b/filter/pom.xml index f7cccd71f8..41bda9d0d2 100644 --- a/filter/pom.xml +++ b/filter/pom.xml @@ -20,7 +20,7 @@ rocketmq-all org.apache.rocketmq - 4.9.0 + 4.9.1-SNAPSHOT 4.0.0 diff --git a/logappender/pom.xml b/logappender/pom.xml index 181f1bd478..cac520b610 100644 --- a/logappender/pom.xml +++ b/logappender/pom.xml @@ -19,7 +19,7 @@ org.apache.rocketmq rocketmq-all - 4.9.0 + 4.9.1-SNAPSHOT 4.0.0 rocketmq-logappender diff --git a/logging/pom.xml b/logging/pom.xml index e9fa2a42f4..8ebb243045 100644 --- a/logging/pom.xml +++ b/logging/pom.xml @@ -19,7 +19,7 @@ org.apache.rocketmq rocketmq-all - 4.9.0 + 4.9.1-SNAPSHOT 4.0.0 diff --git a/namesrv/pom.xml b/namesrv/pom.xml index 95cc7b2faa..01ef5b60fe 100644 --- a/namesrv/pom.xml +++ b/namesrv/pom.xml @@ -19,7 +19,7 @@ org.apache.rocketmq rocketmq-all - 4.9.0 + 4.9.1-SNAPSHOT 4.0.0 diff --git a/openmessaging/pom.xml b/openmessaging/pom.xml index 069900cb48..4688c04043 100644 --- a/openmessaging/pom.xml +++ b/openmessaging/pom.xml @@ -20,7 +20,7 @@ rocketmq-all org.apache.rocketmq - 4.9.0 + 4.9.1-SNAPSHOT 4.0.0 diff --git a/pom.xml b/pom.xml index ed700c9551..b6f1770e2d 100644 --- a/pom.xml +++ b/pom.xml @@ -29,7 +29,7 @@ 2012 org.apache.rocketmq rocketmq-all - 4.9.0 + 4.9.1-SNAPSHOT pom Apache RocketMQ ${project.version} http://rocketmq.apache.org/ @@ -38,7 +38,7 @@ git@github.com:apache/rocketmq.git scm:git:git@github.com:apache/rocketmq.git scm:git:git@github.com:apache/rocketmq.git - rocketmq-all-4.9.0 + HEAD diff --git a/remoting/pom.xml b/remoting/pom.xml index 6df93dc692..765e1a93bc 100644 --- a/remoting/pom.xml +++ b/remoting/pom.xml @@ -19,7 +19,7 @@ org.apache.rocketmq rocketmq-all - 4.9.0 + 4.9.1-SNAPSHOT 4.0.0 diff --git a/srvutil/pom.xml b/srvutil/pom.xml index 7f1d4c0102..9c4544c0b2 100644 --- a/srvutil/pom.xml +++ b/srvutil/pom.xml @@ -19,7 +19,7 @@ org.apache.rocketmq rocketmq-all - 4.9.0 + 4.9.1-SNAPSHOT 4.0.0 diff --git a/store/pom.xml b/store/pom.xml index 33e8dc9e60..23e9763d81 100644 --- a/store/pom.xml +++ b/store/pom.xml @@ -19,7 +19,7 @@ org.apache.rocketmq rocketmq-all - 4.9.0 + 4.9.1-SNAPSHOT 4.0.0 diff --git a/test/pom.xml b/test/pom.xml index 02f90101d8..41296501c2 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -20,7 +20,7 @@ rocketmq-all org.apache.rocketmq - 4.9.0 + 4.9.1-SNAPSHOT 4.0.0 diff --git a/tools/pom.xml b/tools/pom.xml index 355464e2ff..abe8197de3 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -19,7 +19,7 @@ org.apache.rocketmq rocketmq-all - 4.9.0 + 4.9.1-SNAPSHOT 4.0.0