zipkin(五):zipkin-spring-boot-starter兼容dubbo2.7.6

通过引入zipkin-spring-boot-starter(下文简称zipkin-starter)依赖,小伙伴们可以分分钟让应用集成链路跟踪能力。随着集成链路跟踪的应用数量持续增加,作为中间件依赖的zipkin-starter开始面临的各种框架的兼容性问题。

今天就聊一下dubbo高版本(2.7.5&2.7.6,两个版本兼容性问题相似,下文只对2.7.6进行分析)的兼容性问题:因zipkin-starter默认生成的Config覆盖dubbo自身Config,导致配置文件中的dubboConfig不生效。

现象描述

在使用dubbo2.7.6版本时,发现在配置文件里面配置的dubbo.provider.filter=logFilter没有生效:在dubbo-admin控制台上看注册上来的服务url的filter参数值中没有logFilter,只有zipkin-starter配置的tracing;

问题排查

按照常规debug流程进行打断点:发现DubboConfigProcessor#postProcessBeforeInitialization进入了两个ProviderConfig实例

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (!properties.enable.getDubbo()) {
            return bean;
        }

        if (bean instanceof ProviderConfig) {
            ProviderConfig config = (ProviderConfig)bean;
            return addProviderTracingFilter(config);
        } else if (bean instanceof ConsumerConfig) {
            ConsumerConfig consumerConfig = (ConsumerConfig)bean;
            return addConsumerTracingFilter(consumerConfig);
        } else {
            return bean;
        }
    }

通过实例属性信息判断:有一个实例是zipkin-starter创建的defaultProviderConfig;另外一个看上去是dubbo自己创建的。

这里简单说明一下zipkin-starter为什么要创建providerConfig? 因为zipkin-starter是基于Spring容器的配置初始化工具。如果没有providerConfig bean,那么就无法在dubbo注册URL上加上tracingFilter。所以作为兜底方案,zipkin-starter会利用@ConditionalOnMissingBean创建providerConfig

这里就奇怪了,zipkin-starter中我使用了@ConditionalOnMissingBean注解防止重复创建providerConfig,为什么Spring还是触发了defaultProviderConfig实例的初始化?

继续打断点,发现是org.apache.dubbo.config.spring.ReferenceBean#prepareDubboConfigBeans方法触发了实例的初始化。

    /**
     * 为了提前初始化dubbo所需要的ConfigBean
     * Initializes there Dubbo's Config Beans before @Reference bean autowiring
     */
    private void prepareDubboConfigBeans() {
		....
        beansOfTypeIncludingAncestors(applicationContext, ProviderConfig.class);
        beansOfTypeIncludingAncestors(applicationContext, ConsumerConfig.class);
		....
    }

经排查这个方法从2.7.5开始做了调整,会尝试从SpringContext中获取指定类型的bean的。

  1. 正是这个方法触发了zipkin-starter的defaultProviderConfig的初始化。注意此时dubbo的providerConfig还没有初始化,这就是@ConditionalOnMissingBean注解看上去没有生效的原因!
  2. 等defaultProviderConfig初始化完成以后,紧接着dubbo的providerConfig开始初始化。dubbo的providerConfig也不会每次都会初始化,只有在配置文件中配置了dubbo相关配置(比如dubbo.provider.filter=xxx)才会进行初始化。详见DubboConfigConfiguration

这就造成了SpringContext中有两个providerConfig,而dubbo2.7.6注册provider到注册中心时会默认取第一个provider,导致了zipkin-starter创建的defaultProviderConfig覆盖了dubbo自己创建的providerConfig。

这里大家肯定会问为什么低版本(<2.7.5)的dubbo没有问题?

是的,我也有这个疑问。经过对比高低版本的变化,发现dubbo在调用ListableBeanFactory#getBeansOfType获取指定类型bean时进行了调整:

  1. 低版本(<2.7.5)使用的是getBeansOfType(Class<T> type, boolean includeNonSingletons, boolean allowEagerInit),其中includeNonSingletons和allowEagerInit参数值都是false,也就是可以延迟初始化。这相当于留给@ConditionalOnMissingBean进行判断的时间
  2. 高版本(>=2.7.5)使用的是getBeansOfType(Class<T> type),这个方法不允许延迟加载。会把所有指定类型的bean全部初始化,而恰好zipkin-starter的defaultProviderConfig的BeanDefinition比较靠前,导致被初始化。

解决措施

现在问题现象和原因比较清晰明了了:因为dubbo版本的升级,导致提前触发zipkin-starter的defaultProviderConfig的初始化,且dubbo会扫描配置文件中的配置并再生成一个providerConfig;而dubbo注册时只会拿第一个providerConfig进行注册。

解法一:对provider进行合并

在DubboConfigProcessor保存所有的providerConfig,并想办法对所有的providerConfig进行合并。 这种方式黑箱操作太多,不太理想。如果合并providerConfig出了问题,开发者要花很大力气去排查。

解法二:调整ProviderConfig初始化顺序

想办法调整DefaultProviderConfig初始化顺序,使其在dubbo ProviderConfig初始化之后再出发,让@ConditionalOnMissingBean生效、防止重复创建ProviderConfig对象。 但这种方式也被否决了:首先调整初始化顺序非常困难;其次严重依赖Spring初始化策略,也就是说不同版本可能效果不一样。

解法三:dubbo ConfigPostProcessor

一筹莫展之际,心想dubbo有没有可能提供了配置扩展给开发者呢?抱着这个想法看了下dubbo的源码,发现有一个类名叫org.apache.dubbo.config.ConfigPostProcessor,开始有点小激动。 点进去看了接口的两个方法,心中窃喜:这不就是dubbo版本的Spring的BeanPostProcessor嘛。

@SPI
public interface ConfigPostProcessor {

    default void postProcessReferConfig(ReferenceConfig referenceConfig) {
    }

    default void postProcessServiceConfig(ServiceConfig serviceConfig) {
    }
}

这是dubbo提供的SPI扩展机制,只要在META-INF/dubbo目录下新建一个文件名为org.apache.dubbo.config.ConfigPostProcessor,文件内容按照要求填上自定义的Processor就行

tracingConfigProcessor=com.xxx.config.processor.DubboSPIConfigProcessor

然后把原先DubboConfigProcessor的代码拷贝过来稍作调整即可,这里就不展开了。如果对org.apache.dubbo.config.ConfigPostProcessor感兴趣可以去看看源码~

为了区分两种Processor,我把原先的DubboConfigProcessor改成了DubboSpringConfigProcessor; 新增的Processor叫做DubboSPIConfigProcessor~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章