1.概要
当消费者请求时,首先有路由规则遴选出符合条件的服务提供者。然后在这些服务提供者之中应用负载均衡进一步选择。
2.dubbo路由规则配置
目前dubbo支持的路由类型:conditon(条件)、script(脚本)和tag(标签)
这里简介一下条件路由类型的配置方式:通过硬编码方式和在dubbo admin上进行配置
2.1 硬编码方式
// 获取注册中心的扩展类
RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.c
lass).getAdaptiveExtension();
// 获取注册中心,这里使用zk
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://10.20.153.10:
2181"));
// 注册中心上配置规则
registry.register(URL.valueOf("condition://0.0.0.0/com.foo.BarService?category=routers
&dynamic=false&rule=" + URL.encode("host = 10.23.151.11 => host = 10.23.151.12") + "));
在服务调用前,执行以上代码即可实现dubbo路由规则配置。向注册中心写入路由规则的操作通常由监控中心或治理中心的页面完成。
因为在服务调用时先经过路由规则的过滤,再经过负载均衡的过滤,参考AbstractClusterInvoker的invoke方法
2.2 dubbo admin方式
在dubbo admin上进行页面配置,dubbo admin的开源地址:https://github.com/apache/incubator-dubbo-admin,如下图示例:
3. dubbo路由通知及规则解析
3.1 dubbo路由配置原理
首先明确一下refresh Invokers时机有三个,在refresh invokers之前会加载路由:
- 时机一:当消费者进行启动时refer服务接口时,会订阅zk的结点变化动作,并notify对应的listener,源码流程如下:
RegistryProtocol的doRefer方法 -> RegistryDirectory的subscribe方法 -> FailbackRegistry的subscribe方法 -> ZookeeperRegistry的doSubscribe方法
时机二:Dubbo里的zookeeper Client和Server重新连接上时。注册中心在创建的时候启动了一个定时任务,默认每5秒尝试注册失败的重新注册、订阅失败的重新订阅、取消注册失败的重新取消注册、取消订阅失败的重新取消订阅。另外,注册中心在创建的时候开启了对zk状态的监听,当Dubbo里的zookeeper Client和Server重新连接上时,dubbo就会将之前注册的的URL添加入这几个失败集合中,然后上述定时任务就可以进行重新注册了,包括路由的加载。
参考ZookeeperRegistry的初始化过程
时机三:当listener监听的zk结点发生变化时,会通知刷新invoker列表。在消费端启动时在每个dubbo分类下(如consumers, configurators, routers, providers)都设置了监听器,详情参考ZookeeperRegistry的doSubscribe方法,代码片断如下:
if (zkListener == null) { listeners.putIfAbsent(listener, new ChildListener() { public void childChanged(String parentPath, List<String> currentChilds) { ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds)); }}); zkListener = listeners.get(listener); }
即最终会调用RegistryDirectory中的notify方法,源码如下:
@Override
public synchronized void notify(List<URL> urls) {
// url列表按照 configurators、routers、providers进行分组
Map<String, List<URL>> categoryUrls = urls.stream()
.filter(Objects::nonNull)
.filter(this::isValidCategory)
.filter(this::isNotCompatibleFor26x)
.collect(Collectors.groupingBy(url -> {
if (UrlUtils.isConfigurator(url)) {
return CONFIGURATORS_CATEGORY; // "configurators"
} else if (UrlUtils.isRoute(url)) {
return ROUTERS_CATEGORY; // "routers"
} else if (UrlUtils.isProvider(url)) {
return PROVIDERS_CATEGORY; // "providers"
}
return "";
}));
List<URL> configuratorURLs = categoryUrls.getOrDefault(CONFIGURATORS_CATEGORY, Collections.emptyList());
this.configurators = Configurator.toConfigurators(configuratorURLs).orElse(this.configurators);
List<URL> routerURLs = categoryUrls.getOrDefault(ROUTERS_CATEGORY, Collections.emptyList());
toRouters(routerURLs).ifPresent(this::addRouters);
// 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<>();
for (URL url : urls) {
// 若url的protocol是”empty“,跳到下一次循环
if (Constants.EMPTY_PROTOCOL.equals(url.getProtocol())) {
continue;
}
// 获取url的”router“参数值,并设置到protocol中
String routerType = url.getParameter(Constants.ROUTER_KEY);
if (routerType != null && routerType.length() > 0) {
url = url.setProtocol(routerType);
}
try {
// 加入到路由列表中并返回该列表
Router router = routerFactory.getRouter(url);
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);
}