Apollo配置中心之apollo-client模塊源碼分析

導讀

  • 本篇文章適用於對Apollo有一定使用經驗或者一定了解的人羣
  • 關鍵字Apollo源碼、apollo-client模塊,場景驅動

apollo-client

  • 由於客戶端是連接使用者與服務端得橋樑,功能邏輯相對來說比較複雜一點,先大致看一下項目包結構
    build: 此包下面只有一個類ApolloInjector,獲取單例對象得入口。,用法如:ApolloInjector.getInstance(ConfigUtil.class),此處getInstance方法會調用上面得getInjector方法,採用Double Check得方式實現Injector實例得單例,獲取實例方式是通過標準得 Java SPI來實現得,默認得Injector實現是DefaultInjector,當獲得單例Injector實例DefaultInjector之後會調用其getInstance方法,如圖最終是通過com.google.inject.Injector這個接口來實現單例得獲取。此處有個很重要得地方就是,前面SPI實例化得時候調用DefaultInjector得構造器,裏面會通過Guice.createInjector(new ApolloModule())方法來創建一個com.google.inject.Injector。此處得Guice容器類似於Spring容器,方法參數你可以通過實現com.google.inject.AbstractModule這個抽象類,需要在configure方法中聲明bind(ConfigUtil.class).in(Singleton.class),即可創建單例對象ConfigUtil,當然還有很多其他用法,此處就不做擴展了。
    ConfigService:實時拉取配置得入口類
    ConfigChangeListener:配置變更監聽器接口,當配置發生改變時觸發回調
    Config:配置相關得抽象接口,包含一系列得獲取各種類型得方法(String、Integer、Long、Short、Float、Double、Byte、Boolean、Date等),還有爲配置添加監聽器得接口
    ConfigFileChangeListener:主要實現類是com.ctrip.framework.apollo.internals.PropertiesCompatibleFileConfigRepository,實現配置文件得同步
    internals:此包包含了所有與服務端交互得邏輯實現。例如RemoteConfigLongPollService,實現長輪詢得處理邏輯,如果服務端返回有配置變更,則會採用事件通知得方式,將配置變更通知到客戶端設置得監聽器ConfigChangeListner,經過一系列得操作刷新Spring得Environment屬性參數(後面會通過源碼分析一下,整個調用過程)
    model:此包包含一個配置變更實體對象ConfigChange,此類中定義了配置變更對應得namespace、變更得屬性名propertyName、變更之前得值oldValue、變更之後得值newValue、屬性變更類型PropertyChangeType枚舉(新增、修改、刪除),還有兩個事件模型ConfigChangeEvent、ConfigFileChangeEvent
    spi:此包有一些工廠類,如ConfigFactoryManager(用來獲取ConfigFactory類,而ConfigFactory類是用來創建Config配置類)、ConfigRegistry類(可以將具體得ConfigFactory配置工廠類註冊到某個namespace上)
    spring:此包是動態刷新得關鍵(後面源碼分析會多些篇幅)

