前言
apollo官方文檔: Apollo
本篇主要基于apollo-client 1.4.0源碼魁蒜,介紹springboot項(xiàng)目如何在啟動(dòng)時(shí)調(diào)用apollo景图,以及apollo客戶(hù)端遠(yuǎn)程獲取配置的流程蚯舱。 如有筆誤,歡迎評(píng)論留言交流指正逆趋。
一句話(huà)概括:apollo客戶(hù)端主要任務(wù),是在spring容器啟動(dòng)時(shí)晒奕,根據(jù)集群和namespace拉取對(duì)應(yīng)配置中心的配置數(shù)據(jù)闻书,再根據(jù)@Value注解注入對(duì)應(yīng)bean的對(duì)應(yīng)屬性,同時(shí)通過(guò)http請(qǐng)求監(jiān)聽(tīng)指定的集群和namespace對(duì)應(yīng)的配置是否發(fā)生變化吴汪,實(shí)現(xiàn)配置的動(dòng)態(tài)變化
1惠窄、SPI加載
找到META-INF路徑下的spring.factories文件,里面是SPI機(jī)制添加的初始化類(lèi)
2、初始化方法
ApplicationContextInitializer 接口用于在 Spring 容器刷新之前執(zhí)行的一個(gè)回調(diào)函數(shù)淘捡,通常用于向 SpringBoot 容器中注入屬性藕各。通過(guò)實(shí)現(xiàn)ApplicationContextInitializer接口的initialize( )方法進(jìn)行初始化。
2.1 ApolloInjector初始化映射流程
首先打開(kāi)ApolloInjector類(lèi)乌逐,通過(guò)靜態(tài)方法構(gòu)造,雙重檢查加鎖 防止生成多個(gè)Injector创葡,調(diào)用loadFirst方法2.2 trySync( )方法
try Sync( )方法,里面嘗試http請(qǐng)求獲取配置文件中的遠(yuǎn)程服務(wù)地址2.3 另外兩個(gè)方法
schedulePeriodicRefresh( ) 線(xiàn)程池多線(xiàn)程執(zhí)行缤剧,定時(shí)5分鐘一次嘗試刷新配置信息為什么是這樣的設(shè)計(jì)呢犹褒?網(wǎng)上找到其他博主請(qǐng)教了 Apollo 的作者宋老師兄纺。
原文指路:https://so.csdn.net/so/search?q=Apollo%20%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%20%E2%80%94%E2%80%94%20Client%20%E8%BD%AE%E8%AF%A2%E9%85%8D%E7%BD%AE&t=blog&u=github_38592071
聊天內(nèi)容如下:
* 老艿艿:
* [https://github.com/ctripcorp/apollo/issues/652](https://github.com/ctripcorp/apollo/issues/652)
* 看這個(gè) issue 問(wèn)了
* 問(wèn)題:非常感謝。為什么不在 long polling 的返回結(jié)果中直接返回更新的結(jié)果呢化漆?
* 回答:這樣推送消息就是有狀態(tài)了,做不到冪等了钦奋,會(huì)帶來(lái)很多問(wèn)題座云。
* [呲牙] 周末打擾哈疙赠。帶來(lái)的問(wèn)題主要是哪些哈?
* 張大佬:
* 可能會(huì)有數(shù)據(jù)的丟失朦拖,如果使用只推送變更通知的話(huà)圃阳,即使丟失了,還是能在下一次變更的時(shí)候達(dá)到一個(gè)一致的狀態(tài)
* 宋老師:
* @老艿艿 主要是冪等考慮
* 加載配置接口是冪等的璧帝,推送配置的話(huà)就做不到了捍岳,因?yàn)楹屯扑晚樞蛳嚓P(guān)
* 老艿艿:
* 推送順序指的是?
* 張大佬:
* 我想的是睬隶,比如兩次修改操作的通知锣夹,由于網(wǎng)絡(luò)問(wèn)題,客戶(hù)端接收到的順序可能是反的苏潜,如果這兩次修改的是同一個(gè)key银萍,就有問(wèn)題了。
* 老艿艿:
* 從目前代碼看下來(lái)恤左,長(zhǎng)輪詢(xún)發(fā)起贴唇,同時(shí)只會(huì)有一個(gè),應(yīng)該不會(huì)同時(shí)兩個(gè)通知呀飞袋。
* 現(xiàn)在 client 是一個(gè)長(zhǎng)輪詢(xún)+定時(shí)輪詢(xún)戳气,是不是這兩個(gè)會(huì)有互相的影響。
* 張大佬:
* @宋老師 嗯嗯
* 宋老師:
* 目前推送是單連接走h(yuǎn)ttp的巧鸭,所以問(wèn)題可能不大瓶您,不過(guò)在設(shè)計(jì)上而言是有這個(gè)問(wèn)題的,比如如果推送是走的tcp長(zhǎng)連接的話(huà)蹄皱。另外览闰,長(zhǎng)輪詢(xún)和推送之間也會(huì)有沖突,如果連續(xù)兩次配置變化巷折,就可能造成雙寫(xiě)压鉴。還有一點(diǎn),就是保持邏輯的簡(jiǎn)單锻拘,目前的做法推送只負(fù)責(zé)做簡(jiǎn)單的通知油吭,不需要去計(jì)算客戶(hù)端的配置應(yīng)該是什么,因?yàn)橛?jì)算邏輯挺復(fù)雜的署拟,需要考慮集群婉宰,關(guān)聯(lián),灰度等推穷。
* 老艿艿:
* 現(xiàn)在的設(shè)計(jì)思路上心包,是不是可以理解.
* 1、client 的定時(shí)輪詢(xún)馒铃,可以保持最終一致蟹腾。
* 2痕惋、client 的長(zhǎng)輪詢(xún),是定時(shí)輪訓(xùn)的“實(shí)時(shí)”補(bǔ)充娃殖,通過(guò)這樣的方式值戳,讓流程統(tǒng)一?
* 宋老師:
* 總而言之炉爆,就是在滿(mǎn)足冪等性堕虹,實(shí)時(shí)性的基礎(chǔ)上保持設(shè)計(jì)的簡(jiǎn)單
* 是的,推拉結(jié)合
* 張大佬:
* 長(zhǎng)輪詢(xún)個(gè)推送直接的沖突沒(méi)太理解
* 有沒(méi)有可能有這種問(wèn)題芬首,一次長(zhǎng)輪詢(xún)中消息丟失了桨菜,但是長(zhǎng)輪詢(xún)還在
* 老艿艿: 1\. 長(zhǎng)輪詢(xún)的通知里面浴捆,帶有配置信息 2\. 定時(shí)輪訓(xùn)箍铭,里面也拿到配置信息
* 這個(gè)時(shí)候赁豆,client 是沒(méi)辦法判斷哪個(gè)配置信息是新的。
* @張大佬 我認(rèn)為是可能的艺晴,這個(gè)時(shí)候昼钻,client 可以發(fā)起新的長(zhǎng)輪詢(xún)。
* 宋大佬:
* @張大佬 長(zhǎng)輪詢(xún)和推送的沖突封寞,這個(gè)更正為定時(shí)輪詢(xún)和推送的沖突
* 老艿艿:
* 通知是定時(shí)輪詢(xún)配置的補(bǔ)充然评。有了通知,立馬輪詢(xún)狈究。不用在定時(shí)了
* 張大佬:
* get
* 老艿艿:
* 謝謝宋老師和張大佬[壞笑] ::: 三個(gè)獲取方法主要就是不停地查詢(xún)遠(yuǎn)程配置和本地的對(duì)比碗淌,不同就更新替換
3、自動(dòng)更新方法
接下來(lái)看EnableAutoConfigration類(lèi)如何實(shí)現(xiàn)自動(dòng)更新配置抖锥。 一句話(huà)概括:apollo通過(guò)這個(gè)類(lèi)和@Bean注解亿眠,向容器注入了多個(gè)加載器和監(jiān)聽(tīng)器,通過(guò)spring的其他注解在啟動(dòng)時(shí)拿到bean工廠(chǎng)和其中所有屬性磅废,最后通過(guò)監(jiān)聽(tīng)器不斷http長(zhǎng)輪詢(xún)監(jiān)聽(tīng)配置是否發(fā)生變化纳像,發(fā)生變化就通過(guò)java反射,將更新后的值注入bean的對(duì)應(yīng)屬性拯勉。
3.1 怎么做到自動(dòng)更新屬性
首先通過(guò)configuration注解和Bean注解竟趾,向容器注入了一個(gè)ConfigPropertySourcesProcessor類(lèi),它繼承了PropertySourcesProcessor類(lèi)宫峦,實(shí)現(xiàn)了postProcessBeanDefinitionRegistry類(lèi)在監(jiān)聽(tīng)器里锡足,拿到spring的BeanFactory和監(jiān)聽(tīng)到更新的key,再找到哪個(gè)具體的類(lèi)壳坪,哪個(gè)具體的屬性值需要更新舶得,如上圖,在update( )方法里還是用java反射重新注入新的值爽蝴。
3.2 為什么@Value的值會(huì)被更新
關(guān)于springValueRegistry里為什么有那些屬性沐批、怎么獲取的@Value的值,在最初注入的加載器中蝎亚,這個(gè)加載器根據(jù)所有的bean和屬性名九孩,維護(hù)了一個(gè)存儲(chǔ)所有@Value注解所在位置的關(guān)系數(shù)據(jù)結(jié)構(gòu)總結(jié)
apollo客戶(hù)端通過(guò)注解在spring啟動(dòng)時(shí)拿到所有的bean徙歼,通過(guò)自己的XXXProcessor類(lèi)犁河,掃描@Value注解和@ApolloConfigChangeListener注解。前者跟bean一起封裝存儲(chǔ)到本地的內(nèi)存中魄梯,方便后續(xù)自動(dòng)更新時(shí)找到和注入對(duì)應(yīng)的屬性桨螺,后者允許開(kāi)發(fā)者拓展自己的監(jiān)聽(tīng)器,在更新時(shí)執(zhí)行額外操作酿秸,或是對(duì)指定的變化進(jìn)行處理灭翔。apollo對(duì)屬性的注入是通過(guò)java反射機(jī)制,對(duì)配置變化的監(jiān)聽(tīng)是通過(guò)http請(qǐng)求,同時(shí)用SPI機(jī)制給開(kāi)發(fā)者留了非常多的拓展空間肝箱。