一、覆盖规则概念
覆盖规则是Dubbo设计的在无需重启应用的情况下,动态调整RPC调用行为的一种能力。2.7.0版本开始,支持从服务和应用两个粒度来调整动态配置。
二、覆盖规则原理
从2.7.0版本开始,覆盖规则主要依靠一个动态配置类和四个监听器来完成。目前支持得动态配置组件有:

大家可以参考Dubbo是如何利用Apollo和Zookeeper实现节点的监听的。

ServiceConfigurationListener、ProviderConfigurationListener在服务发布的时候被初始化。ConsumerConfigurationListener、ReferenceConfigurationListener在服务引用的时候被初始化。
- 当Provider的Url发生变化的时候会通过监听器监听到服务的URL发生的变化,如若和注册中心的不一致,就重新暴露服务。
-> RegistryProtocol.ServiceConfigurationListener#notifyOverrides() -> RegistryProtocol.OverrideListener#doOverrideIfNecessary() -> RegistryProtocol#reExport() // 重新暴露服务 - 当Consumer的Url发生变化的时候监听器监听到变化后,会通过服务字典重新刷新Invoker列表。
-> RegistryDirectory.ReferenceConfigurationListener#notifyOverrides() -> RegistryDirectory#refreshInvoker()
三、如何书写覆盖规则
我们只要按照Dubbo文档提供的来配置就可以了,但是有几个地方是Dubbo文档中没有写明的,我们还需要注意一下。
public synchronized void doOverrideIfNecessary() {
final Invoker<?> invoker;
if (originInvoker instanceof InvokerDelegate) {
invoker = ((InvokerDelegate<?>) originInvoker).getInvoker();
} else {
invoker = originInvoker;
}
// 已经注册服务的URL
URL originUrl = RegistryProtocol.this.getProviderUrl(invoker);
String key = getCacheKey(originInvoker);
ExporterChangeableWrapper<?> exporter = bounds.get(key);
if (exporter == null) {
logger.warn(new IllegalStateException("error state, exporter should not be null"));
return;
}
//The current, may have been merged many times
URL currentUrl = exporter.getInvoker().getUrl();
//Merged with this configuration
// 兼容旧版本
URL newUrl = getConfigedInvokerUrl(configurators, originUrl);
// 获取服务粒度配置
newUrl = getConfigedInvokerUrl(serviceConfigurationListeners.get(originUrl.getServiceKey())
.getConfigurators(), newUrl);
// 获取应用粒度配置
newUrl = getConfigedInvokerUrl(providerConfigurationListener.getConfigurators(), newUrl);
if (!currentUrl.equals(newUrl)) {
RegistryProtocol.this.reExport(originInvoker, newUrl);
logger.info("exported provider url changed, origin url: " + originUrl +
", old export url: " + currentUrl + ", new export url: " + newUrl);
}
}
-> RegistryProtocol#getConfigedInvokerUrl
-> AbstractConfigurator#configure
public URL configure(URL url) {
// If override url is not enabled or is invalid, just return.
if (!configuratorUrl.getParameter(Constants.ENABLED_KEY, true) || configuratorUrl.getHost() == null || url == null || url.getHost() == null) {
return url;
}
// 获取apiVersion
String apiVersion = configuratorUrl.getParameter(Constants.CONFIG_VERSION_KEY);
if (StringUtils.isNotEmpty(apiVersion)) {
// 获取当前url side
String currentSide = url.getParameter(Constants.SIDE_KEY);
// 获取变更url side
String configuratorSide = configuratorUrl.getParameter(Constants.SIDE_KEY);
// 变更url的端口号为[0],变更所有消费者实例
if (currentSide.equals(configuratorSide) && Constants.CONSUMER.equals(configuratorSide) && 0 == configuratorUrl.getPort()) {
url = configureIfMatch(NetUtils.getLocalHost(), url);
// 根据端口号变更服务提供者
} else if (currentSide.equals(configuratorSide) && Constants.PROVIDER.equals(configuratorSide) && url.getPort() == configuratorUrl.getPort()) {
url = configureIfMatch(url.getHost(), url);
}
}
/**
* This else branch is deprecated and is left only to keep compatibility with versions before 2.7.0
*/
else {
url = configureDeprecated(url);
}
return url;
}
四、总结
-
不同的规则以不同的key后缀区分:
- configurators - 覆盖规则
- tag-router - 标签路由
- condition-router - 条件路由
-
如果服务进行了分组和版本区分:
key: group*service:version.configurators -
2.7.0版本以后配置内容必须添加:
configVersion: v2.7 -
Provider必须指定端口号指令实例
-
Consumer作用于所有实例
建议直接配置
addresses : [0.0.0.0]