Dubbo源码学习13

本篇幅分析Dubbo的配置规则Configurators和路由规则Routers的使用以及源码实现

Configurators

覆盖规则是Dubbo设计的在无需重启应用的情况下,动态调整RPC调用行为的一种能力向注册中心写入动态配置覆盖规则。该功能通常由监控中心或治理中心的页面完成。

RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://10.20.153.10:2181"));
registry.register(URL.valueOf("override://0.0.0.0/com.foo.BarService?category=configurators&dynamic=false&application=foo&timeout=1000"));

其中:

  • override:// 表示数据采用覆盖方式,支持 override 和 absent,可扩展,必填

  • 0.0.0.0 表示对所有 IP 地址生效,如果只想覆盖某个 IP 的数据,请填入具体 IP,必填

  • com.foo.BarService 表示只对指定服务生效,必填

  • category=configurators 表示该数据为动态配置类型,必填

  • dynamic=false 表示该数据为持久数据,当注册方退出时,数据依然保存在注册中心,必填

  • enabled=true 覆盖规则是否生效,可不填,缺省生效。

  • application=foo 表示只对指定应用生效,可不填,表示对所有应用生效。

  • timeout=1000 表示将满足以上条件的 timeout 参数的值覆盖为 1000。如果想覆盖其它参数,直接加在 override 的 URL 参数上。

示例:

  • 禁用提供者:(通常用于临时踢除某台提供者机器,相似的,禁止消费者访问请使用路由规则)
override://10.20.153.10/com.foo.BarService?category=configurators&dynamic=false&disbaled=true
  • 调整权重:(通常用于容量评估,缺省权重为 100)
override://10.20.153.10/com.foo.BarService?category=configurators&dynamic=false&weight=200
  • 调整负载均衡策略:(缺省负载均衡策略为 random)
override://10.20.153.10/com.foo.BarService?category=configurators&dynamic=false&loadbalance=leastactive
  • 服务降级:(通常用于临时屏蔽某个出错的非关键服务)
override://0.0.0.0/com.foo.BarService?category=configurators&dynamic=false&application=foo&mock=force:return+null

实现类:

  • Configurator:配置接口,该接口继承了Comparable,即Configurator是是有序的
public interface Configurator extends Comparable<Configurator> {

    /**
     * 获取配置url
     * override://10.20.153.10/com.foo.BarService?category=configurators&dynamic=false&disbaled=true
     * @return configurator url.
     */
    URL getUrl();

    /**
     * 配置提供者url
     * @param url - old provider url.
     * @return new provider url.
     */
    URL configure(URL url);

}
  • AbstractConfigurator:实现了,配置覆盖前的条件匹配,过滤configurator url中的conditionKey,以及configurator的排序,配置的覆盖追加交给子类实现。在configureIfMatch方法中我们不难得知带有"~"前缀的被当成条件处理,通过~前缀我们可以自定义匹配的条件然后在应用配置的覆盖或者追加。
public abstract class AbstractConfigurator implements Configurator {
    /**
     * 携带配置的Url
     * override://10.20.153.10/com.foo.BarService?category=configurators&dynamic=false&disbaled=true
     */
    private final URL configuratorUrl;

    public AbstractConfigurator(URL url) {
        if (url == null) {
            throw new IllegalArgumentException("configurator url == null");
        }
        this.configuratorUrl = url;
    }

    public static void main(String[] args) {
        System.out.println(URL.encode("timeout=100"));
    }

    @Override
    public URL getUrl() {
        return configuratorUrl;
    }

