微服務之配置中心ConfigKeeper

在微服務架構中,配置中心是必不可少的基礎服務。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

  • 用戶先去配置中心 添加、修改配置;
  • 應用啓動時:(Spring boot應用向配置中心客戶端獲取配置、而後緩存配置到本地內存及本地文件緩存、應用根據配置進行啓動;)
  • 不停機更新配置(調用Spring Cloud的RefreshEndpoint、經過RefreshEndpoint刷新配置)
  • 使用先後端分離架構,若是須要從新設計管理界面,也可使用本身習慣的技術實現

在這裏插入圖片描述

設計的初衷

經過講解管理後臺功能,理解咱們當初出於什麼緣由爲何要這麼設計?能解決哪些問題?設計時的考慮點有哪些?經過前面的閱閱讀,已知ConfigKeeper有如下核心功能:
在這裏插入圖片描述github

權限管理

爲何要有權限管理?spring

  • 1.對於企業級應用來講,權限管理是必不能夠一個需求;
  • 2.經過權限管理隔離數據,保證數據的安全性,避免誤操做;
  • 3.在微服務比較多狀況下,也能夠經過權限自動過濾出咱們所關心的服務,不須要再本身手動過濾,減小沒必要要的操做,能夠提升工做效率;

在這裏插入圖片描述

這個權限系統是咱們最初設計的,咱們內部如今使用了一個統一的權限系統。爲了下降管理成本,咱們也開發了微服務管理平臺,將配置中心,註冊中心,網關管理後臺等一系列基礎服務都接入到此平臺來管理,並經過此平臺統一進行權限管理;mongodb

咱們使用開源系統越多,那麼須要管理的帳號就會越多,若是團隊比較大的話,會增長很是大的管理成本。docker

多環境管理

配置中心的部署比較靈活,支持多環境集中式管理。可是隨行付內部,爲了隔離生產環境,咱們分開部署了兩套配置中心,一套負責開發環境、測試環境、準生產環境的配置管理,另外一套負責生產環境的配置管理。固然開發工程師能夠選擇使用本地配置,不強制開發者環境與配置中心強關聯。(只要考慮開發人員衆多,需求同步進行)數據庫

在這裏插入圖片描述

配置設計

先回想一下:你有使用jar將配置共享給別人,或別人將提供給你帶配置的jar?答案是確定的,這應該是開發中必須面對的問題,那麼使用jar共享配置會帶來哪些問題呢?

容易形成衝突

以前爲了統一日誌的輸出格式,將logback.xml打成一個jar裏,讓你們使用;而我去年在推新的logback配置規範時,發現與它發生衝突了。爲了解決這個衝突,咱們在每一個項目中增長了個空的logbak.xml文件。

不方便修改。

須要與jar包提供方進行協調,還要確認修改是否對其它應用產生影響。

不能作差別化配置

好比有些項目爲了複用數據庫操做部分代碼,將數據庫操做以及配置都放到單獨的模塊,以jar的形式進行復用,若是從複用的角度來看,是很是不錯的方法。

可是當系統發展到必定程度後,有些應用的併發量上來了,其數據庫鏈接池的配置就要與其它應用有差異,這時咱們仍是須要將配置今後模塊中拆出來。

經過上面的例子,能夠發現配置之因此從代碼中提取出,其核心做用就是爲了更好適應變化。由於共享配置存在以這些問題,並且微服務架構下,儘可能仍是以服務的方式來複用業務功能。再者咱們一直要將代碼進行解偶,那麼配置更須要進行解偶。

出於以上種種緣由考慮,咱們在設計配置中心時,也就沒有考慮設計以「組」的形式來共享配置。這也是咱們設計時爭議比較大的地方。

配置內容

分爲應用配置和全局配置:

  • 全局配置:是某一環境下全部應用共享的配置,好比公司的郵件服務配置;註冊中心地址、公司名稱、公司地址等,可能會變化,但廣泛性很是高的配置。
  • 應用配置:每一個應用個性化的配置;

爲何還要全局配置?這遇前面講的組共享配置不是衝突了嗎?

全局配置只是用於適應運行環境的變化而設計的,不設計到業務配置。「組」的界限不是很清楚,很容易亂,而全局配置不存在這方面的問題。

爲何單個應用只支持單個配置?
微服務已經拆得比較小了,其配置內容也不會很是多,因此只設計爲一個應用只有一個配置。並且通過咱們的實踐呢,一個配置是能夠知足實際須要的。

