Apache Dubbo编程指南系列之条件路由、路由标签

routing-rule

通过 Dubbo 中的路由规则做服务治理,路由规则在发起一次RPC调用前起到过滤目标服务器地址的作用,过滤后的地址列表,将作为消费端最终发起RPC调用的备选地址。

  • 条件路由。支持以服务或 Consumer 应用为粒度配置路由规则。
  • 标签路由。以 Provider 应用为粒度配置路由规则。

后续我们计划在 2.6.x 版本的基础上继续增强脚本路由功能。

条件路由
  • 应用粒度

    # app1的消费者只能消费所有端口为20880的服务实例
    # app2的消费者只能消费所有端口为20881的服务实例
    ---
    scope: application
    force: true
    runtime: true
    enabled: true
    key: governance-conditionrouter-consumer
    conditions:
      - application=app1 => address=*:20880
      - application=app2 => address=*:20881
    ...
    
  • 服务粒度

    # DemoService的sayHello方法只能消费所有端口为20880的服务实例
    # DemoService的sayHi方法只能消费所有端口为20881的服务实例
    ---
    scope: service
    force: true
    runtime: true
    enabled: true
    key: org.apache.dubbo.samples.governance.api.DemoService
    conditions:
      - method=sayHello => address=*:20880
      - method=sayHi => address=*:20881
    ...
    

各字段含义

  • scope【必填】表示路由规则的作用粒度,scope的取值会决定key的取值。
    • service 服务粒度
    • application 应用粒度
  • Key【必填】明确规则体作用在哪个服务或应用。
    • scope=service时,key取值为{service}:{version}:{group}的组合
    • scope=application时,key取值为服务消费者的application名称
  • enabled=true 当前路由规则是否生效,可不填,缺省生效。
  • force=false 当路由结果为空时,是否强制执行,如果不强制执行,路由结果为空的路由规则将自动失效,可不填,缺省为 false
  • runtime=false 是否在每次调用时执行路由规则,否则只在提供者地址列表变更时预先执行并缓存结果,调用时直接从缓存中获取路由结果。如果用了参数路由,必须设为 true,需要注意设置会影响调用的性能,可不填,缺省为 false
  • priority=1 路由规则的优先级,用于排序,优先级越大越靠前执行,可不填,缺省为 0
  • conditions 定义具体的路由规则内容。必填

这里需要注意目前dubbo-admin服务存在bug,不建议大家使用dubbo-admin提交路由规则。

  • 应用粒度
<dependency>
    <groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
    <version>1.24</version>
</dependency>

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>2.12.0</version>
</dependency>
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>2.12.0</version>
</dependency>
public class RoutingRule {
    private String key;
    private String scope;
    private int priority;
    private boolean enabled;
    private boolean force;
    private boolean runtime;
    private List<String> conditions;
    ...
}
public class YamlParser {

    private static Yaml yaml;

    static {
        Representer representer = new Representer() {

            protected NodeTuple representJavaBeanProperty(Object javaBean, Property property, Object propertyValue, Tag customTag) {
                if (propertyValue == null) {
                    return null;
                }
                else {
                    return super.representJavaBeanProperty(javaBean, property, propertyValue, customTag);
                }
            }
        };
        yaml = new Yaml(representer);
    }

    public static String dumpObject(Object object) {
        return yaml.dumpAsMap(object);
    }

    public static <T> T loadObject(String content, Class<T> type) {
        return yaml.loadAs(content, type);
    }
}
# dubbo-user-consumer下的消费者不可以访问20880和20881服务
---
conditions:
- => address!=*:20880
- => address!=*:20881
enabled: true
force: true
key: dubbo-user-consumer
priority: 0
runtime: true
scope: application

这些配置必须写在/dubbo/config/dubbo/{消费者应用名}.condition-router目录下