    @Override
    public URL configure(URL url) {
        //如果configuratorUrl为null或者configuratorUrl的host为null或者当前参数url为null或者当前url的host为null
        //直接返回url不处理
        if (configuratorUrl == null || configuratorUrl.getHost() == null
                || url == null || url.getHost() == null) {
            return url;
        }
        // override输入提供端地址,意图是控制提供者机器。可能在提供端生效 也可能在消费端生效
        if (configuratorUrl.getPort() != 0) {
            if (url.getPort() == configuratorUrl.getPort()) {
                return configureIfMatch(url.getHost(), url);
            }
        } else {// 替代网址没有端口,表示ip替代网址指定为使用者地址或0.0.0.0
            // 1.如果url为消费方则它是一个消费者IP地址,目的是控制一个特定的消费者实例,它必须在消费者端生效,任何接收到此替代URL的提供者都应忽略;
            // 2.如果url为提供方,则该url可以在消费者上使用,也可以在提供者上使用
            if (url.getParameter(Constants.SIDE_KEY, Constants.PROVIDER).equals(Constants.CONSUMER)) {
                return configureIfMatch(NetUtils.getLocalHost(), url);
            } else if (url.getParameter(Constants.SIDE_KEY, Constants.CONSUMER).equals(Constants.PROVIDER)) {
                return configureIfMatch(Constants.ANYHOST_VALUE, url);
            }
        }
        return url;
    }

    private URL configureIfMatch(String host, URL url) {
        //匹配host
        if (Constants.ANYHOST_VALUE.equals(configuratorUrl.getHost()) || host.equals(configuratorUrl.getHost())) {
            //该配置url携带的application属性
            String configApplication = configuratorUrl.getParameter(Constants.APPLICATION_KEY,
                    configuratorUrl.getUsername());
            //需要被覆盖配置的url的application属性
            String currentApplication = url.getParameter(Constants.APPLICATION_KEY, url.getUsername());
            //如果override://xxx没有配置application或者为*或者application相同
            if (configApplication == null || Constants.ANY_VALUE.equals(configApplication)
                    || configApplication.equals(currentApplication)) {
                //条件keys,不参与url的配置覆盖或者追加
                Set<String> conditionKeys = new HashSet<String>();
                conditionKeys.add(Constants.CATEGORY_KEY);
                conditionKeys.add(Constants.CHECK_KEY);
                conditionKeys.add(Constants.DYNAMIC_KEY);
                conditionKeys.add(Constants.ENABLED_KEY);
                //遍历configuratorUrl的parameters
                for (Map.Entry<String, String> entry : configuratorUrl.getParameters().entrySet()) {
                    String key = entry.getKey();
                    String value = entry.getValue();
                    // "application" "side" 带有 `"~"` 开头的 KEY都需要放入conditionKeys中
                    if (key.startsWith("~") || Constants.APPLICATION_KEY.equals(key) || Constants.SIDE_KEY.equals(key)) {
                        conditionKeys.add(key);
                        //如果value不为null并且value 不等于* 并且value的值不等于 截取"~"之后的key获取到的值 说明条件不匹配直接返回
                        if (value != null && !Constants.ANY_VALUE.equals(value)
                                && !value.equals(url.getParameter(key.startsWith("~") ? key.substring(1) : key))) {
                            return url;
                        }
                    }
                }
                return doConfigure(url, configuratorUrl.removeParameters(conditionKeys));
            }
        }
        return url;
    }

    /**
     * 按照host排序sort优先级
     * 1. 具有特定主机IP的URL的优先级应高于0.0.0.0
     * 2. 如果两个URL具有相同的主机,则按优先级值进行比较;
     *
     * @param o
     * @return
     */
    @Override
    public int compareTo(Configurator o) {
        if (o == null) {
            return -1;
        }
        //比较host
        int ipCompare = getUrl().getHost().compareTo(o.getUrl().getHost());
        //host相同通过比较priority
        if (ipCompare == 0) {
            int i = getUrl().getParameter(Constants.PRIORITY_KEY, 0),
                    j = o.getUrl().getParameter(Constants.PRIORITY_KEY, 0);
            return i < j ? -1 : (i == j ? 0 : 1);
        } else {
            return ipCompare;
        }


    }

    protected abstract URL doConfigure(URL currentUrl, URL configUrl);

}
  • AbsentConfigurator:添加不存在的配置
public class AbsentConfigurator extends AbstractConfigurator {