支持版本控制

咱們的版本設計相比Git的,要比較簡單,可是相應的功能也還有的。主要職責以下:

  • 配置每被修改一次,會將舊數據及版本號保存到日誌表中,更新配置內容的同量,將版本號加一
  • 支持版本比較功能:方便查看與最新版本的差別;檢查在哪天作了什麼調整;
  • 支持回退功能:若是配置出現問題,能夠快速回退;

修改配置

無論是在內部推廣時,仍是開源後,都有人問能支持properties嗎?其時最第一版本是支持的,但咱們在前端頁面把這個功能屏蔽了,由於咱們決定只支持yaml格式。

  • 1.properties 對中文支持不是好,而yml卻沒有這個問題;
  • 2.yaml能很好管理同類項配置,避免配置重複key。看過很多properties文件,配置雜亂以及同一個文件出重相同的key,不一樣value的狀況;不是全部的開發都是有強迫症;
  • 3.統一你們的習慣;

在這裏插入圖片描述

當Yml也不是徹底沒有問題的,在實踐過程當中,偶爾也出現有人把縮進搞錯的狀況。

使用Yml在線編輯器,能夠很是方便編輯,好比:複製粘貼內容,就像在修改配置文件同樣,尤爲是批量修改時更爲方便。不像其它經過key value方便管理的配置中心,每次修改都須要先找到相應的key才能進行一個個修改,很是費時費力;

Yml的JSON預覽功能。當用戶編輯內容時,會實時檢查格式是否符合yaml格式時,若是格式是正確的,右則會正確顯示其對應的json內容,若是格式不正確則,右則會提示相應的錯誤信息,能及時發現錯誤。

在這裏插入圖片描述

實例基本信息及批量刷新

不停機實時刷新配置是配置中心的核心需求之一。好比在生產中運行的應用,忽然因需求或性能等緣由,須要調整配置,若是咱們還須要通過修改代碼,從新打包,測試並部署等一系列的操做步驟的話,那效率可想可知,所以帶來的損失也可能會很是之大。ConfigKeeper使用Spring Cloud提供的RefreshEndpoint刷新配置,在最初的版本中,咱們是經過curl或Postman等工具實現此功能,但這樣操做效率比較差,爲此在最新版本中增長了以下功能:

在此頁面,咱們實現以下功能:

  • 1.列出全部應用實例的IP、管理端口等信息
  • 2.查看應用中配置的版本是不是最新的;(很是方便覈對應用版本是不是最新的;避免漏操做等問題;)
  • 3.實現灰度發佈;(能夠手動刷新選中的一個或多個實例的配置;)

客戶端實現

由於隨行付從Spring boot 1.2.2版本就開始使用Spring boot,到如今已經實現全部應用boot化,因此咱們在設計配置中心時,其客戶端必需要無縫兼容Spring boot、Spring cloud應用,因此咱們就參考Spring cloud config的實現。

無縫兼容Spring boot、Spring cloud應用

爲何ConfigKeeper能實現無縫兼容Spring boot、Spring cloud應用?其緣由很是簡單,由於核心實現仍是由Spring cloud提供的,咱們只是在對Spring cloud進行擴展,而不是在其基礎上從新造輪子。

  • 只依賴 spring-cloud-context 和 spring-cloud-commons 兩個jar;
  • Spring cloud 提供PropertySourceLocator接口,方便咱們去加載外部配置,ConfigKeeper的客戶端核心代碼就是實現此接口;

客戶端源碼解析

要想學習客戶端的源碼的話,可能以/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的實現類,其具體實現請你們查看源碼文件。

客戶端特性

  • 支持客戶端負載:若是有多個配置中心服務器實例,能夠經過簡單的輪詢實現客戶端負載,達到高可能的效果。固然也可使用nginx 反向代理實現服務端負載。
  • 支持失敗後重試功能;
  • 支持本地緩存
    • 客戶端從配置中心拉取最新配置後,會緩存到本地磁盤。每次去拉取配置以前,會加載本地緩存配置的版本信息,前傳到服務端,若是服務端與客戶端的版本一致時,接口會返回304狀態,並使用本地緩存進行啓動應用,當服務端與客戶端的版本不一致時,會返回最新版本,並緩存到本地磁盤中。經過此緩存機制,一方面能夠下降網絡帶寬,二是即便配置中心不可用,也不會影響應用的啓動。
  • 上報應用實例信息

