This is an automated email from the ASF dual-hosted git repository.

liujun pushed a commit to branch 3.3
in repository https://gitbox.apache.org/repos/asf/dubbo.git


The following commit(s) were added to refs/heads/3.3 by this push:
     new a93624e8cf New condition router v3.1 (#14456)
a93624e8cf is described below

commit a93624e8cfb43991fca670cf9ad25197df331758
Author: 王聪洋 <[email protected]>
AuthorDate: Tue Jul 23 10:05:09 2024 +0800

    New condition router v3.1 (#14456)
---
 .../org/apache/dubbo/rpc/cluster/Constants.java    |   4 +
 .../router/condition/MultiDestConditionRouter.java | 341 +++++++++++++++
 .../condition/config/ListenableStateRouter.java    |  51 ++-
 .../config/model/ConditionRouterRule.java          |   2 +-
 .../config/model/ConditionRuleParser.java          |  40 +-
 .../condition/config/model/ConditionSubSet.java    |  60 +++
 .../{ConditionRuleParser.java => Destination.java} |  50 +--
 .../condition/config/model/DestinationSet.java     |  73 ++++
 ...tionRuleParser.java => MultiDestCondition.java} |  52 +--
 ...Rule.java => MultiDestConditionRouterRule.java} |  33 +-
 .../config/ConditionStateRouterTestV31.java        | 466 +++++++++++++++++++++
 11 files changed, 1083 insertions(+), 89 deletions(-)

diff --git 
a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/Constants.java 
b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/Constants.java
index 0e59f2cacf..eab16e6d0f 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/Constants.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/Constants.java
@@ -138,4 +138,8 @@ public interface Constants {
     String RULE_VERSION_V27 = "v2.7";
 
     String RULE_VERSION_V30 = "v3.0";
+
+    String RULE_VERSION_V31 = "v3.1";
+
+    public static final int DefaultRouteConditionSubSetWeight = 100;
 }
diff --git 
a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/MultiDestConditionRouter.java
 