    public AbsentConfigurator(URL url) {
        super(url);
    }

    @Override
    public URL doConfigure(URL currentUrl, URL configUrl) {
        //添加不存在的属性
        return currentUrl.addParametersIfAbsent(configUrl.getParameters());
    }

}
  • OverrideConfigurator:覆盖已有属性添加不存在的属性
public class OverrideConfigurator extends AbstractConfigurator {

    public OverrideConfigurator(URL url) {
        super(url);
    }

    @Override
    public URL doConfigure(URL currentUrl, URL configUrl) {
        //覆盖currentUrl中的属性
        return currentUrl.addParameters(configUrl.getParameters());
    }

}

Routers

路由规则决定一次 dubbo 服务调用的目标服务器,分为条件路由规则和脚本路由规则,并且支持可扩展 向注册中心写入路由规则的操作通常由监控中心或治理中心的页面完成

RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://10.20.153.10:2181"));
registry.register(URL.valueOf("route://0.0.0.0/com.foo.BarService?category=routers&dynamic=false&rule=" + URL.encode("host = 10.20.153.10 => host = 10.20.153.11")));

其中:

  • route:// 表示路由规则的类型,支持条件路由规则和脚本路由规则,可扩展,必填
  • 0.0.0.0 表示对所有 IP 地址生效,如果只想对某个 IP 的生效,请填入具体 IP,必填
  • com.foo.BarService 表示只对指定服务生效,必填
  • group=foo 对指定服务的指定group生效,不填表示对未配置group的指定服务生效
  • version=1.0对指定服务的指定version生效,不填表示对未配置version的指定服务生效
  • category=routers 表示该数据为动态配置类型,必填
  • dynamic=false 表示该数据为持久数据,当注册方退出时,数据依然保存在注册中心,必填
  • enabled=true 覆盖规则是否生效,可不填,缺省生效。
  • force=false 当路由结果为空时,是否强制执行,如果不强制执行,路由结果为空的路由规则将自动失效,可不填,缺省为 false
  • runtime=false 是否在每次调用时执行路由规则,否则只在提供者地址列表变更时预先执行并缓存结果,调用时直接从缓存中获取路由结果。如果用了参数路由,必须设为 true,需要注意设置会影响调用的性能,可不填,缺省为 false
  • priority=1 路由规则的优先级,用于排序,优先级越大越靠前执行,可不填,缺省为 0
  • rule=URL.encode("host = 10.20.153.10 => host = 10.20.153.11") 表示路由规则的内容,必填

条件路由规则

基于条件表达式的路由规则,如:host = 10.20.153.10 => host = 10.20.153.11

规则:

  • => 之前的为消费者匹配条件,所有参数和消费者的 URL 进行对比,当消费者满足匹配条件时,对该消费者执行后面的过滤规则。
  • => 之后为提供者地址列表的过滤条件,所有参数和提供者的 URL 进行对比,消费者最终只拿到过滤后的地址列表。
  • 如果匹配条件为空,表示对所有消费方应用,如:=> host != 10.20.153.11
  • 如果过滤条件为空,表示禁止访问,如:host = 10.20.153.10 =>

表达式

参数支持:

  • 服务调用信息,如:method, argument 等,暂不支持参数路由
  • URL 本身的字段,如:protocol, host, port 等
  • 以及 URL 上的所有参数,如:application, organization 等

条件支持:

  • 等号 = 表示"匹配",如:host = 10.20.153.10
  • 不等号 != 表示"不匹配",如:host != 10.20.153.10

值支持:

  • 以逗号 , 分隔多个值,如:host != 10.20.153.10,10.20.153.11
  • 以星号 * 结尾,表示通配,如:host != 10.20.*
  • 以美元符 $ 开头,表示引用消费者参数,如:host = $host

示例

排除预发布机:所有消费者过滤掉172.22.3.91这台提供者

=> host != 172.22.3.91

白名单:注意:一个服务只能有一条白名单规则,否则两条规则交叉,就都被筛选掉了

