乐趣区

关于java:Dubbo-路由规则之条件路由

前言

大家好,明天开始给大家分享 — Dubbo 专题之 Dubbo 路由规定之条件路由。在前一个章节中咱们介绍了 Dubbo 令牌验证和优雅停机,以及咱们也例举了常见的应用场景并且进行了源码解析来剖析其实现原理,同时晓得 Dubbo 中的令牌验证核心思想就是通过服务提供端提供的 token 或者随机产生的 token 放入注册核心进行治理,而后服务生产端获取 token 令牌并且在调用服务提供端时携带 token,服务提供端依据生产端携带的 token 进行验证。有的小伙伴可能会想:咱们多个服务提供者是否通过肯定的规定对调用的服务提供者进行过滤和限度呢?那接下来咱们就围绕着这个问题一起来学习下 Dubbo 中的路由规定。上面就让咱们疾速开始吧!

1. 条件路由简介

首先咱们得理解什么是路由规定?假如有这样一个场景如下图所示:

上图中咱们能够看到有两个机房别离是机房 A、机房 B,其中机房 A 只能拜访到 Service A 和 Service B,而机房 B 只能拜访到 Service C 和 Service D。要实现下面这种场景咱们就须要用到所谓的路由规定。路由规定是在发动一次 RPC 调用前过滤指标服务器地址,而过滤后的地址列表,将作为生产端最终发动 RPC 调用的备选地址。在 Dubbo 中反对两种路由规定明天咱们次要探讨条件路由。

  • 条件路由:反对以接口服务或消费者利用为粒度配置路由规定。

2. 应用形式

上面咱们简略的探讨下条件路由应用形式:

条件路由

  • 接口服务粒度

    # demo-consumer1 的消费者只能生产所有端口为 20880 的服务实例
    # demo-consumer2 的消费者只能生产所有端口为 20881 的服务实例
    ---
    scope: application #利用粒度
    force: true
    runtime: true
    enabled: true
    key: demo-provider 
    conditions:
      - application=demo-consumer1 => address=*:20880
      - application=demo-consumer2 => address=*:20881
  • 利用粒度

    # BookFacade 的 queryAll 办法只能生产所有端口为 20880 的服务实例
    # BookFacade 的 queryByName 办法只能生产所有端口为 20881 的服务实例
    ---
    scope: service #服务粒度
    force: true
    runtime: true
    enabled: true
    key: com.muke.dubbocourse.common.api.BookFacade
    conditions:
      - method=queryAll => address=*:20880
      - method=queryByName => address=*:20881
  • 字段阐明:

    编号 字段名称 阐明 必填
    1 scope 路由规定的作用粒度,scope 的取值会决定 key 的取值。
    service 服务粒度 application 利用粒度。
    必填
    2 Key 明确规定体作用在哪个接口服务或利用。scope=service 时,
    key 取值为 [{group}:]{service}[:{version}] 的组合 scope=application 时,
    key 取值为 application 名称。
    必填
    3 enabled enabled=true 以后路由规定是否失效,,缺省失效。 可不填
    4 force force=false 当路由后果为空时,是否强制执行,如果不强制执行,
    路由后果为空的路由规定将主动生效,缺省为 false
    可不填
    5 runtime runtime=false 是否在每次调用时执行路由规定,
    否则只在提供者地址列表变更时事后执行并缓存后果,
    调用时间接从缓存中获取路由后果。如果用了参数路由,必须设为 true
    须要留神设置会影响调用的性能,缺省为 false
    可不填
    6 priority priority=1 路由规定的优先级,用于排序,优先级越大越靠前执行,缺省为 0 可不填
    7 conditions 定义具体的路由规定内容。 必填
  • Conditions 规定体

    格局:

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

    表达式:

    参数反对:

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

    条件反对:

    • 等号 = 示意 ” 匹配 ”,如:host = 192.168.53.10
    • 不等号 != 示意 ” 不匹配 ”,如:host != 192.168.53.10

    值反对:

    • 以逗号 , 分隔多个值,如:host != 192.168.53.10,192.168.53.11
    • 以星号 * 结尾,示意通配,如:host != 10.20.*
    • 以美元符 $ 结尾,示意援用消费者参数,如:host = $host

Tips:conditions局部是规定的主体,由 1 到任意多条规定组成,上面咱们就每个规定的配置语法做具体阐明:

<script async src=”https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js”></script>
<ins class=”adsbygoogle”

 style="display:block; text-align:center;"
 data-ad-layout="in-article"
 data-ad-format="fluid"
 data-ad-client="ca-pub-4279907681900931"
 data-ad-slot="6812672741"></ins>

