摘要
Router 的主要功能就是根据用户配置的路由规则以及请求携带的信息,过滤出符合条件的 Invoker 集合,供后续负载均衡逻辑使用。本博文将详细的讲解dubbo的路由相关原理。
一、路由机制原理
Dubbo的路由机制主要解决的目的就是服务调用时,从已知的所有服务提供者中根据路由规则刷选服务提供者。
# demo-provider 应用增加了两个标签分组 TAG_A 和 TAG_B
# TAG_A 包含一个实例 127.0.0.1:20880
# TAG_B 包含一个实例 127.0.0.1:20881
force: true
enabled: true
runtime: false
tags:
- name: TAG_A
addresses: [192.168.0.1:20880]
- name: TAG_B
addresses: [192.168.0.2:20881]
上图中我们可以看到有两个机房分别是机房A、机房B,其中机房 A 只能访问到 Service A 和 Service B ,而机房B 只能访问到 Service C 和 Service D。要实现上面这种场景我们就需要用到标签路由。从机房 A 发起的调用携带标签 TAG_A 访问到 Service A 和 Service B,而从机房 B 发起的调用携带 TAG_B Service C 和 Service D。
- 提供两个提供者(一台本机作为提供者,一台为其他的服务器),每个提供者会在调用时可以返回不同的信息 以区分提供者。
- 针对于消费者,我们这里通过一个死循环,每次等待用户输入,再进行调用,来模拟真实的请求情况。通过调用的返回值 确认具体的提供者。
- 我们通过ipconfifig来查询到我们的IP地址,并且单独启动一个客户端,来进行如下配置(这里假设我们希望隔离掉本机的请求,都发送到另外一台机器上)。
- 通过这个程序执行后,我们就通过消费端不停的发起请求,看到真实的请求都发到了除去本机以外的另外一台机器上。
1.1 路由规则详解
- route:// 表示路由规则的类型,支持条件路由规则和脚本路由规则,可扩展,必填。
- 0.0.0.0 表示对所有 IP 地址生效,如果只想对某个 IP 的生效,请填入具体 IP,必填。
- com.lagou.service.HelloService 表示只对指定服务生效,必填。
- category=routers 表示该数据为动态配置类型,必填。
- dynamic : 是否为持久数据,当指定服务重启时是否继续生效。必填。
- runtime : 是否在设置规则时自动缓存规则,如果设置为true则会影响部分性能。
- rule : 是整个路由最关键的配置,用于配置路由规则。
1.2 路由机制应用场景
标签路由通过将某一个或多个服务的提供者划分到同一个分组,约束流量只在指定分组中流转,从而实现流量隔离的目的。我们日常工作中常用的场景有:蓝绿发布、灰度发布等场景的能力基础等。我们去想象这样的一个场景,一个dubbo的提供者要准备进行上线,一般都提供多台提供者来同时在线上提供服务。这时候一个请求刚到达一个提供者,提供者却进行了关闭操作。那么此次请求就应该认定为失败了。所以基于这样的场景,我们可以通过路由的规则,把预发布(灰度)的机器进行从机器列表中移除。并且等待一定的时间,让其把现有的请求处理完成之后再进行关闭服务。同时,在启动时,同样需要等待一定的时间,以免因为尚未重启结束,就已经注册上去。等启动到达一定时间之后,再进行开启流量操作。
1.利用zookeeper的路径感知能力,在服务准备进行重启之前将当前机器的IP地址和应用名写入 zookeeper。
2.服务消费者监听该目录,读取其中需要进行关闭的应用名和机器IP列表并且保存到内存中。
3.当前请求过来时,判断是否是请求该应用,如果是请求重启应用,则将该提供者从服务列表中移除。
- 引入 Curator 框架,用于方便操作Zookeeper
- 编写Zookeeper的操作类,用于方便进行zookeeper处理
- 编写需要进行预发布的路径管理器,用于缓存和监听所有的待灰度机器信息列表。
- 编写路由类(实现 org.apache.dubbo.rpc.cluster.Router ),主要目的在于对
- ReadyRestartInstances 中的数据进行处理,并且移除路由调用列表中正在重启中的服务。
- 由于 Router 机制比较特殊,所以需要利用一个专门的 RouterFactory 来生成,原因在于并不是所有的都需要添加路由,所以需要利用 @Activate 来锁定具体哪些服务才需要生成使用。
- 对 RouterFactory 进行注册,同样放入到
META-INF/dubbo/org.apache.dubbo.rpc.cluster.RouterFactory 文件中。
restartInstances=com.lagou.router.RestartingInstanceRouterFactory
- 将dubbo-spi-router项目引入至 consumer 项目的依赖中。
- 这时直接启动程序,还是利用上面中所写好的 consumer 程序进行执行,确认各个 provider 可以正常执行。
- 单独写一个 main 函数来进行将某台实例设置为启动中的状态,比如这里我们认定为当前这台机器中的 service-provider 这个提供者需要进行重启操作。
ReadyRestartInstances.create().addRestartingInstance("service-provider", "正在
重新启动的机器IP");
- 执行完成后,再次进行尝试通过 consumer 进行调用,即可看到当前这台机器没有再发送任何请求
- 一般情况下,当机器重启到一定时间后,我们可以再通过 removeRestartingInstance 方法对这个机器设定为既可以继续执行。
- 调用完成后,我们再次通过 consumer 去调用,即可看到已经再次恢当前机器的请求参数。
二、路由机制源码分析
2.1 RouterChain、RouterFactory 与 Router
- invokers(List<Invoker
<T>
> 类型):当前 RouterChain 对象要过滤的 Invoker 集合。我们可以看到,在 StaticDirectory 中是通过 RouterChain.setInvokers() 方法进行设置的。 - builtinRouters(List
<Router>
类型):当前 RouterChain 激活的内置 Router 集合。 - routers(List
<Router>
类型):当前 RouterChain 中真正要使用的 Router 集合,其中不仅包括了上面 builtinRouters 集合中全部的 Router 对象,还包括通过 addRouters() 方法添加的 Router 对象。
在 RouterChain 的构造函数中,会在传入的 URL 参数中查找 router 参数值,并根据该值获取确定激活的 RouterFactory,之后通过 Dubbo SPI 机制加载这些激活的 RouterFactory 对象,由 RouterFactory 创建当前激活的内置 Router 实例,
private RouterChain(URL url) {
// 通过ExtensionLoader加载激活的RouterFactory
List<RouterFactory> extensionFactories = ExtensionLoader.getExtensionLoader(RouterFactory.class)
.getActivateExtension(url, "router");
// 遍历所有RouterFactory,调用其getRouter()方法创建相应的Router对象
List<Router> routers = extensionFactories.stream()
.map(factory -> factory.getRouter(url))
.collect(Collectors.toList());
initWithRouters(routers); // 初始化buildinRouters字段以及routers字段
}
public void initWithRouters(List<Router> builtinRouters) {
this.builtinRouters = builtinRouters;
this.routers = new ArrayList<>(builtinRouters);
this.sort(); // 这里会对routers集合进行排序
}
博文参考
32 路由机制:请求到底怎么走,它说了算(上).md
33 路由机制:请求到底怎么走,它说了算(下).md
标签:Dubbo,请求,提供者,RouterFactory,TAG,Router,路由 From: https://blog.51cto.com/u_13643065/6139634