host != 10.20.153.10,10.20.153.11 =>

黑名单:指定10.20.153.10,10.20.153.11禁止消费

host = 10.20.153.10,10.20.153.11 =>

服务寄宿在应用上,只暴露一部分的机器,防止整个集群挂掉:

=> host = 172.22.3.1*,172.22.3.2*

为重要应用提供额外的机器:

application != kylin => host != 172.22.3.95,172.22.3.96

读写分离:

method = find*,list*,get*,is* => host = 172.22.3.94,172.22.3.95,172.22.3.96
method != find*,list*,get*,is* => host = 172.22.3.97,172.22.3.98

前后台分离

application = bops => host = 172.22.3.91,172.22.3.92,172.22.3.93
application != bops => host = 172.22.3.94,172.22.3.95,172.22.3.96

隔离不同机房网段:

host != 172.22.3.* => host != 172.22.3.*

提供者与消费者部署在同集群内,本机只访问本机的服务:

脚本路由规则

脚本路由规则支持 JDK 脚本引擎的所有脚本,比如:javascript, jruby, groovy 等,通过 type=javascript参数设置脚本类型,缺省为 javascript。

"script://0.0.0.0/com.foo.BarService?category=routers&dynamic=false&rule=" + URL.encode("(function route(invokers) { ... } (invokers))")

基于脚本引擎的路由规则,如:

(function route(invokers) {
    var result = new java.util.ArrayList(invokers.size());
    for (i = 0; i < invokers.size(); i ++) {
        if ("10.20.153.10".equals(invokers.get(i).getUrl().getHost())) {
            result.add(invokers.get(i));
        }
    }
    return result;
} (invokers)); // 表示立即执行方法

实现类

  • Router:抽象路由接口
public interface Router extends Comparable<Router>{

    /**
     *获取router url
     * route://0.0.0.0/com.foo.BarService?category=routers&dynamic=false&rule=" + URL.encode("host = 10.20.153.10 => host = 10.20.153.11
     * @return url
     */
    URL getUrl();

    /**
     * 路由.
     * @param invokers 调用者列表
     * @param url        refer url
     * @param invocation
     * @return routed invokers
     * @throws RpcException
     */
    <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;

    /**
     * 路由器的优先级,用于对路由器进行排序。
     *
     * @return router's priority
     */
    int getPriority();

}
  • AbstractRouter:路由接口的抽象实现类,添加了路由url、priority属性,实现了Comparator接口的compareTo方法
public abstract class AbstractRouter implements Router {
    /**
     * route://0.0.0.0/com.foo.BarService?category=routers&dynamic=false&rule=" + URL.encode("host = 10.20.153.10 => host = 10.20.153.11
     */
    protected URL url;
    /**
     * 路由优先级
     */
    protected int priority;

    @Override
    public URL getUrl() {
        return url;
    }

    @Override
    public int compareTo(Router o) {
        return (this.getPriority() < o.getPriority()) ? -1 : ((this.getPriority() == o.getPriority()) ? 0 : 1);
    }

    public int getPriority() {
        return priority;
    }
}
  • ScriptRouter:脚本路由实现类
public class ScriptRouter extends AbstractRouter {

    private static final Logger logger = LoggerFactory.getLogger(ScriptRouter.class);

    private static final int DEFAULT_PRIORITY = 1;

    private static final Map<String, ScriptEngine> engines = new ConcurrentHashMap<String, ScriptEngine>();

    private final ScriptEngine engine;

    private final String rule;