<script>

 (adsbygoogle = window.adsbygoogle || []).push({});

</script>

3. 应用场景

从下面的简略介绍咱们能够大抵理解到,当咱们生产对拜访服务提供者时咱们能够通过肯定的规定对服务提供者列表进行过滤。那上面咱们列举下工作中常应用的场景:

  1. 黑名单:比方咱们须要禁止某些服务消费者生产服务
host = 192.168.53.10,192.168.53.11 =>

下面配置示意禁止 192.168.53.10192.168.53.11 消费者拜访服务提供者。

  1. 服务寄宿在利用上,只裸露一部分的机器,避免整个集群挂掉
=> host = 192.168.53.1*,192.168.53.2*

下面配置示意只能放192.168.53.1*192.168.53.2 ip 地址结尾的服务提供者。

  1. 读写拆散:读取数据和写入数据操作离开
method = find*,list*,get*,is* => host = 192.168.53.10,192.168.53.11,192.168.53.12
method != find*,list*,get*,is* => host = 192.168.20.97,192.168.53.21

下面配置示意以 find*,list*,get*,is* 办法命名开始的办法只能拜访 192.168.53.10,192.168.53.11,192.168.53.12 服务提供者,而不是 find*,list*,get*,is* 办法命名开始的办法只能拜访 192.168.20.97,192.168.53.21 服务提供者。

  1. 提供者与消费者部署在同集群内,本机只拜访本机的服务
=> host = $host

下面配置示意所有消费者只能拜访集群内的服务。

4. 示例演示

咱们以获取图书列表为例进行实例演示,其中咱们会启动两个服务提供者配置两个端口:2088020881,而后指定路由规定为:调用办法 queryAll 拜访 20880、调用办法queryByName 拜访 20881 服务。我的项目结构图如下:

这里咱们次要看服务提供端 dubbo-provider-xml.xml 配置内容:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <dubbo:protocol port="20880"/>
<!-- 别离应用 20881 和 20880 配置启动两个服务 -->
<!--    <dubbo:protocol port="20881"/>-->

    <dubbo:application name="demo-provider" metadata-type="remote"/>

    <dubbo:registry address="zookeeper://127.0.0.1:2181"/>

    <bean id="bookFacade" class="com.muke.dubbocourse.tokenverify.provider.BookFacadeImpl"/>

    <!-- 裸露服务为 Dubbo 服务 -->
    <dubbo:service interface="com.muke.dubbocourse.common.api.BookFacade" ref="bookFacade" token="12345"/>

</beans>

下面咱们指定了服务提供者的端口,这里申请小伙伴别离以 2088020881启动两个服务。接下来咱们看看在 Dubbo Admin 中配置的路由规定:

enabled: true
runtime: false
force: true
conditions:
 - 'method = queryAll => address=*:20880'
 - 'method = queryByName => address=*:20881'

如下图所示:

Tips:这里的Service Unique ID 配置规定为:接口权限定名: 版本: 分组。

5. 实现原理

依据后面的介绍咱们晓得在生产端调用近程服务时通过路由规定进行服务的过滤,那么咱们通过源码简略的剖析下这个处理过程。这里咱们间接看到路由规定的调用外围代码 org.apache.dubbo.rpc.cluster.RouterChain#route 外围办法如下:

    public List<Invoker<T>> route(URL url, Invocation invocation) {
        List<Invoker<T>> finalInvokers = invokers;
        for (Router router : routers) {finalInvokers = router.route(finalInvokers, url, invocation);
        }
        return finalInvokers;
    }

上面展现了咱们运行过程中的路由规定:

其中 ConditionRouter 就是咱们的条件路由外围代码如下:

    public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation)
            throws RpcException {if (!enabled) {return invokers;}

        if (CollectionUtils.isEmpty(invokers)) {return invokers;}
        try {
            // 匹配 method = queryAll => address=*:20880 示意中的 method = queryAll 局部
            if (!matchWhen(url, invocation)) {return invokers;}
            List<Invoker<T>> result = new ArrayList<Invoker<T>>();
            if (thenCondition == null) {
                //..
                return result;
            }
            for (Invoker<T> invoker : invokers) {
                // 匹配 method = queryAll => address=*:20880 示意中的 address=*:20880 局部
                if (matchThen(invoker.getUrl(), url)) {result.add(invoker);
                }
            }
            if (!result.isEmpty()) {
                return result;

                // 配置 force: true 示意如果通过路由规定后没有服务条件的返回一个空集合,否则路由规定有效返回过滤器的 Invoker 近程服务代理列表
            } else if (force) {
                //..
                return result;
            }
        } catch (Throwable t) {//...}
        return invokers;
    }

这里有两个最为重要的办法别离是:org.apache.dubbo.rpc.cluster.router.condition.ConditionRouter#matchWhenorg.apache.dubbo.rpc.cluster.router.condition.ConditionRouter#matchThenmatchWhen 办法次要负责匹配前置条件例如:method = queryAll => address=*:20880 示意中的 method = queryAll 局部,matchThen办法次要负责匹配后置条件 method = queryAll => address=*:20880 示意中的 address=*:20880 局部。matchWhen 外围代码如下:

   boolean matchWhen(URL url, Invocation invocation) {
        // 判断 whenCondition 条件是否为空,并且执行 matchCondition 匹配表达式
        return CollectionUtils.isEmptyMap(whenCondition) || matchCondition(whenCondition, url, null, invocation);
    }

办法 org.apache.dubbo.rpc.cluster.router.condition.ConditionRouter#matchCondition 外围代码如下:

/**
     *
     * 匹配条件
     *
     * @author liyong
     * @date 11:36 PM 2020/11/28
     * @param condition
     * @param url
     * @param param
     * @param invocation
     * @exception
     * @return boolean
     **/
    private boolean matchCondition(Map<String, MatchPair> condition, URL url, URL param, Invocation invocation) {Map<String, String> sample = url.toMap();
        boolean result = false;
        //method = queryAll => address=*:20880
        for (Map.Entry<String, MatchPair> matchPair : condition.entrySet()) {String key = matchPair.getKey();
            String sampleValue;
            // 从 invocation 中获取调用的实在办法名称
            if (invocation != null && (METHOD_KEY.equals(key) || METHODS_KEY.equals(key))) {sampleValue = invocation.getMethodName();
                // 判断是否配置 address
            } else if (ADDRESS_KEY.equals(key)) {sampleValue = url.getAddress();
                // 判断是否配置 host
            } else if (HOST_KEY.equals(key)) {sampleValue = url.getHost();
            } else {
                // 从 URL 转换的 map 中获取对应 key 的值
                sampleValue = sample.get(key);
                if (sampleValue == null) {sampleValue = sample.get(key);
                }
            }
            if (sampleValue != null) {
                // 匹配条件配置和实在调用参数值是否匹配
                if (!matchPair.getValue().isMatch(sampleValue, param)) {return false;} else {result = true;}
            } else {
                // 没有匹配的条件
                if (!matchPair.getValue().matches.isEmpty()) {return false;} else {result = true;}
            }
        }
        return result;
    }

下面的代码非常简单小伙伴能够依据正文去学习,其中 Map<String, MatchPair> condition 构造如下:

从数据结构中咱们能够看出咱们配置的 method = queryByName,咱们这里没有配置host 默认为 *。接下来咱们持续看看matchThen 办法外围代码如下:

    private boolean matchThen(URL url, URL param) {
         // 判断 thenCondition 条件是否为空,并且执行 matchCondition 匹配表达式
        return CollectionUtils.isNotEmptyMap(thenCondition) && matchCondition(thenCondition, url, param, null);
    }

这里和下面的 matchWhen 办法都调用 matchCondition,那咱们看看thenCondition 的数据结构:

是咱们配置的规定后半局部 address=*:20881。由此咱们能够总结:假如咱们的条件路由规定是method = queryByName => address=*:20881 那么先对服务调用方匹配 method = queryByName 前局部,如果满足后面局部则持续去匹配规定的前面局部address=*:20881,如果都匹配则 Invoker 代理对象将作为调用代理候选者。

6. 小结

在本大节中咱们次要学习了 Dubbo 中路由规定之条件路由以及应用形式。同时也剖析了条件路由实现的原理,其本质上是通过过滤器对服务提供者列表进行规定的匹配,如果匹配不上则过滤掉服务提供者。

本节课程的重点如下:

  1. 了解 Dubbo 路由规定和条件路由
  2. 理解了条件路由应用形式
  3. 理解条件路由实现原理
  4. 理解条件路由应用场景

作者

集体从事金融行业,就任过易极付、思建科技、某网约车平台等重庆一流技术团队,目前就任于某银行负责对立领取零碎建设。本身对金融行业有强烈的喜好。同时也实际大数据、数据存储、自动化集成和部署、散布式微服务、响应式编程、人工智能等畛域。同时也热衷于技术分享创建公众号和博客站点对常识体系进行分享。关注公众号:青年 IT 男 获取最新技术文章推送!

博客地址: http://youngitman.tech

微信公众号:

退出移动版