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到任意多条规则组成,下面我们就每个规则的配置语法做详细说明:

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.xsdhttp://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=*:20880for (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

微信公众号:

Dubbo 路由规则之条件路由相关推荐

  1. mysql mycat 路由规则_Mycat分库路由规则

    Mycat分库路由规则 发布时间:2020-06-15 16:54:10 来源:51CTO 阅读:11651 作者:lzf05303774 一.Mycat分库路由分为连续路由和离散路由. 1.连续路由 ...

  2. Express新建工程以及新建路由规则、匹配路由规则、控制权转移

    场景 npm提供了大量的第三方模块,其中不乏许多Web框架,我们没有必要重复发明轮子, 因而选择使用Express作为开发框架,因为它是目前最稳定.使用最广泛,而且Node.js官 方推荐的唯一一个W ...

  3. zuul默认的路由规则及禁用路由规则

  4. dubbo系列九、dubbo路由规则和蓝绿切换

    dubbo蓝绿 目前做devops项目,去年提供了蓝绿发布功能,因此分享下,首先介绍下几种常见的部署方式,最后介绍下蓝绿发布的实现 1.常见部署方案介绍 1.1.蓝绿发布 蓝绿部署,是指同时运行两个版 ...

  5. Dubbo负载均衡和路由规则的区别

    点击关注强哥,查看更多精彩文章呀 哈喽,大家好,我是强哥. 大家知道,强哥之前有一篇推文Dubbo也支持基于应用粒度的服务发现机制啦中说到,Dubbo2.x版本目前大都还是使用接口粒度的服务发现机制. ...

  6. thinkphp学习笔记10—看不懂的路由规则

    原文:thinkphp学习笔记10-看不懂的路由规则 路由这部分貌似在实际工作中没有怎么设计过,只是在用默认的设置,在手册里面看到部分,艰涩难懂. 1.路由定义 要使用路由功能需要支持PATH_INF ...

  7. 第一节:WebApi的纯原生态的RestFul风格接口和路由规则介绍

    一. 原生态接口 1. 从默认路由开始分析 在WebApiConfig.cs类中的Register方法中,我们可以看到默认路由如下: 分析:请求地址在 controller 前面需要加上 api/,c ...

  8. 三分钟了解Spring Cloud Gateway路由转发之自动路由

    文章目录 一.前言 二.路由配置 1. 静态路由 2. 动态路由 3. 自动路由 三.Spring Cloud Gateway 是如何实现动态路由 工作原理 源码解析 路由转发原理 路由转发源码解析 ...

  9. dubbo服务的集群扩展、目录服务、路由规则、负载均衡

    1. Cluster-集群扩展 当有多个服务提供方时,将多个服务提供方组织成一个集群,并伪装成一个提供方.已知的实现如下, 1-1. FailoverCluster(默认的) 失败转移,当出现失败,重 ...

最新文章

  1. Error:(17, 0) SDK location not found. Define location with sdk.dir in the local.properties file or w
  2. 频频霸榜的Python,竟遭开发者嫌弃!
  3. 第三节:Windows平台部署Asp.Net Core应用(基于IIS和Windows服务两种模式)
  4. centos修改ip mac等
  5. EasyUI加zTree使用解析 easyui修改操作的表单回显方法 验证框提交表单前验证 datagrid的load方法
  6. python循环经典例题_python练习题:循环打印嵌套列表
  7. springboot logback自定义配置文件路径
  8. Android 网格视图GridView
  9. mysql pdo 读取字段名_PDO如何处理SQL语句中对字段名以及表名的转义
  10. ITIL4 讲解:监控管理
  11. java 微博 屏蔽_最新JAVA调用新浪微博API之发微博(转)
  12. Android飞行模式过程,在Android中切换飞行模式
  13. 每天都使用微信语音,但你必须晓得互联网语音协议(VoIP)
  14. 柳州铁一中机器人_柳州铁一中学学子在2020年广西中小学电脑机器人竞赛中勇创佳绩...
  15. bad assignment报错
  16. 企业微信登录不了怎么办 企业微信无法登录的原因及解决方法
  17. 7-38 数列求和-给定某数字A(1≤A≤9)以及非负整数N(0≤N≤100000),求数列之和S=A+AA+AAA+⋯+AA⋯A(N个A)。例如A=1, N=3时,S=1+11+111=123。
  18. framemaker 导出word文档,利用list遍历输出换行以及保留段落格式(首行空两格)
  19. 802.11协议:wifi
  20. 阿里 arthas 使用介绍

热门文章

  1. 强化学习1-思想及分类
  2. 前端 - 查找关键词 - 高亮 - 软考 - 程序员 - 简单编程算法计算
  3. AE特效 动态拼贴实现及分析
  4. nc测试UDP是否正常
  5. 工业“元宇宙”蓄势待发,未来制造业将如何变化?
  6. 活字印刷引入简单工厂模式
  7. 敏捷迭代管理 --工时估算(估点)
  8. 基于JavaWeb的网上书店的设计与实现
  9. 计算机教师继续教育心得,教师继续教育培训个人心得体会(精选6篇)
  10. 电子设计基础——有效值检波技术