版本
springboot :2.1.7.RELEASE
dubbo:2.7.23
curator-framework:4.0.1
背景:工作中遇到一个奇葩的需要,要求Consumer 根据参数的不同选择调用不同的group。闲话少活,下面分享下解决过程。
前提:springboot初始化时 @Reference注解的处理。简单说下,@Reference 是在初始化过程中注入进来的,在使用类的bean 初始化过程中会生成@Reference 的动态代理类,代理类生成完成后会注入进使用类里,这个动态代理类里面存储了提供者的抽象invoker列表,封装了一系列的参数。入口在bean的属性注入方法
后面依次调用会走到代理类的创建方法
生成代理的时候,代理类会组合继承一些列功能,例如降级,容错,负载均衡,期间大量采用的dubbo的spi机制(大部分场景都可以通过spi机制定制化或者扩展功能)。同时从zk获取提供者的信息,设置zk节点变动的监听,并组装成invoker 类都是在这个时候。
刷新并获取invoker
从zk上创建节点,并获取子节点信息,也就是提供者的信息
铺垫的差不多了,下面开始具体的解决过程
猜想1:dubbo加载的时候是不是会获取所有group 的提供者?
dubbo 确实会获取所有提供者,但是组装url信息的时候会根据消费者信息过滤提供者的url,比如消费者配置的group 信息、版本信息、category信息等,因此在这个时候就已经把group给过滤掉了
猜想不成立了,能不能解决呢?还真能,dubbo有一种可合并的集群策略,具体实现类是MergeableClusterInvoker , 这个类会拿到所有 Invoker 返回的结果对象之后,会遍历每个返回结果,并调用 merger 参数指定的方法,合并这些结果值.这个功能可以通过在group的属性上设置 group='*',或者group ='a,b' 来拿到多个分组的提供者,经过验证确实可以拿到所有的提供者了。
猜想2:现在提供者拿到了,能否通过配置负载均衡的策略来实现功能呢?
通过源码和debug发现并不行,如果是单分组,代理类的invoker的包含顺序是这样的 MockClusterInvoker->FailoverClusterInvoker,如果是多分组 代理类的invoker 的顺序是这样的MockClusterInvoker->MergeableClusterInvoker->FailoverClusterInvoker,相应的代码如下
通过这段代码可以(COMMA_SPLIT_PATTERN.split(group)).length > 1 || "*".equals(group)可以知道,当group中有逗号或者* 会走下面这个代码
return doRefer(Cluster.getCluster(MergeableCluster.NAME), registry, type, url, qs);
Cluster.getCluster(MergeableCluster.NAME) 通过spi机制获取到实现类就是MergeableCluster,而单一的group 会走下面的代码,由于没有设置spi的名字,所以会获取默认的实现,就是FailoverCluster
而MergeableClusterInvoker实现类中,会判断是否指定合并策略,如果没有指定,就会选择第一个invoker来执行,因此修改负载均衡的策略也是不可行的,因为这个地方已经把invoker给过滤掉了,而负载均衡策略是在FailoverClusterInvoker 类中生效了,换句话说还没走到负载均衡策略的时候,invoker就已经过滤掉了。
猜想三:能否通过修改MergeableClusterInvoker 类实现功能?
MergeableClusterInvoker 类是通过 MergeableCluster 类生成的,上面已经说过MergeableCluster 是dubbo通过spi机制选择的,这个地方就给了咱们扩展的空间
当代理对象配置的是多分组的时候,这个ExtensionLoader.getExtensionLoader(Cluster.class).getExtension(name, wrap),name 是固定的也就是mergeable,因此新增一种Cluster 的配置方式是行不通,只有通过添加Wrapper 类可以修改这个spi类,然后来返回咱们自己的invoker来实现修改mergeable类,具体代码如下
经过简单的测试,完美运行啦