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~

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