通過引入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的。
- 正是這個方法觸發了zipkin-starter的defaultProviderConfig的初始化。注意此時dubbo的providerConfig還沒有初始化,這就是
@ConditionalOnMissingBean
註解看上去沒有生效的原因! - 等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時進行了調整:
- 低版本(<2.7.5)使用的是
getBeansOfType(Class<T> type, boolean includeNonSingletons, boolean allowEagerInit)
,其中includeNonSingletons和allowEagerInit參數值都是false,也就是可以延遲初始化。這相當於留給@ConditionalOnMissingBean
進行判斷的時間 - 高版本(>=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
~