1萎河、前言
分布式配置中心ConfigServer的使用方式 :
SpringCloud之ConfigServer配置中心以及Bus消息總線
2、配置中心服務(wù)端
2.1荸型、源碼入口
@EnableConfigServer 這個(gè)注解僅僅起到一個(gè)標(biāo)識(shí)的作用,沒(méi)有引入任何東西
那么就 還是spi的方式
找到Maven: org.springframework.cloud:spring-cloud-config-server:2.0.0.RELEASE2包下的META-INF/Spring.factories.
2.2繁涂、接口的注冊(cè)
在配置中心服務(wù)端會(huì)創(chuàng)建很多接口,比如
- 配置信息的加解密接口
- 查看 配置中心服務(wù) 拉取到的配置信息接口 :我們可以直接在調(diào)用查看配置信息, 配置中心客戶端在啟動(dòng)的時(shí)候拱她,也會(huì)調(diào)用這個(gè)接口從配置中心服務(wù)端獲取對(duì)應(yīng)的配置信息。
SPI從spring.factories導(dǎo)入的org.springframework.cloud.config.server.config.ConfigServerAutoConfiguration類扔罪。
會(huì)導(dǎo)入 ConfigServerEncryptionConfiguration秉沼,ConfigServerMvcConfiguration這兩個(gè)類,接口的創(chuàng)建就在這兩個(gè)類中矿酵。
2.3.1唬复、獲取配置信息的接口注冊(cè)
ConfigServerMvcConfiguration類里會(huì)用@Bean 注冊(cè) EnvironmentController接口
EnvironmentController 就是用來(lái)請(qǐng)求獲取 配置信息的Controller類,類上有@RestController+ @RequestMapping注解。
里面有各種形式的接口來(lái)獲取配置信息坏瘩。
2.3.2盅抚、加解密接口的注冊(cè)
ConfigServerEncryptionConfiguration 會(huì)用@Bean 注冊(cè) EncryptionController接口
EncryptionController就是一個(gè)Controller類 ,有@RestController + @RequestMapping 注解,里面會(huì)配置 加解密接口倔矾。
2.3.2.1妄均、加密接口
2.3.2.2、解密接口
2.3哪自、獲取配置信息接口 源碼
獲取配置信息的接口請(qǐng)求路徑有很多種形式,我們挑/{label}/{name}-{profiles}.properties 這種丰包,對(duì)應(yīng)的是EnvironmentController的 這個(gè)接口。 看看源碼的執(zhí)行流程壤巷。
調(diào)用這個(gè)接口 , 獲取 master分支, micro-order服務(wù),dev環(huán)境的配置文件.
http://localhost:9101/master/micro-order-dev.properties
斷點(diǎn)進(jìn)到了這個(gè)接口, 走labelled方法獲取環(huán)境對(duì)象
調(diào)用EnvironmentEncryptorEnvironmentRepository對(duì)象的findOne方法
調(diào)用 SearchPathCompositeEnvironmentRepository類的findOne方法邑彪。
由于SearchPathCompositeEnvironmentRepository類沒(méi)有實(shí)現(xiàn)findOne方法,會(huì)調(diào)到它的父類AbstractScmEnvironmentRepository的findOne方法。
進(jìn)入getLocations 方法
進(jìn)入 MultipleJGitEnvironmentRepository類的 getLocations()胧华。
MultipleJGitEnvironmentRepository 的父類 是JGitEnvironmentRepository 類寄症,調(diào)父類的getLocations方法宙彪。
如果請(qǐng)求接口傳入 git分支參數(shù), 那么獲取的lebel就是請(qǐng)求傳的,否則默認(rèn)獲取的是master分支的有巧。
然后調(diào)refresh方法释漆,獲取這個(gè)分支下的所有配置。
createGitClient() 篮迎,會(huì)先檢查git本地倉(cāng)庫(kù)是否存在,不存在就會(huì)創(chuàng)建git本地倉(cāng)庫(kù)男图。
接著判斷 git本地倉(cāng)庫(kù) 文件夾是否存在, 判斷的文件夾路徑 由getWorkingDirectory()返回,我這mac電腦的路徑是
/var/folders/tb/w7qkq1w54w3g25_3_jp0w6500000gn/T/config-repo-8553408175024803312, 的子路徑下是否有 .git/index.lock文件甜橱,是否存在git的鎖逊笆,有的話就刪除。
windows的本地倉(cāng)庫(kù)路徑 好像是什么 C://work/config/tmp啥的岂傲。
接下來(lái)判斷 本地倉(cāng)庫(kù)/var/folders/tb/w7qkq1w54w3g25_3_jp0w6500000gn/T/config-repo-8553408175024803312 下是否有.git文件存在难裆。
如果存在就直接返回這個(gè)路徑下的git操作對(duì)象, 不存在就會(huì) 新建一個(gè)這個(gè)路徑,然后在下面生成.git文件
我們先把這個(gè)文件夾刪除刪除掉譬胎。讓他重新創(chuàng)建一個(gè)差牛。
這個(gè)就會(huì)創(chuàng)建一個(gè)這樣的目錄,并且生成.git文件堰乔。并且 設(shè)置遠(yuǎn)程倉(cāng)庫(kù)地址
并且會(huì)執(zhí)行g(shù)it clone 指令,把整個(gè) 倉(cāng)庫(kù)克隆到本地倉(cāng)庫(kù)脐恩。
這個(gè)時(shí)候镐侯,這個(gè)路徑下有 整個(gè)配置文件的config文件夾了。
/var/folders/tb/w7qkq1w54w3g25_3_jp0w6500000gn/T/config-repo-8553408175024803312/config :
拉取完配置文件之后驶冒,判斷是否需要更新苟翻,如果需要的話,就會(huì)先執(zhí)行 git fetch指令,
git checkout 切換分支 到 我們接口請(qǐng)求對(duì)應(yīng)的分支下骗污。
然后執(zhí)行 merge指令, 把文件更新到最新的狀態(tài)崇猫。
這個(gè)時(shí)候,服務(wù)端 已經(jīng)把所有最新的配置文件 拉取到本地倉(cāng)庫(kù)了需忿。
然后返回getLocations 返回 本地倉(cāng)庫(kù)的地址,以及服務(wù)名诅炉,環(huán)境,分支屋厘,版本號(hào)涕烧。
然后會(huì)把對(duì)應(yīng)服務(wù)的配置文件 放到 enviroment對(duì)象 里的PropertySources 數(shù)組中,返回enviroment對(duì)象汗洒。
再把Environment對(duì)象 议纯, 本地倉(cāng)庫(kù)路徑 替換成git倉(cāng)庫(kù)地址,變成這樣溢谤。然后返回
最后瞻凤,從environment對(duì)象里獲取 micro-order-dev.properties里的內(nèi)容憨攒,把map變成 字符串形式,返回出去阀参。
2.4浓恶、服務(wù)端 本地配置倉(cāng)庫(kù) 初始化
當(dāng)然,配置中心服務(wù)端 不會(huì)只等到 被請(qǐng)求接口了才會(huì) 去git拉取 配置文件到本地倉(cāng)庫(kù)结笨,而是在啟動(dòng)完成之后會(huì)由線程池 定時(shí)觸發(fā) 健康檢測(cè)包晰。在健康檢測(cè)里,就會(huì)從git 更新 配置文件到本地倉(cāng)庫(kù)炕吸。
走的也是 請(qǐng)求git,創(chuàng)建本地倉(cāng)庫(kù),git clone, 然后merge更新伐憾。
3、配置中心客戶端
配置中心客戶端 會(huì)在啟動(dòng)的時(shí)候請(qǐng)求 配置中心服務(wù)端 獲取 配置信息赫模, 然后塞到自己的environment 對(duì)象的propertySource屬性中树肃。
3.1、源碼入口
同樣是spi瀑罗。
Maven: org.springframework.cloud:spring-cloud-config-client:2.0.0.RELEASE2包下的META-INF/spring.factories胸嘴。
3.1.1、加載 key為 BootstrapConfiguration的類
只不過(guò)這里配置的key是BootstrapConfiguration類型的,springboot啟動(dòng)的時(shí)候默認(rèn)加載的是key為EnableAutoConfiguration的類斩祭。
key為BootstrapConfiguration的類 是由監(jiān)聽(tīng)器加載的劣像。
有個(gè) BootstrapApplicationListener 監(jiān)聽(tīng)器類,實(shí)現(xiàn)了ApplicationListener接口摧玫,當(dāng)Spring容器發(fā)布 ApplicationEnvironmentPreparedEvent 事件時(shí)耳奕,會(huì)調(diào)用實(shí)現(xiàn)的onApplicationEvent方法。
這里就會(huì)加載META-INF/Spring.factories 下配置的key為 BootstrapConfiguration類型的類诬像。
關(guān)于事件是啥時(shí)候發(fā)布的屋群,可以看一下調(diào)用鏈,也是在Springboot的run方法里調(diào)用的坏挠。
3.1.2芍躏、ConfigServicePropertySourceLocator實(shí)例和ConfigClientProperties實(shí)例的注冊(cè)
META-INF/spring.factories文件里配置了 key為BootstrapConfiguration 的有ConfigServiceBootstrapConfiguration類
ConfigServiceBootstrapConfiguration類會(huì)用@Bean注冊(cè)ConfigServicePropertySourceLocator類 ,并且還會(huì)在方法列表注入配置中心客戶端的配置對(duì)象ConfigClientProperties對(duì)象
ConfigServicePropertySourceLocator實(shí)現(xiàn)PropertySourceLocator接口,實(shí)現(xiàn)locate方法降狠。
ConfigClientProperties對(duì)象加載 spring.cloud.config為前綴的配置信息
3.1.3对竣、ConfigServicePropertySourceLocator類locate()的調(diào)用時(shí)機(jī)
之后Springboot在run方法里面的prepareContext方法會(huì)調(diào)用所有 ApplicationContextInitializer接口實(shí)例的 initialize方法。
有一個(gè)ApplicationContextInitializer接口實(shí)現(xiàn)類 PropertySourceBootstrapConfiguration喊熟,會(huì)注入PropertySourceLocator接口類型的實(shí)例柏肪,也就是ConfigServicePropertySourceLocator實(shí)例。
然后會(huì)在 initialize方法里調(diào)用所有 PropertySourceLocator接口類型 的實(shí)例的locate方法芥牌。也就會(huì)調(diào)用到 ConfigServicePropertySourceLocator的locate方法烦味。
3.1.4、ConfigServicePropertySourceLocator的locate()去配置中心服務(wù)端 拉取配置信息
3.1.4.1、去配置中心服務(wù)端 拉取配置信息
ConfigServicePropertySourceLocator的locate方法谬俄,去配置中心服務(wù)端 拉取配置信息柏靶。
先創(chuàng)建一個(gè)restTemplate對(duì)象溃论,getRemoteEnvironment()
在getRemoteEnvironment()方法里屎蜓,先拼接請(qǐng)求的接口路徑
是 /{name}/{profile}/{label}
替換變量就是 /micro-user/dev/master 接口
1赊瞬、配置中心服務(wù)端uri的獲取
在我們的配置里先煎,一般都是配serviceId的。
spring.cloud.config.discovery.enabled=true
spring.cloud.config.discovery.serviceId=config-server
所以需要根據(jù)服務(wù)名稱 從 注冊(cè)中心 獲取到 配置中心服務(wù)端的 ip+端口巧涧。
Springcloud eureka客戶端從注冊(cè)中心 拉取完服務(wù)列表里薯蝎,會(huì)發(fā)布HeartbeatEvent事件。
SpringCloud之Eureka客戶端源碼解析 這篇有介紹褒侧,這里就簡(jiǎn)單帶過(guò)了良风。
這里發(fā)布的是HeartbeatEvent事件。
有一個(gè)監(jiān)聽(tīng) HeartbeatEvent事件的 ApplicationListenerMethodAdapter類闷供,在響應(yīng)事件時(shí),會(huì)反射調(diào)用org.springframework.cloud.config.client.DiscoveryClientConfigServiceBootstrapConfiguration類的 heartbeat方法
DiscoveryClientConfigServiceBootstrapConfiguration 會(huì)注入ConfigClientProperties 配置中心客戶端配置類ConfigClientProperties對(duì)象
在DiscoveryClientConfigServiceBootstrapConfiguration的heartbeat方法统诺,會(huì)調(diào)用refresh()
refresh()則會(huì)從配置中心客戶端配置類ConfigClientProperties對(duì)象 里拿到我們配置的配置中心服務(wù)端的服務(wù)名稱, 然后從 拉取到的服務(wù)列表里歪脏,獲取 這個(gè)服務(wù)名稱下對(duì)應(yīng)的服務(wù)實(shí)例。
拿到所有服務(wù)實(shí)例之后, 遍歷獲取 ip+端口粮呢,放到一個(gè)url的列表里婿失,最后設(shè)置到 配置中心客戶端配置類ConfigClientProperties對(duì)象 的uri 屬性中。
所以啄寡,后面 配置中心 客戶端 直接就可以從 配置中心客戶端配置類ConfigClientProperties對(duì)象里的 uri數(shù)組里豪硅,獲取到 ip+端口, 發(fā)起請(qǐng)求就行了。
2挺物、發(fā)起請(qǐng)求
**遍歷 配置中心客戶端配置類ConfigClientProperties對(duì)象里的 uri數(shù)組, 對(duì) 每個(gè) 配置中心服務(wù)端的 ip+端口進(jìn)行 接口請(qǐng)求懒浮。如果有一個(gè)配置中心服務(wù)端成功返回了 它的配置信息,就返回。 **
先 把用戶名砚著,密碼 用base64 加密 方法請(qǐng)求頭Authorization里次伶,
最后向配置中心服務(wù)端http://192.168.31.211:9101/{name}/{profile}/{label} 發(fā)GET請(qǐng)求,獲取對(duì)應(yīng)的配置信息
返回的reponse里的Body是一個(gè)Environment對(duì)象稽穆,里面的propertiesSource 列表就是 這個(gè)服務(wù)dev環(huán)境從master分支 對(duì)應(yīng) 的 配置信息冠王。
最后return,跳出對(duì) 配置中心服務(wù)端的 uri數(shù)組的循環(huán)。
3.1.4.2舌镶、設(shè)置到本地的 Environment對(duì)象的PropertySource中
獲取到Environment對(duì)象之后柱彻,加到name = configService 的CompositePropertySource對(duì)象中,
最終返回的CompositePropertySource對(duì)象是這樣的,里面有一個(gè)propertySources列表
就是從配置中心服務(wù)端拉到的 配置信息
然后把新返回的 CompositePropertySource對(duì)象 再添加到name = bootstrapProperties的CompositePropertySource對(duì)象的propertySource列表中,
最終添加到 插入到 配置中心客戶端的 environment對(duì)象中
最后environment的propertySource就有 從配置中心服務(wù)端拉取的配置信息了餐胀。
后面其他的bean實(shí)例化,就可以 從 environment里面 獲取 從配置中心服務(wù)端拉取的配置信息哟楷,注入到需要使用的地方了。
4骂澄、配置信息的刷新
如果我們?cè)趃it里的配置信息的配置信息發(fā)生改變吓蘑,SpringCloud提供了 配置中心服務(wù)端 刷新配置信息的功能。
有以下兩種方式 :
- 手動(dòng)調(diào)用每臺(tái)服務(wù) actuator組件提供的 /actuator/refresh 接口坟冲。
- 引入SpringCloudBus, 當(dāng)配置發(fā)生改變時(shí)磨镶,調(diào)用 /actuator/bus-refresh接口,通過(guò)消息中間件 以廣播的形式 通知 所有 服務(wù) 刷新配置信息健提。 或者 結(jié)合github -webhook琳猫,自動(dòng)調(diào)用 /actuator/bus-refresh接口。
SpringCloudBus 與 手動(dòng)調(diào)用 每臺(tái)服務(wù)的 /actuator/refresh接口 所做的刷新配置的工作 是一樣的私痹,只不過(guò) 不用 一臺(tái)一臺(tái)的 去調(diào) /actuator/refresh 接口脐嫂,而是通過(guò)廣播。
所以我們這里只介紹 /actuator/refresh 接口這種 是怎么刷新 配置的紊遵。
關(guān)于服務(wù)刷新配置的 大致原理 我在SpringCloud之ConfigServer配置中心以及Bus消息總線里有介紹過(guò)账千。大致有兩點(diǎn) :
- 替換當(dāng)前spring上下文的environment對(duì)象的propertySources對(duì)象里的配置信息為最新從 配置中心服務(wù)端拉去的配置。
- 為了bean里 有@Value之類的引用重新 賦值 暗膜,刷新作用域?yàn)锧RefreshScope 的bean匀奏,讓這些bean重新 初始化,重新從 environment對(duì)象里獲取配置, 將最新值注入到 @Value的屬性中。
4.1学搜、重新拉取配置信息, 替換 environment對(duì)象的propertySources
首先找到 /actuator/refresh接口所在的源碼位置娃善。
RefreshEndpoint類的refresh方法
當(dāng)我們調(diào)用 /actuator/refresh接口,就會(huì)進(jìn)到這里瑞佩。進(jìn)入contextRefresher的refresh()
先把當(dāng)前的Environment對(duì)象的PropertySources列表保存聚磺。然后調(diào)用addConfigFilesToEnvironment方法。
addConfigFilesToEnvironment方法里 會(huì) 重新 創(chuàng)建一個(gè)SpringApplication類炬丸,
- 指定environment為當(dāng)前Spring上下文environment對(duì)象的備份瘫寝。PropertySources的引用是相同的。
- 要加載的source類 是 Empty類, 是個(gè)空的類。
- 并且指定 WebApplicationType.NONE矢沿,不啟動(dòng)spring里的web容器相關(guān)的東西滥搭。
- 并且手動(dòng)加入 BootstrapApplicationListener監(jiān)聽(tīng)器類,注意捣鲸,這個(gè)類 是會(huì) 加載META-INF/spring.factories 文件里配置的 key為 BootstrapConfiguration的類的瑟匆。
之后調(diào)用SpringApplication的run方法。
解讀一下這是要干嘛栽惶。其實(shí)就是 啟動(dòng)了一個(gè)特別輕量級(jí)的springboot容器愁溜。什么功能也不啟用,web相關(guān)的東西也不啟用外厂。 然后run的時(shí)候冕象,就會(huì)加載 SPI里配置的相關(guān)的類。其他什么bean都不注冊(cè)汁蝶,因?yàn)樗麄魅氲腸lass是個(gè)空的類渐扮。
新的Spring容器里要實(shí)例化的bean也就只有這么幾個(gè)spi導(dǎo)入進(jìn)來(lái)的
加載spi之后,那么肯定就會(huì)加載到 配置中心客戶端 的 ConfigServicePropertySourceLocator類, 然后又可以借用 ApplicationContextInitializer接口實(shí)例 去調(diào)用PropertySourceLocator接口的實(shí)現(xiàn)類onfigServicePropertySourceLocator類的 locate()方法掖棉,重新從配置中心服務(wù)端 拉取配置墓律。
拉取完配置之后,傳入的environment對(duì)象的propertySources對(duì)象 就又有 從配置中心服務(wù)端 拉取的配置了。
新創(chuàng)建的spring容器 的 environment對(duì)象:
最后返回這個(gè)Spring上下文對(duì)象幔亥。
返回之后,就是 與之前的 Environment對(duì)象里PropertySources集合進(jìn)行比較交換耻讽。把更改后的配置信息 更新到 當(dāng)前spring上下文的Environment對(duì)象里。
由于剛才新創(chuàng)建并啟動(dòng)的Spring容器傳入的 Environment對(duì)象里 的 propertySources 對(duì)象 的引用 就是 當(dāng)前spring上下文的Environment對(duì)象里的帕棉。所以就直接用 當(dāng)前spring上下文的Environment對(duì)象里的propertySources對(duì)象進(jìn)行比較针肥。
然后返回 有改動(dòng)的 配置文件的key的 集合。
到這里, Environment對(duì)象里 的 propertySources 已經(jīng)被替換成最新拉取的配置了香伴。
4.2慰枕、刷新作用域?yàn)锧RefreshScope 的bean
更新完 Environment對(duì)象里 的 propertySources之后,下面還有一行很重要的代碼即纲。
刷新 作用域?yàn)?@RefreshScope的所有bean捺僻。
刷新其實(shí)就是把他們?nèi)慷间N毀了。
直接把@RefreshScope作用域里存放bean的 緩存清了崇裁。
作用域Scope接口
作用域其實(shí)就是Scope接口的實(shí)現(xiàn)類 來(lái)實(shí)現(xiàn) 管理bean的方式。
Spring在獲取bean的時(shí)候束昵,會(huì)調(diào)用 Scope接口的實(shí)現(xiàn)類的get方法拔稳,來(lái)獲取bean。并傳入ObjectFactory對(duì)象锹雏。
Spring創(chuàng)建bean:
ObjectFactory的getObject方法是會(huì)去創(chuàng)建bean的巴比。Scope接口的實(shí)現(xiàn)類的get方法需要自己來(lái)判斷 bean是不是存在,不存在 就調(diào)用 ObjectFactory的getObject方法去創(chuàng)建bean, 然后返回這個(gè)bean。 存在就直接 返回轻绞。
像spring里的單例作用域采记,get方法里如果不存在,創(chuàng)建完bean之后政勃,就會(huì)存起來(lái)唧龄,下次get就從緩存里拿,不會(huì)再創(chuàng)建新的實(shí)例奸远,以保證從spring容器里獲取的該bean永遠(yuǎn)返回的是同一個(gè)實(shí)例既棺。
而spring里的多例作用域, get方法里如果不存在,創(chuàng)建完bean之后,它不會(huì)緩存起來(lái)懒叛,下次來(lái)還當(dāng)沒(méi)有丸冕,總是創(chuàng)建最新的,所以 從spring容器里獲取的該beanu永遠(yuǎn)是新創(chuàng)建的實(shí)例薛窥。
RefreshScope 如何管理bean胖烛?
RefreshScope是借用內(nèi)部緩存來(lái) 管理bean的。
get方法
走到父類GenericScope的get方法,
會(huì)往BeanLifecycleWrapperCache進(jìn)行緩存诅迷。
然后返回BeanLifecycleWrapper.get方法佩番,判斷bean是不是實(shí)例化,沒(méi)有實(shí)例化就調(diào)用ObjectFactory的getObject方法, 實(shí)例化了就直接返回竟贯。
bean的銷毀
所以現(xiàn)在我們調(diào)用 /actuator/refresh接口刷新配置時(shí)答捕,就把 RefreshScope作用域里的緩存全部都清了, 下次這些bean 被用到時(shí),進(jìn)RefreshScope的get方法 屑那,緩存里沒(méi)有, 那么就會(huì)重新實(shí)例化這些bean拱镐。重新這些實(shí)例化bean,那么這些bean里用到 @value的地方 就會(huì)從 最新的environment對(duì)象里獲取最新的配置持际,注入進(jìn)去, 以達(dá)到 刷新配置的效果沃琅。