Dubbo 源码分析 – 集群容错之 Router
1. 简介
上一篇文章分析了集群容错的第一部分 – 服务目录 Directory。服务目录在刷新 Invoker 列表的过程中,会通过 Router 进行服务路由。上一篇文章关于服务路由相关逻辑没有细致分析,一笔带过了,本篇文章将对此进行详细的分析。首先,先来介绍一下服务目录是什么。服务路由包含一条路由规则,路由规则决定了服务消费者的调用目标,即规定了服务消费者可调用哪些服务提供者。Dubbo 目前提供了三种服务路由实现,分别为条件路由 ConditionRouter、脚本路由 ScriptRouter 和标签路由 TagRouter。其中条件路由是我们最常使用的,标签路由暂未在我所分析的 2.6.4 版本中提供,该实现会在 2.7.0 版本中提供。本篇文章将分析条件路由相关源码,脚本路由和标签路由这里就不分析了。下面进入正题。
2. 源码分析
条件路由规则有两个条件组成,分别用于对服务消费者和提供者进行匹配。比如有这样一条规则:
host = 10.20.153.10 => host = 10.20.153.11
该条规则表示 IP 为 10.20.153.10 的服务消费者只可调用 IP 为 10.20.153.11 机器上的服务,不可调用其他机器上的服务。条件路由规则的格式如下:
[服务消费者匹配条件] => [服务提供者匹配条件]
如果服务消费者匹配条件为空,表示不对服务消费者进行限制。如果服务提供者匹配条件为空,表示对某些服务消费者禁用服务。Dubbo 官方文档对条件路由进行了比较详细的介绍,大家可以参考下,这里就不过多说明了。
条件路由实现类 ConditionRouter 需要对用户配置的路由规则进行解析,得到一系列的条件。然后再根据这些条件对服务进行路由。本章将分两节进行说明,2.1节介绍表达式解析过程。2.2 节介绍服务路由的过程。接下来,我们先从表达式解析过程看起。
2.1 表达式解析
条件路由规则是一条字符串,对于 Dubbo 来说,它并不能直接理解字符串的意思,需要将其解析成内部格式才行。条件表达式的解析过程始于 ConditionRouter 的构造方法,下面一起看一下:
public ConditionRouter(URL url) {this.url = url;// 获取 priority 和 force 配置this.priority = url.getParameter(Constants.PRIORITY_KEY, 0);this.force = url.getParameter(Constants.FORCE_KEY, false);try {// 获取路由规则String rule = url.getParameterAndDecoded(Constants.RULE_KEY);if (rule == null || rule.trim().length() == 0) {throw new IllegalArgumentException("Illegal route rule!");}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);}
}
如上,ConditionRouter 构造方法先是对路由规则做预处理,然后调用 parseRule 方法分别对服务提供者和消费者规则进行解析,最后将解析结果赋值给 whenCondition 和 thenCondition 成员变量。ConditionRouter 构造方法不是很复杂,这里就不多说了。下面我们把重点放在 parseRule 方法上,在详细介绍这个方法之前,我们先来看一个内部类。
private static final class MatchPair {final Set<String> matches = new HashSet<String>();final Set<String> mismatches = new HashSet<String>();
}
MatchPair 内部包含了两个 Set 型的成员变量,分别用于存放匹配和不匹配的条件。这个类两个成员变量会在 parseRule 方法中被用到,下面来看一下。
private static Map<String, MatchPair> parseRule(String rule)throws ParseException {// 定义条件映射集合Map<String, MatchPair> condition = new HashMap<String, MatchPair>();if (StringUtils.isBlank(rule)) {return condition;}MatchPair pair = null;Set<String> values = null;// 通过正则表达式匹配路由规则,ROUTE_PATTERN = ([&!=,]*)\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. = hellofinal Matcher matcher = ROUTE_PATTERN.matcher(rule);while (matcher.find()) {// 获取括号一内的匹配结果String separator = matcher.group(1);// 获取括号二内的匹配结果String content = matcher.group(2);// 分隔符为空,表示匹配的是表达式的开始部分if (separator == null || separator.length() == 0) {// 创建 MatchPair 对象pair = new MatchPair();// 存储 <匹配项, MatchPair> 键值对,比如 <host, MatchPair>condition.put(content, pair); } // 如果分隔符为 &,表明接下来也是一个条件else if ("&".equals(separator)) {// 尝试从 condition 获取 MatchPairif (condition.get(content) == null) {// 未获取到 MatchPair,重新创建一个,并放入 condition 中pair = new MatchPair();condition.put(content, pair);} else {pair = condition.get(content);}} // 分隔符为 =else if ("=".equals(separator)) {if (pair == null)throw new ParseException("Illegal route rule ...");values = pair.matches;// 将 content 存入到 MatchPair 的 matches 集合中values.add(content);} // 分隔符为 != else if ("!=".equals(separator)) {if (pair == null)throw new ParseException("Illegal route rule ...");values = pair.mismatches;// 将 content 存入到 MatchPair 的 mismatches 集合中values.add(content);}// 分隔符为 ,else if (",".equals(separator)) {if (values == null || values.isEmpty())throw new ParseException("Illegal route rule ...");// 将 content 存入到上一步获取到的 values 中,可能是 matches,也可能是 mismatchesvalues.add(content);} else {throw new ParseException("Illegal route rule ...");}}return condition;
}
以上就是路由规则的解析逻辑,该逻辑由正则表达式 + 一个 while 循环 + 数个条件分支组成。下面使用一个示例对解析逻辑进行演绎。示例为 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
现在线程进入 while 循环:
第一次循环:分隔符 separator = null,content = “host”。此时创建 MatchPair 对象,并存入到 condition 中,condition = {“host”: MatchPair@123}
第二次循环:分隔符 separator = “=”,content = “2.2.2.2”,pair = MatchPair@123。此时将 2.2.2.2 放入到 MatchPair@123 对象的 matches 集合中。
第三次循环:分隔符 separator = “&”,content = “host”。host 已存在于 condition 中,因此 pair = MatchPair@123。
第四次循环:分隔符 separator = “!=”,content = “1.1.1.1”,pair = MatchPair@123。此时将 1.1.1.1 放入到 MatchPair@123 对象的 mismatches 集合中。
第五次循环:分隔符 separator = “&”,content = “method”。condition.get(“method”) = null,因此新建一个 MatchPair 对象,并放入到 condition 中。此时 condition = {“host”: MatchPair@123, “method”: MatchPair@ 456}
第六次循环:分隔符 separator = “=”,content = “2.2.2.2”,pair = MatchPair@456。此时将 hello 放入到 MatchPair@456 对象的 matches 集合中。
循环结束,此时 condition 的内容如下:
相关文章:
Dubbo 源码分析 – 集群容错之 Router
1. 简介 上一篇文章分析了集群容错的第一部分 – 服务目录 Directory。服务目录在刷新 Invoker 列表的过程中,会通过 Router 进行服务路由。上一篇文章关于服务路由相关逻辑没有细致分析,一笔带过了,本篇文章将对此进行详细的分析。首先&…...

