① Dubbo(一)——Dubbo 集成于 Spring 的原理
最近一直在看bbo的源码部分。在阅读耐团的时候,需要有一个入手点,才能一点一点的进行下去。自己在研究的时候,发现思绪比较乱,于是就以 芋道源码 为基础,一点一点的啃食。芋道源码是直接从bbo的配置和一些核心的API开始讲起,是从bbo已经启动的过程作为开始节点岩亩猜,而这些核心 API 与 Spring 的之间的关系被省略了,这些东西对我来说属于前置的知识点,所以花了比较长的时间又从 Dubbo 的核心 API 倒着往前看。
在阅读 Dubbo 时,发现前置知识越来越多,如:Spring 的 refresh 中的一些核心点,Spring 中 bean 的生命周期,BeanFactory 与 FactoryBean 的区别等。所以这些前置知识花了特别多的时间去补。所幸,虽然补前置知识虽然时间长,但是性价比还是可以的。Dubbo 是依赖于Spring 的上下文环境的框架,其他依赖于 Spring 的框架也是相同的道理。Spring 的一些对外的扩展点,读过之后也会心中有数。
1、本篇主要是描述了 Dubbo 在 Spring 创建上下文的时候,是如何从创建,到能完整提供一个RPC调用能力的一些相关点。 2、由于源码比较多,直接贴断点也太过臃肿,所以仅仅贴一些关键点来概括整个流程。 3、本文是依赖于前面的 bbo 项目进行断点分析,项目结构可以参照这里。项目中 bbo 的配置方式是 xml 文件,所以本篇主要说 xml 配置方式。其他方式道理相同,并不是问题的关键点。
4、项目启动的是 bbo-user 服务,所以 UserService 为 bbo:service,OrderService 为 bbo:reference。
下图为Spring 启动时是如何加载 Dubbo 的,其中省略了大量过程,只保留了一些关键节点,省略的部分可以略微脑补一下。
整个流程的入口是 Spring 的 refresh 方法。每个方法都有比较深的调用栈。与 Dubbo 有关的入口是 refresh 中的 方法
这个方法是执行 beanFactory 的一些后处理操作,其核心流程为在Spring容器中找出实现了BeanFactoryPostProcessor接口的processor并执行。Spring容器会委托给的方法执行。 是比较核心的类,在这里我们关注一下这个类。它的作用是对项目中配置的类进行处理。具体处理粗型可以分为几步:
在加载类信息时,spring 会去用各种方式扫到注册的 bean 信息。我们在 spring 中注册的 bean,逃不出这个方法的扫描方式。 核心方法是:
扫描之后,会将扫描到的 bean 注册到 beanDefinitionMap 中
首先是此处 org.springframework.beans.factory.xml.#parseBeanDefinitions,可以看出方法会以配置文件根节点起,遍历所有子节点。
其次是这里 org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseCustomElement(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinition), 此方法会通过解析出来的节点,获取对应的 Spring 的 namespaceUri ,进而获取对应的配置文件处理器。 此处 ele 参数实际值为 <bbo:service … />,namespaceUri 为 http://code.alibabatech.com/schema/bbo
我们看一下 resolve 方法中的细节。因为这个方法内部才是 Dubbo 依赖于 Spring 的关键点。
此处的 NamespaceHandler 为 DubboNamespaceHandler,再创建结束之后,进行 init 初始化。
可以看到,DubboNamespaceHandler 在初始化的时候,会创建所有 bbo 标签对应的Config 类的 DubboBeanDefinitionParser。并将 DubboBeanDefinitionParser 和 对应的 bbo 标签类注册到 NamespaceHandlerSupport 的 parsers 中。
最后,会在 com.alibaba.bbo.config.spring.schema.DubboBeanDefinitionParser#parse(org.w3c.dom.Element, org.springframework.beans.factory.xml.ParserContext, java.lang.Class<?>, boolean) 方法中进行处理
Dubbo 服务比较特殊,beanDefinition 跟普通的 bean 不太一样。在向 beanDefinitionMap 注册时,普通的 beanDefinition 的 beanName 与 beanClass 是对应的;而 bbo 服务的 beanDefinition 的 beanName 是bbo 服务的名称,beanClass 为 bbo 对应的 Bean。
普通的 beanDefinition:
bbo 引用的服务的 beanDefinition:
这一步的核心流程是从 beanFactory 中获取所有的 ApplicationListener,然后注册到监听器集合中。它的关键点其实是 ServiceBean。因为 ServiceBean 是 ApplicationListener 的实现。
所以 beanFactory 中 ServiceBean 也会被注册到监听器集合中。项目中的 ServiceBean 的 beanClass 实际是 UserService。
这一步的核心点,主要是创建剩余的各类对象,并将其保存到 singletonObjects 中。其中关联的前置知识为 Spring 中 bean 的生命周期 。它的核心方法是: org.springframework.beans.factory.support.#doCreateBean
它的具体流程为:
ps:此处并不是只有这一步才会跟 bean 生命周期相关,bean 生命周期贯穿在 refresh 的很多流程中,只要执行doGetBean 方法,都会走这个流程。此处仅仅借楼关联一下。
这一步的核心点,是通知所有的监听器上下文刷新结束的事件。在这一步执行时,会通知到 ServiceBean。
此处暴露的是 UserService。
Dubbo 的启动条件是完全依赖于 Spring 的启动流程,Spring 的启动流程中核心的点是 refresh 方法。所以只要搞懂 refresh 方法,其他的拓展框架流程也会明白。只不过关联的知识点太多了,还是需要时间的积累才能一点一点的搞懂。 如果本篇有描述不清,或者描述有误的地方,还望在下方留言,大家一起交流,一起学习,一起进步~
② Dubbo简介
Dubbo是Alibaba开源的分布式服务框架,它按照分层的方式来架构,使用这种方式可以使各层解耦。 Dubbo在调用远程的服务的时候再本地有一个接口,就想调用本地方法一样去调用,底层实现好参数传输和远程服务运行结果传回之后的返回。 Dubbo的特点: (1)它主要使用高效的网络框架和序列化框架,让分布式服务之间调用效率更高。 (2)采用注册中心管理众多的服务接口地址,当你想调用服务的时候只需要跟注册中心询问即可,不像使用WebService一样每个服务都得记录好接口调用方式。 (3)监控中心时实现服务方和调用方之间运行状态的监控,还能控制服务的优先级、权限、权重、上下线等,让整个庞大的分布式服务系统的维护和治理比较方便。 (4)高可用,如果有服务挂了,注册中心就会从服务列表去掉该节点,客户端会像注册中心请求另一台可用的服务节点重新调用。同时注册中心也能实现高可用(ZooKeeper)。 (5)负载均衡,采用软负载均衡算法实现对多个相同服务的节点的请求负载均衡。 Dubbo需要四大基本组件:Rigistry,Monitor,Provider,Consumer。1、监控中心的配置文件-bbo.properties文件 (1)容器,监控中心是在jetty和spring环境下运行,依赖于注册中心,日志系统是log4j bbo.container = log4j,spring,registry,jetty (2)监控服务的名称,监控系统对整个Dubbo服务系统来说也是一个服务 bbo.application.name = simple-monitor (3)服务的所有者,这是Dubbbo的服务的功能,可以指定服务的负责人 bbo.application.owner = coselding (4)注册中心的地址,配置后监控中心就能通过注册中心获取当前可用的服务列表及其状态,在页面向你汇报Dubbo中的服务运行情况。 bbo.registr.address = multicast://{ip}:{port} //广播 bbo.registr.address = zookeeper://{ip}:{port} //zookeper bbo.registr.address = redis://{ip}:{port} //redis bbo.registr.address = bbo://{ip}:{port} //bbo (5)bbo协议端口号 bbo.protocol.port = 7070 (6)jetty工作端口号 bbo.jetty.port = 8082 (7)工作目录,用于存放监控中心的数据 bbo.jetty.directory = ${user.home}/monitor (8)监控中心报表存放目录 bbo.charts.directory=${bbo.jetty.directory}/charts (9)监控中心数据资料目录 bbo.statistics.directory=${user.home}/monitor/statistics (10)监控中心日志文件路径 bbo.log4j.file=logs/bbo-monitor-simple.log (11)监控中心日志记录级别 bbo.log4j.level=WARN 2、Dubbo提供负载均衡方式 (1)Random,随机,按权重配置随机概率,调用量越大分布越均匀,默认方式。 (2)RounRobin,轮询,按权重设置轮询比例,如果存在比较慢的机器容易在这台机器上请求阻塞较多。 (3)LeastActive,最少活跃调用数,不支持权重,只能根据自动识别的活跃数分配,不能灵活调配。 (4)ConsistenHash,一致性hash,对相同参数的请求路由到一个服务提供者上,如果有类似灰度发布需求可采用。 3、Dubbo过滤器 Dubbo初始化过程加载ClassPath下的META-INF/bbo/internal/,META-INF/bbo/,META-INF/services/三个路径下的com.alibaba.bbo.rpc.Filter文件。文件内容: Name = FullClassName,这些类必须实现Filter接口。自定义Filter类: 配置文件在配置过滤器,consumer.xml中:Dubbo对过滤器的加载过程: 先加载三个路径下的com.alibaba.bbo.rpc.Filter文件里面的键值对,key为过滤器名称,value为过滤器的类的全限定名(这个类必须实现Dubbo中的Filter接口)。 自定义的类中@Active注解是过滤器设定的全局基本属性。 Spring在加载consumer.xml文件时,通过 <bbo:consumer filter="xxx" id = "xxx" retrries = "0">这个配置指定消费者端要加载的过滤器,通过filter属性指定过滤器名称。 @Activate注解-自动激活,group属性是表示匹配了对应的角色才被加载,value表示表明过滤条件,不写则表示所有条件都会被加载,写了则只有bbo URL中包含该参数名且参数值不为空才被加载,这个参数会以bbo协议的一个参数K-V对传到Provider。 4、Dubbo的Provider配置 5、Dubbo的Consumer配置 1、Dubbo是什么? Dubbo是阿里巴巴开源的基于Java的高性能RPC分布式框架。 2、为什么使用Dubbo? 很多公司都在使用,经过很多线上的考验,内部使用了Netty,Zookeeper,保证了高性能可用性。 使用Dubbo可以将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,可以提高业务复用灵活性扩展,使前端应用能快速的响应对边的市场需求。分布式架构可以承受更大规模的并发流量。 Dubbo的服务治理图: 3、Dubbo和Spring Cloud的区别 两个没有关联,但是非要说区别,有如下几点: (1)通信方式不同,Dubbo使用RPC通信,Spring Cloud使用HTTP Restful方式 (2)组成部分不同4、Dubbo支持的协议 bbo:// (推荐);rmi:// ;hessian:// ;http:// ;webservice:// ;thrift:// ;memcached:// ;redis:// ;rest:// 。 5、Dubbo需要容器吗? 不需要,如果硬要容器的话,会增加复杂性,同时也浪费资源。 6、Dubbo内置的服务容器 Spring Container;Jetty Container;Log4j Container。 7、Dubbo中节点角色 Register,Monitor,Provider,Consumer,Container(服务运行的容器)。 8、Dubbo的服务注册和发现的流程图 9、Dubbo的注册中心 默认使用Zookeper作为注册中心,还有Redis,Multicast,bbo注册中心。 10、Dubbo的配置方式 Spring配置方式和Java API配置方式 11、Dubbo的核心配置 (1)bbo:service 服务配置 (2)bbo:referece 引用配置 (3)bbo:protocol 协议配置 (4)bbo:application 应用配置 (5)bbo:registry 注册中心配置 (6)bbo:monitor 监控中心配置 (7)bbo:provider 提供方配置 (8)bbo:consumer 消费方配置 (9)bbo:method 方法配置 (10)bbo:argument 参数配置 12、在Provider 节点上可以配置Consumer端的属性有哪些? (1)timeout:方法调用超时 (2)retries:失败重试次数,默认是2次 (3)loadbalance:负载均衡算法,默认随机 (4)actives消费者端,最大并发调用控制 13、Dubbo启动时如果依赖的服务不可用会怎样 Dubbo缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止Spring初始化完成。默认check ="true"。 14、Dubbo序列化框架 推荐使用Hessian序列化,还有Dubbo,Fastjson,Java自带序列化。 15、Dubbo的通信框架 默认使用Netty框架,另外也提供了Mina,Grizzly。 16、Dubbo集群容错方案 (1)Failover Cluster,失败自动切换,自动重试其他服务器。 (2)Failfast Cluster,快速失败,立即报错,只发起一次调用。 (3)Failsafe Cluster,失败安全,出现异常时,直接忽略。 (4)Failback Cluster,失败自动恢复,记录失败请求,定时重发。 (5)Forking Cluster,并行调用多个服务器,只要一个返回成功即可。 (6)Broadcast Cluster,广播逐个调用所有提供者,任意一个报错则报错。 17、Dubbo的负载均衡策略 (1)Random LoadBalance,随机,按权重设置随机概率,默认。 (2)RoundRobin LoadBalace,轮询,按公约后的权重设置轮训比例。 (3)LeastActive LoadBalace,最少活跃调用数,相同活跃数的随机。 (4)ConsistenHash LoadBalance,一致性hash,相同参数的请求总是发到用一个服务器。 18、指定某一个服务 可以配置环境点对点直连,绕过注册中心,将以服务接口为单位,忽略注册中心的提供者列表。 <bbo:reference interface="com.weidian.bbo.IMyDemo" version="1.0" id="myDemo" url="bbo://127.0.0.1:20880/"></bbo:reference> 19、Dubbo多协议 Dubbo允许配置多协议,在不同服务器上支持不同协议,或者同一服务支持多种协议。 20、当一个服务有多种实现时怎么做? 当一个接口有多种是现实,可以用group属性来分组,服务提供方和消费方都指定同一个group即可。 21、兼容旧版本 使用版本号过度,多个不同版本的服务注册到注册中心,版本号不同的服务相互间不引用。 22、Dubbo可以缓存吗? Dubbo提供声明式缓存,用于加速热门数据的访问速度,以减少用户加缓存的工作量。 23、Dubbo服务之间的调用时阻塞的吗? 默认是同步等待结果阻塞的,支持异步调用。Dubbo是基于NIO的非阻塞实现并行调用的,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小,异步调用会返回一个Future对象。 24、Dubbo不支持分布式事务 25、Dubbo必须依赖的包 Dubbo必须依赖JDK,其他为可选。 26、Dubbo使用过程中的问题 Dubbo的设计目的是为了满足高并发小数据量的rpc请求,在大数据量下性能表现不是很好,建议使用rmi或http协议。 27、Dubbo的管理控制台的作用 路由规则,动态配置,服务降级,访问控制,权重调整,负载均衡。 28、Spring boot整合Dubbo (1)添加依赖 <!– https://mvnrepository.com/artifact/com.alibaba.boot/bbo-spring-boot-project –> <dependency> <groupId>com.alibaba.boot</groupId> <artifactId>bbo-spring-boot-starter</artifactId> <version>0.1.0</version> </dependency> <!– https://mvnrepository.com/artifact/com.101tec/zkclient –> <dependency> <groupId>com.101tec</groupId> <artifactId>zkclient</artifactId> <version>0.10</version> </dependency>(2)配置bbo ## Dubbo 服务提供者配置 spring.bbo.application.name=provider spring.bbo.registry.address=zookeeper://127.0.0.1:2181 spring.bbo.protocol.name=bbo spring.bbo.protocol.port=20880 spring.bbo.scan=org.spring.springboot.bbo ## Dubbo 服务消费者配置 spring.bbo.application.name=consumer spring.bbo.registry.address=zookeeper://127.0.0.1:2181 spring.bbo.scan=org.spring.springboot.bbo
③ bbo version: 2.6.0, current host: 192.168.245.1
您可以通过修改橡大缓Dubbo配置文件来设置Dubbo应用的IP地址。Dubbo应用的IP地址可以通过bbo.registry.address配置项来指定。例如,您可以在Dubbo配置文件中添加以下配置项:bbo.registry.address = zookeeper://192.168.245.1:2181这里的IP地址为您希望Dubbo应用使用的IP地址。这个配置仿困项指定了Dubbo应用使用Zookeeper作为注册中心,并将Zookeeper的地址设置为192.168.245.1:2181。您也可以使用其他类型的注册中心,例如Redis、Consul等,具体配置方梁模式可以参考Dubbo文档。
④ Dubbo——路由机制(下)
在 Dubbo——路由机制(上) ,介绍了 Router 接口的基本功能以及 RouterChain 加载多个 Router 的实现,之后介绍了 ConditionRouter 这个类对条件路由规则的处理逻辑以及 ScriptRouter 这个类对脚本路由规则的处理逻辑。本文继续介绍剩余的三个 Router 接口实现类。 FileRouterFactory 是 ScriptRouterFactory 的装饰器,其扩展名为 file,FileRouterFactory 在 ScriptRouterFactory 基础上增加了读取文件的能力。可以将 ScriptRouter 使用的路由规则保存到文件中,然后在 URL 中指定文件路径,FileRouterFactory 从中解析到该脚本文件的路径并进行读取,调用 ScriptRouterFactory 去创建相应的 ScriptRouter 对象。 下面来看 FileRouterFactory 对 getRouter() 方法的具体实现,其中完成了 file 协议的 URL 到 script 协议 URL 的转换,如下是一个转换示例,首先会将 file:// 协议转换成 script:// 协议,然后会添加 type 参数和 rule 参数,其中 type 参数值根据文件后缀名确定,该示例为 js,rule 参数值为文件内容。 可以再结合接下来这个示例分析 getRouter() 方法的具体实现: TagRouterFactory 作为 RouterFactory 接口的扩展实现,其扩展名为 tag。但是需要注意的是,TagRouterFactory 与之前介绍的 ConditionRouterFactory、ScriptRouterFactory 的不同之处在于,它是通过继承 CacheableRouterFactory 这个抽象类,间接实现了 RouterFactory 接口。 CacheableRouterFactory 抽象类中维护了一个 ConcurrentMap 集合(routerMap 字段)用来缓存 Router,其中的 Key 是 ServiceKey。在 CacheableRouterFactory 的 getRouter() 方法中,会优先根据 URL 的 ServiceKey 查询 routerMap 集合,查询失败之后会调用 createRouter() 抽象方法来创建相应的 Router 对象。在 TagRouterFactory.createRouter() 方法中,创建的自然就是 TagRouter 对象了。 通过 TagRouter,可以将某一个或多个 Provider 划分到同一分组,约束流量只在指定分组中流转,这样就可以轻松达到流量隔离的目的,从而支持灰度发布等场景。 目前,Dubbo 提供了动态和静态两种方式给 Provider 打标签,其中动态方式就是通过服务治理平台动态下发标签,静态方式就是在 XML 等静态配置中打标签。Consumer 端可以在 RpcContext 的 attachment 中添加 request.tag 附加属性,注意保存在 attachment 中的值将会在一次完整的远程调用中持续传递,我们只需要在起始调用时进行设置,就可以达到标签的持续传递。 了解了 Tag 的基本概念和功能之后,再简单介绍一个 Tag 的使用示例。 在实际的开发测试中,一个完整的请求会涉及非常多的 Provider,分属不同团队进行维护,这些团队每天都会处理不同的需求,并在其负责的 Provider 服务中进行修改,如果所有团队都使用一套测试环境,那么测试环境就会变得很不稳定。如下图所示,4 个 Provider 分属不同的团队管理,Provider 2 和 Provider 4 在测试环境测试,部署了有 Bug 的版本,这样就会导致整个测试环境无法正常处理请求,在这样一个不稳定的测试环境中排查 Bug 是非常困难的,因为可能排查到最后,发现是别人的 Bug。 为了解决上述问题,我们可以针对每个需求分别独立出一套测试环境,但是这个方案会占用大量机器,前期的搭建成本以及后续的维护成本也都非常高。 下面是一个通过 Tag 方式实现环境隔离的架构图,其中,需求 1 对 Provider 2 的请求会全部落到有需求 1 标签的 Provider 上,其他 Provider 使用稳定测试环境中的 Provider;需求 2 对 Provider 4 的请求会全部落到有需求 2 标签的 Provider 4 上,其他 Provider 使用稳定测试环境中的 Provider。 在一些特殊场景中,会有 Tag 降级的场景,比如找不到对应 Tag 的 Provider,会按照一定的规则进行降级。如果在 Provider 集群中不存在与请求 Tag 对应的 Provider 节点,则默认将降级请求 Tag 为空的 Provider;如果希望在找不到匹配 Tag 的 Provider 节点时抛出异常的话,我们需设置 request.tag.force = true。 如果请求中的 request.tag 未设置,只会匹配 Tag 为空的 Provider,也就是说即使集群中存在可用的服务,若 Tag 不匹配也就无法调用。一句话总结,携带 Tag 的请求可以降级访问到无 Tag 的 Provider,但不携带 Tag 的请求永远无法访问到带有 Tag 的 Provider。 下面再来看 TagRouter 的具体实现。在 TagRouter 中持有一个 TagRouterRule 对象的引用,在 TagRouterRule 中维护了一个 Tag 集合,而在每个 Tag 对象中又都维护了一个 Tag 的名称,以及 Tag 绑定的网络地址集合,如下图所示: 另外,在 TagRouterRule 中还维护了 addressToTagnames、tagnameToAddresses 两个集合(都是 Map<String, List<String>> 类型),分别记录了 Tag 名称到各个 address 的映射以及 address 到 Tag 名称的映射。在 TagRouterRule 的 init() 方法中,会根据 tags 集合初始化这两个集合。 了解了 TagRouterRule 的基本构造之后,我们继续来看 TagRouter 构造 TagRouterRule 的过程。TagRouter 除了实现了 Router 接口之外,还实现了 ConfigurationListener 接口,如下图所示: ConfigurationListener 用于监听配置的变化,其中就包括 TagRouterRule 配置的变更。当我们通过动态更新 TagRouterRule 配置的时候,就会触发 ConfigurationListener 接口的 process() 方法,TagRouter 对 process() 方法的实现如下: 我们可以看到,如果是删除配置的操作,则直接将 tagRouterRule 设置为 null,如果是修改或新增配置,则通过 TagRuleParser 解析传入的配置,得到对应的 TagRouterRule 对象。TagRuleParser 可以解析 yaml 格式的 TagRouterRule 配置,下面是一个配置示例: 经过 TagRuleParser 解析得到的 TagRouterRule 结构,如下所示: 除了上图展示的几个集合字段,TagRouterRule 还从 AbstractRouterRule 抽象类继承了一些控制字段,后面介绍的 ConditionRouterRule 也继承了 AbstractRouterRule。 AbstractRouterRule 中核心字段的具体含义大致可总结为如下: 我们可以看到,AbstractRouterRule 中的核心字段与前面的示例配置是一一对应的。 我们知道,Router 最终目的是要过滤符合条件的 Invoker 对象,下面我们一起来看 TagRouter 是如何使用 TagRouterRule 路由逻辑进行 Invoker 过滤的,大致步骤如下: 上述流程的具体实现是在 TagRouter.route() 方法中,如下所示: 除了之前介绍的 TagRouterFactory 继承了 CacheableRouterFactory 之外,ServiceRouterFactory 也继承 CachabelRouterFactory,具有了缓存的能力,具体继承关系如下图所示: ServiceRouterFactory 创建的 Router 实现是 ServiceRouter,与 ServiceRouter 类似的是 AppRouter,两者都继承了 ListenableRouter 抽象类(虽然 ListenableRouter 是个抽象类,但是没有抽象方法留给子类实现),继承关系如下图所示: ListenableRouter 在 ConditionRouter 基础上添加了动态配置的能力,ListenableRouter 的 process() 方法与 TagRouter 中的 process() 方法类似,对于 ConfigChangedEvent.DELETE 事件,直接清空 ListenableRouter 中维护的 ConditionRouterRule 和 ConditionRouter 集合的引用;对于 ADDED、UPDATED 事件,则通过 ConditionRuleParser 解析事件内容,得到相应的 ConditionRouterRule 对象和 ConditionRouter 集合。这里的 ConditionRuleParser 同样是以 yaml 文件的格式解析 ConditionRouterRule 的相关配置。ConditionRouterRule 中维护了一个 conditions 集合(List<String> 类型),记录了多个 Condition 路由规则,对应生成多个 ConditionRouter 对象。 整个解析 ConditionRouterRule 的过程,与前文介绍的解析 TagRouterRule 的流程类似。 在 ListenableRouter 的 route() 方法中,会遍历全部 ConditionRouter 过滤出符合全部路由条件的 Invoker 集合,具体实现如下: ServiceRouter 和 AppRouter 都是简单地继承了 ListenableRouter 抽象类,且没有覆盖 ListenableRouter 的任何方法,两者只有以下两点区别。 本文我们是紧接 Dubbo——路由机制(上) 的内容,继续介绍了剩余 Router 接口实现的内容。 我们介绍了基于文件的 FileRouter 实现,其底层会依赖之前介绍的 ScriptRouter;接下来又讲解了基于 Tag 的测试环境隔离方案,以及如何基于 TagRouter 实现该方案,同时深入分析了 TagRouter 的核心实现;最后我们还介绍了 ListenableRouter 抽象类以及 ServerRouter 和 AppRouter 两个实现,它们是在条件路由的基础上添加了动态变更路由规则的能力,同时区分了服务级别和服务实例级别的配置。