Apollo配置中心之a(chǎn)pollo-client模塊源碼分析

導(dǎo)讀

  • 本篇文章適用于對(duì)Apollo有一定使用經(jīng)驗(yàn)或者一定了解的人群
  • 關(guān)鍵字Apollo源碼、apollo-client模塊,場(chǎng)景驅(qū)動(dòng)

apollo-client

  • 由于客戶(hù)端是連接使用者與服務(wù)端得橋梁,功能邏輯相對(duì)來(lái)說(shuō)比較復(fù)雜一點(diǎn),先大致看一下項(xiàng)目包結(jié)構(gòu)
    apollo-client結(jié)構(gòu)

    build: 此包下面只有一個(gè)類(lèi)ApolloInjector,獲取單例對(duì)象得入口。
    ApolloInjector
    ,用法如:ApolloInjector.getInstance(ConfigUtil.class),此處getInstance方法會(huì)調(diào)用上面得getInjector方法,采用Double Check得方式實(shí)現(xiàn)Injector實(shí)例得單例,獲取實(shí)例方式是通過(guò)標(biāo)準(zhǔn)得 Java SPI來(lái)實(shí)現(xiàn)得,默認(rèn)得Injector實(shí)現(xiàn)是DefaultInjector
    Injector得SPI
    ,當(dāng)獲得單例Injector實(shí)例DefaultInjector之后會(huì)調(diào)用其getInstance方法
    DefaultInjector
    ,如圖最終是通過(guò)com.google.inject.Injector這個(gè)接口來(lái)實(shí)現(xiàn)單例得獲取。此處有個(gè)很重要得地方就是,前面SPI實(shí)例化得時(shí)候調(diào)用DefaultInjector得構(gòu)造器,里面會(huì)通過(guò)Guice.createInjector(new ApolloModule())方法來(lái)創(chuàng)建一個(gè)com.google.inject.Injector。此處得Guice容器類(lèi)似于Spring容器,方法參數(shù)你可以通過(guò)實(shí)現(xiàn)com.google.inject.AbstractModule這個(gè)抽象類(lèi),需要在configure方法中聲明bind(ConfigUtil.class).in(Singleton.class),即可創(chuàng)建單例對(duì)象ConfigUtil,當(dāng)然還有很多其他用法,此處就不做擴(kuò)展了。
    ConfigService:實(shí)時(shí)拉取配置得入口類(lèi)
    ConfigChangeListener:配置變更監(jiān)聽(tīng)器接口,當(dāng)配置發(fā)生改變時(shí)觸發(fā)回調(diào)
    Config:配置相關(guān)得抽象接口,包含一系列得獲取各種類(lèi)型得方法(String、Integer、Long、Short、Float、Double、Byte、Boolean、Date等),還有為配置添加監(jiān)聽(tīng)器得接口
    ConfigFileChangeListener:主要實(shí)現(xiàn)類(lèi)是com.ctrip.framework.apollo.internals.PropertiesCompatibleFileConfigRepository,實(shí)現(xiàn)配置文件得同步
    internals:此包包含了所有與服務(wù)端交互得邏輯實(shí)現(xiàn)。例如RemoteConfigLongPollService,實(shí)現(xiàn)長(zhǎng)輪詢(xún)得處理邏輯,如果服務(wù)端返回有配置變更,則會(huì)采用事件通知得方式,將配置變更通知到客戶(hù)端設(shè)置得監(jiān)聽(tīng)器ConfigChangeListner,經(jīng)過(guò)一系列得操作刷新Spring得Environment屬性參數(shù)(后面會(huì)通過(guò)源碼分析一下,整個(gè)調(diào)用過(guò)程)
    model:此包包含一個(gè)配置變更實(shí)體對(duì)象ConfigChange,此類(lèi)中定義了配置變更對(duì)應(yīng)得namespace、變更得屬性名propertyName、變更之前得值oldValue、變更之后得值newValue、屬性變更類(lèi)型PropertyChangeType枚舉(新增、修改、刪除),還有兩個(gè)事件模型ConfigChangeEvent、ConfigFileChangeEvent
    spi:此包有一些工廠類(lèi),如ConfigFactoryManager(用來(lái)獲取ConfigFactory類(lèi),而ConfigFactory類(lèi)是用來(lái)創(chuàng)建Config配置類(lèi))、ConfigRegistry類(lèi)(可以將具體得ConfigFactory配置工廠類(lèi)注冊(cè)到某個(gè)namespace上)
    spring:此包是動(dòng)態(tài)刷新得關(guān)鍵(后面源碼分析會(huì)多些篇幅)