行人检测(人体检测)3:Android实现人体检测(含源码,可实时人体检测)
行人检测(人体检测)3:Android实现人体检测(含源码,可实时人体检测) 目录 行人检测(人体检测)3:Android实现人体检测(含源码,可实时人体检测) 1. 前言 2. 人体检测数据集说明 3. 基于YOLOv5的人体检测模型训练 4.人体检测模型…...

【图像分类】基于PyTorch搭建LSTM实现MNIST手写数字体识别(单向LSTM,附完整代码和数据集)
写在前面: 首先感谢兄弟们的关注和订阅,让我有创作的动力,在创作过程我会尽最大能力,保证作品的质量,如果有问题,可以私信我,让我们携手共进,共创辉煌。 提起LSTM大家第一反应是在NLP的数据集上比较常见,不过在图片分类中,它同样也可以使用。我们以比较熟悉的 mnist…...

Kotlin 1.8.0 现已发布,有那些新特性?
文章目录**如何安装 Kotlin 1.8.0****如果您遇到任何问题****更多文章和视频**结语Kotlin 1.8.0 版本现已发布,以下是其部分最大亮点: JVM 的新实验性功能:递归复制或删除目录内容提升了 kotlin-reflect 性能新的-Xdebug编译器选项ÿ…...

likeshop单商户SaaS商城系统—无限多开,搭建多个商城
likeshop单商户SaaS商城系统:适用于多开(SaaS)、B2C、单商户、自营商城场景,完美契合私域流量变现闭环交易使用,系统拥有丰富的营销玩法,强大的分销能力,支持DIY多模板,前后端分离。…...