b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/MultiDestConditionRouter.java
new file mode 100644
index 0000000000..fa986ba147
--- /dev/null
+++ 
b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/MultiDestConditionRouter.java
@@ -0,0 +1,341 @@
+/*
+ * 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.dubbo.rpc.cluster.router.condition;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.utils.CollectionUtils;
+import org.apache.dubbo.common.utils.Holder;
+import org.apache.dubbo.common.utils.NetUtils;
+import org.apache.dubbo.common.utils.StringUtils;
+import org.apache.dubbo.rpc.Invocation;
+import org.apache.dubbo.rpc.Invoker;
+import org.apache.dubbo.rpc.RpcException;
+import org.apache.dubbo.rpc.cluster.router.RouterSnapshotNode;
+import 
org.apache.dubbo.rpc.cluster.router.condition.config.model.ConditionSubSet;
+import 
org.apache.dubbo.rpc.cluster.router.condition.config.model.DestinationSet;
+import 
org.apache.dubbo.rpc.cluster.router.condition.config.model.MultiDestCondition;
+import org.apache.dubbo.rpc.cluster.router.condition.matcher.ConditionMatcher;
+import 
org.apache.dubbo.rpc.cluster.router.condition.matcher.ConditionMatcherFactory;
+import org.apache.dubbo.rpc.cluster.router.state.AbstractStateRouter;
+import org.apache.dubbo.rpc.cluster.router.state.BitList;
+
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static 
org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_CONDITIONAL_ROUTE_LIST_EMPTY;
+import static 
org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_FAILED_EXEC_CONDITION_ROUTER;
+import static 
org.apache.dubbo.rpc.cluster.Constants.DefaultRouteConditionSubSetWeight;
+import static org.apache.dubbo.rpc.cluster.Constants.RULE_KEY;
+
+public class MultiDestConditionRouter<T> extends AbstractStateRouter<T> {
+    public static final String NAME = "multi_condition";
+
+    private static final ErrorTypeAwareLogger logger = 
LoggerFactory.getErrorTypeAwareLogger(AbstractStateRouter.class);
+    protected static final Pattern ROUTE_PATTERN = 
Pattern.compile("([&!=,]*)\\s*([^&!=,\\s]+)");
+    private Map<String, ConditionMatcher> whenCondition;
+    private List<ConditionSubSet> thenCondition;
+    private boolean force;
+    protected List<ConditionMatcherFactory> matcherFactories;
+    private boolean enabled;
+
+    public MultiDestConditionRouter(URL url, MultiDestCondition 
multiDestCondition, boolean force, boolean enabled) {
+        super(url);
+        this.setForce(force);
+        this.enabled = enabled;
+        matcherFactories =
+                
moduleModel.getExtensionLoader(ConditionMatcherFactory.class).getActivateExtensions();
+        this.init(multiDestCondition.getFrom(), multiDestCondition.getTo());
+    }
+
+    public void init(Map<String, String> from, List<Map<String, String>> to) {
+        try {
+            if (from == null || to == null) {
+                throw new IllegalArgumentException("Illegal route rule!");
+            }
+            String whenRule = from.get("match");
+            Map<String, ConditionMatcher> when =
+                    StringUtils.isBlank(whenRule) || "true".equals(whenRule) ? 
new HashMap<>() : parseRule(whenRule);
+            this.whenCondition = when;
+
+            List<ConditionSubSet> thenConditions = new ArrayList<>();
+            for (Map<String, String> toMap : to) {
+                String thenRule = toMap.get("match");
+                Map<String, ConditionMatcher> then = 
StringUtils.isBlank(thenRule) || "false".equals(thenRule)
+                        ? new HashMap<>()
+                        : parseRule(thenRule);
+                // NOTE: It should be determined on the business level whether 
the `When condition` can be empty or not.
+
+                thenConditions.add(new ConditionSubSet(
+                        then,
+                        Integer.valueOf(
+                                toMap.getOrDefault("weight", 
String.valueOf(DefaultRouteConditionSubSetWeight)))));
+            }
+            this.thenCondition = thenConditions;
+        } catch (ParseException e) {
+            throw new IllegalStateException(e.getMessage(), e);
+        }
+    }
+
+    private Map<String, ConditionMatcher> parseRule(String rule) throws 
ParseException {
+        Map<String, ConditionMatcher> condition = new HashMap<>();
+        if (StringUtils.isBlank(rule)) {
+            return condition;
+        }
+        // Key-Value pair, stores both match and mismatch conditions
+        ConditionMatcher matcherPair = null;
+        // Multiple values
+        Set<String> values = null;
+        final Matcher matcher = ROUTE_PATTERN.matcher(rule);
+        while (matcher.find()) { // Try to match one by one
+            String separator = matcher.group(1);
+            String content = matcher.group(2);
+            // Start part of the condition expression.
+            if (StringUtils.isEmpty(separator)) {
+                matcherPair = this.getMatcher(content);
+                condition.put(content, matcherPair);
+            }
+            // The KV part of the condition expression
+            else if ("&".equals(separator)) {
+                if (condition.get(content) == null) {
+                    matcherPair = this.getMatcher(content);
+                    condition.put(content, matcherPair);
+                } else {
+                    matcherPair = condition.get(content);
+                }
+            }
+            // The Value in the KV part.
+            else if ("=".equals(separator)) {
+                if (matcherPair == null) {
+                    throw new ParseException(
+                            "Illegal route rule \"" + rule + "\", The error 
char '" + separator + "' at index "
+                                    + matcher.start() + " before \"" + content 
+ "\".",
+                            matcher.start());
+                }
+
+                values = matcherPair.getMatches();
+                values.add(content);
+            }
+            // The Value in the KV part.
+            else if ("!=".equals(separator)) {
+                if (matcherPair == null) {
+                    throw new ParseException(
+                            "Illegal route rule \"" + rule + "\", The error 
char '" + separator + "' at index "
+                                    + matcher.start() + " before \"" + content 
+ "\".",
+                            matcher.start());
+                }
+
+                values = matcherPair.getMismatches();
+                values.add(content);
+            }
+            // The Value in the KV part, if Value have more than one items.
+            else if (",".equals(separator)) { // Should be separated by ','
+                if (values == null || values.isEmpty()) {
+                    throw new ParseException(
+                            "Illegal route rule \"" + rule + "\", The error 
char '" + separator + "' at index "
+                                    + matcher.start() + " before \"" + content 
+ "\".",
+                            matcher.start());
+                }
+                values.add(content);
+            } else {
+                throw new ParseException(
+                        "Illegal route rule \"" + rule + "\", The error char 
'" + separator + "' at index "
+                                + matcher.start() + " before \"" + content + 
"\".",
+                        matcher.start());
+            }
+        }
+        return condition;
+    }
+
+    private ConditionMatcher getMatcher(String key) {
+        for (ConditionMatcherFactory factory : matcherFactories) {
+            if (factory.shouldMatch(key)) {
+                return factory.createMatcher(key, moduleModel);
+            }
+        }
+        return moduleModel
+                .getExtensionLoader(ConditionMatcherFactory.class)
+                .getExtension("param")
+                .createMatcher(key, moduleModel);
+    }
+
+    @Override
+    protected BitList<Invoker<T>> doRoute(
+            BitList<Invoker<T>> invokers,
+            URL url,
+            Invocation invocation,
+            boolean needToPrintMessage,
+            Holder<RouterSnapshotNode<T>> routerSnapshotNodeHolder,
+            Holder<String> messageHolder)
+            throws RpcException {
+
+        if (!enabled) {
+            if (needToPrintMessage) {
+                messageHolder.set("Directly return. Reason: ConditionRouter 
disabled.");
+            }
+            return invokers;
+        }
+
+        if (CollectionUtils.isEmpty(invokers)) {
+            if (needToPrintMessage) {
+                messageHolder.set("Directly return. Reason: Invokers from 
previous router is empty.");
+            }
+            return invokers;
+        }
+
+        try {
+            if (!matchWhen(url, invocation)) {
+                if (needToPrintMessage) {
+                    messageHolder.set("Directly return. Reason: WhenCondition 
not match.");
+                }
+                return invokers;
+            }
+            if (thenCondition == null || thenCondition.size() == 0) {
+                logger.warn(
+                        CLUSTER_CONDITIONAL_ROUTE_LIST_EMPTY,
+                        "condition state router thenCondition is empty",
+                        "",
+                        "The current consumer in the service blocklist. 
consumer: " + NetUtils.getLocalHost()
+                                + ", service: " + url.getServiceKey());
+                if (needToPrintMessage) {
+                    messageHolder.set("Empty return. Reason: ThenCondition is 
empty.");
+                }
+                return BitList.emptyList();
+            }
+
+            DestinationSet destinations = new DestinationSet();
+            for (ConditionSubSet condition : thenCondition) {
+                BitList<Invoker<T>> res = invokers.clone();
+
+                for (Invoker invoker : invokers) {
+                    if (!doMatch(invoker.getUrl(), url, null, 
condition.getCondition(), false)) {
+                        res.remove(invoker);
+                    }
+                }
+                if (!res.isEmpty()) {
+                    destinations.addDestination(
+                            condition.getSubSetWeight() == null
+                                    ? DefaultRouteConditionSubSetWeight
+                                    : condition.getSubSetWeight(),
+                            res.clone());
+                }
+            }
+
+            if (!destinations.getDestinations().isEmpty()) {
+                BitList<Invoker<T>> res = destinations.randDestination();
+                return res;
+            } else if (this.isForce()) {
+                logger.warn(
+                        CLUSTER_CONDITIONAL_ROUTE_LIST_EMPTY,
+                        "execute condition state router result list is " + 
"empty. and force=true",
+                        "",
+                        "The route result is empty and force execute. 
consumer: " + NetUtils.getLocalHost()
+                                + ", service: " + url.getServiceKey() + ", 
router: "
+                                + url.getParameterAndDecoded(RULE_KEY));
+                if (needToPrintMessage) {
+                    messageHolder.set("Empty return. Reason: Empty result from 
condition and condition is force.");
+                }
+                return BitList.emptyList();
+            }
+
+        } catch (Throwable t) {
+            logger.error(
+                    CLUSTER_FAILED_EXEC_CONDITION_ROUTER,
+                    "execute condition state router exception",
+                    "",
+                    "Failed to execute condition router rule: " + getUrl() + 
", invokers: " + invokers + ", cause: "
+                            + t.getMessage(),
+                    t);
+        }
+        if (needToPrintMessage) {
+            messageHolder.set("Directly return. Reason: Error occurred ( or 
result is empty ).");
+        }
+        return invokers;
+    }
+
+    boolean matchWhen(URL url, Invocation invocation) {
+        if (CollectionUtils.isEmptyMap(whenCondition)) {
+            return true;
+        }
+
+        return doMatch(url, null, invocation, whenCondition, true);
+    }
+
+    private boolean doMatch(
+            URL url,
+            URL param,
+            Invocation invocation,
+            Map<String, ConditionMatcher> conditions,
+            boolean isWhenCondition) {
+        Map<String, String> sample = url.toOriginalMap();
+        for (Map.Entry<String, ConditionMatcher> entry : 
conditions.entrySet()) {
+            ConditionMatcher matchPair = entry.getValue();
+
+            if (!matchPair.isMatch(sample, param, invocation, 
isWhenCondition)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public void setWhenCondition(Map<String, ConditionMatcher> whenCondition) {
+        this.whenCondition = whenCondition;
+    }
+
+    public void setThenCondition(List<ConditionSubSet> thenCondition) {
+        this.thenCondition = thenCondition;
+    }
+
+    public void setForce(boolean force) {
+        this.force = force;
+    }
+
+    public Map<String, ConditionMatcher> getWhenCondition() {
+        return whenCondition;
+    }
+
+    public boolean isForce() {
+        return force;
+    }
+
+    public List<ConditionSubSet> getThenCondition() {
+        return thenCondition;
+    }
+
+    public List<ConditionMatcherFactory> getMatcherFactories() {
+        return matcherFactories;
+    }
+
+    public void setMatcherFactories(List<ConditionMatcherFactory> 
matcherFactories) {
+        this.matcherFactories = matcherFactories;
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+}
diff --git 
a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/ListenableStateRouter.java
 
b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/ListenableStateRouter.java
index 575bd46282..a74214d2dc 100644
--- 
a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/ListenableStateRouter.java
+++ 
b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/ListenableStateRouter.java
@@ -29,10 +29,13 @@ import org.apache.dubbo.common.utils.StringUtils;
 import org.apache.dubbo.rpc.Invocation;
 import org.apache.dubbo.rpc.Invoker;
 import org.apache.dubbo.rpc.RpcException;
+import org.apache.dubbo.rpc.cluster.router.AbstractRouterRule;
 import org.apache.dubbo.rpc.cluster.router.RouterSnapshotNode;
 import org.apache.dubbo.rpc.cluster.router.condition.ConditionStateRouter;
+import org.apache.dubbo.rpc.cluster.router.condition.MultiDestConditionRouter;
 import 
org.apache.dubbo.rpc.cluster.router.condition.config.model.ConditionRouterRule;
 import 
org.apache.dubbo.rpc.cluster.router.condition.config.model.ConditionRuleParser;
+import 
org.apache.dubbo.rpc.cluster.router.condition.config.model.MultiDestConditionRouterRule;
 import org.apache.dubbo.rpc.cluster.router.state.AbstractStateRouter;
 import org.apache.dubbo.rpc.cluster.router.state.BitList;
 import org.apache.dubbo.rpc.cluster.router.state.TailStateRouter;
@@ -52,8 +55,11 @@ public abstract class ListenableStateRouter<T> extends 
AbstractStateRouter<T> im
 
     private static final ErrorTypeAwareLogger logger =
             LoggerFactory.getErrorTypeAwareLogger(ListenableStateRouter.class);
-    private volatile ConditionRouterRule routerRule;
+    private volatile AbstractRouterRule routerRule;
     private volatile List<ConditionStateRouter<T>> conditionRouters = 
Collections.emptyList();
+
+    //    for v3.1
+    private volatile List<MultiDestConditionRouter<T>> 
multiDestConditionRouters = Collections.emptyList();
     private final String ruleKey;
 
     public ListenableStateRouter(URL url, String ruleKey) {
@@ -73,6 +79,8 @@ public abstract class ListenableStateRouter<T> extends 
AbstractStateRouter<T> im
         if (event.getChangeType().equals(ConfigChangeType.DELETED)) {
             routerRule = null;
             conditionRouters = Collections.emptyList();
+            //          for v3.1
+            multiDestConditionRouters = Collections.emptyList();
         } else {
             try {
                 routerRule = ConditionRuleParser.parse(event.getContent());
@@ -99,7 +107,8 @@ public abstract class ListenableStateRouter<T> extends 
AbstractStateRouter<T> im
             Holder<RouterSnapshotNode<T>> nodeHolder,
             Holder<String> messageHolder)
             throws RpcException {
-        if (CollectionUtils.isEmpty(invokers) || conditionRouters.size() == 0) 
{
+        if (CollectionUtils.isEmpty(invokers)
+                || (conditionRouters.size() == 0 && 
multiDestConditionRouters.size() == 0)) {
             if (needToPrintMessage) {
                 messageHolder.set(
                         "Directly return. Reason: Invokers from previous 
router is empty or conditionRouters is empty.");
@@ -112,7 +121,15 @@ public abstract class ListenableStateRouter<T> extends 
AbstractStateRouter<T> im
         if (needToPrintMessage) {
             resultMessage = new StringBuilder();
         }
-        for (AbstractStateRouter<T> router : conditionRouters) {
+
+        List<? extends AbstractStateRouter<T>> routers;
+        if (routerRule instanceof MultiDestConditionRouterRule) {
+            routers = multiDestConditionRouters;
+        } else {
+            routers = conditionRouters;
+        }
+
+        for (AbstractStateRouter<T> router : routers) {
             invokers = router.route(invokers, url, invocation, 
needToPrintMessage, nodeHolder);
             if (needToPrintMessage) {
                 resultMessage.append(messageHolder.get());
@@ -135,15 +152,31 @@ public abstract class ListenableStateRouter<T> extends 
AbstractStateRouter<T> im
         return routerRule != null && routerRule.isValid() && 
routerRule.isRuntime();
     }
 
-    private void generateConditions(ConditionRouterRule rule) {
-        if (rule != null && rule.isValid()) {
-            this.conditionRouters = rule.getConditions().stream()
-                    .map(condition ->
-                            new ConditionStateRouter<T>(getUrl(), condition, 
rule.isForce(), rule.isEnabled()))
-                    .collect(Collectors.toList());
+    private void generateConditions(AbstractRouterRule rule) {
+        if (rule == null || !rule.isValid()) {
+            return;
+        }
+
+        if (rule instanceof ConditionRouterRule) {
+            this.conditionRouters = ((ConditionRouterRule) rule)
+                    .getConditions().stream()
+                            .map(condition ->
+                                    new ConditionStateRouter<T>(getUrl(), 
condition, rule.isForce(), rule.isEnabled()))
+                            .collect(Collectors.toList());
+
             for (ConditionStateRouter<T> conditionRouter : 
this.conditionRouters) {
                 conditionRouter.setNextRouter(TailStateRouter.getInstance());
             }
+        } else if (rule instanceof MultiDestConditionRouterRule) {
+            this.multiDestConditionRouters = ((MultiDestConditionRouterRule) 
rule)
+                    .getConditions().stream()
+                            .map(condition -> new MultiDestConditionRouter<T>(
+                                    getUrl(), condition, rule.isForce(), 
rule.isEnabled()))
+                            .collect(Collectors.toList());
+
+            for (MultiDestConditionRouter<T> conditionRouter : 
this.multiDestConditionRouters) {
+                conditionRouter.setNextRouter(TailStateRouter.getInstance());
+            }
         }
     }
 
diff --git 
a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/ConditionRouterRule.java
 
b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/ConditionRouterRule.java
index 7d9c936c3f..08469da085 100644
--- 
a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/ConditionRouterRule.java
+++ 
b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/ConditionRouterRule.java
@@ -28,7 +28,7 @@ public class ConditionRouterRule extends AbstractRouterRule {
     private List<String> conditions;
 
     @SuppressWarnings("unchecked")
-    public static ConditionRouterRule parseFromMap(Map<String, Object> map) {
+    public static AbstractRouterRule parseFromMap(Map<String, Object> map) {
         ConditionRouterRule conditionRouterRule = new ConditionRouterRule();
         conditionRouterRule.parseFromMap0(map);
 
diff --git 
a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/ConditionRuleParser.java
 
b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/ConditionRuleParser.java
index 9aa80f908b..297acb7d1d 100644
--- 
a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/ConditionRuleParser.java
+++ 
b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/ConditionRuleParser.java
@@ -16,7 +16,10 @@
  */
 package org.apache.dubbo.rpc.cluster.router.condition.config.model;
 