    public ScriptRouter(URL url) {
        //script://xxxx 赋值
        this.url = url;
        //获取属性类型type
        String type = url.getParameter(Constants.TYPE_KEY);
        //获取优先级属性priority
        this.priority = url.getParameter(Constants.PRIORITY_KEY, DEFAULT_PRIORITY);
        //获取rule的key
        String rule = url.getParameterAndDecoded(Constants.RULE_KEY);
        //没有type属性赋值javascript
        if (type == null || type.length() == 0) {
            type = Constants.DEFAULT_SCRIPT_TYPE_KEY;
        }
        //路由规则脚本没设置抛出异常
        if (rule == null || rule.length() == 0) {
            throw new IllegalStateException(new IllegalStateException("route rule can not be empty. rule:" + rule));
        }
        //获取ScriptEngine对象,解析脚本,没有创建有的话从缓存取
        ScriptEngine engine = engines.get(type);
        if (engine == null) {
            engine = new ScriptEngineManager().getEngineByName(type);
            if (engine == null) {
                throw new IllegalStateException(new IllegalStateException("Unsupported route rule type: " + type + ", rule: " + rule));
            }
            engines.put(type, engine);
        }
        this.engine = engine;
        this.rule = rule;
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException {
        try {
            /**
             * 通过engine执行定义的脚本获取route后的List<Invoker>
             */
            List<Invoker<T>> invokersCopy = new ArrayList<Invoker<T>>(invokers);
            Compilable compilable = (Compilable) engine;
            Bindings bindings = engine.createBindings();
            bindings.put("invokers", invokersCopy);
            bindings.put("invocation", invocation);
            bindings.put("context", RpcContext.getContext());
            CompiledScript function = compilable.compile(rule);
            Object obj = function.eval(bindings);
            if (obj instanceof Invoker[]) {
                invokersCopy = Arrays.asList((Invoker<T>[]) obj);
            } else if (obj instanceof Object[]) {
                invokersCopy = new ArrayList<Invoker<T>>();
                for (Object inv : (Object[]) obj) {
                    invokersCopy.add((Invoker<T>) inv);
                }
            } else {
                invokersCopy = (List<Invoker<T>>) obj;
            }
            return invokersCopy;
        } catch (ScriptException e) {
            //fail then ignore rule .invokers.
            logger.error("route error , rule has been ignored. rule: " + rule + ", method:" + invocation.getMethodName() + ", url: " + RpcContext.getContext().getUrl(), e);
            return invokers;
        }
    }

}
  • ConditionRouter:条件路由实现
public class ConditionRouter extends AbstractRouter {

    private static final Logger logger = LoggerFactory.getLogger(ConditionRouter.class);
    private static final int DEFAULT_PRIORITY = 2;
    /**
     *分组正则匹配
     */
    private static Pattern ROUTE_PATTERN = Pattern.compile("([&!=,]*)\\s*([^&!=,\\s]+)");
    /**
     * 当路由结果为空时,是否强制执行,如果不强制执行,路由结果为空的路由规则将自动失效,可不填,缺省为 false 。
     */
    private final boolean force;
    /**
     * 消费者匹配条件集合,通过解析【条件表达式 rule 的 `=>` 之前半部分】
     */
    private final Map<String, MatchPair> whenCondition;
    /**
     * 提供者地址列表的过滤条件,通过解析【条件表达式 rule 的 `=>` 之后半部分】
     */
    private final Map<String, MatchPair> thenCondition;