Bean(Spring)的执行流程和生命周期
Bean(Spring)的执行流程具体的流程就和我们创建Spring基本相似。启动 Spring 容器 -> 实例化 Bean(分配内存空间,从无到有) -> Bean 注册到 Spring 中(存操作) -> 将 Bean 装配到需要的…...

工作记录------PostMan自测文件导入、导出功能
工作记录------PostMan自测文件导入、导出功能 测试文件导出 背景:写了一个文件下载功能,是数据写到excel中,下载,使用PostMan点击send后,返回报文是乱码。 解决办法: 点击send下面的 send and Downlo…...

上海亚商投顾:沪指震荡上行 大消费板块全线走强
上海亚商投顾前言:无惧大盘涨跌,解密龙虎榜资金,跟踪一线游资和机构资金动向,识别短期热点和强势个股。市场情绪三大指数今日震荡反弹,沪指全天低开高走,深成指、创业板指均涨超1%。工程机械板块集体大涨&a…...
linux中的图形化UDP调试工具
sokit freeware version: 1.3.1 (GPLv3) website: https://github.com/sinpolib/sokit/ 这是一个TCP / UDP数据包收发和传输工具 linux汉化 默认是英文版本的,如果想使用中文,把软件目录下的sokit.lan_rename重命令为sokit.lan再次打开软件就发现已经…...

前端react面试题指南
概述下 React 中的事件处理逻辑 抹平浏览器差异,实现更好的跨平台。避免垃圾回收,React 引入事件池,在事件池中获取或释放事件对象,避免频繁地去创建和销毁。方便事件统一管理和事务机制。 为了解决跨浏览器兼容性问题࿰…...

深入浅出原核基因表达调控(乳糖操纵子、色氨酸操纵子)
原核基因表达调控 前言 自然界里,能量时有时无,各种生命为了让自己能够活下去,需要适应环境,在不同的环境合成不同的蛋白质。 原核生物体内有很多细胞,细胞里面有很多蛋白质,但是这些蛋白质在这些细胞里…...
10分钟理解Mysql索引
一、索引介绍 索引是什么 官方介绍索引是帮助MySQL高效获取数据的数据结构。更通俗的说,数据库索引好比是一本书前面的目录,能加快数据库的查询速度。 一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往是存储在磁盘…...

nVisual综合布线可视化管理系统解决方案
一、综合布线管理系统的必要性 如今企事业单位办公人员变化很快,如果还是采用传统方式通过工程竣工图或者网络拓扑图来进行网络维护工作会非常麻烦,并且对管理人员的要求也会很高,管理人员需要清楚的知道工作区的信息点与配线架点之间的对…...