使用建議

配置治理

在咱們實踐後發現,使用配置中心,還能夠很好地對配置進行治理,好比統一使用YAML格式配置,使用配置內容更加清晰;避免了使用jar來共享配置帶來的一系列問題等等。但Spring boot、Spring cloud應用可加載的配置源很是之多,還須要注意一些問題。

下面是截取https://docs.spring.io/spring-boot/docs/1.5.16.RELEASE/reference/htmlsingle/#boot-features-external-config中的內容:

  1. Command line arguments.
  2. Properties from SPRING_APPLICATION_JSON (inline JSON embedded in an environment variable or system property)
  3. ServletConfig init parameters.
  4. ServletContext init parameters.
  5. JNDI attributes from java:comp/env.
  6. Java System properties (System.getProperties()).
  7. OS environment variables.
  8. A RandomValuePropertySource that only has properties in random.*.
  9. Profile-specific bootstrap properties outside of your packaged jar (bootstrap-{profile}.properties and YAML variants)
  10. Profile-specific bootstrap properties packaged inside your jar (bootstrap-{profile}.properties and YAML variants)
  11. Bootstrap properties outside of your packaged jar (bootstrap.properties and YAML variants).
  12. Bootstrap properties packaged inside your jar (bootstrap.properties and YAML variants).
  13. Profile-specific application properties outside of your packaged jar (application-{profile}.properties and YAML variants)
  14. Profile-specific application properties packaged inside your jar (application-{profile}.properties and YAML variants)
  15. Application properties outside of your packaged jar (application.properties and YAML variants).
  16. Application properties packaged inside your jar (application.properties and YAML variants).
  17. 經過 PropertySourceLocator 加載配置(應用配置優先級要高於全局配置)
  18. @PropertySource annotations on your @Configuration classes.
  19. Default properties (specified using SpringApplication.setDefaultProperties).

從上面內容可見,Spring boot是支持很是多種方式加載配置的,並且支持重複配置以及支持覆蓋,即相同key的配置,先加載的內容會被後加載的覆蓋,爲了方便後期維護,儘可能遵照如下原則:

  1. 儘可能避免同一key在多個地方配置的狀況;
  2. 若是第1種狀況不可避免,那麼要注意各個配置中的優化級,好比ConfigKeeper中全局配置的優先級要低於應用配置;
  3. 約定配置位置
    可配置的比較那麼多,在團隊中每一個人使用的方法不同,拋必形成混亂,因此須要你們提早作好約定,好比:哪些配置經過命令行來配置,那些配置放到bootstrap 文件中,那些放到application 文件中。
  4. 拒絕使用jar共享配置

是否是全部的配置均可以經過配置中心來實時刷新?

相信不少人都會有這樣的誤區:全部的配置都是能夠經過配置中心來實時刷新,否則配置中心的就沒有多大意義了。爲了解答這個問題,我先來看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主要作了三件事情:

  1. 加載最新配置到Environment
  2. 發送EnvironmentChangeEvent
  3. 清空RefreshScope緩存

因此咱們要想獲取最新配置配置,能夠經過如下途徑:

  1. 直接經過Environment獲取,好比:

    String applicationName = environment.getProperty("spring.application.name");
  2. 處理EnvironmentChangeEvent,好比對於線程池大小的調整,咱們能夠監聽EnvironmentChangeEvent,當接收到EnvironmentChangeEvent時,關閉原來的線程池,前從新實例化新的線程池;

    Spring boot官方建議咱們儘可能咱們使用@ConfigurationProperties管理配置,那麼它是否能自動刷新配置呢?其實它是能夠的,由於在ConfigurationPropertiesRebinder中會監聽EnvironmentChangeEvent,詳細內容請查看org.springframework.cloud.context.properties. ConfigurationPropertiesRebinder。
  3. 在實例化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。生產環境的應用,安全問題不可忽視,因此建議作以下處理:

  • management.port 與 server.port 設置不一樣的值,而且此端口不容許外網訪問;
  • 增長安全驗證;
  • 修改management.context-path
  • 生產環境的management相關配置,儘可能與其它環境的配置要有差別,不能徹底同樣。

調整後的配置實例以下:

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/

相關文章
相關標籤/搜索