This is an automated email from the ASF dual-hosted git repository. heliang666s pushed a commit to branch feat-tagStateRouter in repository https://gitbox.apache.org/repos/asf/dubbo.git
commit a75591c6c0f9d60097090a7d321460b689c3a04a Author: 何亮 <[email protected]> AuthorDate: Tue Dec 16 02:20:34 2025 +0800 fix(cluster): support broadcast and wildcard tag routing --- .../rpc/cluster/router/tag/TagStateRouter.java | 21 +++- .../rpc/cluster/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 cacb941b16..07c3186625 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.Set; 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 class TagStateRouter<T> extends AbstractStateRouter<T> implements Configu 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 class TagStateRouter<T> extends AbstractStateRouter<T> implements Configu } BitList<Invoker<T>> 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 @@ public class TagStateRouter<T> extends AbstractStateRouter<T> implements Configu // 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 79b34c6823..d228a315e7 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.BeforeEach; 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 @@ class TagStateRouterTest { Set<String> selectedAddresses = TagStateRouter.selectAddressByTagLevel(tagAddresses, "beta", true); Assertions.assertEquals(addresses, selectedAddresses); } + + @Test + void testTagRouteWithWildcardShouldReturnAllProviders() { + StateRouter<TagRouterRule> router = new TagStateRouterFactory().getRouter(TagRouterRule.class, url); + + List<Invoker<TagRouterRule>> 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<TagRouterRule> invoker1 = new MockInvoker<>(url1, true); + Invoker<TagRouterRule> invoker2 = new MockInvoker<>(url2, true); + Invoker<TagRouterRule> invoker3 = new MockInvoker<>(url3, true); + Invoker<TagRouterRule> invoker4 = new MockInvoker<>(url4, true); + originInvokers.add(invoker1); + originInvokers.add(invoker2); + originInvokers.add(invoker3); + originInvokers.add(invoker4); + BitList<Invoker<TagRouterRule>> invokers = new BitList<>(originInvokers); + + RpcInvocation invocation = new RpcInvocation(); + invocation.setAttachment(TAG_KEY, ANY_VALUE); + + List<Invoker<TagRouterRule>> 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<TagRouterRule> router = new TagStateRouterFactory().getRouter(TagRouterRule.class, url); + + List<Invoker<TagRouterRule>> 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<TagRouterRule> invoker1 = new MockInvoker<>(url1, true); + Invoker<TagRouterRule> invoker2 = new MockInvoker<>(url2, true); + Invoker<TagRouterRule> invoker3 = new MockInvoker<>(url3, true); + Invoker<TagRouterRule> invoker4 = new MockInvoker<>(url4, true); + originInvokers.add(invoker1); + originInvokers.add(invoker2); + originInvokers.add(invoker3); + originInvokers.add(invoker4); + BitList<Invoker<TagRouterRule>> invokers = new BitList<>(originInvokers); + + RpcInvocation invocation = new RpcInvocation(); + URL consumerUrl = url.addParameter(CLUSTER_KEY, BROADCAST_CLUSTER); + + List<Invoker<TagRouterRule>> 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<Invoker<String>> 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<String> invoker1 = new MockInvoker<>(url1, true); + Invoker<String> invoker2 = new MockInvoker<>(url2, true); + Invoker<String> invoker3 = new MockInvoker<>(url3, true); + originInvokers.add(invoker1); + originInvokers.add(invoker2); + originInvokers.add(invoker3); + BitList<Invoker<String>> 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<Invoker<String>> 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)); + } }
