源码分析Dubbo配置规则机制(override协议)

编程

在上篇在讲解RegistryDirectory的时候,dubbo管理员可以通过dubbo-admin管理系统在线上修改dubbo服务提供者的参数,最终将存储在注册中心的configurators catalog,然后通知RegistryDirectory更新服务提供者的URL中相关属性,按照最新的配置,重新创建Invoker并销毁原来的Invoker。

有关官方文档关于动态改变配置(override协议)的详细描述如下:

dubbo-admin 管理后台,界面如下:

当Dubbo管理人员在上述界面,选择配置后点击保存,会构建override:// url存入到注册中心(configurators) catalog下,此时基于注册中心发现服务提供者的监听器(RegistryDirectory)会收到回调(notify)方法,接下来我们再来看一下RegistryDirectory#notify方法。

RegistryDirectory#notify

public synchronized void notify(List<url> urls) {        // @1

List<url> invokerUrls = new ArrayList<url>();

List<url> routerUrls = new ArrayList<url>();

List<url> configuratorUrls = new ArrayList<url>();

for (URL url : urls) {

String protocol = url.getProtocol();

String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);

if (Constants.ROUTERS_CATEGORY.equals(category)

|| Constants.ROUTE_PROTOCOL.equals(protocol)) {

routerUrls.add(url);

} else if (Constants.CONFIGURATORS_CATEGORY.equals(category)

|| Constants.OVERRIDE_PROTOCOL.equals(protocol)) {

configuratorUrls.add(url);

} else if (Constants.PROVIDERS_CATEGORY.equals(category)) {

invokerUrls.add(url);

} else {

logger.warn("Unsupported category " + category + " in notified url: " + url + " from registry " + getUrl().getAddress() + " to consumer " +

NetUtils.getLocalHost());

}

}

// configurators

if (configuratorUrls != null &amp;&amp; configuratorUrls.size() &gt;0 ){ // @2

this.configurators = toConfigurators(configuratorUrls);

}

// routers

if (routerUrls != null &amp;&amp; routerUrls.size() &gt;0 ){

List<router> routers = toRouters(routerUrls);

if(routers != null){ // null - do nothing

setRouters(routers);

}

}

List<configurator> localConfigurators = this.configurators; // local reference

// 合并override参数

this.overrideDirectoryUrl = directoryUrl;

if (localConfigurators != null &amp;&amp; localConfigurators.size() &gt; 0) {

for (Configurator configurator : localConfigurators) {

this.overrideDirectoryUrl = configurator.configure(overrideDirectoryUrl);

}

}

// providers

refreshInvoker(invokerUrls); // @3

}

由于这个方法的实现在上一篇文章《源码分析Dubbo服务发现机制(RegistryDirectory)》中详细分析,故这里只列出与本文章相关的关注点:

代码@1:参数为当前configurators目录下所有的URL,例如:

urls: [override://0.0.0.0/com.wuys.frame.api.service.IUserService?category=configurators&amp;dynamic=false&amp;enabled=true&amp;timeout=10000, override://0.0.0.0/com.wuys.frame.api.service.IUserService?category=configurators&amp;dynamic=false&amp;enabled=true&amp;weight=200]。

代码@2:将override url转换为List< Configurator>,是本节重点要讨论的内容。

代码@3:调用refreshInvoker方法,由于这里的invokerUrls为空,此时会对原先的invoker马上应用新的配置参数吗?带着这个疑问,我们先看一下refreshInvoker是如何处理的,然后回头重点分析代码@2的实现细节。

关于refreshInvoker的实现,在上一篇源码分析Dubbo服务注册与发现机制RegistryDirectory)中也详细分析过,这里只是为了求证一下:

if (invokerUrls.size() == 0 &amp;&amp; this.cachedInvokerUrls != null){

invokerUrls.addAll(this.cachedInvokerUrls);

} else {

this.cachedInvokerUrls = new HashSet<url>();

this.cachedInvokerUrls.addAll(invokerUrls);//缓存invokerUrls列表,便于交叉对比

}

if (invokerUrls.size() ==0 ){

return;

}

// 省略部分代码

}

从这里看出,如果invokerUrls为空,如果已缓存的服务提供者不为空,则将已缓存的服务提供者加入到invokerUrls中,此时invokerUrls不为空,则会重新用新的配置生成新的invoker,然后销毁原先的invoker。

接下来重点分析Dubbo关于override协议的解析实现细节。

1、dubbo关于override类图

  1. Configurator:协议配置接口,主要抽象出两个接口方法:

  • URL getUrl():获取配置URL。
  • URL configure(URL url):根据configureUrl来配置 URL url。

  1. AbstractConfigurator:协议配置抽象实现类(模板类)。
  2. AbsentConfigurator:absent配置器,其策略是,如果configureUrl存在的属性,则不覆盖。
  3. OverrideConfigurator:override配置器,其策略是,直接覆盖属性。

2、源码分析OverrideConfigurator实现原理

2.1 源码分析AbstractConfigurator#configure

