欢迎大家关注 github.com/hsfxuebao ,希望对大家有所帮助,要是觉得可以的话麻烦给点一下Star哈
1. 什么是服务路由
服务路由包含一条路由规则,路由规则决定了服务消费者的调用目标,即规定了服务消 费者可调用哪些服务提供者
。 Dubbo 目前提供了三种服务路由实现,分别为条件路由 ConditionRouter、 脚本路由 ScriptRouter 和标签路由 TagRouter
。其中条件路由是我们最常 使用的。
下面将以条件路由为例来讲解服务路由的用法。
2. 路由规则的设置
2.1 路由规则的设置平台
我们通过 Dubbo 管控平台
为某服务/应用设置的路由规则,其不仅会设置到注册中心该 服务下,同时也会设置到配置中心
该服务下(如果搭建了配置中心的话)。
当然,我们也可以直接通过配置中心的 GUI 管理端
来设置和修改路由规则。例如,通过 Apollo 可视化平台来设置 Apollo 中的动态配置,通过 ZooInspector 来设置 Zookeeper 中的动 态配置等。
不过,通过配置中心管理端所进行的修改,是不会修改到注册中心中的路由规则的。所 以,在设置了配置中心后, 配置中心中的动态配置优先级是高于注册中心中的
。
2.2 Dubbo 管控平台设置路由规则
我们可以像下面这里创建条件路由规则。但生产中使用最多的是黑白名单,所以,在管 控平台中给出了黑白名称的直接设置界面。不过,通过直接的黑白名单设置与通过条件路由 设置,只设置一种即可,且在当前版本中,两种设置均可用。
2.2.1 设置黑白名单
上例中设置了黑名单,即 IP 为 192.168.124.7 的消费者是无法调用任意提供者的。
2.2.2 设置条件路由
也可以通过在“条件路由”中的配置来设置“黑白名单”。
3. 路由规则详解
3.1 初识路由规则
应用粒度路由规则:
scope: application
force: true
runtime: true
enabled: true
key: governance-conditionrouter-consumer
conditions:
# app1 的消费者只能消费所有端口为 20880 的服务提供实例
- application=app1 => address=*:20880
# app2 的消费者只能消费所有端口为 20881 的服务提供实例
- application=app2 => address=*:20881
服务粒度路由规则:
scope: service
force: true
runtime: true
enabled: true
key: org.apache.dubbo.samples.governance.api.DemoService
conditions:
# DemoService 的 sayHello 方法只能消费所有端口为 20880 的服务提供者实例
- method=sayHello => address=*:20880
# DemoService 的 sayHi 方法只能消费所有端口为 20881 的服务提供者实例
- method=sayHi => address=*:20881
3.2 属性详解
scope
必填项。表示路由规则的作用粒度, 其取值将会决定 key 的取值。 其取值范围如下:
- service:表示服务粒度
- application:表示应用粒度
Key
必填项。 指定规则体将作用于哪个服务或应用。 其取值取决于 scope 的取值。
- scope 取值为 application 时, key 取值为 application 名称,即<dubbo:application name=””/>的值。
- scope 取值为 service 时, key 取值为
[group:]service[:version]的组合
,即组、接口名称与版本号。
enabled
可选项。指定当前路由规则是否立即生效。 缺省值为 true,表示立即生效。
force
可选项。指定当路由结果为空时,是否强制执行。 如果不强制执行,路由结果为空则路 由规则自动失效,不使用路由。 缺省为 false,不强制执行。
runtime
可选项。指定是否在每次调用时执行路由规则。
-
若为 false 则表示只在提供者地址列表变更时会预先执行路由规则,并将路由结果进行缓存, 消费者调用时直接从缓存中获取路由结果。
-
若为 true 则表示每次调用都要重新计算路由规则,其将会直接影响调用的性能。 缺省为 false。
priority
可选项。 用于设置路由规则的优先级, 数字越大,优先级越高
, 越靠前执行。 缺省为 0。
3.3 规则体 conditions
必填项。定义具体的路由规则内容, 由 1 到任意多条规则组成。
3.3.1 格式
路由规则由两个条件组成,分别用于对服务消费者和提供者进行匹配。 [服务消费者匹配条件] => [服务提供者匹配条件]
-
当消费者的 URL 满足匹配条件时,对该消费者执行后面的过滤规则。
-
=> 之后为提供者地址列表的过滤条件
,所有参数和提供者的 URL 进行对比,消费者最终只拿到过滤后的地址列表。例如:路由规则为:
host = 10.20.153.10 => host = 10.20.153.11
。其表示 IP 为 10.20.153.10的服务消费者只可调用 IP 为 10.20.153.11 机器上的服务,不可调用其他机器上的服务。
不过,这两个条件存在缺省的情况:
-
服务消费者匹配条件为空,表示不对服务消费者进行限制,所有消费者均将被路由到行后面的提供者。
-
服务提供者匹配条件为空,表示对符合消费者条件的消费者将禁止调用任何提供者。
例如:路由规则为: => host != 10.20.153.11,则表示所有消费者均可调用IP为10.20.153.11之外的所有提供者。 再如路由规则为: host = 10.20.153.10 => ,则表示 IP 为 10.20.153.10 的提供者不能调用任何提供者。
3.3.2 符号支持
-
参数符号:
- method:将调用方法作为路由规则比较的对象
- argument:将调用方法参数作为路由规则比较的对象
- protocol:将调用协议作为路由规则比较的对象
- host:将 IP 作为路由规则比较的对象
- port:将端口号作为路由规则比较的对象
- address:将 IP:端口号作为路由规则比较的对象
- application:将应用名称作为路由规则比较的对象
-
条件符号:
- 等于号(=):将参数类型匹配上参数值作为路由条件
- 不等号(!=):将参数类型不匹配参数值作为路由条件
-
取值符号:
- 逗号(,):多个取值的分隔符, 如: host != 10.20.153.10,10.20.153.11
- 星号():通配符, 如: host != 10.20.
- 美元符号( host
3.3.3 举例
- 黑名单
- host = 10.20.153.10,10.20.153.11 =>
- IP 为 10.20.153.10 与 10.20.153.11 的主机将被禁用。
- 白名单
- host != 10.20.153.10,10.20.153.11 =>
- 禁用 IP 为 10.20.153.10 与 10.20.153.11 之外的所有主机。
- 只暴露一部分的提供者
- => host = 172.22.3.1*,172.22.3.2*
- 消费者只可访问 IP 为 172.22.3.1与 172.22.3.2的提供者主机。
- 为重要应用提供额外的机器
- application != kylin => host != 172.22.3.95,172.22.3.96
- 应用名称不为 kylin 的应用不能访问 172.22.3.95 与 172.22.3.96 两台提供者主机。即只有名称为 kylin 的消费者可以访问 172.22.3.95 与 172.22.3.96 两台提供者主机。当然, kylin 还可以访问其它提供者主机,而其它消费者也可以访问 172.22.3.95 与 172.22.3.96 之外的所有提供者主机。
- 读写分离
- method = find*,list*,get*,is* => host = 172.22.3.94,172.22.3.95,172.22.3.96
- method != find*,list*,get*,is* => host = 172.22.3.97,172.22.3.98
- find、 list、 get、 is 开头的消费者方法会被路由到 172.22.3.94、 172.22.3.95 与 172.22.3.96 三台提供者主机,而其它方法则会被路由到 172.22.3.97 与 172.22.3.98 两台提供者主机。
- 前后台分离
- application = bops => host = 172.22.3.91,172.22.3.92,172.22.3.93
- application != bops => host = 172.22.3.94,172.22.3.95,172.22.3.96
- 应用名称的 bops 的消费者会被路由到 172.22.3.91、 172.22.3.92 与 172.22.3.93 三台提供者主机,而其它消费者则会被路由到 172.22.3.94、 172.22.3.95 与 172.22.3.96 三台提供者。
- 隔离不同机房网段
- host != 172.22.3.* => host != 172.22.3.*
- 不是 172.22.3 网段的消费者是不能访问 172.22.3 网段的提供者的。即只有 172.22.3 网段的消费者才可访问 172.22.3 网段的提供者。当然, 172.22.3 网段的消费者也可访问其它网段的提供者。
- 只访问本机的服务
- => host = $host
- $host 表示获取消费者请求中的消费者主机 IP。故该规则就表示消费者只能访问本机的服务
4. Directory目录
Directory 代表了多个 invoker
(对于消费端来说,每个 invoker 代表了一个服务提供者
),其 内部维护着一个 List,并且这个 List 的内容是动态变化
的, 比如, 当服务提供者集群新增 或者减少机器时,服务注册中心就会推送当前服务提供者的地址列表,然后 Directory 中 的 List 就会根据服务提供者地址列表相应变化 。
在 Dubbo 中, 接口 Directory 的实现有 RegistryDirectory 和 StaticDirectory 两种
,其中 前者管理的 invoker 列表是根据服务注册中心的推送变化而变化的,而后者是当消费端使用了多注册中心时,其把所有服务注册中心的 invoker 列表汇集到一个 invoker 列表中。
4. 源码追踪
4.1 添加激活 RouterFactory 创建的 Router 到 Directory
4.1.1 整体调用图
4.1.2 源码分析
RegistryProtocol.doCreateInvoker():
protected <T> ClusterInvoker<T> doCreateInvoker(DynamicDirectory<T> directory, Cluster cluster, Registry registry, Class<T> type) {
...
// 将所有RouterFactory激活扩展类创建的router添加到directory
directory.buildRouterChain(urlToRegistry);
// 服务订阅
directory.subscribe(toSubscribeUrl(urlToRegistry));
// 将多个invoker伪装为一个具有复合功能的invoker
return (ClusterInvoker<T>) cluster.join(directory);
}
接下来DynamicDirectory.buildRouterChain():
public void buildRouterChain(URL url) {
// 创建一个RouterChain,并设置到directory
this.setRouterChain(RouterChain.buildChain(url));
}
RouterChain.buildChain():
public static <T> RouterChain<T> buildChain(URL url) {
// 创建RouterChain
return new RouterChain<>(url);
}
private RouterChain(URL url) {
loopPool = executorRepository.nextExecutorExecutor();
// -------------- 对普通路由的处理 --------------------
// 获取激活RouterFactory实例
List<RouterFactory> extensionFactories = ExtensionLoader.getExtensionLoader(RouterFactory.class)
.getActivateExtension(url, ROUTER_KEY);
// 对所有RouterFactory实例的遍历
List<Router> routers = extensionFactories.stream()
// 流中的元素由factory映射为了该factory创建出的router
.map(factory -> factory.getRouter(url))
.collect(Collectors.toList());
// 将routers写入到routerChain
initWithRouters(routers);
// -------------- 对状态路由的处理 --------------------
List<StateRouterFactory> extensionStateRouterFactories = ExtensionLoader.getExtensionLoader(
StateRouterFactory.class)
.getActivateExtension(url, STATE_ROUTER_KEY);
List<StateRouter> stateRouters = extensionStateRouterFactories.stream()
.map(factory -> factory.getRouter(url, this))
.sorted(StateRouter::compareTo)
.collect(Collectors.toList());
// init state routers
initWithStateRouters(stateRouters);
}
4.2 读取配置中心的路由规则
CacheableRouterFactory:
public abstract class CacheableRouterFactory implements RouterFactory {
private ConcurrentMap<String, Router> routerMap = new ConcurrentHashMap<>();
@Override
public Router getRouter(URL url) {
// 从缓存map中获取当前服务标识key对应的router,
// 若没有,则创建一个router,放入到缓存map后返回新创建的router
return routerMap.computeIfAbsent(url.getServiceKey(), k -> createRouter(url));
}
protected abstract Router createRouter(URL url);
}
接下来ListenableRouter#ListenableRouter:
public ListenableRouter(URL url, String ruleKey) {
super(url);
this.setForce(false);
// 从配置中心读取路由规则,初始化到这里
this.init(ruleKey);
}
private synchronized void init(String ruleKey) {
if (StringUtils.isEmpty(ruleKey)) {
return;
}
// 由服务标识与Router后辍构成router key
String routerKey = ruleKey + RULE_SUFFIX;
this.getRuleRepository().addListener(routerKey, this);
// 从配置中心读取动态路由规则
String rule = this.getRuleRepository().getRule(routerKey, DynamicConfiguration.DEFAULT_GROUP);
if (StringUtils.isNotEmpty(rule)) {
this.process(new ConfigChangedEvent(routerKey, DynamicConfiguration.DEFAULT_GROUP, rule));
}
}
接下来,直接到ZookeeperDynamicConfiguration#doGetConfig:
protected String doGetConfig(String pathKey) throws Exception {
// 读取zk节点中指定path的节点内容
return zkClient.getContent(pathKey);
}
读取配置中心的路由配置,这是刚才我们设备黑名单的在zk中的配置如下:
4.3 读取注册中心的路由规则
我们直接进入RegistryDirectory.notify():
public synchronized void notify(List<URL> urls) {
if (isDestroyed()) {
return;
}
Map<String, List<URL>> categoryUrls = urls.stream()
.filter(Objects::nonNull) // 过滤掉为null的元素
.filter(this::isValidCategory) // 过滤掉不可用分类元素
.filter(this::isNotCompatibleFor26x) // 过滤掉不兼容2.6版本的元素
.collect(Collectors.groupingBy(this::judgeCategory));
// 处理configurators分类节点
List<URL> configuratorURLs = categoryUrls.getOrDefault(CONFIGURATORS_CATEGORY, Collections.emptyList());
this.configurators = Configurator.toConfigurators(configuratorURLs).orElse(this.configurators);
// 处理routers分类节点
List<URL> routerURLs = categoryUrls.getOrDefault(ROUTERS_CATEGORY, Collections.emptyList());
// 读取routers子节点下的路由规则,并添加到directory中
toRouters(routerURLs).ifPresent(this::addRouters);
// providers
// 处理providers分类节点
List<URL> providerURLs = categoryUrls.getOrDefault(PROVIDERS_CATEGORY, Collections.emptyList());
...
// 刷新invoker
refreshOverrideAndInvoker(providerURLs);
}
private Optional<List<Router>> toRouters(List<URL> urls) {
if (urls == null || urls.isEmpty()) {
return Optional.empty();
}
List<Router> routers = new ArrayList<>();
// 遍历routers子节点下的所有Url
for (URL url : urls) {
if (EMPTY_PROTOCOL.equals(url.getProtocol())) {
continue;
}
// 获取router属性值,这里就是condition
String routerType = url.getParameter(ROUTER_KEY);
if (routerType != null && routerType.length() > 0) {
// 将url的protocol由router://...变为condition://...
url = url.setProtocol(routerType);
}
try {
// 创建相应的router
Router router = ROUTER_FACTORY.getRouter(url);
// 添加到routers中
if (!routers.contains(router)) {
routers.add(router);
}
} catch (Throwable t) {
logger.error("convert router url to router error, url: " + url, t);
}
}
return Optional.of(routers);
}
这样就可以将 注册中心的 路由配置加载进入了
4.4 RegistryDirectiry 中invoke列表的更新
下面看看 Regist叩Directory 中 invoker 列表的更新流程,如图所示:
通过图可知,创建完 RegistryDirectory 后,调用了其 subscribe()方法,这里假设使用的服务注册中心为 ZooKeeper,这样就会去 ZooKeeper 订阅 需要调用的服务提供者 的地址列表, 然后添加了 一个监听器,当 ZooKeeper 服务端发现服务提供者地址列表发生变化后,会将地址列表推送到服务消费端,然后 zkClient 会回调该监听器的notify()方法。
在步骤 3 设置完监昕器后,同步返回了订阅的服务地址列表、路由规则、 配置信息等, 然后 同步调用了 RegistrγDirectory 的 notify()方法:
public synchronized void notify(List<URL> urls) {
if (isDestroyed()) {
return;
}
Map<String, List<URL>> categoryUrls = urls.stream()
.filter(Objects::nonNull) // 过滤掉为null的元素
.filter(this::isValidCategory) // 过滤掉不可用分类元素
.filter(this::isNotCompatibleFor26x) // 过滤掉不兼容2.6版本的元素
.collect(Collectors.groupingBy(this::judgeCategory));
// 处理configurators分类节点
List<URL> configuratorURLs = categoryUrls.getOrDefault(CONFIGURATORS_CATEGORY, Collections.emptyList());
this.configurators = Configurator.toConfigurators(configuratorURLs).orElse(this.configurators);
// 处理routers分类节点
List<URL> routerURLs = categoryUrls.getOrDefault(ROUTERS_CATEGORY, Collections.emptyList());
// 读取routers子节点下的路由规则,并添加到directory中
toRouters(routerURLs).ifPresent(this::addRouters);
// providers
// 处理providers分类节点
List<URL> providerURLs = categoryUrls.getOrDefault(PROVIDERS_CATEGORY, Collections.emptyList());
/**
* 3.x added for extend URL address
*/
ExtensionLoader<AddressListener> addressListenerExtensionLoader = ExtensionLoader.getExtensionLoader(AddressListener.class);
List<AddressListener> supportedListeners = addressListenerExtensionLoader.getActivateExtension(getUrl(), (String[]) null);
if (supportedListeners != null && !supportedListeners.isEmpty()) {
for (AddressListener addressListener : supportedListeners) {
providerURLs = addressListener.notify(providerURLs, getConsumerUrl(),this);
}
}
// 刷新invoker
refreshOverrideAndInvoker(providerURLs);
}
步骤 6 从 ZooKeeper 返回 的服务提供者 的信息里获取对应的路 由 规则, 并使用步骤 7 保存到 RouterChain 里.
步骤 9 根据服务降级信息 , 重写 URL C 也就是把 mock=retum null 等信息拼接到 URL 中〉并保存到 overrideDirectoryUrl 中,然后执行步骤 10,把服务提供者的 URL 列表转换 为 invoker 列表,并保存到 RouterChain 里:
// 更新本地invoker列表
private void refreshInvoker(List<URL> invokerUrls) {
Assert.notNull(invokerUrls, "invokerUrls should not be null");
if (invokerUrls.size() == 1
&& invokerUrls.get(0) != null
&& EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) { // 判断是否有empty://开头
this.forbidden = true; // Forbid to access 禁止远程调用
this.invokers = Collections.emptyList();
routerChain.setInvokers(this.invokers);
destroyAllInvokers(); // Close all invokers 将缓存中的所有invoker删除
} else {
this.forbidden = false; // Allow to access 允许远程访问
// 缓存map,更新本地invoker列表,就是更新这里
// key为URL,value为该URL对应的invoker实例(invoker委托对象)
// 先将缓存map发生变更前的值进行暂存
Map<URL, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap; // local reference
if (invokerUrls == Collections.<URL>emptyList()) {
invokerUrls = new ArrayList<>();
}
// 这段代码可以增加provider的可用性,
// 如果provider和注册中心网络抖动,导致注册不成功,但是之前注册成功并且更新到本地缓存了,这种情况我们可以直接调用
if (invokerUrls.isEmpty() && this.cachedInvokerUrls != null) {
invokerUrls.addAll(this.cachedInvokerUrls);
} else {
this.cachedInvokerUrls = new HashSet<>();
this.cachedInvokerUrls.addAll(invokerUrls);//Cached invoker urls, convenient for comparison
}
// 走到这里invokerUrls仍为空,则说明真的是没有任何可用的invoker
if (invokerUrls.isEmpty()) {
return;
}
// 从缓存map中获取相应的invoker,若存在,则返回,并将其从缓存map中删除
// 若不存在,则创建一个invoker
Map<URL, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);// Translate url list to Invoker map
/**
* If the calculation is wrong, it is not processed.
*
* 1. The protocol configured by the client is inconsistent with the protocol of the server.
* eg: consumer protocol = dubbo, provider only has other protocol services(rest).
* 2. The registration center is not robust and pushes illegal specification data.
*
*/
if (CollectionUtils.isEmptyMap(newUrlInvokerMap)) {
logger.error(new IllegalStateException("urls to invokers error .invokerUrls.size :" + invokerUrls.size() + ", invoker.size :0. urls :" + invokerUrls
.toString()));
return;
}
// 获取到最新的可用invoker列表
List<Invoker<T>> newInvokers = Collections.unmodifiableList(new ArrayList<>(newUrlInvokerMap.values()));
// pre-route and build cache, notice that route cache should build on original Invoker list.
// toMergeMethodInvokerMap() will wrap some invokers having different groups, those wrapped invokers not should be routed.
routerChain.setInvokers(newInvokers);
this.invokers = multiGroup ? toMergeInvokerList(newInvokers) : newInvokers;
// 将缓存map更新为新map
this.urlInvokerMap = newUrlInvokerMap;
try {
// 删除老map中剩余的invoker
destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); // Close the unused Invoker
} catch (Exception e) {
logger.warn("destroyUnusedInvokers error. ", e);
}
// notify invokers refreshed
this.invokersChanged();
}
}
4.5 服务路由过滤
RouterChain 里也保存了可用服务提供者对应的 invokers 列表和路由规则信 息,当服务消费方的集群容错策略要获取可用服务提供者对应的 invoker 列表时,会调用 RouterChain 的 route()方法,其内部根据路由规则信息和 invokers 列表来提供服务,流程 如图所示:
我们直接看org.apache.dubbo.registry.integration.DynamicDirectory#doList:
public List<Invoker<T>> doList(Invocation invocation) {
...
List<Invoker<T>> invokers = null;
try {
// Get invokers from cache, only runtime routers will be executed.
invokers = routerChain.route(getConsumerUrl(), invocation); // 进行路由
} catch (Throwable t) {
logger.error("Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t);
}
return invokers == null ? Collections.emptyList() : invokers;
}
org.apache.dubbo.rpc.cluster.RouterChain#route:
public List<Invoker<T>> route(URL url, Invocation invocation) {
AddrCache<T> cache = this.cache.get();
...
// 获取属性invokers集合值,这个是本地缓存的invoker集合
BitList<Invoker<T>> finalBitListInvokers = new BitList<>(invokers, false);
// 处理状态路由
for (StateRouter stateRouter : stateRouters) {
if (stateRouter.isEnable()) {
RouterCache<T> routerCache = cache.getCache().get(stateRouter.getName());
finalBitListInvokers = stateRouter.route(finalBitListInvokers, routerCache, url, invocation);
}
}
List<Invoker<T>> finalInvokers = new ArrayList<>(finalBitListInvokers.size());
for(Invoker<T> invoker: finalBitListInvokers) {
finalInvokers.add(invoker);
}
// 处理普通路由
for (Router router : routers) {
finalInvokers = router.route(finalInvokers, url, invocation);
}
return finalInvokers;
}
我们重点看一下 条件路由的处理:org.apache.dubbo.rpc.cluster.router.condition.ConditionRouter#route:
public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation)
throws RpcException {
// 若router不可用,则直接返回所有invoker
if (!enabled) {
return invokers;
}
if (CollectionUtils.isEmpty(invokers)) {
return invokers;
}
try {
// matchWhen() 判断当前消费者与条件规则中前半部分(规则体中=>之前的内容)是否匹配
// 若没有匹配上,则返回所有invoker
if (!matchWhen(url, invocation)) {
return invokers;
}
// 用于存放过滤出的invoker
List<Invoker<T>> result = new ArrayList<Invoker<T>>();
// thenCondition 代表规则体=>之后的表达式
// 若该表达式为空,说明当前路由规则为黑白名单
if (thenCondition == null) {
logger.warn("The current consumer in the service blacklist. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey());
return result;
}
// 代码走到这里,说明thenCondition不为null
// matchThen() 用于判断invoker与thenCondition是否匹配
// 遍历所有invoker,查找到所有可用的invoker
for (Invoker<T> invoker : invokers) {
if (matchThen(invoker.getUrl(), url)) {
result.add(invoker);
}
}
if (!result.isEmpty()) {
return result;
} else if (this.isForce()) {
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;
}
就是 按照我们配置的各种条件进行过滤匹配,然后返回可用的Invoker集合。
总结一下就是,在服务消费端应用中,每个需要消费的服务都被包装为ReferenceConfig , 在应用启动时会调用每个服务对应的 ReferenceConfig 的 get()方法, 然后会为每个服务创建一个自己的 RegistryDirectory 对象,每个 RegistryDirectory 管理该服务提供者的地址列表、路由规则、动态配置等信息,当服务提供者的信息发生变化时,Regist叩Directory 会动态地得到变化通知,并自动更新。
下篇文章,我们看一下服务降级。