0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Dubbo通过RouterFactory接口实现路由机制服务

电子设计 来源:博客园 作者:佚名 2020-04-16 07:43 次阅读

Dubbo路由机制是在服务间的调用时,通过将服务提供者按照设定的路由规则来决定调用哪一个具体的服务。

路由服务结构

Dubbo实现路由都是通过实现RouterFactory接口。当前版本 dubbo-2.7.5 实现该接口类如下:

路由实现工厂类是在 router 包下

由于 RouterFactory 是 SPI 接口,同时在获取路由 RouterFactory#getRouter 方法上有 @Adaptive(“protocol”) 注解,所以在获取路由的时候会动态调用需要的工厂类。

可以看到 getRouter 方法返回的是一个 Router 接口,该接口信息如下

其中 Router#route 是服务路由的入口,对于不同类型的路由工厂,有特定的 Router 实现类。

以上就是通过解析 URL,获取到具体的 Router,通过调用 Router#router 过滤出符合当前路由规则的 invokers

服务路由实现

上面展示了路由实现类,这几个实现类型中,ConditionRouter 条件路由是最为常用的类型,由于文章篇幅有限,本文就不对全部的路由类型逐一分析,只对条件路由进行具体分析,只要弄懂这一个类型,其它类型的解析就能容易掌握。

条件路由参数规则

在分析条件路由前,先了解条件路由的参数配置,官方文档如下:

条件路由规则内容如下:

条件路由实现分析

分析路由实现,主要分析工厂类的 xxxRouterFactory#getRouter 和 xxxRouter#route 方法。

ConditionRouterFactory#getRouter

ConditionRouterFactory 中通过创建 ConditionRouter 对象来初始化解析相关参数配置。

在 ConditionRouter 构造函数中,从 URL 里获取 rule 的字符串格式的规则,解析规则在 ConditionRouter#init 初始化方法中。

public void init(String rule) {

try {

if (rule == null || rule.trim().length() == 0) {

throw new IllegalArgumentException(“Illegal route 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);

// NOTE: It should be determined on the business level whether the `When condition` can be empty or not.

this.whenCondition = when;

this.thenCondition = then;

} catch (ParseException e) {

throw new IllegalStateException(e.getMessage(), e);

}

}

以路由规则字符串中的=》为分隔符,将消费者匹配条件和提供者匹配条件分割,解析两个路由规则后,赋值给当前对象的变量。

调用 parseRule 方法来解析消费者和服务者路由规则。

// 正则验证路由规则

protected static final Pattern ROUTE_PATTERN = Pattern.compile(“([&!=,]*)\s*([^&!=,\s]+)”);

private static Map《String, MatchPair》 parseRule(String rule)

throws ParseException {

/**

* 条件变量和条件变量值的映射关系

* 比如 host =》 127.0.0.1 则保存着 host 和 127.0.0.1 的映射关系

*/

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;

final Matcher matcher = ROUTE_PATTERN.matcher(rule);

while (matcher.find()) {

// 获取正则前部分匹配(第一个括号)的内容

String separator = matcher.group(1);

// 获取正则后部分匹配(第二个括号)的内容

String content = matcher.group(2);

// 如果获取前部分为空,则表示规则开始位置,则当前 content 必为条件变量

if (StringUtils.isEmpty(separator)) {

pair = new MatchPair();

condition.put(content, pair);

}

// 如果分隔符是 &,则 content 为条件变量

else if (“&”.equals(separator)) {

// 当前 content 是条件变量,用来做映射集合的 key 的,如果没有则添加一个元素

if (condition.get(content) == null) {

pair = new MatchPair();

condition.put(content, pair);

} else {

pair = condition.get(content);

}

}

// 如果当前分割符是 = ,则当前 content 为条件变量值

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());

}

// 由于 pair 还没有被重新初始化,所以还是上一个条件变量的对象,所以可以将当前条件变量值在引用对象上赋值

values = pair.matches;

values.add(content);

}

// 如果当前分割符是 = ,则当前 content 也是条件变量值

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;

values.add(content);

}

// 如果当前分割符为 ‘,’,则当前 content 也为条件变量值

else if (“,”.equals(separator)) { // Should be separated 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());

}

// 直接向条件变量值集合中添加数据

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;

}

上面就是解析条件路由规则的过程,条件变量的值都保存在 MatchPair 中的 matches、mismatches 属性中,=和,的条件变量值放在可以匹配的 matches 中,!=的条件变量值放在不可匹配路由规则的 mismatches 中。赋值过程中,代码还是比较优雅。

实际上 matches、mismatches 就是保存的是条件变量值。

ConditionRouter#route

Router#route的作用就是匹配出符合路由规则的 Invoker 集合。

// 在初始化中进行被复制的变量

// 消费者条件匹配规则

protected Map《String, MatchPair》 whenCondition;

// 提供者条件匹配规则

protected Map《String, MatchPair》 thenCondition;

