背景

很久之前我给业务方写了一个 dubbo loadbalance 的扩展(为了叙述方便,这个 loadbalance 扩展就叫它 XLB 吧),这两天业务方反馈说 XLB 不生效了

我心想,不可能啊,都用了大半年了~

排查

于是我登上不生效的 consumer 机器进行排查,还好我留了一手,当 XLB 加载时,会打印一行日志

看了下这个服务,并没有打印日志,说明 XLB 并没有加载成功

于是,我就去问对应的开发,有按照我的文档配置 loadbalance 吗?答复:完全按照文档配置

这下我就有点不相信了,但转念一想,配置 loadbalance 如此简单,不应该出错啊,我的文档和他的应用都在 xml 文件中配置了 consumer 的 loadbalance

<dubbo:consumer loadbalance="xlb"/>

抱着试一试的态度,拉取了他们项目的代码,发现配置确实如上,但我发现他们的 application.properties 配置文件也配了一个 consumer 的属性

dubbo.consumer.check=false

以多年和 dubbo 打交道的经验来说,这里有问题,又确认了代码,确实 xml 和 application.properties 都加载了

那这里可能就有问题了,dubbo 从 xml 加载生成了一个 consumer 配置,dubbo-springboot-starter 又从 application.properties 加载配置生成了一个 consumer 配置,这不就冲突了?

别看只配置了 dubbo.consumer.check,它实际上会生成一个完整的 consumer 配置,只不过 loadbalance 为默认值

业务方为什么会这样配置?大概率是因为我的文档里只给出了 xml 形式的配置,没有给 spring-boot 配置,他们原先使用的是 spring-boot 的配置方式,然后看到我的文档是 xml,结果就不会配置了,也写了个 xml,和原先的配置冲突

验证

为了验证是这个问题导致,我把他的 application.properties 的 dubbo.consumer.check 配置挪到了 xml 文件中,果然重启后就加载到了 XLB

随后我又在本地的测试应用上做了这样一个验证:

<!-- case 1 -->
<dubbo:consumer />
<dubbo:consumer loadbalance="xlb"/> <!-- case 2 -->
<dubbo:consumer loadbalance="xlb"/>
<dubbo:consumer />

两组配置相同,但顺序不同,测试结果为 case 1 可以加载到 XLB,case 2 不行

于是猜测,dubbo consumer 配置以后加载的为准

撸源码

显然猜测不符合我的风格,下面开撸源码,不感兴趣可以划过,最下面有总结

首先搞清楚,何时会加载 loadbalance,在 AbstractClusterInvokerinvoke 方法中,加载了 loadbalance

@Override
public Result invoke(final Invocation invocation) throws RpcException {
...
List<Invoker<T>> invokers = list(invocation);
LoadBalance loadbalance = initLoadBalance(invokers, invocation);
RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
return doInvoke(invocation, invokers, loadbalance);
}

加载代码如下

protected LoadBalance initLoadBalance(List<Invoker<T>> invokers, Invocation invocation) {
if (CollectionUtils.isNotEmpty(invokers)) {
return ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl()
.getMethodParameter(RpcUtils.getMethodName(invocation), LOADBALANCE_KEY, DEFAULT_LOADBALANCE));
} else {
return ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(DEFAULT_LOADBALANCE);
}
}

带缓存的加载扩展

public T getExtension(String name) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
return getDefaultExtension();
}
final Holder<Object> holder = getOrCreateHolder(name);
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}

可以看出

  • loadbalance 是发起 dubbo 调用时,且当 invokers 非空时(即 providers 非空)会被初始化,后续都从缓存中取
  • loadbalance 是根据第一个 invoker 的 loadbalance 参数决定使用哪个 loadbalance 的

于是问题转移到 invoker 的 loadbalance 从哪来?provider 不会配置 loadbalance,所以这个参数一定是从 consumer 的配置上得到的