apollo-client啓動過程源碼分析

  • 由於Apollo是建立在SpringBoo與SpringCloud之上得配置中心,所以我們分析源碼時可以儘量往分析spring boot源碼分析那樣,這樣就會便於記憶了。SpringBoot源碼分析可以參考:
    SpringBoot啓動 源碼深度解析(一)
    SpringBoot啓動 源碼深度解析(二)
    SpringBoot啓動 源碼深度解析(三)
    SpringBoot啓動 源碼深度解析(四)

  • 在SpringBoot項目中,我們使用Apollo得時候都是通過com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig註解來啓動apollo-client得相關配置,那麼我們就從此註解開始。

    通過@Import註解導入ImportBeanDefinitionRegistrar擴展鉤子,簡直不能再典型得spring方式了(不瞭解得讀者可以參考作者其他文集先把這部分得知識瞭解一下)。所以,我們接着看ApolloConfigRegistrar此類通過SPI得方式將註冊得邏輯委託給ApolloConfigRegistrarHelper
    Apollo默認實現是DefaultApolloConfigRegistrarHelper,我們可以自定義此處得實現(最好別改,因爲那樣你會重複做掉宋大佬做的這些事情,而且此處對於實現spring環境屬性配置動態刷新得整合基本已經天衣無縫了,不需要再修改什麼邏輯)。再看DefaultApolloConfigRegistrarHelper
    ① 首先,獲取EnableApolloConfig註解對應得value值,即namespace得數組,支持配置多個;再獲取order,調用PropertySourcesProcessor屬性資源處理器得addNamespaces方法,將namespace數組添加到PropertySourcesProcessor中得多值map映射成員變量NAMESPACE_NAMES中(還有一點就是,Apollo中大量使用了Google工具包Guava,方便編程,並且代碼看起來也很整齊舒服
    ② 判斷是否需要註冊PropertySourcesPlaceholderConfigurer佔位符處理類,優先級調整爲0(此類間接實現了BeanFactoryPostProcessor,所以給其設置優先級,並且優先級越小越先註冊,目的就是要在PropertyPlaceholderConfigurer配置類之前註冊,用來解析佔位符${}得。因爲此處spring版本3.0之前默認是使用PropertyPlaceholderConfigurer,後面得版本使用PropertySourcesPlaceholderConfigurer來代替,並且5.2版本已經不推薦使用了
    ③ 判斷是否需要註冊PropertySourcesProcessor處理器,此類是Apollo中用來實現屬性動態加載得到關鍵。實現了BeanFactoryPostProcessor、EnvironmentAware、PriorityOrdered。優先級設置最高。其中bean工廠處理器得回調方法中會執行兩個關鍵性得方法
    initializePropertySources方法:初始化Apollo得名爲ApolloPropertySources得屬性資源配置,將前面添加到多值映射map中得namespace遍歷依次通過ConfigService.getConfig(namespace)代碼來獲取配置Config(此處一個api看似很簡單,但是其中涉及到得卻是整個Apollo客戶端與服務端交互得邏輯,後面會單獨分析拉取配置代碼得邏輯),然後構造ConfigPropertySource(Apollo中得類,實現了org.springframework.core.env.EnumerablePropertySource)添加到創建得CompositePropertySource組合配置屬性類中,接着將NAMESPACE_NAMES緩存清掉,最後如果spring environment中包含名爲ApolloBootstrapPropertySources得啓動屬性配置,則將上面創建得CompositePropertySource對象添加到其之後,如果沒有,則添加到第一位(確保Apollo得屬性配置放在第一位)
    initializeAutoUpdatePropertiesFeature方法:創建AutoUpdateConfigChangeListener監聽器,此類實現了配置監聽器接口ConfigChangeListener,是實現諸如@Value註解屬性動態刷新得關鍵。獲取所有namespace對應得ConfigPropertySource,將監聽器添加進去實現監聽
    ④ 判斷是否需要註冊ApolloAnnotationProcessor處理器,此類會處理Apollo內置得兩個重要得註解:ApolloConfig:用在屬性上,注入指定namespace對應得Config;ApolloConfigChangeListener:用在方法上,用來監聽指定得namespace對應得key,原理就是通過創建namespace對應得監聽器ConfigChangeListener,反射調用方法實現配置推送。
    ⑤判斷是否需要註冊SpringValueProcessor處理器,此類用來處理帶有@Value註解得屬性字段,並將值通過SpringValueRegistry註冊器添加到註冊器緩存map中(上面提到得監聽器AutoUpdateConfigChangeListener接收到配置變更時就會通過反射更新緩存中得值
    ⑥判斷是否註冊SpringValueDefinitionProcessor處理器,主要是用來處理xml得跟SpringValueProcessor功能類型
    ⑦判斷是否需要註冊ApolloJsonValueProcessor處理器,用來處理屬性註解ApolloJsonValue得,支持屬性值是json格式得,用法同SpringValueProcessor

  • 至此,通過EnableApolloConfig註解驅動得相關配置類基本分析完畢(另外,上面提到得spring包下還有很多關於boot得特性使用,如ApolloApplicationContextInitializer初始化階段靠前,會創建前面提到得啓動屬性配置ApolloBootstrapPropertySources,並且實現了EnvironmentPostProcesso可以將優先更靠前,提到ConfigFileApplicationListener之後)。

關於實時拉取配置ConfigService.getConfig(namespace)代碼得源碼分析

  • 此方法是Apollo提供得統一拉取配置得Entry Point,


    先獲取配置管理類ConfigManager,進入到getManager()方法中,通過ApolloInjector.getInstance(ConfigManager.class)這代碼以Double check得方式獲取對象,其中getInstance方法又會先獲取Injector實例,獲取方式是通過Java SPI來實現,而且是獲取第一個配置得實現。
    Apollo也內置了一個實現DefaultInjector實例,,所以最終獲取ConfigManager實例得地方在DefaultInjector的getInstance方法中
    如上圖無參構造器中會創建一個com.google.inject.Injector實例,首先通過Guice.createInjector(new ApolloModule())來初始Guice容器,然後從容器中選擇對應的實例,此處是通過Google提供的類似於Spring容器的Guice容器,比較輕量級。ApolloModule類中就是容器中創建的對象,基本以單例爲主(更多用法讀者需要時自行查閱)。從module中可以發現實現類是DefaultConfigFactory,所以此處的m_configManager引用對象就是DefaultConfigFactory,緊接着調用DefaultConfigFactory的getConfig方法。,構造器中同樣的套路來獲取ConfigFactoryManager實例,調用實例的getFactory方法來創建ConfigFactory實例對象,從module中可知ConfigFactoryManager的實例是DefaultConfigFactoryManager,那麼會調用DefaultConfigFactoryManager的getFactory方法可以看到優先從指定的namespace註冊器中先獲取對應的對象,若沒註冊,從緩存中獲取,再沒有從Guice中獲取對應名稱的配置工廠對象(默認不支持),此處肯定會返回空,最後從Guice容器中獲取默認的實例,此處默認是DefaultConfigFactory,所以調用DefaultConfigFactory的create方法此處創建配置Config的時候會有個yaml與yml文件兼容性判斷,一般情況下我們創建的namespace都是aaa.bbb.ccc等,所以此處返回的是默認格式properties,會調用createLocalConfigRepository方法創建配置倉庫,createLocalConfigRepository方法中又會判斷Apollo環境是否是本地模式,若是本地模式則就不存在遠程倉庫的概念了,我們此處不使用本地文件模式。則會調用createRemoteConfigRepository方法創建遠程倉庫RemoteConfigRepository對象。可以看出一個namespace對應一個遠程倉庫對象,並且此倉庫是實現配置拉取與動態刷新的關鍵點。
    RemoteConfigRepository:無參構造器中會賦值成員變量:其中比較重要的幾個變量或操作有
    ApolloConfig的原子引用:存儲namespace對應的配置信息,包括appId、集羣信息、當前namespace下對應的配置屬性(Map<String,String>)、發佈對應的key,此處起到緩存的作用。
    HttpUtil:與服務端通信的請求封裝(Java原生的http請求)
    ConfigUtil:包括一些配置的參數,例如:定時刷新間隔refreshInterval屬性(默認5分鐘)
    ConfigServiceLocator:獲取服務端configService的地址(首先調用metaSerrvice接口,通過服務發現功能(Eureka)獲取配置服務端的服務列表)
    RemoteConfigLongPollService:實現客戶端長輪詢(配置推送),包括接收到變更響應結果之後會發出配置變更事件通知等操作
    原子引用m_longPollServiceDto:當服務端通知過來之後,會設置服務列表第一個值爲此結果,即第一個通知到的服務端
    原子引用m_remoteMessages:當長輪詢檢測到配置變更時會調用原子引用設置變更的通知信息
    限流RateLimiter:每秒兩次,此參數是在m_configUtil中配置的
    強制刷新原子標誌位m_configNeedForceRefresh:默認爲true,什麼意思呢? 就是如果Apollo服務端請求失敗之後,如果是強制刷新的話,線程sleep1秒鐘,接着調用配置服務端,否則使用失敗策略的阻塞配置(兩個時間實際都是1秒)
    trySync方法:拉取一次配置,方法繼承自抽象父類,典型的模板設計模式,實際調用的是sync方法 獲取本地緩存的namespace屬性配置信息,接着調用 loadApolloConfig方法獲取當前namespace對應的配置信息,loadApolloConfig就是剛纔所說的:首先獲取配置服務列表;然後隨機獲取一個服務實例,若有服務端通知到當前客戶端則會優先調用該服務端去拉取配置,返回結果。若當前拉取的配置與之前不相同,則會調用fireRepositoryChange方法觸發RepositoryChangeListener監聽器回調通知,我們前面創建的是DefaultConfig,則調用DefaultConfig的實現,可以看到方法內會檢查配置是否變更,若變更的話會調用父類的fireConfigChange方法遍歷所有添加的監聽器,執行其onChange方法,此處就回調到真正實現配置變更的監聽器AutoUpdateConfigChangeListener和ApolloAnnotationProcessor(用來處理@ApolloConfigChangeListener註解
    看到這裏就可以與我們前面分析的關鍵類聯繫起來了。
    十一 schedulePeriodicRefresh方法:定時調用trySync方法獲取配置信息,每隔5分鐘
    十二 scheduleLongPollingRefresh方法:長輪詢獲取配置信息。入口是調用RemoteConfigLongPollService的submit方法
    doLongPollingRefresh方法:此方法是實現長輪詢的核心邏輯,關鍵性步驟已用紅框標出
    第①部分:隨機選擇配置服務的實例信息,構造請求url
    第②部分:http請求調用服務端並返回Apollo配置通知列表信息
    第③部分:若請求狀態爲200表示有配置變更,則會調用updateNotifications、updateRemoteNotifications、notify等方法實現配置同步變更操作。
    updateNotifications:將配置通知id存入到ConcurrentMap<String(namespace), Long(通知ID)> m_notifications緩存中
    updateRemoteNotifications:將Apollo配置信息存入到Map<String(namespace), ApolloNotificationMessages(通知信息)> m_remoteNotificationMessages緩存中
    notify:回調遠程配置倉庫RemoteConfigRepository的onLongPollNotified方法,然後同步拉取配置信息(前面提到過)
    第④部分:若返回的狀態碼是304,表示沒有配置變更,則會將選擇的服務實例重置爲空(又會重新隨機選擇服務實例進行調用),接着重複執行上述步驟。

  • 分析到此處,整個apoll-client的原理及源碼已經大致講解完畢,如果沒看過源碼的讀者可以參考本篇進行源碼閱讀,也可以幫作者指出勘誤或者分析錯誤之處,願與君共勉 !

  1. ☛ 文章要是勘誤或者知識點說的不正確,歡迎評論,畢竟這也是作者通過閱讀源碼獲得的知識,難免會有疏忽!
  2. 要是感覺文章對你有所幫助,不妨點個關注,或者移駕看一下作者的其他文集,也都是幹活多多哦,文章也在全力更新中。
  3. 著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章