From 0597823ff7a9f33ffc571d86dfabd342c63b260b Mon Sep 17 00:00:00 2001 From: bingyan Date: Tue, 20 Aug 2019 21:14:29 +0800 Subject: [PATCH 01/49] add a thread to do disk space detection --- .../rocketmq/store/DefaultMessageStore.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) 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 971b1e75b3..ea62fc6523 100644 --- a/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java +++ b/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java @@ -111,6 +111,9 @@ public class DefaultMessageStore implements MessageStore { boolean shutDownNormal = false; + private final ScheduledExecutorService diskCheckScheduledExecutorService = + Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl("DiskCheckScheduledThread")); + public DefaultMessageStore(final MessageStoreConfig messageStoreConfig, final BrokerStatsManager brokerStatsManager, final MessageArrivingListener messageArrivingListener, final BrokerConfig brokerConfig) throws IOException { this.messageArrivingListener = messageArrivingListener; @@ -1261,6 +1264,11 @@ public class DefaultMessageStore implements MessageStore { // DefaultMessageStore.this.cleanExpiredConsumerQueue(); // } // }, 1, 1, TimeUnit.HOURS); + this.diskCheckScheduledExecutorService.scheduleAtFixedRate(new Runnable() { + public void run() { + DefaultMessageStore.this.cleanCommitLogService.isSpaceFull(); + } + }, 1000L, 10000L, TimeUnit.MILLISECONDS); } private void cleanFilesPeriodically() { @@ -1659,6 +1667,30 @@ public class DefaultMessageStore implements MessageStore { public void setManualDeleteFileSeveralTimes(int manualDeleteFileSeveralTimes) { this.manualDeleteFileSeveralTimes = manualDeleteFileSeveralTimes; } + public boolean isSpaceFull() { + String storePathPhysic = DefaultMessageStore.this.getMessageStoreConfig().getStorePathCommitLog(); + double physicRatio = UtilAll.getDiskPartitionSpaceUsedPercent(storePathPhysic); + double ratio = DefaultMessageStore.this.getMessageStoreConfig().getDiskMaxUsedSpaceRatio() / 100.0; + if (physicRatio > ratio) { + DefaultMessageStore.log.info("physic disk of commitLog used: " + physicRatio); + } + if (physicRatio > this.diskSpaceWarningLevelRatio) { + boolean diskok = DefaultMessageStore.this.runningFlags.getAndMakeDiskFull(); + if (diskok) { + DefaultMessageStore.log.error("physic disk of commitLog maybe full soon, used " + physicRatio + ", so mark disk full"); + } + + return true; + } else { + boolean diskok = DefaultMessageStore.this.runningFlags.getAndMakeDiskOK(); + + if (!diskok) { + DefaultMessageStore.log.info("physic disk space of commitLog OK " + physicRatio + ", so mark disk ok"); + } + + return false; + } + } } class CleanConsumeQueueService { From 33b3688bfa1d23d9897e57e8045ee617a0b9f576 Mon Sep 17 00:00:00 2001 From: qqeasonchen Date: Mon, 9 Sep 2019 10:23:37 +0800 Subject: [PATCH 02/49] shutdown the disk check service when the server closed --- .../java/org/apache/rocketmq/store/DefaultMessageStore.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ea62fc6523..171a331899 100644 --- a/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java +++ b/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java @@ -295,7 +295,7 @@ public class DefaultMessageStore implements MessageStore { this.shutdown = true; this.scheduledExecutorService.shutdown(); - + this.diskCheckScheduledExecutorService.shutdown(); try { Thread.sleep(1000); From d4c4baf2336e30cd6237b9606b1d65916d57591a Mon Sep 17 00:00:00 2001 From: qqeasonchen Date: Thu, 12 Sep 2019 10:33:19 +0800 Subject: [PATCH 03/49] isspacefull unit test --- .../DefaultMessageStoreCleanFilesTest.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreCleanFilesTest.java b/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreCleanFilesTest.java index fd510f11a5..db726f31c5 100644 --- a/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreCleanFilesTest.java +++ b/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreCleanFilesTest.java @@ -65,6 +65,40 @@ public class DefaultMessageStoreCleanFilesTest { bornHost = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 0); } + @Test + public void testIsSpaceFullFunction() throws Exception { + String deleteWhen = "04"; + // the min value of diskMaxUsedSpaceRatio. + int diskMaxUsedSpaceRatio = 1; + // used to ensure set diskfull flag + double diskSpaceCleanForciblyRatio = 0.01D; + initMessageStore(deleteWhen, diskMaxUsedSpaceRatio, diskSpaceCleanForciblyRatio); + // build and put 55 messages, exactly one message per CommitLog file. + buildAndPutMessagesToMessageStore(msgCount); + MappedFileQueue commitLogQueue = getMappedFileQueueCommitLog(); + assertEquals(fileCountCommitLog, commitLogQueue.getMappedFiles().size()); + int fileCountConsumeQueue = getFileCountConsumeQueue(); + MappedFileQueue consumeQueue = getMappedFileQueueConsumeQueue(); + assertEquals(fileCountConsumeQueue, consumeQueue.getMappedFiles().size()); + cleanCommitLogService.isSpaceFull(); + assertEquals(1 << 4, messageStore.getRunningFlags().getFlagBits() & (1 << 4)); + messageStore.shutdown(); + messageStore.destroy(); + + //use to reset diskfull flag + diskSpaceCleanForciblyRatio = 0.999D; + initMessageStore(deleteWhen, diskMaxUsedSpaceRatio, diskSpaceCleanForciblyRatio); + // build and put 55 messages, exactly one message per CommitLog file. + buildAndPutMessagesToMessageStore(msgCount); + commitLogQueue = getMappedFileQueueCommitLog(); + assertEquals(fileCountCommitLog, commitLogQueue.getMappedFiles().size()); + fileCountConsumeQueue = getFileCountConsumeQueue(); + consumeQueue = getMappedFileQueueConsumeQueue(); + assertEquals(fileCountConsumeQueue, consumeQueue.getMappedFiles().size()); + cleanCommitLogService.isSpaceFull(); + assertEquals(0, messageStore.getRunningFlags().getFlagBits() & (1 << 4)); + } + @Test public void testDeleteExpiredFilesByTimeUp() throws Exception { String deleteWhen = Calendar.getInstance().get(Calendar.HOUR_OF_DAY) + ""; From dcb7c081a116081fd3691390af984156145afc5b Mon Sep 17 00:00:00 2001 From: qqeasonchen Date: Mon, 16 Sep 2019 20:51:09 +0800 Subject: [PATCH 04/49] unit test of statistics persistence modified --- .../DefaultMessageStoreCleanFilesTest.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreCleanFilesTest.java b/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreCleanFilesTest.java index db726f31c5..c0dc9b9741 100644 --- a/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreCleanFilesTest.java +++ b/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreCleanFilesTest.java @@ -66,7 +66,7 @@ public class DefaultMessageStoreCleanFilesTest { } @Test - public void testIsSpaceFullFunction() throws Exception { + public void testIsSpaceFullFunctionEmpty2Full() throws Exception { String deleteWhen = "04"; // the min value of diskMaxUsedSpaceRatio. int diskMaxUsedSpaceRatio = 1; @@ -85,16 +85,19 @@ public class DefaultMessageStoreCleanFilesTest { messageStore.shutdown(); messageStore.destroy(); + } + + @Test + public void testIsSpaceFullFunctionFull2Empty() throws Exception { + String deleteWhen = "04"; + // the min value of diskMaxUsedSpaceRatio. + int diskMaxUsedSpaceRatio = 1; //use to reset diskfull flag - diskSpaceCleanForciblyRatio = 0.999D; + double diskSpaceCleanForciblyRatio = 0.999D; initMessageStore(deleteWhen, diskMaxUsedSpaceRatio, diskSpaceCleanForciblyRatio); - // build and put 55 messages, exactly one message per CommitLog file. - buildAndPutMessagesToMessageStore(msgCount); - commitLogQueue = getMappedFileQueueCommitLog(); - assertEquals(fileCountCommitLog, commitLogQueue.getMappedFiles().size()); - fileCountConsumeQueue = getFileCountConsumeQueue(); - consumeQueue = getMappedFileQueueConsumeQueue(); - assertEquals(fileCountConsumeQueue, consumeQueue.getMappedFiles().size()); + //set disk full + messageStore.getRunningFlags().getAndMakeDiskFull(); + cleanCommitLogService.isSpaceFull(); assertEquals(0, messageStore.getRunningFlags().getFlagBits() & (1 << 4)); } From 9176e05778035e76d9165a3b8e12f116b3ecacd6 Mon Sep 17 00:00:00 2001 From: qqeasonchen Date: Mon, 28 Oct 2019 17:35:37 +0800 Subject: [PATCH 05/49] unit test of isSpaceFull function --- .../rocketmq/store/DefaultMessageStoreCleanFilesTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreCleanFilesTest.java b/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreCleanFilesTest.java index c0dc9b9741..fa218619da 100644 --- a/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreCleanFilesTest.java +++ b/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreCleanFilesTest.java @@ -70,7 +70,7 @@ public class DefaultMessageStoreCleanFilesTest { String deleteWhen = "04"; // the min value of diskMaxUsedSpaceRatio. int diskMaxUsedSpaceRatio = 1; - // used to ensure set diskfull flag + // used to set disk-full flag double diskSpaceCleanForciblyRatio = 0.01D; initMessageStore(deleteWhen, diskMaxUsedSpaceRatio, diskSpaceCleanForciblyRatio); // build and put 55 messages, exactly one message per CommitLog file. @@ -92,7 +92,7 @@ public class DefaultMessageStoreCleanFilesTest { String deleteWhen = "04"; // the min value of diskMaxUsedSpaceRatio. int diskMaxUsedSpaceRatio = 1; - //use to reset diskfull flag + //use to reset disk-full flag double diskSpaceCleanForciblyRatio = 0.999D; initMessageStore(deleteWhen, diskMaxUsedSpaceRatio, diskSpaceCleanForciblyRatio); //set disk full From fe724b30356e221da3b7268da266a7a8fc453a3f Mon Sep 17 00:00:00 2001 From: rushsky518 Date: Tue, 3 Mar 2020 12:53:04 +0800 Subject: [PATCH 06/49] add delete IndexFile --- .../DefaultMessageStoreCleanFilesTest.java | 71 +++++++++++++++++-- 1 file changed, 67 insertions(+), 4 deletions(-) diff --git a/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreCleanFilesTest.java b/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreCleanFilesTest.java index fd510f11a5..abf7356e8a 100644 --- a/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreCleanFilesTest.java +++ b/store/src/test/java/org/apache/rocketmq/store/DefaultMessageStoreCleanFilesTest.java @@ -19,8 +19,12 @@ package org.apache.rocketmq.store; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.UtilAll; +import org.apache.rocketmq.common.message.MessageConst; +import org.apache.rocketmq.common.message.MessageDecoder; import org.apache.rocketmq.store.config.FlushDiskType; import org.apache.rocketmq.store.config.MessageStoreConfig; +import org.apache.rocketmq.store.index.IndexFile; +import org.apache.rocketmq.store.index.IndexService; import org.apache.rocketmq.store.stats.BrokerStatsManager; import org.junit.After; import org.junit.Before; @@ -31,7 +35,9 @@ import java.lang.reflect.Field; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; +import java.util.ArrayList; import java.util.Calendar; +import java.util.HashMap; import java.util.Map; import java.util.UUID; @@ -52,6 +58,7 @@ public class DefaultMessageStoreCleanFilesTest { private SocketAddress storeHost; private String topic = "test"; + private String keys = "hello"; private int queueId = 0; private int fileCountCommitLog = 55; // exactly one message per CommitLog file. @@ -87,6 +94,9 @@ public class DefaultMessageStoreCleanFilesTest { MappedFileQueue consumeQueue = getMappedFileQueueConsumeQueue(); assertEquals(fileCountConsumeQueue, consumeQueue.getMappedFiles().size()); + int fileCountIndexFile = getFileCountIndexFile(); + assertEquals(fileCountIndexFile, getIndexFileList().size()); + int expireFileCount = 15; expireFiles(commitLogQueue, expireFileCount); @@ -101,6 +111,10 @@ public class DefaultMessageStoreCleanFilesTest { int msgCountPerFile = getMsgCountPerConsumeQueueMappedFile(); int expectDeleteCountConsumeQueue = (int) Math.floor((double) expectDeletedCount / msgCountPerFile); assertEquals(fileCountConsumeQueue - expectDeleteCountConsumeQueue, consumeQueue.getMappedFiles().size()); + + int msgCountPerIndexFile = getMsgCountPerIndexFile(); + int expectDeleteCountIndexFile = (int) Math.floor((double) expectDeletedCount / msgCountPerIndexFile); + assertEquals(fileCountIndexFile - expectDeleteCountIndexFile, getIndexFileList().size()); } } @@ -126,6 +140,9 @@ public class DefaultMessageStoreCleanFilesTest { MappedFileQueue consumeQueue = getMappedFileQueueConsumeQueue(); assertEquals(fileCountConsumeQueue, consumeQueue.getMappedFiles().size()); + int fileCountIndexFile = getFileCountIndexFile(); + assertEquals(fileCountIndexFile, getIndexFileList().size()); + int expireFileCount = 15; expireFiles(commitLogQueue, expireFileCount); @@ -140,6 +157,10 @@ public class DefaultMessageStoreCleanFilesTest { int msgCountPerFile = getMsgCountPerConsumeQueueMappedFile(); int expectDeleteCountConsumeQueue = (int) Math.floor((double) expectDeletedCount / msgCountPerFile); assertEquals(fileCountConsumeQueue - expectDeleteCountConsumeQueue, consumeQueue.getMappedFiles().size()); + + int msgCountPerIndexFile = getMsgCountPerIndexFile(); + int expectDeleteCountIndexFile = (int) Math.floor((double) expectDeletedCount / msgCountPerIndexFile); + assertEquals(fileCountIndexFile - expectDeleteCountIndexFile, getIndexFileList().size()); } } @@ -165,6 +186,9 @@ public class DefaultMessageStoreCleanFilesTest { MappedFileQueue consumeQueue = getMappedFileQueueConsumeQueue(); assertEquals(fileCountConsumeQueue, consumeQueue.getMappedFiles().size()); + int fileCountIndexFile = getFileCountIndexFile(); + assertEquals(fileCountIndexFile, getIndexFileList().size()); + // In this case, there is no need to expire the files. // int expireFileCount = 15; // expireFiles(commitLogQueue, expireFileCount); @@ -181,6 +205,10 @@ public class DefaultMessageStoreCleanFilesTest { int msgCountPerFile = getMsgCountPerConsumeQueueMappedFile(); int expectDeleteCountConsumeQueue = (int) Math.floor((double) (a * 10) / msgCountPerFile); assertEquals(fileCountConsumeQueue - expectDeleteCountConsumeQueue, consumeQueue.getMappedFiles().size()); + + int msgCountPerIndexFile = getMsgCountPerIndexFile(); + int expectDeleteCountIndexFile = (int) Math.floor((double) (a * 10) / msgCountPerIndexFile); + assertEquals(fileCountIndexFile - expectDeleteCountIndexFile, getIndexFileList().size()); } } @@ -208,6 +236,9 @@ public class DefaultMessageStoreCleanFilesTest { MappedFileQueue consumeQueue = getMappedFileQueueConsumeQueue(); assertEquals(fileCountConsumeQueue, consumeQueue.getMappedFiles().size()); + int fileCountIndexFile = getFileCountIndexFile(); + assertEquals(fileCountIndexFile, getIndexFileList().size()); + int expireFileCount = 15; expireFiles(commitLogQueue, expireFileCount); @@ -222,6 +253,10 @@ public class DefaultMessageStoreCleanFilesTest { int msgCountPerFile = getMsgCountPerConsumeQueueMappedFile(); int expectDeleteCountConsumeQueue = (int) Math.floor((double) expectDeletedCount / msgCountPerFile); assertEquals(fileCountConsumeQueue - expectDeleteCountConsumeQueue, consumeQueue.getMappedFiles().size()); + + int msgCountPerIndexFile = getMsgCountPerIndexFile(); + int expectDeleteCountIndexFile = (int) Math.floor((double) (a * 10) / msgCountPerIndexFile); + assertEquals(fileCountIndexFile - expectDeleteCountIndexFile, getIndexFileList().size()); } } @@ -274,32 +309,60 @@ public class DefaultMessageStoreCleanFilesTest { return fileQueue; } + private ArrayList getIndexFileList() throws Exception { + Field indexServiceField = messageStore.getClass().getDeclaredField("indexService"); + indexServiceField.setAccessible(true); + IndexService indexService = (IndexService) indexServiceField.get(messageStore); + + Field indexFileListField = indexService.getClass().getDeclaredField("indexFileList"); + indexFileListField.setAccessible(true); + ArrayList indexFileList = (ArrayList) indexFileListField.get(indexService); + + return indexFileList; + } + private int getFileCountConsumeQueue() { int countPerFile = getMsgCountPerConsumeQueueMappedFile(); double fileCount = (double) msgCount / countPerFile; return (int) Math.ceil(fileCount); } + private int getFileCountIndexFile() { + int countPerFile = getMsgCountPerIndexFile(); + double fileCount = (double) msgCount / countPerFile; + return (int) Math.ceil(fileCount); + } + private int getMsgCountPerConsumeQueueMappedFile() { int size = messageStore.getMessageStoreConfig().getMappedFileSizeConsumeQueue(); return size / CQ_STORE_UNIT_SIZE;// 7 in this case } + private int getMsgCountPerIndexFile() { + // 7 in this case + return messageStore.getMessageStoreConfig().getMaxIndexNum() - 1; + } + private void buildAndPutMessagesToMessageStore(int msgCount) throws Exception { int msgLen = topic.getBytes(CHARSET_UTF8).length + 91; + Map properties = new HashMap<>(4); + properties.put(MessageConst.PROPERTY_KEYS, keys); + String s = MessageDecoder.messageProperties2String(properties); + int propertiesLen = s.getBytes(CHARSET_UTF8).length; int commitLogEndFileMinBlankLength = 4 + 4; - int singleMsgBodyLen = mappedFileSize - msgLen - commitLogEndFileMinBlankLength; + int singleMsgBodyLen = mappedFileSize - msgLen - propertiesLen - commitLogEndFileMinBlankLength; for (int i = 0; i < msgCount; i++) { MessageExtBrokerInner msg = new MessageExtBrokerInner(); msg.setTopic(topic); msg.setBody(new byte[singleMsgBodyLen]); - msg.setKeys(String.valueOf(System.currentTimeMillis())); + msg.setKeys(keys); msg.setQueueId(queueId); msg.setSysFlag(0); msg.setBornTimestamp(System.currentTimeMillis()); msg.setStoreHost(storeHost); msg.setBornHost(bornHost); + msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties())); PutMessageResult result = messageStore.putMessage(msg); assertTrue(result != null && result.isOk()); } @@ -324,8 +387,8 @@ public class DefaultMessageStoreCleanFilesTest { MessageStoreConfig messageStoreConfig = new MessageStoreConfigForTest(); messageStoreConfig.setMappedFileSizeCommitLog(mappedFileSize); messageStoreConfig.setMappedFileSizeConsumeQueue(mappedFileSize); - messageStoreConfig.setMaxHashSlotNum(10000); - messageStoreConfig.setMaxIndexNum(100 * 100); + messageStoreConfig.setMaxHashSlotNum(100); + messageStoreConfig.setMaxIndexNum(8); messageStoreConfig.setFlushDiskType(FlushDiskType.SYNC_FLUSH); messageStoreConfig.setFlushIntervalConsumeQueue(1); From 3974677f04815609951c17059d85d3795eb51247 Mon Sep 17 00:00:00 2001 From: rongtongjin <794220751@qq.com> Date: Thu, 5 Mar 2020 19:07:09 +0800 Subject: [PATCH 07/49] [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 | 6 +++--- 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, 20 insertions(+), 20 deletions(-) diff --git a/acl/pom.xml b/acl/pom.xml index a455361887..bb91ac8988 100644 --- a/acl/pom.xml +++ b/acl/pom.xml @@ -13,7 +13,7 @@ org.apache.rocketmq rocketmq-all - 4.7.0 + 4.7.1-SNAPSHOT rocketmq-acl rocketmq-acl ${project.version} diff --git a/broker/pom.xml b/broker/pom.xml index 50144f01f2..275758f928 100644 --- a/broker/pom.xml +++ b/broker/pom.xml @@ -13,7 +13,7 @@ org.apache.rocketmq rocketmq-all - 4.7.0 + 4.7.1-SNAPSHOT 4.0.0 diff --git a/client/pom.xml b/client/pom.xml index cf1479ddfd..b73b6b697b 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -19,7 +19,7 @@ org.apache.rocketmq rocketmq-all - 4.7.0 + 4.7.1-SNAPSHOT 4.0.0 diff --git a/common/pom.xml b/common/pom.xml index 64904fc2eb..56c150f877 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -19,7 +19,7 @@ org.apache.rocketmq rocketmq-all - 4.7.0 + 4.7.1-SNAPSHOT 4.0.0 diff --git a/distribution/pom.xml b/distribution/pom.xml index 887847b759..09e7ad0306 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -20,7 +20,7 @@ org.apache.rocketmq rocketmq-all - 4.7.0 + 4.7.1-SNAPSHOT rocketmq-distribution rocketmq-distribution ${project.version} diff --git a/example/pom.xml b/example/pom.xml index 8fb505b6d3..dba83fe6d2 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -19,7 +19,7 @@ org.apache.rocketmq rocketmq-all - 4.7.0 + 4.7.1-SNAPSHOT 4.0.0 @@ -51,12 +51,12 @@ org.apache.rocketmq rocketmq-openmessaging - 4.7.0 + 4.7.1-SNAPSHOT org.apache.rocketmq rocketmq-acl - 4.7.0 + 4.7.1-SNAPSHOT diff --git a/filter/pom.xml b/filter/pom.xml index a02d1220a3..46debc3e38 100644 --- a/filter/pom.xml +++ b/filter/pom.xml @@ -20,7 +20,7 @@ rocketmq-all org.apache.rocketmq - 4.7.0 + 4.7.1-SNAPSHOT 4.0.0 diff --git a/logappender/pom.xml b/logappender/pom.xml index 884e6e9899..c4d0b217d3 100644 --- a/logappender/pom.xml +++ b/logappender/pom.xml @@ -19,7 +19,7 @@ org.apache.rocketmq rocketmq-all - 4.7.0 + 4.7.1-SNAPSHOT 4.0.0 rocketmq-logappender diff --git a/logging/pom.xml b/logging/pom.xml index 24088c40cf..b4a52a7b0a 100644 --- a/logging/pom.xml +++ b/logging/pom.xml @@ -19,7 +19,7 @@ org.apache.rocketmq rocketmq-all - 4.7.0 + 4.7.1-SNAPSHOT 4.0.0 diff --git a/namesrv/pom.xml b/namesrv/pom.xml index ff821cf5ef..d8ae216588 100644 --- a/namesrv/pom.xml +++ b/namesrv/pom.xml @@ -19,7 +19,7 @@ org.apache.rocketmq rocketmq-all - 4.7.0 + 4.7.1-SNAPSHOT 4.0.0 diff --git a/openmessaging/pom.xml b/openmessaging/pom.xml index 1b2fa8bcef..9fcd708ef9 100644 --- a/openmessaging/pom.xml +++ b/openmessaging/pom.xml @@ -20,7 +20,7 @@ rocketmq-all org.apache.rocketmq - 4.7.0 + 4.7.1-SNAPSHOT 4.0.0 diff --git a/pom.xml b/pom.xml index 3dda417142..f87a1dda3b 100644 --- a/pom.xml +++ b/pom.xml @@ -29,7 +29,7 @@ 2012 org.apache.rocketmq rocketmq-all - 4.7.0 + 4.7.1-SNAPSHOT pom Apache RocketMQ ${project.version} http://rocketmq.apache.org/ @@ -42,7 +42,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.7.0 + HEAD diff --git a/remoting/pom.xml b/remoting/pom.xml index 64c6c11c50..d71f0364a6 100644 --- a/remoting/pom.xml +++ b/remoting/pom.xml @@ -19,7 +19,7 @@ org.apache.rocketmq rocketmq-all - 4.7.0 + 4.7.1-SNAPSHOT 4.0.0 diff --git a/srvutil/pom.xml b/srvutil/pom.xml index 47c3ef5610..6009097862 100644 --- a/srvutil/pom.xml +++ b/srvutil/pom.xml @@ -19,7 +19,7 @@ org.apache.rocketmq rocketmq-all - 4.7.0 + 4.7.1-SNAPSHOT 4.0.0 diff --git a/store/pom.xml b/store/pom.xml index 34c08bc87d..59a9bfa8f2 100644 --- a/store/pom.xml +++ b/store/pom.xml @@ -19,7 +19,7 @@ org.apache.rocketmq rocketmq-all - 4.7.0 + 4.7.1-SNAPSHOT 4.0.0 diff --git a/test/pom.xml b/test/pom.xml index a6d0fdba58..ce9a8a9a7d 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -20,7 +20,7 @@ rocketmq-all org.apache.rocketmq - 4.7.0 + 4.7.1-SNAPSHOT 4.0.0 diff --git a/tools/pom.xml b/tools/pom.xml index ceb58dd111..c6938afdb7 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -19,7 +19,7 @@ org.apache.rocketmq rocketmq-all - 4.7.0 + 4.7.1-SNAPSHOT 4.0.0 From 1558e052018ae12d273e175dd3c6ba004e9e0995 Mon Sep 17 00:00:00 2001 From: xu <774590465@qq.com> Date: Mon, 16 Mar 2020 20:05:32 +0800 Subject: [PATCH 08/49] [ISSUE #1857] Refactor the duplicated code close #1857 --- .../impl/producer/DefaultMQProducerImpl.java | 46 +++++++------------ 1 file changed, 16 insertions(+), 30 deletions(-) diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java index fca50cc565..6ca4b72403 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java @@ -1367,16 +1367,7 @@ public class DefaultMQProducerImpl implements MQProducerInner { } }, timeout - cost); - Message responseMessage = requestResponseFuture.waitResponseMessage(timeout - cost); - if (responseMessage == null) { - if (requestResponseFuture.isSendRequestOk()) { - throw new RequestTimeoutException(ClientErrorCode.REQUEST_TIMEOUT_EXCEPTION, - "send request message to <" + msg.getTopic() + "> OK, but wait reply message timeout, " + timeout + " ms."); - } else { - throw new MQClientException("send request message to <" + msg.getTopic() + "> fail", requestResponseFuture.getCause()); - } - } - return responseMessage; + return waitResponse(msg, timeout, requestResponseFuture, cost); } finally { RequestFutureTable.getRequestFutureTable().remove(correlationId); } @@ -1432,16 +1423,7 @@ public class DefaultMQProducerImpl implements MQProducerInner { } }, timeout - cost); - Message responseMessage = requestResponseFuture.waitResponseMessage(timeout - cost); - if (responseMessage == null) { - if (requestResponseFuture.isSendRequestOk()) { - throw new RequestTimeoutException(ClientErrorCode.REQUEST_TIMEOUT_EXCEPTION, - "send request message to <" + msg.getTopic() + "> OK, but wait reply message timeout, " + timeout + " ms."); - } else { - throw new MQClientException("send request message to <" + msg.getTopic() + "> fail", requestResponseFuture.getCause()); - } - } - return responseMessage; + return waitResponse(msg, timeout, requestResponseFuture, cost); } finally { RequestFutureTable.getRequestFutureTable().remove(correlationId); } @@ -1498,21 +1480,25 @@ public class DefaultMQProducerImpl implements MQProducerInner { } }, null, timeout - cost); - Message responseMessage = requestResponseFuture.waitResponseMessage(timeout - cost); - if (responseMessage == null) { - if (requestResponseFuture.isSendRequestOk()) { - throw new RequestTimeoutException(ClientErrorCode.REQUEST_TIMEOUT_EXCEPTION, - "send request message to <" + msg.getTopic() + "> OK, but wait reply message timeout, " + timeout + " ms."); - } else { - throw new MQClientException("send request message to <" + msg.getTopic() + "> fail", requestResponseFuture.getCause()); - } - } - return responseMessage; + return waitResponse(msg, timeout, requestResponseFuture, cost); } finally { RequestFutureTable.getRequestFutureTable().remove(correlationId); } } + private Message waitResponse(Message msg, long timeout, RequestResponseFuture requestResponseFuture, long cost) throws InterruptedException, RequestTimeoutException, MQClientException { + Message responseMessage = requestResponseFuture.waitResponseMessage(timeout - cost); + if (responseMessage == null) { + if (requestResponseFuture.isSendRequestOk()) { + throw new RequestTimeoutException(ClientErrorCode.REQUEST_TIMEOUT_EXCEPTION, + "send request message to <" + msg.getTopic() + "> OK, but wait reply message timeout, " + timeout + " ms."); + } else { + throw new MQClientException("send request message to <" + msg.getTopic() + "> fail", requestResponseFuture.getCause()); + } + } + return responseMessage; + } + public void request(final Message msg, final MessageQueue mq, final RequestCallback requestCallback, long timeout) throws RemotingException, InterruptedException, MQClientException, MQBrokerException { long beginTimestamp = System.currentTimeMillis(); From bebb4a86da3cdb99aee4e052fc39c923b5a2eb81 Mon Sep 17 00:00:00 2001 From: "andy.xu" Date: Wed, 18 Mar 2020 21:34:01 +0800 Subject: [PATCH 09/49] fix transaction message doc --- docs/cn/RocketMQ_Example.md | 4 ++-- docs/en/Example_Transaction.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/cn/RocketMQ_Example.md b/docs/cn/RocketMQ_Example.md index 09acb0de97..66980d577a 100644 --- a/docs/cn/RocketMQ_Example.md +++ b/docs/cn/RocketMQ_Example.md @@ -737,8 +737,8 @@ public class TransactionListenerImpl implements TransactionListener { ### 6.2 事务消息使用上的限制 1. 事务消息不支持延时消息和批量消息。 -2. 为了避免单个消息被检查太多次而导致半队列消息累积,我们默认将单个消息的检查次数限制为 15 次,但是用户可以通过 Broker 配置文件的 `transactionCheckMax`参数来修改此限制。如果已经检查某条消息超过 N 次的话( N = `transactionCheckMax` ) 则 Broker 将丢弃此消息,并在默认情况下同时打印错误日志。用户可以通过重写 `AbstractTransactionCheckListener` 类来修改这个行为。 -3. 事务消息将在 Broker 配置文件中的参数 transactionMsgTimeout 这样的特定时间长度之后被检查。当发送事务消息时,用户还可以通过设置用户属性 CHECK_IMMUNITY_TIME_IN_SECONDS 来改变这个限制,该参数优先于 `transactionMsgTimeout` 参数。 +2. 为了避免单个消息被检查太多次而导致半队列消息累积,我们默认将单个消息的检查次数限制为 15 次,但是用户可以通过 Broker 配置文件的 `transactionCheckMax`参数来修改此限制。如果已经检查某条消息超过 N 次的话( N = `transactionCheckMax` ) 则 Broker 将丢弃此消息,并在默认情况下同时打印错误日志。用户可以通过重写 `AbstractTransactionalMessageCheckListener` 类来修改这个行为。 +3. 事务消息将在 Broker 配置文件中的参数 transactionTimeout 这样的特定时间长度之后被检查。当发送事务消息时,用户还可以通过设置用户属性 CHECK_IMMUNITY_TIME_IN_SECONDS 来改变这个限制,该参数优先于 `transactionTimeout` 参数。 4. 事务性消息可能不止一次被检查或消费。 5. 提交给用户的目标主题消息可能会失败,目前这依日志的记录而定。它的高可用性通过 RocketMQ 本身的高可用性机制来保证,如果希望确保事务消息不丢失、并且事务完整性得到保证,建议使用同步的双重写入机制。 6. 事务消息的生产者 ID 不能与其他类型消息的生产者 ID 共享。与其他类型的消息不同,事务消息允许反向查询、MQ服务器能通过它们的生产者 ID 查询到消费者。 diff --git a/docs/en/Example_Transaction.md b/docs/en/Example_Transaction.md index 9dc700e375..2f74368b52 100644 --- a/docs/en/Example_Transaction.md +++ b/docs/en/Example_Transaction.md @@ -88,8 +88,8 @@ public class TransactionListenerImpl implements TransactionListener { ## 3 Usage Constraint 1. Messages of the transactional have no schedule and batch support. -2. In order to avoid a single message being checked too many times and lead to half queue message accumulation, we limited the number of checks for a single message to 15 times by default, but users can change this limit by change the ```transactionCheckMax``` parameter in the configuration of the broker, if one message has been checked over ```transactionCheckMax``` times, broker will discard this message and print an error log at the same time by default. Users can change this behavior by override the ```AbstractTransactionCheckListener``` class. -3. A transactional message will be checked after a certain period of time that determined by parameter ```transactionTimeout``` in the configuration of the broker. And users also can change this limit by set user property “CHECK_IMMUNITY_TIME_IN_SECONDS” when sending transactional message, this parameter takes precedence over the “transactionMsgTimeout” parameter. +2. In order to avoid a single message being checked too many times and lead to half queue message accumulation, we limited the number of checks for a single message to 15 times by default, but users can change this limit by change the ```transactionCheckMax``` parameter in the configuration of the broker, if one message has been checked over ```transactionCheckMax``` times, broker will discard this message and print an error log at the same time by default. Users can change this behavior by override the `AbstractTransactionalMessageCheckListener` class. +3. A transactional message will be checked after a certain period of time that determined by parameter ```transactionTimeout``` in the configuration of the broker. And users also can change this limit by set user property “CHECK_IMMUNITY_TIME_IN_SECONDS” when sending transactional message, this parameter takes precedence over the “transactionTimeout” parameter. 4. A transactional message maybe checked or consumed more than once. 5. Committed message reput to the user’s target topic may fail. Currently, it depends on the log record. High availability is ensured by the high availability mechanism of RocketMQ itself. If you want to ensure that the transactional message isn’t lost and the transaction integrity is guaranteed, it is recommended to use synchronous double write. mechanism. 6. Producer IDs of transactional messages cannot be shared with producer IDs of other types of messages. Unlike other types of message, transactional messages allow backward queries. MQ Server query clients by their Producer IDs. From 14969aa389f1c87d362504a3899c19ed37a3ee51 Mon Sep 17 00:00:00 2001 From: duhenglucky Date: Tue, 24 Mar 2020 12:08:34 +0800 Subject: [PATCH 10/49] feat(broker) prevent blocked by group transfer service --- .../java/org/apache/rocketmq/broker/BrokerController.java | 5 +++-- .../rocketmq/broker/processor/SendMessageProcessor.java | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java index 85009d620f..194f2850fa 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java @@ -1240,6 +1240,7 @@ public class BrokerController { } } - - + public ExecutorService getSendMessageExecutor() { + return sendMessageExecutor; + } } 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 801d886c43..4dc311db91 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 @@ -79,7 +79,7 @@ public class SendMessageProcessor extends AbstractSendMessageProcessor implement @Override public void asyncProcessRequest(ChannelHandlerContext ctx, RemotingCommand request, RemotingResponseCallback responseCallback) throws Exception { - asyncProcessRequest(ctx, request).thenAccept(responseCallback::callback); + asyncProcessRequest(ctx, request).thenAcceptAsync(responseCallback::callback, this.brokerController.getSendMessageExecutor()); } public CompletableFuture asyncProcessRequest(ChannelHandlerContext ctx, From b56fa6ad2b64766c87dcb485f928ce6cdbc35f2d Mon Sep 17 00:00:00 2001 From: coder-zzzz <349427217@qq.com> Date: Wed, 25 Mar 2020 19:34:20 +0800 Subject: [PATCH 11/49] fix #1883 fix wipeWritePerm doc desc --- docs/cn/operation.md | 2 +- docs/en/CLITools.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/cn/operation.md b/docs/cn/operation.md index e35bd6812d..4dcf97c677 100644 --- a/docs/cn/operation.md +++ b/docs/cn/operation.md @@ -564,7 +564,7 @@ $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-sync/broker 从NameServer上清除 Broker写权限 -b - Broker 地址,地址为ip:port + BrokerName -n diff --git a/docs/en/CLITools.md b/docs/en/CLITools.md index 909f002ff4..3a56ccd16a 100644 --- a/docs/en/CLITools.md +++ b/docs/en/CLITools.md @@ -424,7 +424,7 @@ Before introducing the mqadmin management tool, the following points need to be Clear write permissions for broker from nameServer -b - Declare the address of the broker and format as ip:port + Declare the BrokerName -n From 30ad172331a56b67a33b9ee466e300d2acd0586b Mon Sep 17 00:00:00 2001 From: xujianhai666 <52450794+xujianhai666@users.noreply.github.com> Date: Mon, 30 Mar 2020 16:44:58 +0800 Subject: [PATCH 12/49] [ISSUE #1741] Rename takeMessags to takeMessages Closes #1741 --- .../impl/consumer/ConsumeMessageOrderlyService.java | 2 +- .../rocketmq/client/impl/consumer/ProcessQueue.java | 2 +- .../client/impl/consumer/ProcessQueueTest.java | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageOrderlyService.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageOrderlyService.java index e440bd98a9..f65567b685 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageOrderlyService.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ConsumeMessageOrderlyService.java @@ -452,7 +452,7 @@ public class ConsumeMessageOrderlyService implements ConsumeMessageService { final int consumeBatchSize = ConsumeMessageOrderlyService.this.defaultMQPushConsumer.getConsumeMessageBatchMaxSize(); - List msgs = this.processQueue.takeMessags(consumeBatchSize); + List msgs = this.processQueue.takeMessages(consumeBatchSize); defaultMQPushConsumerImpl.resetRetryAndNamespace(msgs, defaultMQPushConsumer.getConsumerGroup()); if (!msgs.isEmpty()) { final ConsumeOrderlyContext context = new ConsumeOrderlyContext(this.messageQueue); diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ProcessQueue.java b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ProcessQueue.java index 092da9aa33..f659bd3f83 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ProcessQueue.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/consumer/ProcessQueue.java @@ -296,7 +296,7 @@ public class ProcessQueue { } } - public List takeMessags(final int batchSize) { + public List takeMessages(final int batchSize) { List result = new ArrayList(batchSize); final long now = System.currentTimeMillis(); try { diff --git a/client/src/test/java/org/apache/rocketmq/client/impl/consumer/ProcessQueueTest.java b/client/src/test/java/org/apache/rocketmq/client/impl/consumer/ProcessQueueTest.java index d6a6dcf72f..16e4a0d901 100644 --- a/client/src/test/java/org/apache/rocketmq/client/impl/consumer/ProcessQueueTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/impl/consumer/ProcessQueueTest.java @@ -38,7 +38,7 @@ public class ProcessQueueTest { assertThat(pq.getMsgCount().get()).isEqualTo(100); - pq.takeMessags(10); + pq.takeMessages(10); pq.commit(); assertThat(pq.getMsgCount().get()).isEqualTo(90); @@ -55,7 +55,7 @@ public class ProcessQueueTest { assertThat(pq.getMsgSize().get()).isEqualTo(100 * 123); - pq.takeMessags(10); + pq.takeMessages(10); pq.commit(); assertThat(pq.getMsgSize().get()).isEqualTo(90 * 123); @@ -74,17 +74,17 @@ public class ProcessQueueTest { assertThat(processQueueInfo.getCachedMsgSizeInMiB()).isEqualTo(12); - pq.takeMessags(10000); + pq.takeMessages(10000); pq.commit(); pq.fillProcessQueueInfo(processQueueInfo); assertThat(processQueueInfo.getCachedMsgSizeInMiB()).isEqualTo(10); - pq.takeMessags(10000); + pq.takeMessages(10000); pq.commit(); pq.fillProcessQueueInfo(processQueueInfo); assertThat(processQueueInfo.getCachedMsgSizeInMiB()).isEqualTo(9); - pq.takeMessags(80000); + pq.takeMessages(80000); pq.commit(); pq.fillProcessQueueInfo(processQueueInfo); assertThat(processQueueInfo.getCachedMsgSizeInMiB()).isEqualTo(0); From 92b8cf9738aa898032bf33e59065686e029779c2 Mon Sep 17 00:00:00 2001 From: MountainOne <593591605@qq.com> Date: Mon, 30 Mar 2020 17:50:24 +0800 Subject: [PATCH 13/49] [ISSUE #1505]Fix the ListSplitter of batch sample in docs --- docs/cn/RocketMQ_Example.md | 88 +++++++++++++++++++------------------ docs/en/Example_Batch.md | 59 +++++++++++++------------ 2 files changed, 78 insertions(+), 69 deletions(-) diff --git a/docs/cn/RocketMQ_Example.md b/docs/cn/RocketMQ_Example.md index 66980d577a..9559a6049b 100644 --- a/docs/cn/RocketMQ_Example.md +++ b/docs/cn/RocketMQ_Example.md @@ -500,48 +500,52 @@ try { 复杂度只有当你发送大批量时才会增长,你可能不确定它是否超过了大小限制(4MB)。这时候你最好把你的消息列表分割一下: ```java - -public class ListSplitter implements Iterator> { - private final int SIZE_LIMIT = 1024 * 1024 * 4; - private final List messages; - private int currIndex; - public ListSplitter(List messages) { - this.messages = messages; - } - @Override public boolean hasNext() { - return currIndex < messages.size(); - } - @Override public List next() { - int nextIndex = currIndex; - int totalSize = 0; - for (; nextIndex < messages.size(); nextIndex++) { - Message message = messages.get(nextIndex); - int tmpSize = message.getTopic().length() + message.getBody().length; - Map properties = message.getProperties(); - for (Map.Entry entry : properties.entrySet()) { - tmpSize += entry.getKey().length() + entry.getValue().length(); - } - tmpSize = tmpSize + 20; // 增加日志的开销20字节 - if (tmpSize > SIZE_LIMIT) { - //单个消息超过了最大的限制 - //忽略,否则会阻塞分裂的进程 - if (nextIndex - currIndex == 0) { - //假如下一个子列表没有元素,则添加这个子列表然后退出循环,否则只是退出循环 - nextIndex++; - } - break; - } - if (tmpSize + totalSize > SIZE_LIMIT) { - break; - } else { - totalSize += tmpSize; - } - - } - List subList = messages.subList(currIndex, nextIndex); - currIndex = nextIndex; - return subList; - } +public class ListSplitter implements Iterator> { + private final int SIZE_LIMIT = 1024 * 1024 * 4; + private final List messages; + private int currIndex; + public ListSplitter(List messages) { + this.messages = messages; + } + @Override public boolean hasNext() { + return currIndex < messages.size(); + } + @Override public List next() { + int startIndex = getStartIndex(); + int nextIndex = startIndex; + int totalSize = 0; + for (; nextIndex < messages.size(); nextIndex++) { + Message message = messages.get(nextIndex); + int tmpSize = calcMessageSize(message); + if (tmpSize + totalSize > SIZE_LIMIT) { + break; + } else { + totalSize += tmpSize; + } + } + List subList = messages.subList(startIndex, nextIndex); + currIndex = nextIndex; + return subList; + } + private int getStartIndex() { + Message currMessage = messages.get(currIndex); + int tmpSize = calcMessageSize(currMessage); + while(tmpSize > SIZE_LIMIT) { + currIndex += 1; + Message message = messages.get(curIndex); + tmpSize = calcMessageSize(message); + } + return currIndex; + } + private int calcMessageSize(Message message) { + int tmpSize = message.getTopic().length() + message.getBody().length(); + Map properties = message.getProperties(); + for (Map.Entry entry : properties.entrySet()) { + tmpSize += entry.getKey().length() + entry.getValue().length(); + } + tmpSize = tmpSize + 20; // 增加⽇日志的开销20字节 + return tmpSize; + } } //把大的消息分裂成若干个小的消息 ListSplitter splitter = new ListSplitter(messages); diff --git a/docs/en/Example_Batch.md b/docs/en/Example_Batch.md index d7199ca7c7..11bb42ffbe 100644 --- a/docs/en/Example_Batch.md +++ b/docs/en/Example_Batch.md @@ -19,47 +19,52 @@ try { ### 2 Split into Lists The complexity only grow when you send large batch and you may not sure if it exceeds the size limit (4MiB). At this time, you’d better split the lists: ```java -public class ListSplitter implements Iterator> { - private final int SIZE_LIMIT = 1000 * 1000; +public class ListSplitter implements Iterator> { + private final int SIZE_LIMIT = 1024 * 1024 * 4; private final List messages; private int currIndex; - public ListSplitter(List messages) { - this.messages = messages; + public ListSplitter(List messages) { + this.messages = messages; } @Override public boolean hasNext() { - return currIndex < messages.size(); + return currIndex < messages.size(); } - @Override public List next() { - int nextIndex = currIndex; + @Override public List next() { + int startIndex = getStartIndex(); + int nextIndex = startIndex; int totalSize = 0; for (; nextIndex < messages.size(); nextIndex++) { - Message message = messages.get(nextIndex); - int tmpSize = message.getTopic().length() + message.getBody().length; - Map properties = message.getProperties(); - for (Map.Entry entry : properties.entrySet()) { - tmpSize += entry.getKey().length() + entry.getValue().length(); - } - tmpSize = tmpSize + 20; //for log overhead - if (tmpSize > SIZE_LIMIT) { - //it is unexpected that single message exceeds the SIZE_LIMIT - //here just let it go, otherwise it will block the splitting process - if (nextIndex - currIndex == 0) { - //if the next sublist has no element, add this one and then break, otherwise just break - nextIndex++; - } - break; - } + Message message = messages.get(nextIndex); + int tmpSize = calcMessageSize(message); if (tmpSize + totalSize > SIZE_LIMIT) { - break; + break; } else { - totalSize += tmpSize; + totalSize += tmpSize; } - } - List subList = messages.subList(currIndex, nextIndex); + List subList = messages.subList(startIndex, nextIndex); currIndex = nextIndex; return subList; } + private int getStartIndex() { + Message currMessage = messages.get(currIndex); + int tmpSize = calcMessageSize(currMessage); + while(tmpSize > SIZE_LIMIT) { + currIndex += 1; + Message message = messages.get(curIndex); + tmpSize = calcMessageSize(message); + } + return currIndex; + } + private int calcMessageSize(Message message) { + int tmpSize = message.getTopic().length() + message.getBody().length(); + Map properties = message.getProperties(); + for (Map.Entry entry : properties.entrySet()) { + tmpSize += entry.getKey().length() + entry.getValue().length(); + } + tmpSize = tmpSize + 20; // Increase the log overhead by 20 bytes + return tmpSize; + } } // then you could split the large list into small ones: From bca05f7b5ce349717b4cdf820484a46b74217df4 Mon Sep 17 00:00:00 2001 From: SSpirits Date: Mon, 30 Mar 2020 17:53:33 +0800 Subject: [PATCH 14/49] [ISSUE #1574] change GET_ROUTEINTO_BY_TOPIC to GET_ROUTEINFO_BY_TOPIC [ISSUE #1574] change GET_ROUTEINTO_BY_TOPIC to GET_ROUTEINFO_BY_TOPIC --- .../java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java | 2 +- .../java/org/apache/rocketmq/common/protocol/RequestCode.java | 2 +- .../rocketmq/namesrv/processor/DefaultRequestProcessor.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java index c7d5a06dfd..643b287303 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java @@ -1366,7 +1366,7 @@ public class MQClientAPIImpl { GetRouteInfoRequestHeader requestHeader = new GetRouteInfoRequestHeader(); requestHeader.setTopic(topic); - RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ROUTEINTO_BY_TOPIC, requestHeader); + RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ROUTEINFO_BY_TOPIC, requestHeader); RemotingCommand response = this.remotingClient.invokeSync(null, request, timeoutMillis); assert response != null; diff --git a/common/src/main/java/org/apache/rocketmq/common/protocol/RequestCode.java b/common/src/main/java/org/apache/rocketmq/common/protocol/RequestCode.java index b3009d738f..75ceff38cf 100644 --- a/common/src/main/java/org/apache/rocketmq/common/protocol/RequestCode.java +++ b/common/src/main/java/org/apache/rocketmq/common/protocol/RequestCode.java @@ -89,7 +89,7 @@ public class RequestCode { public static final int REGISTER_BROKER = 103; public static final int UNREGISTER_BROKER = 104; - public static final int GET_ROUTEINTO_BY_TOPIC = 105; + public static final int GET_ROUTEINFO_BY_TOPIC = 105; public static final int GET_BROKER_CLUSTER_INFO = 106; public static final int UPDATE_AND_CREATE_SUBSCRIPTIONGROUP = 200; 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 7210246edc..d62be47b84 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 @@ -97,7 +97,7 @@ public class DefaultRequestProcessor extends AsyncNettyRequestProcessor implemen } case RequestCode.UNREGISTER_BROKER: return this.unregisterBroker(ctx, request); - case RequestCode.GET_ROUTEINTO_BY_TOPIC: + case RequestCode.GET_ROUTEINFO_BY_TOPIC: return this.getRouteInfoByTopic(ctx, request); case RequestCode.GET_BROKER_CLUSTER_INFO: return this.getBrokerClusterInfo(ctx, request); From 3d13dae790e0b1e1e0ec3d744ace04af52c24fd4 Mon Sep 17 00:00:00 2001 From: Hoolefu Date: Mon, 30 Mar 2020 20:35:24 +0800 Subject: [PATCH 15/49] [ISSUE#728]check ChannelHandlerContext whether is null [ISSUE#728]check ChannelHandlerContext whether is null --- .../processor/DefaultRequestProcessor.java | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) 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 d62be47b84..44a2b6f261 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 @@ -384,10 +384,12 @@ public class DefaultRequestProcessor extends AsyncNettyRequestProcessor implemen int wipeTopicCnt = this.namesrvController.getRouteInfoManager().wipeWritePermOfBrokerByLock(requestHeader.getBrokerName()); - log.info("wipe write perm of broker[{}], client: {}, {}", - requestHeader.getBrokerName(), - RemotingHelper.parseChannelRemoteAddr(ctx.channel()), - wipeTopicCnt); + if (ctx != null) { + log.info("wipe write perm of broker[{}], client: {}, {}", + requestHeader.getBrokerName(), + RemotingHelper.parseChannelRemoteAddr(ctx.channel()), + wipeTopicCnt); + } responseHeader.setWipeTopicCount(wipeTopicCnt); response.setCode(ResponseCode.SUCCESS); @@ -502,7 +504,9 @@ public class DefaultRequestProcessor extends AsyncNettyRequestProcessor implemen } private RemotingCommand updateConfig(ChannelHandlerContext ctx, RemotingCommand request) { - log.info("updateConfig called by {}", RemotingHelper.parseChannelRemoteAddr(ctx.channel())); + if (ctx != null) { + log.info("updateConfig called by {}", RemotingHelper.parseChannelRemoteAddr(ctx.channel())); + } final RemotingCommand response = RemotingCommand.createResponseCommand(null); @@ -518,13 +522,6 @@ public class DefaultRequestProcessor extends AsyncNettyRequestProcessor implemen return response; } - if (bodyStr == null) { - log.error("updateConfig get null body!"); - response.setCode(ResponseCode.SYSTEM_ERROR); - response.setRemark("string2Properties error"); - return response; - } - Properties properties = MixAll.string2Properties(bodyStr); if (properties == null) { log.error("updateConfig MixAll.string2Properties error {}", bodyStr); From c5e5f8dd6e2ff22e6d433880cce2d789640cfa29 Mon Sep 17 00:00:00 2001 From: Crearns Date: Tue, 31 Mar 2020 14:12:29 +0800 Subject: [PATCH 16/49] Correct the Java doc for MessageListenerOrderly.java (#1897) * Update the javadoc for MessageListenerOrderly --- .../client/consumer/listener/MessageListenerOrderly.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/src/main/java/org/apache/rocketmq/client/consumer/listener/MessageListenerOrderly.java b/client/src/main/java/org/apache/rocketmq/client/consumer/listener/MessageListenerOrderly.java index d148df5c67..419169d76a 100644 --- a/client/src/main/java/org/apache/rocketmq/client/consumer/listener/MessageListenerOrderly.java +++ b/client/src/main/java/org/apache/rocketmq/client/consumer/listener/MessageListenerOrderly.java @@ -20,8 +20,7 @@ import java.util.List; import org.apache.rocketmq.common.message.MessageExt; /** - * A MessageListenerConcurrently object is used to receive asynchronously delivered messages orderly.one queue,one - * thread + * A MessageListenerOrderly object is used to receive messages orderly. One queue by one thread */ public interface MessageListenerOrderly extends MessageListener { /** From a991ea5a7959463ab751ebb17e3f25403924de29 Mon Sep 17 00:00:00 2001 From: dinglei Date: Wed, 1 Apr 2020 11:13:19 +0800 Subject: [PATCH 17/49] [ISSUE #1859] add codecov report for unit tests. [ISSUE #1859] add codecov report for tests. --- .travis.yml | 1 + README.md | 1 + pom.xml | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f3bcb1db58..34d38c2c54 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,3 +33,4 @@ script: after_success: - mvn clean install -Pit-test - mvn sonar:sonar -Psonar-apache + - bash <(curl -s https://codecov.io/bash) || echo 'Codecov failed to upload' diff --git a/README.md b/README.md index a47efbcea3..9b28078f93 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ ## Apache RocketMQ [![Build Status](https://travis-ci.org/apache/rocketmq.svg?branch=master)](https://travis-ci.org/apache/rocketmq) [![Coverage Status](https://coveralls.io/repos/github/apache/rocketmq/badge.svg?branch=master)](https://coveralls.io/github/apache/rocketmq?branch=master) +[![CodeCov](https://codecov.io/gh/apache/rocketmq/branch/master/graph/badge.svg)](https://codecov.io/gh/apache/rocketmq) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.apache.rocketmq/rocketmq-all/badge.svg)](http://search.maven.org/#search%7Cga%7C1%7Corg.apache.rocketmq) [![GitHub release](https://img.shields.io/badge/release-download-orange.svg)](https://rocketmq.apache.org/dowloading/releases) [![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) diff --git a/pom.xml b/pom.xml index f87a1dda3b..2cab4823d4 100644 --- a/pom.xml +++ b/pom.xml @@ -280,7 +280,7 @@ org.jacoco jacoco-maven-plugin - 0.7.8 + 0.8.5 default-prepare-agent From 18a879b58cb546172e72b42c2b15913081013fff Mon Sep 17 00:00:00 2001 From: superleo-cn Date: Wed, 1 Apr 2020 15:19:15 +0800 Subject: [PATCH 18/49] [ISSUE #1866] Fix typo in document --- docs/en/Example_Transaction.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/Example_Transaction.md b/docs/en/Example_Transaction.md index 2f74368b52..a35b040f82 100644 --- a/docs/en/Example_Transaction.md +++ b/docs/en/Example_Transaction.md @@ -88,8 +88,8 @@ public class TransactionListenerImpl implements TransactionListener { ## 3 Usage Constraint 1. Messages of the transactional have no schedule and batch support. -2. In order to avoid a single message being checked too many times and lead to half queue message accumulation, we limited the number of checks for a single message to 15 times by default, but users can change this limit by change the ```transactionCheckMax``` parameter in the configuration of the broker, if one message has been checked over ```transactionCheckMax``` times, broker will discard this message and print an error log at the same time by default. Users can change this behavior by override the `AbstractTransactionalMessageCheckListener` class. -3. A transactional message will be checked after a certain period of time that determined by parameter ```transactionTimeout``` in the configuration of the broker. And users also can change this limit by set user property “CHECK_IMMUNITY_TIME_IN_SECONDS” when sending transactional message, this parameter takes precedence over the “transactionTimeout” parameter. +2. In order to avoid a single message being checked too many times and lead to half queue message accumulation, we limited the number of checks for a single message to 15 times by default, but users can change this limit by change the ```transactionCheckMax``` parameter in the configuration of the broker, if one message has been checked over ```transactionCheckMax``` times, broker will discard this message and print an error log at the same time by default. Users can change this behavior by override the ```AbstractTransactionalMessageCheckListener``` class. +3. A transactional message will be checked after a certain period of time that determined by parameter g```transactionTimeout``` in the configuration of the broker. And users also can change this limit by set user property ```CHECK_IMMUNITY_TIME_IN_SECONDS``` when sending transactional message, this parameter takes precedence over the ```transactionTimeout``` parameter. 4. A transactional message maybe checked or consumed more than once. 5. Committed message reput to the user’s target topic may fail. Currently, it depends on the log record. High availability is ensured by the high availability mechanism of RocketMQ itself. If you want to ensure that the transactional message isn’t lost and the transaction integrity is guaranteed, it is recommended to use synchronous double write. mechanism. 6. Producer IDs of transactional messages cannot be shared with producer IDs of other types of messages. Unlike other types of message, transactional messages allow backward queries. MQ Server query clients by their Producer IDs. From 0a9822e5f4667654b37bd480865174d1bb2c28fe Mon Sep 17 00:00:00 2001 From: Heng Du Date: Wed, 1 Apr 2020 15:25:55 +0800 Subject: [PATCH 19/49] [ISSUE #1895] Expose the flush disk timeout error --- .../main/java/org/apache/rocketmq/store/CommitLog.java | 9 +++------ .../java/org/apache/rocketmq/store/ha/HAService.java | 3 ++- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/store/src/main/java/org/apache/rocketmq/store/CommitLog.java b/store/src/main/java/org/apache/rocketmq/store/CommitLog.java index 82bd6706c9..651fa56dee 100644 --- a/store/src/main/java/org/apache/rocketmq/store/CommitLog.java +++ b/store/src/main/java/org/apache/rocketmq/store/CommitLog.java @@ -1383,11 +1383,8 @@ public class CommitLog { return nextOffset; } - public void wakeupCustomer(final boolean flushOK) { - long endTimestamp = System.currentTimeMillis(); - PutMessageStatus result = (flushOK && ((endTimestamp - this.startTimestamp) <= this.timeoutMillis)) ? - PutMessageStatus.PUT_OK : PutMessageStatus.FLUSH_SLAVE_TIMEOUT; - this.flushOKFuture.complete(result); + public void wakeupCustomer(final PutMessageStatus putMessageStatus) { + this.flushOKFuture.complete(putMessageStatus); } public CompletableFuture future() { @@ -1433,7 +1430,7 @@ public class CommitLog { } } - req.wakeupCustomer(flushOK); + req.wakeupCustomer(flushOK ? PutMessageStatus.PUT_OK : PutMessageStatus.FLUSH_DISK_TIMEOUT); } long storeTimestamp = CommitLog.this.mappedFileQueue.getStoreTimestamp(); diff --git a/store/src/main/java/org/apache/rocketmq/store/ha/HAService.java b/store/src/main/java/org/apache/rocketmq/store/ha/HAService.java index 3035c575cf..d3220d091d 100644 --- a/store/src/main/java/org/apache/rocketmq/store/ha/HAService.java +++ b/store/src/main/java/org/apache/rocketmq/store/ha/HAService.java @@ -39,6 +39,7 @@ import org.apache.rocketmq.logging.InternalLoggerFactory; import org.apache.rocketmq.remoting.common.RemotingUtil; import org.apache.rocketmq.store.CommitLog; import org.apache.rocketmq.store.DefaultMessageStore; +import org.apache.rocketmq.store.PutMessageStatus; public class HAService { private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); @@ -291,7 +292,7 @@ public class HAService { log.warn("transfer messsage to slave timeout, " + req.getNextOffset()); } - req.wakeupCustomer(transferOK); + req.wakeupCustomer(transferOK ? PutMessageStatus.PUT_OK : PutMessageStatus.FLUSH_SLAVE_TIMEOUT); } this.requestsRead.clear(); From 276536ed341a5b3f78479ba23b027ceb0b98cf8d Mon Sep 17 00:00:00 2001 From: rongtong Date: Fri, 3 Apr 2020 15:49:58 +0800 Subject: [PATCH 20/49] [ISSUE #1901] Fix bug in sending request/reply message using broker 4.7.0 fix(broker):fix bug sending request message using broker 4.7.0 (#1902) --- .../rocketmq/broker/processor/SendMessageProcessor.java | 4 ++++ 1 file changed, 4 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 801d886c43..dac4c27856 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 @@ -281,6 +281,10 @@ public class SendMessageProcessor extends AbstractSendMessageProcessor implement msgInner.setBornHost(ctx.channel().remoteAddress()); msgInner.setStoreHost(this.getStoreHost()); msgInner.setReconsumeTimes(requestHeader.getReconsumeTimes() == null ? 0 : requestHeader.getReconsumeTimes()); + String clusterName = this.brokerController.getBrokerConfig().getBrokerClusterName(); + MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_CLUSTER, clusterName); + msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties())); + CompletableFuture putMessageResult = null; Map origProps = MessageDecoder.string2messageProperties(requestHeader.getProperties()); String transFlag = origProps.get(MessageConst.PROPERTY_TRANSACTION_PREPARED); From 7760bb4a43924608cb998fc87d038c9cf472fcd1 Mon Sep 17 00:00:00 2001 From: carlvine500 Date: Fri, 3 Apr 2020 16:20:08 +0800 Subject: [PATCH 21/49] [ISSUE #1463] Skip encode the null value property as string "null" (#1899) * Update MessageDecoder.java * add test case for MessageDecoder --- .../common/message/MessageDecoder.java | 3 +++ .../common/message/MessageDecoderTest.java | 21 ++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/org/apache/rocketmq/common/message/MessageDecoder.java b/common/src/main/java/org/apache/rocketmq/common/message/MessageDecoder.java index 54d5e6b60f..d048dde2b9 100644 --- a/common/src/main/java/org/apache/rocketmq/common/message/MessageDecoder.java +++ b/common/src/main/java/org/apache/rocketmq/common/message/MessageDecoder.java @@ -414,6 +414,9 @@ public class MessageDecoder { final String name = entry.getKey(); final String value = entry.getValue(); + if (value == null) { + continue; + } sb.append(name); sb.append(NAME_VALUE_SEPARATOR); sb.append(value); diff --git a/common/src/test/java/org/apache/rocketmq/common/message/MessageDecoderTest.java b/common/src/test/java/org/apache/rocketmq/common/message/MessageDecoderTest.java index 82ad2714dd..fde523dba9 100644 --- a/common/src/test/java/org/apache/rocketmq/common/message/MessageDecoderTest.java +++ b/common/src/test/java/org/apache/rocketmq/common/message/MessageDecoderTest.java @@ -246,4 +246,23 @@ public class MessageDecoderTest { assertThat("abc").isEqualTo(decodedMsg.getTopic()); } -} + + public void testNullValueProperty() throws Exception { + MessageExt msg = new MessageExt(); + msg.setBody("x".getBytes()); + msg.setTopic("x"); + msg.setBornHost(new InetSocketAddress("127.0.0.1", 9000)); + msg.setStoreHost(new InetSocketAddress("127.0.0.1", 9000)); + String key = "NullValueKey"; + msg.putProperty(key, null); + try { + byte[] encode = MessageDecoder.encode(msg, false); + MessageExt decode = MessageDecoder.decode(ByteBuffer.wrap(encode)); + assertThat(decode.getProperty(key)).isNull(); + } catch (Exception e) { + e.printStackTrace(); + assertThat(Boolean.FALSE).isTrue(); + } + } + +} \ No newline at end of file From dc815cc581f9e9dd36efa926fc7087ff1ecbf1a6 Mon Sep 17 00:00:00 2001 From: Heng Du Date: Fri, 3 Apr 2020 16:30:39 +0800 Subject: [PATCH 22/49] [ISSUE #1912]Polish the committed offset logic for the lite pull consumer. [ISSUE #1912]Polish the committed offset logic for the lite pull consumer. --- .../rocketmq/client/consumer/DefaultLitePullConsumer.java | 6 +++--- .../client/impl/consumer/DefaultLitePullConsumerImpl.java | 2 +- 2 files changed, 4 insertions(+), 4 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 ef76cfd4d5..6718eb55fd 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 @@ -279,7 +279,7 @@ public class DefaultLitePullConsumer extends ClientConfig implements LitePullCon @Override public Long committed(MessageQueue messageQueue) throws MQClientException { - return this.defaultLitePullConsumerImpl.committed(messageQueue); + return this.defaultLitePullConsumerImpl.committed(queueWithNamespace(messageQueue)); } @Override @@ -289,12 +289,12 @@ public class DefaultLitePullConsumer extends ClientConfig implements LitePullCon @Override public void seekToBegin(MessageQueue messageQueue) throws MQClientException { - this.defaultLitePullConsumerImpl.seekToBegin(messageQueue); + this.defaultLitePullConsumerImpl.seekToBegin(queueWithNamespace(messageQueue)); } @Override public void seekToEnd(MessageQueue messageQueue) throws MQClientException { - this.defaultLitePullConsumerImpl.seekToEnd(messageQueue); + this.defaultLitePullConsumerImpl.seekToEnd(queueWithNamespace(messageQueue)); } @Override 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 8ad7a6b44b..f54078fcfd 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 @@ -631,7 +631,7 @@ public class DefaultLitePullConsumerImpl implements MQConsumerInner { public long committed(MessageQueue messageQueue) throws MQClientException { checkServiceState(); - long offset = this.offsetStore.readOffset(messageQueue, ReadOffsetType.READ_FROM_STORE); + long offset = this.offsetStore.readOffset(messageQueue, ReadOffsetType.MEMORY_FIRST_THEN_STORE); if (offset == -2) throw new MQClientException("Fetch consume offset from broker exception", null); return offset; From 78b6d7b5bb128b0d4ad80f2814e228485d3b8c15 Mon Sep 17 00:00:00 2001 From: James Yin Date: Wed, 15 Apr 2020 11:42:51 +0800 Subject: [PATCH 23/49] fixed typo in RequestResponseFuture. rename RequestResponseFuture.setSendReqeustOk to RequestResponseFuture.setSendRequestOk. --- .../impl/producer/DefaultMQProducerImpl.java | 20 +++++++++---------- .../producer/RequestResponseFuture.java | 4 ++-- .../producer/DefaultMQProducerTest.java | 2 +- .../producer/RequestResponseFutureTest.java | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java index fca50cc565..2f171ae1e3 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java @@ -1356,12 +1356,12 @@ public class DefaultMQProducerImpl implements MQProducerInner { this.sendDefaultImpl(msg, CommunicationMode.ASYNC, new SendCallback() { @Override public void onSuccess(SendResult sendResult) { - requestResponseFuture.setSendReqeustOk(true); + requestResponseFuture.setSendRequestOk(true); } @Override public void onException(Throwable e) { - requestResponseFuture.setSendReqeustOk(false); + requestResponseFuture.setSendRequestOk(false); requestResponseFuture.putResponseMessage(null); requestResponseFuture.setCause(e); } @@ -1395,7 +1395,7 @@ public class DefaultMQProducerImpl implements MQProducerInner { this.sendDefaultImpl(msg, CommunicationMode.ASYNC, new SendCallback() { @Override public void onSuccess(SendResult sendResult) { - requestResponseFuture.setSendReqeustOk(true); + requestResponseFuture.setSendRequestOk(true); } @Override @@ -1421,12 +1421,12 @@ public class DefaultMQProducerImpl implements MQProducerInner { this.sendSelectImpl(msg, selector, arg, CommunicationMode.ASYNC, new SendCallback() { @Override public void onSuccess(SendResult sendResult) { - requestResponseFuture.setSendReqeustOk(true); + requestResponseFuture.setSendRequestOk(true); } @Override public void onException(Throwable e) { - requestResponseFuture.setSendReqeustOk(false); + requestResponseFuture.setSendRequestOk(false); requestResponseFuture.putResponseMessage(null); requestResponseFuture.setCause(e); } @@ -1461,7 +1461,7 @@ public class DefaultMQProducerImpl implements MQProducerInner { this.sendSelectImpl(msg, selector, arg, CommunicationMode.ASYNC, new SendCallback() { @Override public void onSuccess(SendResult sendResult) { - requestResponseFuture.setSendReqeustOk(true); + requestResponseFuture.setSendRequestOk(true); } @Override @@ -1487,12 +1487,12 @@ public class DefaultMQProducerImpl implements MQProducerInner { this.sendKernelImpl(msg, mq, CommunicationMode.ASYNC, new SendCallback() { @Override public void onSuccess(SendResult sendResult) { - requestResponseFuture.setSendReqeustOk(true); + requestResponseFuture.setSendRequestOk(true); } @Override public void onException(Throwable e) { - requestResponseFuture.setSendReqeustOk(false); + requestResponseFuture.setSendRequestOk(false); requestResponseFuture.putResponseMessage(null); requestResponseFuture.setCause(e); } @@ -1526,7 +1526,7 @@ public class DefaultMQProducerImpl implements MQProducerInner { this.sendKernelImpl(msg, mq, CommunicationMode.ASYNC, new SendCallback() { @Override public void onSuccess(SendResult sendResult) { - requestResponseFuture.setSendReqeustOk(true); + requestResponseFuture.setSendRequestOk(true); } @Override @@ -1540,7 +1540,7 @@ public class DefaultMQProducerImpl implements MQProducerInner { private void requestFail(final String correlationId) { RequestResponseFuture responseFuture = RequestFutureTable.getRequestFutureTable().remove(correlationId); if (responseFuture != null) { - responseFuture.setSendReqeustOk(false); + responseFuture.setSendRequestOk(false); responseFuture.putResponseMessage(null); try { responseFuture.executeRequestCallback(); diff --git a/client/src/main/java/org/apache/rocketmq/client/producer/RequestResponseFuture.java b/client/src/main/java/org/apache/rocketmq/client/producer/RequestResponseFuture.java index c54b236b16..e66c08fdc5 100644 --- a/client/src/main/java/org/apache/rocketmq/client/producer/RequestResponseFuture.java +++ b/client/src/main/java/org/apache/rocketmq/client/producer/RequestResponseFuture.java @@ -103,8 +103,8 @@ public class RequestResponseFuture { return sendRequestOk; } - public void setSendReqeustOk(boolean sendReqeustOk) { - this.sendRequestOk = sendReqeustOk; + public void setSendRequestOk(boolean sendRequestOk) { + this.sendRequestOk = sendRequestOk; } public Message getRequestMsg() { diff --git a/client/src/test/java/org/apache/rocketmq/client/producer/DefaultMQProducerTest.java b/client/src/test/java/org/apache/rocketmq/client/producer/DefaultMQProducerTest.java index d572a2320e..41046fcdf5 100644 --- a/client/src/test/java/org/apache/rocketmq/client/producer/DefaultMQProducerTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/producer/DefaultMQProducerTest.java @@ -373,7 +373,7 @@ public class DefaultMQProducerTest { assertThat(responseMap).isNotNull(); for (Map.Entry entry : responseMap.entrySet()) { RequestResponseFuture future = entry.getValue(); - future.setSendReqeustOk(true); + future.setSendRequestOk(true); message.setFlag(1); future.getRequestCallback().onSuccess(message); } diff --git a/client/src/test/java/org/apache/rocketmq/client/producer/RequestResponseFutureTest.java b/client/src/test/java/org/apache/rocketmq/client/producer/RequestResponseFutureTest.java index 90e4623e9b..bc20873c11 100644 --- a/client/src/test/java/org/apache/rocketmq/client/producer/RequestResponseFutureTest.java +++ b/client/src/test/java/org/apache/rocketmq/client/producer/RequestResponseFutureTest.java @@ -37,7 +37,7 @@ public class RequestResponseFutureTest { @Override public void onException(Throwable e) { } }); - future.setSendReqeustOk(true); + future.setSendRequestOk(true); future.executeRequestCallback(); assertThat(cc.get()).isEqualTo(1); } From 55d66ff4ae649fd32ca77f321a9f67711faa2f2e Mon Sep 17 00:00:00 2001 From: coder-zzzz <349427217@qq.com> Date: Wed, 22 Apr 2020 15:34:07 +0800 Subject: [PATCH 24/49] fix #1957 DefaultMQProducer didn't shutdown completely --- .../apache/rocketmq/client/impl/factory/MQClientInstance.java | 1 + .../rocketmq/client/impl/producer/DefaultMQProducerImpl.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/factory/MQClientInstance.java b/client/src/main/java/org/apache/rocketmq/client/impl/factory/MQClientInstance.java index bbd2eecb1c..4844da51d5 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/factory/MQClientInstance.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/factory/MQClientInstance.java @@ -853,6 +853,7 @@ public class MQClientInstance { this.scheduledExecutorService.shutdown(); this.mQClientAPIImpl.shutdown(); this.rebalanceService.shutdown(); + this.defaultMQProducer.shutdown(); if (this.datagramSocket != null) { this.datagramSocket.close(); diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java index fca50cc565..a7bd1842cd 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/producer/DefaultMQProducerImpl.java @@ -258,7 +258,7 @@ public class DefaultMQProducerImpl implements MQProducerInner { if (shutdownFactory) { this.mQClientFactory.shutdown(); } - + this.timer.cancel(); log.info("the producer [{}] shutdown OK", this.defaultMQProducer.getProducerGroup()); this.serviceState = ServiceState.SHUTDOWN_ALREADY; break; From 2b024c647df3efa05d457696cbf1ecc0aeec09b0 Mon Sep 17 00:00:00 2001 From: coder-zzzz <349427217@qq.com> Date: Fri, 24 Apr 2020 19:59:48 +0800 Subject: [PATCH 25/49] remove duplicate shutdown producer --- .../apache/rocketmq/client/impl/factory/MQClientInstance.java | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/factory/MQClientInstance.java b/client/src/main/java/org/apache/rocketmq/client/impl/factory/MQClientInstance.java index 4844da51d5..bbd2eecb1c 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/factory/MQClientInstance.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/factory/MQClientInstance.java @@ -853,7 +853,6 @@ public class MQClientInstance { this.scheduledExecutorService.shutdown(); this.mQClientAPIImpl.shutdown(); this.rebalanceService.shutdown(); - this.defaultMQProducer.shutdown(); if (this.datagramSocket != null) { this.datagramSocket.close(); From 1052edcc031705026d08bc9f2c68f708d24dc347 Mon Sep 17 00:00:00 2001 From: Jun <62043001+coder-zzzz@users.noreply.github.com> Date: Wed, 29 Apr 2020 11:01:43 +0800 Subject: [PATCH 26/49] [ISSUE #1964]fix Doc error: Connection related command instructions Doc error: Connection related command instructions --- docs/cn/operation.md | 4 ++-- docs/en/CLITools.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/cn/operation.md b/docs/cn/operation.md index 4dcf97c677..d8c0bb151d 100644 --- a/docs/cn/operation.md +++ b/docs/cn/operation.md @@ -1161,7 +1161,7 @@ $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-sync/broker consumerConnec tion + height:89.0pt;border-top:none;width:65pt'>consumerConnection 查询 Consumer 的网络连接 -g @@ -1177,7 +1177,7 @@ $ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-sync/broker producerConnec tion + height:106.0pt;border-top:none;width:65pt'>producerConnection 查询 Producer 的网络连接 -g diff --git a/docs/en/CLITools.md b/docs/en/CLITools.md index 3a56ccd16a..47ba69e571 100644 --- a/docs/en/CLITools.md +++ b/docs/en/CLITools.md @@ -1053,7 +1053,7 @@ Before introducing the mqadmin management tool, the following points need to be consumerConnec tion + height:89.0pt;border-top:none;width:65pt'>consumerConnection Query the network connection of consumer -g @@ -1069,7 +1069,7 @@ Before introducing the mqadmin management tool, the following points need to be producerConnec tion + height:106.0pt;border-top:none;width:65pt'>producerConnection Query the network connection of producer -g From bc692b408d9725bf81dbe59c9aae4b4f2b304572 Mon Sep 17 00:00:00 2001 From: bruce <841326226@qq.com> Date: Wed, 29 Apr 2020 11:04:56 +0800 Subject: [PATCH 27/49] [ISSUE #1906] The BooleanConstantExpression might lead to class loading deadlock [ISSUE #1906] The BooleanConstantExpression might lead to class loading deadlock --- .../expression/BooleanConstantExpression.java | 37 ++++++++++++++++++ .../expression/ComparisonExpression.java | 4 +- .../filter/expression/ConstantExpression.java | 39 +++++-------------- .../filter/parser/SelectorParser.java | 7 ++-- 4 files changed, 53 insertions(+), 34 deletions(-) create mode 100644 filter/src/main/java/org/apache/rocketmq/filter/expression/BooleanConstantExpression.java diff --git a/filter/src/main/java/org/apache/rocketmq/filter/expression/BooleanConstantExpression.java b/filter/src/main/java/org/apache/rocketmq/filter/expression/BooleanConstantExpression.java new file mode 100644 index 0000000000..958e622f6c --- /dev/null +++ b/filter/src/main/java/org/apache/rocketmq/filter/expression/BooleanConstantExpression.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.rocketmq.filter.expression; + +/** + * BooleanConstantExpression + */ +public class BooleanConstantExpression extends ConstantExpression implements BooleanExpression { + + public static final BooleanConstantExpression NULL = new BooleanConstantExpression(null); + public static final BooleanConstantExpression TRUE = new BooleanConstantExpression(Boolean.TRUE); + public static final BooleanConstantExpression FALSE = new BooleanConstantExpression(Boolean.FALSE); + + public BooleanConstantExpression(Object value) { + super(value); + } + + public boolean matches(EvaluationContext context) throws Exception { + Object object = evaluate(context); + return object != null && object == Boolean.TRUE; + } +} diff --git a/filter/src/main/java/org/apache/rocketmq/filter/expression/ComparisonExpression.java b/filter/src/main/java/org/apache/rocketmq/filter/expression/ComparisonExpression.java index fe898e7884..9a919c4441 100644 --- a/filter/src/main/java/org/apache/rocketmq/filter/expression/ComparisonExpression.java +++ b/filter/src/main/java/org/apache/rocketmq/filter/expression/ComparisonExpression.java @@ -90,11 +90,11 @@ public abstract class ComparisonExpression extends BinaryExpression implements B } public static BooleanExpression createIsNull(Expression left) { - return doCreateEqual(left, ConstantExpression.NULL); + return doCreateEqual(left, BooleanConstantExpression.NULL); } public static BooleanExpression createIsNotNull(Expression left) { - return UnaryExpression.createNOT(doCreateEqual(left, ConstantExpression.NULL)); + return UnaryExpression.createNOT(doCreateEqual(left, BooleanConstantExpression.NULL)); } public static BooleanExpression createNotEqual(Expression left, Expression right) { diff --git a/filter/src/main/java/org/apache/rocketmq/filter/expression/ConstantExpression.java b/filter/src/main/java/org/apache/rocketmq/filter/expression/ConstantExpression.java index e649f5a232..127bd8ba7b 100644 --- a/filter/src/main/java/org/apache/rocketmq/filter/expression/ConstantExpression.java +++ b/filter/src/main/java/org/apache/rocketmq/filter/expression/ConstantExpression.java @@ -30,21 +30,6 @@ package org.apache.rocketmq.filter.expression; */ public class ConstantExpression implements Expression { - static class BooleanConstantExpression extends ConstantExpression implements BooleanExpression { - public BooleanConstantExpression(Object value) { - super(value); - } - - public boolean matches(EvaluationContext context) throws Exception { - Object object = evaluate(context); - return object != null && object == Boolean.TRUE; - } - } - - public static final BooleanConstantExpression NULL = new BooleanConstantExpression(null); - public static final BooleanConstantExpression TRUE = new BooleanConstantExpression(Boolean.TRUE); - public static final BooleanConstantExpression FALSE = new BooleanConstantExpression(Boolean.FALSE); - private Object value; public ConstantExpression(Object value) { @@ -60,16 +45,10 @@ public class ConstantExpression implements Expression { // only support Long.MIN_VALUE ~ Long.MAX_VALUE Number value = new Long(text); -// try { -// value = new Long(text); -// } catch (NumberFormatException e) { -// // The number may be too big to fit in a long. -// value = new BigDecimal(text); -// } long l = value.longValue(); if (Integer.MIN_VALUE <= l && l <= Integer.MAX_VALUE) { - value = Integer.valueOf(value.intValue()); + value = value.intValue(); } return new ConstantExpression(value); } @@ -106,7 +85,7 @@ public class ConstantExpression implements Expression { return "NULL"; } if (value instanceof Boolean) { - return ((Boolean) value).booleanValue() ? "TRUE" : "FALSE"; + return (Boolean) value ? "TRUE" : "FALSE"; } if (value instanceof String) { return encodeString((String) value); @@ -138,17 +117,19 @@ public class ConstantExpression implements Expression { * it was provided in a selector. */ public static String encodeString(String s) { - StringBuffer b = new StringBuffer(); - b.append('\''); + + StringBuilder builder = new StringBuilder(); + + builder.append('\''); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (c == '\'') { - b.append(c); + builder.append(c); } - b.append(c); + builder.append(c); } - b.append('\''); - return b.toString(); + builder.append('\''); + return builder.toString(); } } diff --git a/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParser.java b/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParser.java index 198aacf83e..4ea7dcfd42 100644 --- a/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParser.java +++ b/filter/src/main/java/org/apache/rocketmq/filter/parser/SelectorParser.java @@ -20,6 +20,7 @@ package org.apache.rocketmq.filter.parser; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; +import org.apache.rocketmq.filter.expression.BooleanConstantExpression; import org.apache.rocketmq.filter.expression.BooleanExpression; import org.apache.rocketmq.filter.expression.ComparisonExpression; import org.apache.rocketmq.filter.expression.ConstantExpression; @@ -437,15 +438,15 @@ public class SelectorParser implements SelectorParserConstants { break; case TRUE: jj_consume_token(TRUE); - left = ConstantExpression.TRUE; + left = BooleanConstantExpression.TRUE; break; case FALSE: jj_consume_token(FALSE); - left = ConstantExpression.FALSE; + left = BooleanConstantExpression.FALSE; break; case NULL: jj_consume_token(NULL); - left = ConstantExpression.NULL; + left = BooleanConstantExpression.NULL; break; default: jjLa1[12] = jjGen; From e95c90bf2e10b3642f1f6adbdf2702cc6b013cc5 Mon Sep 17 00:00:00 2001 From: keranbingaa Date: Mon, 11 May 2020 09:57:09 +0800 Subject: [PATCH 28/49] add request-reply doc --- docs/cn/image/consumer_reply.png | Bin 0 -> 52036 bytes docs/cn/image/producer_send_request.png | Bin 0 -> 87577 bytes docs/cn/rpc_request.md | 116 ++++++++++++++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 docs/cn/image/consumer_reply.png create mode 100644 docs/cn/image/producer_send_request.png create mode 100644 docs/cn/rpc_request.md diff --git a/docs/cn/image/consumer_reply.png b/docs/cn/image/consumer_reply.png new file mode 100644 index 0000000000000000000000000000000000000000..ae00da665d73b709609a4957339de115526b6407 GIT binary patch literal 52036 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 literal 0 HcmV?d00001 diff --git a/docs/cn/image/producer_send_request.png b/docs/cn/image/producer_send_request.png new file mode 100644 index 0000000000000000000000000000000000000000..016fedac6fb3e70cf26dd2c14e118ed3b5470efc GIT binary patch literal 87577 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 WuJJPA+s$FYA6Y3y$sBPb@BabrydstW literal 0 HcmV?d00001 diff --git a/docs/cn/rpc_request.md b/docs/cn/rpc_request.md new file mode 100644 index 0000000000..1c4add50ee --- /dev/null +++ b/docs/cn/rpc_request.md @@ -0,0 +1,116 @@ +# “Request-Reply”特性 +--- + +##使用场景 +随着服务规模的扩大,单机服务无法满足性能和容量的要求,此时需要将服务拆分为更小粒度的服务或者部署多个服务实例构成集群来提供服务。在分布式场景下,RPC是最常用的联机调用的方式。 + +在构建分布式应用时,有些领域,例如金融服务领域,常常使用消息队列来构建服务总线,实现联机调用的目的。消息队列的主要场景是解耦、削峰填谷,在联机调用的场景下,需要将服务的调用抽象成基于消息的交互,并增强同步调用的这种交互逻辑。为了更好地支持消息队列在联机调用场景下的应用,rocketmq-4.7.0推出了“Request-Reply”特性来支持RPC调用。 + +##设计思路 +在rocketmq中,整个同步调用主要包括两个过程: + +(1)请求方生成消息,发送给响应方,并等待响应方回包; + +(2)响应方收到请求消息后,消费这条消息,并发出一条响应消息给请求方。 + +整个过程实质上是两个消息收发过程的组合。所以这里最关键的问题是如何将异步的消息收发过程构建成一个同步的过程。其中主要有两个问题需要解决: + +* 请求方如何同步等待回包 +这个问题的解决方案中,一个关键的数据结构是RequestResponseFuture。 + +``` +public class RequestResponseFuture { + private final String correlationId; + private final RequestCallback requestCallback; + private final long beginTimestamp = System.currentTimeMillis(); + private final Message requestMsg = null; + private long timeoutMillis; + private CountDownLatch countDownLatch = new CountDownLatch(1); + private volatile Message responseMsg = null; + private volatile boolean sendRequestOk = true; + private volatile Throwable cause = null; +} +``` +RequestResponseFuture中,利用correlationId来标识一个请求。如下图所示,Producer发送request时创建一个RequestResponseFuture,以correlationId为key,RequestResponseFuture为value存入map,同时请求中带上RequestResponseFuture中的correlationId,收到回包后根据correlationId拿到对应的RequestResponseFuture,并设置回包内容。 +![](image/producer_send_request.png) + +* consumer消费消息后,如何准确回包 +(1)producer在发送消息的时候,会给每条消息生成唯一的标识符,同时还带上了producer的clientId。当consumer收到并消费消息后,从消息中取出消息的标识符correlationId和producer的标识符clientId,放入响应消息,用来确定此响应消息是哪条请求消息的回包,以及此响应消息应该发给哪个producer。同时响应消息中设置了消息的类型以及响应消息的topic,然后consumer将消息发给broker,如下图所示。 +![](image/consumer_reply.png) +(2)broker收到响应消息后,需要将消息发回给指定的producer。Broker如何知道发回给哪个producer?因为消息中包含了producer的标识符clientId,在ProducerManager中,维护了标识符和channel信息的对应关系,通过这个对应关系,就能把回包发给对应的producer。 + +响应消息发送和一般的消息发送流程区别在于,响应消息不需要producer拉取,而是由broker直接推给producer。同时选择broker的策略也有变化:请求消息从哪个broker发过来,响应消息也发到对应的broker上。 + +Producer收到响应消息后,根据消息中的唯一标识符,从RequestResponseFuture的map中找到对应的RequestResponseFuture结构,设置响应消息,同时计数器减一,解除等待状态,使请求方收到响应消息。 + +##使用方法 + +同步调用的示例在example文件夹的rpc目录下。 + +###Producer +``` +Message msg = new Message(topic, + "", + "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET)); + + long begin = System.currentTimeMillis(); + Message retMsg = producer.request(msg, ttl); + long cost = System.currentTimeMillis() - begin; + System.out.printf("request to <%s> cost: %d replyMessage: %s %n", topic, cost, retMsg); +``` +调用接口替换为request即可。 + +###Consumer +需要启动一个producer,同时在覆写consumeMessage方法的时候,自定义响应消息并发送。 + +``` + @Override + public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { + System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs); + for (MessageExt msg : msgs) { + try { + System.out.printf("handle message: %s", msg.toString()); + String replyTo = MessageUtil.getReplyToClient(msg); + byte[] replyContent = "reply message contents.".getBytes(); + // create reply message with given util, do not create reply message by yourself + Message replyMessage = MessageUtil.createReplyMessage(msg, replyContent); + + // send reply message with producer + SendResult replyResult = replyProducer.send(replyMessage, 3000); + System.out.printf("reply to %s , %s %n", replyTo, replyResult.toString()); + } catch (MQClientException | RemotingException | MQBrokerException | InterruptedException e) { + e.printStackTrace(); + } + } + return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; + } +``` + +##接口参数 +* public Message request(Message msg,long timeout) +msg:待发送的消息 +timeout:同步调用超时时间 +* public void request(Message msg, final RequestCallback requestCallback, long timeout) +msg:待发送的消息 +requestCallback:回调函数 +timeout:同步调用超时时间 +* public Message request(final Message msg, final MessageQueueSelector selector, final Object arg,final long timeout) +msg:待发送的消息 +selector:消息队列选择器 +arg:消息队列选择器需要的参数 +timeout:同步调用超时时间 +* public void request(final Message msg, final MessageQueueSelector selector, final Object arg,final RequestCallback requestCallback, final long timeout) +msg:待发送的消息 +selector:消息队列选择器 +arg:消息队列选择器需要的参数 +requestCallback:回调函数 +timeout:同步调用超时时间 +* public Message request(final Message msg, final MessageQueue mq, final long timeout) +msg:待发送的消息 +mq:目标消息队列 +timeout:同步调用超时时间 +* public void request(final Message msg, final MessageQueue mq, final RequestCallback requestCallback, long timeout) +msg:待发送的消息 +mq:目标消息队列 +requestCallback:回调函数 +timeout:同步调用超时时间 From 408eead2bbc81a0bd2378a1e00fb0d10b94fd8cd Mon Sep 17 00:00:00 2001 From: keranbingaa Date: Mon, 11 May 2020 10:08:31 +0800 Subject: [PATCH 29/49] modify request-reply doc --- docs/cn/rpc_request.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/cn/rpc_request.md b/docs/cn/rpc_request.md index 1c4add50ee..6ac73ee997 100644 --- a/docs/cn/rpc_request.md +++ b/docs/cn/rpc_request.md @@ -1,12 +1,12 @@ # “Request-Reply”特性 --- -##使用场景 +## 1 使用场景 随着服务规模的扩大,单机服务无法满足性能和容量的要求,此时需要将服务拆分为更小粒度的服务或者部署多个服务实例构成集群来提供服务。在分布式场景下,RPC是最常用的联机调用的方式。 在构建分布式应用时,有些领域,例如金融服务领域,常常使用消息队列来构建服务总线,实现联机调用的目的。消息队列的主要场景是解耦、削峰填谷,在联机调用的场景下,需要将服务的调用抽象成基于消息的交互,并增强同步调用的这种交互逻辑。为了更好地支持消息队列在联机调用场景下的应用,rocketmq-4.7.0推出了“Request-Reply”特性来支持RPC调用。 -##设计思路 +## 2 设计思路 在rocketmq中,整个同步调用主要包括两个过程: (1)请求方生成消息,发送给响应方,并等待响应方回包; @@ -43,11 +43,11 @@ RequestResponseFuture中,利用correlationId来标识一个请求。如下图 Producer收到响应消息后,根据消息中的唯一标识符,从RequestResponseFuture的map中找到对应的RequestResponseFuture结构,设置响应消息,同时计数器减一,解除等待状态,使请求方收到响应消息。 -##使用方法 +## 3 使用方法 同步调用的示例在example文件夹的rpc目录下。 -###Producer +### 3.1 Producer ``` Message msg = new Message(topic, "", @@ -60,7 +60,7 @@ Message msg = new Message(topic, ``` 调用接口替换为request即可。 -###Consumer +### 3.2 Consumer 需要启动一个producer,同时在覆写consumeMessage方法的时候,自定义响应消息并发送。 ``` @@ -86,7 +86,7 @@ Message msg = new Message(topic, } ``` -##接口参数 +## 4 接口参数 * public Message request(Message msg,long timeout) msg:待发送的消息 timeout:同步调用超时时间 From ff1a6282b90cd97cbf2638b3a2bd45dea3c37261 Mon Sep 17 00:00:00 2001 From: keranbingaa Date: Mon, 11 May 2020 10:13:10 +0800 Subject: [PATCH 30/49] modify request-reply doc --- docs/cn/rpc_request.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/cn/rpc_request.md b/docs/cn/rpc_request.md index 6ac73ee997..b9d4331099 100644 --- a/docs/cn/rpc_request.md +++ b/docs/cn/rpc_request.md @@ -35,8 +35,10 @@ RequestResponseFuture中,利用correlationId来标识一个请求。如下图 ![](image/producer_send_request.png) * consumer消费消息后,如何准确回包 + (1)producer在发送消息的时候,会给每条消息生成唯一的标识符,同时还带上了producer的clientId。当consumer收到并消费消息后,从消息中取出消息的标识符correlationId和producer的标识符clientId,放入响应消息,用来确定此响应消息是哪条请求消息的回包,以及此响应消息应该发给哪个producer。同时响应消息中设置了消息的类型以及响应消息的topic,然后consumer将消息发给broker,如下图所示。 ![](image/consumer_reply.png) + (2)broker收到响应消息后,需要将消息发回给指定的producer。Broker如何知道发回给哪个producer?因为消息中包含了producer的标识符clientId,在ProducerManager中,维护了标识符和channel信息的对应关系,通过这个对应关系,就能把回包发给对应的producer。 响应消息发送和一般的消息发送流程区别在于,响应消息不需要producer拉取,而是由broker直接推给producer。同时选择broker的策略也有变化:请求消息从哪个broker发过来,响应消息也发到对应的broker上。 @@ -88,29 +90,50 @@ Message msg = new Message(topic, ## 4 接口参数 * public Message request(Message msg,long timeout) + msg:待发送的消息 + timeout:同步调用超时时间 * public void request(Message msg, final RequestCallback requestCallback, long timeout) + msg:待发送的消息 + requestCallback:回调函数 + timeout:同步调用超时时间 * public Message request(final Message msg, final MessageQueueSelector selector, final Object arg,final long timeout) + msg:待发送的消息 + selector:消息队列选择器 + arg:消息队列选择器需要的参数 + timeout:同步调用超时时间 * public void request(final Message msg, final MessageQueueSelector selector, final Object arg,final RequestCallback requestCallback, final long timeout) + msg:待发送的消息 + selector:消息队列选择器 + arg:消息队列选择器需要的参数 + requestCallback:回调函数 + timeout:同步调用超时时间 * public Message request(final Message msg, final MessageQueue mq, final long timeout) + msg:待发送的消息 + mq:目标消息队列 + timeout:同步调用超时时间 * public void request(final Message msg, final MessageQueue mq, final RequestCallback requestCallback, long timeout) + msg:待发送的消息 + mq:目标消息队列 + requestCallback:回调函数 + timeout:同步调用超时时间 From 6594c72944d104d2a123a83119fd44ca0a43d813 Mon Sep 17 00:00:00 2001 From: keranbingaa Date: Mon, 11 May 2020 10:20:13 +0800 Subject: [PATCH 31/49] modify request-reply doc --- docs/cn/rpc_request.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/cn/rpc_request.md b/docs/cn/rpc_request.md index b9d4331099..714a0ce2da 100644 --- a/docs/cn/rpc_request.md +++ b/docs/cn/rpc_request.md @@ -15,7 +15,8 @@ 整个过程实质上是两个消息收发过程的组合。所以这里最关键的问题是如何将异步的消息收发过程构建成一个同步的过程。其中主要有两个问题需要解决: -* 请求方如何同步等待回包 +### 2.1 请求方如何同步等待回包 + 这个问题的解决方案中,一个关键的数据结构是RequestResponseFuture。 ``` @@ -34,7 +35,7 @@ public class RequestResponseFuture { RequestResponseFuture中,利用correlationId来标识一个请求。如下图所示,Producer发送request时创建一个RequestResponseFuture,以correlationId为key,RequestResponseFuture为value存入map,同时请求中带上RequestResponseFuture中的correlationId,收到回包后根据correlationId拿到对应的RequestResponseFuture,并设置回包内容。 ![](image/producer_send_request.png) -* consumer消费消息后,如何准确回包 +###2.2 consumer消费消息后,如何准确回包 (1)producer在发送消息的时候,会给每条消息生成唯一的标识符,同时还带上了producer的clientId。当consumer收到并消费消息后,从消息中取出消息的标识符correlationId和producer的标识符clientId,放入响应消息,用来确定此响应消息是哪条请求消息的回包,以及此响应消息应该发给哪个producer。同时响应消息中设置了消息的类型以及响应消息的topic,然后consumer将消息发给broker,如下图所示。 ![](image/consumer_reply.png) @@ -89,19 +90,19 @@ Message msg = new Message(topic, ``` ## 4 接口参数 -* public Message request(Message msg,long timeout) +### 4.1 public Message request(Message msg,long timeout) msg:待发送的消息 timeout:同步调用超时时间 -* public void request(Message msg, final RequestCallback requestCallback, long timeout) +### 4.2 public void request(Message msg, final RequestCallback requestCallback, long timeout) msg:待发送的消息 requestCallback:回调函数 timeout:同步调用超时时间 -* public Message request(final Message msg, final MessageQueueSelector selector, final Object arg,final long timeout) +### 4.3 public Message request(final Message msg, final MessageQueueSelector selector, final Object arg,final long timeout) msg:待发送的消息 @@ -110,7 +111,7 @@ selector:消息队列选择器 arg:消息队列选择器需要的参数 timeout:同步调用超时时间 -* public void request(final Message msg, final MessageQueueSelector selector, final Object arg,final RequestCallback requestCallback, final long timeout) +### 4.4 public void request(final Message msg, final MessageQueueSelector selector, final Object arg,final RequestCallback requestCallback, final long timeout) msg:待发送的消息 @@ -121,14 +122,14 @@ arg:消息队列选择器需要的参数 requestCallback:回调函数 timeout:同步调用超时时间 -* public Message request(final Message msg, final MessageQueue mq, final long timeout) +### 4.5 public Message request(final Message msg, final MessageQueue mq, final long timeout) msg:待发送的消息 mq:目标消息队列 timeout:同步调用超时时间 -* public void request(final Message msg, final MessageQueue mq, final RequestCallback requestCallback, long timeout) +### 4.6 public void request(final Message msg, final MessageQueue mq, final RequestCallback requestCallback, long timeout) msg:待发送的消息 From d70a745ee0e165b5a0a973ea39608e45e97cfb1c Mon Sep 17 00:00:00 2001 From: keranbingaa Date: Mon, 11 May 2020 10:25:45 +0800 Subject: [PATCH 32/49] modify request-reply doc --- docs/cn/rpc_request.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cn/rpc_request.md b/docs/cn/rpc_request.md index 714a0ce2da..f915ecbaf5 100644 --- a/docs/cn/rpc_request.md +++ b/docs/cn/rpc_request.md @@ -35,7 +35,7 @@ public class RequestResponseFuture { RequestResponseFuture中,利用correlationId来标识一个请求。如下图所示,Producer发送request时创建一个RequestResponseFuture,以correlationId为key,RequestResponseFuture为value存入map,同时请求中带上RequestResponseFuture中的correlationId,收到回包后根据correlationId拿到对应的RequestResponseFuture,并设置回包内容。 ![](image/producer_send_request.png) -###2.2 consumer消费消息后,如何准确回包 +### 2.2 consumer消费消息后,如何准确回包 (1)producer在发送消息的时候,会给每条消息生成唯一的标识符,同时还带上了producer的clientId。当consumer收到并消费消息后,从消息中取出消息的标识符correlationId和producer的标识符clientId,放入响应消息,用来确定此响应消息是哪条请求消息的回包,以及此响应消息应该发给哪个producer。同时响应消息中设置了消息的类型以及响应消息的topic,然后consumer将消息发给broker,如下图所示。 ![](image/consumer_reply.png) From 16b38ba9b2fd0d7a0f0da4da05db013d1da86b8e Mon Sep 17 00:00:00 2001 From: huangli Date: Mon, 11 May 2020 18:33:48 +0800 Subject: [PATCH 33/49] fix Broker2Client logs --- .../broker/client/net/Broker2Client.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/client/net/Broker2Client.java b/broker/src/main/java/org/apache/rocketmq/broker/client/net/Broker2Client.java index 4c409f2254..4d28cd819f 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/client/net/Broker2Client.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/client/net/Broker2Client.java @@ -70,7 +70,8 @@ public class Broker2Client { try { this.brokerController.getRemotingServer().invokeOneway(channel, request, 10); } catch (Exception e) { - log.error("Check transaction failed because invoke producer exception. group={}, msgId={}", group, messageExt.getMsgId(), e.getMessage()); + log.error("Check transaction failed because invoke producer exception. group={}, msgId={}, error={}", + group, messageExt.getMsgId(), e.toString()); } } @@ -96,7 +97,7 @@ public class Broker2Client { try { this.brokerController.getRemotingServer().invokeOneway(channel, request, 10); } catch (Exception e) { - log.error("notifyConsumerIdsChanged exception, " + consumerGroup, e.getMessage()); + log.error("notifyConsumerIdsChanged exception. group={}, error={}", consumerGroup, e.toString()); } } @@ -185,14 +186,14 @@ public class Broker2Client { log.info("[reset-offset] reset offset success. topic={}, group={}, clientId={}", topic, group, entry.getValue().getClientId()); } catch (Exception e) { - log.error("[reset-offset] reset offset exception. topic={}, group={}", - new Object[] {topic, group}, e); + log.error("[reset-offset] reset offset exception. topic={}, group={} ,error={}", + topic, group, e.toString()); } } else { response.setCode(ResponseCode.SYSTEM_ERROR); response.setRemark("the client does not support this feature. version=" + MQVersion.getVersionDesc(version)); - log.warn("[reset-offset] the client does not support this feature. version={}", + log.warn("[reset-offset] the client does not support this feature. channel={}, version={}", RemotingHelper.parseChannelRemoteAddr(entry.getKey()), MQVersion.getVersionDesc(version)); return response; } @@ -253,7 +254,7 @@ public class Broker2Client { result.setCode(ResponseCode.SYSTEM_ERROR); result.setRemark("the client does not support this feature. version=" + MQVersion.getVersionDesc(version)); - log.warn("[get-consumer-status] the client does not support this feature. version={}", + log.warn("[get-consumer-status] the client does not support this feature. channel={}, version={}", RemotingHelper.parseChannelRemoteAddr(entry.getKey()), MQVersion.getVersionDesc(version)); return result; } else if (UtilAll.isBlank(originClientId) || originClientId.equals(clientId)) { @@ -279,8 +280,8 @@ public class Broker2Client { } } catch (Exception e) { log.error( - "[get-consumer-status] get consumer status exception. topic={}, group={}, offset={}", - new Object[] {topic, group}, e); + "[get-consumer-status] get consumer status exception. topic={}, group={}, error={}", + topic, group, e.toString()); } if (!UtilAll.isBlank(originClientId) && originClientId.equals(clientId)) { From b1550689c5d8647d9b6902269bb0466e19a7bb5a Mon Sep 17 00:00:00 2001 From: rushsky518 Date: Sun, 17 May 2020 14:53:25 +0800 Subject: [PATCH 34/49] remove unused code --- .../rocketmq/client/impl/factory/MQClientInstance.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/factory/MQClientInstance.java b/client/src/main/java/org/apache/rocketmq/client/impl/factory/MQClientInstance.java index bbd2eecb1c..886203fe7d 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/factory/MQClientInstance.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/factory/MQClientInstance.java @@ -17,7 +17,6 @@ package org.apache.rocketmq.client.impl.factory; import java.io.UnsupportedEncodingException; -import java.net.DatagramSocket; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -118,7 +117,6 @@ public class MQClientInstance { private final ConsumerStatsManager consumerStatsManager; private final AtomicLong sendHeartbeatTimesTotal = new AtomicLong(0); private ServiceState serviceState = ServiceState.CREATE_JUST; - private DatagramSocket datagramSocket; private Random random = new Random(); public MQClientInstance(ClientConfig clientConfig, int instanceIndex, String clientId) { @@ -854,10 +852,6 @@ public class MQClientInstance { this.mQClientAPIImpl.shutdown(); this.rebalanceService.shutdown(); - if (this.datagramSocket != null) { - this.datagramSocket.close(); - this.datagramSocket = null; - } MQClientManager.getInstance().removeClientFactory(this.clientId); log.info("the client factory [{}] shutdown OK", this.clientId); break; From 4d2dea18cb6ff22adcec437d9bdbb1b36afd32a1 Mon Sep 17 00:00:00 2001 From: keranbingaa Date: Mon, 18 May 2020 19:09:39 +0800 Subject: [PATCH 35/49] modify request-reply doc --- docs/cn/rpc_request.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/cn/rpc_request.md b/docs/cn/rpc_request.md index f915ecbaf5..a8fe802a15 100644 --- a/docs/cn/rpc_request.md +++ b/docs/cn/rpc_request.md @@ -90,19 +90,22 @@ Message msg = new Message(topic, ``` ## 4 接口参数 -### 4.1 public Message request(Message msg,long timeout) + +4.1 public Message request(Message msg,long timeout) msg:待发送的消息 timeout:同步调用超时时间 -### 4.2 public void request(Message msg, final RequestCallback requestCallback, long timeout) + +4.2 public void request(Message msg, final RequestCallback requestCallback, long timeout) msg:待发送的消息 requestCallback:回调函数 timeout:同步调用超时时间 -### 4.3 public Message request(final Message msg, final MessageQueueSelector selector, final Object arg,final long timeout) + +4.3 public Message request(final Message msg, final MessageQueueSelector selector, final Object arg,final long timeout) msg:待发送的消息 @@ -111,7 +114,8 @@ selector:消息队列选择器 arg:消息队列选择器需要的参数 timeout:同步调用超时时间 -### 4.4 public void request(final Message msg, final MessageQueueSelector selector, final Object arg,final RequestCallback requestCallback, final long timeout) + +4.4 public void request(final Message msg, final MessageQueueSelector selector, final Object arg,final RequestCallback requestCallback, final long timeout) msg:待发送的消息 @@ -122,14 +126,16 @@ arg:消息队列选择器需要的参数 requestCallback:回调函数 timeout:同步调用超时时间 -### 4.5 public Message request(final Message msg, final MessageQueue mq, final long timeout) + +4.5 public Message request(final Message msg, final MessageQueue mq, final long timeout) msg:待发送的消息 mq:目标消息队列 timeout:同步调用超时时间 -### 4.6 public void request(final Message msg, final MessageQueue mq, final RequestCallback requestCallback, long timeout) + +4.6 public void request(final Message msg, final MessageQueue mq, final RequestCallback requestCallback, long timeout) msg:待发送的消息 From a60d93890351df5f4e82c748e38ef200f55c644b Mon Sep 17 00:00:00 2001 From: chi-chi weng <949409306@qq.com> Date: Mon, 18 May 2020 19:46:45 +0800 Subject: [PATCH 36/49] Fastjson has a serious security problem in 1.2.62,which will cause RCE Fastjson has a serious security problem in 1.2.62,which will cause RCE Fix https://github.com/apache/rocketmq/issues/2007 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2cab4823d4..05f544c7d6 100644 --- a/pom.xml +++ b/pom.xml @@ -568,7 +568,7 @@ com.alibaba fastjson - 1.2.62 + 1.2.68 org.javassist From a14cf634a56abefdec3cef7531d2b855dff1c939 Mon Sep 17 00:00:00 2001 From: affe Date: Wed, 20 May 2020 10:57:48 -0700 Subject: [PATCH 37/49] fix increase hashSlot value --- .../main/java/org/apache/rocketmq/store/index/IndexFile.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/store/src/main/java/org/apache/rocketmq/store/index/IndexFile.java b/store/src/main/java/org/apache/rocketmq/store/index/IndexFile.java index 3d76b73dc2..e513edf111 100644 --- a/store/src/main/java/org/apache/rocketmq/store/index/IndexFile.java +++ b/store/src/main/java/org/apache/rocketmq/store/index/IndexFile.java @@ -134,7 +134,9 @@ public class IndexFile { this.indexHeader.setBeginTimestamp(storeTimestamp); } - this.indexHeader.incHashSlotCount(); + if (invalidIndex == slotValue) { + this.indexHeader.incHashSlotCount(); + } this.indexHeader.incIndexCount(); this.indexHeader.setEndPhyOffset(phyOffset); this.indexHeader.setEndTimestamp(storeTimestamp); From 42a87abb97cc1f77febf6bde98dede7018fa3acf Mon Sep 17 00:00:00 2001 From: affe Date: Wed, 20 May 2020 16:20:50 -0700 Subject: [PATCH 38/49] fix #2020, use wakeup() instead of writing duplicate code --- store/src/main/java/org/apache/rocketmq/store/CommitLog.java | 4 +--- .../src/main/java/org/apache/rocketmq/store/ha/HAService.java | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/store/src/main/java/org/apache/rocketmq/store/CommitLog.java b/store/src/main/java/org/apache/rocketmq/store/CommitLog.java index 82bd6706c9..9d9bbce69d 100644 --- a/store/src/main/java/org/apache/rocketmq/store/CommitLog.java +++ b/store/src/main/java/org/apache/rocketmq/store/CommitLog.java @@ -1407,9 +1407,7 @@ public class CommitLog { synchronized (this.requestsWrite) { this.requestsWrite.add(request); } - if (hasNotified.compareAndSet(false, true)) { - waitPoint.countDown(); // notify - } + this.wakeup(); } private void swapRequests() { diff --git a/store/src/main/java/org/apache/rocketmq/store/ha/HAService.java b/store/src/main/java/org/apache/rocketmq/store/ha/HAService.java index 3035c575cf..69f85269f4 100644 --- a/store/src/main/java/org/apache/rocketmq/store/ha/HAService.java +++ b/store/src/main/java/org/apache/rocketmq/store/ha/HAService.java @@ -260,9 +260,7 @@ public class HAService { synchronized (this.requestsWrite) { this.requestsWrite.add(request); } - if (hasNotified.compareAndSet(false, true)) { - waitPoint.countDown(); // notify - } + this.wakeup(); } public void notifyTransferSome() { From 23bf87bef6a9fde138353f9cca271b2813ba6b01 Mon Sep 17 00:00:00 2001 From: chicc999 <123695646@qq.com> Date: Thu, 21 May 2020 16:58:35 +0800 Subject: [PATCH 39/49] fix issue #705 (#706) --- .../org/apache/rocketmq/store/ha/WaitNotifyObject.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/store/src/main/java/org/apache/rocketmq/store/ha/WaitNotifyObject.java b/store/src/main/java/org/apache/rocketmq/store/ha/WaitNotifyObject.java index 3483c99217..75b7597fea 100644 --- a/store/src/main/java/org/apache/rocketmq/store/ha/WaitNotifyObject.java +++ b/store/src/main/java/org/apache/rocketmq/store/ha/WaitNotifyObject.java @@ -21,6 +21,7 @@ import org.apache.rocketmq.logging.InternalLogger; import org.apache.rocketmq.logging.InternalLoggerFactory; import java.util.HashMap; +import java.util.Map; public class WaitNotifyObject { private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); @@ -65,9 +66,9 @@ public class WaitNotifyObject { synchronized (this) { boolean needNotify = false; - for (Boolean value : this.waitingThreadTable.values()) { - needNotify = needNotify || !value; - value = true; + for (Map.Entry entry : this.waitingThreadTable.entrySet()) { + needNotify = needNotify || !entry.getValue(); + entry.setValue(true); } if (needNotify) { From 371fd951371ff751997fd0d0c6c713d8785a812e Mon Sep 17 00:00:00 2001 From: rushsky518 Date: Thu, 21 May 2020 18:58:43 +0800 Subject: [PATCH 40/49] clean code --- .../rocketmq/client/impl/MQClientAPIImpl.java | 111 ++++++++---------- 1 file changed, 52 insertions(+), 59 deletions(-) diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java index 643b287303..760b2cd29a 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java @@ -643,72 +643,65 @@ public class MQClientAPIImpl { final Message msg, final RemotingCommand response ) throws MQBrokerException, RemotingCommandException { + SendStatus sendStatus; switch (response.getCode()) { - case ResponseCode.FLUSH_DISK_TIMEOUT: - case ResponseCode.FLUSH_SLAVE_TIMEOUT: + case ResponseCode.FLUSH_DISK_TIMEOUT: { + sendStatus = SendStatus.FLUSH_DISK_TIMEOUT; + break; + } + case ResponseCode.FLUSH_SLAVE_TIMEOUT: { + sendStatus = SendStatus.FLUSH_SLAVE_TIMEOUT; + break; + } case ResponseCode.SLAVE_NOT_AVAILABLE: { + sendStatus = SendStatus.SLAVE_NOT_AVAILABLE; + break; } case ResponseCode.SUCCESS: { - SendStatus sendStatus = SendStatus.SEND_OK; - switch (response.getCode()) { - case ResponseCode.FLUSH_DISK_TIMEOUT: - sendStatus = SendStatus.FLUSH_DISK_TIMEOUT; - break; - case ResponseCode.FLUSH_SLAVE_TIMEOUT: - sendStatus = SendStatus.FLUSH_SLAVE_TIMEOUT; - break; - case ResponseCode.SLAVE_NOT_AVAILABLE: - sendStatus = SendStatus.SLAVE_NOT_AVAILABLE; - break; - case ResponseCode.SUCCESS: - sendStatus = SendStatus.SEND_OK; - break; - default: - assert false; - break; - } - - SendMessageResponseHeader responseHeader = - (SendMessageResponseHeader) response.decodeCommandCustomHeader(SendMessageResponseHeader.class); - - //If namespace not null , reset Topic without namespace. - String topic = msg.getTopic(); - if (StringUtils.isNotEmpty(this.clientConfig.getNamespace())) { - topic = NamespaceUtil.withoutNamespace(topic, this.clientConfig.getNamespace()); - } - - MessageQueue messageQueue = new MessageQueue(topic, brokerName, responseHeader.getQueueId()); - - String uniqMsgId = MessageClientIDSetter.getUniqID(msg); - if (msg instanceof MessageBatch) { - StringBuilder sb = new StringBuilder(); - for (Message message : (MessageBatch) msg) { - sb.append(sb.length() == 0 ? "" : ",").append(MessageClientIDSetter.getUniqID(message)); - } - uniqMsgId = sb.toString(); - } - SendResult sendResult = new SendResult(sendStatus, - uniqMsgId, - responseHeader.getMsgId(), messageQueue, responseHeader.getQueueOffset()); - sendResult.setTransactionId(responseHeader.getTransactionId()); - String regionId = response.getExtFields().get(MessageConst.PROPERTY_MSG_REGION); - String traceOn = response.getExtFields().get(MessageConst.PROPERTY_TRACE_SWITCH); - if (regionId == null || regionId.isEmpty()) { - regionId = MixAll.DEFAULT_TRACE_REGION_ID; - } - if (traceOn != null && traceOn.equals("false")) { - sendResult.setTraceOn(false); - } else { - sendResult.setTraceOn(true); - } - sendResult.setRegionId(regionId); - return sendResult; - } - default: + sendStatus = SendStatus.SEND_OK; break; + } + default: { + assert false; + throw new MQBrokerException(response.getCode(), response.getRemark()); + } } - throw new MQBrokerException(response.getCode(), response.getRemark()); + SendMessageResponseHeader responseHeader = + (SendMessageResponseHeader) response.decodeCommandCustomHeader(SendMessageResponseHeader.class); + + //If namespace not null , reset Topic without namespace. + String topic = msg.getTopic(); + if (StringUtils.isNotEmpty(this.clientConfig.getNamespace())) { + topic = NamespaceUtil.withoutNamespace(topic, this.clientConfig.getNamespace()); + } + + MessageQueue messageQueue = new MessageQueue(topic, brokerName, responseHeader.getQueueId()); + + String uniqMsgId = MessageClientIDSetter.getUniqID(msg); + if (msg instanceof MessageBatch) { + StringBuilder sb = new StringBuilder(); + for (Message message : (MessageBatch) msg) { + sb.append(sb.length() == 0 ? "" : ",").append(MessageClientIDSetter.getUniqID(message)); + } + uniqMsgId = sb.toString(); + } + SendResult sendResult = new SendResult(sendStatus, + uniqMsgId, + responseHeader.getMsgId(), messageQueue, responseHeader.getQueueOffset()); + sendResult.setTransactionId(responseHeader.getTransactionId()); + String regionId = response.getExtFields().get(MessageConst.PROPERTY_MSG_REGION); + String traceOn = response.getExtFields().get(MessageConst.PROPERTY_TRACE_SWITCH); + if (regionId == null || regionId.isEmpty()) { + regionId = MixAll.DEFAULT_TRACE_REGION_ID; + } + if (traceOn != null && traceOn.equals("false")) { + sendResult.setTraceOn(false); + } else { + sendResult.setTraceOn(true); + } + sendResult.setRegionId(regionId); + return sendResult; } public PullResult pullMessage( From 24ee6be79bda0e72f293c62a8b5f98acdc54a1ab Mon Sep 17 00:00:00 2001 From: rushsky518 Date: Thu, 21 May 2020 19:26:23 +0800 Subject: [PATCH 41/49] remove assert --- .../java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java b/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java index 760b2cd29a..073e3679be 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/MQClientAPIImpl.java @@ -662,7 +662,6 @@ public class MQClientAPIImpl { break; } default: { - assert false; throw new MQBrokerException(response.getCode(), response.getRemark()); } } From 5873c76dbd29c8fefd7cb3f1df455eaeaec7ce83 Mon Sep 17 00:00:00 2001 From: Hao Zhang <1006618650@qq.com> Date: Sat, 23 May 2020 18:04:41 +0800 Subject: [PATCH 42/49] [ISSUE #1689]add interfaces to remove unused statsItems in BrokerStatsManager. (#2029) --- .../processor/AdminBrokerProcessor.java | 7 +- .../apache/rocketmq/common/BrokerConfig.java | 10 ++ .../common/stats/MomentStatsItemSet.java | 20 +++ .../rocketmq/common/stats/StatsItemSet.java | 37 +++++ .../rocketmq/common/BrokerConfigTest.java | 2 + .../rocketmq/store/DefaultMessageStore.java | 4 + .../store/stats/BrokerStatsManager.java | 20 +++ .../java/stats/BrokerStatsManagerTest.java | 141 ++++++++++++++++++ 8 files changed, 240 insertions(+), 1 deletion(-) create mode 100644 store/src/test/java/stats/BrokerStatsManagerTest.java diff --git a/broker/src/main/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessor.java b/broker/src/main/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessor.java index fb7aeceda9..eb81183205 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessor.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/processor/AdminBrokerProcessor.java @@ -299,7 +299,9 @@ public class AdminBrokerProcessor extends AsyncNettyRequestProcessor implements this.brokerController.getTopicConfigManager().deleteTopicConfig(requestHeader.getTopic()); this.brokerController.getMessageStore() .cleanUnusedTopic(this.brokerController.getTopicConfigManager().getTopicConfigTable().keySet()); - + if (this.brokerController.getBrokerConfig().isAutoDeleteUnusedStats()) { + this.brokerController.getBrokerStatsManager().onTopicDeleted(requestHeader.getTopic()); + } response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; @@ -715,6 +717,9 @@ public class AdminBrokerProcessor extends AsyncNettyRequestProcessor implements this.brokerController.getSubscriptionGroupManager().deleteSubscriptionGroupConfig(requestHeader.getGroupName()); + if (this.brokerController.getBrokerConfig().isAutoDeleteUnusedStats()) { + this.brokerController.getBrokerStatsManager().onGroupDeleted(requestHeader.getGroupName()); + } response.setCode(ResponseCode.SUCCESS); response.setRemark(null); return response; diff --git a/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java b/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java index a7568f0a20..bfe8a2104d 100644 --- a/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java +++ b/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java @@ -184,6 +184,8 @@ public class BrokerConfig { private boolean storeReplyMessageEnable = true; + private boolean autoDeleteUnusedStats = false; + public static String localHostName() { try { return InetAddress.getLocalHost().getHostName(); @@ -793,4 +795,12 @@ public class BrokerConfig { public void setStoreReplyMessageEnable(boolean storeReplyMessageEnable) { this.storeReplyMessageEnable = storeReplyMessageEnable; } + + public boolean isAutoDeleteUnusedStats() { + return autoDeleteUnusedStats; + } + + public void setAutoDeleteUnusedStats(boolean autoDeleteUnusedStats) { + this.autoDeleteUnusedStats = autoDeleteUnusedStats; + } } diff --git a/common/src/main/java/org/apache/rocketmq/common/stats/MomentStatsItemSet.java b/common/src/main/java/org/apache/rocketmq/common/stats/MomentStatsItemSet.java index 7ff26cd3b8..4d2ce0cfcd 100644 --- a/common/src/main/java/org/apache/rocketmq/common/stats/MomentStatsItemSet.java +++ b/common/src/main/java/org/apache/rocketmq/common/stats/MomentStatsItemSet.java @@ -74,6 +74,26 @@ public class MomentStatsItemSet { statsItem.getValue().set(value); } + public void delValueByInfixKey(final String statsKey, String separator) { + Iterator> it = this.statsItemTable.entrySet().iterator(); + while (it.hasNext()) { + Entry next = it.next(); + if (next.getKey().contains(separator + statsKey + separator)) { + it.remove(); + } + } + } + + public void delValueBySuffixKey(final String statsKey, String separator) { + Iterator> it = this.statsItemTable.entrySet().iterator(); + while (it.hasNext()) { + Entry next = it.next(); + if (next.getKey().endsWith(separator + statsKey)) { + it.remove(); + } + } + } + public MomentStatsItem getAndCreateStatsItem(final String statsKey) { MomentStatsItem statsItem = this.statsItemTable.get(statsKey); if (null == statsItem) { diff --git a/common/src/main/java/org/apache/rocketmq/common/stats/StatsItemSet.java b/common/src/main/java/org/apache/rocketmq/common/stats/StatsItemSet.java index 1c2de330bc..bcf9665959 100644 --- a/common/src/main/java/org/apache/rocketmq/common/stats/StatsItemSet.java +++ b/common/src/main/java/org/apache/rocketmq/common/stats/StatsItemSet.java @@ -158,6 +158,43 @@ public class StatsItemSet { statsItem.getTimes().addAndGet(incTimes); } + public void delValue(final String statsKey) { + StatsItem statsItem = this.statsItemTable.get(statsKey); + if (null != statsItem) { + this.statsItemTable.remove(statsKey); + } + } + + public void delValueByPrefixKey(final String statsKey, String separator) { + Iterator> it = this.statsItemTable.entrySet().iterator(); + while (it.hasNext()) { + Entry next = it.next(); + if (next.getKey().startsWith(statsKey + separator)) { + it.remove(); + } + } + } + + public void delValueByInfixKey(final String statsKey, String separator) { + Iterator> it = this.statsItemTable.entrySet().iterator(); + while (it.hasNext()) { + Entry next = it.next(); + if (next.getKey().contains(separator + statsKey + separator)) { + it.remove(); + } + } + } + + public void delValueBySuffixKey(final String statsKey, String separator) { + Iterator> it = this.statsItemTable.entrySet().iterator(); + while (it.hasNext()) { + Entry next = it.next(); + if (next.getKey().endsWith(separator + statsKey)) { + it.remove(); + } + } + } + public StatsItem getAndCreateStatsItem(final String statsKey) { StatsItem statsItem = this.statsItemTable.get(statsKey); if (null == statsItem) { diff --git a/common/src/test/java/org/apache/rocketmq/common/BrokerConfigTest.java b/common/src/test/java/org/apache/rocketmq/common/BrokerConfigTest.java index aa8bcfa725..07e132ff81 100644 --- a/common/src/test/java/org/apache/rocketmq/common/BrokerConfigTest.java +++ b/common/src/test/java/org/apache/rocketmq/common/BrokerConfigTest.java @@ -37,11 +37,13 @@ public class BrokerConfigTest { brokerConfig.setBrokerId(0); brokerConfig.setBrokerClusterName("DefaultCluster"); brokerConfig.setMsgTraceTopicName("RMQ_SYS_TRACE_TOPIC4"); + brokerConfig.setAutoDeleteUnusedStats(true); assertThat(brokerConfig.getBrokerClusterName()).isEqualTo("DefaultCluster"); assertThat(brokerConfig.getNamesrvAddr()).isEqualTo("127.0.0.1:9876"); assertThat(brokerConfig.getMsgTraceTopicName()).isEqualTo("RMQ_SYS_TRACE_TOPIC4"); assertThat(brokerConfig.getBrokerId()).isEqualTo(0); assertThat(brokerConfig.getBrokerName()).isEqualTo("broker-a"); assertThat(brokerConfig.isAutoCreateTopicEnable()).isEqualTo(false); + assertThat(brokerConfig.isAutoDeleteUnusedStats()).isEqualTo(true); } } \ No newline at end of file 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 f59b5eb98b..14d24d66f3 100644 --- a/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java +++ b/store/src/main/java/org/apache/rocketmq/store/DefaultMessageStore.java @@ -1029,6 +1029,10 @@ public class DefaultMessageStore implements MessageStore { } it.remove(); + if (this.brokerConfig.isAutoDeleteUnusedStats()) { + this.brokerStatsManager.onTopicDeleted(topic); + } + log.info("cleanUnusedTopic: {},topic destroyed", topic); } } diff --git a/store/src/main/java/org/apache/rocketmq/store/stats/BrokerStatsManager.java b/store/src/main/java/org/apache/rocketmq/store/stats/BrokerStatsManager.java index 4adbed76ab..e151844706 100644 --- a/store/src/main/java/org/apache/rocketmq/store/stats/BrokerStatsManager.java +++ b/store/src/main/java/org/apache/rocketmq/store/stats/BrokerStatsManager.java @@ -121,6 +121,26 @@ public class BrokerStatsManager { return null; } + public void onTopicDeleted(final String topic) { + this.statsTable.get(TOPIC_PUT_NUMS).delValue(topic); + this.statsTable.get(TOPIC_PUT_SIZE).delValue(topic); + this.statsTable.get(GROUP_GET_NUMS).delValueByPrefixKey(topic, "@"); + this.statsTable.get(GROUP_GET_SIZE).delValueByPrefixKey(topic, "@"); + this.statsTable.get(SNDBCK_PUT_NUMS).delValueByPrefixKey(topic, "@"); + this.statsTable.get(GROUP_GET_LATENCY).delValueByInfixKey(topic, "@"); + this.momentStatsItemSetFallSize.delValueByInfixKey(topic, "@"); + this.momentStatsItemSetFallTime.delValueByInfixKey(topic, "@"); + } + + public void onGroupDeleted(final String group) { + this.statsTable.get(GROUP_GET_NUMS).delValueBySuffixKey(group, "@"); + this.statsTable.get(GROUP_GET_SIZE).delValueBySuffixKey(group, "@"); + this.statsTable.get(SNDBCK_PUT_NUMS).delValueBySuffixKey(group, "@"); + this.statsTable.get(GROUP_GET_LATENCY).delValueBySuffixKey(group, "@"); + this.momentStatsItemSetFallSize.delValueBySuffixKey(group, "@"); + this.momentStatsItemSetFallTime.delValueBySuffixKey(group, "@"); + } + public void incTopicPutNums(final String topic) { this.statsTable.get(TOPIC_PUT_NUMS).addValue(topic, 1, 1); } diff --git a/store/src/test/java/stats/BrokerStatsManagerTest.java b/store/src/test/java/stats/BrokerStatsManagerTest.java new file mode 100644 index 0000000000..17020729a6 --- /dev/null +++ b/store/src/test/java/stats/BrokerStatsManagerTest.java @@ -0,0 +1,141 @@ +/* + * 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 stats; + +import org.apache.rocketmq.store.stats.BrokerStatsManager; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import static org.apache.rocketmq.store.stats.BrokerStatsManager.BROKER_PUT_NUMS; +import static org.apache.rocketmq.store.stats.BrokerStatsManager.GROUP_GET_FALL_SIZE; +import static org.apache.rocketmq.store.stats.BrokerStatsManager.GROUP_GET_FALL_TIME; +import static org.apache.rocketmq.store.stats.BrokerStatsManager.GROUP_GET_LATENCY; +import static org.apache.rocketmq.store.stats.BrokerStatsManager.GROUP_GET_NUMS; +import static org.apache.rocketmq.store.stats.BrokerStatsManager.GROUP_GET_SIZE; +import static org.apache.rocketmq.store.stats.BrokerStatsManager.SNDBCK_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; + +public class BrokerStatsManagerTest { + private BrokerStatsManager brokerStatsManager; + + private String TOPIC = "TOPIC_TEST"; + private String GROUP_NAME = "GROUP_TEST"; + + @Before + public void init() { + brokerStatsManager = new BrokerStatsManager("DefaultCluster"); + brokerStatsManager.start(); + } + + @After + public void destory() { + brokerStatsManager.shutdown(); + } + + @Test + public void testGetStatsItem() { + assertThat(brokerStatsManager.getStatsItem("TEST", "TEST")).isNull(); + } + + @Test + public void testIncTopicPutNums() { + brokerStatsManager.incTopicPutNums(TOPIC); + assertThat(brokerStatsManager.getStatsItem(TOPIC_PUT_NUMS, TOPIC).getTimes().doubleValue()).isEqualTo(1L); + brokerStatsManager.incTopicPutNums(TOPIC, 2, 2); + assertThat(brokerStatsManager.getStatsItem(TOPIC_PUT_NUMS, TOPIC).getValue().doubleValue()).isEqualTo(3L); + } + + @Test + public void testIncTopicPutSize() { + brokerStatsManager.incTopicPutSize(TOPIC, 2); + assertThat(brokerStatsManager.getStatsItem(TOPIC_PUT_SIZE, TOPIC).getValue().doubleValue()).isEqualTo(2L); + } + + @Test + public void testIncGroupGetNums() { + brokerStatsManager.incGroupGetNums(GROUP_NAME, TOPIC, 1); + String statsKey = brokerStatsManager.buildStatsKey(TOPIC, GROUP_NAME); + assertThat(brokerStatsManager.getStatsItem(GROUP_GET_NUMS, statsKey).getValue().doubleValue()).isEqualTo(1L); + } + + @Test + public void testIncGroupGetSize() { + brokerStatsManager.incGroupGetSize(GROUP_NAME, TOPIC, 1); + String statsKey = brokerStatsManager.buildStatsKey(TOPIC, GROUP_NAME); + assertThat(brokerStatsManager.getStatsItem(GROUP_GET_SIZE, statsKey).getValue().doubleValue()).isEqualTo(1L); + } + + @Test + public void testIncGroupGetLatency() { + brokerStatsManager.incGroupGetLatency(GROUP_NAME, TOPIC, 1, 1); + String statsKey = String.format("%d@%s@%s", 1, TOPIC, GROUP_NAME); + assertThat(brokerStatsManager.getStatsItem(GROUP_GET_LATENCY, statsKey).getValue().doubleValue()).isEqualTo(1L); + } + + @Test + public void testIncBrokerPutNums() { + brokerStatsManager.incBrokerPutNums(); + assertThat(brokerStatsManager.getStatsItem(BROKER_PUT_NUMS, "DefaultCluster").getValue().doubleValue()).isEqualTo(1L); + } + + @Test + public void testOnTopicDeleted() { + brokerStatsManager.incTopicPutNums(TOPIC); + brokerStatsManager.incTopicPutSize(TOPIC, 100); + brokerStatsManager.incGroupGetNums(GROUP_NAME, TOPIC, 1); + brokerStatsManager.incGroupGetSize(GROUP_NAME, TOPIC, 100); + brokerStatsManager.incSendBackNums(GROUP_NAME, TOPIC); + brokerStatsManager.incGroupGetLatency(GROUP_NAME, TOPIC, 1, 1); + brokerStatsManager.recordDiskFallBehindTime(GROUP_NAME, TOPIC, 1, 11L); + brokerStatsManager.recordDiskFallBehindSize(GROUP_NAME, TOPIC, 1, 11L); + + brokerStatsManager.onTopicDeleted(TOPIC); + + Assert.assertNull(brokerStatsManager.getStatsItem(TOPIC_PUT_NUMS, TOPIC)); + Assert.assertNull(brokerStatsManager.getStatsItem(TOPIC_PUT_SIZE, TOPIC)); + Assert.assertNull(brokerStatsManager.getStatsItem(GROUP_GET_SIZE, TOPIC + "@" + GROUP_NAME)); + Assert.assertNull(brokerStatsManager.getStatsItem(GROUP_GET_NUMS, TOPIC + "@" + GROUP_NAME)); + Assert.assertNull(brokerStatsManager.getStatsItem(SNDBCK_PUT_NUMS, TOPIC + "@" + GROUP_NAME)); + Assert.assertNull(brokerStatsManager.getStatsItem(GROUP_GET_LATENCY, "1@" + TOPIC + "@" + GROUP_NAME)); + Assert.assertNull(brokerStatsManager.getStatsItem(GROUP_GET_FALL_SIZE, "1@" + TOPIC + "@" + GROUP_NAME)); + Assert.assertNull(brokerStatsManager.getStatsItem(GROUP_GET_FALL_TIME, "1@" + TOPIC + "@" + GROUP_NAME)); + } + + @Test + public void testOnGroupDeleted(){ + brokerStatsManager.incGroupGetNums(GROUP_NAME, TOPIC, 1); + brokerStatsManager.incGroupGetSize(GROUP_NAME, TOPIC, 100); + brokerStatsManager.incSendBackNums(GROUP_NAME, TOPIC); + brokerStatsManager.incGroupGetLatency(GROUP_NAME, TOPIC, 1, 1); + brokerStatsManager.recordDiskFallBehindTime(GROUP_NAME, TOPIC, 1, 11L); + brokerStatsManager.recordDiskFallBehindSize(GROUP_NAME, TOPIC, 1, 11L); + + brokerStatsManager.onGroupDeleted(GROUP_NAME); + + Assert.assertNull(brokerStatsManager.getStatsItem(GROUP_GET_SIZE, TOPIC + "@" + GROUP_NAME)); + Assert.assertNull(brokerStatsManager.getStatsItem(GROUP_GET_NUMS, TOPIC + "@" + GROUP_NAME)); + Assert.assertNull(brokerStatsManager.getStatsItem(SNDBCK_PUT_NUMS, TOPIC + "@" + GROUP_NAME)); + Assert.assertNull(brokerStatsManager.getStatsItem(GROUP_GET_LATENCY, "1@" + TOPIC + "@" + GROUP_NAME)); + Assert.assertNull(brokerStatsManager.getStatsItem(GROUP_GET_FALL_SIZE, "1@" + TOPIC + "@" + GROUP_NAME)); + Assert.assertNull(brokerStatsManager.getStatsItem(GROUP_GET_FALL_TIME, "1@" + TOPIC + "@" + GROUP_NAME)); + } +} From fae6825f880ce592fc087cd86e8812482d68818a Mon Sep 17 00:00:00 2001 From: lebron374 Date: Mon, 25 May 2020 23:26:39 +0800 Subject: [PATCH 43/49] comment fix --- .../apache/rocketmq/broker/filter/ConsumerFilterManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/filter/ConsumerFilterManager.java b/broker/src/main/java/org/apache/rocketmq/broker/filter/ConsumerFilterManager.java index e9c5286bb1..5ccabb9f6d 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/filter/ConsumerFilterManager.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/filter/ConsumerFilterManager.java @@ -47,7 +47,7 @@ public class ConsumerFilterManager extends ConfigManager { private static final long MS_24_HOUR = 24 * 3600 * 1000; private ConcurrentMap - filterDataByTopic = new ConcurrentHashMap(256); + filterDataByTopic = new ConcurrentHashMap(256); private transient BrokerController brokerController; private transient BloomFilter bloomFilter; From 4feb8cae0cf3d4432e7a3beb4b48377f1b33e2ae Mon Sep 17 00:00:00 2001 From: lebron374 Date: Mon, 25 May 2020 23:37:50 +0800 Subject: [PATCH 44/49] npe fix --- .../apache/rocketmq/common/message/MessageExt.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/common/src/main/java/org/apache/rocketmq/common/message/MessageExt.java b/common/src/main/java/org/apache/rocketmq/common/message/MessageExt.java index 7f35172534..577c4f4bc1 100644 --- a/common/src/main/java/org/apache/rocketmq/common/message/MessageExt.java +++ b/common/src/main/java/org/apache/rocketmq/common/message/MessageExt.java @@ -142,18 +142,20 @@ public class MessageExt extends Message { } public String getBornHostString() { - if (this.bornHost != null) { - InetSocketAddress inetSocketAddress = (InetSocketAddress) this.bornHost; - return inetSocketAddress.getAddress().getHostAddress(); + if (null != this.bornHost) { + InetAddress inetAddress = ((InetSocketAddress) this.bornHost).getAddress(); + + return null != inetAddress ? inetAddress.getHostAddress() : null; } return null; } public String getBornHostNameString() { - if (this.bornHost != null) { - InetSocketAddress inetSocketAddress = (InetSocketAddress) this.bornHost; - return inetSocketAddress.getAddress().getHostName(); + if (null != this.bornHost) { + InetAddress inetAddress = ((InetSocketAddress) this.bornHost).getAddress(); + + return null != inetAddress ? inetAddress.getHostName() : null; } return null; From 3245bd3cc604ca7ad30b29eb8469102320478a2d Mon Sep 17 00:00:00 2001 From: wqliang Date: Mon, 25 May 2020 23:59:33 +0800 Subject: [PATCH 45/49] select a new namesrv when namesrvAddrChoosed not in new addr list --- .../apache/rocketmq/remoting/netty/NettyRemotingClient.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java index 415d4956b2..d836c8e0bf 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java @@ -354,6 +354,10 @@ public class NettyRemotingClient extends NettyRemotingAbstract implements Remoti Collections.shuffle(addrs); log.info("name server address updated. NEW : {} , OLD: {}", addrs, old); this.namesrvAddrList.set(addrs); + + if (!addrs.contains(this.namesrvAddrChoosed.get())) { + this.namesrvAddrChoosed.set(null); + } } } } From cf20a26c98cd8453caef395e1458ac2da391580a Mon Sep 17 00:00:00 2001 From: H <35485029+htf1129@users.noreply.github.com> Date: Tue, 26 May 2020 11:04:57 +0800 Subject: [PATCH 46/49] Fix typo in README Co-authored-by: haotianfei --- 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 9559a6049b..0cf977ed7d 100644 --- a/docs/cn/RocketMQ_Example.md +++ b/docs/cn/RocketMQ_Example.md @@ -706,7 +706,7 @@ public class TransactionProducer { ``` #### 2、实现事务的监听接口 -当发送半消息成功时,我们使用 `executeLocalTransaction` 方法来执行本地事务。它返回前一节中提到的三个事务状态之一。`checkLocalTranscation` 方法用于检查本地事务状态,并回应消息队列的检查请求。它也是返回前一节中提到的三个事务状态之一。 +当发送半消息成功时,我们使用 `executeLocalTransaction` 方法来执行本地事务。它返回前一节中提到的三个事务状态之一。`checkLocalTransaction` 方法用于检查本地事务状态,并回应消息队列的检查请求。它也是返回前一节中提到的三个事务状态之一。 ```java public class TransactionListenerImpl implements TransactionListener { From f12cc81e80ab33c2cce0fbd10e106048659419f4 Mon Sep 17 00:00:00 2001 From: lizhiboo Date: Tue, 26 May 2020 11:11:49 +0800 Subject: [PATCH 47/49] Init english version of the README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 李致波 --- docs/en/README.md | 49 ++ docs/en/RocketMQ_Example.md | 15 + docs/en/dledger/deploy_guide.md | 78 ++ docs/en/dledger/quick_start.md | 63 ++ docs/en/msg_trace/user_guide.md | 105 +++ docs/en/operation.md | 1374 +++++++++++++++++++++++++++++++ 6 files changed, 1684 insertions(+) create mode 100644 docs/en/README.md create mode 100644 docs/en/RocketMQ_Example.md create mode 100644 docs/en/dledger/deploy_guide.md create mode 100644 docs/en/dledger/quick_start.md create mode 100644 docs/en/msg_trace/user_guide.md create mode 100644 docs/en/operation.md diff --git a/docs/en/README.md b/docs/en/README.md new file mode 100644 index 0000000000..b24d2403fd --- /dev/null +++ b/docs/en/README.md @@ -0,0 +1,49 @@ +Apache RocketMQ Developer Guide +-------- + +##### This guide helps develpers understand and use Apache RocketMQ quickly. + +### 1. Concepts & Features + +- [Concept](Concept.md):introduce basic concepts in RocketMQ. + +- [Feature](Feature.md):introduce functional features of RocketMQ's implementations. + + +### 2. Architecture Design + +- [Architecture](architecture.md):introduce RocketMQ's deployment and technical architecture. + +- [Design](design.md):introduce design concept of RocketMQ's key mechanisms, including message storage, communication mechanisms, message filter, loadbalance, transaction message, etc. + + +### 3. Example + +- [Example](RocketMQ_Example.md) :introduce RocketMQ's common usage, including basic example, sequence message example, delay message example, batch message example, filter message example, transaction message example, etc. + + +### 4. Best Practice +- [Best Practice](best_practice.md):introduce RocketMQ's best practice, including producer, consumer, broker, NameServer, configuration of client, and the best parameter configuration of JVM, linux. + +- [Message Trace](msg_trace/user_guide.md):introduce how to use RocketMQ's message tracing feature. + +- [Auth Management](acl/Operations_ACL.md):introduce how to deployment quickly and how to use RocketMQ cluster enabling auth management feature. + +- [Quick Start](dledger/quick_start.md):introduce how to deploy Dledger quickly. + +- [Cluster Deployment](dledger/deploy_guide.md):introduce how to deploy Dledger in cluster. + +### 5. Operation and maintenance management +- [Operation](operation.md):introduce RocketMQ's deployment modes that including single-master mode, multi-master mode, multi-master multi-slave mode and so on, as well as the usage of operation tool mqadmin. + + +### 6. API Reference(TODO) + +- [DefaultMQProducer API Reference](client/java/API_Reference_DefaultMQProducer.md) + + + + + + + diff --git a/docs/en/RocketMQ_Example.md b/docs/en/RocketMQ_Example.md new file mode 100644 index 0000000000..48011aff9a --- /dev/null +++ b/docs/en/RocketMQ_Example.md @@ -0,0 +1,15 @@ +### Examples List + +- [basic example](Example_Simple.md) + +- [sequence message example](Example_Orderly.md) + +- [delay message example](Example_Delay.md) + +- [batch message example](Example_Batch.md) + +- [filter message example](Example_Filter.md) + +- [transaction message example](Example_Transaction.md) + +- [openmessaging example](Example_OpenMessaging.md) diff --git a/docs/en/dledger/deploy_guide.md b/docs/en/dledger/deploy_guide.md new file mode 100644 index 0000000000..55509498dd --- /dev/null +++ b/docs/en/dledger/deploy_guide.md @@ -0,0 +1,78 @@ +# Dledger cluster deployment +--- +## preface +This document introduces how to deploy auto failover RocketMQ-on-DLedger Group。 + +RocketMQ-on-DLedger Group is a broker group with **same name**, needs at least 3 nodes, elect a Leader by Raft algorithm automatically, the others as Follower, replicating data between Leader and Follower for system high available. +RocketMQ-on-DLedger Group can failover automatically, and maintains consistent. +RocketMQ-on-DLedger Group can scale up horizontal, that is, can deploy any RocketMQ-on-DLedger Groups providing services external. + +## 1. New cluster deployment + +#### 1.1 Write the configuration +each RocketMQ-on-DLedger Group needs at least 3 machines.(assuming 3 in this document) +write 3 configuration files, advising refer to the directory of conf/dledger 's example configuration file. +key configuration items: + +| name | meaning | example | +| --- | --- | --- | +| enableDLegerCommitLog | whether enable DLedger  | true | +| dLegerGroup | DLedger Raft Group's name, advising maintain consistent to brokerName | RaftNode00 | +| dLegerPeers | DLedger Group's nodes port infos, each node's configuration stay consistent in the same group. | n0-127.0.0.1:40911;n1-127.0.0.1:40912;n2-127.0.0.1:40913 | +| dLegerSelfId | node id, must belongs to dLegerPeers; each node is unique in the same group. | n0 | +| sendMessageThreadPoolNums | the count of sending thread, advising set equal to the cpu cores. | 16 | + +the following presents an example configuration conf/dledger/broker-n0.conf. + +``` +brokerClusterName = RaftCluster +brokerName=RaftNode00 +listenPort=30911 +namesrvAddr=127.0.0.1:9876 +storePathRootDir=/tmp/rmqstore/node00 +storePathCommitLog=/tmp/rmqstore/node00/commitlog +enableDLegerCommitLog=true +dLegerGroup=RaftNode00 +dLegerPeers=n0-127.0.0.1:40911;n1-127.0.0.1:40912;n2-127.0.0.1:40913 +## must be unique +dLegerSelfId=n0 +sendMessageThreadPoolNums=16 +``` + +### 1.2 Start Broker + +Startup stays consistent with the old version. + +`nohup sh bin/mqbroker -c conf/dledger/xxx-n0.conf & ` +`nohup sh bin/mqbroker -c conf/dledger/xxx-n1.conf & ` +`nohup sh bin/mqbroker -c conf/dledger/xxx-n2.conf & ` + + +## 2. Upgrade old cluster + +If old cluster deployed in Master mode, then each Master needs to be transformed into a RocketMQ-on-DLedger Group. +If old cluster deployed in Master-Slave mode, then each Master-Slave group needs to be transformed into a RocketMQ-on-DLedger Group. + +### 2.1 Kill old Broker + +execute kill command, or call `bin/mqshutdown broker`. + +### 2.2 Check old Commitlog + +Each node in RocketMQ-on-DLedger group is compatible with old Commitlog, but Raft replicating process works on the adding message only. So, to avoid occurring exceptions, old Commitlog must be consistent. +If old cluster deployed in Master-Slave mode, it maybe inconsistent after shutdown. Advising use md5sum to check at least 2 recently Commitlog file, if occur inconsistent, maintain consistent by copy. + +Although RocketMQ-on-DLedger Group can deployed with 2 nodes, it lacks failover ability(at least 3 nodes can tolerate one node fail). +Make sure that both Master and Slave's Commitlog is consistent, then prepare 3 machines, copy old Commitlog from Master to this 3 machines(BTW, copy the config directory). + +Then, go ahead to set configurations. + +### 2.3 Modify configuration + +Refer to New cluster deployment. + +### 2.4 Restart Broker + +Refer to New cluster deployment. + + diff --git a/docs/en/dledger/quick_start.md b/docs/en/dledger/quick_start.md new file mode 100644 index 0000000000..f947e166d5 --- /dev/null +++ b/docs/en/dledger/quick_start.md @@ -0,0 +1,63 @@ +# Dledger Quick Deployment +--- +### preface +This document is mainly introduced for how to build and deploy auto failover RocketMQ cluster based on DLedger. + +For detailed new cluster deployment and old cluster upgrade document, please refer to [Deployment Guide](deploy_guide.md)。 + +### 1. Build from source code +Build phase contains two parts, first, build DLedger, then build RocketMQ. + +#### 1.1 Build DLedger + +`git clone https://github.com/openmessaging/openmessaging-storage-dledger.git` + +`cd openmessaging-storage-dledger` + +`mvn clean install -DskipTests` + +#### 1.2 Build RocketMQ + +`git clone https://github.com/apache/rocketmq.git` + +`cd rocketmq` + +`git checkout -b store_with_dledger origin/store_with_dledger` + +`mvn -Prelease-all -DskipTests clean install -U` + +### 2. Quick Deployment + +after build successful + +`cd distribution/target/apache-rocketmq` + +`sh bin/dledger/fast-try.sh start` + +if the above commands executed successfully, then check cluster status by using mqadmin operation commands. + +`sh bin/mqadmin clusterList -n 127.0.0.1:9876` + +If everything goes well, the following content will appear: + +![ClusterList](https://img.alicdn.com/5476e8b07b923/TB11Z.ZyCzqK1RjSZFLXXcn2XXa) + +(BID is 0 indicate Master,the others are Follower) + +After startup successful, producer can produce message, and then test failover scenario. + +Stop cluster fastly, execute the following command: + +`sh bin/dledger/fast-try.sh stop` + +Quick deployment, default configuration is in directory conf/dledger,default storage path is /tmp/rmqstore. + + +### 3. Failover + +After successful deployment, kill Leader process(as the above example, kill process that binds port 30931), about 10 seconds elapses, use clusterList command check cluster's status, Leader switch to another node. + + + + + diff --git a/docs/en/msg_trace/user_guide.md b/docs/en/msg_trace/user_guide.md new file mode 100644 index 0000000000..eb540d02be --- /dev/null +++ b/docs/en/msg_trace/user_guide.md @@ -0,0 +1,105 @@ +# message trace +---- + +## 1. Message trace data's key properties +| Producer End| Consumer End| Broker End| +| --- | --- | --- | +| produce message | consume message | message's topic | +| send message time | delivery time, delivery rounds  | message store location | +| whether the message was sent successfully | whether message was consumed successfully | message's key | +| send cost-time | consume cost-time | message's tag value | + +## 2. Enable message trace in cluster deployment + +### 2.1 Broker's configuration file +following by Broker's properties file configuration that enable message trace: +``` +brokerClusterName=DefaultCluster +brokerName=broker-a +brokerId=0 +deleteWhen=04 +fileReservedTime=48 +brokerRole=ASYNC_MASTER +flushDiskType=ASYNC_FLUSH +storePathRootDir=/data/rocketmq/rootdir-a-m +storePathCommitLog=/data/rocketmq/commitlog-a-m +autoCreateSubscriptionGroup=true +## if msg tracing is open,the flag will be true +traceTopicEnable=true +listenPort=10911 +brokerIP1=XX.XX.XX.XX1 +namesrvAddr=XX.XX.XX.XX:9876 +``` + +### 2.2 Common mode +Each Broker node in RocketMQ cluster used for storing message trace data that client collected and sent. So, there is no requirements and limitations to the size of Broker node in RocketMQ cluster. + +### 2.3 IO physical isolation mode +For huge amounts of message trace data scenario, we can select any one Broker node in RocketMQ cluster used for storing message trace data special, thus, common message data's IO are isolated from message trace data's IO in physical, not impact each other. In this mode, RocketMQ cluster must have at least two Broker nodes, the one that defined as storing message trace data. + +### 2.4 Start Broker that enable message trace +`nohup sh mqbroker -c ../conf/2m-noslave/broker-a.properties &` + +## 3. Save the definition of topic that with support message trace +RocketMQ's message trace feature supports two types of storage. + +### 3.1 System level TraceTopic +Be default, message trace data is stored in system level TraceTopic(topic name: **RMQ_SYS_TRACE_TOPIC**). That topic will be created at startup of broker(As mentioned above, set **traceTopicEnable** to **true** in Broker's configuration). + +### 3.2 User defined TraceTopic +If user don't want to store message trace data in system level TraceTopic, he can create user defined TraceTopic used for storing message trace data(that is, create common topic for storing message trace data). The following part will introduce how client SDK support user defined TraceTopic. + +## 4. Client SDK demo with message trace feature +For business system adapting to use RocketMQ's message trace feature easily, in design phase, the author add a switch parameter(**enableMsgTrace**) for enable message trace; add a custom parameter(**customizedTraceTopic**) for user defined TraceTopic. + +### 4.1 Enable message trace when sending messages +``` + DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName",true); + producer.setNamesrvAddr("XX.XX.XX.XX1"); + producer.start(); + try { + { + Message msg = new Message("TopicTest", + "TagA", + "OrderID188", + "Hello world".getBytes(RemotingHelper.DEFAULT_CHARSET)); + SendResult sendResult = producer.send(msg); + System.out.printf("%s%n", sendResult); + } + + } catch (Exception e) { + e.printStackTrace(); + } +``` + +### 4.2 Enable message trace when subscribe messages +``` + DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("CID_JODIE_1",true); + consumer.subscribe("TopicTest", "*"); + consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); + consumer.setConsumeTimestamp("20181109221800"); + consumer.registerMessageListener(new MessageListenerConcurrently() { + @Override + public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { + System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs); + return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; + } + }); + consumer.start(); + System.out.printf("Consumer Started.%n"); +``` + +### 4.3 Self-defined topic support message trace +Adjusting instantiation of DefaultMQProducer and DefaultMQPushConsumer as following code to support user defined TraceTopic. +``` + ##Topic_test11111 should be created by user, used for storing message trace data. + DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName",true,"Topic_test11111"); + ...... + + DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("CID_JODIE_1",true,"Topic_test11111"); + ...... +``` + + + + diff --git a/docs/en/operation.md b/docs/en/operation.md new file mode 100644 index 0000000000..1d41e98444 --- /dev/null +++ b/docs/en/operation.md @@ -0,0 +1,1374 @@ + +# Operation Management +--- + +### 1 Deploy cluster + +#### 1.1 Single Master mode + +This mode is risky, upon broker restart or broken down, the whole service is unavailable. It's not recommended in production environment, it can be used for local test. + +##### 1)Start NameServer + +```bash +### Start Name Server +$ nohup sh mqnamesrv & + +### check whether Name Server is successfully started +$ tail -f ~/logs/rocketmqlogs/namesrv.log +The Name Server boot success... +``` + +##### 2)Start Broker + +```bash +### start Broker +$ nohup sh bin/mqbroker -n localhost:9876 & + +### check whether Name Server is successfully started, eg: Broker's IP is 192.168.1.2, Broker's name is broker-a +$ tail -f ~/logs/rocketmqlogs/Broker.log +The broker[broker-a, 192.169.1.2:10911] boot success... +``` + +#### 1.2 Multi Master mode + +Cluster contains Master node only, no Slave node, eg: 2 Master nodes, 3 Master nodes, advantages and disadvantages of this mode are shown below: + +- advantages:simple configuration, single Master node broke down or restart do not impact application. Under RAID10 disk config, even if machine broken down and cannot recover, message do not get lost because of RAID10's high reliable(async flush to disk lost little message, sync to disk do not lost message), this mode get highest performance. + +- disadvantages:during the machine's down time, messages have not be consumed on this machine can not be subscribed before recovery. That will impacts message's instantaneity. + +##### 1)Start NameServer + +NameServer should be started before broker. If under production environment, we recommend start 3 NameServer nodes for high available. Startup command is equal, as shown below: + +```bash +### start Name Server +$ nohup sh mqnamesrv & + +### check whether Name Server is successfully started +$ tail -f ~/logs/rocketmqlogs/namesrv.log +The Name Server boot success... +``` + +##### 2)start Broker cluster + +```bash +### start the first Master on machine A, eg:NameServer's IP is :192.168.1.1 +$ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-noslave/broker-a.properties & + +### start the second Master on machine B, eg:NameServer's IP is :192.168.1.1 +$ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-noslave/broker-b.properties & + +... +``` + +The above commands only used for single NameServer. In multi NameServer cluster, multi addresses concat by semicolon followed by -n in broker start command. + +#### 1.3 Multi Master Multi Slave mode - async replication + +Each Master node is equipped with one Slave node, this mode has many Master-Slave group, using async replication for HA, slaver has a lag(ms level) behind master, advantages and disadvantages of this mode are shown below: + +- advantages: message lost a little, even if disk is broken; message instantaneity do not loss; Consumer can still consume from slave when master is down, this process is transparency to user, no human intervention is required; Performance is almost equal to Multi Master mode. + +- disadvantages: message lost a little data, when Master is down and disk broken. + +##### 1)Start NameServer + +```bash +### start Name Server +$ nohup sh mqnamesrv & + +### check whether Name Server is successfully started +$ tail -f ~/logs/rocketmqlogs/namesrv.log +The Name Server boot success... +``` + +##### 2)Start Broker cluster + +```bash +### start first Master on machine A, eg: NameServer's IP is 192.168.1.1 +$ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-async/broker-a.properties & + +### start second Master on machine B, eg: NameServer's IP is 192.168.1.1 +$ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-async/broker-b.properties & + +### start first Slave on machine C, eg: NameServer's IP is 192.168.1.1 +$ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-async/broker-a-s.properties & + +### start second Slave on machine D, eg: NameServer's IP is 192.168.1.1 +$ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-async/broker-b-s.properties & +``` + +#### 1.4 Multi Master Multi Slave mode - synchronous double write + +Each Master node is equipped with one Slave node, this mode has many Master-Slave group, using synchronous double write for HA, application's write operation is successful means both master and slave write successful, advantages and disadvantages of this mode are shown below: + +- advantages:both data and service have no single point failure, message has no lantancy even if Master is down, service available and data available is very high; + +- disadvantages:this mode's performance is 10% lower than async replication mode, sending latency is a little high, in the current version, it do not have auto Master-Slave switch when Master is down. + +##### 1)Start NameServer + +```bash +### start Name Server +$ nohup sh mqnamesrv & + +### check whether Name Server is successfully started +$ tail -f ~/logs/rocketmqlogs/namesrv.log +The Name Server boot success... +``` + +##### 2)Start Broker cluster + +```bash +### start first Master on machine A, eg:NameServer's IP is 192.168.1.1 +$ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-sync/broker-a.properties & + +### start second Master on machine B, eg:NameServer's IP is 192.168.1.1 +$ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-sync/broker-b.properties & + +### start first Slave on machine C, eg: NameServer's IP is 192.168.1.1 +$ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-sync/broker-a-s.properties & + +### start second Slave on machine D, eg: NameServer's IP is 192.168.1.1 +$ nohup sh mqbroker -n 192.168.1.1:9876 -c $ROCKETMQ_HOME/conf/2m-2s-sync/broker-b-s.properties & +``` + +The above Broker matches Slave by specifying the same BrokerName, Master's BrokerId must be 0, Slave's BrokerId must larger than 0. Besides, a Master can have multi Slaves that each has a distinct BrokerId. $ROCKETMQ_HOME indicates RocketMQ's install directory, user needs to set this environment parameter. + +### 2 mqadmin management tool + +> Attentions: +> +> 1. execute command:`./mqadmin {command} {args}` +> 2. almost all commands need -n indicates NameSerer address, format is ip:port +> 3. almost all commands can get help info by -h +> 4. if command contains both Broker address(-b) and cluster name(-c), it's prior to use broker address. If command do not contains broker address, it will executed on all hosts in this cluster. Support only one broker host. -b format is ip:port, default port is 10911 +> 5. there are many commands under tools, but not all command can be used, only commands that initialized in MQAdminStartup can be used, you can modify this class, add or self-define command. +> 6. because of version update, little command do not update timely, please refer to source code directly when occur error. +#### 2.1 Topic + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
namemeaningcommand itemsexplaination
updateTopiccreate or update Topic's config-bBroker address, means which Broker that topic is located, only support single Broker, address format is ip:port
-ccluster name, whic cluster that topic belongs to(query cluster info by clusterList)
-h-print help info
-nNameServer Service address, format is ip:port
-passign read write authority to new topic(W=2|R=4|WR=6)
-rthe count of queue that can be read(default is 8)
-wthe count of queue that can be wrote(default is 8)
-ttopic name(can only use characters ^[a-zA-Z0-9_-]+$ )
deleteTopicdelete Topic-ccluster name, which cluster that topic will be deleted belongs to(query cluster info by clusterList)
-hprint help info
-nNameServer Service address, format is ip:port
-ttopic name(can only use characters ^[a-zA-Z0-9_-]+$ )
topicListquery Topic list info-hprint help info
-creturn topic list only if do not contains -c, if containis -c, it will return cluster name, topic name, consumer group name
-nNameServer Service address, format is ip:port
topicRoutequery Topic's route info-ttopic name
-hprint help info
-nNameServer Service address, format is ip:port
topicStatusquery Topic's offset-ttopic name
-hprint help info
-nNameServer Service address, format is ip:port
topicClusterListquery cluster list where Topic belongs to-ttopic name
-hprint help info
-nNameServer Service address, format is ip:port
updateTopicPermupdate Topic's produce and consume authority-ttopic name
-hprint help info
-nNameServer Service address, format is ip:port
-bBroker address which topic belongs to, support single broker only, format is ip:port
-passign read and write authority to the new topic(W=2|R=4|WR=6)
-ccluster name, which topic belongs to(query cluster info by clusterList), if do not have -b, execute comman an all brokers.
updateOrderConfcreate delete get specified namespace's kv config from NameServer, have not enabled at present-hprint help info
-nNameServer Service address, format is ip:port
-ttopic, key
-vorderConf, value
-mmethod, including get, put, delete
allocateMQcalculate consumer list rebalance result by average rebalance algorithm-ttopic name
-hprint help info
-nNameServer Service address, format is ip:port
-iipList, seperate by comma, calculate which topic queue that ips will load.
statsAllprint Topic's subscribe info, TPS, size of message blocked, count of read and write at last 24h, eg.-hprint help info
-nNameServer Service address, format is ip:port
-aonly print active topic or not
-tassign topic
+ + + +#### 2.2 Cluster + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
名称meaningcommand itemsexplaination
clusterListquery cluster info, including cluster, BrokerName, BrokerId, TPS, eg.-mprint more infos(eg: #InTotalYest, #OutTotalYest, #InTotalToday ,#OutTotalToday)
-hprint help info
-nNameServer Service address, format is ip:port
-iprint interval, unit second
clusterRTsend message to detect each cluster's Broker RT. Message will be sent to ${BrokerName} Topic。-aamount, count of detection, RT = sum time / + amount
-ssize of message, unit B
-cwhich cluster will be detected
-pwhether print format log, splitted by |, default is not print
-hprint help info
-mwhich machine room it belongs to, just for print
-isend interval, unit second
-nNameServer Service address, format is ip:port
+ + +#### 2.3 Broker + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
名称meaningcommand itemsexplaination
updateBrokerConfigupdate Broker's config file, it will modify Broker.conf-bBroker address, format is ip:port
-ccluster name
-kkey
-vvalue
-hprint help info
-nNameServer Service address, format is ip:port
brokerStatusget Broker's statistics info, running status(including whatever you want).-bBroker address, fomat isip:port
-hprint help info
-nNameServer Service address, format is ip:port
brokerConsumeStatsBroker's consumer info, including Consume Offset, Broker Offset, Diff, Timestamp that ordered by essage Queue-bBroker address, fomat isip:port
-trequest timeout time
-ldiff threshold, it will print when exceed this threshold.
-owhether is sequencial topic, generally false
-hprint help info
-nNameServer Service address, format is ip:port
getBrokerConfigget Broker's config-bBroker address, fomat isip:port
-nNameServer Service address, format is ip:port
wipeWritePermrevoke broker's write authority from NameServer.-bBroker address, fomat isip:port
-nNameServer Service address, format is ip:port
-hprint help info
cleanExpiredCQclean Broker's expired Consume Queue that maybe generated by decrease queue count.-nNameServer Service address, format is ip:port
-hprint help info
-bBroker address, fomat isip:port
-ccluster name
cleanUnusedTopicclean Broker's unused Topic that deleted mannually to release memory that Topic's Consume Queue occupied.-nNameServer Service address, format is ip:port
-hprint help info
-bBroker address, fomat isip:port
-ccluster name
sendMsgStatussend message to Broker, return send status and RT-nNameServer Service address, format is ip:port
-hprint help info
-bBrokerName, is different from broker address
-smessage size, unit B
-csend count
+ + +#### 2.4 Message + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
名称meaningcommand itemsexplaination
queryMsgByIdquery message by offsetMsgId. If use opensource console, it should use offsetMsgId. Please refer to QueryMsgByIdSubCommand for detail.-imsgId
-hprint help info
-nNameServer Service address, format is ip:port
queryMsgByKeyquery message by Message's Key-kmsgKey
-ttopic name
-hprint help info
-nNameServer Service address, format is ip:port
queryMsgByOffsetquery message by Offset-bBroker name(it's not broker address, can query Broker name by clusterList).
-iquery queue id
-ooffset value
-ttopic name
-hprint help info
-nNameServer Service address, format is ip:port
queryMsgByUniqueKeyquery by msgId, msgId is different from offsetMsgId, please refer to Frequently asked questions about operations for details. Use -g and -d to let specified consumer return consume result.-hprint help info
-nNameServer Service address, format is ip:port
-iuniqe msg id
-gconsumerGroup
-dclientId
-ttopic name
checkMsgSendRTdetect RT of sending a message to a topic, similiar to clusterRT-hprint help info
-nNameServer Service address, format is ip:port
-ttopic name
-adetection count
-ssize of the message
sendMessagesend a message, also can send to a specified Message Queue.-hprint help info
-nNameServer Service address, format is ip:port
-ttopic name
-pbody, message entity
-kkeys
-ctags
-bBrokerName
-iqueueId
consumeMessageconsume message. Differert consume logic depends on offset, start & end timestamp, message queue, please refer to ConsumeMessageCommand for details.-hprint help info
-nNameServer Service address, format is ip:port
-ttopic name
-bBrokerName
-ooffset that consumer start consume
-iqueueId
-gconsumer gropu
-stimestamp at start, refer to -h to get format开
-dtimestamp at the end
-csize of message that consumed
printMsgconsume and print messages from broker, support a time range-hprint help info
-nNameServer Service address, format is ip:port
-ttopic name
-ccharset, eg: UTF-8
-ssubExpress, filter expression
-btimestap at start, refer to -h to get format
-etimestamp at the end
-dwhether print message entity or not
printMsgByQueuesimilar to printMsg, but it need specified Message Queue-hprint help info
-nNameServer Service address, format is ip:port
-ttopic name
-iqueueId
-aBrokerName
-ccharset, eg: UTF-8
-ssubExpress, filter expression
-btimestamp at start, refer to -h to get format
-etimestamp at the end
-pwhether print message or not
-dwhether print message entity or not
-fwhether count and print tag or not
resetOffsetByTimereset offset by timestamp, Broker and consumer will all be reseted-hprint help info
-nNameServer Service address, format is ip:port
-gconsumer group
-ttopic name
-sreset offset corresponding to this timestamp
-fwhether enforce to reset or not, if set false, only can reset offset, if set true, it omit the relationship between timestamp and consumer offset.
-cwhether reset c++ sdk's offset or not
+ + +#### 2.5 Consumer, Consumer Group + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
namemeaningcommand itemsexplaination
consumerProgressquery subscribe status, can get blocking counts of a concrete client ip.-gconsumer group name
-swhether print client IP or not
-hprint help info
-nNameServer Service address, format is ip:port
consumerStatusquery consumer status, including message blocking, and consumer's jstack result(please refer to ConsumerStatusSubCommand)-hprint help info
-nNameServer Service address, format is ip:port
-gconsumer group
-iclientId
-swhether execute jstack or not
updateSubGroupcreate or update subscribe info-nNameServer Service address, format is ip:port
-hprint help info
-bBroker address
-ccluster name
-gconsumer group name
-sconsumer group is allowed to consume or not
-mstart consume from minimal offset or not
-dbroadcast mode or not
-qcapacity of retry queue
-rmax retry count
-iIt works when slaveReadEnable enabled, and that not consumed from slave. Suggesting that consume from slave node by specify slave id.
-wIf broker consume from slave, whic slave node depends on this config that configed by BrokerId, eg: 1.
-awhether notify other consumers to rebalance or not when the count of consumer changes
deleteSubGroupdelete subscribe info from Broker-nNameServer Service address, format is ip:port
-hprint help info
-bBroker address
-ccluster name
-gconsumer group name
cloneGroupOffsetuse source group's offset at target group-nNameServer Service address, format is ip:port
-hprint help info
-ssource consumer group
-dtarget consumer group
-ttopic name
-onot used at present
+ + + + +#### 2.6 Connection + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
namemeaningcommand itemsexplaination
consumerConnec tionquery Consumer's connection-gconsumer group name
-nNameServer Service address, format is ip:port
-hprint help info
producerConnec tionquery Producer's connection-gproducer group name
-ttopic name
-nNameServer Service address, format is ip:port
-hprint help info
+ + + + +#### 2.7 NameServer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
namemeaningcommand itemsexplaination
updateKvConfigupdate NameServer's kv config, not used at present-snamespace
-kkey
-vvalue
-nNameServer Service address, format is ip:port
-hprint help info
deleteKvConfigdelete NameServer's kv config-snamespace
-kkey
-nNameServer Service address, format is ip:port
-hprint help info
getNamesrvConfigget NameServer's config-nNameServer Service address, format is ip:port
-hprint help info
updateNamesrvConfigmodify NameServer's config-nNameServer Service address, format is ip:port
-hprint help info
-kkey
-vvalue
+ + + + +#### 2.8 Other + + + + + + + + + + + + + + + + + + + + + + +
namemeaningcommand itemsexplaination
startMonitoringStart the monitoring process, monitor message deletion and the number of retried messages in the queue-nNameServer Service address, format is ip:port
-hprint help info
+ + +### 3 Frequently asked questions about operations + +#### 3.1 RocketMQ's mqadmin command error + +> question description:execute mqadmin occur below exception after deploy RocketMQ cluster. +> +> ```java +> org.apache.rocketmq.remoting.exception.RemotingConnectException: connect to failed +> ``` + +Solution: execute command `export NAMESRV_ADDR=ip:9876` (ip is NameServer's ip address), then execute mqadmin commands. + +#### 3.2 RocketMQ consumer cannot consume, because of different version of producer and consumer. + +> question description: one producer produce message, consumer A can consume, consume B cannot consume, RocketMQ console print: +> +> ```java +> Not found the consumer group consume stats, because return offset table is empty, maybe the consumer not consume any message。 +> ``` + +Solution: make sure that producer and consumer has the same version of rocketmq-client. + +#### 3.3 Consumer cannot consume oldest message, when a new consumer group is added. + +> question description: when a new consumer group start, it consumes from current offset, do not fetch oldest message. + +Solution: rocketmq's default policy is consume from latest, that is skip oldest message. If you want consume oldest message, you need to set `org.apache.rocketmq.client.consumer.DefaultMQPushConsumer#setConsumeFromWhere`. The following is three common configurations: + +- default configuration, a new consumer group consume from latest position at first startup, then consume from last time's offset at next startup, that is skip oldest message; + +```java +consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET); +``` + +- a new consumer group consume from oldest postion at first startup, then consume from last time's offset at next startup, that is consume the unexpired message; + +```java +consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); +``` + +- a new consumer group consume from specified timestamp at first startup, then consume from last time's offset at next startup, cooperate with consumer.setConsumeTimestamp(), default is half an hour before; + +```java +consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_TIMESTAMP); +``` + +#### 3.4 How to enable consume from Slave + +In some cases, consumer need reset offset to a day or two before, if Master Broker has limited memory, it's CommitLog will have a high IO load, then it will impact other message's read and write that on this broker. When `slaveReadEnable=true` is set, and consumer's offset exceeds `accessMessageInMemoryMaxRatio=40%`, Master Broker will recommend consumer consume from Slave Broker to lower Master Broker IO. + +#### 3.5 Performance tuning + +A spin lock is recommended for asynchronous disk flush, a reentrant lock is recommended for synchronous disk flush, configuration item is `useReentrantLockWhenPutMessage`, default is false; Enable `TransientStorePoolEnable` is recommended when use asynchronous disk flush; Recommend to close `transferMsgByHeap` to improve fetch efficiency; Set a little larger `sendMessageThreadPoolNums`, when use synchronous disk flush. + +#### 3.6 The meaning and difference between msgId and offsetMsgId in RocketMQ + +You will usually see the following log print message after sending message by using RocketMQ sdk. + +```java +SendResult [sendStatus=SEND_OK, msgId=0A42333A0DC818B4AAC246C290FD0000, offsetMsgId=0A42333A00002A9F000000000134F1F5, messageQueue=MessageQueue [topic=topicTest1, BrokerName=mac.local, queueId=3], queueOffset=4] +``` + +- msgId, is generated by producer sdk. In particular, call method `MessageClientIDSetter.createUniqIDBuffer()` to generate unique Id; +- offsetMsgId, offsetMsgId is generated by Broker server(format is "Ip Address + port + CommitLog offset"). offsetMsgId is messageId that is RocketMQ console's input. From 67b8a2aa83a7a120e286bf7c34854cc4addfa532 Mon Sep 17 00:00:00 2001 From: Shaofeng Li <63688663+tuobuxie@users.noreply.github.com> Date: Tue, 26 May 2020 11:20:43 +0800 Subject: [PATCH 48/49] Add @Override for RMQOrderListener.java (#1939) Co-authored-by: lishaofeng --- .../rocketmq/test/listener/rmq/order/RMQOrderListener.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/src/main/java/org/apache/rocketmq/test/listener/rmq/order/RMQOrderListener.java b/test/src/main/java/org/apache/rocketmq/test/listener/rmq/order/RMQOrderListener.java index 85c61770e0..f483866608 100644 --- a/test/src/main/java/org/apache/rocketmq/test/listener/rmq/order/RMQOrderListener.java +++ b/test/src/main/java/org/apache/rocketmq/test/listener/rmq/order/RMQOrderListener.java @@ -64,8 +64,9 @@ public class RMQOrderListener extends AbstractListener implements MessageListene return String.format("%s_%s", queueId, brokerIp); } + @Override public ConsumeOrderlyStatus consumeMessage(List msgs, - ConsumeOrderlyContext context) { + ConsumeOrderlyContext context) { for (MessageExt msg : msgs) { if (isDebug) { if (listenerName != null && listenerName != "") { From 3f37c8515795db7c757a97df404589a1bdf4624e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=9C=9F=E4=B9=8B=E6=A3=92?= <1374178468@qq.com> Date: Tue, 2 Jun 2020 10:43:07 +0800 Subject: [PATCH 49/49] [ISSUE #1988] Fix the issue that can not update messageDelay correctly with mqadmin updateBrokerConfig command use "$@" instead of #@ to make mutiple value parceled by quoted available. (cherry picked from commit 03e8b03ba397063945a1619293f0a15579e4764d) --- distribution/bin/mqadmin | 2 +- distribution/bin/tools.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/distribution/bin/mqadmin b/distribution/bin/mqadmin index cd0253bc87..980d6e3b6b 100644 --- a/distribution/bin/mqadmin +++ b/distribution/bin/mqadmin @@ -42,4 +42,4 @@ fi export ROCKETMQ_HOME -sh ${ROCKETMQ_HOME}/bin/tools.sh org.apache.rocketmq.tools.command.MQAdminStartup $@ +sh ${ROCKETMQ_HOME}/bin/tools.sh org.apache.rocketmq.tools.command.MQAdminStartup "$@" diff --git a/distribution/bin/tools.sh b/distribution/bin/tools.sh index 071794c630..a13dc7e620 100644 --- a/distribution/bin/tools.sh +++ b/distribution/bin/tools.sh @@ -40,4 +40,4 @@ JAVA_OPT="${JAVA_OPT} -server -Xms1g -Xmx1g -Xmn256m -XX:MetaspaceSize=128m -XX: JAVA_OPT="${JAVA_OPT} -Djava.ext.dirs=${BASE_DIR}/lib:${JAVA_HOME}/jre/lib/ext:${JAVA_HOME}/lib/ext" JAVA_OPT="${JAVA_OPT} -cp ${CLASSPATH}" -$JAVA ${JAVA_OPT} $@ +$JAVA ${JAVA_OPT} "$@"