From 6e6c91a40a920a05c6c21b2b32b158b811ef56a2 Mon Sep 17 00:00:00 2001 From: saz97 Date: Mon, 30 Jun 2025 15:11:39 +0800 Subject: [PATCH 01/19] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E7=B3=BB=E7=BB=9FTopic=201.=20=E5=88=9B=E5=BB=BA=E8=B7=AF?= =?UTF-8?q?=E7=94=B1=E4=BA=8B=E4=BB=B6=E4=B8=93=E7=94=A8=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?Topic=20=20=20=20-=20=E6=96=B0=E5=A2=9ETopicValidator.RMQ=5FROU?= =?UTF-8?q?TE=5FEVENT=5FTOPIC=E5=AE=9A=E4=B9=89=20=20=20=20-=20=E5=8A=A0?= =?UTF-8?q?=E5=85=A5=E7=B3=BB=E7=BB=9F=E4=B8=BB=E9=A2=98=E9=9B=86=E5=90=88?= =?UTF-8?q?(SYSTEM=5FTOPIC=5FSET)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 2. 实现主备切换事件发送 - 在changeToMaster()中触发发送操作 --- .../broker/controller/ReplicasManager.java | 46 +++++++++++++++++++ .../broker/topic/TopicConfigManager.java | 10 ++++ .../rocketmq/common/topic/TopicValidator.java | 2 + 3 files changed, 58 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java b/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java index f22f22a12bd..4de3d14fc4f 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java @@ -17,10 +17,13 @@ package org.apache.rocketmq.broker.controller; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Random; import java.util.Set; import java.util.concurrent.ArrayBlockingQueue; @@ -35,11 +38,15 @@ import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.out.BrokerOuterAPI; import org.apache.rocketmq.client.exception.MQBrokerException; +import org.apache.rocketmq.client.producer.DefaultMQProducer; +import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.constant.LoggerName; +import org.apache.rocketmq.common.message.Message; +import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; @@ -56,6 +63,8 @@ import org.apache.rocketmq.store.ha.autoswitch.BrokerMetadata; import org.apache.rocketmq.store.ha.autoswitch.TempBrokerMetadata; +import com.alibaba.fastjson2.JSON; + import static org.apache.rocketmq.remoting.protocol.ResponseCode.CONTROLLER_BROKER_METADATA_NOT_EXIST; /** @@ -234,6 +243,42 @@ public synchronized void changeBrokerRole(final Long newMasterBrokerId, final St } } + private void sendRouteChangeEvent(int newMasterEpoch) { + DefaultMQProducer producer = new DefaultMQProducer("ROUTE_EVENT_PRODUCER"); + + try { + producer.setNamesrvAddr( + StringUtils.join(this.brokerController.getBrokerConfig().getNamesrvAddr(), ";") + ); + producer.setSendMsgTimeout(5000); + producer.start(); + + Map eventData = new HashMap<>(); + eventData.put("eventType", "SWITCH"); + eventData.put("brokerName", this.brokerConfig.getBrokerName()); + eventData.put("brokerId", this.brokerControllerId); + eventData.put("masterEpoch", newMasterEpoch); + eventData.put("timestamp", System.currentTimeMillis()); + + Message msg = new Message( + TopicValidator.RMQ_ROUTE_EVENT_TOPIC, + "", + JSON.toJSONString(eventData).getBytes(StandardCharsets.UTF_8) + ); + + msg.putUserProperty("__SYS_FLAG__", "SWITCH"); + msg.putUserProperty("BROKER_NAME", this.brokerConfig.getBrokerName()); + + SendResult sendResult = producer.send(msg); + LOGGER.info("Sent master change event: {}", sendResult); + + } catch (Exception e) { + LOGGER.error("Failed to send master change event", e); + } finally { + producer.shutdown(); + } + } + public void changeToMaster(final int newMasterEpoch, final int syncStateSetEpoch, final Set syncStateSet) throws Exception { synchronized (this) { if (newMasterEpoch > this.masterEpoch) { @@ -260,6 +305,7 @@ public void changeToMaster(final int newMasterEpoch, final int syncStateSetEpoch // Notify ha service, change to master this.haService.changeToMaster(newMasterEpoch); + sendRouteChangeEvent(newMasterEpoch); this.brokerController.getBrokerConfig().setBrokerId(MixAll.MASTER_ID); this.brokerController.getMessageStoreConfig().setBrokerRole(BrokerRole.SYNC_MASTER); diff --git a/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java b/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java index b20cafc1018..79c233d8f22 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java @@ -223,6 +223,16 @@ protected void init() { this.topicConfigTable.put(topicConfig.getTopicName(), topicConfig); } } + + { + // TopicValidator.RMQ_ROUTE_EVENT_TOPIC + String topic = TopicValidator.RMQ_ROUTE_EVENT_TOPIC; + TopicConfig topicConfig = new TopicConfig(topic); + TopicValidator.addSystemTopic(topic); + topicConfig.setReadQueueNums(1); + topicConfig.setWriteQueueNums(1); + putTopicConfig(topicConfig); + } } public TopicConfig putTopicConfig(TopicConfig topicConfig) { diff --git a/common/src/main/java/org/apache/rocketmq/common/topic/TopicValidator.java b/common/src/main/java/org/apache/rocketmq/common/topic/TopicValidator.java index c19592a44c3..1933279b9e2 100644 --- a/common/src/main/java/org/apache/rocketmq/common/topic/TopicValidator.java +++ b/common/src/main/java/org/apache/rocketmq/common/topic/TopicValidator.java @@ -32,6 +32,7 @@ public class TopicValidator { public static final String RMQ_SYS_SELF_TEST_TOPIC = "SELF_TEST_TOPIC"; public static final String RMQ_SYS_OFFSET_MOVED_EVENT = "OFFSET_MOVED_EVENT"; public static final String RMQ_SYS_ROCKSDB_OFFSET_TOPIC = "CHECKPOINT_TOPIC"; + public static final String RMQ_ROUTE_EVENT_TOPIC = "ROUTE_EVENT_TOPIC"; public static final String SYSTEM_TOPIC_PREFIX = "rmq_sys_"; public static final String SYNC_BROKER_MEMBER_GROUP_PREFIX = SYSTEM_TOPIC_PREFIX + "SYNC_BROKER_MEMBER_"; @@ -57,6 +58,7 @@ public class TopicValidator { SYSTEM_TOPIC_SET.add(RMQ_SYS_SELF_TEST_TOPIC); SYSTEM_TOPIC_SET.add(RMQ_SYS_OFFSET_MOVED_EVENT); SYSTEM_TOPIC_SET.add(RMQ_SYS_ROCKSDB_OFFSET_TOPIC); + SYSTEM_TOPIC_SET.add(RMQ_ROUTE_EVENT_TOPIC); NOT_ALLOWED_SEND_TOPIC_SET.add(RMQ_SYS_SCHEDULE_TOPIC); NOT_ALLOWED_SEND_TOPIC_SET.add(RMQ_SYS_TRANS_HALF_TOPIC); From 64833bba2fb5feed0501d6ee81b03ae6b7a678c9 Mon Sep 17 00:00:00 2001 From: saz97 Date: Thu, 3 Jul 2025 16:06:31 +0800 Subject: [PATCH 02/19] =?UTF-8?q?=E5=AE=9E=E7=8E=B0broker=E9=80=9A?= =?UTF-8?q?=E8=BF=87=E9=95=BF=E8=BF=9E=E6=8E=A5=E9=80=9A=E7=9F=A5Client?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. broker端通知机制 - 当broker切换为slave时,构造ROUTE_EVENT(299)事件请求 - 通过remotingServer获取所有活跃连接(activeChannels) - 向所有活跃客户端连接发送一次性通知 2. Client路由更新处理 - ClientRemotingProcessor处理ROUTE_EVENT请求 - 触发客户端立即更新路由信息(updateTopicRouteInfoFromNameServer) 3. RemotingServer新增 - 新增getActiveChannels() - NettyRemotingServer实现活跃连接维护: * 新增activeChannels集合 * channel激活时自动添加 * channel关闭时自动移除 --- .../broker/controller/ReplicasManager.java | 37 +++++++++++++++++++ .../client/impl/ClientRemotingProcessor.java | 11 ++++++ .../rocketmq/client/impl/MQClientAPIImpl.java | 2 + .../rocketmq/remoting/RemotingServer.java | 4 ++ .../remoting/netty/NettyRemotingServer.java | 21 +++++++++++ .../remoting/protocol/RequestCode.java | 2 + 6 files changed, 77 insertions(+) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java b/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java index 4de3d14fc4f..c332f9253fa 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java @@ -50,7 +50,12 @@ import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; +import org.apache.rocketmq.remoting.RemotingServer; +import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.protocol.EpochEntry; +import org.apache.rocketmq.remoting.protocol.LanguageCode; +import org.apache.rocketmq.remoting.protocol.RemotingCommand; +import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.body.SyncStateSet; import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.GetMetaDataResponseHeader; @@ -65,6 +70,8 @@ import com.alibaba.fastjson2.JSON; +import io.netty.channel.Channel; + import static org.apache.rocketmq.remoting.protocol.ResponseCode.CONTROLLER_BROKER_METADATA_NOT_EXIST; /** @@ -324,6 +331,20 @@ public void changeToMaster(final int newMasterEpoch, final int syncStateSetEpoch } } + private RemotingCommand buildNotify(String newMasterAddress) { + RemotingCommand switchNotify = RemotingCommand.createRequestCommand( + RequestCode.ROUTE_EVENT, + null + ); + + switchNotify.addExtField("newMasterAddr", newMasterAddress); + switchNotify.addExtField("oldMasterAddr", this.brokerAddress); + switchNotify.addExtField("Event", "SWITCH"); + switchNotify.setLanguage(LanguageCode.JAVA); + + return switchNotify; + } + public void changeToSlave(final String newMasterAddress, final int newMasterEpoch, Long newMasterBrokerId) { synchronized (this) { if (newMasterEpoch > this.masterEpoch) { @@ -358,6 +379,22 @@ public void changeToSlave(final String newMasterAddress, final int newMasterEpoc // Notify ha service, change to slave this.haService.changeToSlave(newMasterAddress, newMasterEpoch, brokerControllerId); + RemotingServer remotingServer = brokerController.getRemotingServer(); + + Set activeChannels = remotingServer.getActiveChannels(); + + RemotingCommand switchNotify = buildNotify(newMasterAddress); + + activeChannels.forEach(channel -> { + if (channel.isActive()) { + try { + remotingServer.invokeOneway(channel, switchNotify, 3000); + } catch (Exception e) { + LOGGER.warn("Notify client failed: {}", RemotingHelper.parseChannelRemoteAddr(channel), e); + } + } + }); + this.brokerController.getTopicConfigManager().getDataVersion().nextVersion(newMasterEpoch); registerBrokerWhenRoleChange(); } diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/ClientRemotingProcessor.java b/client/src/main/java/org/apache/rocketmq/client/impl/ClientRemotingProcessor.java index e46c651f928..2c40cc7987e 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/ClientRemotingProcessor.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/ClientRemotingProcessor.java @@ -86,6 +86,10 @@ public RemotingCommand processRequest(ChannelHandlerContext ctx, case RequestCode.PUSH_REPLY_MESSAGE_TO_CLIENT: return this.receiveReplyMessage(ctx, request); + + case RequestCode.ROUTE_EVENT: + return this.processRouteEventNotify(request, ctx); + default: break; } @@ -292,4 +296,11 @@ private void processReplyMessage(MessageExt replyMsg) { correlationId, bornHost); } } + + private RemotingCommand processRouteEventNotify(RemotingCommand request, ChannelHandlerContext ctx) { + this.mqClientFactory.updateTopicRouteInfoFromNameServer(); + return RemotingCommand.createResponseCommand(null); + + } + } 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 c001b33fa98..fa132adb4ad 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 @@ -322,6 +322,8 @@ public MQClientAPIImpl(final NettyClientConfig nettyClientConfig, this.remotingClient.registerProcessor(RequestCode.CONSUME_MESSAGE_DIRECTLY, this.clientRemotingProcessor, null); this.remotingClient.registerProcessor(RequestCode.PUSH_REPLY_MESSAGE_TO_CLIENT, this.clientRemotingProcessor, null); + + this.remotingClient.registerProcessor(RequestCode.ROUTE_EVENT, this.clientRemotingProcessor, null); } public List getNameServerAddressList() { diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/RemotingServer.java b/remoting/src/main/java/org/apache/rocketmq/remoting/RemotingServer.java index 8cfa1e1a083..64c62e6b600 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/RemotingServer.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/RemotingServer.java @@ -17,6 +17,8 @@ package org.apache.rocketmq.remoting; import io.netty.channel.Channel; + +import java.util.Set; import java.util.concurrent.ExecutorService; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.remoting.exception.RemotingSendRequestException; @@ -36,6 +38,8 @@ void registerProcessor(final int requestCode, final NettyRequestProcessor proces Pair getProcessorPair(final int requestCode); + Set getActiveChannels(); + Pair getDefaultProcessorPair(); RemotingServer newRemotingServer(int port); diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java index d56d6faa336..dac31980ae6 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java @@ -79,8 +79,11 @@ import java.net.InetSocketAddress; import java.security.cert.CertificateException; import java.time.Duration; +import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.NoSuchElementException; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutorService; @@ -106,6 +109,10 @@ public class NettyRemotingServer extends NettyRemotingAbstract implements Remoti private DefaultEventExecutorGroup defaultEventExecutorGroup; + private final Set activeChannels = Collections.newSetFromMap( + new ConcurrentHashMap() + ); + /** * NettyRemotingServer may hold multiple SubRemotingServer, each server will be stored in this container with a * ListenPort key. @@ -413,6 +420,11 @@ public ExecutorService getCallbackExecutor() { return this.publicExecutor; } + @Override + public Set getActiveChannels() { + return new HashSet<>(activeChannels); + } + private void printRemotingCodeDistribution() { if (distributionHandler != null) { String inBoundSnapshotString = distributionHandler.getInBoundSnapshotString(); @@ -594,6 +606,8 @@ public void channelActive(ChannelHandlerContext ctx) throws Exception { log.info("NETTY SERVER PIPELINE: channelActive, the channel[{}]", remoteAddress); super.channelActive(ctx); + NettyRemotingServer.this.activeChannels.add(ctx.channel()); + if (NettyRemotingServer.this.channelEventListener != null) { NettyRemotingServer.this.putNettyEvent(new NettyEvent(NettyEventType.CONNECT, remoteAddress, ctx.channel())); } @@ -605,6 +619,8 @@ public void channelInactive(ChannelHandlerContext ctx) throws Exception { log.info("NETTY SERVER PIPELINE: channelInactive, the channel[{}]", remoteAddress); super.channelInactive(ctx); + NettyRemotingServer.this.activeChannels.remove(ctx.channel()); + if (NettyRemotingServer.this.channelEventListener != null) { NettyRemotingServer.this.putNettyEvent(new NettyEvent(NettyEventType.CLOSE, remoteAddress, ctx.channel())); } @@ -683,6 +699,11 @@ public Pair getProcessorPair(final int r return this.processorTable.get(requestCode); } + @Override + public Set getActiveChannels() { + return null; + } + @Override public Pair getDefaultProcessorPair() { return this.defaultRequestProcessorPair; diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RequestCode.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RequestCode.java index 8b2749eaae2..b8b0a7e87fa 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RequestCode.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RequestCode.java @@ -51,6 +51,8 @@ public class RequestCode { public static final int HEART_BEAT = 34; + public static final int ROUTE_EVENT = 299; + public static final int UNREGISTER_CLIENT = 35; public static final int CONSUMER_SEND_MSG_BACK = 36; From b869ef0bc0c77cb549e12efc8f2208692cb0217b Mon Sep 17 00:00:00 2001 From: saz97 Date: Fri, 4 Jul 2025 16:57:09 +0800 Subject: [PATCH 03/19] =?UTF-8?q?Proxy=E7=AB=AF=E5=AE=9E=E7=8E=B0=E4=BA=8B?= =?UTF-8?q?=E4=BB=B6=E9=A9=B1=E5=8A=A8=E7=9A=84=E8=B7=AF=E7=94=B1=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=EF=BC=9A=201.=20=E6=96=B0=E5=A2=9ERouteEventSubscribe?= =?UTF-8?q?r=E8=AE=A2=E9=98=85=E8=B7=AF=E7=94=B1=E4=BA=8B=E4=BB=B6=20-?= =?UTF-8?q?=E5=88=9B=E5=BB=BA=E8=BD=BB=E9=87=8F=E7=BA=A7=E6=B6=88=E8=B4=B9?= =?UTF-8?q?=E8=80=85=E7=9B=91=E5=90=AC=E7=B3=BB=E7=BB=9FTopic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 2.实现缓存失效标记机制 -标记相关Topic缓存为dirtyTopics -dirtyTopics加入pendingTopics队列 3.路由异步更新机制: -200ms窗口处理pendingTopics -使用现有cacheRefreshExecutor线程池并行更新 -失败重试 -保留原有定时刷新作为兜底策略 --- .../broker/controller/ReplicasManager.java | 1 + .../route/ClusterTopicRouteService.java | 5 + .../service/route/RouteEventSubscriber.java | 94 +++++++++++++++++++ .../service/route/TopicRouteService.java | 70 ++++++++++++++ 4 files changed, 170 insertions(+) create mode 100644 proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java diff --git a/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java b/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java index c332f9253fa..d6ed403c44a 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java @@ -266,6 +266,7 @@ private void sendRouteChangeEvent(int newMasterEpoch) { eventData.put("brokerId", this.brokerControllerId); eventData.put("masterEpoch", newMasterEpoch); eventData.put("timestamp", System.currentTimeMillis()); + eventData.put("affectedTopic", this.brokerController.getTopicConfigManager().getTopicConfigTable().keySet().toArray()); Message msg = new Message( TopicValidator.RMQ_ROUTE_EVENT_TOPIC, diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/ClusterTopicRouteService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/ClusterTopicRouteService.java index a4df98971cb..b6d98ddec49 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/ClusterTopicRouteService.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/ClusterTopicRouteService.java @@ -24,9 +24,14 @@ import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; public class ClusterTopicRouteService extends TopicRouteService { + private final RouteEventSubscriber eventSubscriber; public ClusterTopicRouteService(MQClientAPIFactory mqClientAPIFactory) { super(mqClientAPIFactory); + + this.eventSubscriber = new RouteEventSubscriber(topic -> { + markCacheDirty(topic); + }); } @Override diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java new file mode 100644 index 00000000000..7e8fe8a4e95 --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.proxy.service.route; + +import java.lang.management.ManagementFactory; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; +import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; +import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; +import org.apache.rocketmq.client.exception.MQClientException; +import org.apache.rocketmq.common.message.MessageExt; +import org.apache.rocketmq.common.topic.TopicValidator; +import org.apache.rocketmq.proxy.config.ConfigurationManager; +import org.apache.rocketmq.proxy.config.ProxyConfig; + +import com.alibaba.fastjson2.JSON; + +public class RouteEventSubscriber { + private final Consumer dirtyMarker; + private final DefaultMQPushConsumer consumer; + + public RouteEventSubscriber(Consumer dirtyMarker) { + this.dirtyMarker = dirtyMarker; + this.consumer = createSimplifiedConsumer(); + startListening(); + } + + private DefaultMQPushConsumer createSimplifiedConsumer() { + ProxyConfig config = ConfigurationManager.getProxyConfig(); + + DefaultMQPushConsumer consumer = new DefaultMQPushConsumer( + "ProxyRouteSubGroup_" + ManagementFactory.getRuntimeMXBean().getName() + ); + + consumer.setNamesrvAddr(config.getNamesrvAddr()); + consumer.setPullBatchSize(10); + consumer.setConsumeThreadMin(1); + consumer.setConsumeThreadMax(1); + + return consumer; + } + + private void startListening() { + try { + consumer.subscribe(TopicValidator.RMQ_ROUTE_EVENT_TOPIC, "*"); + + consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { + processMessages(msgs); + return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; + }); + + consumer.start(); + } catch (MQClientException e) { + } + } + + private void processMessages(List msgs) { + for (MessageExt msg : msgs) { + try { + String json = new String(msg.getBody(), StandardCharsets.UTF_8); + Map event = JSON.parseObject(json, Map.class); + + Object[] topics = (Object[]) event.get("affectedTopic"); + for (Object topicObj : topics) { + String topic = (String) topicObj; + dirtyMarker.accept(topic); + } + } catch (Exception e) { + } + } + } + + public void shutdown() { + consumer.shutdown(); + } +} \ No newline at end of file diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java index bcdf8140bc5..60d80c70a77 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java @@ -21,8 +21,15 @@ import com.github.benmanes.caffeine.cache.LoadingCache; import java.time.Duration; +import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -60,6 +67,11 @@ public abstract class TopicRouteService extends AbstractStartAndShutdown { protected final ScheduledExecutorService scheduledExecutorService; protected final ThreadPoolExecutor cacheRefreshExecutor; + protected final ConcurrentMap dirtyTopics = new ConcurrentHashMap<>(); + protected final BlockingQueue pendingTopics = new LinkedBlockingQueue<>(); + protected final ScheduledExecutorService lazyUpdateExecutor; + protected final Object batchUpdateLock = new Object(); + public TopicRouteService(MQClientAPIFactory mqClientAPIFactory) { ProxyConfig config = ConfigurationManager.getProxyConfig(); @@ -134,6 +146,13 @@ public String resolve(String name) { } } }, serviceDetector); + this.lazyUpdateExecutor = Executors.newSingleThreadScheduledExecutor( + new ThreadFactoryImpl("RouteLazyUpdate_")); + + lazyUpdateExecutor.scheduleWithFixedDelay(() -> { + batchRefreshRoutes(); + }, 50, 200, TimeUnit.MILLISECONDS); + this.init(); } @@ -226,4 +245,55 @@ protected MessageQueueView buildMessageQueueView(String topic, TopicRouteData to } return MessageQueueView.WRAPPED_EMPTY_QUEUE; } + + protected void markCacheDirty(String topic) { + if (dirtyTopics.putIfAbsent(topic, System.currentTimeMillis()) == null) { + pendingTopics.offer(topic); + } + } + + private void batchRefreshRoutes() { + if (pendingTopics.isEmpty()) return; + + List refreshList = new ArrayList<>(100); + synchronized (batchUpdateLock) { + int count = 0; + while (!pendingTopics.isEmpty() && count < 100) { + String topic = pendingTopics.poll(); + if (topic != null) { + refreshList.add(topic); + count++; + } + } + } + + List> futures = new ArrayList<>(); + for (String topic : refreshList) { + futures.add(CompletableFuture.runAsync(() -> { + try { + refreshSingleRoute(topic); + } catch (Exception e) { + log.warn("Refresh topic route failed: {}", topic, e); + pendingTopics.offer(topic); + } + }, cacheRefreshExecutor)); + } + + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); + } + + private void refreshSingleRoute(String topic) { + try { + TopicRouteData routeData = mqClientAPIFactory.getClient() + .getTopicRouteInfoFromNameServer(topic, 1000); + + MessageQueueView newView = buildMessageQueueView(topic, routeData); + topicCache.put(topic, newView); + + dirtyTopics.remove(topic); + } catch (Exception e) { + + } + } + } From a2bceed4c2da8c1a6c0b7b4243ac16b16d346e0b Mon Sep 17 00:00:00 2001 From: saz97 Date: Wed, 23 Jul 2025 17:01:58 +0800 Subject: [PATCH 04/19] =?UTF-8?q?=E8=A7=A3=E5=86=B3Broker=E4=B8=8A?= =?UTF-8?q?=E4=B8=8B=E7=BA=BF=E5=AF=BC=E8=87=B4Proxy=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=B8=8D=E5=8F=8A=E6=97=B6=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Broker事件发布机制: - 在BrokerController添加`publishRouteEvent`方法 - Broker启动/关闭/时发送事件到`RMQ_ROUTE_EVENT_TOPIC` 2. Proxy事件订阅机制: - 创建RouteEventSubscriber消费路由事件 - 实现事件处理流水线: * 订阅系统Topic `RMQ_ROUTE_EVENT_TOPIC` * 提取受影响Topic列表 * 调用markCacheDirty标记脏数据 - 向namesrv获取新路由 测试了单副本Broker的IP变更场景,Proxy端能及时感知,并更新路由。 --- .../rocketmq/broker/BrokerController.java | 43 +++++++++++++ .../broker/controller/ReplicasManager.java | 48 +------------- .../route/ClusterTopicRouteService.java | 25 ++++++++ .../service/route/RouteEventSubscriber.java | 48 ++++++-------- .../service/route/TopicRouteService.java | 64 +++++++++++++------ 5 files changed, 137 insertions(+), 91 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 d3485f9f65b..31f4b217f76 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java @@ -16,6 +16,7 @@ */ package org.apache.rocketmq.broker; +import com.alibaba.fastjson2.JSON; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory; @@ -110,6 +111,7 @@ import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.stats.MomentStatsItem; +import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.common.utils.ServiceProvider; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; @@ -141,6 +143,7 @@ import org.apache.rocketmq.store.MessageArrivingListener; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.PutMessageResult; +import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.RocksDBMessageStore; import org.apache.rocketmq.store.config.BrokerRole; import org.apache.rocketmq.store.config.MessageStoreConfig; @@ -157,10 +160,12 @@ import org.apache.rocketmq.store.timer.TimerMetrics; import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -1391,6 +1396,10 @@ public ChangeInvisibleTimeProcessor getChangeInvisibleTimeProcessor() { protected void shutdownBasicService() { + publishRouteEvent("shutdown"); + LOG.info("[ROUTE_UPDATE]: publishRouteEvent before shutdown basic service, brokerName: {}, brokerId: {}", + this.brokerConfig.getBrokerName(), this.brokerConfig.getBrokerId()); + shutdown = true; this.unregisterBrokerAll(); @@ -1610,6 +1619,37 @@ protected void shutdownBasicService() { } } + public void publishRouteEvent(String eventType) { + + Map eventData = new HashMap<>(); + eventData.put("eventType", eventType); + eventData.put("brokerName", this.brokerConfig.getBrokerName()); + eventData.put("brokerId", this.brokerConfig.getBrokerId()); + eventData.put("timestamp", System.currentTimeMillis()); + eventData.put("affectedTopic", this.getTopicConfigManager().getTopicConfigTable().keySet().toArray()); + LOG.info("[ROUTE_UPDATE]: eventData {}", eventData); + + MessageExtBrokerInner innerMsg = new MessageExtBrokerInner(); + innerMsg.setTopic(TopicValidator.RMQ_ROUTE_EVENT_TOPIC); + innerMsg.setBody(JSON.toJSONString(eventData).getBytes(StandardCharsets.UTF_8)); + innerMsg.setTags("broker_event"); + innerMsg.setQueueId(0); + innerMsg.setBornTimestamp(System.currentTimeMillis()); + innerMsg.setBornHost(this.getStoreHost()); + innerMsg.setStoreHost(this.getStoreHost()); + innerMsg.setSysFlag(0); + LOG.info("[ROUTE_UPDATE]: innerMsg {}", innerMsg); + + PutMessageResult result = this.messageStore.putMessage(innerMsg); + this.messageStore.flush(); + + if (result.getPutMessageStatus() == PutMessageStatus.PUT_OK) { + return; + } else { + LOG.error("[ROUTE_UPDATE]: broadcasting failed, status {}", result.getPutMessageStatus()); + } + } + public void shutdown() { shutdownBasicService(); @@ -1830,6 +1870,9 @@ public void run() { } } }, 10, 5, TimeUnit.SECONDS); + + publishRouteEvent("start"); + } protected void scheduleSendHeartbeat() { diff --git a/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java b/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java index d6ed403c44a..2f2c7530f7c 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java @@ -17,13 +17,10 @@ package org.apache.rocketmq.broker.controller; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Random; import java.util.Set; import java.util.concurrent.ArrayBlockingQueue; @@ -38,15 +35,11 @@ import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.out.BrokerOuterAPI; import org.apache.rocketmq.client.exception.MQBrokerException; -import org.apache.rocketmq.client.producer.DefaultMQProducer; -import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.common.message.Message; -import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; @@ -68,8 +61,6 @@ import org.apache.rocketmq.store.ha.autoswitch.BrokerMetadata; import org.apache.rocketmq.store.ha.autoswitch.TempBrokerMetadata; -import com.alibaba.fastjson2.JSON; - import io.netty.channel.Channel; import static org.apache.rocketmq.remoting.protocol.ResponseCode.CONTROLLER_BROKER_METADATA_NOT_EXIST; @@ -250,42 +241,6 @@ public synchronized void changeBrokerRole(final Long newMasterBrokerId, final St } } - private void sendRouteChangeEvent(int newMasterEpoch) { - DefaultMQProducer producer = new DefaultMQProducer("ROUTE_EVENT_PRODUCER"); - - try { - producer.setNamesrvAddr( - StringUtils.join(this.brokerController.getBrokerConfig().getNamesrvAddr(), ";") - ); - producer.setSendMsgTimeout(5000); - producer.start(); - - Map eventData = new HashMap<>(); - eventData.put("eventType", "SWITCH"); - eventData.put("brokerName", this.brokerConfig.getBrokerName()); - eventData.put("brokerId", this.brokerControllerId); - eventData.put("masterEpoch", newMasterEpoch); - eventData.put("timestamp", System.currentTimeMillis()); - eventData.put("affectedTopic", this.brokerController.getTopicConfigManager().getTopicConfigTable().keySet().toArray()); - - Message msg = new Message( - TopicValidator.RMQ_ROUTE_EVENT_TOPIC, - "", - JSON.toJSONString(eventData).getBytes(StandardCharsets.UTF_8) - ); - - msg.putUserProperty("__SYS_FLAG__", "SWITCH"); - msg.putUserProperty("BROKER_NAME", this.brokerConfig.getBrokerName()); - - SendResult sendResult = producer.send(msg); - LOGGER.info("Sent master change event: {}", sendResult); - - } catch (Exception e) { - LOGGER.error("Failed to send master change event", e); - } finally { - producer.shutdown(); - } - } public void changeToMaster(final int newMasterEpoch, final int syncStateSetEpoch, final Set syncStateSet) throws Exception { synchronized (this) { @@ -313,7 +268,8 @@ public void changeToMaster(final int newMasterEpoch, final int syncStateSetEpoch // Notify ha service, change to master this.haService.changeToMaster(newMasterEpoch); - sendRouteChangeEvent(newMasterEpoch); + LOGGER.info("[ROUTE_UPDATE]: publishRouteEvent in {}, {} {}", this.brokerConfig.getBrokerName(), this.brokerConfig.getBrokerId(), this.brokerAddress); + this.brokerController.publishRouteEvent("SWITCH"); this.brokerController.getBrokerConfig().setBrokerId(MixAll.MASTER_ID); this.brokerController.getMessageStoreConfig().setBrokerRole(BrokerRole.SYNC_MASTER); diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/ClusterTopicRouteService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/ClusterTopicRouteService.java index b6d98ddec49..87c35d2047e 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/ClusterTopicRouteService.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/ClusterTopicRouteService.java @@ -18,13 +18,17 @@ import java.util.List; import org.apache.rocketmq.client.impl.mqclient.MQClientAPIFactory; +import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageQueue; +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.common.Address; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; public class ClusterTopicRouteService extends TopicRouteService { private final RouteEventSubscriber eventSubscriber; + private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); public ClusterTopicRouteService(MQClientAPIFactory mqClientAPIFactory) { super(mqClientAPIFactory); @@ -33,7 +37,28 @@ public ClusterTopicRouteService(MQClientAPIFactory mqClientAPIFactory) { markCacheDirty(topic); }); } + @Override + public void start() throws Exception { + super.start(); + + try { + eventSubscriber.start(); + log.info("Route event subscriber started after parent initialization"); + } catch (Exception e) { + log.error("Failed to start route event subscriber", e); + } + } + @Override + public void shutdown() throws Exception { + try { + eventSubscriber.shutdown(); + } catch (Exception e) { + log.error("Error shutting down event subscriber", e); + } + + super.shutdown(); + } @Override public MessageQueueView getCurrentMessageQueueView(ProxyContext ctx, String topicName) throws Exception { return getAllMessageQueueView(ctx, topicName); diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java index 7e8fe8a4e95..dd63192eaf6 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java @@ -16,7 +16,6 @@ */ package org.apache.rocketmq.proxy.service.route; -import java.lang.management.ManagementFactory; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; @@ -26,49 +25,37 @@ import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.exception.MQClientException; +import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.topic.TopicValidator; -import org.apache.rocketmq.proxy.config.ConfigurationManager; -import org.apache.rocketmq.proxy.config.ProxyConfig; - +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import com.alibaba.fastjson2.JSON; public class RouteEventSubscriber { private final Consumer dirtyMarker; private final DefaultMQPushConsumer consumer; + private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); public RouteEventSubscriber(Consumer dirtyMarker) { this.dirtyMarker = dirtyMarker; - this.consumer = createSimplifiedConsumer(); - startListening(); + this.consumer = new DefaultMQPushConsumer("PROXY_ROUTE_EVENT_GROUP"); } - - private DefaultMQPushConsumer createSimplifiedConsumer() { - ProxyConfig config = ConfigurationManager.getProxyConfig(); - - DefaultMQPushConsumer consumer = new DefaultMQPushConsumer( - "ProxyRouteSubGroup_" + ManagementFactory.getRuntimeMXBean().getName() - ); - - consumer.setNamesrvAddr(config.getNamesrvAddr()); - consumer.setPullBatchSize(10); - consumer.setConsumeThreadMin(1); - consumer.setConsumeThreadMax(1); - - return consumer; - } - - private void startListening() { + public void start() { try { consumer.subscribe(TopicValidator.RMQ_ROUTE_EVENT_TOPIC, "*"); + LOGGER.warn("Subscribed to system topic: {}", TopicValidator.RMQ_ROUTE_EVENT_TOPIC); consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { + LOGGER.warn("[ROUTE_UPDATE] Received {} events", msgs.size()); processMessages(msgs); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); consumer.start(); + LOGGER.warn("Route event consumer started"); } catch (MQClientException e) { + LOGGER.error("Failed to start route event consumer", e); } } @@ -77,13 +64,20 @@ private void processMessages(List msgs) { try { String json = new String(msg.getBody(), StandardCharsets.UTF_8); Map event = JSON.parseObject(json, Map.class); + LOGGER.warn("[ROUTE_UPDATE]: Received route event: {}", event); + + List topics = (List) event.get("affectedTopic"); + if (topics == null || topics.isEmpty()) { + LOGGER.warn("[ROUTE_UPDATE] No affected topics in event"); + continue; + } - Object[] topics = (Object[]) event.get("affectedTopic"); - for (Object topicObj : topics) { - String topic = (String) topicObj; + for (String topic : topics) { + LOGGER.warn("[ROUTE_UPDATE] Processing topic: {}", topic); dirtyMarker.accept(topic); } } catch (Exception e) { + LOGGER.error("[ROUTE_UPDATE]: Error processing route event", e); } } } @@ -91,4 +85,4 @@ private void processMessages(List msgs) { public void shutdown() { consumer.shutdown(); } -} \ No newline at end of file +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java index 60d80c70a77..9a1bbb2830c 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java @@ -22,14 +22,15 @@ import java.time.Duration; import java.util.ArrayList; +import java.util.Date; import java.util.List; import java.util.Optional; -import java.util.concurrent.BlockingQueue; +import java.util.Queue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executors; -import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -68,7 +69,7 @@ public abstract class TopicRouteService extends AbstractStartAndShutdown { protected final ThreadPoolExecutor cacheRefreshExecutor; protected final ConcurrentMap dirtyTopics = new ConcurrentHashMap<>(); - protected final BlockingQueue pendingTopics = new LinkedBlockingQueue<>(); + private final Queue pendingTopics = new ConcurrentLinkedQueue<>(); protected final ScheduledExecutorService lazyUpdateExecutor; protected final Object batchUpdateLock = new Object(); @@ -247,33 +248,48 @@ protected MessageQueueView buildMessageQueueView(String topic, TopicRouteData to } protected void markCacheDirty(String topic) { - if (dirtyTopics.putIfAbsent(topic, System.currentTimeMillis()) == null) { + long currentTime = System.currentTimeMillis(); + Long previousTime = dirtyTopics.get(topic); + + if (previousTime == null) { + log.warn("[ROUTE_UPDATE]: markCacheDirty (NEW): {}", topic); + dirtyTopics.put(topic, currentTime); pendingTopics.offer(topic); + } else { + log.info("[ROUTE_UPDATE]: markCacheDirty (EXISTING): {} lastMarked={}", + topic, new Date(previousTime)); } } private void batchRefreshRoutes() { - if (pendingTopics.isEmpty()) return; - - List refreshList = new ArrayList<>(100); - synchronized (batchUpdateLock) { - int count = 0; - while (!pendingTopics.isEmpty() && count < 100) { - String topic = pendingTopics.poll(); - if (topic != null) { - refreshList.add(topic); - count++; - } + int pendingCount = pendingTopics.size(); + if (pendingCount == 0) { + return; + } + + log.warn("[ROUTE_UPDATE] Starting batch refresh for {} topics", pendingCount); + + List refreshList = new ArrayList<>(Math.min(pendingCount, 100)); + int count = 0; + while (!pendingTopics.isEmpty() && count < 100) { + String topic = pendingTopics.poll(); + if (topic != null) { + refreshList.add(topic); + count++; + log.warn("[ROUTE_UPDATE]: Adding to refresh: {}", topic); } } + log.warn("[ROUTE_UPDATE] Will refresh {} topics: {}", refreshList.size(), refreshList); + List> futures = new ArrayList<>(); for (String topic : refreshList) { futures.add(CompletableFuture.runAsync(() -> { try { refreshSingleRoute(topic); + log.warn("[ROUTE_UPDATE]: Refresh topic route success: {}", topic); } catch (Exception e) { - log.warn("Refresh topic route failed: {}", topic, e); + log.warn("[ROUTE_UPDATE]: Refresh topic route failed: {}", topic, e); pendingTopics.offer(topic); } }, cacheRefreshExecutor)); @@ -284,15 +300,27 @@ private void batchRefreshRoutes() { private void refreshSingleRoute(String topic) { try { + log.warn("[ROUTE_UPDATE]: Refreshing route for: {}", topic); + long startTime = System.currentTimeMillis(); + TopicRouteData routeData = mqClientAPIFactory.getClient() .getTopicRouteInfoFromNameServer(topic, 1000); + if (routeData == null) { + log.warn("[ROUTE_UPDATE]: Null route data for topic: {}", topic); + return; + } + MessageQueueView newView = buildMessageQueueView(topic, routeData); topicCache.put(topic, newView); - dirtyTopics.remove(topic); - } catch (Exception e) { + long duration = System.currentTimeMillis() - startTime; + log.warn("[ROUTE_UPDATE]: Refresh success for {} in {}ms.", + topic, duration); + } catch (Exception e) { + log.error("[ROUTE_UPDATE]: Refresh failed for: {}", topic, e); + pendingTopics.offer(topic); } } From 82b607d54f3d94cd84c6f773379cfd7d2127ab87 Mon Sep 17 00:00:00 2001 From: saz97 Date: Thu, 7 Aug 2025 16:23:34 +0800 Subject: [PATCH 05/19] =?UTF-8?q?=E9=87=8D=E6=9E=84Route=20Event=20Notific?= =?UTF-8?q?ation=E4=B8=ADProxy=E7=BC=93=E5=AD=98=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E6=9C=BA=E5=88=B6=E2=80=8B=E2=80=8B=E5=92=8CBroker=E9=80=9A?= =?UTF-8?q?=E7=9F=A5=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ​​Broker 层事件发布重构​​ 1. 移除原有 publishRouteEvent()方法及其硬编码逻辑。 -引入新的 RouteEventService类,专门负责将 Broker 的生命周期事件(暂时是启动和关闭)发布到系统Topic (RMQ_ROUTE_EVENT_TOPIC)。 -用RouteEventType枚举替代原有的。 2.修改 BrokerController: -在启动 (start()) 和关闭 (shutdown()) 流程中调用 RouteEventService发布相应事件。 Proxy 层事件消费与缓存刷新重构​​ 1.移除 ClusterTopicRouteService中直接启动和关闭 RouteEventSubscriber的逻辑(该职责移至基类)。 2.重构 TopicRouteService: -引入 RouteCacheRefresher组件,专门负责管理代理层的路由缓存失效与刷新策略。 -维护broker-topic的映射,用于确定affectedTopic 3.将路由功能收敛到工具类 -RouteEventSubscriber​​仅负责​​订阅 Broker 发布的事件,提取 affectedTopics并通知 RouteCacheRefresher将这些 Topic 标记为“脏数据”。 -RouteCacheRefresher​​负责​​维护脏数据队列、执行异步批量刷新任务(使用专用线程池 cacheRefreshExecutor)、处理刷新超时(超时后重回队列重试)以及更新缓存状态。 -删除原有的 batchRefreshRoutes, refreshSingleRoute以及基于 dirtyTopics/pendingTopics/lazyUpdateExecutor的刷新逻辑,全部由 RouteCacheRefresher接管。 4. 简化 ClusterTopicRouteService实现,仅保留与集群路由获取相关的基类逻辑。 5.​​移除冗余功能​​ -删除 ReplicasManager中试图直接通知客户端连接关于主从切换的逻辑 (buildNotify, activeChannels通知等),其功能已被事件机制替代。 -删除client修改,将修改集中于Proxy修改 --- .../rocketmq/broker/BrokerController.java | 49 +----- .../broker/controller/ReplicasManager.java | 40 ----- .../broker/route/RouteEventConstants.java | 25 +++ .../broker/route/RouteEventService.java | 86 +++++++++++ .../rocketmq/broker/route/RouteEventType.java | 25 +++ .../route/ClusterTopicRouteService.java | 30 ---- .../service/route/RouteCacheRefresher.java | 122 +++++++++++++++ .../service/route/RouteEventSubscriber.java | 22 ++- .../service/route/TopicRouteService.java | 143 ++++++------------ 9 files changed, 332 insertions(+), 210 deletions(-) create mode 100644 broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventConstants.java create mode 100644 broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventService.java create mode 100644 broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventType.java create mode 100644 proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresher.java 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 31f4b217f76..5a8c44b7a7e 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java @@ -16,7 +16,6 @@ */ package org.apache.rocketmq.broker; -import com.alibaba.fastjson2.JSON; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; import org.apache.rocketmq.auth.authentication.factory.AuthenticationFactory; @@ -81,6 +80,8 @@ import org.apache.rocketmq.broker.processor.RecallMessageProcessor; import org.apache.rocketmq.broker.processor.ReplyMessageProcessor; import org.apache.rocketmq.broker.processor.SendMessageProcessor; +import org.apache.rocketmq.broker.route.RouteEventService; +import org.apache.rocketmq.broker.route.RouteEventType; import org.apache.rocketmq.broker.schedule.ScheduleMessageService; import org.apache.rocketmq.broker.slave.SlaveSynchronize; import org.apache.rocketmq.broker.subscription.LmqSubscriptionGroupManager; @@ -111,7 +112,6 @@ import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.message.MessageExtBrokerInner; import org.apache.rocketmq.common.stats.MomentStatsItem; -import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.common.utils.ServiceProvider; import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; @@ -143,7 +143,6 @@ import org.apache.rocketmq.store.MessageArrivingListener; import org.apache.rocketmq.store.MessageStore; import org.apache.rocketmq.store.PutMessageResult; -import org.apache.rocketmq.store.PutMessageStatus; import org.apache.rocketmq.store.RocksDBMessageStore; import org.apache.rocketmq.store.config.BrokerRole; import org.apache.rocketmq.store.config.MessageStoreConfig; @@ -160,12 +159,10 @@ import org.apache.rocketmq.store.timer.TimerMetrics; import java.net.InetSocketAddress; -import java.nio.charset.StandardCharsets; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -305,6 +302,7 @@ public class BrokerController { private TransactionMetricsFlushService transactionMetricsFlushService; private AuthenticationMetadataManager authenticationMetadataManager; private AuthorizationMetadataManager authorizationMetadataManager; + private RouteEventService routeEventService; public BrokerController( final BrokerConfig brokerConfig, @@ -870,6 +868,7 @@ public boolean initialize() throws CloneNotSupportedException { if (!result) { return false; } + this.routeEventService = new RouteEventService(this); return this.recoverAndInitService(); } @@ -1396,12 +1395,10 @@ public ChangeInvisibleTimeProcessor getChangeInvisibleTimeProcessor() { protected void shutdownBasicService() { - publishRouteEvent("shutdown"); - LOG.info("[ROUTE_UPDATE]: publishRouteEvent before shutdown basic service, brokerName: {}, brokerId: {}", - this.brokerConfig.getBrokerName(), this.brokerConfig.getBrokerId()); - shutdown = true; + this.routeEventService.publishEvent(RouteEventType.SHUTDOWN); + this.unregisterBrokerAll(); if (this.shutdownHook != null) { @@ -1619,37 +1616,6 @@ protected void shutdownBasicService() { } } - public void publishRouteEvent(String eventType) { - - Map eventData = new HashMap<>(); - eventData.put("eventType", eventType); - eventData.put("brokerName", this.brokerConfig.getBrokerName()); - eventData.put("brokerId", this.brokerConfig.getBrokerId()); - eventData.put("timestamp", System.currentTimeMillis()); - eventData.put("affectedTopic", this.getTopicConfigManager().getTopicConfigTable().keySet().toArray()); - LOG.info("[ROUTE_UPDATE]: eventData {}", eventData); - - MessageExtBrokerInner innerMsg = new MessageExtBrokerInner(); - innerMsg.setTopic(TopicValidator.RMQ_ROUTE_EVENT_TOPIC); - innerMsg.setBody(JSON.toJSONString(eventData).getBytes(StandardCharsets.UTF_8)); - innerMsg.setTags("broker_event"); - innerMsg.setQueueId(0); - innerMsg.setBornTimestamp(System.currentTimeMillis()); - innerMsg.setBornHost(this.getStoreHost()); - innerMsg.setStoreHost(this.getStoreHost()); - innerMsg.setSysFlag(0); - LOG.info("[ROUTE_UPDATE]: innerMsg {}", innerMsg); - - PutMessageResult result = this.messageStore.putMessage(innerMsg); - this.messageStore.flush(); - - if (result.getPutMessageStatus() == PutMessageStatus.PUT_OK) { - return; - } else { - LOG.error("[ROUTE_UPDATE]: broadcasting failed, status {}", result.getPutMessageStatus()); - } - } - public void shutdown() { shutdownBasicService(); @@ -1871,8 +1837,7 @@ public void run() { } }, 10, 5, TimeUnit.SECONDS); - publishRouteEvent("start"); - + this.routeEventService.publishEvent(RouteEventType.START); } protected void scheduleSendHeartbeat() { diff --git a/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java b/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java index 2f2c7530f7c..f22f22a12bd 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/controller/ReplicasManager.java @@ -43,12 +43,7 @@ import org.apache.rocketmq.common.utils.ThreadUtils; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; -import org.apache.rocketmq.remoting.RemotingServer; -import org.apache.rocketmq.remoting.common.RemotingHelper; import org.apache.rocketmq.remoting.protocol.EpochEntry; -import org.apache.rocketmq.remoting.protocol.LanguageCode; -import org.apache.rocketmq.remoting.protocol.RemotingCommand; -import org.apache.rocketmq.remoting.protocol.RequestCode; import org.apache.rocketmq.remoting.protocol.body.SyncStateSet; import org.apache.rocketmq.remoting.protocol.header.controller.ElectMasterResponseHeader; import org.apache.rocketmq.remoting.protocol.header.controller.GetMetaDataResponseHeader; @@ -61,8 +56,6 @@ import org.apache.rocketmq.store.ha.autoswitch.BrokerMetadata; import org.apache.rocketmq.store.ha.autoswitch.TempBrokerMetadata; -import io.netty.channel.Channel; - import static org.apache.rocketmq.remoting.protocol.ResponseCode.CONTROLLER_BROKER_METADATA_NOT_EXIST; /** @@ -241,7 +234,6 @@ public synchronized void changeBrokerRole(final Long newMasterBrokerId, final St } } - public void changeToMaster(final int newMasterEpoch, final int syncStateSetEpoch, final Set syncStateSet) throws Exception { synchronized (this) { if (newMasterEpoch > this.masterEpoch) { @@ -268,8 +260,6 @@ public void changeToMaster(final int newMasterEpoch, final int syncStateSetEpoch // Notify ha service, change to master this.haService.changeToMaster(newMasterEpoch); - LOGGER.info("[ROUTE_UPDATE]: publishRouteEvent in {}, {} {}", this.brokerConfig.getBrokerName(), this.brokerConfig.getBrokerId(), this.brokerAddress); - this.brokerController.publishRouteEvent("SWITCH"); this.brokerController.getBrokerConfig().setBrokerId(MixAll.MASTER_ID); this.brokerController.getMessageStoreConfig().setBrokerRole(BrokerRole.SYNC_MASTER); @@ -288,20 +278,6 @@ public void changeToMaster(final int newMasterEpoch, final int syncStateSetEpoch } } - private RemotingCommand buildNotify(String newMasterAddress) { - RemotingCommand switchNotify = RemotingCommand.createRequestCommand( - RequestCode.ROUTE_EVENT, - null - ); - - switchNotify.addExtField("newMasterAddr", newMasterAddress); - switchNotify.addExtField("oldMasterAddr", this.brokerAddress); - switchNotify.addExtField("Event", "SWITCH"); - switchNotify.setLanguage(LanguageCode.JAVA); - - return switchNotify; - } - public void changeToSlave(final String newMasterAddress, final int newMasterEpoch, Long newMasterBrokerId) { synchronized (this) { if (newMasterEpoch > this.masterEpoch) { @@ -336,22 +312,6 @@ public void changeToSlave(final String newMasterAddress, final int newMasterEpoc // Notify ha service, change to slave this.haService.changeToSlave(newMasterAddress, newMasterEpoch, brokerControllerId); - RemotingServer remotingServer = brokerController.getRemotingServer(); - - Set activeChannels = remotingServer.getActiveChannels(); - - RemotingCommand switchNotify = buildNotify(newMasterAddress); - - activeChannels.forEach(channel -> { - if (channel.isActive()) { - try { - remotingServer.invokeOneway(channel, switchNotify, 3000); - } catch (Exception e) { - LOGGER.warn("Notify client failed: {}", RemotingHelper.parseChannelRemoteAddr(channel), e); - } - } - }); - this.brokerController.getTopicConfigManager().getDataVersion().nextVersion(newMasterEpoch); registerBrokerWhenRoleChange(); } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventConstants.java b/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventConstants.java new file mode 100644 index 00000000000..d98cf12f479 --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventConstants.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.rocketmq.broker.route; + +public class RouteEventConstants { + public static final String EVENT_TYPE = "eventType"; + public static final String BROKER_NAME = "brokerName"; + public static final String BROKER_ID = "brokerId"; + public static final String TIMESTAMP = "timestamp"; +} \ No newline at end of file diff --git a/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventService.java b/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventService.java new file mode 100644 index 00000000000..bf1d0c1bf3c --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventService.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.broker.route; + +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +import org.apache.rocketmq.broker.BrokerController; +import org.apache.rocketmq.common.constant.LoggerName; +import org.apache.rocketmq.common.message.MessageExtBrokerInner; +import org.apache.rocketmq.common.topic.TopicValidator; +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; +import org.apache.rocketmq.store.PutMessageResult; + +import com.alibaba.fastjson2.JSON; + +public class RouteEventService { + private static final Logger LOG = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); + private final BrokerController brokerController; + + public RouteEventService(BrokerController brokerController) { + this.brokerController = brokerController; + LOG.info("RouteEventService initialized for broker: {}", + brokerController.getBrokerConfig().getBrokerName()); + } + + public void publishEvent(RouteEventType eventType) { + Map eventData = new HashMap<>(); + PutMessageResult result; + eventData.put(RouteEventConstants.EVENT_TYPE, eventType.name()); + eventData.put(RouteEventConstants.BROKER_NAME, brokerController.getBrokerConfig().getBrokerName()); + eventData.put(RouteEventConstants.BROKER_ID, brokerController.getBrokerConfig().getBrokerId()); + eventData.put(RouteEventConstants.TIMESTAMP, System.currentTimeMillis()); + + LOG.info("[ROUTE_EVENT] Preparing to publish {} event with data: {}", + eventType, JSON.toJSONString(eventData)); + MessageExtBrokerInner msg = createEventMessage(eventData); + LOG.info("[ROUTE_EVENT] Created event message: {}", msg); + try { + result = brokerController.getMessageStore().putMessage(msg); + } + catch (Exception e) { + LOG.error("[ROUTE_EVENT] Failed to publish event: {}", eventType, e); + return; + } + brokerController.getMessageStore().flush(); + LOG.info("[ROUTE_EVENT] Flushed message store after publishing event: {}", eventType); + + if (result.isOk()) { + LOG.info("[ROUTE_EVENT] Published {} event for {}", + eventType, eventData.get(RouteEventConstants.BROKER_NAME)); + } else { + LOG.error("[ROUTE_EVENT] Publish failed: {}", result.getPutMessageStatus()); + } + } + + private MessageExtBrokerInner createEventMessage(Map eventData) { + MessageExtBrokerInner msg = new MessageExtBrokerInner(); + msg.setTopic(TopicValidator.RMQ_ROUTE_EVENT_TOPIC); + msg.setBody(JSON.toJSONString(eventData).getBytes(StandardCharsets.UTF_8)); + msg.setTags(eventData.get(RouteEventConstants.EVENT_TYPE).toString()); + msg.setQueueId(0); + msg.setBornTimestamp(System.currentTimeMillis()); + msg.setBornHost(brokerController.getStoreHost()); + msg.setStoreHost(brokerController.getStoreHost()); + msg.setSysFlag(0); + + return msg; + } +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventType.java b/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventType.java new file mode 100644 index 00000000000..d7e4c9fcd6b --- /dev/null +++ b/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventType.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.rocketmq.broker.route; + +public enum RouteEventType { + START, + SHUTDOWN, + SWITCH, + TOPIC_CHANGE +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/ClusterTopicRouteService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/ClusterTopicRouteService.java index 87c35d2047e..a4df98971cb 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/ClusterTopicRouteService.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/ClusterTopicRouteService.java @@ -18,47 +18,17 @@ import java.util.List; import org.apache.rocketmq.client.impl.mqclient.MQClientAPIFactory; -import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageQueue; -import org.apache.rocketmq.logging.org.slf4j.Logger; -import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; import org.apache.rocketmq.proxy.common.Address; import org.apache.rocketmq.proxy.common.ProxyContext; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; public class ClusterTopicRouteService extends TopicRouteService { - private final RouteEventSubscriber eventSubscriber; - private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); public ClusterTopicRouteService(MQClientAPIFactory mqClientAPIFactory) { super(mqClientAPIFactory); - - this.eventSubscriber = new RouteEventSubscriber(topic -> { - markCacheDirty(topic); - }); - } - @Override - public void start() throws Exception { - super.start(); - - try { - eventSubscriber.start(); - log.info("Route event subscriber started after parent initialization"); - } catch (Exception e) { - log.error("Failed to start route event subscriber", e); - } } - @Override - public void shutdown() throws Exception { - try { - eventSubscriber.shutdown(); - } catch (Exception e) { - log.error("Error shutting down event subscriber", e); - } - - super.shutdown(); - } @Override public MessageQueueView getCurrentMessageQueueView(ProxyContext ctx, String topicName) throws Exception { return getAllMessageQueueView(ctx, topicName); diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresher.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresher.java new file mode 100644 index 00000000000..5e739a16a8d --- /dev/null +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresher.java @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.rocketmq.proxy.service.route; + +import org.apache.rocketmq.common.constant.LoggerName; +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; +import org.apache.rocketmq.common.ThreadFactoryImpl; + +import com.github.benmanes.caffeine.cache.LoadingCache; +import org.apache.rocketmq.common.utils.ThreadUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map.Entry; +import java.util.Queue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public class RouteCacheRefresher { + private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); + + private final LoadingCache topicCache; + private final ThreadPoolExecutor executor; + + private final ConcurrentMap dirtyTopics = new ConcurrentHashMap<>(); + private final Queue pendingTopics = new ConcurrentLinkedQueue<>(); + private final ScheduledExecutorService scheduler; + + private final ConcurrentMap refreshingTopics = new ConcurrentHashMap<>(); + + public RouteCacheRefresher(LoadingCache topicCache, + ThreadPoolExecutor executor) { + this.topicCache = topicCache; + this.executor = executor; + + this.scheduler = ThreadUtils.newSingleThreadScheduledExecutor( + new ThreadFactoryImpl("RouteCacheScheduler_") + ); + } + + public void start() { + scheduler.scheduleWithFixedDelay(this::checkRefreshStatus, 1, 1, TimeUnit.SECONDS); + + scheduler.scheduleWithFixedDelay(this::processDirtyTopics, 50, 200, TimeUnit.MILLISECONDS); + } + + public void markCacheDirty(String topic) { + dirtyTopics.put(topic, System.currentTimeMillis()); + pendingTopics.offer(topic); + } + + private void processDirtyTopics() { + List batch = new ArrayList<>(); + while (!pendingTopics.isEmpty() && batch.size() < 100) { + batch.add(pendingTopics.poll()); + } + + for (String topic : batch) { + executor.execute(() -> refreshSingleRoute(topic)); + } + } + + private void refreshSingleRoute(String topic) { + try { + log.info("Refreshing route for: {}", topic); + refreshingTopics.put(topic, System.currentTimeMillis()); + topicCache.refresh(topic); + + } catch (Exception e) { + log.error("Refresh failed for: {}", topic, e); + pendingTopics.offer(topic); + } + } + + private void checkRefreshStatus() { + long currentTime = System.currentTimeMillis(); + List completed = new ArrayList<>(); + + for (Entry entry : refreshingTopics.entrySet()) { + String topic = entry.getKey(); + long startTime = entry.getValue(); + + if (currentTime - startTime > 5000) { + log.warn("Refresh timeout for topic: {}", topic); + completed.add(topic); + pendingTopics.offer(topic); + } + + if (topicCache.getIfPresent(topic) != null) { + completed.add(topic); + dirtyTopics.remove(topic); + log.info("Refresh confirmed for topic: {}", topic); + } + } + + completed.forEach(refreshingTopics::remove); + } + + public void shutdown() { + scheduler.shutdown(); + } +} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java index dd63192eaf6..06e40912f4f 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java @@ -19,8 +19,11 @@ import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.function.Consumer; +import org.apache.rocketmq.broker.route.RouteEventConstants; +import org.apache.rocketmq.broker.route.RouteEventType; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; @@ -30,16 +33,21 @@ import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; +import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; + import com.alibaba.fastjson2.JSON; public class RouteEventSubscriber { private final Consumer dirtyMarker; + private final TopicRouteService topicRouteService; private final DefaultMQPushConsumer consumer; private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); - public RouteEventSubscriber(Consumer dirtyMarker) { + public RouteEventSubscriber(TopicRouteService topicRouteService, Consumer dirtyMarker) { + this.topicRouteService = topicRouteService; this.dirtyMarker = dirtyMarker; this.consumer = new DefaultMQPushConsumer("PROXY_ROUTE_EVENT_GROUP"); + this.consumer.setMessageModel(MessageModel.BROADCASTING); } public void start() { try { @@ -64,14 +72,22 @@ private void processMessages(List msgs) { try { String json = new String(msg.getBody(), StandardCharsets.UTF_8); Map event = JSON.parseObject(json, Map.class); - LOGGER.warn("[ROUTE_UPDATE]: Received route event: {}", event); + LOGGER.info("[ROUTE_UPDATE]: Received route event: {} consumer {}", event, this.consumer.getInstanceName()); + + String brokerName = (String) event.get(RouteEventConstants.BROKER_NAME); + RouteEventType eventType = RouteEventType.valueOf((String) event.get(RouteEventConstants.EVENT_TYPE)); + + Set topics = this.topicRouteService.getBrokerTopics(brokerName); - List topics = (List) event.get("affectedTopic"); if (topics == null || topics.isEmpty()) { LOGGER.warn("[ROUTE_UPDATE] No affected topics in event"); continue; } + if (eventType == RouteEventType.SHUTDOWN) { + topicRouteService.removeBrokerTopics(brokerName); + } + for (String topic : topics) { LOGGER.warn("[ROUTE_UPDATE] Processing topic: {}", topic); dirtyMarker.accept(topic); diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java index 9a1bbb2830c..5e47a671ff9 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java @@ -21,19 +21,16 @@ import com.github.benmanes.caffeine.cache.LoadingCache; import java.time.Duration; -import java.util.ArrayList; -import java.util.Date; import java.util.List; import java.util.Optional; -import java.util.Queue; -import java.util.concurrent.CompletableFuture; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.impl.mqclient.MQClientAPIFactory; @@ -54,6 +51,7 @@ import org.apache.rocketmq.proxy.config.ProxyConfig; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.GetMaxOffsetRequestHeader; +import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -64,15 +62,14 @@ public abstract class TopicRouteService extends AbstractStartAndShutdown { private final MQClientAPIFactory mqClientAPIFactory; private MQFaultStrategy mqFaultStrategy; + private final RouteEventSubscriber routeEventSubscriber; + private final RouteCacheRefresher routeCacheRefresher; + private final ConcurrentMap> brokerTopicsMap = new ConcurrentHashMap<>(); + protected final LoadingCache topicCache; protected final ScheduledExecutorService scheduledExecutorService; protected final ThreadPoolExecutor cacheRefreshExecutor; - protected final ConcurrentMap dirtyTopics = new ConcurrentHashMap<>(); - private final Queue pendingTopics = new ConcurrentLinkedQueue<>(); - protected final ScheduledExecutorService lazyUpdateExecutor; - protected final Object batchUpdateLock = new Object(); - public TopicRouteService(MQClientAPIFactory mqClientAPIFactory) { ProxyConfig config = ConfigurationManager.getProxyConfig(); @@ -98,6 +95,9 @@ public TopicRouteService(MQClientAPIFactory mqClientAPIFactory) { public @Nullable MessageQueueView load(String topic) throws Exception { try { TopicRouteData topicRouteData = mqClientAPIFactory.getClient().getTopicRouteInfoFromNameServer(topic, Duration.ofSeconds(3).toMillis()); + log.info("[Route_Event]:load topic route from namesrv. topic: {}", topic); + updateBrokerTopicMapping(topic, topicRouteData); + return buildMessageQueueView(topic, topicRouteData); } catch (Exception e) { if (TopicRouteHelper.isTopicNotExistError(e)) { @@ -147,16 +147,43 @@ public String resolve(String name) { } } }, serviceDetector); - this.lazyUpdateExecutor = Executors.newSingleThreadScheduledExecutor( - new ThreadFactoryImpl("RouteLazyUpdate_")); - - lazyUpdateExecutor.scheduleWithFixedDelay(() -> { - batchRefreshRoutes(); - }, 50, 200, TimeUnit.MILLISECONDS); + this.routeCacheRefresher = new RouteCacheRefresher( + this.topicCache, + this.cacheRefreshExecutor + ); + this.routeEventSubscriber = new RouteEventSubscriber( + this, + topic -> { + this.routeCacheRefresher.markCacheDirty(topic); + } + ); this.init(); } + private void updateBrokerTopicMapping(String topic, TopicRouteData topicRouteData) { + Set brokerNames = topicRouteData.getBrokerDatas().stream() + .map(BrokerData::getBrokerName) + .collect(Collectors.toSet()); + log.info("[Route_Event]: Update broker topics mapping for topic: {}, brokers: {}", topic, brokerNames); + + for (String brokerName : brokerNames) { + brokerTopicsMap.computeIfAbsent(brokerName, k -> ConcurrentHashMap.newKeySet()) + .add(topic); + } + } + + public Set getBrokerTopics(String brokerName) { + return brokerTopicsMap.getOrDefault(brokerName, Set.of()); + } + + public void removeBrokerTopics(String brokerName) { + Set topics = brokerTopicsMap.remove(brokerName); + if (topics != null) { + log.info("[Route_Event]: Removed {} topics for broker: {}", topics.size(), brokerName); + } + } + // pickup one topic in the topic cache private Optional pickTopic() { if (topicCache.asMap().isEmpty()) { @@ -175,6 +202,8 @@ public void shutdown() throws Exception { if (this.mqFaultStrategy.isStartDetectorEnable()) { mqFaultStrategy.shutdown(); } + this.routeCacheRefresher.shutdown(); + this.routeEventSubscriber.shutdown(); } @Override @@ -182,6 +211,8 @@ public void start() throws Exception { if (this.mqFaultStrategy.isStartDetectorEnable()) { this.mqFaultStrategy.startDetector(); } + this.routeEventSubscriber.start(); + this.routeCacheRefresher.start(); } public ClientConfig extractClientConfigFromProxyConfig(ProxyConfig proxyConfig) { @@ -246,82 +277,4 @@ protected MessageQueueView buildMessageQueueView(String topic, TopicRouteData to } return MessageQueueView.WRAPPED_EMPTY_QUEUE; } - - protected void markCacheDirty(String topic) { - long currentTime = System.currentTimeMillis(); - Long previousTime = dirtyTopics.get(topic); - - if (previousTime == null) { - log.warn("[ROUTE_UPDATE]: markCacheDirty (NEW): {}", topic); - dirtyTopics.put(topic, currentTime); - pendingTopics.offer(topic); - } else { - log.info("[ROUTE_UPDATE]: markCacheDirty (EXISTING): {} lastMarked={}", - topic, new Date(previousTime)); - } - } - - private void batchRefreshRoutes() { - int pendingCount = pendingTopics.size(); - if (pendingCount == 0) { - return; - } - - log.warn("[ROUTE_UPDATE] Starting batch refresh for {} topics", pendingCount); - - List refreshList = new ArrayList<>(Math.min(pendingCount, 100)); - int count = 0; - while (!pendingTopics.isEmpty() && count < 100) { - String topic = pendingTopics.poll(); - if (topic != null) { - refreshList.add(topic); - count++; - log.warn("[ROUTE_UPDATE]: Adding to refresh: {}", topic); - } - } - - log.warn("[ROUTE_UPDATE] Will refresh {} topics: {}", refreshList.size(), refreshList); - - List> futures = new ArrayList<>(); - for (String topic : refreshList) { - futures.add(CompletableFuture.runAsync(() -> { - try { - refreshSingleRoute(topic); - log.warn("[ROUTE_UPDATE]: Refresh topic route success: {}", topic); - } catch (Exception e) { - log.warn("[ROUTE_UPDATE]: Refresh topic route failed: {}", topic, e); - pendingTopics.offer(topic); - } - }, cacheRefreshExecutor)); - } - - CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); - } - - private void refreshSingleRoute(String topic) { - try { - log.warn("[ROUTE_UPDATE]: Refreshing route for: {}", topic); - long startTime = System.currentTimeMillis(); - - TopicRouteData routeData = mqClientAPIFactory.getClient() - .getTopicRouteInfoFromNameServer(topic, 1000); - - if (routeData == null) { - log.warn("[ROUTE_UPDATE]: Null route data for topic: {}", topic); - return; - } - - MessageQueueView newView = buildMessageQueueView(topic, routeData); - topicCache.put(topic, newView); - dirtyTopics.remove(topic); - - long duration = System.currentTimeMillis() - startTime; - log.warn("[ROUTE_UPDATE]: Refresh success for {} in {}ms.", - topic, duration); - } catch (Exception e) { - log.error("[ROUTE_UPDATE]: Refresh failed for: {}", topic, e); - pendingTopics.offer(topic); - } - } - } From fd9cf1025ed236aa04a8ae33f14669f7e8aa9e4f Mon Sep 17 00:00:00 2001 From: saz97 Date: Wed, 13 Aug 2025 14:47:23 +0800 Subject: [PATCH 06/19] =?UTF-8?q?1.=20=E5=88=A0=E5=8E=BBclient=E5=92=8Crem?= =?UTF-8?q?oting=E7=9A=84=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/impl/ClientRemotingProcessor.java | 11 ---------- .../rocketmq/client/impl/MQClientAPIImpl.java | 2 -- .../service/route/RouteCacheRefresher.java | 4 ++-- .../service/route/RouteEventSubscriber.java | 4 ++-- .../rocketmq/remoting/RemotingServer.java | 4 ---- .../remoting/netty/NettyRemotingServer.java | 21 ------------------- .../remoting/protocol/RequestCode.java | 2 -- 7 files changed, 4 insertions(+), 44 deletions(-) diff --git a/client/src/main/java/org/apache/rocketmq/client/impl/ClientRemotingProcessor.java b/client/src/main/java/org/apache/rocketmq/client/impl/ClientRemotingProcessor.java index 2c40cc7987e..e46c651f928 100644 --- a/client/src/main/java/org/apache/rocketmq/client/impl/ClientRemotingProcessor.java +++ b/client/src/main/java/org/apache/rocketmq/client/impl/ClientRemotingProcessor.java @@ -86,10 +86,6 @@ public RemotingCommand processRequest(ChannelHandlerContext ctx, case RequestCode.PUSH_REPLY_MESSAGE_TO_CLIENT: return this.receiveReplyMessage(ctx, request); - - case RequestCode.ROUTE_EVENT: - return this.processRouteEventNotify(request, ctx); - default: break; } @@ -296,11 +292,4 @@ private void processReplyMessage(MessageExt replyMsg) { correlationId, bornHost); } } - - private RemotingCommand processRouteEventNotify(RemotingCommand request, ChannelHandlerContext ctx) { - this.mqClientFactory.updateTopicRouteInfoFromNameServer(); - return RemotingCommand.createResponseCommand(null); - - } - } 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 fa132adb4ad..c001b33fa98 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 @@ -322,8 +322,6 @@ public MQClientAPIImpl(final NettyClientConfig nettyClientConfig, this.remotingClient.registerProcessor(RequestCode.CONSUME_MESSAGE_DIRECTLY, this.clientRemotingProcessor, null); this.remotingClient.registerProcessor(RequestCode.PUSH_REPLY_MESSAGE_TO_CLIENT, this.clientRemotingProcessor, null); - - this.remotingClient.registerProcessor(RequestCode.ROUTE_EVENT, this.clientRemotingProcessor, null); } public List getNameServerAddressList() { diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresher.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresher.java index 5e739a16a8d..ca03d87be61 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresher.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresher.java @@ -47,7 +47,7 @@ public class RouteCacheRefresher { private final ScheduledExecutorService scheduler; private final ConcurrentMap refreshingTopics = new ConcurrentHashMap<>(); - + public RouteCacheRefresher(LoadingCache topicCache, ThreadPoolExecutor executor) { this.topicCache = topicCache; @@ -79,7 +79,7 @@ private void processDirtyTopics() { executor.execute(() -> refreshSingleRoute(topic)); } } - + private void refreshSingleRoute(String topic) { try { log.info("Refreshing route for: {}", topic); diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java index 06e40912f4f..ef1ba4979a8 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java @@ -39,7 +39,7 @@ public class RouteEventSubscriber { private final Consumer dirtyMarker; - private final TopicRouteService topicRouteService; + private final TopicRouteService topicRouteService; private final DefaultMQPushConsumer consumer; private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); @@ -85,7 +85,7 @@ private void processMessages(List msgs) { } if (eventType == RouteEventType.SHUTDOWN) { - topicRouteService.removeBrokerTopics(brokerName); + topicRouteService.removeBrokerTopics(brokerName); } for (String topic : topics) { diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/RemotingServer.java b/remoting/src/main/java/org/apache/rocketmq/remoting/RemotingServer.java index 64c62e6b600..8cfa1e1a083 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/RemotingServer.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/RemotingServer.java @@ -17,8 +17,6 @@ package org.apache.rocketmq.remoting; import io.netty.channel.Channel; - -import java.util.Set; import java.util.concurrent.ExecutorService; import org.apache.rocketmq.common.Pair; import org.apache.rocketmq.remoting.exception.RemotingSendRequestException; @@ -38,8 +36,6 @@ void registerProcessor(final int requestCode, final NettyRequestProcessor proces Pair getProcessorPair(final int requestCode); - Set getActiveChannels(); - Pair getDefaultProcessorPair(); RemotingServer newRemotingServer(int port); diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java index dac31980ae6..d56d6faa336 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingServer.java @@ -79,11 +79,8 @@ import java.net.InetSocketAddress; import java.security.cert.CertificateException; import java.time.Duration; -import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.NoSuchElementException; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutorService; @@ -109,10 +106,6 @@ public class NettyRemotingServer extends NettyRemotingAbstract implements Remoti private DefaultEventExecutorGroup defaultEventExecutorGroup; - private final Set activeChannels = Collections.newSetFromMap( - new ConcurrentHashMap() - ); - /** * NettyRemotingServer may hold multiple SubRemotingServer, each server will be stored in this container with a * ListenPort key. @@ -420,11 +413,6 @@ public ExecutorService getCallbackExecutor() { return this.publicExecutor; } - @Override - public Set getActiveChannels() { - return new HashSet<>(activeChannels); - } - private void printRemotingCodeDistribution() { if (distributionHandler != null) { String inBoundSnapshotString = distributionHandler.getInBoundSnapshotString(); @@ -606,8 +594,6 @@ public void channelActive(ChannelHandlerContext ctx) throws Exception { log.info("NETTY SERVER PIPELINE: channelActive, the channel[{}]", remoteAddress); super.channelActive(ctx); - NettyRemotingServer.this.activeChannels.add(ctx.channel()); - if (NettyRemotingServer.this.channelEventListener != null) { NettyRemotingServer.this.putNettyEvent(new NettyEvent(NettyEventType.CONNECT, remoteAddress, ctx.channel())); } @@ -619,8 +605,6 @@ public void channelInactive(ChannelHandlerContext ctx) throws Exception { log.info("NETTY SERVER PIPELINE: channelInactive, the channel[{}]", remoteAddress); super.channelInactive(ctx); - NettyRemotingServer.this.activeChannels.remove(ctx.channel()); - if (NettyRemotingServer.this.channelEventListener != null) { NettyRemotingServer.this.putNettyEvent(new NettyEvent(NettyEventType.CLOSE, remoteAddress, ctx.channel())); } @@ -699,11 +683,6 @@ public Pair getProcessorPair(final int r return this.processorTable.get(requestCode); } - @Override - public Set getActiveChannels() { - return null; - } - @Override public Pair getDefaultProcessorPair() { return this.defaultRequestProcessorPair; diff --git a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RequestCode.java b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RequestCode.java index b8b0a7e87fa..8b2749eaae2 100644 --- a/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RequestCode.java +++ b/remoting/src/main/java/org/apache/rocketmq/remoting/protocol/RequestCode.java @@ -51,8 +51,6 @@ public class RequestCode { public static final int HEART_BEAT = 34; - public static final int ROUTE_EVENT = 299; - public static final int UNREGISTER_CLIENT = 35; public static final int CONSUMER_SEND_MSG_BACK = 36; From b5573eb013bff3048008a1d755562baed85dc41a Mon Sep 17 00:00:00 2001 From: saz97 Date: Tue, 19 Aug 2025 10:59:30 +0800 Subject: [PATCH 07/19] =?UTF-8?q?=E6=96=B0=E5=A2=9ETopic=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E4=BB=A5=E5=8F=8A=E6=97=B6=E6=95=88=E6=80=A7?= =?UTF-8?q?=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1.在Broker端,当Topic配置发生变化: -通过RouteEventService发布TOPIC_CHANGE事件 -事件携带受影响的主题名称(AFFECTED_TOPIC) 2.Proxy端重构逻辑: -增强维护brokerToTopics映射关系 3.增强时效性检查: -消息携带时间戳字段 -暂定延迟超过1分钟的事件将被丢弃,避免处理陈旧事件造成的无效刷新 4.完善路由刷新机制: -刷新前检查topicCache是否存在对应主题 -无缓存主题时跳过刷新操作 --- .../rocketmq/broker/BrokerController.java | 4 ++ .../broker/route/RouteEventConstants.java | 1 + .../broker/route/RouteEventService.java | 17 +++---- .../broker/topic/TopicConfigManager.java | 5 ++ .../service/route/RouteCacheRefresher.java | 14 +++++- .../service/route/RouteEventSubscriber.java | 47 ++++++++++++------- .../service/route/TopicRouteService.java | 39 ++++++++++----- 7 files changed, 86 insertions(+), 41 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 5a8c44b7a7e..5ce9aff7f63 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java @@ -1301,6 +1301,10 @@ public void setMessageStore(MessageStore messageStore) { this.messageStore = messageStore; } + public RouteEventService getRouteEventService() { + return routeEventService; + } + protected void printMasterAndSlaveDiff() { if (messageStore.getHaService() != null && messageStore.getHaService().getConnectionCount().get() > 0) { long diff = this.messageStore.slaveFallBehindMuch(); diff --git a/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventConstants.java b/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventConstants.java index d98cf12f479..58edaacedfd 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventConstants.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventConstants.java @@ -22,4 +22,5 @@ public class RouteEventConstants { public static final String BROKER_NAME = "brokerName"; public static final String BROKER_ID = "brokerId"; public static final String TIMESTAMP = "timestamp"; + public static final String AFFECTED_TOPIC = "affectedTopic"; } \ No newline at end of file diff --git a/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventService.java b/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventService.java index bf1d0c1bf3c..68819a384f7 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventService.java @@ -41,17 +41,22 @@ public RouteEventService(BrokerController brokerController) { } public void publishEvent(RouteEventType eventType) { + publishEvent(eventType, null); + } + + public void publishEvent(RouteEventType eventType, String affectedtopic) { Map eventData = new HashMap<>(); PutMessageResult result; eventData.put(RouteEventConstants.EVENT_TYPE, eventType.name()); eventData.put(RouteEventConstants.BROKER_NAME, brokerController.getBrokerConfig().getBrokerName()); eventData.put(RouteEventConstants.BROKER_ID, brokerController.getBrokerConfig().getBrokerId()); eventData.put(RouteEventConstants.TIMESTAMP, System.currentTimeMillis()); + if (affectedtopic != null) { + eventData.put(RouteEventConstants.AFFECTED_TOPIC, affectedtopic); + } - LOG.info("[ROUTE_EVENT] Preparing to publish {} event with data: {}", - eventType, JSON.toJSONString(eventData)); MessageExtBrokerInner msg = createEventMessage(eventData); - LOG.info("[ROUTE_EVENT] Created event message: {}", msg); + try { result = brokerController.getMessageStore().putMessage(msg); } @@ -60,12 +65,8 @@ public void publishEvent(RouteEventType eventType) { return; } brokerController.getMessageStore().flush(); - LOG.info("[ROUTE_EVENT] Flushed message store after publishing event: {}", eventType); - if (result.isOk()) { - LOG.info("[ROUTE_EVENT] Published {} event for {}", - eventType, eventData.get(RouteEventConstants.BROKER_NAME)); - } else { + if (!result.isOk()) { LOG.error("[ROUTE_EVENT] Publish failed: {}", result.getPutMessageStatus()); } } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java b/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java index 79c233d8f22..90c0bcb5dd7 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java @@ -34,6 +34,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.BrokerPathConfigHelper; +import org.apache.rocketmq.broker.route.RouteEventType; import org.apache.rocketmq.common.ConfigManager; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.common.PopAckConstants; @@ -236,6 +237,9 @@ protected void init() { } public TopicConfig putTopicConfig(TopicConfig topicConfig) { + if (!TopicValidator.isSystemTopic(topicConfig.getTopicName())) { + this.brokerController.getRouteEventService().publishEvent(RouteEventType.TOPIC_CHANGE, topicConfig.getTopicName()); + } return this.topicConfigTable.put(topicConfig.getTopicName(), topicConfig); } @@ -244,6 +248,7 @@ protected TopicConfig getTopicConfig(String topicName) { } protected TopicConfig removeTopicConfig(String topicName) { + this.brokerController.getRouteEventService().publishEvent(RouteEventType.TOPIC_CHANGE, topicName); return this.topicConfigTable.remove(topicName); } diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresher.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresher.java index ca03d87be61..4ed5b9632fa 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresher.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresher.java @@ -64,8 +64,13 @@ public void start() { scheduler.scheduleWithFixedDelay(this::processDirtyTopics, 50, 200, TimeUnit.MILLISECONDS); } - public void markCacheDirty(String topic) { - dirtyTopics.put(topic, System.currentTimeMillis()); + public void markCacheDirty(String topic, long timeStamp) { + long currentTime = System.currentTimeMillis(); + if (currentTime - timeStamp > TimeUnit.MINUTES.toMillis(1)) { + return; + } + + dirtyTopics.put(topic, currentTime); pendingTopics.offer(topic); } @@ -83,6 +88,11 @@ private void processDirtyTopics() { private void refreshSingleRoute(String topic) { try { log.info("Refreshing route for: {}", topic); + if (topicCache.getIfPresent(topic) == null) { + log.warn("No cache entry found for topic: {}", topic); + return; + } + refreshingTopics.put(topic, System.currentTimeMillis()); topicCache.refresh(topic); diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java index ef1ba4979a8..1f9efd57097 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java @@ -20,7 +20,7 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.function.Consumer; +import java.util.function.BiConsumer; import org.apache.rocketmq.broker.route.RouteEventConstants; import org.apache.rocketmq.broker.route.RouteEventType; @@ -38,12 +38,12 @@ import com.alibaba.fastjson2.JSON; public class RouteEventSubscriber { - private final Consumer dirtyMarker; + private final BiConsumer dirtyMarker; private final TopicRouteService topicRouteService; private final DefaultMQPushConsumer consumer; private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); - public RouteEventSubscriber(TopicRouteService topicRouteService, Consumer dirtyMarker) { + public RouteEventSubscriber(TopicRouteService topicRouteService, BiConsumer dirtyMarker) { this.topicRouteService = topicRouteService; this.dirtyMarker = dirtyMarker; this.consumer = new DefaultMQPushConsumer("PROXY_ROUTE_EVENT_GROUP"); @@ -52,16 +52,12 @@ public RouteEventSubscriber(TopicRouteService topicRouteService, Consumer { - LOGGER.warn("[ROUTE_UPDATE] Received {} events", msgs.size()); processMessages(msgs); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); consumer.start(); - LOGGER.warn("Route event consumer started"); } catch (MQClientException e) { LOGGER.error("Failed to start route event consumer", e); } @@ -76,22 +72,37 @@ private void processMessages(List msgs) { String brokerName = (String) event.get(RouteEventConstants.BROKER_NAME); RouteEventType eventType = RouteEventType.valueOf((String) event.get(RouteEventConstants.EVENT_TYPE)); + Long eventTimeStamp = (Long) event.get(RouteEventConstants.TIMESTAMP); - Set topics = this.topicRouteService.getBrokerTopics(brokerName); - if (topics == null || topics.isEmpty()) { - LOGGER.warn("[ROUTE_UPDATE] No affected topics in event"); - continue; - } + switch (eventType) { + case SHUTDOWN: + Set topics = this.topicRouteService.getBrokerTopics(brokerName); + this.topicRouteService.removeBrokerToTopics(brokerName); - if (eventType == RouteEventType.SHUTDOWN) { - topicRouteService.removeBrokerTopics(brokerName); - } + for (String topic : topics) { + LOGGER.info("[ROUTE_UPDATE] Processing topic: {}", topic); + dirtyMarker.accept(topic, eventTimeStamp); + } + break; + + case TOPIC_CHANGE: + String affectedTopic = (String) event.get(RouteEventConstants.AFFECTED_TOPIC); - for (String topic : topics) { - LOGGER.warn("[ROUTE_UPDATE] Processing topic: {}", topic); - dirtyMarker.accept(topic); + if (affectedTopic != null) { + LOGGER.info("[ROUTE_UPDATE] Affected topic: {}", affectedTopic); + this.topicRouteService.removeBrokerToTopic(brokerName, affectedTopic); + + dirtyMarker.accept(affectedTopic, eventTimeStamp); + } else { + LOGGER.info("[ROUTE_UPDATE] No affected topic specified in event: {}", event); + } + break; + + default: + break; } + } catch (Exception e) { LOGGER.error("[ROUTE_UPDATE]: Error processing route event", e); } diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java index 5e47a671ff9..dc7f45b1d52 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java @@ -64,7 +64,7 @@ public abstract class TopicRouteService extends AbstractStartAndShutdown { private final RouteEventSubscriber routeEventSubscriber; private final RouteCacheRefresher routeCacheRefresher; - private final ConcurrentMap> brokerTopicsMap = new ConcurrentHashMap<>(); + private final ConcurrentMap> brokerToTopics = new ConcurrentHashMap<>(); protected final LoadingCache topicCache; protected final ScheduledExecutorService scheduledExecutorService; @@ -95,7 +95,6 @@ public TopicRouteService(MQClientAPIFactory mqClientAPIFactory) { public @Nullable MessageQueueView load(String topic) throws Exception { try { TopicRouteData topicRouteData = mqClientAPIFactory.getClient().getTopicRouteInfoFromNameServer(topic, Duration.ofSeconds(3).toMillis()); - log.info("[Route_Event]:load topic route from namesrv. topic: {}", topic); updateBrokerTopicMapping(topic, topicRouteData); return buildMessageQueueView(topic, topicRouteData); @@ -154,36 +153,50 @@ public String resolve(String name) { this.routeEventSubscriber = new RouteEventSubscriber( this, - topic -> { - this.routeCacheRefresher.markCacheDirty(topic); + (topic, timeStamp) -> { + this.routeCacheRefresher.markCacheDirty(topic, timeStamp); } ); this.init(); } - private void updateBrokerTopicMapping(String topic, TopicRouteData topicRouteData) { - Set brokerNames = topicRouteData.getBrokerDatas().stream() + private void updateBrokerTopicMapping(String topic, TopicRouteData route) { + Set currentBrokers = route.getBrokerDatas().stream() .map(BrokerData::getBrokerName) .collect(Collectors.toSet()); - log.info("[Route_Event]: Update broker topics mapping for topic: {}, brokers: {}", topic, brokerNames); - for (String brokerName : brokerNames) { - brokerTopicsMap.computeIfAbsent(brokerName, k -> ConcurrentHashMap.newKeySet()) - .add(topic); + for (String broker : currentBrokers) { + brokerToTopics.computeIfAbsent(broker, k -> ConcurrentHashMap.newKeySet()) + .add(topic); } + + log.info("Updated mapping for topic: {} -> brokers: {}", topic, currentBrokers); } public Set getBrokerTopics(String brokerName) { - return brokerTopicsMap.getOrDefault(brokerName, Set.of()); + return brokerToTopics.getOrDefault(brokerName, Set.of()); } - public void removeBrokerTopics(String brokerName) { - Set topics = brokerTopicsMap.remove(brokerName); + public void removeBrokerToTopics(String brokerName) { + Set topics = brokerToTopics.remove(brokerName); if (topics != null) { log.info("[Route_Event]: Removed {} topics for broker: {}", topics.size(), brokerName); } } + public void removeBrokerToTopic(String brokerName, String topic) { + Set topics = brokerToTopics.get(brokerName); + log.info("[Route_Event]: topics: {} broker: {}", topics, brokerName); + if (topics != null) { + topics.remove(topic); + log.info("[Route_Event]: Removed topic {} for broker: {}", topic, brokerName); + + if (topics.isEmpty()) { + brokerToTopics.remove(brokerName); + } + } + } + // pickup one topic in the topic cache private Optional pickTopic() { if (topicCache.asMap().isEmpty()) { From 8139467a7e847959dc5b7492cfc9908c10a7954a Mon Sep 17 00:00:00 2001 From: saz97 Date: Wed, 20 Aug 2025 11:34:01 +0800 Subject: [PATCH 08/19] =?UTF-8?q?Bug=20fix:=20=E4=BF=AE=E6=AD=A3=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E6=A3=80=E6=B5=8B=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 移除错误的checkRefreshStatus - 原 checkRefreshStatus 方法通过 topicCache.getIfPresent 检测刷新完成不可靠 - 该方法错误地将缓存存在视为刷新完成,导致状态误判 2. 重构状态确认 - 新增 markCompleted/markRetry 方法在 reload 回调中精确更新状态 - 在 processDirtyTopics 中增加超时检测,避免无限重试 3. 优化错误处理流程 - 刷新失败时通过 markRetry 重新加入队列 - 超时topic直接标记完成并报出错误日志,避免阻塞 --- .../service/route/RouteCacheRefresher.java | 57 ++++++++----------- .../service/route/TopicRouteService.java | 29 ++++++---- 2 files changed, 44 insertions(+), 42 deletions(-) diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresher.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresher.java index 4ed5b9632fa..0a7fe2902a8 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresher.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresher.java @@ -27,7 +27,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.Map.Entry; import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; @@ -46,8 +45,6 @@ public class RouteCacheRefresher { private final Queue pendingTopics = new ConcurrentLinkedQueue<>(); private final ScheduledExecutorService scheduler; - private final ConcurrentMap refreshingTopics = new ConcurrentHashMap<>(); - public RouteCacheRefresher(LoadingCache topicCache, ThreadPoolExecutor executor) { this.topicCache = topicCache; @@ -59,8 +56,6 @@ public RouteCacheRefresher(LoadingCache topicCache, } public void start() { - scheduler.scheduleWithFixedDelay(this::checkRefreshStatus, 1, 1, TimeUnit.SECONDS); - scheduler.scheduleWithFixedDelay(this::processDirtyTopics, 50, 200, TimeUnit.MILLISECONDS); } @@ -74,10 +69,31 @@ public void markCacheDirty(String topic, long timeStamp) { pendingTopics.offer(topic); } + public void markCompleted(String topic) { + log.info("markCompleted topic: {}", topic); + dirtyTopics.remove(topic); + } + + public void markRetry(String topic) { + pendingTopics.offer(topic); + } + private void processDirtyTopics() { List batch = new ArrayList<>(); while (!pendingTopics.isEmpty() && batch.size() < 100) { - batch.add(pendingTopics.poll()); + String topic = pendingTopics.poll(); + if (topic == null) break; + + Long timestamp = dirtyTopics.get(topic); + if (timestamp == null) continue; + + if (System.currentTimeMillis() - timestamp > TimeUnit.MINUTES.toMillis(1)) { + markCompleted(topic); + log.error("refreshing topic: {} stop for delay", topic); + continue; + } + + batch.add(topic); } for (String topic : batch) { @@ -88,44 +104,21 @@ private void processDirtyTopics() { private void refreshSingleRoute(String topic) { try { log.info("Refreshing route for: {}", topic); + if (topicCache.getIfPresent(topic) == null) { + markCompleted(topic); log.warn("No cache entry found for topic: {}", topic); return; } - refreshingTopics.put(topic, System.currentTimeMillis()); topicCache.refresh(topic); } catch (Exception e) { log.error("Refresh failed for: {}", topic, e); - pendingTopics.offer(topic); + markRetry(topic); } } - private void checkRefreshStatus() { - long currentTime = System.currentTimeMillis(); - List completed = new ArrayList<>(); - - for (Entry entry : refreshingTopics.entrySet()) { - String topic = entry.getKey(); - long startTime = entry.getValue(); - - if (currentTime - startTime > 5000) { - log.warn("Refresh timeout for topic: {}", topic); - completed.add(topic); - pendingTopics.offer(topic); - } - - if (topicCache.getIfPresent(topic) != null) { - completed.add(topic); - dirtyTopics.remove(topic); - log.info("Refresh confirmed for topic: {}", topic); - } - } - - completed.forEach(refreshingTopics::remove); - } - public void shutdown() { scheduler.shutdown(); } diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java index dc7f45b1d52..0a110637a8c 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java @@ -21,6 +21,7 @@ import com.github.benmanes.caffeine.cache.LoadingCache; import java.time.Duration; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.Set; @@ -110,9 +111,12 @@ public TopicRouteService(MQClientAPIFactory mqClientAPIFactory) { public @Nullable MessageQueueView reload(@NonNull String key, @NonNull MessageQueueView oldValue) throws Exception { try { - return load(key); + MessageQueueView newValue = load(key); + markCompleted(key); + return newValue; } catch (Exception e) { log.warn(String.format("reload topic route from namesrv. topic: %s", key), e); + markRetry(key); return oldValue; } } @@ -169,27 +173,20 @@ private void updateBrokerTopicMapping(String topic, TopicRouteData route) { brokerToTopics.computeIfAbsent(broker, k -> ConcurrentHashMap.newKeySet()) .add(topic); } - - log.info("Updated mapping for topic: {} -> brokers: {}", topic, currentBrokers); } public Set getBrokerTopics(String brokerName) { - return brokerToTopics.getOrDefault(brokerName, Set.of()); + return brokerToTopics.getOrDefault(brokerName, Collections.emptySet()); } public void removeBrokerToTopics(String brokerName) { - Set topics = brokerToTopics.remove(brokerName); - if (topics != null) { - log.info("[Route_Event]: Removed {} topics for broker: {}", topics.size(), brokerName); - } + brokerToTopics.remove(brokerName); } public void removeBrokerToTopic(String brokerName, String topic) { Set topics = brokerToTopics.get(brokerName); - log.info("[Route_Event]: topics: {} broker: {}", topics, brokerName); if (topics != null) { topics.remove(topic); - log.info("[Route_Event]: Removed topic {} for broker: {}", topic, brokerName); if (topics.isEmpty()) { brokerToTopics.remove(brokerName); @@ -197,6 +194,18 @@ public void removeBrokerToTopic(String brokerName, String topic) { } } + private void markCompleted(String topic) { + if (routeCacheRefresher != null) { + routeCacheRefresher.markCompleted(topic); + } + } + + private void markRetry(String topic) { + if (routeCacheRefresher != null) { + routeCacheRefresher.markRetry(topic); + } + } + // pickup one topic in the topic cache private Optional pickTopic() { if (topicCache.asMap().isEmpty()) { From 08b5674d712ad3eb2d8fdef3157835b217cff0a1 Mon Sep 17 00:00:00 2001 From: saz97 Date: Thu, 21 Aug 2025 14:29:34 +0800 Subject: [PATCH 09/19] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=8D=95?= =?UTF-8?q?=E6=B5=8B=E4=B8=8D=E9=80=9A=E8=BF=87=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/rocketmq/broker/topic/TopicConfigManager.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java b/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java index 90c0bcb5dd7..037ee5277f4 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java @@ -238,7 +238,9 @@ protected void init() { public TopicConfig putTopicConfig(TopicConfig topicConfig) { if (!TopicValidator.isSystemTopic(topicConfig.getTopicName())) { - this.brokerController.getRouteEventService().publishEvent(RouteEventType.TOPIC_CHANGE, topicConfig.getTopicName()); + if (this.brokerController.getRouteEventService() != null) { + this.brokerController.getRouteEventService().publishEvent(RouteEventType.TOPIC_CHANGE, topicConfig.getTopicName()); + } } return this.topicConfigTable.put(topicConfig.getTopicName(), topicConfig); } @@ -248,7 +250,9 @@ protected TopicConfig getTopicConfig(String topicName) { } protected TopicConfig removeTopicConfig(String topicName) { - this.brokerController.getRouteEventService().publishEvent(RouteEventType.TOPIC_CHANGE, topicName); + if (this.brokerController.getRouteEventService() != null) { + this.brokerController.getRouteEventService().publishEvent(RouteEventType.TOPIC_CHANGE, topicName); + } return this.topicConfigTable.remove(topicName); } From 887843d20707e56d959ecf623991e1260eff2fb5 Mon Sep 17 00:00:00 2001 From: saz97 Date: Tue, 26 Aug 2025 14:16:14 +0800 Subject: [PATCH 10/19] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=BC=80=E5=85=B3=E4=BB=A5=E5=8F=8A=E8=A1=A5=E5=85=85=E5=8D=95?= =?UTF-8?q?=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 本次提交在Broker和Proxy模块为新增的路由事件通知功能引入了可配置的开关。 1.Broker端修改: - 在 BrokerConfig 中新增 routeEventServiceEnable,用于控制RouteEventService 的初始化 - 修改 BrokerController,使其根据配置值有条件地初始化RouteEventService - 在流程中增加空指针检查 2.Proxy端修改: - 在ProxyConfig中新增 brokerRouteEventServiceEnable - 修改 TopicRouteService,使其根据配置初始化 RouteEventSubscriber 和 RouteCacheRefresher 测试新增: - 新增 RouteEventServiceTest 用于测试Broker端的事件发布 - 新增 RouteEventSubscriberTest 用于测试Proxy端的事件处理 - 新增 RouteCacheRefresherTest 用于测试Proxy端的缓存刷新机制 --- .../rocketmq/broker/BrokerController.java | 16 ++- .../broker/topic/TopicConfigManager.java | 6 +- .../broker/route/RouteEventServiceTest.java | 87 +++++++++++++++++ .../apache/rocketmq/common/BrokerConfig.java | 10 ++ .../rocketmq/proxy/config/ProxyConfig.java | 10 ++ .../service/route/RouteCacheRefresher.java | 3 - .../service/route/RouteEventSubscriber.java | 3 - .../service/route/TopicRouteService.java | 44 ++++++--- .../route/RouteCacheRefresherTest.java | 93 ++++++++++++++++++ .../route/RouteEventSubscriberTest.java | 97 +++++++++++++++++++ 10 files changed, 341 insertions(+), 28 deletions(-) create mode 100644 broker/src/test/java/org/apache/rocketmq/broker/route/RouteEventServiceTest.java create mode 100644 proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresherTest.java create mode 100644 proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriberTest.java 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 5ce9aff7f63..c39b5d14f30 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java @@ -868,7 +868,11 @@ public boolean initialize() throws CloneNotSupportedException { if (!result) { return false; } - this.routeEventService = new RouteEventService(this); + + if (this.brokerConfig.isRouteEventServiceEnable()) { + this.routeEventService = new RouteEventService(this); + LOG.info("initialize routeEventService"); + } return this.recoverAndInitService(); } @@ -1400,8 +1404,9 @@ public ChangeInvisibleTimeProcessor getChangeInvisibleTimeProcessor() { protected void shutdownBasicService() { shutdown = true; - - this.routeEventService.publishEvent(RouteEventType.SHUTDOWN); + if (this.routeEventService != null) { + this.routeEventService.publishEvent(RouteEventType.SHUTDOWN); + } this.unregisterBrokerAll(); @@ -1841,7 +1846,10 @@ public void run() { } }, 10, 5, TimeUnit.SECONDS); - this.routeEventService.publishEvent(RouteEventType.START); + if (this.routeEventService != null) { + this.routeEventService.publishEvent(RouteEventType.START); + } + } protected void scheduleSendHeartbeat() { diff --git a/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java b/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java index 037ee5277f4..7092c5e4ca8 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java @@ -238,7 +238,8 @@ protected void init() { public TopicConfig putTopicConfig(TopicConfig topicConfig) { if (!TopicValidator.isSystemTopic(topicConfig.getTopicName())) { - if (this.brokerController.getRouteEventService() != null) { + if (this.brokerController.getBrokerConfig().isRouteEventServiceEnable() + && this.brokerController.getRouteEventService() != null) { this.brokerController.getRouteEventService().publishEvent(RouteEventType.TOPIC_CHANGE, topicConfig.getTopicName()); } } @@ -250,7 +251,8 @@ protected TopicConfig getTopicConfig(String topicName) { } protected TopicConfig removeTopicConfig(String topicName) { - if (this.brokerController.getRouteEventService() != null) { + if (this.brokerController.getBrokerConfig().isRouteEventServiceEnable() + && this.brokerController.getRouteEventService() != null) { this.brokerController.getRouteEventService().publishEvent(RouteEventType.TOPIC_CHANGE, topicName); } return this.topicConfigTable.remove(topicName); diff --git a/broker/src/test/java/org/apache/rocketmq/broker/route/RouteEventServiceTest.java b/broker/src/test/java/org/apache/rocketmq/broker/route/RouteEventServiceTest.java new file mode 100644 index 00000000000..ce94fe49ba7 --- /dev/null +++ b/broker/src/test/java/org/apache/rocketmq/broker/route/RouteEventServiceTest.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.rocketmq.broker.route; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Map; + +import org.apache.rocketmq.broker.BrokerController; +import org.apache.rocketmq.common.BrokerConfig; +import org.apache.rocketmq.common.message.MessageExtBrokerInner; +import org.apache.rocketmq.remoting.netty.NettyClientConfig; +import org.apache.rocketmq.remoting.netty.NettyServerConfig; +import org.apache.rocketmq.store.MessageStore; +import org.apache.rocketmq.store.PutMessageResult; +import org.apache.rocketmq.store.PutMessageStatus; +import org.apache.rocketmq.store.config.MessageStoreConfig; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import com.alibaba.fastjson2.JSON; + +public class RouteEventServiceTest { + private BrokerController brokerController; + private MessageStore mockMessageStore; + private RouteEventService routeEventService; + + @Before + public void setUp() { + BrokerConfig brokerConfig = new BrokerConfig(); + brokerConfig.setRouteEventServiceEnable(true); + + brokerController = new BrokerController( + brokerConfig, + new NettyServerConfig(), + new NettyClientConfig(), + new MessageStoreConfig() + ); + + mockMessageStore = mock(MessageStore.class); + brokerController.setMessageStore(mockMessageStore); + + routeEventService = new RouteEventService(brokerController); + } + + @Test + public void testPublishEventSuccessfully() { + when(mockMessageStore.putMessage(any())).thenReturn(new PutMessageResult(PutMessageStatus.PUT_OK, null)); + + routeEventService.publishEvent(RouteEventType.START); + + verify(mockMessageStore).putMessage(any(MessageExtBrokerInner.class)); + } + + @Test + public void testIncludeTopicInEvent() { + when(mockMessageStore.putMessage(any())).thenReturn(new PutMessageResult(PutMessageStatus.PUT_OK, null)); + + routeEventService.publishEvent(RouteEventType.TOPIC_CHANGE, "TestTopic"); + + ArgumentCaptor captor = ArgumentCaptor.forClass(MessageExtBrokerInner.class); + verify(mockMessageStore).putMessage(captor.capture()); + + Map eventData = JSON.parseObject(new String(captor.getValue().getBody()), Map.class); + assertEquals("TestTopic", eventData.get("affectedTopic")); + } +} 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 3d0feec8a7f..9952ccd5f54 100644 --- a/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java +++ b/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java @@ -141,6 +141,8 @@ public class BrokerConfig extends BrokerIdentity { private boolean slaveReadEnable = false; + private boolean routeEventServiceEnable = true; + private boolean disableConsumeIfConsumerReadSlowly = false; private long consumerFallbehindThreshold = 1024L * 1024 * 1024 * 16; @@ -721,6 +723,14 @@ public void setSlaveReadEnable(final boolean slaveReadEnable) { this.slaveReadEnable = slaveReadEnable; } + public boolean isRouteEventServiceEnable() { + return routeEventServiceEnable; + } + + public void setRouteEventServiceEnable(final boolean routeEventServiceEnable) { + this.routeEventServiceEnable = routeEventServiceEnable; + } + public int getRegisterBrokerTimeoutMills() { return registerBrokerTimeoutMills; } diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java b/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java index e3e60b76bb5..3f6b8f706b0 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java @@ -206,6 +206,8 @@ public class ProxyConfig implements ConfigFile { private boolean enableAclRpcHookForClusterMode = false; + private boolean brokerRouteEventServiceEnable = true; + private boolean useDelayLevel = false; private String messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h"; private transient ConcurrentSkipListMap delayLevelTable = new ConcurrentSkipListMap<>(); @@ -1053,6 +1055,14 @@ public void setEnableAclRpcHookForClusterMode(boolean enableAclRpcHookForCluster this.enableAclRpcHookForClusterMode = enableAclRpcHookForClusterMode; } + public boolean isBrokerRouteEventServiceEnable() { + return brokerRouteEventServiceEnable; + } + + public void setBrokerRouteEventServiceEnable(final boolean brokerRouteEventServiceEnable) { + this.brokerRouteEventServiceEnable = brokerRouteEventServiceEnable; + } + public boolean isEnableTopicMessageTypeCheck() { return enableTopicMessageTypeCheck; } diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresher.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresher.java index 0a7fe2902a8..c99d2aa9dc4 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresher.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresher.java @@ -70,7 +70,6 @@ public void markCacheDirty(String topic, long timeStamp) { } public void markCompleted(String topic) { - log.info("markCompleted topic: {}", topic); dirtyTopics.remove(topic); } @@ -103,8 +102,6 @@ private void processDirtyTopics() { private void refreshSingleRoute(String topic) { try { - log.info("Refreshing route for: {}", topic); - if (topicCache.getIfPresent(topic) == null) { markCompleted(topic); log.warn("No cache entry found for topic: {}", topic); diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java index 1f9efd57097..3f39d21a4f5 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java @@ -68,7 +68,6 @@ private void processMessages(List msgs) { try { String json = new String(msg.getBody(), StandardCharsets.UTF_8); Map event = JSON.parseObject(json, Map.class); - LOGGER.info("[ROUTE_UPDATE]: Received route event: {} consumer {}", event, this.consumer.getInstanceName()); String brokerName = (String) event.get(RouteEventConstants.BROKER_NAME); RouteEventType eventType = RouteEventType.valueOf((String) event.get(RouteEventConstants.EVENT_TYPE)); @@ -81,7 +80,6 @@ private void processMessages(List msgs) { this.topicRouteService.removeBrokerToTopics(brokerName); for (String topic : topics) { - LOGGER.info("[ROUTE_UPDATE] Processing topic: {}", topic); dirtyMarker.accept(topic, eventTimeStamp); } break; @@ -90,7 +88,6 @@ private void processMessages(List msgs) { String affectedTopic = (String) event.get(RouteEventConstants.AFFECTED_TOPIC); if (affectedTopic != null) { - LOGGER.info("[ROUTE_UPDATE] Affected topic: {}", affectedTopic); this.topicRouteService.removeBrokerToTopic(brokerName, affectedTopic); dirtyMarker.accept(affectedTopic, eventTimeStamp); diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java index 0a110637a8c..79a758fb504 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java @@ -63,8 +63,8 @@ public abstract class TopicRouteService extends AbstractStartAndShutdown { private final MQClientAPIFactory mqClientAPIFactory; private MQFaultStrategy mqFaultStrategy; - private final RouteEventSubscriber routeEventSubscriber; - private final RouteCacheRefresher routeCacheRefresher; + private RouteEventSubscriber routeEventSubscriber; + private RouteCacheRefresher routeCacheRefresher; private final ConcurrentMap> brokerToTopics = new ConcurrentHashMap<>(); protected final LoadingCache topicCache; @@ -150,17 +150,23 @@ public String resolve(String name) { } } }, serviceDetector); - this.routeCacheRefresher = new RouteCacheRefresher( - this.topicCache, - this.cacheRefreshExecutor - ); - this.routeEventSubscriber = new RouteEventSubscriber( - this, - (topic, timeStamp) -> { - this.routeCacheRefresher.markCacheDirty(topic, timeStamp); - } - ); + if (config.isBrokerRouteEventServiceEnable()) { + this.routeCacheRefresher = new RouteCacheRefresher( + this.topicCache, + this.cacheRefreshExecutor + ); + log.info("initialize routeCacheRefresher"); + + this.routeEventSubscriber = new RouteEventSubscriber( + this, + (topic, timeStamp) -> { + this.routeCacheRefresher.markCacheDirty(topic, timeStamp); + } + ); + log.info("initialize routeEventSubscriber"); + } + this.init(); } @@ -224,8 +230,11 @@ public void shutdown() throws Exception { if (this.mqFaultStrategy.isStartDetectorEnable()) { mqFaultStrategy.shutdown(); } - this.routeCacheRefresher.shutdown(); - this.routeEventSubscriber.shutdown(); + + if (ConfigurationManager.getProxyConfig().isBrokerRouteEventServiceEnable()) { + this.routeCacheRefresher.shutdown(); + this.routeEventSubscriber.shutdown(); + } } @Override @@ -233,8 +242,11 @@ public void start() throws Exception { if (this.mqFaultStrategy.isStartDetectorEnable()) { this.mqFaultStrategy.startDetector(); } - this.routeEventSubscriber.start(); - this.routeCacheRefresher.start(); + + if (ConfigurationManager.getProxyConfig().isBrokerRouteEventServiceEnable()) { + this.routeEventSubscriber.start(); + this.routeCacheRefresher.start(); + } } public ClientConfig extractClientConfigFromProxyConfig(ProxyConfig proxyConfig) { diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresherTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresherTest.java new file mode 100644 index 00000000000..a4779bd6d94 --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresherTest.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.rocketmq.proxy.service.route; + +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.lang.reflect.Field; +import java.util.Queue; +import java.util.concurrent.ThreadPoolExecutor; + +import org.junit.Before; +import org.junit.Test; + +import com.github.benmanes.caffeine.cache.LoadingCache; + +public class RouteCacheRefresherTest { + private LoadingCache mockCache; + private ThreadPoolExecutor mockExecutor; + private RouteCacheRefresher refresher; + + @Before + public void setUp() { + mockCache = mock(LoadingCache.class); + mockExecutor = mock(ThreadPoolExecutor.class); + refresher = new RouteCacheRefresher(mockCache, mockExecutor); + } + + @Test + public void testMarkCacheDirtyAddsToPendingTopics() throws Exception { + refresher.markCacheDirty("TestTopic", System.currentTimeMillis()); + + Field pendingTopicsField = RouteCacheRefresher.class.getDeclaredField("pendingTopics"); + pendingTopicsField.setAccessible(true); + Queue pendingTopics = (Queue) pendingTopicsField.get(refresher); + + assertTrue(pendingTopics.contains("TestTopic")); + } + + @Test + public void testRefreshTriggersCacheRefresh() throws Exception { + refresher.markCacheDirty("TestTopic", System.currentTimeMillis()); + + when(mockCache.getIfPresent("TestTopic")).thenReturn(mock(MessageQueueView.class)); + + invokePrivateMethod(refresher, "refreshSingleRoute", "TestTopic"); + + verify(mockCache).refresh("TestTopic"); + } + + @Test + public void testRefreshFailureAddsToRetryQueue() throws Exception { + refresher.markCacheDirty("TestTopic", System.currentTimeMillis()); + + when(mockCache.getIfPresent("TestTopic")).thenReturn(mock(MessageQueueView.class)); + + doThrow(new RuntimeException("Refresh error")).when(mockCache).refresh("TestTopic"); + + invokePrivateMethod(refresher, "refreshSingleRoute", "TestTopic"); + + Field pendingTopicsField = RouteCacheRefresher.class.getDeclaredField("pendingTopics"); + pendingTopicsField.setAccessible(true); + Queue pendingTopics = (Queue) pendingTopicsField.get(refresher); + + assertTrue(pendingTopics.contains("TestTopic")); + } + + private void invokePrivateMethod(Object obj, String methodName, String arg) + throws Exception { + + java.lang.reflect.Method method = obj.getClass().getDeclaredMethod(methodName, String.class); + method.setAccessible(true); + method.invoke(obj, arg); + } +} diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriberTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriberTest.java new file mode 100644 index 00000000000..7012acadc1f --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriberTest.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.rocketmq.proxy.service.route; + +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; + +import org.apache.rocketmq.common.message.MessageExt; +import org.junit.Before; +import org.junit.Test; + +import com.alibaba.fastjson2.JSON; + +public class RouteEventSubscriberTest { + private TopicRouteService mockRouteService; + private BiConsumer mockDirtyMarker; + private RouteEventSubscriber subscriber; + + @Before + public void setUp() { + mockRouteService = mock(TopicRouteService.class); + mockDirtyMarker = mock(BiConsumer.class); + subscriber = new RouteEventSubscriber(mockRouteService, mockDirtyMarker); + } + + @Test + public void testHandleShutdownEvent() throws Exception { + Map eventData = new HashMap<>(); + eventData.put("eventType", "SHUTDOWN"); + eventData.put("brokerName", "TestBroker"); + eventData.put("timestamp", System.currentTimeMillis()); + + MessageExt msg = new MessageExt(); + msg.setBody(JSON.toJSONString(eventData).getBytes()); + + Set topics = new HashSet<>(Arrays.asList("TopicA", "TopicB")); + when(mockRouteService.getBrokerTopics("TestBroker")).thenReturn(topics); + + invokePrivateMethod(subscriber, "processMessages", Collections.singletonList(msg)); + + verify(mockRouteService).removeBrokerToTopics("TestBroker"); + verify(mockDirtyMarker).accept(eq("TopicA"), anyLong()); + verify(mockDirtyMarker).accept(eq("TopicB"), anyLong()); + } + + @Test + public void testHandleTopicChangeEvent() throws Exception { + Map eventData = new HashMap<>(); + eventData.put("eventType", "TOPIC_CHANGE"); + eventData.put("brokerName", "TestBroker"); + eventData.put("affectedTopic", "TestTopic"); + eventData.put("timestamp", System.currentTimeMillis()); + + MessageExt msg = new MessageExt(); + msg.setBody(JSON.toJSONString(eventData).getBytes()); + + invokePrivateMethod(subscriber, "processMessages", Collections.singletonList(msg)); + + verify(mockRouteService).removeBrokerToTopic(eq("TestBroker"), eq("TestTopic")); + verify(mockDirtyMarker).accept(eq("TestTopic"), anyLong()); + } + + private void invokePrivateMethod(Object obj, String methodName, List arg) + throws Exception { + + java.lang.reflect.Method method = obj.getClass().getDeclaredMethod(methodName, List.class); + method.setAccessible(true); + method.invoke(obj, arg); + } +} From 9af300b7bced9aa8ce330eb5e805b4a495103820 Mon Sep 17 00:00:00 2001 From: saz97 Date: Sat, 30 Aug 2025 00:17:37 +0800 Subject: [PATCH 11/19] =?UTF-8?q?=E4=BF=AE=E6=94=B9Ubuntu=E7=9A=84workflow?= =?UTF-8?q?=E5=8D=95=E6=B5=8B=E4=B8=8D=E8=BF=87=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/org/apache/rocketmq/common/BrokerConfig.java | 2 ++ .../main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java | 2 ++ 2 files changed, 4 insertions(+) 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 9952ccd5f54..c141dc1053f 100644 --- a/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java +++ b/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java @@ -25,6 +25,7 @@ import org.apache.rocketmq.common.utils.NetworkUtil; import java.util.concurrent.TimeUnit; +import com.alibaba.fastjson2.annotation.JSONField; public class BrokerConfig extends BrokerIdentity { @@ -141,6 +142,7 @@ public class BrokerConfig extends BrokerIdentity { private boolean slaveReadEnable = false; + @JSONField(serialize = false) private boolean routeEventServiceEnable = true; private boolean disableConsumeIfConsumerReadSlowly = false; diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java b/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java index 3f6b8f706b0..316405bb9c7 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java @@ -38,6 +38,7 @@ import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import com.alibaba.fastjson2.annotation.JSONField; public class ProxyConfig implements ConfigFile { private final static Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); @@ -206,6 +207,7 @@ public class ProxyConfig implements ConfigFile { private boolean enableAclRpcHookForClusterMode = false; + @JSONField(serialize = false) private boolean brokerRouteEventServiceEnable = true; private boolean useDelayLevel = false; From 2d2c1fb6395853d71dd35852bab08e00a4636a3f Mon Sep 17 00:00:00 2001 From: saz97 Date: Wed, 10 Sep 2025 09:35:06 +0800 Subject: [PATCH 12/19] =?UTF-8?q?=E8=A7=A3=E5=86=B3comments=E6=8F=90?= =?UTF-8?q?=E5=87=BA=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 修改Broker端触发逻辑 - 移除putTopicConfig和removeTopicConfig方法的事件触发 - 在Topic创建/删除的方法中触发 2. 修改开关命名 - 将routeEventServiceEnable更名为enableRouteChangeNotification 3. 调整开关逻辑 - 事件发布时检查enableRouteChangeNotification开关 4. 删除Proxy端重试逻辑 - 删除RouteCacheRefresher中的markRetry方法 - 失败时不再重试,依赖基础定时更新 --- .../rocketmq/broker/BrokerController.java | 10 +---- .../broker/route/RouteEventService.java | 3 ++ .../rocketmq/broker/route/RouteEventType.java | 2 - .../broker/topic/TopicConfigManager.java | 39 +++++++++++++----- .../broker/route/RouteEventServiceTest.java | 4 +- .../apache/rocketmq/common/BrokerConfig.java | 12 +++--- .../rocketmq/proxy/config/ProxyConfig.java | 12 +++--- .../service/route/RouteCacheRefresher.java | 12 +++--- .../service/route/TopicRouteService.java | 40 +++++++------------ 9 files changed, 64 insertions(+), 70 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 b9a611b0eee..c5c004b6053 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java @@ -868,11 +868,7 @@ public boolean initialize() throws CloneNotSupportedException { if (!result) { return false; } - - if (this.brokerConfig.isRouteEventServiceEnable()) { - this.routeEventService = new RouteEventService(this); - LOG.info("initialize routeEventService"); - } + this.routeEventService = new RouteEventService(this); return this.recoverAndInitService(); } @@ -1850,10 +1846,6 @@ public void run() { } }, 10, 5, TimeUnit.SECONDS); - if (this.routeEventService != null) { - this.routeEventService.publishEvent(RouteEventType.START); - } - } protected void scheduleSendHeartbeat() { diff --git a/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventService.java b/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventService.java index 68819a384f7..0f9538dbb1e 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventService.java @@ -45,6 +45,9 @@ public void publishEvent(RouteEventType eventType) { } public void publishEvent(RouteEventType eventType, String affectedtopic) { + if (!brokerController.getBrokerConfig().isEnableRouteChangeNotification()) { + return; + } Map eventData = new HashMap<>(); PutMessageResult result; eventData.put(RouteEventConstants.EVENT_TYPE, eventType.name()); diff --git a/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventType.java b/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventType.java index d7e4c9fcd6b..fa44c239351 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventType.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventType.java @@ -18,8 +18,6 @@ package org.apache.rocketmq.broker.route; public enum RouteEventType { - START, SHUTDOWN, - SWITCH, TOPIC_CHANGE } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java b/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java index 0324cd8b5b7..ed57ca543e0 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java @@ -238,12 +238,6 @@ protected void init() { } public TopicConfig putTopicConfig(TopicConfig topicConfig) { - if (!TopicValidator.isSystemTopic(topicConfig.getTopicName())) { - if (this.brokerController.getBrokerConfig().isRouteEventServiceEnable() - && this.brokerController.getRouteEventService() != null) { - this.brokerController.getRouteEventService().publishEvent(RouteEventType.TOPIC_CHANGE, topicConfig.getTopicName()); - } - } return this.topicConfigTable.put(topicConfig.getTopicName(), topicConfig); } @@ -252,10 +246,6 @@ protected TopicConfig getTopicConfig(String topicName) { } protected TopicConfig removeTopicConfig(String topicName) { - if (this.brokerController.getBrokerConfig().isRouteEventServiceEnable() - && this.brokerController.getRouteEventService() != null) { - this.brokerController.getRouteEventService().publishEvent(RouteEventType.TOPIC_CHANGE, topicName); - } return this.topicConfigTable.remove(topicName); } @@ -331,6 +321,12 @@ public TopicConfig createTopicInSendMessageMethod(final String topic, final Stri if (createNew) { registerBrokerData(topicConfig); + + if (this.brokerController.getBrokerConfig().isEnableRouteChangeNotification() + && this.brokerController.getRouteEventService() != null) { + this.brokerController.getRouteEventService().publishEvent( + RouteEventType.TOPIC_CHANGE, topicConfig.getTopicName()); + } } return topicConfig; @@ -370,6 +366,12 @@ public TopicConfig createTopicIfAbsent(TopicConfig topicConfig, boolean register } if (createNew && register) { registerBrokerData(topicConfig); + + if (this.brokerController.getBrokerConfig().isEnableRouteChangeNotification() + && this.brokerController.getRouteEventService() != null) { + this.brokerController.getRouteEventService().publishEvent( + RouteEventType.TOPIC_CHANGE, topicConfig.getTopicName()); + } } return getTopicConfig(topicConfig.getTopicName()); } @@ -429,6 +431,12 @@ public TopicConfig createTopicInSendMessageBackMethod( if (createNew) { registerBrokerData(topicConfig); + + if (this.brokerController.getBrokerConfig().isEnableRouteChangeNotification() + && this.brokerController.getRouteEventService() != null) { + this.brokerController.getRouteEventService().publishEvent( + RouteEventType.TOPIC_CHANGE, topicConfig.getTopicName()); + } } return topicConfig; @@ -469,6 +477,12 @@ public TopicConfig createTopicOfTranCheckMaxTime(final int clientDefaultTopicQue if (createNew) { registerBrokerData(topicConfig); + + if (this.brokerController.getBrokerConfig().isEnableRouteChangeNotification() + && this.brokerController.getRouteEventService() != null) { + this.brokerController.getRouteEventService().publishEvent( + RouteEventType.TOPIC_CHANGE, topicConfig.getTopicName()); + } } return topicConfig; @@ -622,6 +636,11 @@ public void deleteTopicConfig(final String topic) { log.info("delete topic config OK, topic: {}", old); updateDataVersion(); this.persist(); + + if (this.brokerController.getBrokerConfig().isEnableRouteChangeNotification() + && this.brokerController.getRouteEventService() != null) { + this.brokerController.getRouteEventService().publishEvent(RouteEventType.TOPIC_CHANGE, topic); + } } else { log.warn("delete topic config failed, topic: {} not exists", topic); } diff --git a/broker/src/test/java/org/apache/rocketmq/broker/route/RouteEventServiceTest.java b/broker/src/test/java/org/apache/rocketmq/broker/route/RouteEventServiceTest.java index ce94fe49ba7..b1be1c63337 100644 --- a/broker/src/test/java/org/apache/rocketmq/broker/route/RouteEventServiceTest.java +++ b/broker/src/test/java/org/apache/rocketmq/broker/route/RouteEventServiceTest.java @@ -48,7 +48,7 @@ public class RouteEventServiceTest { @Before public void setUp() { BrokerConfig brokerConfig = new BrokerConfig(); - brokerConfig.setRouteEventServiceEnable(true); + brokerConfig.setEnableRouteChangeNotification(true); brokerController = new BrokerController( brokerConfig, @@ -67,7 +67,7 @@ public void setUp() { public void testPublishEventSuccessfully() { when(mockMessageStore.putMessage(any())).thenReturn(new PutMessageResult(PutMessageStatus.PUT_OK, null)); - routeEventService.publishEvent(RouteEventType.START); + routeEventService.publishEvent(RouteEventType.SHUTDOWN); verify(mockMessageStore).putMessage(any(MessageExtBrokerInner.class)); } 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 1b54f20dd11..c166ed8426c 100644 --- a/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java +++ b/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java @@ -25,7 +25,6 @@ import org.apache.rocketmq.common.utils.NetworkUtil; import java.util.concurrent.TimeUnit; -import com.alibaba.fastjson2.annotation.JSONField; public class BrokerConfig extends BrokerIdentity { @@ -142,8 +141,7 @@ public class BrokerConfig extends BrokerIdentity { private boolean slaveReadEnable = false; - @JSONField(serialize = false) - private boolean routeEventServiceEnable = true; + private boolean enableRouteChangeNotification = true; private boolean disableConsumeIfConsumerReadSlowly = false; private long consumerFallbehindThreshold = 1024L * 1024 * 1024 * 16; @@ -737,12 +735,12 @@ public void setSlaveReadEnable(final boolean slaveReadEnable) { this.slaveReadEnable = slaveReadEnable; } - public boolean isRouteEventServiceEnable() { - return routeEventServiceEnable; + public boolean isEnableRouteChangeNotification() { + return enableRouteChangeNotification; } - public void setRouteEventServiceEnable(final boolean routeEventServiceEnable) { - this.routeEventServiceEnable = routeEventServiceEnable; + public void setEnableRouteChangeNotification(final boolean enableRouteChangeNotification) { + this.enableRouteChangeNotification = enableRouteChangeNotification; } public int getRegisterBrokerTimeoutMills() { diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java b/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java index e6eded50e92..c661e366195 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java @@ -38,7 +38,6 @@ import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -import com.alibaba.fastjson2.annotation.JSONField; public class ProxyConfig implements ConfigFile { private final static Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); @@ -208,8 +207,7 @@ public class ProxyConfig implements ConfigFile { private boolean enableAclRpcHookForClusterMode = false; - @JSONField(serialize = false) - private boolean brokerRouteEventServiceEnable = true; + private boolean enableRouteChangeNotification = true; private boolean useDelayLevel = false; private String messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h"; @@ -1066,12 +1064,12 @@ public void setEnableAclRpcHookForClusterMode(boolean enableAclRpcHookForCluster this.enableAclRpcHookForClusterMode = enableAclRpcHookForClusterMode; } - public boolean isBrokerRouteEventServiceEnable() { - return brokerRouteEventServiceEnable; + public boolean isEnableRouteChangeNotification() { + return enableRouteChangeNotification; } - public void setBrokerRouteEventServiceEnable(final boolean brokerRouteEventServiceEnable) { - this.brokerRouteEventServiceEnable = brokerRouteEventServiceEnable; + public void setEnableRouteChangeNotification(final boolean enableRouteChangeNotification) { + this.enableRouteChangeNotification = enableRouteChangeNotification; } public boolean isEnableTopicMessageTypeCheck() { diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresher.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresher.java index c99d2aa9dc4..991ab693908 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresher.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresher.java @@ -73,10 +73,6 @@ public void markCompleted(String topic) { dirtyTopics.remove(topic); } - public void markRetry(String topic) { - pendingTopics.offer(topic); - } - private void processDirtyTopics() { List batch = new ArrayList<>(); while (!pendingTopics.isEmpty() && batch.size() < 100) { @@ -96,23 +92,25 @@ private void processDirtyTopics() { } for (String topic : batch) { - executor.execute(() -> refreshSingleRoute(topic)); + executor.execute(() -> { + refreshSingleRoute(topic); + markCompleted(topic); + }); } } private void refreshSingleRoute(String topic) { try { if (topicCache.getIfPresent(topic) == null) { - markCompleted(topic); log.warn("No cache entry found for topic: {}", topic); return; } topicCache.refresh(topic); + log.info("[ROUTE_NOTIFICATION]: Refresh topic: {}", topic); } catch (Exception e) { log.error("Refresh failed for: {}", topic, e); - markRetry(topic); } } diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java index 79a758fb504..b34986c1134 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java @@ -111,12 +111,10 @@ public TopicRouteService(MQClientAPIFactory mqClientAPIFactory) { public @Nullable MessageQueueView reload(@NonNull String key, @NonNull MessageQueueView oldValue) throws Exception { try { - MessageQueueView newValue = load(key); markCompleted(key); - return newValue; + return load(key); } catch (Exception e) { log.warn(String.format("reload topic route from namesrv. topic: %s", key), e); - markRetry(key); return oldValue; } } @@ -151,21 +149,17 @@ public String resolve(String name) { } }, serviceDetector); - if (config.isBrokerRouteEventServiceEnable()) { - this.routeCacheRefresher = new RouteCacheRefresher( - this.topicCache, - this.cacheRefreshExecutor - ); - log.info("initialize routeCacheRefresher"); - - this.routeEventSubscriber = new RouteEventSubscriber( - this, - (topic, timeStamp) -> { - this.routeCacheRefresher.markCacheDirty(topic, timeStamp); - } - ); - log.info("initialize routeEventSubscriber"); - } + this.routeCacheRefresher = new RouteCacheRefresher( + this.topicCache, + this.cacheRefreshExecutor + ); + + this.routeEventSubscriber = new RouteEventSubscriber( + this, + (topic, timeStamp) -> { + this.routeCacheRefresher.markCacheDirty(topic, timeStamp); + } + ); this.init(); } @@ -206,12 +200,6 @@ private void markCompleted(String topic) { } } - private void markRetry(String topic) { - if (routeCacheRefresher != null) { - routeCacheRefresher.markRetry(topic); - } - } - // pickup one topic in the topic cache private Optional pickTopic() { if (topicCache.asMap().isEmpty()) { @@ -231,7 +219,7 @@ public void shutdown() throws Exception { mqFaultStrategy.shutdown(); } - if (ConfigurationManager.getProxyConfig().isBrokerRouteEventServiceEnable()) { + if (ConfigurationManager.getProxyConfig().isEnableRouteChangeNotification()) { this.routeCacheRefresher.shutdown(); this.routeEventSubscriber.shutdown(); } @@ -243,7 +231,7 @@ public void start() throws Exception { this.mqFaultStrategy.startDetector(); } - if (ConfigurationManager.getProxyConfig().isBrokerRouteEventServiceEnable()) { + if (ConfigurationManager.getProxyConfig().isEnableRouteChangeNotification()) { this.routeEventSubscriber.start(); this.routeCacheRefresher.start(); } From 13bc8ffe2fb3ebf4ab6162842242377cd6bd7ab0 Mon Sep 17 00:00:00 2001 From: saz97 Date: Fri, 12 Sep 2025 11:08:04 +0800 Subject: [PATCH 13/19] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=AF=B9START=E4=BA=8B?= =?UTF-8?q?=E4=BB=B6=E7=9A=84=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91=201.brok?= =?UTF-8?q?er=E4=BE=A7=20=20=20-=20=E5=9C=A8BrokerController=E7=9A=84start?= =?UTF-8?q?=E4=B8=AD=E8=B0=83=E7=94=A8publishevent=EF=BC=8C=E5=8F=91?= =?UTF-8?q?=E9=80=81START=E4=BA=8B=E4=BB=B6=202.proxy=E4=BE=A7=20=20=20-?= =?UTF-8?q?=20=E5=A2=9E=E5=8A=A0=E5=AF=B9START=E4=BA=8B=E4=BB=B6=E7=9A=84?= =?UTF-8?q?=E5=A4=84=E7=90=86=EF=BC=8C=E9=80=BB=E8=BE=91=E5=92=8CTOPIC=5FC?= =?UTF-8?q?HANGE=E4=B8=80=E8=87=B4=EF=BC=8C=E5=8D=B3=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E5=8F=97=E5=BD=B1=E5=93=8D=E7=9A=84topic=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apache/rocketmq/broker/BrokerController.java | 14 ++++++++++++++ .../rocketmq/broker/route/RouteEventType.java | 1 + .../proxy/service/route/RouteEventSubscriber.java | 4 +++- .../proxy/service/route/TopicRouteService.java | 1 + 4 files changed, 19 insertions(+), 1 deletion(-) 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 c5c004b6053..9e5402d5111 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java @@ -167,6 +167,7 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -1846,6 +1847,19 @@ public void run() { } }, 10, 5, TimeUnit.SECONDS); + if (this.routeEventService != null && this.topicConfigManager != null) { + Set topics = this.topicConfigManager.getTopicConfigTable().keySet(); + + for (String topic : topics) { + try { + this.routeEventService.publishEvent(RouteEventType.START, topic); + LOG.info("[START]: publish {}", topic); + } catch (Exception e) { + LOG.error("Failed to publish route change event for topic: {}", topic, e); + } + } + LOG.info("Published route change events for {} topics", topics.size()); + } } protected void scheduleSendHeartbeat() { diff --git a/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventType.java b/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventType.java index fa44c239351..0c76f70180d 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventType.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventType.java @@ -18,6 +18,7 @@ package org.apache.rocketmq.broker.route; public enum RouteEventType { + START, SHUTDOWN, TOPIC_CHANGE } diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java index 3f39d21a4f5..3cd02b4d543 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java @@ -69,6 +69,7 @@ private void processMessages(List msgs) { String json = new String(msg.getBody(), StandardCharsets.UTF_8); Map event = JSON.parseObject(json, Map.class); + LOGGER.info("[ROUTE_EVENT]: accept event {}", event); String brokerName = (String) event.get(RouteEventConstants.BROKER_NAME); RouteEventType eventType = RouteEventType.valueOf((String) event.get(RouteEventConstants.EVENT_TYPE)); Long eventTimeStamp = (Long) event.get(RouteEventConstants.TIMESTAMP); @@ -83,7 +84,8 @@ private void processMessages(List msgs) { dirtyMarker.accept(topic, eventTimeStamp); } break; - + + case START: case TOPIC_CHANGE: String affectedTopic = (String) event.get(RouteEventConstants.AFFECTED_TOPIC); diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java index b34986c1134..411e69947b0 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java @@ -97,6 +97,7 @@ public TopicRouteService(MQClientAPIFactory mqClientAPIFactory) { try { TopicRouteData topicRouteData = mqClientAPIFactory.getClient().getTopicRouteInfoFromNameServer(topic, Duration.ofSeconds(3).toMillis()); updateBrokerTopicMapping(topic, topicRouteData); + log.info("[topicCache]: load topic: {} topicRouteData: {}", topic, topicRouteData); return buildMessageQueueView(topic, topicRouteData); } catch (Exception e) { From 47117a1d07619554ec5bd35010f58ecc295338fd Mon Sep 17 00:00:00 2001 From: saz97 Date: Thu, 18 Sep 2025 16:58:12 +0800 Subject: [PATCH 14/19] =?UTF-8?q?=E4=BF=AE=E6=94=B9broker=E4=BE=A7?= =?UTF-8?q?=E4=BA=8B=E4=BB=B6=E9=80=9A=E7=9F=A5=E6=96=B9=E6=B3=95=201.=20?= =?UTF-8?q?=E6=94=B6=E6=95=9B=E9=87=8D=E5=A4=8D=E4=BB=A3=E7=A0=81=EF=BC=9A?= =?UTF-8?q?=20=20=20=20-=20=E6=8F=90=E5=8F=96=E5=85=AC=E5=85=B1=E4=BA=8B?= =?UTF-8?q?=E4=BB=B6=E5=8F=91=E5=B8=83=E9=80=BB=E8=BE=91=E5=88=B0=E7=BB=9F?= =?UTF-8?q?=E4=B8=80=E6=96=B9=E6=B3=95=20=20=20=20-=20=E6=B6=88=E9=99=A4To?= =?UTF-8?q?picConfigManager=E4=B8=AD=E5=90=84=E6=93=8D=E4=BD=9C=E7=82=B9?= =?UTF-8?q?=E7=9A=84=E9=87=8D=E5=A4=8D=E4=BA=8B=E4=BB=B6=E5=8F=91=E5=B8=83?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 2. 实现事件分块发送: - 当topic数量超过阈值时自动分块发送 - 新增partitionTopics方法处理主题分批 - 支持MAX_TOPICS_PER_EVENT配置控制批量大小 --- .../rocketmq/broker/BrokerController.java | 27 ++++---- .../broker/route/RouteEventConstants.java | 2 +- .../broker/route/RouteEventService.java | 65 +++++++++++++------ .../broker/topic/TopicConfigManager.java | 40 +++++------- .../broker/route/RouteEventServiceTest.java | 18 +++-- .../service/route/RouteEventSubscriber.java | 26 ++------ .../service/route/TopicRouteService.java | 39 ----------- .../route/RouteEventSubscriberTest.java | 17 ++--- 8 files changed, 102 insertions(+), 132 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 9e5402d5111..50770e89318 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java @@ -1401,12 +1401,19 @@ public ChangeInvisibleTimeProcessor getChangeInvisibleTimeProcessor() { protected void shutdownBasicService() { shutdown = true; - if (this.routeEventService != null) { - this.routeEventService.publishEvent(RouteEventType.SHUTDOWN); - } this.unregisterBrokerAll(); + if (this.routeEventService != null && this.topicConfigManager != null) { + Set topics = this.topicConfigManager.getTopicConfigTable().keySet(); + try { + this.routeEventService.publishEvent(RouteEventType.SHUTDOWN, topics); + LOG.info("[SHUTDOWN AFTER unregisterBrokerAll]: publish {}", topics); + } catch (Exception e) { + LOG.error("Failed to publish route change event for topic: {}", topics, e); + } + } + if (this.shutdownHook != null) { this.shutdownHook.beforeShutdown(this); } @@ -1849,16 +1856,12 @@ public void run() { if (this.routeEventService != null && this.topicConfigManager != null) { Set topics = this.topicConfigManager.getTopicConfigTable().keySet(); - - for (String topic : topics) { - try { - this.routeEventService.publishEvent(RouteEventType.START, topic); - LOG.info("[START]: publish {}", topic); - } catch (Exception e) { - LOG.error("Failed to publish route change event for topic: {}", topic, e); - } + try { + this.routeEventService.publishEvent(RouteEventType.START, topics); + LOG.info("[START]: publish {}", topics); + } catch (Exception e) { + LOG.error("Failed to publish route change event for topic: {}", topics, e); } - LOG.info("Published route change events for {} topics", topics.size()); } } diff --git a/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventConstants.java b/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventConstants.java index 58edaacedfd..48bc0e25da6 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventConstants.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventConstants.java @@ -22,5 +22,5 @@ public class RouteEventConstants { public static final String BROKER_NAME = "brokerName"; public static final String BROKER_ID = "brokerId"; public static final String TIMESTAMP = "timestamp"; - public static final String AFFECTED_TOPIC = "affectedTopic"; + public static final String AFFECTED_TOPICS = "affectedTopics"; } \ No newline at end of file diff --git a/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventService.java b/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventService.java index 0f9538dbb1e..844f18de778 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventService.java @@ -17,8 +17,11 @@ package org.apache.rocketmq.broker.route; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Set; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.common.constant.LoggerName; @@ -33,6 +36,7 @@ public class RouteEventService { private static final Logger LOG = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private final BrokerController brokerController; + private static final int MAX_TOPICS_PER_EVENT = 100; public RouteEventService(BrokerController brokerController) { this.brokerController = brokerController; @@ -40,40 +44,61 @@ public RouteEventService(BrokerController brokerController) { brokerController.getBrokerConfig().getBrokerName()); } - public void publishEvent(RouteEventType eventType) { - publishEvent(eventType, null); - } - - public void publishEvent(RouteEventType eventType, String affectedtopic) { + public void publishEvent(RouteEventType eventType, Set topics) { if (!brokerController.getBrokerConfig().isEnableRouteChangeNotification()) { return; } + + if (topics == null || topics.isEmpty()) { + sendEvent(eventType, null); + return; + } + + List topicList = new ArrayList<>(topics); + partitionTopics(topicList, MAX_TOPICS_PER_EVENT) + .forEach(batch -> sendEvent(eventType, batch)); + } + + private void sendEvent(RouteEventType eventType, List topics) { + try { + Map eventData = createEventData(eventType, topics); + MessageExtBrokerInner msg = createEventMessage(eventData); + + PutMessageResult result = brokerController.getMessageStore().putMessage(msg); + brokerController.getMessageStore().flush(); + + if (!result.isOk()) { + LOG.warn("[ROUTE_EVENT] Publish failed: {}", result.getPutMessageStatus()); + } + } catch (Exception e) { + LOG.error("[ROUTE_EVENT] Failed to publish event: {}", eventType, e); + } + } + + private Map createEventData(RouteEventType eventType, List topics) { Map eventData = new HashMap<>(); - PutMessageResult result; eventData.put(RouteEventConstants.EVENT_TYPE, eventType.name()); eventData.put(RouteEventConstants.BROKER_NAME, brokerController.getBrokerConfig().getBrokerName()); eventData.put(RouteEventConstants.BROKER_ID, brokerController.getBrokerConfig().getBrokerId()); eventData.put(RouteEventConstants.TIMESTAMP, System.currentTimeMillis()); - if (affectedtopic != null) { - eventData.put(RouteEventConstants.AFFECTED_TOPIC, affectedtopic); + + if (topics != null && !topics.isEmpty()) { + eventData.put(RouteEventConstants.AFFECTED_TOPICS, topics); } - MessageExtBrokerInner msg = createEventMessage(eventData); + return eventData; + } - try { - result = brokerController.getMessageStore().putMessage(msg); - } - catch (Exception e) { - LOG.error("[ROUTE_EVENT] Failed to publish event: {}", eventType, e); - return; - } - brokerController.getMessageStore().flush(); + private List> partitionTopics(List topics, int batchSize) { + List> batches = new ArrayList<>(); - if (!result.isOk()) { - LOG.error("[ROUTE_EVENT] Publish failed: {}", result.getPutMessageStatus()); + for (int i = 0; i < topics.size(); i += batchSize) { + int end = Math.min(i + batchSize, topics.size()); + batches.add(topics.subList(i, end)); } - } + return batches; + } private MessageExtBrokerInner createEventMessage(Map eventData) { MessageExtBrokerInner msg = new MessageExtBrokerInner(); msg.setTopic(TopicValidator.RMQ_ROUTE_EVENT_TOPIC); diff --git a/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java b/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java index ed57ca543e0..2b153a88ceb 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java @@ -16,6 +16,7 @@ */ package org.apache.rocketmq.broker.topic; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -322,11 +323,7 @@ public TopicConfig createTopicInSendMessageMethod(final String topic, final Stri if (createNew) { registerBrokerData(topicConfig); - if (this.brokerController.getBrokerConfig().isEnableRouteChangeNotification() - && this.brokerController.getRouteEventService() != null) { - this.brokerController.getRouteEventService().publishEvent( - RouteEventType.TOPIC_CHANGE, topicConfig.getTopicName()); - } + publishTopicChangeEvent(topicConfig.getTopicName()); } return topicConfig; @@ -367,11 +364,7 @@ public TopicConfig createTopicIfAbsent(TopicConfig topicConfig, boolean register if (createNew && register) { registerBrokerData(topicConfig); - if (this.brokerController.getBrokerConfig().isEnableRouteChangeNotification() - && this.brokerController.getRouteEventService() != null) { - this.brokerController.getRouteEventService().publishEvent( - RouteEventType.TOPIC_CHANGE, topicConfig.getTopicName()); - } + publishTopicChangeEvent(topicConfig.getTopicName()); } return getTopicConfig(topicConfig.getTopicName()); } @@ -432,11 +425,7 @@ public TopicConfig createTopicInSendMessageBackMethod( if (createNew) { registerBrokerData(topicConfig); - if (this.brokerController.getBrokerConfig().isEnableRouteChangeNotification() - && this.brokerController.getRouteEventService() != null) { - this.brokerController.getRouteEventService().publishEvent( - RouteEventType.TOPIC_CHANGE, topicConfig.getTopicName()); - } + publishTopicChangeEvent(topicConfig.getTopicName()); } return topicConfig; @@ -478,11 +467,7 @@ public TopicConfig createTopicOfTranCheckMaxTime(final int clientDefaultTopicQue if (createNew) { registerBrokerData(topicConfig); - if (this.brokerController.getBrokerConfig().isEnableRouteChangeNotification() - && this.brokerController.getRouteEventService() != null) { - this.brokerController.getRouteEventService().publishEvent( - RouteEventType.TOPIC_CHANGE, topicConfig.getTopicName()); - } + publishTopicChangeEvent(topicConfig.getTopicName()); } return topicConfig; @@ -637,15 +622,22 @@ public void deleteTopicConfig(final String topic) { updateDataVersion(); this.persist(); - if (this.brokerController.getBrokerConfig().isEnableRouteChangeNotification() - && this.brokerController.getRouteEventService() != null) { - this.brokerController.getRouteEventService().publishEvent(RouteEventType.TOPIC_CHANGE, topic); - } + publishTopicChangeEvent(topic); } else { log.warn("delete topic config failed, topic: {} not exists", topic); } } + private void publishTopicChangeEvent(String topicName) { + if (this.brokerController.getBrokerConfig().isEnableRouteChangeNotification() + && this.brokerController.getRouteEventService() != null) { + this.brokerController.getRouteEventService().publishEvent( + RouteEventType.TOPIC_CHANGE, + Collections.singleton(topicName) + ); + } + } + public TopicConfigSerializeWrapper buildTopicConfigSerializeWrapper() { TopicConfigSerializeWrapper topicConfigSerializeWrapper = new TopicConfigSerializeWrapper(); topicConfigSerializeWrapper.setTopicConfigTable(this.topicConfigTable); diff --git a/broker/src/test/java/org/apache/rocketmq/broker/route/RouteEventServiceTest.java b/broker/src/test/java/org/apache/rocketmq/broker/route/RouteEventServiceTest.java index b1be1c63337..29386648cff 100644 --- a/broker/src/test/java/org/apache/rocketmq/broker/route/RouteEventServiceTest.java +++ b/broker/src/test/java/org/apache/rocketmq/broker/route/RouteEventServiceTest.java @@ -23,6 +23,8 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.util.Collections; +import java.util.List; import java.util.Map; import org.apache.rocketmq.broker.BrokerController; @@ -67,7 +69,7 @@ public void setUp() { public void testPublishEventSuccessfully() { when(mockMessageStore.putMessage(any())).thenReturn(new PutMessageResult(PutMessageStatus.PUT_OK, null)); - routeEventService.publishEvent(RouteEventType.SHUTDOWN); + routeEventService.publishEvent(RouteEventType.SHUTDOWN, Collections.singleton("TestTopic")); verify(mockMessageStore).putMessage(any(MessageExtBrokerInner.class)); } @@ -76,12 +78,20 @@ public void testPublishEventSuccessfully() { public void testIncludeTopicInEvent() { when(mockMessageStore.putMessage(any())).thenReturn(new PutMessageResult(PutMessageStatus.PUT_OK, null)); - routeEventService.publishEvent(RouteEventType.TOPIC_CHANGE, "TestTopic"); + routeEventService.publishEvent(RouteEventType.TOPIC_CHANGE, Collections.singleton("TestTopic")); ArgumentCaptor captor = ArgumentCaptor.forClass(MessageExtBrokerInner.class); verify(mockMessageStore).putMessage(captor.capture()); - Map eventData = JSON.parseObject(new String(captor.getValue().getBody()), Map.class); - assertEquals("TestTopic", eventData.get("affectedTopic")); + Map eventData = JSON.parseObject( + new String(captor.getValue().getBody()), + Map.class + ); + + List affectedTopics = (List) eventData.get(RouteEventConstants.AFFECTED_TOPICS); + + List expectedTopics = Collections.singletonList("TestTopic"); + + assertEquals(expectedTopics, affectedTopics); } } diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java index 3cd02b4d543..b5d4454eebf 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java @@ -19,7 +19,6 @@ import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.function.BiConsumer; import org.apache.rocketmq.broker.route.RouteEventConstants; @@ -39,12 +38,10 @@ public class RouteEventSubscriber { private final BiConsumer dirtyMarker; - private final TopicRouteService topicRouteService; private final DefaultMQPushConsumer consumer; private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); - public RouteEventSubscriber(TopicRouteService topicRouteService, BiConsumer dirtyMarker) { - this.topicRouteService = topicRouteService; + public RouteEventSubscriber(BiConsumer dirtyMarker) { this.dirtyMarker = dirtyMarker; this.consumer = new DefaultMQPushConsumer("PROXY_ROUTE_EVENT_GROUP"); this.consumer.setMessageModel(MessageModel.BROADCASTING); @@ -70,29 +67,20 @@ private void processMessages(List msgs) { Map event = JSON.parseObject(json, Map.class); LOGGER.info("[ROUTE_EVENT]: accept event {}", event); - String brokerName = (String) event.get(RouteEventConstants.BROKER_NAME); RouteEventType eventType = RouteEventType.valueOf((String) event.get(RouteEventConstants.EVENT_TYPE)); Long eventTimeStamp = (Long) event.get(RouteEventConstants.TIMESTAMP); switch (eventType) { - case SHUTDOWN: - Set topics = this.topicRouteService.getBrokerTopics(brokerName); - this.topicRouteService.removeBrokerToTopics(brokerName); - - for (String topic : topics) { - dirtyMarker.accept(topic, eventTimeStamp); - } - break; - case START: + case SHUTDOWN: case TOPIC_CHANGE: - String affectedTopic = (String) event.get(RouteEventConstants.AFFECTED_TOPIC); - - if (affectedTopic != null) { - this.topicRouteService.removeBrokerToTopic(brokerName, affectedTopic); + List affectedTopics = (List) event.get(RouteEventConstants.AFFECTED_TOPICS); - dirtyMarker.accept(affectedTopic, eventTimeStamp); + if (affectedTopics != null) { + for (String topic : affectedTopics) { + dirtyMarker.accept(topic, eventTimeStamp); + } } else { LOGGER.info("[ROUTE_UPDATE] No affected topic specified in event: {}", event); } diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java index 411e69947b0..02b068a521a 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java @@ -21,16 +21,11 @@ import com.github.benmanes.caffeine.cache.LoadingCache; import java.time.Duration; -import java.util.Collections; import java.util.List; import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; import org.apache.rocketmq.client.ClientConfig; import org.apache.rocketmq.client.exception.MQClientException; @@ -52,7 +47,6 @@ import org.apache.rocketmq.proxy.config.ProxyConfig; import org.apache.rocketmq.remoting.protocol.ResponseCode; import org.apache.rocketmq.remoting.protocol.header.GetMaxOffsetRequestHeader; -import org.apache.rocketmq.remoting.protocol.route.BrokerData; import org.apache.rocketmq.remoting.protocol.route.TopicRouteData; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -65,7 +59,6 @@ public abstract class TopicRouteService extends AbstractStartAndShutdown { private RouteEventSubscriber routeEventSubscriber; private RouteCacheRefresher routeCacheRefresher; - private final ConcurrentMap> brokerToTopics = new ConcurrentHashMap<>(); protected final LoadingCache topicCache; protected final ScheduledExecutorService scheduledExecutorService; @@ -96,7 +89,6 @@ public TopicRouteService(MQClientAPIFactory mqClientAPIFactory) { public @Nullable MessageQueueView load(String topic) throws Exception { try { TopicRouteData topicRouteData = mqClientAPIFactory.getClient().getTopicRouteInfoFromNameServer(topic, Duration.ofSeconds(3).toMillis()); - updateBrokerTopicMapping(topic, topicRouteData); log.info("[topicCache]: load topic: {} topicRouteData: {}", topic, topicRouteData); return buildMessageQueueView(topic, topicRouteData); @@ -156,7 +148,6 @@ public String resolve(String name) { ); this.routeEventSubscriber = new RouteEventSubscriber( - this, (topic, timeStamp) -> { this.routeCacheRefresher.markCacheDirty(topic, timeStamp); } @@ -165,36 +156,6 @@ public String resolve(String name) { this.init(); } - private void updateBrokerTopicMapping(String topic, TopicRouteData route) { - Set currentBrokers = route.getBrokerDatas().stream() - .map(BrokerData::getBrokerName) - .collect(Collectors.toSet()); - - for (String broker : currentBrokers) { - brokerToTopics.computeIfAbsent(broker, k -> ConcurrentHashMap.newKeySet()) - .add(topic); - } - } - - public Set getBrokerTopics(String brokerName) { - return brokerToTopics.getOrDefault(brokerName, Collections.emptySet()); - } - - public void removeBrokerToTopics(String brokerName) { - brokerToTopics.remove(brokerName); - } - - public void removeBrokerToTopic(String brokerName, String topic) { - Set topics = brokerToTopics.get(brokerName); - if (topics != null) { - topics.remove(topic); - - if (topics.isEmpty()) { - brokerToTopics.remove(brokerName); - } - } - } - private void markCompleted(String topic) { if (routeCacheRefresher != null) { routeCacheRefresher.markCompleted(topic); diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriberTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriberTest.java index 7012acadc1f..d796e6ee313 100644 --- a/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriberTest.java +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriberTest.java @@ -21,15 +21,12 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.function.BiConsumer; import org.apache.rocketmq.common.message.MessageExt; @@ -39,15 +36,13 @@ import com.alibaba.fastjson2.JSON; public class RouteEventSubscriberTest { - private TopicRouteService mockRouteService; private BiConsumer mockDirtyMarker; private RouteEventSubscriber subscriber; @Before public void setUp() { - mockRouteService = mock(TopicRouteService.class); mockDirtyMarker = mock(BiConsumer.class); - subscriber = new RouteEventSubscriber(mockRouteService, mockDirtyMarker); + subscriber = new RouteEventSubscriber(mockDirtyMarker); } @Test @@ -56,16 +51,13 @@ public void testHandleShutdownEvent() throws Exception { eventData.put("eventType", "SHUTDOWN"); eventData.put("brokerName", "TestBroker"); eventData.put("timestamp", System.currentTimeMillis()); + eventData.put("affectedTopics", Arrays.asList("TopicA", "TopicB")); MessageExt msg = new MessageExt(); msg.setBody(JSON.toJSONString(eventData).getBytes()); - Set topics = new HashSet<>(Arrays.asList("TopicA", "TopicB")); - when(mockRouteService.getBrokerTopics("TestBroker")).thenReturn(topics); - invokePrivateMethod(subscriber, "processMessages", Collections.singletonList(msg)); - verify(mockRouteService).removeBrokerToTopics("TestBroker"); verify(mockDirtyMarker).accept(eq("TopicA"), anyLong()); verify(mockDirtyMarker).accept(eq("TopicB"), anyLong()); } @@ -75,7 +67,7 @@ public void testHandleTopicChangeEvent() throws Exception { Map eventData = new HashMap<>(); eventData.put("eventType", "TOPIC_CHANGE"); eventData.put("brokerName", "TestBroker"); - eventData.put("affectedTopic", "TestTopic"); + eventData.put("affectedTopics", Collections.singletonList("TestTopic")); eventData.put("timestamp", System.currentTimeMillis()); MessageExt msg = new MessageExt(); @@ -83,7 +75,6 @@ public void testHandleTopicChangeEvent() throws Exception { invokePrivateMethod(subscriber, "processMessages", Collections.singletonList(msg)); - verify(mockRouteService).removeBrokerToTopic(eq("TestBroker"), eq("TestTopic")); verify(mockDirtyMarker).accept(eq("TestTopic"), anyLong()); } @@ -94,4 +85,4 @@ private void invokePrivateMethod(Object obj, String methodName, List method.setAccessible(true); method.invoke(obj, arg); } -} +} \ No newline at end of file From ffd4795da67f8b783424f939596a67456889b5d1 Mon Sep 17 00:00:00 2001 From: saz97 Date: Mon, 22 Sep 2025 09:57:21 +0800 Subject: [PATCH 15/19] =?UTF-8?q?=E8=A7=A3=E5=86=B3comments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将BrokerController中的START事件发布时机移至Broker registerBrokerAll后执行 - 在发送START和SHUTDOWN事件前,增加enableRouteChangeNotification配置检查 - 将RouteEventService中MAX_TOPICS_PER_EVENT批次大小从100调整为5000 - 在TopicConfigManager中新增带publishEvent参数的deleteTopicConfig重载方法 - 修改SlaveSynchronize在同步操作时调用publishEvent=false的deleteTopicConfig方法 - 将TOPIC_CHANGE逻辑统一收敛到registerBrokerData方法中 - 在RouteEventSubscriber构造函数中设置消费位点为CONSUME_FROM_LAST_OFFSET --- .../rocketmq/broker/BrokerController.java | 23 +++++++++++-------- .../broker/route/RouteEventService.java | 2 +- .../broker/slave/SlaveSynchronize.java | 2 +- .../broker/topic/TopicConfigManager.java | 22 +++++++++--------- .../broker/route/RouteEventServiceTest.java | 8 +++---- .../service/route/RouteEventSubscriber.java | 2 ++ .../route/RouteCacheRefresherTest.java | 2 +- .../route/RouteEventSubscriberTest.java | 6 ++--- 8 files changed, 36 insertions(+), 31 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 50770e89318..79c4c49131c 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java @@ -1404,7 +1404,8 @@ protected void shutdownBasicService() { this.unregisterBrokerAll(); - if (this.routeEventService != null && this.topicConfigManager != null) { + if (this.routeEventService != null && this.topicConfigManager != null + && this.brokerConfig.isEnableRouteChangeNotification()) { Set topics = this.topicConfigManager.getTopicConfigTable().keySet(); try { this.routeEventService.publishEvent(RouteEventType.SHUTDOWN, topics); @@ -1799,6 +1800,17 @@ public void start() throws Exception { if (!isIsolated && !this.messageStoreConfig.isEnableDLegerCommitLog() && !this.messageStoreConfig.isDuplicationEnable()) { changeSpecialServiceStatus(this.brokerConfig.getBrokerId() == MixAll.MASTER_ID); this.registerBrokerAll(true, false, true); + + if (this.routeEventService != null && this.topicConfigManager != null + && this.brokerConfig.isEnableRouteChangeNotification()) { + Set topics = this.topicConfigManager.getTopicConfigTable().keySet(); + try { + this.routeEventService.publishEvent(RouteEventType.START, topics); + LOG.info("[START]: publish {}", topics); + } catch (Exception e) { + LOG.error("Failed to publish route change event for topic: {}", topics, e); + } + } } scheduledFutures.add(this.scheduledExecutorService.scheduleAtFixedRate(new AbstractBrokerRunnable(this.getBrokerIdentity()) { @@ -1854,15 +1866,6 @@ public void run() { } }, 10, 5, TimeUnit.SECONDS); - if (this.routeEventService != null && this.topicConfigManager != null) { - Set topics = this.topicConfigManager.getTopicConfigTable().keySet(); - try { - this.routeEventService.publishEvent(RouteEventType.START, topics); - LOG.info("[START]: publish {}", topics); - } catch (Exception e) { - LOG.error("Failed to publish route change event for topic: {}", topics, e); - } - } } protected void scheduleSendHeartbeat() { diff --git a/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventService.java b/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventService.java index 844f18de778..b0599f92851 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventService.java @@ -36,7 +36,7 @@ public class RouteEventService { private static final Logger LOG = LoggerFactory.getLogger(LoggerName.BROKER_LOGGER_NAME); private final BrokerController brokerController; - private static final int MAX_TOPICS_PER_EVENT = 100; + private static final int MAX_TOPICS_PER_EVENT = 5000; public RouteEventService(BrokerController brokerController) { this.brokerController = brokerController; diff --git a/broker/src/main/java/org/apache/rocketmq/broker/slave/SlaveSynchronize.java b/broker/src/main/java/org/apache/rocketmq/broker/slave/SlaveSynchronize.java index ee716a565d7..580e5a66418 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/slave/SlaveSynchronize.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/slave/SlaveSynchronize.java @@ -95,7 +95,7 @@ private void syncTopicConfig() { if (!newTopicConfigTable.containsKey(entry.getKey())) { iterator.remove(); } - topicConfigManager.deleteTopicConfig(entry.getKey()); + topicConfigManager.deleteTopicConfig(entry.getKey(), false); } //update diff --git a/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java b/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java index 2b153a88ceb..fb795a89a5f 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java @@ -322,8 +322,6 @@ public TopicConfig createTopicInSendMessageMethod(final String topic, final Stri if (createNew) { registerBrokerData(topicConfig); - - publishTopicChangeEvent(topicConfig.getTopicName()); } return topicConfig; @@ -363,8 +361,6 @@ public TopicConfig createTopicIfAbsent(TopicConfig topicConfig, boolean register } if (createNew && register) { registerBrokerData(topicConfig); - - publishTopicChangeEvent(topicConfig.getTopicName()); } return getTopicConfig(topicConfig.getTopicName()); } @@ -424,8 +420,6 @@ public TopicConfig createTopicInSendMessageBackMethod( if (createNew) { registerBrokerData(topicConfig); - - publishTopicChangeEvent(topicConfig.getTopicName()); } return topicConfig; @@ -466,8 +460,6 @@ public TopicConfig createTopicOfTranCheckMaxTime(final int clientDefaultTopicQue if (createNew) { registerBrokerData(topicConfig); - - publishTopicChangeEvent(topicConfig.getTopicName()); } return topicConfig; @@ -616,23 +608,29 @@ public boolean isOrderTopic(final String topic) { } public void deleteTopicConfig(final String topic) { + deleteTopicConfig(topic, true); + } + + public void deleteTopicConfig(final String topic, boolean publishEvent) { TopicConfig old = removeTopicConfig(topic); if (old != null) { log.info("delete topic config OK, topic: {}", old); updateDataVersion(); this.persist(); - publishTopicChangeEvent(topic); + if (publishEvent) { + publishTopicChangeEvent(topic); + } } else { log.warn("delete topic config failed, topic: {} not exists", topic); } } private void publishTopicChangeEvent(String topicName) { - if (this.brokerController.getBrokerConfig().isEnableRouteChangeNotification() + if (this.brokerController.getBrokerConfig().isEnableRouteChangeNotification() && this.brokerController.getRouteEventService() != null) { this.brokerController.getRouteEventService().publishEvent( - RouteEventType.TOPIC_CHANGE, + RouteEventType.TOPIC_CHANGE, Collections.singleton(topicName) ); } @@ -779,6 +777,8 @@ private void registerBrokerData(TopicConfig topicConfig) { } else { this.brokerController.registerIncrementBrokerData(topicConfig, dataVersion); } + + publishTopicChangeEvent(topicConfig.getTopicName()); } public boolean containsTopic(String topic) { diff --git a/broker/src/test/java/org/apache/rocketmq/broker/route/RouteEventServiceTest.java b/broker/src/test/java/org/apache/rocketmq/broker/route/RouteEventServiceTest.java index 29386648cff..dd07422a7a7 100644 --- a/broker/src/test/java/org/apache/rocketmq/broker/route/RouteEventServiceTest.java +++ b/broker/src/test/java/org/apache/rocketmq/broker/route/RouteEventServiceTest.java @@ -51,17 +51,17 @@ public class RouteEventServiceTest { public void setUp() { BrokerConfig brokerConfig = new BrokerConfig(); brokerConfig.setEnableRouteChangeNotification(true); - + brokerController = new BrokerController( brokerConfig, new NettyServerConfig(), new NettyClientConfig(), new MessageStoreConfig() ); - + mockMessageStore = mock(MessageStore.class); brokerController.setMessageStore(mockMessageStore); - + routeEventService = new RouteEventService(brokerController); } @@ -84,7 +84,7 @@ public void testIncludeTopicInEvent() { verify(mockMessageStore).putMessage(captor.capture()); Map eventData = JSON.parseObject( - new String(captor.getValue().getBody()), + new String(captor.getValue().getBody()), Map.class ); diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java index b5d4454eebf..7127bb15f70 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java @@ -28,6 +28,7 @@ import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.constant.LoggerName; +import org.apache.rocketmq.common.consumer.ConsumeFromWhere; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.logging.org.slf4j.Logger; @@ -45,6 +46,7 @@ public RouteEventSubscriber(BiConsumer dirtyMarker) { this.dirtyMarker = dirtyMarker; this.consumer = new DefaultMQPushConsumer("PROXY_ROUTE_EVENT_GROUP"); this.consumer.setMessageModel(MessageModel.BROADCASTING); + this.consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET); } public void start() { try { diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresherTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresherTest.java index a4779bd6d94..a7a56d01225 100644 --- a/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresherTest.java +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresherTest.java @@ -83,7 +83,7 @@ public void testRefreshFailureAddsToRetryQueue() throws Exception { assertTrue(pendingTopics.contains("TestTopic")); } - private void invokePrivateMethod(Object obj, String methodName, String arg) + private void invokePrivateMethod(Object obj, String methodName, String arg) throws Exception { java.lang.reflect.Method method = obj.getClass().getDeclaredMethod(methodName, String.class); diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriberTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriberTest.java index d796e6ee313..631276972c1 100644 --- a/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriberTest.java +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriberTest.java @@ -74,15 +74,15 @@ public void testHandleTopicChangeEvent() throws Exception { msg.setBody(JSON.toJSONString(eventData).getBytes()); invokePrivateMethod(subscriber, "processMessages", Collections.singletonList(msg)); - + verify(mockDirtyMarker).accept(eq("TestTopic"), anyLong()); } - private void invokePrivateMethod(Object obj, String methodName, List arg) + private void invokePrivateMethod(Object obj, String methodName, List arg) throws Exception { java.lang.reflect.Method method = obj.getClass().getDeclaredMethod(methodName, List.class); method.setAccessible(true); method.invoke(obj, arg); } -} \ No newline at end of file +} From a29868089903cb80f0bd8b09c94c8027be74cc87 Mon Sep 17 00:00:00 2001 From: saz97 Date: Mon, 22 Sep 2025 18:16:47 +0800 Subject: [PATCH 16/19] =?UTF-8?q?=E9=87=8D=E6=9E=84=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E5=8F=98=E6=9B=B4=E9=80=9A=E7=9F=A5=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 将RouteEventSubscriber和RouteCacheRefresher合并为RouteChangeNotifier 2. 简化BrokerController中事件发布代码,改为直接调用routeEventService封装方法 3. 在RouteEventService中添加publishEvent重载方法 4. 更新相关测试代码适应新的类结构 --- .../rocketmq/broker/BrokerController.java | 25 +-- .../broker/route/RouteEventConstants.java | 2 +- .../broker/route/RouteEventService.java | 40 ++++- .../broker/slave/SlaveSynchronize.java | 2 +- .../broker/topic/TopicConfigManager.java | 26 +-- .../broker/route/RouteEventServiceTest.java | 21 ++- ...efresher.java => RouteChangeNotifier.java} | 123 ++++++++++++--- .../service/route/RouteEventSubscriber.java | 104 ------------ .../service/route/TopicRouteService.java | 29 +--- .../route/RouteCacheRefresherTest.java | 93 ----------- .../route/RouteChangeNotifierTest.java | 148 ++++++++++++++++++ .../route/RouteEventSubscriberTest.java | 88 ----------- 12 files changed, 323 insertions(+), 378 deletions(-) rename proxy/src/main/java/org/apache/rocketmq/proxy/service/route/{RouteCacheRefresher.java => RouteChangeNotifier.java} (51%) delete mode 100644 proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java delete mode 100644 proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresherTest.java create mode 100644 proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteChangeNotifierTest.java delete mode 100644 proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriberTest.java 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 79c4c49131c..7ec51d0db41 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java @@ -167,7 +167,6 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -1404,15 +1403,8 @@ protected void shutdownBasicService() { this.unregisterBrokerAll(); - if (this.routeEventService != null && this.topicConfigManager != null - && this.brokerConfig.isEnableRouteChangeNotification()) { - Set topics = this.topicConfigManager.getTopicConfigTable().keySet(); - try { - this.routeEventService.publishEvent(RouteEventType.SHUTDOWN, topics); - LOG.info("[SHUTDOWN AFTER unregisterBrokerAll]: publish {}", topics); - } catch (Exception e) { - LOG.error("Failed to publish route change event for topic: {}", topics, e); - } + if (this.routeEventService != null) { + this.routeEventService.publishEvent(RouteEventType.SHUTDOWN); } if (this.shutdownHook != null) { @@ -1800,17 +1792,10 @@ public void start() throws Exception { if (!isIsolated && !this.messageStoreConfig.isEnableDLegerCommitLog() && !this.messageStoreConfig.isDuplicationEnable()) { changeSpecialServiceStatus(this.brokerConfig.getBrokerId() == MixAll.MASTER_ID); this.registerBrokerAll(true, false, true); - - if (this.routeEventService != null && this.topicConfigManager != null - && this.brokerConfig.isEnableRouteChangeNotification()) { - Set topics = this.topicConfigManager.getTopicConfigTable().keySet(); - try { - this.routeEventService.publishEvent(RouteEventType.START, topics); - LOG.info("[START]: publish {}", topics); - } catch (Exception e) { - LOG.error("Failed to publish route change event for topic: {}", topics, e); - } + if (this.routeEventService != null) { + this.routeEventService.publishEvent(RouteEventType.START); } + } scheduledFutures.add(this.scheduledExecutorService.scheduleAtFixedRate(new AbstractBrokerRunnable(this.getBrokerIdentity()) { diff --git a/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventConstants.java b/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventConstants.java index 48bc0e25da6..ac956c66e12 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventConstants.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventConstants.java @@ -23,4 +23,4 @@ public class RouteEventConstants { public static final String BROKER_ID = "brokerId"; public static final String TIMESTAMP = "timestamp"; public static final String AFFECTED_TOPICS = "affectedTopics"; -} \ No newline at end of file +} diff --git a/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventService.java b/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventService.java index b0599f92851..44986f6ab52 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventService.java @@ -18,6 +18,7 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -44,19 +45,46 @@ public RouteEventService(BrokerController brokerController) { brokerController.getBrokerConfig().getBrokerName()); } - public void publishEvent(RouteEventType eventType, Set topics) { + public void publishEvent(RouteEventType eventType) { if (!brokerController.getBrokerConfig().isEnableRouteChangeNotification()) { return; } - if (topics == null || topics.isEmpty()) { - sendEvent(eventType, null); + if (brokerController.getTopicConfigManager() == null) { return; } - List topicList = new ArrayList<>(topics); - partitionTopics(topicList, MAX_TOPICS_PER_EVENT) - .forEach(batch -> sendEvent(eventType, batch)); + Set topics = brokerController.getTopicConfigManager().getTopicConfigTable().keySet(); + publishEventInternal(eventType, topics); + } + + public void publishEvent(RouteEventType eventType, String topicName) { + if (!brokerController.getBrokerConfig().isEnableRouteChangeNotification()) { + return; + } + + if (topicName == null) { + return; + } + + publishEventInternal(eventType, Collections.singleton(topicName)); + } + + private void publishEventInternal(RouteEventType eventType, Set topics) { + try { + if (topics == null || topics.isEmpty()) { + sendEvent(eventType, null); + return; + } + + List topicList = new ArrayList<>(topics); + partitionTopics(topicList, MAX_TOPICS_PER_EVENT) + .forEach(batch -> sendEvent(eventType, batch)); + + LOG.info("[{}]: published event for {} topics", eventType, topics.size()); + } catch (Exception e) { + LOG.error("Failed to publish {} event for topics: {}", eventType, topics, e); + } } private void sendEvent(RouteEventType eventType, List topics) { diff --git a/broker/src/main/java/org/apache/rocketmq/broker/slave/SlaveSynchronize.java b/broker/src/main/java/org/apache/rocketmq/broker/slave/SlaveSynchronize.java index 580e5a66418..ee716a565d7 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/slave/SlaveSynchronize.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/slave/SlaveSynchronize.java @@ -95,7 +95,7 @@ private void syncTopicConfig() { if (!newTopicConfigTable.containsKey(entry.getKey())) { iterator.remove(); } - topicConfigManager.deleteTopicConfig(entry.getKey(), false); + topicConfigManager.deleteTopicConfig(entry.getKey()); } //update diff --git a/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java b/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java index fb795a89a5f..b803021f9e9 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicConfigManager.java @@ -16,7 +16,6 @@ */ package org.apache.rocketmq.broker.topic; -import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -608,34 +607,16 @@ public boolean isOrderTopic(final String topic) { } public void deleteTopicConfig(final String topic) { - deleteTopicConfig(topic, true); - } - - public void deleteTopicConfig(final String topic, boolean publishEvent) { TopicConfig old = removeTopicConfig(topic); if (old != null) { log.info("delete topic config OK, topic: {}", old); updateDataVersion(); this.persist(); - - if (publishEvent) { - publishTopicChangeEvent(topic); - } } else { log.warn("delete topic config failed, topic: {} not exists", topic); } } - private void publishTopicChangeEvent(String topicName) { - if (this.brokerController.getBrokerConfig().isEnableRouteChangeNotification() - && this.brokerController.getRouteEventService() != null) { - this.brokerController.getRouteEventService().publishEvent( - RouteEventType.TOPIC_CHANGE, - Collections.singleton(topicName) - ); - } - } - public TopicConfigSerializeWrapper buildTopicConfigSerializeWrapper() { TopicConfigSerializeWrapper topicConfigSerializeWrapper = new TopicConfigSerializeWrapper(); topicConfigSerializeWrapper.setTopicConfigTable(this.topicConfigTable); @@ -777,8 +758,13 @@ private void registerBrokerData(TopicConfig topicConfig) { } else { this.brokerController.registerIncrementBrokerData(topicConfig, dataVersion); } + if (this.brokerController.getBrokerConfig().isEnableRouteChangeNotification()) { + this.brokerController.getRouteEventService().publishEvent( + RouteEventType.TOPIC_CHANGE, + topicConfig.getTopicName() + ); + } - publishTopicChangeEvent(topicConfig.getTopicName()); } public boolean containsTopic(String topic) { diff --git a/broker/src/test/java/org/apache/rocketmq/broker/route/RouteEventServiceTest.java b/broker/src/test/java/org/apache/rocketmq/broker/route/RouteEventServiceTest.java index dd07422a7a7..606875bc21e 100644 --- a/broker/src/test/java/org/apache/rocketmq/broker/route/RouteEventServiceTest.java +++ b/broker/src/test/java/org/apache/rocketmq/broker/route/RouteEventServiceTest.java @@ -15,6 +15,23 @@ * limitations under the License. */ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.apache.rocketmq.broker.route; import static org.junit.Assert.assertEquals; @@ -69,7 +86,7 @@ public void setUp() { public void testPublishEventSuccessfully() { when(mockMessageStore.putMessage(any())).thenReturn(new PutMessageResult(PutMessageStatus.PUT_OK, null)); - routeEventService.publishEvent(RouteEventType.SHUTDOWN, Collections.singleton("TestTopic")); + routeEventService.publishEvent(RouteEventType.SHUTDOWN, "TestTopic"); verify(mockMessageStore).putMessage(any(MessageExtBrokerInner.class)); } @@ -78,7 +95,7 @@ public void testPublishEventSuccessfully() { public void testIncludeTopicInEvent() { when(mockMessageStore.putMessage(any())).thenReturn(new PutMessageResult(PutMessageStatus.PUT_OK, null)); - routeEventService.publishEvent(RouteEventType.TOPIC_CHANGE, Collections.singleton("TestTopic")); + routeEventService.publishEvent(RouteEventType.TOPIC_CHANGE, "TestTopic"); ArgumentCaptor captor = ArgumentCaptor.forClass(MessageExtBrokerInner.class); verify(mockMessageStore).putMessage(captor.capture()); diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresher.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteChangeNotifier.java similarity index 51% rename from proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresher.java rename to proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteChangeNotifier.java index 991ab693908..9b031d41ca9 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresher.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteChangeNotifier.java @@ -14,19 +14,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.apache.rocketmq.proxy.service.route; -import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.logging.org.slf4j.Logger; -import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; -import org.apache.rocketmq.common.ThreadFactoryImpl; - -import com.github.benmanes.caffeine.cache.LoadingCache; -import org.apache.rocketmq.common.utils.ThreadUtils; - +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; @@ -34,31 +27,123 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; -public class RouteCacheRefresher { - private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); +import org.apache.rocketmq.common.message.MessageExt; +import org.apache.rocketmq.common.ThreadFactoryImpl; +import org.apache.rocketmq.broker.route.RouteEventConstants; +import org.apache.rocketmq.broker.route.RouteEventType; +import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; +import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; +import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; +import org.apache.rocketmq.client.exception.MQClientException; +import org.apache.rocketmq.common.constant.LoggerName; +import org.apache.rocketmq.common.consumer.ConsumeFromWhere; +import org.apache.rocketmq.common.topic.TopicValidator; +import org.apache.rocketmq.common.utils.ThreadUtils; +import org.apache.rocketmq.logging.org.slf4j.Logger; +import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; +import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; + +import com.github.benmanes.caffeine.cache.LoadingCache; +import com.alibaba.fastjson2.JSON; +public class RouteChangeNotifier { + private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); + private final LoadingCache topicCache; private final ThreadPoolExecutor executor; - + private final ConcurrentMap dirtyTopics = new ConcurrentHashMap<>(); private final Queue pendingTopics = new ConcurrentLinkedQueue<>(); private final ScheduledExecutorService scheduler; - - public RouteCacheRefresher(LoadingCache topicCache, - ThreadPoolExecutor executor) { + + private final DefaultMQPushConsumer consumer; + private final BiConsumer dirtyMarker; + + public RouteChangeNotifier(LoadingCache topicCache, + ThreadPoolExecutor executor) { this.topicCache = topicCache; this.executor = executor; this.scheduler = ThreadUtils.newSingleThreadScheduledExecutor( - new ThreadFactoryImpl("RouteCacheScheduler_") + new ThreadFactoryImpl("RouteUpdaterScheduler_") ); + + this.consumer = new DefaultMQPushConsumer("PROXY_ROUTE_EVENT_GROUP"); + this.consumer.setMessageModel(MessageModel.BROADCASTING); + this.consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET); + + this.dirtyMarker = (topic, timeStamp) -> { + markCacheDirty(topic, timeStamp); + }; } public void start() { + startEventSubscription(); + scheduler.scheduleWithFixedDelay(this::processDirtyTopics, 50, 200, TimeUnit.MILLISECONDS); } + public void shutdown() { + if (consumer != null) { + consumer.shutdown(); + } + + if (scheduler != null) { + scheduler.shutdown(); + } + } + + private void startEventSubscription() { + try { + consumer.subscribe(TopicValidator.RMQ_ROUTE_EVENT_TOPIC, "*"); + consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { + processEventMessages(msgs); + return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; + }); + + consumer.start(); + } catch (MQClientException e) { + log.error("Failed to start route event consumer", e); + } + } + + private void processEventMessages(List msgs) { + for (MessageExt msg : msgs) { + try { + String json = new String(msg.getBody(), StandardCharsets.UTF_8); + Map event = JSON.parseObject(json, Map.class); + + log.info("[ROUTE_EVENT]: accept event {}", event); + RouteEventType eventType = RouteEventType.valueOf((String) event.get(RouteEventConstants.EVENT_TYPE)); + Long eventTimeStamp = (Long) event.get(RouteEventConstants.TIMESTAMP); + + switch (eventType) { + case START: + case SHUTDOWN: + case TOPIC_CHANGE: + List affectedTopics = (List) event.get(RouteEventConstants.AFFECTED_TOPICS); + + if (affectedTopics != null) { + for (String topic : affectedTopics) { + dirtyMarker.accept(topic, eventTimeStamp); + } + } else { + log.info("[ROUTE_UPDATE] No affected topic specified in event: {}", event); + } + break; + + default: + break; + } + + } catch (Exception e) { + log.error("[ROUTE_UPDATE]: Error processing route event", e); + } + } + } + public void markCacheDirty(String topic, long timeStamp) { long currentTime = System.currentTimeMillis(); if (currentTime - timeStamp > TimeUnit.MINUTES.toMillis(1)) { @@ -102,19 +187,13 @@ private void processDirtyTopics() { private void refreshSingleRoute(String topic) { try { if (topicCache.getIfPresent(topic) == null) { - log.warn("No cache entry found for topic: {}", topic); return; } topicCache.refresh(topic); - log.info("[ROUTE_NOTIFICATION]: Refresh topic: {}", topic); } catch (Exception e) { log.error("Refresh failed for: {}", topic, e); } } - - public void shutdown() { - scheduler.shutdown(); - } } diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java deleted file mode 100644 index 7127bb15f70..00000000000 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriber.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.rocketmq.proxy.service.route; - -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.Map; -import java.util.function.BiConsumer; - -import org.apache.rocketmq.broker.route.RouteEventConstants; -import org.apache.rocketmq.broker.route.RouteEventType; -import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; -import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; -import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; -import org.apache.rocketmq.client.exception.MQClientException; -import org.apache.rocketmq.common.constant.LoggerName; -import org.apache.rocketmq.common.consumer.ConsumeFromWhere; -import org.apache.rocketmq.common.message.MessageExt; -import org.apache.rocketmq.common.topic.TopicValidator; -import org.apache.rocketmq.logging.org.slf4j.Logger; -import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; -import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel; - -import com.alibaba.fastjson2.JSON; - -public class RouteEventSubscriber { - private final BiConsumer dirtyMarker; - private final DefaultMQPushConsumer consumer; - private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); - - public RouteEventSubscriber(BiConsumer dirtyMarker) { - this.dirtyMarker = dirtyMarker; - this.consumer = new DefaultMQPushConsumer("PROXY_ROUTE_EVENT_GROUP"); - this.consumer.setMessageModel(MessageModel.BROADCASTING); - this.consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET); - } - public void start() { - try { - consumer.subscribe(TopicValidator.RMQ_ROUTE_EVENT_TOPIC, "*"); - consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> { - processMessages(msgs); - return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; - }); - - consumer.start(); - } catch (MQClientException e) { - LOGGER.error("Failed to start route event consumer", e); - } - } - - private void processMessages(List msgs) { - for (MessageExt msg : msgs) { - try { - String json = new String(msg.getBody(), StandardCharsets.UTF_8); - Map event = JSON.parseObject(json, Map.class); - - LOGGER.info("[ROUTE_EVENT]: accept event {}", event); - RouteEventType eventType = RouteEventType.valueOf((String) event.get(RouteEventConstants.EVENT_TYPE)); - Long eventTimeStamp = (Long) event.get(RouteEventConstants.TIMESTAMP); - - - switch (eventType) { - case START: - case SHUTDOWN: - case TOPIC_CHANGE: - List affectedTopics = (List) event.get(RouteEventConstants.AFFECTED_TOPICS); - - if (affectedTopics != null) { - for (String topic : affectedTopics) { - dirtyMarker.accept(topic, eventTimeStamp); - } - } else { - LOGGER.info("[ROUTE_UPDATE] No affected topic specified in event: {}", event); - } - break; - - default: - break; - } - - } catch (Exception e) { - LOGGER.error("[ROUTE_UPDATE]: Error processing route event", e); - } - } - } - - public void shutdown() { - consumer.shutdown(); - } -} diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java index 02b068a521a..a21f6d3b1e6 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java @@ -57,8 +57,7 @@ public abstract class TopicRouteService extends AbstractStartAndShutdown { private final MQClientAPIFactory mqClientAPIFactory; private MQFaultStrategy mqFaultStrategy; - private RouteEventSubscriber routeEventSubscriber; - private RouteCacheRefresher routeCacheRefresher; + private RouteChangeNotifier routeChangeNotifier; protected final LoadingCache topicCache; protected final ScheduledExecutorService scheduledExecutorService; @@ -89,7 +88,6 @@ public TopicRouteService(MQClientAPIFactory mqClientAPIFactory) { public @Nullable MessageQueueView load(String topic) throws Exception { try { TopicRouteData topicRouteData = mqClientAPIFactory.getClient().getTopicRouteInfoFromNameServer(topic, Duration.ofSeconds(3).toMillis()); - log.info("[topicCache]: load topic: {} topicRouteData: {}", topic, topicRouteData); return buildMessageQueueView(topic, topicRouteData); } catch (Exception e) { @@ -104,7 +102,10 @@ public TopicRouteService(MQClientAPIFactory mqClientAPIFactory) { public @Nullable MessageQueueView reload(@NonNull String key, @NonNull MessageQueueView oldValue) throws Exception { try { - markCompleted(key); + if (routeChangeNotifier != null) { + routeChangeNotifier.markCompleted(key); + } + return load(key); } catch (Exception e) { log.warn(String.format("reload topic route from namesrv. topic: %s", key), e); @@ -142,26 +143,14 @@ public String resolve(String name) { } }, serviceDetector); - this.routeCacheRefresher = new RouteCacheRefresher( + this.routeChangeNotifier = new RouteChangeNotifier( this.topicCache, this.cacheRefreshExecutor ); - this.routeEventSubscriber = new RouteEventSubscriber( - (topic, timeStamp) -> { - this.routeCacheRefresher.markCacheDirty(topic, timeStamp); - } - ); - this.init(); } - private void markCompleted(String topic) { - if (routeCacheRefresher != null) { - routeCacheRefresher.markCompleted(topic); - } - } - // pickup one topic in the topic cache private Optional pickTopic() { if (topicCache.asMap().isEmpty()) { @@ -182,8 +171,7 @@ public void shutdown() throws Exception { } if (ConfigurationManager.getProxyConfig().isEnableRouteChangeNotification()) { - this.routeCacheRefresher.shutdown(); - this.routeEventSubscriber.shutdown(); + this.routeChangeNotifier.shutdown(); } } @@ -194,8 +182,7 @@ public void start() throws Exception { } if (ConfigurationManager.getProxyConfig().isEnableRouteChangeNotification()) { - this.routeEventSubscriber.start(); - this.routeCacheRefresher.start(); + this.routeChangeNotifier.start(); } } diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresherTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresherTest.java deleted file mode 100644 index a7a56d01225..00000000000 --- a/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteCacheRefresherTest.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.rocketmq.proxy.service.route; - -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.lang.reflect.Field; -import java.util.Queue; -import java.util.concurrent.ThreadPoolExecutor; - -import org.junit.Before; -import org.junit.Test; - -import com.github.benmanes.caffeine.cache.LoadingCache; - -public class RouteCacheRefresherTest { - private LoadingCache mockCache; - private ThreadPoolExecutor mockExecutor; - private RouteCacheRefresher refresher; - - @Before - public void setUp() { - mockCache = mock(LoadingCache.class); - mockExecutor = mock(ThreadPoolExecutor.class); - refresher = new RouteCacheRefresher(mockCache, mockExecutor); - } - - @Test - public void testMarkCacheDirtyAddsToPendingTopics() throws Exception { - refresher.markCacheDirty("TestTopic", System.currentTimeMillis()); - - Field pendingTopicsField = RouteCacheRefresher.class.getDeclaredField("pendingTopics"); - pendingTopicsField.setAccessible(true); - Queue pendingTopics = (Queue) pendingTopicsField.get(refresher); - - assertTrue(pendingTopics.contains("TestTopic")); - } - - @Test - public void testRefreshTriggersCacheRefresh() throws Exception { - refresher.markCacheDirty("TestTopic", System.currentTimeMillis()); - - when(mockCache.getIfPresent("TestTopic")).thenReturn(mock(MessageQueueView.class)); - - invokePrivateMethod(refresher, "refreshSingleRoute", "TestTopic"); - - verify(mockCache).refresh("TestTopic"); - } - - @Test - public void testRefreshFailureAddsToRetryQueue() throws Exception { - refresher.markCacheDirty("TestTopic", System.currentTimeMillis()); - - when(mockCache.getIfPresent("TestTopic")).thenReturn(mock(MessageQueueView.class)); - - doThrow(new RuntimeException("Refresh error")).when(mockCache).refresh("TestTopic"); - - invokePrivateMethod(refresher, "refreshSingleRoute", "TestTopic"); - - Field pendingTopicsField = RouteCacheRefresher.class.getDeclaredField("pendingTopics"); - pendingTopicsField.setAccessible(true); - Queue pendingTopics = (Queue) pendingTopicsField.get(refresher); - - assertTrue(pendingTopics.contains("TestTopic")); - } - - private void invokePrivateMethod(Object obj, String methodName, String arg) - throws Exception { - - java.lang.reflect.Method method = obj.getClass().getDeclaredMethod(methodName, String.class); - method.setAccessible(true); - method.invoke(obj, arg); - } -} diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteChangeNotifierTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteChangeNotifierTest.java new file mode 100644 index 00000000000..87fadfe3a96 --- /dev/null +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteChangeNotifierTest.java @@ -0,0 +1,148 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.rocketmq.proxy.service.route; + +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ThreadPoolExecutor; + +import org.apache.rocketmq.common.message.MessageExt; +import org.junit.Before; +import org.junit.Test; + +import com.alibaba.fastjson2.JSON; +import com.github.benmanes.caffeine.cache.LoadingCache; + +public class RouteChangeNotifierTest { + private LoadingCache mockCache; + private ThreadPoolExecutor mockExecutor; + private RouteChangeNotifier notifier; + + @Before + public void setUp() { + mockCache = mock(LoadingCache.class); + mockExecutor = mock(ThreadPoolExecutor.class); + notifier = new RouteChangeNotifier(mockCache, mockExecutor); + } + + @Test + public void testHandleShutdownEvent() throws Exception { + Map eventData = new HashMap<>(); + eventData.put("eventType", "SHUTDOWN"); + eventData.put("brokerName", "TestBroker"); + eventData.put("timestamp", System.currentTimeMillis()); + eventData.put("affectedTopics", Arrays.asList("TopicA", "TopicB")); + + MessageExt msg = new MessageExt(); + msg.setBody(JSON.toJSONString(eventData).getBytes()); + + invokePrivateMethod(notifier, "processEventMessages", Collections.singletonList(msg)); + + verifyMarkCacheDirtyCalled("TopicA"); + verifyMarkCacheDirtyCalled("TopicB"); + } + + @Test + public void testHandleTopicChangeEvent() throws Exception { + Map eventData = new HashMap<>(); + eventData.put("eventType", "TOPIC_CHANGE"); + eventData.put("brokerName", "TestBroker"); + eventData.put("affectedTopics", Collections.singletonList("TestTopic")); + eventData.put("timestamp", System.currentTimeMillis()); + + MessageExt msg = new MessageExt(); + msg.setBody(JSON.toJSONString(eventData).getBytes()); + + invokePrivateMethod(notifier, "processEventMessages", Collections.singletonList(msg)); + + verifyMarkCacheDirtyCalled("TestTopic"); + } + + @Test + public void testMarkCacheDirtyAddsToPendingTopics() throws Exception { + notifier.markCacheDirty("TestTopic", System.currentTimeMillis()); + + Field pendingTopicsField = RouteChangeNotifier.class.getDeclaredField("pendingTopics"); + pendingTopicsField.setAccessible(true); + Queue pendingTopics = (Queue) pendingTopicsField.get(notifier); + + assertTrue(pendingTopics.contains("TestTopic")); + } + + @Test + public void testRefreshTriggersCacheRefresh() throws Exception { + notifier.markCacheDirty("TestTopic", System.currentTimeMillis()); + + when(mockCache.getIfPresent("TestTopic")).thenReturn(mock(MessageQueueView.class)); + + invokePrivateMethod(notifier, "refreshSingleRoute", "TestTopic"); + + verify(mockCache).refresh("TestTopic"); + } + + @Test + public void testRefreshFailureAddsToRetryQueue() throws Exception { + notifier.markCacheDirty("TestTopic", System.currentTimeMillis()); + + when(mockCache.getIfPresent("TestTopic")).thenReturn(mock(MessageQueueView.class)); + + doThrow(new RuntimeException("Refresh error")).when(mockCache).refresh("TestTopic"); + + invokePrivateMethod(notifier, "refreshSingleRoute", "TestTopic"); + + Field pendingTopicsField = RouteChangeNotifier.class.getDeclaredField("pendingTopics"); + pendingTopicsField.setAccessible(true); + Queue pendingTopics = (Queue) pendingTopicsField.get(notifier); + + assertTrue(pendingTopics.contains("TestTopic")); + } + + private void invokePrivateMethod(Object obj, String methodName, Object arg) throws Exception { + java.lang.reflect.Method method; + + if (arg instanceof List) { + method = obj.getClass().getDeclaredMethod(methodName, List.class); + method.setAccessible(true); + method.invoke(obj, arg); + } else if (arg instanceof String) { + method = obj.getClass().getDeclaredMethod(methodName, String.class); + method.setAccessible(true); + method.invoke(obj, arg); + } + } + + private void verifyMarkCacheDirtyCalled(String topic) throws Exception { + Field pendingTopicsField = RouteChangeNotifier.class.getDeclaredField("pendingTopics"); + pendingTopicsField.setAccessible(true); + Queue pendingTopics = (Queue) pendingTopicsField.get(notifier); + + assertTrue("Topic " + topic + " should be in pending topics", + pendingTopics.contains(topic)); + } +} diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriberTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriberTest.java deleted file mode 100644 index 631276972c1..00000000000 --- a/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteEventSubscriberTest.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.rocketmq.proxy.service.route; - -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.BiConsumer; - -import org.apache.rocketmq.common.message.MessageExt; -import org.junit.Before; -import org.junit.Test; - -import com.alibaba.fastjson2.JSON; - -public class RouteEventSubscriberTest { - private BiConsumer mockDirtyMarker; - private RouteEventSubscriber subscriber; - - @Before - public void setUp() { - mockDirtyMarker = mock(BiConsumer.class); - subscriber = new RouteEventSubscriber(mockDirtyMarker); - } - - @Test - public void testHandleShutdownEvent() throws Exception { - Map eventData = new HashMap<>(); - eventData.put("eventType", "SHUTDOWN"); - eventData.put("brokerName", "TestBroker"); - eventData.put("timestamp", System.currentTimeMillis()); - eventData.put("affectedTopics", Arrays.asList("TopicA", "TopicB")); - - MessageExt msg = new MessageExt(); - msg.setBody(JSON.toJSONString(eventData).getBytes()); - - invokePrivateMethod(subscriber, "processMessages", Collections.singletonList(msg)); - - verify(mockDirtyMarker).accept(eq("TopicA"), anyLong()); - verify(mockDirtyMarker).accept(eq("TopicB"), anyLong()); - } - - @Test - public void testHandleTopicChangeEvent() throws Exception { - Map eventData = new HashMap<>(); - eventData.put("eventType", "TOPIC_CHANGE"); - eventData.put("brokerName", "TestBroker"); - eventData.put("affectedTopics", Collections.singletonList("TestTopic")); - eventData.put("timestamp", System.currentTimeMillis()); - - MessageExt msg = new MessageExt(); - msg.setBody(JSON.toJSONString(eventData).getBytes()); - - invokePrivateMethod(subscriber, "processMessages", Collections.singletonList(msg)); - - verify(mockDirtyMarker).accept(eq("TestTopic"), anyLong()); - } - - private void invokePrivateMethod(Object obj, String methodName, List arg) - throws Exception { - - java.lang.reflect.Method method = obj.getClass().getDeclaredMethod(methodName, List.class); - method.setAccessible(true); - method.invoke(obj, arg); - } -} From bb82369dc80e263ee47cafa942e03e4eb1cc921a Mon Sep 17 00:00:00 2001 From: saz97 Date: Mon, 22 Sep 2025 20:48:30 +0800 Subject: [PATCH 17/19] =?UTF-8?q?=E9=87=8D=E6=9E=84Broker=E4=BE=A7?= =?UTF-8?q?=E7=9A=84RouteEventService=E7=9A=84publishEvent=E6=9C=BA?= =?UTF-8?q?=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除向本地store写入的逻辑 - 基于RMQ_ROUTE_EVENT_TOPIC的路由信息,向其他相关的远端Broker发送实现消息 - 添加Broker名称去重处理,避免重复发送 - 使用putMessageToRemoteBroker进行跨Broker消息传输 --- .../broker/route/RouteEventService.java | 42 ++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventService.java b/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventService.java index 44986f6ab52..3493454121c 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventService.java @@ -20,17 +20,21 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.rocketmq.broker.BrokerController; +import org.apache.rocketmq.client.impl.producer.TopicPublishInfo; +import org.apache.rocketmq.client.producer.SendResult; +import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.common.constant.LoggerName; import org.apache.rocketmq.common.message.MessageExtBrokerInner; +import org.apache.rocketmq.common.message.MessageQueue; import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; -import org.apache.rocketmq.store.PutMessageResult; import com.alibaba.fastjson2.JSON; @@ -88,18 +92,36 @@ private void publishEventInternal(RouteEventType eventType, Set topics) } private void sendEvent(RouteEventType eventType, List topics) { - try { - Map eventData = createEventData(eventType, topics); - MessageExtBrokerInner msg = createEventMessage(eventData); + Map eventData = createEventData(eventType, topics); + MessageExtBrokerInner msg = createEventMessage(eventData); + TopicPublishInfo routeInfo = brokerController.getTopicRouteInfoManager() + .tryToFindTopicPublishInfo(TopicValidator.RMQ_ROUTE_EVENT_TOPIC); + + if (routeInfo == null || !routeInfo.ok()) { + LOG.warn("No route info for ROUTE_EVENT_TOPIC"); + return; + } - PutMessageResult result = brokerController.getMessageStore().putMessage(msg); - brokerController.getMessageStore().flush(); + String currentBroker = brokerController.getBrokerConfig().getBrokerName(); + Set processedBrokers = new HashSet<>(); + processedBrokers.add(currentBroker); - if (!result.isOk()) { - LOG.warn("[ROUTE_EVENT] Publish failed: {}", result.getPutMessageStatus()); + for (MessageQueue mq : routeInfo.getMessageQueueList()) { + if (processedBrokers.contains(mq.getBrokerName())) { + continue; + } + + try { + SendResult result = brokerController.getEscapeBridge() + .putMessageToRemoteBroker(msg, mq.getBrokerName()); + + if (result != null && result.getSendStatus() == SendStatus.SEND_OK) { + LOG.debug("Event sent to broker: {}", mq.getBrokerName()); + } + processedBrokers.add(mq.getBrokerName()); + } catch (Exception e) { + LOG.warn("Failed to send to broker: {}", mq.getBrokerName(), e); } - } catch (Exception e) { - LOG.error("[ROUTE_EVENT] Failed to publish event: {}", eventType, e); } } From 462c238aef7584b133599b93007c5bc6d49700d0 Mon Sep 17 00:00:00 2001 From: saz97 Date: Tue, 23 Sep 2025 11:21:46 +0800 Subject: [PATCH 18/19] =?UTF-8?q?=E8=A7=A3=E5=86=B3comment=20-=20=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E5=9C=A8broker=E4=BE=A7TOPIC=5FCHANGE=E4=BA=8B?= =?UTF-8?q?=E4=BB=B6=E7=9A=84=E5=8F=91=E9=80=81=E9=80=BB=E8=BE=91=EF=BC=9A?= =?UTF-8?q?=E7=9B=B4=E6=8E=A5=E5=86=99=E5=85=A5=E6=9C=AC=E5=9C=B0store=20-?= =?UTF-8?q?=20=E9=87=8D=E6=9E=84=E5=90=91=E8=BF=9C=E7=AB=AFbroker=E5=8F=91?= =?UTF-8?q?=E9=80=81=E6=B6=88=E6=81=AF=E7=9A=84=E9=80=BB=E8=BE=91=EF=BC=9A?= =?UTF-8?q?=E7=94=B1=E5=90=91=E5=85=A8=E9=83=A8broker=E5=8F=91=E9=80=81?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E5=8F=AA=E5=90=91=E4=B8=80=E4=B8=AAbroker?= =?UTF-8?q?=E5=8F=91=E9=80=81=20-=20=E4=BF=AE=E6=94=B9RouteChangeNotifier?= =?UTF-8?q?=E7=9A=84=E7=BA=BF=E7=A8=8B=E6=B1=A0=E9=85=8D=E7=BD=AE=EF=BC=8C?= =?UTF-8?q?=E5=8D=95=E7=8B=AC=E4=BD=BF=E7=94=A8=E7=8B=AC=E7=AB=8B=E7=BA=BF?= =?UTF-8?q?=E7=A8=8B=E6=B1=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../broker/route/RouteEventService.java | 51 +++++++++++-------- .../broker/route/RouteEventServiceTest.java | 2 +- .../service/route/RouteChangeNotifier.java | 22 ++++++-- .../service/route/TopicRouteService.java | 8 ++- .../route/RouteChangeNotifierTest.java | 2 +- 5 files changed, 53 insertions(+), 32 deletions(-) diff --git a/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventService.java b/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventService.java index 3493454121c..1dbc81aea0e 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventService.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/route/RouteEventService.java @@ -20,7 +20,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -35,6 +34,7 @@ import org.apache.rocketmq.common.topic.TopicValidator; import org.apache.rocketmq.logging.org.slf4j.Logger; import org.apache.rocketmq.logging.org.slf4j.LoggerFactory; +import org.apache.rocketmq.store.PutMessageResult; import com.alibaba.fastjson2.JSON; @@ -94,34 +94,45 @@ private void publishEventInternal(RouteEventType eventType, Set topics) private void sendEvent(RouteEventType eventType, List topics) { Map eventData = createEventData(eventType, topics); MessageExtBrokerInner msg = createEventMessage(eventData); - TopicPublishInfo routeInfo = brokerController.getTopicRouteInfoManager() - .tryToFindTopicPublishInfo(TopicValidator.RMQ_ROUTE_EVENT_TOPIC); - if (routeInfo == null || !routeInfo.ok()) { - LOG.warn("No route info for ROUTE_EVENT_TOPIC"); + if (eventType == RouteEventType.TOPIC_CHANGE) { + try { + PutMessageResult putResult = brokerController.getMessageStore().putMessage(msg); + brokerController.getMessageStore().flush(); + if (!putResult.isOk()) { + LOG.warn("[ROUTE_EVENT] Publish failed: {}", putResult.getPutMessageStatus()); + } + } catch (Exception e) { + LOG.error("[TOPIC_CHANGE_EVENT] Failed to store event locally.", e); + } return; } - String currentBroker = brokerController.getBrokerConfig().getBrokerName(); - Set processedBrokers = new HashSet<>(); - processedBrokers.add(currentBroker); + if (eventType == RouteEventType.START || eventType == RouteEventType.SHUTDOWN) { + TopicPublishInfo routeInfo = brokerController.getTopicRouteInfoManager() + .tryToFindTopicPublishInfo(TopicValidator.RMQ_ROUTE_EVENT_TOPIC); + String currentBrokerName = brokerController.getBrokerConfig().getBrokerName(); - for (MessageQueue mq : routeInfo.getMessageQueueList()) { - if (processedBrokers.contains(mq.getBrokerName())) { - continue; - } + for (MessageQueue mq : routeInfo.getMessageQueueList()) { + String targetBrokerName = mq.getBrokerName(); + + if (targetBrokerName.equals(currentBrokerName)) { + continue; + } - try { - SendResult result = brokerController.getEscapeBridge() - .putMessageToRemoteBroker(msg, mq.getBrokerName()); + try { + SendResult sendResult = brokerController.getEscapeBridge() + .putMessageToRemoteBroker(msg, targetBrokerName); - if (result != null && result.getSendStatus() == SendStatus.SEND_OK) { - LOG.debug("Event sent to broker: {}", mq.getBrokerName()); + if (sendResult != null && sendResult.getSendStatus() == SendStatus.SEND_OK) { + return; + } + } catch (Exception e) { + LOG.warn("[BROKER_EVENT] Exception occurred when sending {} event to broker: {}", + eventType, targetBrokerName, e); } - processedBrokers.add(mq.getBrokerName()); - } catch (Exception e) { - LOG.warn("Failed to send to broker: {}", mq.getBrokerName(), e); } + LOG.error("[BROKER_EVENT] Failed to send {} event to any remote broker.", eventType); } } diff --git a/broker/src/test/java/org/apache/rocketmq/broker/route/RouteEventServiceTest.java b/broker/src/test/java/org/apache/rocketmq/broker/route/RouteEventServiceTest.java index 606875bc21e..81f46b2cab0 100644 --- a/broker/src/test/java/org/apache/rocketmq/broker/route/RouteEventServiceTest.java +++ b/broker/src/test/java/org/apache/rocketmq/broker/route/RouteEventServiceTest.java @@ -86,7 +86,7 @@ public void setUp() { public void testPublishEventSuccessfully() { when(mockMessageStore.putMessage(any())).thenReturn(new PutMessageResult(PutMessageStatus.PUT_OK, null)); - routeEventService.publishEvent(RouteEventType.SHUTDOWN, "TestTopic"); + routeEventService.publishEvent(RouteEventType.TOPIC_CHANGE, "TestTopic"); verify(mockMessageStore).putMessage(any(MessageExtBrokerInner.class)); } diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteChangeNotifier.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteChangeNotifier.java index 9b031d41ca9..23fa229b7eb 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteChangeNotifier.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/RouteChangeNotifier.java @@ -30,6 +30,7 @@ import java.util.function.BiConsumer; import org.apache.rocketmq.common.message.MessageExt; +import org.apache.rocketmq.common.thread.ThreadPoolMonitor; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.broker.route.RouteEventConstants; import org.apache.rocketmq.broker.route.RouteEventType; @@ -52,7 +53,7 @@ public class RouteChangeNotifier { private static final Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME); private final LoadingCache topicCache; - private final ThreadPoolExecutor executor; + private final ThreadPoolExecutor routeRefreshExecutor; private final ConcurrentMap dirtyTopics = new ConcurrentHashMap<>(); private final Queue pendingTopics = new ConcurrentLinkedQueue<>(); @@ -61,15 +62,22 @@ public class RouteChangeNotifier { private final DefaultMQPushConsumer consumer; private final BiConsumer dirtyMarker; - public RouteChangeNotifier(LoadingCache topicCache, - ThreadPoolExecutor executor) { + public RouteChangeNotifier(LoadingCache topicCache) { this.topicCache = topicCache; - this.executor = executor; this.scheduler = ThreadUtils.newSingleThreadScheduledExecutor( new ThreadFactoryImpl("RouteUpdaterScheduler_") ); + this.routeRefreshExecutor = ThreadPoolMonitor.createAndMonitor( + 2, + 4, + 1000 * 60, + TimeUnit.MILLISECONDS, + "TopicRouteCacheRefresh", + 1000 + ); + this.consumer = new DefaultMQPushConsumer("PROXY_ROUTE_EVENT_GROUP"); this.consumer.setMessageModel(MessageModel.BROADCASTING); this.consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET); @@ -93,6 +101,10 @@ public void shutdown() { if (scheduler != null) { scheduler.shutdown(); } + + if (routeRefreshExecutor != null) { + routeRefreshExecutor.shutdown(); + } } private void startEventSubscription() { @@ -177,7 +189,7 @@ private void processDirtyTopics() { } for (String topic : batch) { - executor.execute(() -> { + routeRefreshExecutor.execute(() -> { refreshSingleRoute(topic); markCompleted(topic); }); diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java index a21f6d3b1e6..7b5ad1e984f 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/service/route/TopicRouteService.java @@ -102,7 +102,8 @@ public TopicRouteService(MQClientAPIFactory mqClientAPIFactory) { public @Nullable MessageQueueView reload(@NonNull String key, @NonNull MessageQueueView oldValue) throws Exception { try { - if (routeChangeNotifier != null) { + if (routeChangeNotifier != null + && ConfigurationManager.getProxyConfig().isEnableRouteChangeNotification()) { routeChangeNotifier.markCompleted(key); } @@ -143,10 +144,7 @@ public String resolve(String name) { } }, serviceDetector); - this.routeChangeNotifier = new RouteChangeNotifier( - this.topicCache, - this.cacheRefreshExecutor - ); + this.routeChangeNotifier = new RouteChangeNotifier(this.topicCache); this.init(); } diff --git a/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteChangeNotifierTest.java b/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteChangeNotifierTest.java index 87fadfe3a96..b46c46f980f 100644 --- a/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteChangeNotifierTest.java +++ b/proxy/src/test/java/org/apache/rocketmq/proxy/service/route/RouteChangeNotifierTest.java @@ -48,7 +48,7 @@ public class RouteChangeNotifierTest { public void setUp() { mockCache = mock(LoadingCache.class); mockExecutor = mock(ThreadPoolExecutor.class); - notifier = new RouteChangeNotifier(mockCache, mockExecutor); + notifier = new RouteChangeNotifier(mockCache); } @Test From f8b601a0478b3b162856762a28ac1e27fb9065ab Mon Sep 17 00:00:00 2001 From: saz97 Date: Wed, 24 Sep 2025 09:15:16 +0800 Subject: [PATCH 19/19] =?UTF-8?q?=E8=A7=A3=E5=86=B3comment=20-=20=E6=8A=8A?= =?UTF-8?q?=E5=BC=80=E5=85=B3enableRouteChangeNotification=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E4=B8=BA=E5=B8=B8=E9=97=AD=20-=20=E6=8A=8ARouteEventS?= =?UTF-8?q?ervice=E7=9A=84=E5=88=9D=E5=A7=8B=E5=8C=96=E6=94=BE=E5=9C=A8Bro?= =?UTF-8?q?kerController=E7=9A=84=E5=88=9D=E5=A7=8B=E5=8C=96=E9=87=8C=20-?= =?UTF-8?q?=20=E5=9C=A8InnerBrokerController=E4=B9=9F=E6=B7=BB=E5=8A=A0STA?= =?UTF-8?q?RT=E7=9A=84publish=20event?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/apache/rocketmq/broker/BrokerController.java | 5 +++-- .../main/java/org/apache/rocketmq/common/BrokerConfig.java | 2 +- .../org/apache/rocketmq/container/InnerBrokerController.java | 5 +++++ .../java/org/apache/rocketmq/proxy/config/ProxyConfig.java | 2 +- 4 files changed, 10 insertions(+), 4 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 7ec51d0db41..fd2cc03becd 100644 --- a/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java +++ b/broker/src/main/java/org/apache/rocketmq/broker/BrokerController.java @@ -302,7 +302,7 @@ public class BrokerController { private TransactionMetricsFlushService transactionMetricsFlushService; private AuthenticationMetadataManager authenticationMetadataManager; private AuthorizationMetadataManager authorizationMetadataManager; - private RouteEventService routeEventService; + protected RouteEventService routeEventService; public BrokerController( final BrokerConfig brokerConfig, @@ -463,6 +463,8 @@ public boolean online(String instanceId, String group, String topic) { if (this.authConfig != null && this.authConfig.isMigrateAuthFromV1Enabled()) { new AuthMigrator(this.authConfig).migrate(); } + + this.routeEventService = new RouteEventService(this); } public AuthConfig getAuthConfig() { @@ -868,7 +870,6 @@ public boolean initialize() throws CloneNotSupportedException { if (!result) { return false; } - this.routeEventService = new RouteEventService(this); return this.recoverAndInitService(); } 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 c166ed8426c..29b384c97d0 100644 --- a/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java +++ b/common/src/main/java/org/apache/rocketmq/common/BrokerConfig.java @@ -141,7 +141,7 @@ public class BrokerConfig extends BrokerIdentity { private boolean slaveReadEnable = false; - private boolean enableRouteChangeNotification = true; + private boolean enableRouteChangeNotification = false; private boolean disableConsumeIfConsumerReadSlowly = false; private long consumerFallbehindThreshold = 1024L * 1024 * 1024 * 16; diff --git a/container/src/main/java/org/apache/rocketmq/container/InnerBrokerController.java b/container/src/main/java/org/apache/rocketmq/container/InnerBrokerController.java index 616188e52d1..3f204d60cc6 100644 --- a/container/src/main/java/org/apache/rocketmq/container/InnerBrokerController.java +++ b/container/src/main/java/org/apache/rocketmq/container/InnerBrokerController.java @@ -20,6 +20,7 @@ import java.util.concurrent.TimeUnit; import org.apache.rocketmq.broker.BrokerController; import org.apache.rocketmq.broker.out.BrokerOuterAPI; +import org.apache.rocketmq.broker.route.RouteEventType; import org.apache.rocketmq.common.AbstractBrokerRunnable; import org.apache.rocketmq.common.BrokerConfig; import org.apache.rocketmq.common.MixAll; @@ -69,6 +70,10 @@ public void start() throws Exception { if (!isIsolated && !this.messageStoreConfig.isEnableDLegerCommitLog() && !this.messageStoreConfig.isDuplicationEnable()) { changeSpecialServiceStatus(this.brokerConfig.getBrokerId() == MixAll.MASTER_ID); this.registerBrokerAll(true, false, true); + if (this.routeEventService != null) { + this.routeEventService.publishEvent(RouteEventType.START); + } + } scheduledFutures.add(this.scheduledExecutorService.scheduleAtFixedRate(new AbstractBrokerRunnable(this.getBrokerIdentity()) { diff --git a/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java b/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java index c661e366195..5e15caf39dd 100644 --- a/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java +++ b/proxy/src/main/java/org/apache/rocketmq/proxy/config/ProxyConfig.java @@ -207,7 +207,7 @@ public class ProxyConfig implements ConfigFile { private boolean enableAclRpcHookForClusterMode = false; - private boolean enableRouteChangeNotification = true; + private boolean enableRouteChangeNotification = false; private boolean useDelayLevel = false; private String messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h";