public 《T》 List《Invoker《T》》 route(List《Invoker《T》》 invokers, URL url, Invocation invocation)

throws RpcException {

if (!enabled) {

return invokers;

}

// 验证 invokers 是否为空

if (CollectionUtils.isEmpty(invokers)) {

return invokers;

}

try {

// 校验消费者是否有规则匹配,如果没有则返回传入的 Invoker

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;

}

// 遍历传入的 invokers,匹配提供者是否有规则匹配

for (Invoker《T》 invoker : invokers) {

if (matchThen(invoker.getUrl(), url)) {

result.add(invoker);

}

}

// 如果 result 不为空,或当前对象 force=true 则返回 result 的 Invoker 列表

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(RULE_KEY));

return result;

}

} catch (Throwable t) {

logger.error(“Failed to execute condition router rule: ” + getUrl() + “, invokers: ” + invokers + “, cause: ” + t.getMessage(), t);

}

return invokers;

}

上面代码可以看到,只要消费者没有匹配的规则或提供者没有匹配的规则及 force=false 时,不会返回传入的参数的 Invoker。

匹配消费者路由规则和提供者路由规则方法是 matchWhen 和 matchThen

这两个匹配方法都是调用同一个方法 matchCondition 实现的。将消费者或提供者 URL 转为 Map,然后与 whenCondition 或 thenCondition 进行匹配。

匹配过程中,如果 key (即 sampleValue 值)存在对应的值,则通过 MatchPair#isMatch 方法再进行匹配。

private boolean isMatch(String value, URL param) {

// 存在可匹配的规则,不存在不可匹配的规则

if (!matches.isEmpty() && mismatches.isEmpty()) {

// 不可匹配的规则列表为空时,只要可匹配的规则匹配上,直接返回 true

for (String match : matches) {

if (UrlUtils.isMatchGlobPattern(match, value, param)) {

return true;

}

}

return false;

}

// 存在不可匹配的规则,不存在可匹配的规则

if (!mismatches.isEmpty() && matches.isEmpty()) {

// 不可匹配的规则列表中存在,则返回false

for (String mismatch : mismatches) {

if (UrlUtils.isMatchGlobPattern(mismatch, value, param)) {

return false;

}

}

return true;

}

// 存在可匹配的规则,也存在不可匹配的规则

if (!matches.isEmpty() && !mismatches.isEmpty()) {

// 都不为空时,不可匹配的规则列表中存在,则返回 false

for (String mismatch : mismatches) {

if (UrlUtils.isMatchGlobPattern(mismatch, value, param)) {

return false;

}

}

for (String match : matches) {

if (UrlUtils.isMatchGlobPattern(match, value, param)) {

return true;

}

}

return false;

}

// 最后剩下的是 可匹配规则和不可匹配规则都为空时

return false;

}

匹配过程再调用 UrlUtils#isMatchGlobPattern 实现

public static boolean isMatchGlobPattern(String pattern, String value, URL param) {

// 如果以 $ 开头,则获取 URL 中对应的值

if (param != null && pattern.startsWith(“$”)) {

pattern = param.getRawParameter(pattern.substring(1));

}

//

return isMatchGlobPattern(pattern, value);

}

public static boolean isMatchGlobPattern(String pattern, String value) {

if (“*”.equals(pattern)) {

return true;

}

if (StringUtils.isEmpty(pattern) && StringUtils.isEmpty(value)) {

return true;

}

if (StringUtils.isEmpty(pattern) || StringUtils.isEmpty(value)) {

return false;

}

// 获取通配符位置

int i = pattern.lastIndexOf(‘*’);

// 如果value中没有 “*” 通配符,则整个字符串值匹配

if (i == -1) {

return value.equals(pattern);

}

// 如果 “*” 在最后面,则匹配字符串 “*” 之前的字符串即可

else if (i == pattern.length() - 1) {

return value.startsWith(pattern.substring(0, i));

}

// 如果 “*” 在最前面,则匹配字符串 “*” 之后的字符串即可

else if (i == 0) {

return value.endsWith(pattern.substring(i + 1));

}

// 如果 “*” 不在字符串两端,则同时匹配字符串 “*” 左右两边的字符串

else {

String prefix = pattern.substring(0, i);

String suffix = pattern.substring(i + 1);

return value.startsWith(prefix) && value.endsWith(suffix);

}

}

就这样完成全部的条件路由规则匹配,虽然看似代码较为繁杂,但是理清规则、思路,一步一步还是较好解析,前提是要熟悉相关参数的用法及形式,不然代码较难理解。

最后

单纯从逻辑上,如果能够掌握条件路由的实现,去研究其它方式的路由实现,相信不会有太大问题。只是例如像脚本路由的实现,你得先会使用脚本执行引擎为前提,不然就不理解它的代码。最后,在 dubbo-admin 上可以设置路由,大家可以尝试各种使用规则,通过实操才能更好掌握和理解路由机制的实现。