public static void setOrUpdateCondationRule(String consumerApplication, RoutingRule routingRule) throws Exception {
    String path="/dubbo/config/dubbo/"+consumerApplication+".condition-router";
    routingRule.setKey(consumerApplication);
    CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient("CentOS7:2181",
                                                                          new RetryNTimes(5,1000));
    curatorFramework.start();

    Stat stat = curatorFramework.checkExists().forPath(path);
    //创建path路径
    if(stat==null){
        curatorFramework.create().creatingParentContainersIfNeeded().forPath(path);
    }
    //写出路由配置
    curatorFramework.setData().forPath(path, YamlParser.dumpObject(routingRule).getBytes());

    curatorFramework.close();
}
RoutingRule routingRule = new RoutingRule();

routingRule.setScope("application");
routingRule.setEnabled(true);
routingRule.setForce(true);
routingRule.setRuntime(true);
routingRule.setPriority(0);
routingRule.setConditions(Arrays.asList("=> address!=*:20880","=> address!=*:20881"));

setOrUpdateCondationRule("dubbo-user-consumer",routingRule);
  • 服务粒度
conditions:
- => address!=*:20880
- => address!=*:20881
enabled: true
force: true
key: com.baizhi.service.IUserService::g1
priority: 0
runtime: true
scope: service
public static void setOrUpdateServiceCondationRule(String group,String service,String version, RoutingRule routingRule) throws Exception {

    String path="/dubbo/config/dubbo/"+service+":"+version+":"+group+".condition-router";
    routingRule.setScope("service");
    routingRule.setKey(service+":"+version+":"+group);
    CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient("CentOS7:2181",
                                                                          new RetryNTimes(5,1000));
    curatorFramework.start();

    Stat stat = curatorFramework.checkExists().forPath(path);
    //创建path路径
    if(stat==null){
        curatorFramework.create().creatingParentContainersIfNeeded().forPath(path);
    }
    //写出路由配置
    curatorFramework.setData().forPath(path, YamlParser.dumpObject(routingRule).getBytes());

    curatorFramework.close();
}

RoutingRule routingRule = new RoutingRule();

routingRule.setEnabled(true);
routingRule.setForce(false);
routingRule.setRuntime(true);
routingRule.setPriority(0);
routingRule.setConditions(Arrays.asList("=> address!=*:20880","=> address!=*:20881"));

setOrUpdateServiceCondationRule("g1","com.baizhi.service.IUserService","",routingRule);
setOrUpdateServiceCondationRule("g2","com.baizhi.service.IUserService","",routingRule);

Conditions规则

conditions部分是规则的主体,由1到任意多条规则组成,下面我们就每个规则的配置语法做详细说明:

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

参数支持:

  • 服务调用信息,如: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
  1. Condition示例
  • 排除预发布机:
=> host != 172.22.3.91
  • 白名单:
host != 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.*
  • 提供者与消费者部署在同集群内,本机只访问本机的服务:
=> host = $host
标签路由

标签路由通过将某一个或多个服务的提供者划分到同一个分组,约束流量只在指定分组中流转,从而实现流量隔离的目的,可以作为蓝绿发布、灰度发布等场景的能力基础。标签主要是指对Provider端应用实例的分组,目前有两种方式可以完成实例分组,分别是动态规则打标静态规则打标,其中动态规则相较于静态规则优先级更高,而当两种规则同时存在且出现冲突时,将以动态规则为准。

  • 动态规则打标,可随时在服务治理控制台下发标签归组规则
enabled: true
force: false
runtime: true
key: dubbo-user-service
tags:
  - name: tag1
    addresses:
      - '192.168.31.87:20880'
  - name: tag2
    addresses:
      - '192.168.31.87:20881'

注意默认dubbo会在路径/dubbo/config/dubbo/{服务提供端}.tag-router路径下写标签路由

规则详解

1、格式

  • Key明确规则体作用到哪个应用。必填
  • enabled=true 当前路由规则是否生效,可不填,缺省生效。
  • force=false 当路由结果为空时,是否强制执行,如果不强制执行,路由结果为空的路由规则将自动失效,可不填,缺省为 false
  • runtime=false 是否在每次调用时执行路由规则,否则只在提供者地址列表变更时预先执行并缓存结果,调用时直接从缓存中获取路由结果。如果用了参数路由,必须设为 true,需要注意设置会影响调用的性能,可不填,缺省为 false
  • priority=1 路由规则的优先级,用于排序,优先级越大越靠前执行,可不填,缺省为 0
  • tags 定义具体的标签分组内容,可定义任意n(n>=1)个标签并为每个标签指定实例列表。必填
    • name, 标签名称
  • addresses, 当前标签包含的实例列表