apollo-client啟動(dòng)過(guò)程源碼分析

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

  • 在SpringBoot項(xiàng)目中,我們使用Apollo得時(shí)候都是通過(guò)com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig注解來(lái)啟動(dòng)apollo-client得相關(guān)配置,那么我們就從此注解開(kāi)始。

    EnableApolloConfig
    通過(guò)@Import注解導(dǎo)入ImportBeanDefinitionRegistrar擴(kuò)展鉤子,簡(jiǎn)直不能再典型得spring方式了(不了解得讀者可以參考作者其他文集先把這部分得知識(shí)了解一下)。所以,我們接著看ApolloConfigRegistrar類(lèi)
    ApolloConfigRegistrar
    此類(lèi)通過(guò)SPI得方式將注冊(cè)得邏輯委托給ApolloConfigRegistrarHelper
    com.ctrip.framework.apollo.spring.spi.ApolloConfigRegistrarHelper

    Apollo默認(rèn)實(shí)現(xiàn)是DefaultApolloConfigRegistrarHelper,我們可以自定義此處得實(shí)現(xiàn)(最好別改,因?yàn)槟菢幽銜?huì)重復(fù)做掉宋大佬做的這些事情,而且此處對(duì)于實(shí)現(xiàn)spring環(huán)境屬性配置動(dòng)態(tài)刷新得整合基本已經(jīng)天衣無(wú)縫了,不需要再修改什么邏輯)。再看DefaultApolloConfigRegistrarHelper
    DefaultApolloConfigRegistrarHelper

    ① 首先,獲取EnableApolloConfig注解對(duì)應(yīng)得value值,即namespace得數(shù)組,支持配置多個(gè);再獲取order,調(diào)用PropertySourcesProcessor屬性資源處理器得addNamespaces方法,將namespace數(shù)組添加到PropertySourcesProcessor中得多值map映射成員變量NAMESPACE_NAMES中(還有一點(diǎn)就是,Apollo中大量使用了Google工具包Guava,方便編程,并且代碼看起來(lái)也很整齊舒服
    PropertySourcesProcessor

    ② 判斷是否需要注冊(cè)PropertySourcesPlaceholderConfigurer占位符處理類(lèi),優(yōu)先級(jí)調(diào)整為0(此類(lèi)間接實(shí)現(xiàn)了BeanFactoryPostProcessor,所以給其設(shè)置優(yōu)先級(jí),并且優(yōu)先級(jí)越小越先注冊(cè),目的就是要在PropertyPlaceholderConfigurer配置類(lèi)之前注冊(cè),用來(lái)解析占位符${}得。因?yàn)榇颂巗pring版本3.0之前默認(rèn)是使用PropertyPlaceholderConfigurer,后面得版本使用PropertySourcesPlaceholderConfigurer來(lái)代替,并且5.2版本已經(jīng)不推薦使用了
    PropertyPlaceholderConfigurer

    ③ 判斷是否需要注冊(cè)PropertySourcesProcessor處理器,此類(lèi)是Apollo中用來(lái)實(shí)現(xiàn)屬性動(dòng)態(tài)加載得到關(guān)鍵。實(shí)現(xiàn)了BeanFactoryPostProcessor、EnvironmentAware、PriorityOrdered。優(yōu)先級(jí)設(shè)置最高。其中bean工廠處理器得回調(diào)方法中會(huì)執(zhí)行兩個(gè)關(guān)鍵性得方法
    PropertySourcesProcessor

    initializePropertySources方法:初始化Apollo得名為ApolloPropertySources得屬性資源配置,將前面添加到多值映射map中得namespace遍歷依次通過(guò)ConfigService.getConfig(namespace)代碼來(lái)獲取配置Config(此處一個(gè)api看似很簡(jiǎn)單,但是其中涉及到得卻是整個(gè)Apollo客戶(hù)端與服務(wù)端交互得邏輯,后面會(huì)單獨(dú)分析拉取配置代碼得邏輯),然后構(gòu)造ConfigPropertySource(Apollo中得類(lèi),實(shí)現(xiàn)了org.springframework.core.env.EnumerablePropertySource)添加到創(chuàng)建得CompositePropertySource組合配置屬性類(lèi)中,接著將NAMESPACE_NAMES緩存清掉,最后如果spring environment中包含名為ApolloBootstrapPropertySources得啟動(dòng)屬性配置,則將上面創(chuàng)建得CompositePropertySource對(duì)象添加到其之后,如果沒(méi)有,則添加到第一位(確保Apollo得屬性配置放在第一位)
    initializePropertySources方法

    initializeAutoUpdatePropertiesFeature方法:創(chuàng)建AutoUpdateConfigChangeListener監(jiān)聽(tīng)器,此類(lèi)實(shí)現(xiàn)了配置監(jiān)聽(tīng)器接口ConfigChangeListener,是實(shí)現(xiàn)諸如@Value注解屬性動(dòng)態(tài)刷新得關(guān)鍵。獲取所有namespace對(duì)應(yīng)得ConfigPropertySource,將監(jiān)聽(tīng)器添加進(jìn)去實(shí)現(xiàn)監(jiān)聽(tīng)
    ④ 判斷是否需要注冊(cè)ApolloAnnotationProcessor處理器,此類(lèi)會(huì)處理Apollo內(nèi)置得兩個(gè)重要得注解:ApolloConfig:用在屬性上,注入指定namespace對(duì)應(yīng)得Config;ApolloConfigChangeListener:用在方法上,用來(lái)監(jiān)聽(tīng)指定得namespace對(duì)應(yīng)得key,原理就是通過(guò)創(chuàng)建namespace對(duì)應(yīng)得監(jiān)聽(tīng)器ConfigChangeListener,反射調(diào)用方法實(shí)現(xiàn)配置推送。
    com.ctrip.framework.apollo.spring.annotation.ApolloAnnotationProcessor#processField處理屬性注解ApolloConfig
    com.ctrip.framework.apollo.spring.annotation.ApolloAnnotationProcessor#processMethod處理方法注解ApolloConfigChangeListener

    ⑤判斷是否需要注冊(cè)SpringValueProcessor處理器,此類(lèi)用來(lái)處理帶有@Value注解得屬性字段,并將值通過(guò)SpringValueRegistry注冊(cè)器添加到注冊(cè)器緩存map中(上面提到得監(jiān)聽(tīng)器AutoUpdateConfigChangeListener接收到配置變更時(shí)就會(huì)通過(guò)反射更新緩存中得值
    ⑥判斷是否注冊(cè)SpringValueDefinitionProcessor處理器,主要是用來(lái)處理xml得跟SpringValueProcessor功能類(lèi)型
    ⑦判斷是否需要注冊(cè)ApolloJsonValueProcessor處理器,用來(lái)處理屬性注解ApolloJsonValue得,支持屬性值是json格式得,用法同SpringValueProcessor

  • 至此,通過(guò)EnableApolloConfig注解驅(qū)動(dòng)得相關(guān)配置類(lèi)基本分析完畢(另外,上面提到得spring包下還有很多關(guān)于boot得特性使用,如ApolloApplicationContextInitializer初始化階段靠前,會(huì)創(chuàng)建前面提到得啟動(dòng)屬性配置ApolloBootstrapPropertySources,并且實(shí)現(xiàn)了EnvironmentPostProcesso可以將優(yōu)先更靠前,提到ConfigFileApplicationListener之后)。

關(guān)于實(shí)時(shí)拉取配置ConfigService.getConfig(namespace)代碼得源碼分析

  • 此方法是Apollo提供得統(tǒng)一拉取配置得Entry Point,

    com.ctrip.framework.apollo.ConfigService#getConfig

    先獲取配置管理類(lèi)ConfigManager,進(jìn)入到getManager()方法中,通過(guò)ApolloInjector.getInstance(ConfigManager.class)這代碼以Double check得方式獲取對(duì)象,其中g(shù)etInstance方法又會(huì)先獲取Injector實(shí)例,獲取方式是通過(guò)Java SPI來(lái)實(shí)現(xiàn),而且是獲取第一個(gè)配置得實(shí)現(xiàn)。
    ApolloInjector

    Apollo也內(nèi)置了一個(gè)實(shí)現(xiàn)DefaultInjector實(shí)例,
    默認(rèn)實(shí)現(xiàn)DefaultInjector
    ,所以最終獲取ConfigManager實(shí)例得地方在DefaultInjector的getInstance方法中
    DefaultInjector

    如上圖無(wú)參構(gòu)造器中會(huì)創(chuàng)建一個(gè)com.google.inject.Injector實(shí)例,首先通過(guò)Guice.createInjector(new ApolloModule())來(lái)初始Guice容器,然后從容器中選擇對(duì)應(yīng)的實(shí)例,此處是通過(guò)Google提供的類(lèi)似于Spring容器的Guice容器,比較輕量級(jí)。ApolloModule類(lèi)中就是容器中創(chuàng)建的對(duì)象,基本以單例為主(更多用法讀者需要時(shí)自行查閱)。從module中可以發(fā)現(xiàn)實(shí)現(xiàn)類(lèi)是DefaultConfigFactory,所以此處的m_configManager引用對(duì)象就是DefaultConfigFactory,緊接著調(diào)用DefaultConfigFactory的getConfig方法。
    DefaultConfigManager
    ,構(gòu)造器中同樣的套路來(lái)獲取ConfigFactoryManager實(shí)例,調(diào)用實(shí)例的getFactory方法來(lái)創(chuàng)建ConfigFactory實(shí)例對(duì)象,從module中可知ConfigFactoryManager的實(shí)例是DefaultConfigFactoryManager,那么會(huì)調(diào)用DefaultConfigFactoryManager的getFactory方法
    com.ctrip.framework.apollo.spi.DefaultConfigFactoryManager#getFactory
    可以看到優(yōu)先從指定的namespace注冊(cè)器中先獲取對(duì)應(yīng)的對(duì)象,若沒(méi)注冊(cè),從緩存中獲取,再?zèng)]有從Guice中獲取對(duì)應(yīng)名稱(chēng)的配置工廠對(duì)象(默認(rèn)不支持),此處肯定會(huì)返回空,最后從Guice容器中獲取默認(rèn)的實(shí)例,此處默認(rèn)是DefaultConfigFactory,所以調(diào)用DefaultConfigFactory的create方法
    DefaultConfigFactory
    此處創(chuàng)建配置Config的時(shí)候會(huì)有個(gè)yaml與yml文件兼容性判斷,一般情況下我們創(chuàng)建的namespace都是aaa.bbb.ccc等,所以此處返回的是默認(rèn)格式properties,會(huì)調(diào)用createLocalConfigRepository方法創(chuàng)建配置倉(cāng)庫(kù),createLocalConfigRepository方法中又會(huì)判斷Apollo環(huán)境是否是本地模式,若是本地模式則就不存在遠(yuǎn)程倉(cāng)庫(kù)的概念了,我們此處不使用本地文件模式。則會(huì)調(diào)用createRemoteConfigRepository方法創(chuàng)建遠(yuǎn)程倉(cāng)庫(kù)RemoteConfigRepository對(duì)象。可以看出一個(gè)namespace對(duì)應(yīng)一個(gè)遠(yuǎn)程倉(cāng)庫(kù)對(duì)象,并且此倉(cāng)庫(kù)是實(shí)現(xiàn)配置拉取與動(dòng)態(tài)刷新的關(guān)鍵點(diǎn)。
    com.ctrip.framework.apollo.spi.DefaultConfigFactory#createRemoteConfigRepository

    RemoteConfigRepository:無(wú)參構(gòu)造器中會(huì)賦值成員變量:
    RemoteConfigRepository構(gòu)造器
    其中比較重要的幾個(gè)變量或操作有
    ApolloConfig的原子引用:存儲(chǔ)namespace對(duì)應(yīng)的配置信息,包括appId、集群信息、當(dāng)前namespace下對(duì)應(yīng)的配置屬性(Map<String,String>)、發(fā)布對(duì)應(yīng)的key,此處起到緩存的作用。
    HttpUtil:與服務(wù)端通信的請(qǐng)求封裝(Java原生的http請(qǐng)求)
    ConfigUtil:包括一些配置的參數(shù),例如:定時(shí)刷新間隔refreshInterval屬性(默認(rèn)5分鐘)
    ConfigServiceLocator:獲取服務(wù)端configService的地址(首先調(diào)用metaSerrvice接口,通過(guò)服務(wù)發(fā)現(xiàn)功能(Eureka)獲取配置服務(wù)端的服務(wù)列表)
    RemoteConfigLongPollService:實(shí)現(xiàn)客戶(hù)端長(zhǎng)輪詢(xún)(配置推送),包括接收到變更響應(yīng)結(jié)果之后會(huì)發(fā)出配置變更事件通知等操作
    原子引用m_longPollServiceDto:當(dāng)服務(wù)端通知過(guò)來(lái)之后,會(huì)設(shè)置服務(wù)列表第一個(gè)值為此結(jié)果,即第一個(gè)通知到的服務(wù)端
    原子引用m_remoteMessages:當(dāng)長(zhǎng)輪詢(xún)檢測(cè)到配置變更時(shí)會(huì)調(diào)用原子引用設(shè)置變更的通知信息
    限流RateLimiter:每秒兩次,此參數(shù)是在m_configUtil中配置的
    強(qiáng)制刷新原子標(biāo)志位m_configNeedForceRefresh:默認(rèn)為true,什么意思呢? 就是如果Apollo服務(wù)端請(qǐng)求失敗之后,如果是強(qiáng)制刷新的話,線程sleep1秒鐘,接著調(diào)用配置服務(wù)端,否則使用失敗策略的阻塞配置(兩個(gè)時(shí)間實(shí)際都是1秒)
    trySync方法:拉取一次配置,方法繼承自抽象父類(lèi),典型的模板設(shè)計(jì)模式,實(shí)際調(diào)用的是sync方法
    com.ctrip.framework.apollo.internals.RemoteConfigRepository#sync
    獲取本地緩存的namespace屬性配置信息,接著調(diào)用 loadApolloConfig方法獲取當(dāng)前namespace對(duì)應(yīng)的配置信息,loadApolloConfig就是剛才所說(shuō)的:首先獲取配置服務(wù)列表;然后隨機(jī)獲取一個(gè)服務(wù)實(shí)例,若有服務(wù)端通知到當(dāng)前客戶(hù)端則會(huì)優(yōu)先調(diào)用該服務(wù)端去拉取配置,返回結(jié)果。若當(dāng)前拉取的配置與之前不相同,則會(huì)調(diào)用fireRepositoryChange方法觸發(fā)RepositoryChangeListener監(jiān)聽(tīng)器回調(diào)通知,
    com.ctrip.framework.apollo.internals.AbstractConfigRepository#fireRepositoryChange
    我們前面創(chuàng)建的是DefaultConfig,則調(diào)用DefaultConfig的實(shí)現(xiàn)
    com.ctrip.framework.apollo.internals.DefaultConfig#onRepositoryChange
    ,可以看到方法內(nèi)會(huì)檢查配置是否變更,若變更的話會(huì)調(diào)用父類(lèi)的fireConfigChange方法
    com.ctrip.framework.apollo.internals.AbstractConfig#fireConfigChange
    遍歷所有添加的監(jiān)聽(tīng)器,執(zhí)行其onChange方法,此處就回調(diào)到真正實(shí)現(xiàn)配置變更的監(jiān)聽(tīng)器AutoUpdateConfigChangeListener和ApolloAnnotationProcessor(用來(lái)處理@ApolloConfigChangeListener注解
    AutoUpdateConfigChangeListener
    com.ctrip.framework.apollo.spring.annotation.ApolloAnnotationProcessor#processMethod

    看到這里就可以與我們前面分析的關(guān)鍵類(lèi)聯(lián)系起來(lái)了。
    十一 schedulePeriodicRefresh方法:定時(shí)調(diào)用trySync方法獲取配置信息,每隔5分鐘
    com.ctrip.framework.apollo.internals.RemoteConfigRepository#schedulePeriodicRefresh

    十二 scheduleLongPollingRefresh方法:長(zhǎng)輪詢(xún)獲取配置信息。入口是調(diào)用RemoteConfigLongPollService的submit方法
    RemoteConfigLongPollService
    com.ctrip.framework.apollo.internals.RemoteConfigLongPollService#doLongPollingRefresh

    doLongPollingRefresh方法:此方法是實(shí)現(xiàn)長(zhǎng)輪詢(xún)的核心邏輯,關(guān)鍵性步驟已用紅框標(biāo)出
    第①部分:隨機(jī)選擇配置服務(wù)的實(shí)例信息,構(gòu)造請(qǐng)求url
    第②部分:http請(qǐng)求調(diào)用服務(wù)端并返回Apollo配置通知列表信息
    第③部分:若請(qǐng)求狀態(tài)為200表示有配置變更,則會(huì)調(diào)用updateNotifications、updateRemoteNotifications、notify等方法實(shí)現(xiàn)配置同步變更操作。
    updateNotifications:將配置通知id存入到ConcurrentMap<String(namespace), Long(通知ID)> m_notifications緩存中
    updateRemoteNotifications:將Apollo配置信息存入到Map<String(namespace), ApolloNotificationMessages(通知信息)> m_remoteNotificationMessages緩存中
    notify:回調(diào)遠(yuǎn)程配置倉(cāng)庫(kù)RemoteConfigRepository的onLongPollNotified方法,然后同步拉取配置信息(前面提到過(guò))
    第④部分:若返回的狀態(tài)碼是304,表示沒(méi)有配置變更,則會(huì)將選擇的服務(wù)實(shí)例重置為空(又會(huì)重新隨機(jī)選擇服務(wù)實(shí)例進(jìn)行調(diào)用),接著重復(fù)執(zhí)行上述步驟。

  • 分析到此處,整個(gè)apoll-client的原理及源碼已經(jīng)大致講解完畢,如果沒(méi)看過(guò)源碼的讀者可以參考本篇進(jìn)行源碼閱讀,也可以幫作者指出勘誤或者分析錯(cuò)誤之處,愿與君共勉 !

  1. ? 文章要是勘誤或者知識(shí)點(diǎn)說(shuō)的不正確,歡迎評(píng)論,畢竟這也是作者通過(guò)閱讀源碼獲得的知識(shí),難免會(huì)有疏忽!
  2. ? 要是感覺(jué)文章對(duì)你有所幫助,不妨點(diǎn)個(gè)關(guān)注,或者移駕看一下作者的其他文集,也都是干活多多哦,文章也在全力更新中。
  3. ? 著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容