34岁测试工程师被辞退,难道测试岗位真的只是青春饭吗?
一:前言:人生的十字路口静坐反思 入软件测试这一行至今已经10年多,承蒙领导们的照顾与重用,同事的支持与信任,我的职业发展算是相对较好,从入行到各类测试技术岗位,再到测试总监,再…...
Java中常见的空指针异常
参考链接: java中什么是空指针异常以及为什么会产生空指针异常天上的云川的博客-CSDN博客什么是java空指针 java中容易产生空指针异常:NullPointerException的场景火龙映天的博客-CSDN博客java怎么制造空指针异常 java空指针异常是什么、怎么发生、如何…...
d亚当替换工厂模式
对象工厂替代方案 一般,需要无需用模块构造器触发d运行时的挑剔循环检测的方法来注册工厂.很多时候,混合模块构造器正是想要方法,但它有全局全开或全闭的循环检测算法. 要全局关闭它,请在Main文件中,添加以下代码行: extern(C) __gshared string[] rt_options ["oncycl…...
Real-time Scene Text Detection with Differentiable Binarization
Abstract 近年来,基于分割的方法在文本检测场景中非常流行,因为分割结果可以更准确地描述曲线文本等各种形状的场景文本。然而,二值化的后处理对于分割检测是必不可少的,它将分割方法产生的概率图转换为文本框/区域。本文提出了一…...

国外客户只想跟工厂合作?可以这样破解
1.客户是愿意和外贸公司合作还是更愿意和工厂合作?一个外贸公司的朋友说:“我去工厂接待过七八次外国人,基本上都是英国、德国、日本、加拿大、美国的。”贸易公司根本不避讳自己是贸易公司,外国人也不在乎。他们更关心的是贸易公司能否妥善安…...

c++重中之重:“换个龟壳继续套娃“:运算符重载等的学习
文章目录 前言一.运算符重载二.const成员三.取地址重载总结前言 上一期我们讲到类的6个默认构造函数中的拷贝构造函数,这一期我们继续往下讲,当然难点肯定是运算符重载了。 一、运算符重载 运算符重载是c为了增强代码的可读性引入了运算符重载…...
RabbitMQ简单使用
这篇文章通过一个最简单的例子,让初学者能了解RabbitMQ如何完成生产消息和消息的。 所有的程序员在学习一门新技术的时候,都是从 Hello World 进入到Colorful World的,本节也将按照惯例,从HelloWorld开始,演示RabbitMQ…...

Spark 之 入门讲解详细版(1)
1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室(Algorithms, Machines, and People Lab)开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目,8个月后成为Apache顶级项目,速度之快足见过人之处&…...

CentOS下的分布式内存计算Spark环境部署
一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架,相比 MapReduce 具有以下核心优势: 内存计算:数据可常驻内存,迭代计算性能提升 10-100 倍(文档段落:3-79…...
JVM垃圾回收机制全解析
Java虚拟机(JVM)中的垃圾收集器(Garbage Collector,简称GC)是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象,从而释放内存空间,避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...
服务器硬防的应用场景都有哪些?
服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式,避免服务器受到各种恶意攻击和网络威胁,那么,服务器硬防通常都会应用在哪些场景当中呢? 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...
SQL慢可能是触发了ring buffer
简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...
scikit-learn机器学习
# 同时添加如下代码, 这样每次环境(kernel)启动的时候只要运行下方代码即可: # Also add the following code, # so that every time the environment (kernel) starts, # just run the following code: import sys sys.path.append(/home/aistudio/external-libraries)机…...

pikachu靶场通关笔记19 SQL注入02-字符型注入(GET)
目录 一、SQL注入 二、字符型SQL注入 三、字符型注入与数字型注入 四、源码分析 五、渗透实战 1、渗透准备 2、SQL注入探测 (1)输入单引号 (2)万能注入语句 3、获取回显列orderby 4、获取数据库名database 5、获取表名…...
前端中slice和splic的区别
1. slice slice 用于从数组中提取一部分元素,返回一个新的数组。 特点: 不修改原数组:slice 不会改变原数组,而是返回一个新的数组。提取数组的部分:slice 会根据指定的开始索引和结束索引提取数组的一部分。不包含…...

Unity中的transform.up
2025年6月8日,周日下午 在Unity中,transform.up是Transform组件的一个属性,表示游戏对象在世界空间中的“上”方向(Y轴正方向),且会随对象旋转动态变化。以下是关键点解析: 基本定义 transfor…...

WebRTC调研
WebRTC是什么,为什么,如何使用 WebRTC有什么优势 WebRTC Architecture Amazon KVS WebRTC 其它厂商WebRTC 海康门禁WebRTC 海康门禁其他界面整理 威视通WebRTC 局域网 Google浏览器 Microsoft Edge 公网 RTSP RTMP NVR ONVIF SIP SRT WebRTC协…...