2、降级约定

  1. dubbo.tag=tag1 时优先选择 标记了tag=tag1 的 provider。若集群中不存在与请求标记对应的服务,默认将降级请求 tag为空的provider;如果要改变这种默认行为,即找不到匹配tag1的provider返回异常,需设置request.tag.force=true
  2. dubbo.tag未设置时,只会匹配tag为空的provider。即使集群中存在可用的服务,若 tag 不匹配也就无法调用,这与约定1不同,携带标签的请求可以降级访问到无标签的服务,但不携带标签/携带其他种类标签的请求永远无法访问到其他标签的服务。
  • 静态打标
<dubbo:provider tag="tag1"/>

<dubbo:service tag="tag1"/>

java -jar xxx-provider.jar -Ddubbo.provider.tag={the tag you want, may come from OS ENV}

Consumer

请求标签的作用域为每一次 invocation,使用 attachment 来传递请求标签,注意保存在 attachment 中的值将会在一次完整的远程调用中持续传递,得益于这样的特性,我们只需要在起始调用时,通过一行代码的设置,达到标签的持续传递。

目前仅仅支持 hardcoding 的方式设置 requestTag。注意到 RpcContext 是线程绑定的,优雅的使用 TagRouter 特性,建议通过 servlet 过滤器(在 web 环境下),或者定制的 SPI 过滤器设置 requestTag。

测试案例

<dependency>
    <groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
    <version>1.24</version>
</dependency>

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>2.12.0</version>
</dependency>
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>2.12.0</version>
</dependency>
public class TagRoute {
    private int priority;
    private boolean enabled;
    private boolean force;
    private boolean runtime;
    private String key;
    private List<Tag> tags;
    ...
}
public class Tag {
    String name;
    String[] addresses;
    ...
}
public class YamlParser {

    private static Yaml yaml;

    static {
        Representer representer = new Representer() {

            protected NodeTuple representJavaBeanProperty(Object javaBean, Property property, Object propertyValue, Tag customTag) {
                if (propertyValue == null) {
                    return null;
                }
                else {
                    return super.representJavaBeanProperty(javaBean, property, propertyValue, customTag);
                }
            }
        };
        yaml = new Yaml(representer);
    }

    public static String dumpObject(Object object) {
        return yaml.dumpAsMap(object);
    }

    public static <T> T loadObject(String content, Class<T> type) {
        return yaml.loadAs(content, type);
    }
}
public static void setOrUpdateApplicatioTagRule(String providerApplication, List<Tag> tags) throws Exception {
    String path="/dubbo/config/dubbo/"+providerApplication+".tag-router";

    TagRoute route = new TagRoute();
    route.setKey(providerApplication);
    route.setEnabled(true);
    route.setForce(false);
    route.setPriority(0);
    route.setRuntime(true);
    route.setTags(tags);


    CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient("CentOS7:2181",
                                                                          new RetryNTimes(5,1000));
    curatorFramework.start();

    Stat stat = curatorFramework.checkExists().forPath(path);
    //创建path路径
    if(stat==null){
        curatorFramework.create().creatingParentContainersIfNeeded().forPath(path);
    }
    //写出路由配置
    curatorFramework.setData().forPath(path, YamlParser.dumpObject(route).getBytes());

    curatorFramework.close();
}
String providerApplication="dubbo-user-service";

List<Tag> tags=new ArrayList<>();
tags.add(new Tag("tag1",new String[]{"192.168.31.87:20880"}));
tags.add(new Tag("tag2",new String[]{"192.168.31.87:20881"}));

setOrUpdateApplicatioTagRule(providerApplication,tags);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值