在微服務架構中,配置中心是必不可少的基礎服務。ConfigKeeper已開源,本文將深度分析配置中心的核心內容,錯過「Spring Cloud中國社區北京沙龍-2018.10.28 」的同窗將從本篇文章中收穫現場的分享內容。
微服務+容器架構後,爲了方便動態更新應用配置,須要把配置文件放到應用執行包以外的配置中心,這樣一來,一個可執行包就能夠在不一樣的環境下運行,大幅度下降包的版本管理成本,也能夠有效控制docker鏡像的版本管理成本。傳統的經過配置文件、數據庫等方式已經愈來愈沒法知足開發人員對配置管理的需求。對程序配置的指望值也愈來愈高:配置修改後實時生效,分環境、分集羣管理配置,完善的權限、審覈機制等等。因而便誕生了ConfigKeeper。html
ConfigKeeper是隨行付架構部基於Spring Cloud研發的分佈式配置中心,與Spring Boot、Spring Cloud應用無縫兼容。
雖然Spring Cloud 已經爲咱們提供了基於git或mongodb等實現的配置中心,可是這些方案實現都過於簡單,沒有達到實際可用的標準。好比:沒有提供統一的管理頁面,不便於操做和使用;沒有權限管理功能;沒有數據驗證功能等等。但Spring Cloud Config的核心技術仍是能夠爲咱們所用,沒有必要從新造輪子。前端
市面上已經有幾款比較成型的配置中心,你們耳熟能詳的攜程Apollo和百度Disconf,而咱們的配置中心底層是基於Spring Cloud Config模塊進行擴展的,首先來看看Apollo、Spring Cloud Config、ConfigKeeper的功能差別:java
功能點 | Apollo | Spring Cloud Config | ConfigKeeper | |
---|---|---|---|---|
配置界面 | 一個界面管理不一樣環境、不一樣集羣配置 | 無,須要經過git操做 | 配置信息落入數據庫中,友好頁面管理 | |
配置生效時間 | 實時 | 重啓生效或者手動refresh生效 | 實時推送、重啓生效、手動refresh生效 | |
版本管理 | 界面上直接提供發佈歷史和回滾按鈕 | 無,須要經過git操做 | 管理頁面一鍵回滾 | |
灰度發佈 | 支持 | 不支持 | 支持,與Spring Cloud其餘組件打通 | |
受權、審覈、審計 | 界面上直接支持,並且支持修改、發佈權限分離 | 須要經過git倉庫設置,且不支持修改、發佈權限分離 | 應用分配製權限管理 | |
實例配置監控 | 能夠方便的看到當前哪些客戶端在使用哪些配置 | 不支持 | 心跳推送,一目瞭然 | |
配置獲取性能 | 快,經過數據庫訪問,還有緩存支持 | 較慢,須要從git clone repository,而後從文件系統讀取 | 本地式緩存文件,配置增量推送 | |
客戶端支持 | 原生支持全部Java和.Net應用 | 支持Spring應用,提供annotation獲取配置 | Spring、Spring Boot、Spring Cloud | |
支持YAML格式 | 不支持 | 支持 | 支持 |
除了上述以外,還有如下其餘功能特性:nginx
有史以來最簡單的配置中心。使用數據庫保存配置是由於微服務拆分粒度相對比較細,使用的配置也會相對比較少,因此使用數據庫表就夠保存,流程以下:git
經過講解管理後臺功能,理解咱們當初出於什麼緣由爲何要這麼設計?能解決哪些問題?設計時的考慮點有哪些?經過前面的閱閱讀,已知ConfigKeeper有如下核心功能:
github
爲何要有權限管理?spring
這個權限系統是咱們最初設計的,咱們內部如今使用了一個統一的權限系統。爲了下降管理成本,咱們也開發了微服務管理平臺,將配置中心,註冊中心,網關管理後臺等一系列基礎服務都接入到此平臺來管理,並經過此平臺統一進行權限管理;mongodb
咱們使用開源系統越多,那麼須要管理的帳號就會越多,若是團隊比較大的話,會增長很是大的管理成本。docker
配置中心的部署比較靈活,支持多環境集中式管理。可是隨行付內部,爲了隔離生產環境,咱們分開部署了兩套配置中心,一套負責開發環境、測試環境、準生產環境的配置管理,另外一套負責生產環境的配置管理。固然開發工程師能夠選擇使用本地配置,不強制開發者環境與配置中心強關聯。(只要考慮開發人員衆多,需求同步進行)數據庫
先回想一下:你有使用jar將配置共享給別人,或別人將提供給你帶配置的jar?答案是確定的,這應該是開發中必須面對的問題,那麼使用jar共享配置會帶來哪些問題呢?
以前爲了統一日誌的輸出格式,將logback.xml打成一個jar裏,讓你們使用;而我去年在推新的logback配置規範時,發現與它發生衝突了。爲了解決這個衝突,咱們在每一個項目中增長了個空的logbak.xml文件。
須要與jar包提供方進行協調,還要確認修改是否對其它應用產生影響。
好比有些項目爲了複用數據庫操做部分代碼,將數據庫操做以及配置都放到單獨的模塊,以jar的形式進行復用,若是從複用的角度來看,是很是不錯的方法。
可是當系統發展到必定程度後,有些應用的併發量上來了,其數據庫鏈接池的配置就要與其它應用有差異,這時咱們仍是須要將配置今後模塊中拆出來。
經過上面的例子,能夠發現配置之因此從代碼中提取出,其核心做用就是爲了更好適應變化。由於共享配置存在以這些問題,並且微服務架構下,儘可能仍是以服務的方式來複用業務功能。再者咱們一直要將代碼進行解偶,那麼配置更須要進行解偶。
出於以上種種緣由考慮,咱們在設計配置中心時,也就沒有考慮設計以「組」的形式來共享配置。這也是咱們設計時爭議比較大的地方。
分爲應用配置和全局配置:
爲何還要全局配置?這遇前面講的組共享配置不是衝突了嗎?
全局配置只是用於適應運行環境的變化而設計的,不設計到業務配置。「組」的界限不是很清楚,很容易亂,而全局配置不存在這方面的問題。
爲何單個應用只支持單個配置?
微服務已經拆得比較小了,其配置內容也不會很是多,因此只設計爲一個應用只有一個配置。並且通過咱們的實踐呢,一個配置是能夠知足實際須要的。
咱們的版本設計相比Git的,要比較簡單,可是相應的功能也還有的。主要職責以下:
無論是在內部推廣時,仍是開源後,都有人問能支持properties嗎?其時最第一版本是支持的,但咱們在前端頁面把這個功能屏蔽了,由於咱們決定只支持yaml格式。
當Yml也不是徹底沒有問題的,在實踐過程當中,偶爾也出現有人把縮進搞錯的狀況。
使用Yml在線編輯器,能夠很是方便編輯,好比:複製粘貼內容,就像在修改配置文件同樣,尤爲是批量修改時更爲方便。不像其它經過key value方便管理的配置中心,每次修改都須要先找到相應的key才能進行一個個修改,很是費時費力;
Yml的JSON預覽功能。當用戶編輯內容時,會實時檢查格式是否符合yaml格式時,若是格式是正確的,右則會正確顯示其對應的json內容,若是格式不正確則,右則會提示相應的錯誤信息,能及時發現錯誤。
不停機實時刷新配置是配置中心的核心需求之一。好比在生產中運行的應用,忽然因需求或性能等緣由,須要調整配置,若是咱們還須要通過修改代碼,從新打包,測試並部署等一系列的操做步驟的話,那效率可想可知,所以帶來的損失也可能會很是之大。ConfigKeeper使用Spring Cloud提供的RefreshEndpoint刷新配置,在最初的版本中,咱們是經過curl或Postman等工具實現此功能,但這樣操做效率比較差,爲此在最新版本中增長了以下功能:
在此頁面,咱們實現以下功能:
由於隨行付從Spring boot 1.2.2版本就開始使用Spring boot,到如今已經實現全部應用boot化,因此咱們在設計配置中心時,其客戶端必需要無縫兼容Spring boot、Spring cloud應用,因此咱們就參考Spring cloud config的實現。
爲何ConfigKeeper能實現無縫兼容Spring boot、Spring cloud應用?其緣由很是簡單,由於核心實現仍是由Spring cloud提供的,咱們只是在對Spring cloud進行擴展,而不是在其基礎上從新造輪子。
要想學習客戶端的源碼的話,可能以/META-INF/spring.factories文件爲入口,此文件中有以下配置:
org.springframework.cloud.bootstrap.BootstrapConfiguration=\ com.suixingpay.config.client.SxfConfigServiceBootstrapConfiguration
而SxfConfigServiceBootstrapConfiguration存在以下代碼:
@Bean @ConditionalOnMissingBean(SxfConfigServicePropertySourceLocator.class) @ConditionalOnProperty(value = "suixingpay.config.enabled", matchIfMissing = true) public SxfConfigServicePropertySourceLocator sxfConfigServicePropertySource(ApplicationContext context) { SxfConfigClientProperties configClientProperties = sxfConfigClientProperties(context); ConfigDAO configDAO = sxfConfigDAO(configClientProperties); return new SxfConfigServicePropertySourceLocator(configDAO, configClientProperties); }
而SxfConfigServicePropertySourceLocator其實就是PropertySourceLocator的實現類,其具體實現請你們查看源碼文件。
在咱們實踐後發現,使用配置中心,還能夠很好地對配置進行治理,好比統一使用YAML格式配置,使用配置內容更加清晰;避免了使用jar來共享配置帶來的一系列問題等等。但Spring boot、Spring cloud應用可加載的配置源很是之多,還須要注意一些問題。
從上面內容可見,Spring boot是支持很是多種方式加載配置的,並且支持重複配置以及支持覆蓋,即相同key的配置,先加載的內容會被後加載的覆蓋,爲了方便後期維護,儘可能遵照如下原則:
相信不少人都會有這樣的誤區:全部的配置都是能夠經過配置中心來實時刷新,否則配置中心的就沒有多大意義了。爲了解答這個問題,我先來看RefreshEndpoint都作了哪些事情:
public synchronized Set<String> refresh() { Map<String, Object> before = extract(this.context.getEnvironment().getPropertySources()); // 加載最新配置到Environment addConfigFilesToEnvironment(); Set<String> keys = changes(before, extract(this.context.getEnvironment().getPropertySources())).keySet(); // 發送EnvironmentChangeEvent this.context.publishEvent(new EnvironmentChangeEvent(context, keys)); // 清空RefreshScope緩存 this.scope.refreshAll(); return keys; }
經過上面的源碼,咱們能夠看出其RefreshEndpoint主要作了三件事情:
因此咱們要想獲取最新配置配置,能夠經過如下途徑:
直接經過Environment獲取,好比:
String applicationName = environment.getProperty("spring.application.name");
處理EnvironmentChangeEvent,好比對於線程池大小的調整,咱們能夠監聽EnvironmentChangeEvent,當接收到EnvironmentChangeEvent時,關閉原來的線程池,前從新實例化新的線程池;
Spring boot官方建議咱們儘可能咱們使用@ConfigurationProperties管理配置,那麼它是否能自動刷新配置呢?其實它是能夠的,由於在ConfigurationPropertiesRebinder中會監聽EnvironmentChangeEvent,詳細內容請查看org.springframework.cloud.context.properties. ConfigurationPropertiesRebinder。在實例化bean時增長@RefreshScope, 好比:
@Autowired private DefaultUserProperties userProperties; @RefreshScope // 支持動態刷新 @Bean(name="defaultUser") public UserDO defaultUser() { UserDO userDO=new UserDO(); userDO.setId(userProperties.getId()); userDO.setName(userProperties.getName()); return userDO; }
Spring cloud 爲了實現運行時動態刷新,增長了RefreshScope(org.springframework.cloud.context.scope.refresh.RefreshScope類),會將加了@RefreshScope的bean放入RefreshScope中,當刷新RefreshScope時,會清空緩存,當下次使用這些bean時會從新實例些這些bean。
經過RefreshEndpoint 刷新的話,就須要開啓Spring boot Endpoint相關功能,而Spring boot Endpoint若是不作特殊處理的話,很容易被探測到,引起一些安全問題。好比:
server: port: 8080 management: security: enabled: false
那麼很容易去調用Spring boot Endpoint。生產環境的應用,安全問題不可忽視,因此建議作以下處理:
調整後的配置實例以下:
server: port: 8080 management: security: enabled: true context-path: /_ops port: 9098 security: basic: enabled: true path: ${management.context-path}/**, /swagger-ui.html, /v2/api-docs, /druid/** user: name: ma password: xxxxxx
Spring 生態功能很是豐富,爲咱們解決了很是多棘手問題,但不少東西要進行本地化開發後才能更好的使用。配置中心使用了很多開源技術,給咱們帶來了很多便利,但願經過此開源項目回饋社區,爲開源社區貢獻綿薄之力。
https://github.com/sxfad/
https://gitee.com/sxfad/