责任编辑:gt


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 接口
    +关注

    关注

    33

    文章

    7639

    浏览量

    148482
  • 路由
    +关注

    关注

    0

    文章

    266

    浏览量

    41513
收藏 人收藏

    评论

    相关推荐

    聊聊Dubbo - Dubbo可扩展机制实战

    的做到了上面两点。这要得益于Dubbo的微内核+插件的机制。接下来的章节中我们会慢慢揭开Dubbo扩展机制的神秘面纱。2. 可扩展的几种解决方案通常可扩展的
    发表于 06-04 17:33

    聊聊Dubbo - Dubbo可扩展机制源码解析

    摘要: 在Dubbo可扩展机制实战中,我们了解了Dubbo扩展机制的一些概念,初探了Dubbo中LoadBalance的
    发表于 06-05 18:43

    Dubbo开源现状与未来规划

    循环依赖和反向依赖的出现。Dubbo 还有一个很重要的设计哲学就是平等对待第三方的扩展,即 Dubbo 内建的功能也是通过同样的扩展机制提供出来的,第三方的扩展和内建功能可以相互取代。
    发表于 07-05 15:21

    携程的 Dubbo 之路

    用户自行扩展,我们在PB序列化器的实现上增加了扩展接口,允许用户在外围继续增加数据压缩的功能。整体序列化器的实现并不是很难,倒是有一点需要注意的是,由于 Dubbo
    发表于 10-12 15:05

    Dubbo解析及原理浅析

    本文详细阐述了Dubbo及其原理。Dubbo是一种分布式服务框架。dubbo除了可以提供服务之外,还可以
    的头像 发表于 02-07 15:57 2640次阅读
    <b class='flag-5'>Dubbo</b>解析及原理浅析

    Dubbo源代码实现服务调用的动态代理和负载均衡

    我们知道,Dubbo服务调用封装成普通的Spring的Bean,于是我们可以像使用本地的Spring Bean一样,来调用远端的Dubbo服务,并有LoadBalance和Failo
    发表于 03-12 14:35 0次下载

    多态路由机制研究

    如何基于有限且确定的路由结构来支持多样化服务是当前研究面临的问题,采用路由结构的自组织和自调节来实现路由与业务的自适配,提出一种面向多样化
    发表于 03-13 16:43 2次下载
    多态<b class='flag-5'>路由</b><b class='flag-5'>机制</b>研究

    服务化改造实践(一)| Dubbo + ZooKeeper

     GreetingService {String sayHello(String name);}2、服务端:服务实现实现  GreetingService接口,并通过 @Service
    发表于 08-27 16:36 171次阅读
    <b class='flag-5'>服务</b>化改造实践(一)| <b class='flag-5'>Dubbo</b> + ZooKeeper

    服务化改造实践(一)| Dubbo + ZooKeeper

    GreetingService接口,并通过 @Service 来标注其为Dubbo的一个服务。@Servicepublic class AnnotatedGreetingServic
    发表于 08-27 17:25 227次阅读
    <b class='flag-5'>服务</b>化改造实践(一)| <b class='flag-5'>Dubbo</b> + ZooKeeper

    dubbo-go 中的 TPS Limit 设计与实现

    限流,比如说限制一分钟内某个接口只能访问 200 次,超过这个次数,则会被拒绝服务。在 Dubbo 的 Java 版本上,只有一个实现,就是 DefaultTPSLimiter 。 D
    发表于 03-17 15:27 507次阅读

    ARM与FPGA的接口实现的解析

    ARM与FPGA的接口实现的解析(应广单片机)-该文档为ARM与FPGA的接口实现的解析详述资料,讲解的还不错,感兴趣的可以下载看看…………………………
    发表于 07-22 09:47 14次下载
    ARM与FPGA的<b class='flag-5'>接口实现</b>的解析

    阿里用什么替代了dubbo

    dubbo简介 Dubbo是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的RPC实现
    的头像 发表于 07-31 10:43 9121次阅读

    C#-Interface接口实现

    C#-Interface接口实现(安徽理士电源技术有限公司招聘信息)-该文档为C#-Interface接口实现讲解文档,是一份还算不错的参考文档,感兴趣的可以下载看看,,,,,,,,,,,,,,,,,,
    发表于 09-28 09:42 3次下载
    C#-Interface<b class='flag-5'>接口实现</b>

    通过标准的CAN接口实现的测试台自动化解决方案

    通过标准的CAN接口实现的测试台自动化解决方案
    的头像 发表于 10-11 14:17 1256次阅读

    Java、Spring、Dubbo三者SPI机制的原理和区别

    其实我之前写过一篇类似的文章,但是这篇文章主要是剖析dubbo的SPI机制的源码,中间只是简单地介绍了一下Java、Spring的SPI机制,并没有进行深入,所以本篇就来深入聊一聊这三者的原理和区别。
    的头像 发表于 06-05 15:21 433次阅读
    Java、Spring、<b class='flag-5'>Dubbo</b>三者SPI<b class='flag-5'>机制</b>的原理和区别