顺藤摸瓜,在 RegistryDirectorytoInvokers 方法中调用了 mergeUrl,它是在注册中心通知时被调用,也就是从注册中心上拿到 provider url 时,还得 merge 一下才能用,merge 了些什么内容?

private URL mergeUrl(URL providerUrl) {
// 1. merge consumer 参数
providerUrl = ClusterUtils.mergeUrl(providerUrl, queryMap);
// 2. merge configurator 参数
providerUrl = overrideWithConfigurator(providerUrl);
...
return providerUrl;
}

1中 merge 了queryMap 里的参数,这个queryMap 其实就是 consumer 的参数,它来自配置的 reference

再看 reference 配置,当 ReferenceConfig 初始化时

// 1
public synchronized void init() {
...
checkAndUpdateSubConfigs();
...
AbstractConfig.appendParameters(map, consumer);
...
} // 2
public void checkAndUpdateSubConfigs() {
...
checkDefault();
...
} // 3
public void checkDefault() throws IllegalStateException {
if (consumer == null) {
consumer = ApplicationModel.getConfigManager()
.getDefaultConsumer()
.orElse(new ConsumerConfig());
}
} // 4
public Optional<ConsumerConfig> getDefaultConsumer() {
List<ConsumerConfig> consumerConfigs = getDefaultConfigs(getConfigsMap(getTagName(ConsumerConfig.class)));
if (CollectionUtils.isNotEmpty(consumerConfigs)) {
return Optional.of(consumerConfigs.get(0));
}
return Optional.empty();
}

上面调用链从 1 到 44 中获取了第1个 consumer,这就是我们要找的根源

总结

  • 每配置一个 consumer ,无论是从 xml 文件,或是 spring-boot 配置,或是 api 直接创建,都会生成一个 consumerConfig 对象
  • 当消费接口,即配置 reference 时,会将 consumer 的参数 merge 过来,如果存在多个 consumer,会挑第一个,当然我们并不知道谁先加载
  • 当 reference 存在 consumer 的配置时,注册中心通知的 provider urls 会和 reference 的参数进行合并,合并后生成可调用的 invoker
  • 对于 loadbalance 来说,调用时,如果 invokers 非空,则会尝试通过第一个 invoker 的 loadbalance 参数加载负载均衡算法,第一次调用进行加载,后续调用则使用缓存

搜索关注微信公众号"捉虫大师",后端技术分享,架构设计、性能优化、源码阅读、问题排查、踩坑实践。

dubbo 配置 loadbalance 不生效?撸一把源码的更多相关文章

  1. Dubbo加权轮询负载均衡的源码和Bug,了解一下?

    本文是对于Dubbo负载均衡策略之一的加权随机算法的详细分析.从2.6.4版本聊起,该版本在某些情况下存在着比较严重的性能问题.由问题入手,层层深入,了解该算法在Dubbo中的演变过程,读懂它的前世今 ...

  2. 配置Eclipse可以查看JDK类库源码

    一.配置方法 配置Eclipse可以查看JDK类库源码 Window->Preferences->Java->Installed JREs 若没有JRE,需要自己添加进来,有的话,点 ...

  3. Dubbo一致性哈希负载均衡的源码和Bug,了解一下?

    本文是对于Dubbo负载均衡策略之一的一致性哈希负载均衡的详细分析.对源码逐行解读.根据实际运行结果,配以丰富的图片,可能是东半球讲一致性哈希算法在Dubbo中的实现最详细的文章了. 文中所示源码,没 ...

  4. ubuntu配置android开发环境和编译源码遇到的一些问题

    ---------------------------------------------环境变量设置--------------------------------------------- 1.设 ...

  5. 聊聊最近撸Spring源码感悟

    一.前言     最近一段时间撸了Spring IOC.AOP.Transactional源码,这篇文章聊聊我写了哪些小玩意,这可能就是阅读源码以后最大收获.希望大家在里面能学习一些什么东西吧: 二. ...

  6. 使用微软企业库5.0提供的unity配置解藕系统demo(源码)

    最近公司集50多号开发人员的人力围绕一个系统做开发,框架是免不了要统一的,公司提供的架构,利于分工合作,便于维护,扩展,升级,其中使用了到微软的企业库来解藕系统,只是因为框架封装,于是在网上学习了一个 ...

  7. SpringtMVC中配置 <mvc:annotation-driven/> 与 <mvc:default-servlet-handler/> 源码解析

    上一篇有提到,当有.无这两个标签时,SpringtMVC 底层所采用的  HandlerMapping 以及 HandlerAdapter 是不一样的.现在就来进行源码调试,揭开 SpringtMVC ...

  8. MySQL主从复制配置指导及PHP读写分离源码分析

    开发环境 master环境:ubuntu16.04.5LTS/i5/8G/500G/64位/mysql5.7.23/php7/apache2 slave环境:kvm虚拟机/ubuntu14.04.01 ...

  9. C++STL内存配置的设计思想与关键源码分析

    说明:我认为要读懂STL中allocator部分的源码,并汲取它的思想,至少以下几点知识你要了解:operator new和operator delete.handler函数以及一点模板知识.否则,下 ...

