Dubbo 配置 Loadbalance 不生效?撸一把源码

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

[[432923]]

文末本文转载自微信公众号「捉虫大师」,作者捉虫大师 。转载本文请联系捉虫大师公众号。

背景

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

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

排查

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

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

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

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

  1. <dubbo:consumer loadbalance="xlb"/> 

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

  1. 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

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

  1. <!-- case 1 --> 
  2. <dubbo:consumer /> 
  3. <dubbo:consumer loadbalance="xlb"/> 
  4.  
  5. <!-- case 2 --> 
  6. <dubbo:consumer loadbalance="xlb"/> 
  7. <dubbo:consumer /> 

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

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

撸源码

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

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

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

加载代码如下

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

带缓存的加载扩展

  1. public T getExtension(String name) { 
  2.     if (StringUtils.isEmpty(name)) { 
  3.         throw new IllegalArgumentException("Extension name == null"); 
  4.     } 
  5.     if ("true".equals(name)) { 
  6.         return getDefaultExtension(); 
  7.     } 
  8.     final Holder<Object> holder = getOrCreateHolder(name); 
  9.     Object instance = holder.get(); 
  10.     if (instance == null) { 
  11.         synchronized (holder) { 
  12.             instance = holder.get(); 
  13.             if (instance == null) { 
  14.                 instance = createExtension(name); 
  15.                 holder.set(instance); 
  16.             } 
  17.         } 
  18.     } 
  19.     return (T) instance; 

可以看出

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

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

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

  1. private URL mergeUrl(URL providerUrl) { 
  2.     // 1. merge consumer 参数 
  3.     providerUrl = ClusterUtils.mergeUrl(providerUrl, queryMap);  
  4.     // 2. merge configurator 参数 
  5.     providerUrl = overrideWithConfigurator(providerUrl); 
  6.     ... 
  7.     return providerUrl; 

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

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

  1. public synchronized void init() { 
  2.     ... 
  3.     checkAndUpdateSubConfigs(); 
  4.     ... 
  5.     AbstractConfig.appendParameters(map, consumer); 
  6.     ... 
  7.  
  8. // 2 
  9. public void checkAndUpdateSubConfigs() { 
  10.     ... 
  11.     checkDefault(); 
  12.     ... 
  13.  
  14. // 3 
  15. public void checkDefault() throws IllegalStateException { 
  16.     if (consumer == null) { 
  17.         consumer = ApplicationModel.getConfigManager() 
  18.                 .getDefaultConsumer() 
  19.                 .orElse(new ConsumerConfig()); 
  20.     } 
  21.  
  22. // 4 
  23. public Optional<ConsumerConfig> getDefaultConsumer() { 
  24.     List<ConsumerConfig> consumerConfigs = getDefaultConfigs(getConfigsMap(getTagName(ConsumerConfig.class))); 
  25.     if (CollectionUtils.isNotEmpty(consumerConfigs)) { 
  26.         return Optional.of(consumerConfigs.get(0)); 
  27.     } 
  28.     return Optional.empty(); 

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

总结

每配置一个 consumer ,无论是从 xml 文件,或是 spring-boot 配置,或是 api 直接创建,都会生成一个 consumerConfig 对象

当消费接口,即配置 reference 时,会将 consumer 的参数 merge 过来,如果存在多个 consumer,会挑第一个,当然我们并不知道谁先加载

当 reference 存在 consumer 的配置时,注册中心通知的 provider urls 会和 reference 的参数进行合并,合并后生成可调用的 invoker

 

 

 

对于 loadbalance 来说,调用时,如果 invokers 非空,则会尝试通过第一个 invoker 的 loadbalance 参数加载负载均衡算法,第一次调用进行加载,后续调用则使用缓存

 

责任编辑:武晓燕 来源: 捉虫大师
相关推荐

2023-06-21 08:24:46

2020-03-02 17:04:47

戴尔

2011-05-04 17:11:12

打印机

2011-11-08 08:14:40

WLANWi-Fi

2024-01-04 14:16:05

腾讯红黑树Socket

2017-06-14 17:39:40

微服务架构服务器

2010-10-28 08:43:07

App StoreSymbian

2024-02-22 17:08:03

腾讯架构RocketMQ

2019-06-12 15:20:25

Redis高性能线程

2022-10-25 15:25:22

网关并行Flowable

2011-04-08 15:15:34

策略路由

2021-08-18 23:10:56

setState代码性能

2009-02-23 19:07:14

SaaS800APP八百客

2018-03-23 08:26:44

Hadoop集群SQL

2024-04-18 08:00:23

openInula​React响应式 API

2022-07-20 08:55:02

区块链技术数据记录

2011-04-08 15:28:48

2019-10-30 05:51:07

物联网设备物联网安全物联网

2014-11-26 15:05:16

魅族MX4Pro

2021-06-03 08:01:12

JVM性能优化
点赞
收藏

51CTO技术栈公众号