From a75591c6c0f9d60097090a7d321460b689c3a04a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=95=E4=BA=AE?= <3596006474@qq.com> Date: Tue, 16 Dec 2025 02:20:34 +0800 Subject: [PATCH 1/2] fix(cluster): support broadcast and wildcard tag routing --- .../cluster/router/tag/TagStateRouter.java | 21 +++- .../router/tag/TagStateRouterTest.java | 114 ++++++++++++++++++ 2 files changed, 131 insertions(+), 4 deletions(-) diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouter.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouter.java index cacb941b16a7..07c3186625fb 100644 --- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouter.java +++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouter.java @@ -41,6 +41,9 @@ import java.util.function.Predicate; import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_VALUE; +import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE; +import static org.apache.dubbo.common.constants.CommonConstants.BROADCAST_CLUSTER; +import static org.apache.dubbo.common.constants.CommonConstants.CLUSTER_KEY; import static org.apache.dubbo.common.constants.CommonConstants.TAG_KEY; import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_TAG_ROUTE_EMPTY; import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_TAG_ROUTE_INVALID; @@ -105,6 +108,17 @@ public BitList> doRoute( return invokers; } + String tag = StringUtils.isEmpty(invocation.getAttachment(TAG_KEY)) + ? url.getParameter(TAG_KEY) + : invocation.getAttachment(TAG_KEY); + if (ANY_VALUE.equals(tag) + || (StringUtils.isEmpty(tag) && BROADCAST_CLUSTER.equals(url.getParameter(CLUSTER_KEY)))) { + if (needToPrintMessage) { + messageHolder.set("Skip tag routing. Reason: wildcard tag request or broadcast cluster without tag"); + } + return invokers; + } + // since the rule can be changed by config center, we should copy one to use. final TagRouterRule tagRouterRuleCopy = tagRouterRule; if (tagRouterRuleCopy == null || !tagRouterRuleCopy.isValid() || !tagRouterRuleCopy.isEnabled()) { @@ -115,9 +129,6 @@ public BitList> doRoute( } BitList> result = invokers; - String tag = StringUtils.isEmpty(invocation.getAttachment(TAG_KEY)) - ? url.getParameter(TAG_KEY) - : invocation.getAttachment(TAG_KEY); // if we are requesting for a Provider with a specific tag if (StringUtils.isNotEmpty(tag)) { @@ -208,7 +219,9 @@ private BitList> filterUsingStaticTag(BitList> invoker // Tag request if (!StringUtils.isEmpty(tag)) { result = filterInvoker( - invokers, invoker -> tag.equals(invoker.getUrl().getParameter(TAG_KEY))); + invokers, + invoker -> + ANY_VALUE.equals(tag) || tag.equals(invoker.getUrl().getParameter(TAG_KEY))); if (CollectionUtils.isEmpty(result) && !isForceUseTag(invocation)) { result = filterInvoker( invokers, diff --git a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouterTest.java b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouterTest.java index 79b34c682356..d228a315e7c3 100644 --- a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouterTest.java +++ b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouterTest.java @@ -41,6 +41,9 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; +import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE; +import static org.apache.dubbo.common.constants.CommonConstants.BROADCAST_CLUSTER; +import static org.apache.dubbo.common.constants.CommonConstants.CLUSTER_KEY; import static org.apache.dubbo.common.constants.CommonConstants.TAG_KEY; import static org.mockito.Mockito.when; @@ -298,4 +301,115 @@ public void tagLevelForceTest() { Set selectedAddresses = TagStateRouter.selectAddressByTagLevel(tagAddresses, "beta", true); Assertions.assertEquals(addresses, selectedAddresses); } + + @Test + void testTagRouteWithWildcardShouldReturnAllProviders() { + StateRouter router = new TagStateRouterFactory().getRouter(TagRouterRule.class, url); + + List> originInvokers = new ArrayList<>(); + + URL url1 = URL.valueOf("test://127.0.0.1:7777/DemoInterface?dubbo.tag=t-01") + .setScopeModel(moduleModel); + URL url2 = URL.valueOf("test://127.0.0.1:7778/DemoInterface?dubbo.tag=t-02") + .setScopeModel(moduleModel); + URL url3 = URL.valueOf("test://127.0.0.1:7779/DemoInterface?dubbo.tag=t-03") + .setScopeModel(moduleModel); + URL url4 = URL.valueOf("test://127.0.0.1:7780/DemoInterface?dubbo.tag=t-04") + .setScopeModel(moduleModel); + + Invoker invoker1 = new MockInvoker<>(url1, true); + Invoker invoker2 = new MockInvoker<>(url2, true); + Invoker invoker3 = new MockInvoker<>(url3, true); + Invoker invoker4 = new MockInvoker<>(url4, true); + originInvokers.add(invoker1); + originInvokers.add(invoker2); + originInvokers.add(invoker3); + originInvokers.add(invoker4); + BitList> invokers = new BitList<>(originInvokers); + + RpcInvocation invocation = new RpcInvocation(); + invocation.setAttachment(TAG_KEY, ANY_VALUE); + + List> filteredInvokers = + router.route(invokers.clone(), invokers.get(0).getUrl(), invocation, false, new Holder<>()); + + Assertions.assertEquals(4, filteredInvokers.size()); + Assertions.assertEquals(invoker1, filteredInvokers.get(0)); + Assertions.assertEquals(invoker2, filteredInvokers.get(1)); + Assertions.assertEquals(invoker3, filteredInvokers.get(2)); + Assertions.assertEquals(invoker4, filteredInvokers.get(3)); + } + + @Test + void testBroadcastClusterWithoutTagShouldReturnAllProviders() { + StateRouter router = new TagStateRouterFactory().getRouter(TagRouterRule.class, url); + + List> originInvokers = new ArrayList<>(); + + URL url1 = URL.valueOf("test://127.0.0.1:7777/DemoInterface?dubbo.tag=t-01") + .setScopeModel(moduleModel); + URL url2 = URL.valueOf("test://127.0.0.1:7778/DemoInterface?dubbo.tag=t-02") + .setScopeModel(moduleModel); + URL url3 = URL.valueOf("test://127.0.0.1:7779/DemoInterface?dubbo.tag=t-03") + .setScopeModel(moduleModel); + URL url4 = URL.valueOf("test://127.0.0.1:7780/DemoInterface?dubbo.tag=t-04") + .setScopeModel(moduleModel); + + Invoker invoker1 = new MockInvoker<>(url1, true); + Invoker invoker2 = new MockInvoker<>(url2, true); + Invoker invoker3 = new MockInvoker<>(url3, true); + Invoker invoker4 = new MockInvoker<>(url4, true); + originInvokers.add(invoker1); + originInvokers.add(invoker2); + originInvokers.add(invoker3); + originInvokers.add(invoker4); + BitList> invokers = new BitList<>(originInvokers); + + RpcInvocation invocation = new RpcInvocation(); + URL consumerUrl = url.addParameter(CLUSTER_KEY, BROADCAST_CLUSTER); + + List> filteredInvokers = + router.route(invokers.clone(), consumerUrl, invocation, false, new Holder<>()); + + Assertions.assertEquals(4, filteredInvokers.size()); + Assertions.assertEquals(invoker1, filteredInvokers.get(0)); + Assertions.assertEquals(invoker2, filteredInvokers.get(1)); + Assertions.assertEquals(invoker3, filteredInvokers.get(2)); + Assertions.assertEquals(invoker4, filteredInvokers.get(3)); + } + + @Test + void testTagRouteWithDynamicRuleWildcardShouldReturnAllProviders() { + TagStateRouter router = (TagStateRouter) new TagStateRouterFactory().getRouter(TagRouterRule.class, url); + router = Mockito.spy(router); + + List> originInvokers = new ArrayList<>(); + + URL url1 = URL.valueOf("test://127.0.0.1:7777/DemoInterface?application=foo&dubbo.tag=tag2&match_key=value") + .setScopeModel(moduleModel); + URL url2 = URL.valueOf("test://127.0.0.1:7778/DemoInterface?application=foo&match_key=value") + .setScopeModel(moduleModel); + URL url3 = URL.valueOf("test://127.0.0.1:7779/DemoInterface?application=foo") + .setScopeModel(moduleModel); + Invoker invoker1 = new MockInvoker<>(url1, true); + Invoker invoker2 = new MockInvoker<>(url2, true); + Invoker invoker3 = new MockInvoker<>(url3, true); + originInvokers.add(invoker1); + originInvokers.add(invoker2); + originInvokers.add(invoker3); + BitList> invokers = new BitList<>(originInvokers); + + RpcInvocation invocation = new RpcInvocation(); + invocation.setAttachment(TAG_KEY, ANY_VALUE); + TagRouterRule rule = getTagRule(); + Mockito.when(router.getInvokers()).thenReturn(invokers); + rule.init(router); + router.setTagRouterRule(rule); + List> filteredInvokers = router.route(invokers.clone(), url, invocation, false, new Holder<>()); + + Assertions.assertEquals(3, filteredInvokers.size()); + Assertions.assertEquals(invoker1, filteredInvokers.get(0)); + Assertions.assertEquals(invoker2, filteredInvokers.get(1)); + Assertions.assertEquals(invoker3, filteredInvokers.get(2)); + } } From 96f5c3c35cf6d7b06aa3b05852b88f01ccfb363e Mon Sep 17 00:00:00 2001 From: heliang666s <3596006474@qq.com> Date: Tue, 27 Jan 2026 12:29:23 +0800 Subject: [PATCH 2/2] fix: don't bypass tag routing for broadcast without tag --- .../cluster/router/tag/TagStateRouter.java | 7 +--- .../router/tag/TagStateRouterTest.java | 40 ------------------- 2 files changed, 2 insertions(+), 45 deletions(-) diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouter.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouter.java index 07c3186625fb..4b11588fd498 100644 --- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouter.java +++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouter.java @@ -42,8 +42,6 @@ import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_VALUE; import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE; -import static org.apache.dubbo.common.constants.CommonConstants.BROADCAST_CLUSTER; -import static org.apache.dubbo.common.constants.CommonConstants.CLUSTER_KEY; import static org.apache.dubbo.common.constants.CommonConstants.TAG_KEY; import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_TAG_ROUTE_EMPTY; import static org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_TAG_ROUTE_INVALID; @@ -111,10 +109,9 @@ public BitList> doRoute( String tag = StringUtils.isEmpty(invocation.getAttachment(TAG_KEY)) ? url.getParameter(TAG_KEY) : invocation.getAttachment(TAG_KEY); - if (ANY_VALUE.equals(tag) - || (StringUtils.isEmpty(tag) && BROADCAST_CLUSTER.equals(url.getParameter(CLUSTER_KEY)))) { + if (ANY_VALUE.equals(tag)) { if (needToPrintMessage) { - messageHolder.set("Skip tag routing. Reason: wildcard tag request or broadcast cluster without tag"); + messageHolder.set("Skip tag routing. Reason: wildcard tag request"); } return invokers; } diff --git a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouterTest.java b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouterTest.java index d228a315e7c3..4807ff56e139 100644 --- a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouterTest.java +++ b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/router/tag/TagStateRouterTest.java @@ -42,8 +42,6 @@ import org.mockito.Mockito; import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE; -import static org.apache.dubbo.common.constants.CommonConstants.BROADCAST_CLUSTER; -import static org.apache.dubbo.common.constants.CommonConstants.CLUSTER_KEY; import static org.apache.dubbo.common.constants.CommonConstants.TAG_KEY; import static org.mockito.Mockito.when; @@ -340,44 +338,6 @@ void testTagRouteWithWildcardShouldReturnAllProviders() { Assertions.assertEquals(invoker4, filteredInvokers.get(3)); } - @Test - void testBroadcastClusterWithoutTagShouldReturnAllProviders() { - StateRouter router = new TagStateRouterFactory().getRouter(TagRouterRule.class, url); - - List> originInvokers = new ArrayList<>(); - - URL url1 = URL.valueOf("test://127.0.0.1:7777/DemoInterface?dubbo.tag=t-01") - .setScopeModel(moduleModel); - URL url2 = URL.valueOf("test://127.0.0.1:7778/DemoInterface?dubbo.tag=t-02") - .setScopeModel(moduleModel); - URL url3 = URL.valueOf("test://127.0.0.1:7779/DemoInterface?dubbo.tag=t-03") - .setScopeModel(moduleModel); - URL url4 = URL.valueOf("test://127.0.0.1:7780/DemoInterface?dubbo.tag=t-04") - .setScopeModel(moduleModel); - - Invoker invoker1 = new MockInvoker<>(url1, true); - Invoker invoker2 = new MockInvoker<>(url2, true); - Invoker invoker3 = new MockInvoker<>(url3, true); - Invoker invoker4 = new MockInvoker<>(url4, true); - originInvokers.add(invoker1); - originInvokers.add(invoker2); - originInvokers.add(invoker3); - originInvokers.add(invoker4); - BitList> invokers = new BitList<>(originInvokers); - - RpcInvocation invocation = new RpcInvocation(); - URL consumerUrl = url.addParameter(CLUSTER_KEY, BROADCAST_CLUSTER); - - List> filteredInvokers = - router.route(invokers.clone(), consumerUrl, invocation, false, new Holder<>()); - - Assertions.assertEquals(4, filteredInvokers.size()); - Assertions.assertEquals(invoker1, filteredInvokers.get(0)); - Assertions.assertEquals(invoker2, filteredInvokers.get(1)); - Assertions.assertEquals(invoker3, filteredInvokers.get(2)); - Assertions.assertEquals(invoker4, filteredInvokers.get(3)); - } - @Test void testTagRouteWithDynamicRuleWildcardShouldReturnAllProviders() { TagStateRouter router = (TagStateRouter) new TagStateRouterFactory().getRouter(TagRouterRule.class, url);