随机推荐

  1. Linux系列(13) - CentOs 8 配置静态IP

    step-1 vim etho的配置文件 [root#localhost ~]# vim /etc/sysconfig/network-scripts/ifcfg-eth0 step-2 新增修改以下 ...

  2. Jmeter系列(27)- 常用逻辑控制器(6) | 如果(if)控制器If Controller

    如果(if)控制器(If Controller) 在实际工作中,当使用JMeter做性能脚本或者接口脚本时,当遇到需要对不同的条件做不同的操作时,我们可以使用JMeter中if控制器来实现 if控制器 ...

  3. 测试验收标准checklist

    需求实现功能清单 功能实现目的 需求改造功能清单 关联功能清单 关联系统 端到端全流程场景 业务联系性场景 业务全流程场景 上下需求关联规则 业务角度在流程中关注项 财报.评级 授信方案 反洗钱 面向 ...

  4. django forms的常用命令及方法(一)

    根据别人网上发布,个人爱好收集 Form表单的功能 自动生成HTML表单元素 检查表单数据的合法性 如果验证错误,重新显示表单(数据不会重置) 数据类型转换(字符类型的数据转换成相应的Python类型 ...

  5. 腾讯的表妹告诉我怎么学Python,今天就教我搭建Python环境和基本语法,我【码上开始】

    本文首发公众号:码上开始 环境准备 Pycharm Python3 window10/win7 安装 Python 打开Python官网地址 下载 executable installer,x86 表 ...

  6. 深入浅出WPF-08.Event( 事件)01

    事件(Event) 首先我们来继续说一下UI组件树,因为WPF事件 的路由环境就是组件树.WPF中的树有两种,一种是逻辑树(Logical Tree),一种是可视元素树(Visual Tree).逻辑 ...

  7. node ***.js或npm run scripts的脚本命令出现Cannot find module 'react-dev-utils/getPublicUrlOrPath'报错的解决办法

    出现类似Cannot find module 'react-dev-utils/getPublicUrlOrPath'一般是项目中没有下载报错中提到的模块(可以在项目中package.json文件de ...

  8. 11.4.4 LVS-Fullnat

    lvs-fullnat(双向转换) 通过请求报文的源地址为DIP,目标为RIP来实现转发:对于响应报文而言,修改源地址为VIP,目标地址为CIP来实现转发: CIP --> DIP VIP -- ...

  9. mysql通过logstash同步数据到es

    大小写问题很严重 input 1.statement:mysql的连接使用 jdk版本有强要求 2.jdbc_driver_library:jar包的版本有对应要求 3.jdbc_driver_cla ...

  10. mybatis-plus最新版代码生成器(Swagger3)

    写项目想用mybatis-plus+swagger3,百度最新版代码生成器都是旧版的,且官网的配置过于简洁,所以手敲一份,在官网的基础上加了一堆配置,lombok,restful,mvc三层结构目录等 ...