AbstractConfigurator#configure

  1. Configurator:协议配置接口,主要抽象出两个接口方法:

  • URL getUrl():获取配置URL。
  • URL configure(URL url):根据configureUrl来配置 URL url。

  1. AbstractConfigurator:协议配置抽象实现类(模板类)。
  2. AbsentConfigurator:absent配置器,其策略是,如果configureUrl存在的属性,则不覆盖。
  3. OverrideConfigurator:override配置器,其策略是,直接覆盖属性。

3、源码分析OverrideConfigurator实现原理

3.1 源码分析AbstractConfigurator#configure

AbstractConfigurator#configure

public URL configure(URL url) {

if (configuratorUrl == null || configuratorUrl.getHost() == null

|| url == null || url.getHost() == null) { // @1

return url;

}

if (configuratorUrl.getPort() != 0) { // @2

if (url.getPort() == configuratorUrl.getPort()) {

return configureIfMatch(url.getHost(), url); // @3

}

} else {

if (url.getParameter(Constants.SIDE_KEY, Constants.PROVIDER).equals(Constants.CONSUMER)) { // @4

return configureIfMatch(NetUtils.getLocalHost(), url);// NetUtils.getLocalHost is the ip address consumer registered to registry.

} else if (url.getParameter(Constants.SIDE_KEY, Constants.CONSUMER).equals(Constants.PROVIDER)) { // @5

return configureIfMatch(Constants.ANYHOST_VALUE, url);

}

}

return url;

}

代码@1:如果configuratorUrl (配置URL)为空host为空,或url为空或host为空,则返回url。这里参数的覆盖方向 configuratorUrl ----> url。

代码@2:如果configuratorUrl如果端口不为空,则需要判断url的端口,端口必须相同,才执行configuratorUrl配置url。

代码@3,执行具体的配置操作,下文待分析。

代码@4、@5:如果端口为空,该配置URL(configuratorUrl)的类型要么是针对消费者,要么地址是0.0.0.0(任意)。

如果url属于服务消费者,host为消费者的注册IP地址,如果是服务提供者,则host为0.0.0.0来配置。

3.2 源码分析AbstractConfigurator#configureIfMatch

private URL configureIfMatch(String host, URL url) {

if (Constants.ANYHOST_VALUE.equals(configuratorUrl.getHost()) || host.equals(configuratorUrl.getHost())) {

String configApplication = configuratorUrl.getParameter(Constants.APPLICATION_KEY,

configuratorUrl.getUsername());

String currentApplication = url.getParameter(Constants.APPLICATION_KEY, url.getUsername());

if (configApplication == null || Constants.ANY_VALUE.equals(configApplication)

|| configApplication.equals(currentApplication)) {

Set<string> condtionKeys = new HashSet<string>();

condtionKeys.add(Constants.CATEGORY_KEY);

condtionKeys.add(Constants.CHECK_KEY);

condtionKeys.add(Constants.DYNAMIC_KEY);

condtionKeys.add(Constants.ENABLED_KEY);

for (Map.Entry<string, string> entry : configuratorUrl.getParameters().entrySet()) {

String key = entry.getKey();

String value = entry.getValue();

if (key.startsWith("~") || Constants.APPLICATION_KEY.equals(key) || Constants.SIDE_KEY.equals(key)) {

condtionKeys.add(key);

if (value != null &amp;&amp; !Constants.ANY_VALUE.equals(value)

&amp;&amp; !value.equals(url.getParameter(key.startsWith("~") ? key.substring(1) : key))) {

return url;

}

}

}

return doConfigure(url, configuratorUrl.removeParameters(condtionKeys));

}

}

return url;

}

该方法主要实现的功能就是排除不能动态修改的属性,不支持属性主要包括:category、check、dynamic、enabled、还有以~开头的属性,并且如果~开头的属性,配置URL与原URL的值不相同,则不使用该配置URL重写原URL。将配置URL(configuratorUrl)移除不支持属性后,调用其子类的doConfigure方法覆盖属性,Dubbo默认支持如下覆盖策略

  • override 直接覆盖。
  • absent,如果原先存在该属性的配置,则以原先配置的属性值优先,如果原先没有配置该属性,则添加新的配置属性。

总结一下:当在dubbo-admin(管理后台)中创建一条override规则后,会首先存储在注册中心(zookeeper的指定目录下${service}/configurators目录下,此时基于注册中心的事件机制,会通知相关监听者(服务消费者),服务消费者收到最新的配置时,会根据最新的配置重新构建Invoker对象,然后销毁原先的Invoker对象。


作者介绍:丁威,《RocketMQ技术内幕》作者,RocketMQ 社区布道师,公众号:中间件兴趣圈 维护者,目前已陆续发表源码分析Java集合、Java 并发包(JUC)、Netty、Mycat、Dubbo、RocketMQ、Mybatis等源码专栏。可以点击链接:中间件知识星球,一起探讨高并发、分布式服务架构,交流源码。

</string,></string></string></url></configurator></router></url></url></url></url></url></url></url>

以上是 源码分析Dubbo配置规则机制(override协议) 的全部内容, 来源链接: utcz.com/z/512248.html

回到顶部