+import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
+import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.common.utils.CollectionUtils;
+import org.apache.dubbo.rpc.cluster.router.AbstractRouterRule;
 
 import java.util.Map;
 
@@ -24,6 +27,10 @@ import org.yaml.snakeyaml.LoaderOptions;
 import org.yaml.snakeyaml.Yaml;
 import org.yaml.snakeyaml.constructor.SafeConstructor;
 
+import static 
org.apache.dubbo.common.constants.LoggerCodeConstants.CLUSTER_FAILED_RULE_PARSING;
+import static org.apache.dubbo.rpc.cluster.Constants.CONFIG_VERSION_KEY;
+import static org.apache.dubbo.rpc.cluster.Constants.RULE_VERSION_V31;
+
 /**
  * %YAML1.2
  *
@@ -40,13 +47,36 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
  */
 public class ConditionRuleParser {
 
-    public static ConditionRouterRule parse(String rawRule) {
+    private static final ErrorTypeAwareLogger logger = 
LoggerFactory.getErrorTypeAwareLogger(ConditionRuleParser.class);
+
+    public static AbstractRouterRule parse(String rawRule) {
+        AbstractRouterRule rule;
         Yaml yaml = new Yaml(new SafeConstructor(new LoaderOptions()));
         Map<String, Object> map = yaml.load(rawRule);
-        ConditionRouterRule rule = ConditionRouterRule.parseFromMap(map);
-        rule.setRawRule(rawRule);
-        if (CollectionUtils.isEmpty(rule.getConditions())) {
-            rule.setValid(false);
+        String confVersion = (String) map.get(CONFIG_VERSION_KEY);
+
+        if (confVersion != null && 
confVersion.toLowerCase().startsWith(RULE_VERSION_V31)) {
+            rule = MultiDestConditionRouterRule.parseFromMap(map);
+            if (CollectionUtils.isEmpty(((MultiDestConditionRouterRule) 
rule).getConditions())) {
+                rule.setValid(false);
+            }
+        } else if (confVersion != null && 
confVersion.compareToIgnoreCase(RULE_VERSION_V31) > 0) {
+            logger.warn(
+                    CLUSTER_FAILED_RULE_PARSING,
+                    "Invalid condition config version number.",
+                    "",
+                    "Ignore this configuration. Only " + RULE_VERSION_V31 + " 
and below are supported in this release");
+            rule = null;
+        } else {
+            //            for under v3.1
+            rule = ConditionRouterRule.parseFromMap(map);
+            if (CollectionUtils.isEmpty(((ConditionRouterRule) 
rule).getConditions())) {
+                rule.setValid(false);
+            }
+        }
+
+        if (rule != null) {
+            rule.setRawRule(rawRule);
         }
 
         return rule;
diff --git 
a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/ConditionSubSet.java
 
b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/ConditionSubSet.java
new file mode 100644
index 0000000000..3f1577222d
--- /dev/null
+++ 
b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/ConditionSubSet.java
@@ -0,0 +1,60 @@
+/*
+ * 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.dubbo.rpc.cluster.router.condition.config.model;
+
+import org.apache.dubbo.rpc.cluster.router.condition.matcher.ConditionMatcher;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static 
org.apache.dubbo.rpc.cluster.Constants.DefaultRouteConditionSubSetWeight;
+
+public class ConditionSubSet {
+    private Map<String, ConditionMatcher> condition = new HashMap<>();
+    private Integer subSetWeight;
+
+    public ConditionSubSet() {}
+
+    public ConditionSubSet(Map<String, ConditionMatcher> condition, Integer 
subSetWeight) {
+        this.condition = condition;
+        this.subSetWeight = subSetWeight;
+        if (subSetWeight <= 0) {
+            this.subSetWeight = DefaultRouteConditionSubSetWeight;
+        }
+    }
+
+    public Map<String, ConditionMatcher> getCondition() {
+        return condition;
+    }
+
+    public void setCondition(Map<String, ConditionMatcher> condition) {
+        this.condition = condition;
+    }
+
+    public Integer getSubSetWeight() {
+        return subSetWeight;
+    }
+
+    public void setSubSetWeight(int subSetWeight) {
+        this.subSetWeight = subSetWeight;
+    }
+
+    @Override
+    public String toString() {
+        return "ConditionSubSet{" + "cond=" + condition + ", subSetWeight=" + 
subSetWeight + '}';
+    }
+}
diff --git 
a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/ConditionRuleParser.java
 
b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/Destination.java
similarity index 51%
copy from 
dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/ConditionRuleParser.java
copy to 
dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/Destination.java
index 9aa80f908b..f5bb74c7e2 100644
--- 
a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/ConditionRuleParser.java
+++ 
b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/Destination.java
@@ -16,39 +16,31 @@
  */
 package org.apache.dubbo.rpc.cluster.router.condition.config.model;
 
-import org.apache.dubbo.common.utils.CollectionUtils;
+import org.apache.dubbo.rpc.Invoker;
+import org.apache.dubbo.rpc.cluster.router.state.BitList;
 
-import java.util.Map;
+public class Destination<T> {
+    private int weight;
+    private BitList<Invoker<T>> invokers;
 
-import org.yaml.snakeyaml.LoaderOptions;
-import org.yaml.snakeyaml.Yaml;
-import org.yaml.snakeyaml.constructor.SafeConstructor;
+    Destination(int weight, BitList<Invoker<T>> invokers) {
+        this.weight = weight;
+        this.invokers = invokers;
+    }
 
-/**
- * %YAML1.2
- *
- * scope: application
- * runtime: true
- * force: false
- * conditions:
- *   - >
- *     method!=sayHello =>
- *   - >
- *     ip=127.0.0.1
- *     =>
- *     1.1.1.1
- */
-public class ConditionRuleParser {
+    public int getWeight() {
+        return weight;
+    }
 
-    public static ConditionRouterRule parse(String rawRule) {
-        Yaml yaml = new Yaml(new SafeConstructor(new LoaderOptions()));
-        Map<String, Object> map = yaml.load(rawRule);
-        ConditionRouterRule rule = ConditionRouterRule.parseFromMap(map);
-        rule.setRawRule(rawRule);
-        if (CollectionUtils.isEmpty(rule.getConditions())) {
-            rule.setValid(false);
-        }
+    public void setWeight(int weight) {
+        this.weight = weight;
+    }
+
+    public BitList<Invoker<T>> getInvokers() {
+        return invokers;
+    }
 
-        return rule;
+    public void setInvokers(BitList<Invoker<T>> invokers) {
+        this.invokers = invokers;
     }
 }
diff --git 
a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/DestinationSet.java
 
b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/DestinationSet.java
new file mode 100644
index 0000000000..42f7eef2a2
--- /dev/null
+++ 
b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/DestinationSet.java
@@ -0,0 +1,73 @@
+/*
+ * 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.dubbo.rpc.cluster.router.condition.config.model;
+
+import org.apache.dubbo.rpc.Invoker;
+import org.apache.dubbo.rpc.cluster.router.state.BitList;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.ThreadLocalRandom;
+
+public class DestinationSet<T> {
+    private final List<Destination<T>> destinations;
+    private long weightSum;
+    private final ThreadLocalRandom random;
+
+    public DestinationSet() {
+        this.destinations = new ArrayList<>();
+        this.weightSum = 0;
+        this.random = ThreadLocalRandom.current();
+    }
+
+    public void addDestination(int weight, BitList<Invoker<T>> invokers) {
+        destinations.add(new Destination(weight, invokers));
+        weightSum += weight;
+    }
+
+    public BitList<Invoker<T>> randDestination() {
+        if (destinations.size() == 1) {
+            return destinations.get(0).getInvokers();
+        }
+
+        long sum = random.nextLong(weightSum);
+        for (Destination destination : destinations) {
+            sum -= destination.getWeight();
+            if (sum <= 0) {
+                return destination.getInvokers();
+            }
+        }
+        return BitList.emptyList();
+    }
+
+    public List<Destination<T>> getDestinations() {
+        return destinations;
+    }
+
+    public long getWeightSum() {
+        return weightSum;
+    }
+
+    public void setWeightSum(long weightSum) {
+        this.weightSum = weightSum;
+    }
+
+    public Random getRandom() {
+        return random;
+    }
+}
diff --git 
a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/ConditionRuleParser.java
 
b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/MultiDestCondition.java
similarity index 52%
copy from 
dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/ConditionRuleParser.java
copy to 
dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/MultiDestCondition.java
index 9aa80f908b..b6c20893e3 100644
--- 
a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/ConditionRuleParser.java
+++ 
b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/MultiDestCondition.java
@@ -16,39 +16,33 @@
  */
 package org.apache.dubbo.rpc.cluster.router.condition.config.model;
 
-import org.apache.dubbo.common.utils.CollectionUtils;
-
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
-import org.yaml.snakeyaml.LoaderOptions;
-import org.yaml.snakeyaml.Yaml;
-import org.yaml.snakeyaml.constructor.SafeConstructor;
+public class MultiDestCondition {
+    private Map<String, String> from = new HashMap<>();
+    private List<Map<String, String>> to = new ArrayList<>();
 
-/**
- * %YAML1.2
- *
- * scope: application
- * runtime: true
- * force: false
- * conditions:
- *   - >
- *     method!=sayHello =>
- *   - >
- *     ip=127.0.0.1
- *     =>
- *     1.1.1.1
- */
-public class ConditionRuleParser {
+    public Map<String, String> getFrom() {
+        return from;
+    }
 
-    public static ConditionRouterRule parse(String rawRule) {
-        Yaml yaml = new Yaml(new SafeConstructor(new LoaderOptions()));
-        Map<String, Object> map = yaml.load(rawRule);
-        ConditionRouterRule rule = ConditionRouterRule.parseFromMap(map);
-        rule.setRawRule(rawRule);
-        if (CollectionUtils.isEmpty(rule.getConditions())) {
-            rule.setValid(false);
-        }
+    public void setFrom(Map<String, String> from) {
+        this.from = from;
+    }
+
+    public List<Map<String, String>> getTo() {
+        return to;
+    }
+
+    public void setTo(List<Map<String, String>> to) {
+        this.to = to;
+    }
 
-        return rule;
+    @Override
+    public String toString() {
+        return "MultiDestCondition{" + "from=" + from + ", to=" + to + '}';
     }
 }
diff --git 
a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/ConditionRouterRule.java
 
b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/MultiDestConditionRouterRule.java
similarity index 52%
copy from 
dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/ConditionRouterRule.java
copy to 
dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/MultiDestConditionRouterRule.java
index 7d9c936c3f..317b1da243 100644
--- 
a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/ConditionRouterRule.java
+++ 
b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/model/MultiDestConditionRouterRule.java
@@ -16,38 +16,39 @@
  */
 package org.apache.dubbo.rpc.cluster.router.condition.config.model;
 
+import org.apache.dubbo.common.utils.JsonUtils;
 import org.apache.dubbo.rpc.cluster.router.AbstractRouterRule;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
-import java.util.stream.Collectors;
 
 import static org.apache.dubbo.rpc.cluster.Constants.CONDITIONS_KEY;
 
-public class ConditionRouterRule extends AbstractRouterRule {
-    private List<String> conditions;
+public class MultiDestConditionRouterRule extends AbstractRouterRule {
 
-    @SuppressWarnings("unchecked")
-    public static ConditionRouterRule parseFromMap(Map<String, Object> map) {
-        ConditionRouterRule conditionRouterRule = new ConditionRouterRule();
-        conditionRouterRule.parseFromMap0(map);
+    private List<MultiDestCondition> conditions;
 
-        Object conditions = map.get(CONDITIONS_KEY);
-        if (conditions != null && 
List.class.isAssignableFrom(conditions.getClass())) {
-            conditionRouterRule.setConditions(
-                    ((List<Object>) 
conditions).stream().map(String::valueOf).collect(Collectors.toList()));
+    public static AbstractRouterRule parseFromMap(Map<String, Object> map) {
+
+        MultiDestConditionRouterRule multiDestConditionRouterRule = new 
MultiDestConditionRouterRule();
+        multiDestConditionRouterRule.parseFromMap0(map);
+        List<Map<String, String>> conditions = (List<Map<String, String>>) 
map.get(CONDITIONS_KEY);
+        List<MultiDestCondition> multiDestConditions = new ArrayList<>();
+
+        for (Map<String, String> condition : conditions) {
+            multiDestConditions.add((MultiDestCondition) 
JsonUtils.convertObject(condition, MultiDestCondition.class));
         }
+        multiDestConditionRouterRule.setConditions(multiDestConditions);
 
-        return conditionRouterRule;
+        return multiDestConditionRouterRule;
     }
 
-    public ConditionRouterRule() {}
-
-    public List<String> getConditions() {
+    public List<MultiDestCondition> getConditions() {
         return conditions;
     }
 
-    public void setConditions(List<String> conditions) {
+    public void setConditions(List<MultiDestCondition> conditions) {
         this.conditions = conditions;
     }
 }
diff --git 
a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/router/condition/config/ConditionStateRouterTestV31.java
 
b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/router/condition/config/ConditionStateRouterTestV31.java
new file mode 100644
index 0000000000..e4db2abf87
--- /dev/null
+++ 
b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/router/condition/config/ConditionStateRouterTestV31.java
@@ -0,0 +1,466 @@
+/*
+ * 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.dubbo.rpc.cluster.router.condition.config;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.config.configcenter.ConfigChangeType;
+import org.apache.dubbo.common.config.configcenter.ConfigChangedEvent;
+import org.apache.dubbo.common.utils.Holder;
+import org.apache.dubbo.rpc.Invoker;
+import org.apache.dubbo.rpc.RpcInvocation;
+import org.apache.dubbo.rpc.cluster.router.AbstractRouterRule;
+import org.apache.dubbo.rpc.cluster.router.MockInvoker;
+import 
org.apache.dubbo.rpc.cluster.router.condition.config.model.ConditionRuleParser;
+import 
org.apache.dubbo.rpc.cluster.router.condition.config.model.MultiDestConditionRouterRule;
+import org.apache.dubbo.rpc.cluster.router.state.BitList;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class ConditionStateRouterTestV31 {
+
+    private static BitList<Invoker<String>> invokers;
+
+    @BeforeAll
+    public static void setUp() {
+
+        List<String> providerUrls = Arrays.asList(
+                "dubbo://127.0.0.1/com.foo.BarService",
+                "dubbo://127.0.0.1/com.foo.BarService",
+                "dubbo://127.0.0.1/com.foo.BarService?env=normal",
+                "dubbo://127.0.0.1/com.foo.BarService?env=normal",
+                "dubbo://127.0.0.1/com.foo.BarService?env=normal",
+                "dubbo://127.0.0.1/com.foo.BarService?region=beijing",
+                "dubbo://127.0.0.1/com.foo.BarService?region=beijing",
+                "dubbo://127.0.0.1/com.foo.BarService?region=beijing",
+                "dubbo://127.0.0.1/com.foo.BarService?region=beijing&env=gray",
+                "dubbo://127.0.0.1/com.foo.BarService?region=beijing&env=gray",
+                "dubbo://127.0.0.1/com.foo.BarService?region=beijing&env=gray",
+                "dubbo://127.0.0.1/com.foo.BarService?region=beijing&env=gray",
+                
"dubbo://127.0.0.1/com.foo.BarService?region=beijing&env=normal",
+                "dubbo://127.0.0.1/com.foo.BarService?region=hangzhou",
+                "dubbo://127.0.0.1/com.foo.BarService?region=hangzhou",
+                
"dubbo://127.0.0.1/com.foo.BarService?region=hangzhou&env=gray",
+                
"dubbo://127.0.0.1/com.foo.BarService?region=hangzhou&env=gray",
+                
"dubbo://127.0.0.1/com.foo.BarService?region=hangzhou&env=normal",
+                
"dubbo://127.0.0.1/com.foo.BarService?region=hangzhou&env=normal",
+                
"dubbo://127.0.0.1/com.foo.BarService?region=hangzhou&env=normal",
+                "dubbo://dubbo.apache.org/com.foo.BarService",
+                "dubbo://dubbo.apache.org/com.foo.BarService",
+                "dubbo://dubbo.apache.org/com.foo.BarService?env=normal",
+                "dubbo://dubbo.apache.org/com.foo.BarService?env=normal",
+                "dubbo://dubbo.apache.org/com.foo.BarService?env=normal",
+                "dubbo://dubbo.apache.org/com.foo.BarService?region=beijing",
+                "dubbo://dubbo.apache.org/com.foo.BarService?region=beijing",
+                "dubbo://dubbo.apache.org/com.foo.BarService?region=beijing",
+                
"dubbo://dubbo.apache.org/com.foo.BarService?region=beijing&env=gray",
+                
"dubbo://dubbo.apache.org/com.foo.BarService?region=beijing&env=gray",
+                
"dubbo://dubbo.apache.org/com.foo.BarService?region=beijing&env=gray",
+                
"dubbo://dubbo.apache.org/com.foo.BarService?region=beijing&env=gray",
+                
"dubbo://dubbo.apache.org/com.foo.BarService?region=beijing&env=normal",
+                "dubbo://dubbo.apache.org/com.foo.BarService?region=hangzhou",
+                "dubbo://dubbo.apache.org/com.foo.BarService?region=hangzhou",
+                
"dubbo://dubbo.apache.org/com.foo.BarService?region=hangzhou&env=gray",
+                
"dubbo://dubbo.apache.org/com.foo.BarService?region=hangzhou&env=gray",
+                
"dubbo://dubbo.apache.org/com.foo.BarService?region=hangzhou&env=normal",
+                
"dubbo://dubbo.apache.org/com.foo.BarService?region=hangzhou&env=normal",
+                
"dubbo://dubbo.apache.org/com.foo.BarService?region=hangzhou&env=normal");
+
+        List<Invoker<String>> invokerList = providerUrls.stream()
+                .map(url -> new MockInvoker<String>(URL.valueOf(url)))
+                .collect(Collectors.toList());
+
+        invokers = new BitList<>(invokerList);
+    }
+
+    @Test
+    public void testParseRawRule() {
+        String config =
+                "configVersion: v3.1\n" + "scope: service\n" + "force: 
false\n" + "runtime: true\n" + "enabled: true\n"
+                        + "key: shop\n" + "conditions:\n" + "  - from:\n" + "  
    match:\n" + "    to:\n"
+                        + "      - match: region=$region & version=v1\n"
+                        + "      - match: region=$region & version=v2\n" + "   
     weight: 200\n"
+                        + "      - match: region=$region & version=v3\n" + "   
     weight: 300\n" + "  - from:\n"
+                        + "      match: region=beijing & version=v1\n" + "    
to:\n"
+                        + "      - match: env=$env & region=beijing\n";
+
+        AbstractRouterRule routerRule = ConditionRuleParser.parse(config);
+        Assertions.assertInstanceOf(MultiDestConditionRouterRule.class, 
routerRule);
+        MultiDestConditionRouterRule rule = (MultiDestConditionRouterRule) 
routerRule;
+        Assertions.assertEquals(rule.getConditions().size(), 2);
+        Assertions.assertEquals(rule.getConditions().get(0).getTo().size(), 3);
+        Assertions.assertEquals(rule.getConditions().get(1).getTo().size(), 1);
+        System.out.println("rule.getConditions() = " + rule.getConditions());
+    }
+
+    @Test
+    public void testMultiplyConditionRoute() {
+
+        String rawRule = "configVersion: v3.1\n" + "scope: service\n"
+                + "key: com.foo.BarService\n"
+                + "force: false\n"
+                + "runtime: true\n"
+                + "enabled: true\n"
+                + "conditions:\n"
+                + "  - from:\n"
+                + "      match: env=gray\n"
+                + "    to:\n"
+                + "      - match: env!=gray\n"
+                + "        weight: 100";
+
+        ServiceStateRouter<String> router = new ServiceStateRouter<>(
+                
URL.valueOf("consumer://127.0.0.1/com.foo.BarService?env=gray&region=beijing"));
+        router.process(new ConfigChangedEvent("com.foo.BarService", "", 
rawRule, ConfigChangeType.ADDED));
+
+        RpcInvocation invocation = new RpcInvocation();
+        invocation.setMethodName("getComment");
+
+        BitList<Invoker<String>> result = router.route(
+                invokers.clone(),
+                
URL.valueOf("consumer://127.0.0.1/com.foo.BarService?env=gray&region=beijing&version=v1"),
+                invocation,
+                false,
+                new Holder<>());
+
+        int count = 0;
+        for (Invoker<String> invoker : invokers) {
+            String url = invoker.getUrl().toString();
+            if (url.contains("env") && !url.contains("gray")) {
+                count++;
+            }
+        }
+        Assertions.assertEquals(count, result.size());
+    }
+
+    @Test
+    public void testRemoveDuplicatesCondition() {
+        String rawRule = "configVersion: v3.1\n" + "scope: service\n"
+                + "key: org.apache.dubbo.samples.CommentService\n"
+                + "force: false\n"
+                + "runtime: true\n"
+                + "enabled: true\n"
+                + "conditions:\n"
+                + "  - from:\n"
+                + "      match: env=gray\n"
+                + "    to:\n"
+                + "      - match: env!=gray\n"
+                + "        weight: 100\n"
+                + "  - from:\n"
+                + "      match: env=gray\n"
+                + "    to:\n"
+                + "      - match: env!=gray\n"
+                + "        weight: 100";
+        ServiceStateRouter<String> router = new ServiceStateRouter<>(
+                
URL.valueOf("consumer://127.0.0.1/com.foo.BarService?env=gray&region=beijing"));
+        router.process(new ConfigChangedEvent("com.foo.BarService", "", 
rawRule, ConfigChangeType.ADDED));
+
+        RpcInvocation invocation = new RpcInvocation();
+        invocation.setMethodName("getComment");
+
+        BitList<Invoker<String>> result = router.route(
+                invokers.clone(),
+                
URL.valueOf("consumer://127.0.0.1/com.foo.BarService?env=gray&region=beijing"),
+                invocation,
+                false,
+                new Holder<>());
+
+        int count = 0;
+        for (Invoker<String> invoker : invokers) {
+            String url = invoker.getUrl().toString();
+            if (url.contains("env") && !url.contains("gray")) {
+                count++;
+            }
+        }
+        Assertions.assertEquals(count, result.size());
+    }
+
+    @Test
+    public void testConsequentCondition() {
+        String rawRule = "configVersion: v3.1\n" + "scope: service\n"
+                + "key: org.apache.dubbo.samples.CommentService\n"
+                + "force: false\n"
+                + "runtime: true\n"
+                + "enabled: true\n"
+                + "conditions:\n"
+                + "  - from:\n"
+                + "      match: env=gray\n"
+                + "    to:\n"
+                + "      - match: env!=gray\n"
+                + "        weight: 100\n"
+                + "  - from:\n"
+                + "      match: region=beijing\n"
+                + "    to:\n"
+                + "      - match: region=beijing\n"
+                + "        weight: 100\n"
+                + "  - from:\n"
+                + "    to:\n"
+                + "      - match: host!=127.0.0.1";
+
+        ServiceStateRouter<String> router = new ServiceStateRouter<>(
+                
URL.valueOf("consumer://127.0.0.1/com.foo.BarService?env=gray&region=beijing"));
+        router.process(new ConfigChangedEvent("com.foo.BarService", "", 
rawRule, ConfigChangeType.ADDED));
+
+        RpcInvocation invocation = new RpcInvocation();
+        invocation.setMethodName("getComment");
+
+        BitList<Invoker<String>> result = router.route(
+                invokers.clone(),
+                
URL.valueOf("consumer://127.0.0.1/com.foo.BarService?env=gray&region=beijing"),
+                invocation,
+                false,
+                new Holder<>());
+
+        int count = 0;
+        for (Invoker<String> invoker : invokers) {
+            String url = invoker.getUrl().toString();
+            if ((url.contains("env") && !url.contains("gray"))
+                    && url.contains("region=beijing")
+                    && !url.contains("127.0.0.1")) {
+                count++;
+            }
+        }
+        Assertions.assertEquals(count, result.size());
+    }
+
+    @Test
+    public void testUnMatchCondition() {
+        String rawRule = "configVersion: v3.1\n" + "scope: service\n"
+                + "key: org.apache.dubbo.samples.CommentService\n"
+                + "force: false\n"
+                + "runtime: true\n"
+                + "enabled: true\n"
+                + "conditions:\n"
+                + "  - from:\n"
+                + "      match: env!=gray\n"
+                + "    to:\n"
+                + "      - match: env=gray\n"
+                + "        weight: 100\n"
+                + "  - from:\n"
+                + "      match: region!=beijing\n"
+                + "    to:\n"
+                + "      - match: region=beijing\n"
+                + "        weight: 100\n"
+                + "  - from:\n"
+                + "    to:\n"
+                + "      - match: host!=127.0.0.1";
+
+        ServiceStateRouter<String> router = new ServiceStateRouter<>(
+                
URL.valueOf("consumer://127.0.0.1/com.foo.BarService?env=gray&region=beijing"));
+        router.process(new ConfigChangedEvent("com.foo.BarService", "", 
rawRule, ConfigChangeType.ADDED));
+
+        RpcInvocation invocation = new RpcInvocation();
+        invocation.setMethodName("getComment");
+
+        BitList<Invoker<String>> result = router.route(
+                invokers.clone(),
+                
URL.valueOf("consumer://127.0.0.1/com.foo.BarService?env=gray&region=beijing"),
+                invocation,
+                false,
+                new Holder<>());
+
+        int count = 0;
+        for (Invoker<String> invoker : invokers) {
+            String url = invoker.getUrl().toString();
+            if (!url.contains("127.0.0.1")) {
+                count++;
+            }
+        }
+        Assertions.assertEquals(count, result.size());
+    }
+
+    @Test
+    public void testMatchAndRouteZero() {
+        String rawRule = "configVersion: v3.1\n" + "scope: service\n"
+                + "key: org.apache.dubbo.samples.CommentService\n"
+                + "force: true\n"
+                + "runtime: true\n"
+                + "enabled: true\n"
+                + "conditions:\n"
+                + "  - from:\n"
+                + "      match: env=gray\n"
+                + "    to:\n"
+                + "      - match: env=ErrTag\n"
+                + "        weight: 100\n"
+                + "  - from:\n"
+                + "      match: region!=beijing\n"
+                + "    to:\n"
+                + "      - match: region=beijing\n"
+                + "        weight: 100\n"
+                + "  - from:\n"
+                + "    to:\n"
+                + "      - match: host!=127.0.0.1";
+
+        ServiceStateRouter<String> router = new ServiceStateRouter<>(
+                
URL.valueOf("consumer://127.0.0.1/com.foo.BarService?env=gray&region=beijing"));
+        router.process(new ConfigChangedEvent("com.foo.BarService", "", 
rawRule, ConfigChangeType.ADDED));
+
+        RpcInvocation invocation = new RpcInvocation();
+        invocation.setMethodName("getComment");
+
+        BitList<Invoker<String>> result = router.route(
+                invokers.clone(),
+                
URL.valueOf("consumer://127.0.0.1/com.foo.BarService?env=gray&region=beijing"),
+                invocation,
+                false,
+                new Holder<>());
+        Assertions.assertEquals(0, result.size());
+    }
+
+    @Test
+    public void testMatchRouteZeroAndIgnore() {
+        String rawRule = "configVersion: v3.1\n" + "scope: service\n"
+                + "key: org.apache.dubbo.samples.CommentService\n"
+                + "force: false\n"
+                + "runtime: true\n"
+                + "enabled: true\n"
+                + "conditions:\n"
+                + "  - from:\n"
+                + "      match: region=beijing\n"
+                + "    to:\n"
+                + "      - match: region!=beijing\n"
+                + "        weight: 100\n"
+                + "  - from:\n"
+                + "    to:\n"
+                + "      - match: host!=127.0.0.1\n"
+                + "  - from:\n"
+                + "      match: env=gray\n"
+                + "    to:\n"
+                + "      - match: env=ErrTag\n"
+                + "        weight: 100";
+
+        ServiceStateRouter<String> router = new ServiceStateRouter<>(
+                
URL.valueOf("consumer://127.0.0.1/com.foo.BarService?env=gray&region=beijing"));
+        router.process(new ConfigChangedEvent("com.foo.BarService", "", 
rawRule, ConfigChangeType.ADDED));
+
+        RpcInvocation invocation = new RpcInvocation();
+        invocation.setMethodName("getComment");
+
+        BitList<Invoker<String>> result = router.route(
+                invokers.clone(),
+                
URL.valueOf("consumer://127.0.0.1/com.foo.BarService?env=gray&region=beijing"),
+                invocation,
+                false,
+                new Holder<>());
+
+        int count = 0;
+        for (Invoker<String> invoker : invokers) {
+            String url = invoker.getUrl().toString();
+            if ((url.contains("region") && !url.contains("beijing") && 
!url.contains("127.0.0.1"))) {
+                count++;
+            }
+        }
+        Assertions.assertEquals(count, result.size());
+    }
+
+    @Test
+    public void testTrafficDisabledAndIgnoreConditionRouteForce() {
+        String rawRule = "configVersion: v3.1\n" + "scope: service\n"
+                + "key: org.apache.dubbo.samples.CommentService\n"
+                + "force: false\n"
+                + "runtime: true\n"
+                + "enabled: true\n"
+                + "conditions:\n"
+                + "  - from:\n"
+                + "      match: host=127.0.0.1\n"
+                + "  - from:\n"
+                + "      match: env=gray\n"
+                + "    to:\n"
+                + "      - match: env!=gray\n"
+                + "        weight: 100\n"
+                + "  - to:\n"
+                + "      - match: region!=beijing";
+
+        ServiceStateRouter<String> router = new ServiceStateRouter<>(
+                
URL.valueOf("consumer://127.0.0.1/com.foo.BarService?env=gray&region=beijing"));
+        router.process(new ConfigChangedEvent("com.foo.BarService", "", 
rawRule, ConfigChangeType.ADDED));
+
+        RpcInvocation invocation = new RpcInvocation();
+        invocation.setMethodName("getComment");
+
+        BitList<Invoker<String>> result = router.route(
+                invokers.clone(),
+                
URL.valueOf("consumer://127.0.0.1/com.foo.BarService?env=gray&region=beijing"),
+                invocation,
+                false,
+                new Holder<>());
+
+        Assertions.assertEquals(0, result.size());
+    }
+
+    @Test
+    public void testMultiplyDestination() {
+        String rawRule = "configVersion: v3.1\n" + "scope: service\n"
+                + "key: org.apache.dubbo.samples.CommentService\n"
+                + "force: false\n"
+                + "runtime: true\n"
+                + "enabled: true\n"
+                + "conditions:\n"
+                + "  - from:\n"
+                + "      match: env=gray\n"
+                + "    to:\n"
+                + "      - match: env!=gray\n"
+                + "        weight: 100\n"
+                + "      - match: env=gray\n"
+                + "        weight: 900\n"
+                + "  - from:\n"
+                + "      match: region=beijing\n"
+                + "    to:\n"
+                + "      - match: region!=beijing\n"
+                + "        weight: 100\n"
+                + "      - match: region=beijing\n"
+                + "        weight: 200";
+
+        ServiceStateRouter<String> router = new ServiceStateRouter<>(
+                
URL.valueOf("consumer://127.0.0.1/com.foo.BarService?env=gray&region=beijing"));
+        router.process(new ConfigChangedEvent("com.foo.BarService", "", 
rawRule, ConfigChangeType.ADDED));
+
+        RpcInvocation invocation = new RpcInvocation();
+        invocation.setMethodName("getComment");
+
+        Map<Integer, Integer> actualDistribution = new HashMap<>();
+        for (int i = 0; i < 1000; i++) {
+            BitList<Invoker<String>> result = router.route(
+                    invokers.clone(),
+                    
URL.valueOf("consumer://127.0.0.1/com.foo.BarService?env=gray&region=beijing"),
+                    invocation,
+                    false,
+                    new Holder<>());
+
+            actualDistribution.put(result.size(), 
actualDistribution.getOrDefault(result.size(), 0) + 1);
+        }
+        System.out.println("actualDistribution = " + actualDistribution);
+        int sum = 0;
+        for (Map.Entry<Integer, Integer> entry : 
actualDistribution.entrySet()) {
+            sum += entry.getValue();
+        }
+        assertEquals(actualDistribution.size(), 4); // 8 6 4 2
+        Assertions.assertNotNull(actualDistribution.get(8));
+        Assertions.assertNotNull(actualDistribution.get(6));
+        Assertions.assertNotNull(actualDistribution.get(4));
+        Assertions.assertNotNull(actualDistribution.get(2));
+        assertEquals(sum, 1000);
+    }
+}

Reply via email to