微服務之配置中心ConfigKeeper

在微服務架構中,配置中心是必不可少的基礎服務。ConfigKeeper已開源,本文將深度分析配置中心的核心內容,錯過「Spring Cloud中國社區北京沙龍-2018.10.28 」的同學將從本篇文章中收穫現場的分享內容。

背景

微服務+容器架構後,爲了方便動態更新應用配置,需要把配置文件放到應用執行包之外的配置中心,這樣一來,一個可執行包就可以在不同的環境下運行,大幅度降低包的版本管理成本,也可以有效控制docker鏡像的版本管理成本。傳統的通過配置文件、數據庫等方式已經越來越無法滿足開發人員對配置管理的需求。對程序配置的期望值也越來越高:配置修改後實時生效,分環境、分集羣管理配置,完善的權限、審覈機制等等。於是便誕生了ConfigKeeper。

ConfigKeeper是隨行付架構部基於Spring Cloud研發的分佈式配置中心,與Spring Boot、Spring Cloud應用無縫兼容。
雖然Spring Cloud 已經爲我們提供了基於git或mongodb等實現的配置中心,但是這些方案實現都過於簡單,沒有達到實際可用的標準。比如:沒有提供統一的管理頁面,不便於操作和使用;沒有權限管理功能;沒有數據驗證功能等等。但Spring Cloud Config的核心技術還是可以爲我們所用,沒有必要重新造輪子。

定製的原因

市面上已經有幾款比較成型的配置中心,大家耳熟能詳的攜程Apollo和百度Disconf,而我們的配置中心底層是基於Spring Cloud Config模塊進行擴展的,首先來看看Apollo、Spring Cloud Config、ConfigKeeper的功能差異:

功能點 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格式 不支持 支持 支持

除了上述之外,還有以下其他功能特性:

  • 開發人員最習慣的就是在文件中修改配置,管理頁面上提供「舒適」的富文本編輯框;
  • 全局配置約定,比如多個項目共享的配置,比如短信地址等採取約定大於配置。全局配置<應用配置;
  • 配置校驗,文本修改高亮對比修改內容,防止低級錯誤等;

架構設計

有史以來最簡單的配置中心。使用數據庫保存配置是因爲微服務拆分粒度相對比較細,使用的配置也會相對比較少,所以使用數據庫表就夠保存,流程如下:

    1. 用戶先去配置中心 添加、修改配置;
    1. 應用啓動時:(Spring boot應用向配置中心客戶端獲取配置、然後緩存配置到本地內存及本地文件緩存、應用根據配置進行啓動;)
    1. 不停機更新配置(調用Spring Cloud的RefreshEndpoint、通過RefreshEndpoint刷新配置)
    1. 使用前後端分離架構,如果需要重新設計管理界面,也可以使用自己習慣的技術實現

在這裏插入圖片描述

設計的初衷

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

權限管理

爲什麼要有權限管理?

  • 1.對於企業級應用來說,權限管理是必不可以一個需求;
  • 2.通過權限管理隔離數據,保證數據的安全性,避免誤操作;
  • 3.在微服務比較多情況下,也可以通過權限自動過濾出我們所關心的服務,不需要再自己手動過濾,減少不必要的操作,可以提高工作效率;

在這裏插入圖片描述

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

我們使用開源系統越多,那麼需要管理的賬號就會越多,如果團隊比較大的話,會增加非常大的管理成本。

多環境管理

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

在這裏插入圖片描述

配置設計

先回想一下:你有使用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進行擴展,而不是在其基礎上重新造輪子。

    1. 只依賴 spring-cloud-context 和 spring-cloud-commons 兩個jar;
    1. 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的實現類,其具體實現請大家查看源碼文件。

客戶端特性

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

使用建議

配置治理

在我們實踐後發現,使用配置中心,還可以很好地對配置進行治理,比如統一使用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。生產環境的應用,安全問題不可忽視,所以建議做如下處理:

    1. management.port 與 server.port 設置不同的值,並且此端口不允許外網訪問;
    1. 增加安全驗證;
    1. 修改management.context-path
    1. 生產環境的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/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章