    public ConditionRouter(URL url) {
        this.url = url;
        //获取url中的priority属性
        this.priority = url.getParameter(Constants.PRIORITY_KEY, DEFAULT_PRIORITY);
        //获取force属性
        this.force = url.getParameter(Constants.FORCE_KEY, false);
        try {
            //获取rule属性
            String rule = url.getParameterAndDecoded(Constants.RULE_KEY);
            if (rule == null || rule.trim().length() == 0) {
                throw new IllegalArgumentException("Illegal route rule!");
            }
            //替换rule字符串中的consumer.属性和provider.属性
            rule = rule.replace("consumer.", "").replace("provider.", "");
            int i = rule.indexOf("=>");
            //分割消费者和提供者规则
            String whenRule = i < 0 ? null : rule.substring(0, i).trim();
            String thenRule = i < 0 ? rule.trim() : rule.substring(i + 2).trim();
            Map<String, MatchPair> when = StringUtils.isBlank(whenRule) || "true".equals(whenRule) ? new HashMap<String, MatchPair>() : parseRule(whenRule);
            Map<String, MatchPair> then = StringUtils.isBlank(thenRule) || "false".equals(thenRule) ? null : parseRule(thenRule);
            // 注意:应在业务级别上确定“何时条件”是否可以为空。
            this.whenCondition = when;
            this.thenCondition = then;
        } catch (ParseException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
    }

    /**
     * 结束后返回类似如下格式的字符串
     * {
     *     "host": {
     *         "matches": ["2.2.2.2"],
     *         "mismatches": ["1.1.1.1"]
     *     },
     *     "method": {
     *         "matches": ["hello"],
     *         "mismatches": ["echo"]
     *     }
     * }
     */
    private static Map<String, MatchPair> parseRule(String rule)
            throws ParseException {
        //空字符串返回空的Map
        Map<String, MatchPair> condition = new HashMap<String, MatchPair>();
        if (StringUtils.isBlank(rule)) {
            return condition;
        }
        // Key-Value pair, stores both match and mismatch conditions
        MatchPair pair = null;
        // Multiple values
        Set<String> values = null;
        //通过正则表达式匹配路由规则"([&!=,]*)\\s*([^&!=,\\s]+)"
        //第一个括号内的表达式用于匹配"&", "!", "=" 和 "," 等符号
        //第二括号内的用于匹配英文字母,数字等字符。举个例子说明一下:
        //host = 2.2.2.2 & host != 1.1.1.1 & method = hello
        // 匹配结果如下:
        //     括号一      括号二
        // 1.  null       host
        // 2.   =         2.2.2.2
        // 3.   &         host
        // 4.   !=        1.1.1.1
        // 5.   &         method
        // 6.   =         hello
        final Matcher matcher = ROUTE_PATTERN.matcher(rule);
        //正则匹配
        while (matcher.find()) {
            //获取括号1内容匹配的结果
            String separator = matcher.group(1);
            //获取括号2内容匹配结果
            String content = matcher.group(2);
            // Start part of the condition expression.
            //分隔符为空,表示匹配的是表达式的开始部分
            if (separator == null || separator.length() == 0) {
                //创建MatchPair对象
                pair = new MatchPair();
                // 存储 <匹配项, MatchPair> 键值对,比如 <host, MatchPair>
                condition.put(content, pair);
            }
            // The KV part of the condition expression
            // 如果分隔符为 &,表明接下来也是一个条件
            else if ("&".equals(separator)) {
                //如果&是null,创建PatchPair
                if (condition.get(content) == null) {
                    pair = new MatchPair();
                    condition.put(content, pair);
                } else {
                    pair = condition.get(content);
                }
            }
            // The Value in the KV part.
            // 如果分隔符为=
            else if ("=".equals(separator)) {
                if (pair == null)
                    throw new ParseException("Illegal route rule \""
                            + rule + "\", The error char '" + separator
                            + "' at index " + matcher.start() + " before \""
                            + content + "\".", matcher.start());

                values = pair.matches;
                // 将 content 存入到 MatchPair 的 matches 集合中
                values.add(content);
            }
            // The Value in the KV part.
            // 分隔符为!=
            else if ("!=".equals(separator)) {
                if (pair == null)
                    throw new ParseException("Illegal route rule \""
                            + rule + "\", The error char '" + separator
                            + "' at index " + matcher.start() + " before \""
                            + content + "\".", matcher.start());
                values = pair.mismatches;
                 将 content 存入到 MatchPair 的 mismatches 集合中
                values.add(content);
            }
            // The Value in the KV part, if Value have more than one items.
            // 分隔符为 ,
            else if (",".equals(separator)) { // Should be seperateed 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());
                // 将 content 存入到上一步获取到的 values 中,可能是 matches,也可能是 mismatches
                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;
    }

    @Override
    public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation)
            throws RpcException {
        if (invokers == null || invokers.isEmpty()) {
            return invokers;
        }
        try {
            // 先对服务消费者条件进行匹配,如果匹配失败,表明当前消费者 url 不符合匹配规则,
            // 无需进行后续匹配,直接返回 Invoker 列表即可。比如下面的规则:
            // host = 10.20.153.10 => host = 10.0.0.10
            // 这条路由规则希望 IP 为 10.20.153.10 的服务消费者调用 IP 为 10.0.0.10 机器上的服务。
            // 当消费者 ip 为 10.20.153.11 时,matchWhen 返回 false,表明当前这条路由规则不适用于
            // 当前的服务消费者,此时无需再进行后续匹配,直接返回即可。
            // 代表需要进行路由规则!!!
            if (!matchWhen(url, invocation)) {
                return invokers;
            }
            List<Invoker<T>> result = new ArrayList<Invoker<T>>();
            //当服务提供者匹配条件未配置,表明对指定服务消费者禁用服务,即加入黑名单了
            if (thenCondition == null) {
                logger.warn("The current consumer in the service blacklist. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey());
                return result;
            }
            //这里把Invoker理解为服务提供者,现在使用服务消费者规则对Invoker列表进行匹配
            for (Invoker<T> invoker : invokers) {
                // 匹配成功,表明当前 Invoker 符合服务提供者匹配规则。
                // 此时将 Invoker 添加到 result 列表中
                if (matchThen(invoker.getUrl(), url)) {
                    result.add(invoker);
                }
            }
            // 返回匹配结果,如果 result 为空列表,且 force = true,表示强制返回空列表,
            // 否则路由结果为空的路由规则将自动失效
            if (!result.isEmpty()) {
                return result;
            } else if (force) {
                logger.warn("The route result is empty and force execute. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey() + ", router: " + url.getParameterAndDecoded(Constants.RULE_KEY));
                return result;
            }
        } catch (Throwable t) {
            logger.error("Failed to execute condition router rule: " + getUrl() + ", invokers: " + invokers + ", cause: " + t.getMessage(), t);
        }
        return invokers;
    }

    @Override
    public URL getUrl() {
        return url;
    }

    @Override
    public int compareTo(Router o) {
        if (o == null || o.getClass() != ConditionRouter.class) {
            return 1;
        }
        ConditionRouter c = (ConditionRouter) o;
        return this.priority == c.priority ? url.toFullString().compareTo(c.url.toFullString()) : (this.priority > c.priority ? 1 : -1);
    }

    boolean matchWhen(URL url, Invocation invocation) {
        //=>host!=1.23.123需要走路由规则所以返回true
        //whenCondition url为consumer://xxx  parama为null
        return whenCondition == null || whenCondition.isEmpty() || matchCondition(whenCondition, url, null, invocation);
    }

    private boolean matchThen(URL url, URL param) {
        return !(thenCondition == null || thenCondition.isEmpty()) && matchCondition(thenCondition, url, param, null);
    }

    private boolean matchCondition(Map<String, MatchPair> condition, URL url, URL param, Invocation invocation) {
        //服务提供者或者消费url转换成map
        Map<String, String> sample = url.toMap();
        boolean result = false;
        //遍历condition
        for (Map.Entry<String, MatchPair> matchPair : condition.entrySet()) {
            //key比如host ,method
            String key = matchPair.getKey();
            String sampleValue;
            //如果invocation不为null并且 key为method(s)
            if (invocation != null && (Constants.METHOD_KEY.equals(key) || Constants.METHODS_KEY.equals(key))) {
                //获取调用方法名称
                sampleValue = invocation.getMethodName();
            } else {
                //从服务提供者或消费者 url 中获取指定字段值,比如 host、application 等
                sampleValue = sample.get(key);
                if (sampleValue == null) {
                    // 尝试通过 default.xxx 获取相应的值
                    sampleValue = sample.get(Constants.DEFAULT_KEY_PREFIX + key);
                }
            }
            //如果sampleValue不为null
            if (sampleValue != null) {
                //调用matchPair的isMatch匹配
                if (!matchPair.getValue().isMatch(sampleValue, param)) {
                    // 只要有一个规则匹配失败,立即返回 false 结束方法逻辑
                    return false;
                } else {
                    result = true;
                }
            } else {
                // sampleValue 为空,表明服务提供者或消费者 url 中不包含相关字段。此时如果
                //  MatchPair 的 matches 不为空,表示匹配失败,返回 false。比如我们有这样
                //  一条匹配条件 loadbalance = random,假设 url 中并不包含 loadbalance 参数,
                //  此时 sampleValue = null。既然路由规则里限制了 loadbalance = random,
                //  但 sampleValue = null,明显不符合规则,因此返回 false
                if (!matchPair.getValue().matches.isEmpty()) {
                    return false;
                } else {
                    result = true;
                }
            }
        }
        return result;
    }

    private static final class MatchPair {

        final Set<String> matches = new HashSet<String>();
        final Set<String> mismatches = new HashSet<String>();

        private boolean isMatch(String value, URL param) {
            //matches非空 mismatches为空
            if (!matches.isEmpty() && mismatches.isEmpty()) {
                // 遍历 matches 集合,检测入参 value 是否能被 matches 集合元素匹配到。
                // 举个例子,如果 value = 10.20.153.11,matches = [10.20.153.*],
                // 此时 isMatchGlobPattern 方法返回 true
                for (String match : matches) {
                    if (UrlUtils.isMatchGlobPattern(match, value, param)) {
                        return true;
                    }
                }
                // 如果所有匹配项都无法匹配到入参,则返回 false
                return false;
            }
            //如果matches为空,mismatches非空
            if (!mismatches.isEmpty() && matches.isEmpty()) {
                // 只要入参被 mismatches 集合中的任意一个元素匹配到,就返回 false
                for (String mismatch : mismatches) {
                    if (UrlUtils.isMatchGlobPattern(mismatch, value, param)) {
                        return false;
                    }
                }
                return true;
            }
            //matches和mismatches都不为空
            if (!matches.isEmpty() && !mismatches.isEmpty()) {
                // matches 和 mismatches 均为非空,此时优先使用 mismatches 集合元素对入参进行匹配。
                // 只要 mismatches 集合中任意一个元素与入参匹配成功,就立即返回 false,结束方法逻辑
                for (String mismatch : mismatches) {
                    if (UrlUtils.isMatchGlobPattern(mismatch, value, param)) {
                        return false;
                    }
                }
                // mismatches 集合元素无法匹配到入参,此时使用 matches 继续匹配
                for (String match : matches) {
                    if (UrlUtils.isMatchGlobPattern(match, value, param)) {
                        return true;
                    }
                }
                return false;
            }
            return false;
        }
    }
}

Routers扩展

从多个服务提供方中选择一个进行调用。

扩展接口

  • org.apache.dubbo.rpc.cluster.RouterFactory
  • org.apache.dubbo.rpc.cluster.Router

已知扩展

  • org.apache.dubbo.rpc.cluster.router.ScriptRouterFactory
  • org.apache.dubbo.rpc.cluster.router.FileRouterFactory

扩展示例

Maven 项目结构:

src
 |-main
    |-java
        |-com
            |-xxx
                |-XxxRouterFactory.java (实现RouterFactory接口)
    |-resources
        |-META-INF
            |-dubbo
                |-org.apache.dubbo.rpc.cluster.RouterFactory (纯文本文件,内容为:xxx=com.xxx.XxxRouterFactory)

XxxRouterFactory.java:

package com.xxx;
 
import org.apache.dubbo.rpc.cluster.RouterFactory;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.RpcException;
 
public class XxxRouterFactory implements RouterFactory {
    public <T> List<Invoker<T>> select(List<Invoker<T>> invokers, Invocation invocation) throws RpcException {
        // ...
    }
}

META-INF/dubbo/org.apache.dubbo.rpc.cluster.RouterFactory:

xxx=com.xxx.XxxRouterFactory

...

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值