隨行付微服務之配置中心ConfigKeeper

馬力_隨行付發表於2019-02-28

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

隨行付微服務之配置中心ConfigKeeper

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

  • 開發人員最習慣的就是在檔案中修改配置,管理頁面上提供「舒適」的富文字編輯框;
  • 全域性配置約定,比如多個專案共享的配置,比如簡訊地址等採取約定大於配置。全域性配置<應用配置;
  • 配置校驗,文字修改高亮對比修改內容,防止低階錯誤等;

架構設計

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

    1. 使用者先去配置中心 新增、修改配置;
    1. 應用啟動時:(Spring boot應用向配置中心客戶端獲取配置、然後快取配置到本地記憶體及本地檔案快取、應用根據配置進行啟動;)
    1. 不停機更新配置(呼叫Spring Cloud的RefreshEndpoint、通過RefreshEndpoint重新整理配置)
    1. 使用前後端分離架構,如果需要重新設計管理介面,也可以使用自己習慣的技術實現
隨行付微服務之配置中心ConfigKeeper

設計的初衷

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

隨行付微服務之配置中心ConfigKeeper

許可權管理

為什麼要有許可權管理?

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

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

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

多環境管理

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

隨行付微服務之配置中心ConfigKeeper

配置設計

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

容易造成衝突

之前為了統一日誌的輸出格式,將logback.xml打成一個jar裡,讓大家使用;而我去年在推新的logback配置規範時,發現與它發生衝突了。為了解決這個衝突,我們在每個專案中增加了個空的logbak.xml檔案。

不方便修改。

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

不能做差異化配置

比如有些專案為了複用資料庫操作部分程式碼,將資料庫操作以及配置都放到單獨的模組,以jar的形式進行復用,如果從複用的角度來看,是非常不錯的方法。

但是當系統發展到一定程度後,有些應用的併發量上來了,其資料庫連線池的配置就要與其它應用有差別,這時我們還是需要將配置從此模組中拆出來。

通過上面的例子,可以發現配置之所以從程式碼中提取出,其核心作用就是為了更好適應變化。因為共享配置存在以這些問題,而且微服務架構下,儘量還是以服務的方式來複用業務功能。再者我們一直要將程式碼進行解偶,那麼配置更需要進行解偶。

出於以上種種原因考慮,我們在設計配置中心時,也就沒有考慮設計以“組”的形式來共享配置。這也是我們設計時爭議比較大的地方。

配置內容

分為應用配置和全域性配置:

  • 全域性配置:是某一環境下所有應用共享的配置,比如公司的郵件服務配置;註冊中心地址、公司名稱、公司地址等,可能會變化,但普遍性非常高的配置。
  • 應用配置:每個應用個性化的配置;

為什麼還要全域性配置?這遇前面講的組共享配置不是衝突了嗎?

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

為什麼單個應用只支援單個配置?
微服務已經拆得比較小了,其配置內容也不會非常多,所以只設計為一個應用只有一個配置。而且經過我們的實踐呢,一個配置是可以滿足實際需要的。

支援版本控制

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

  • 配置每被修改一次,會將舊資料及版本號儲存到日誌表中,更新配置內容的同量,將版本號加一
  • 支援版本比較功能:方便檢視與最新版本的差異;檢查在哪天做了什麼調整;
  • 支援回退功能:如果配置出現問題,可以快速回退;

修改配置

不管是在內部推廣時,還是開源後,都有人問能支援properties嗎?其時最初版本是支援的,但我們在前端頁面把這個功能遮蔽了,因為我們決定只支援yaml格式。

  • 1.properties 對中文支援不是好,而yml卻沒有這個問題;
  • 2.yaml能很好管理同類項配置,避免配置重複key。看過不少properties檔案,配置雜亂以及同一個檔案出重相同的key,不同value的情況;不是所有的開發都是有強迫症;
  • 3.統一大家的習慣;
    隨行付微服務之配置中心ConfigKeeper

當Yml也不是完全沒有問題的,在實踐過程中,偶爾也出現有人把縮排搞錯的情況。

使用Yml線上編輯器,可以非常方便編輯,比如:複製貼上內容,就像在修改配置檔案一樣,尤其是批量修改時更為方便。不像其它通過key value方便管理的配置中心,每次修改都需要先找到相應的key才能進行一個個修改,非常費時費力;

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

隨行付微服務之配置中心ConfigKeeper

例項基本資訊及批量重新整理

不停機實時重新整理配置是配置中心的核心需求之一。比如在生產中執行的應用,突然因需求或效能等原因,需要調整配置,如果我們還需要經過修改程式碼,重新打包,測試並部署等一系列的操作步驟的話,那效率可想可知,因此帶來的損失也可能會非常之大。ConfigKeeper使用Spring Cloud提供的RefreshEndpoint重新整理配置,在最初的版本中,我們是通過curl或Postman等工具實現此功能,但這樣操作效率比較差,為此在最新版本中增加了如下功能:

隨行付微服務之配置中心ConfigKeeper

在此頁面,我們實現如下功能:

  • 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](https://my.oschina.net/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應用可載入的配置源非常之多,還需要注意一些問題。

下面是擷取docs.spring.io/spring-boot…中的內容:

  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 生態功能非常豐富,為我們解決了非常多棘手問題,但很多東西要進行本地化開發後才能更好的使用。配置中心使用了不少開源技術,給我們帶來了不少便利,希望通過此開源專案回饋社群,為開源社群貢獻綿薄之力。

github.com/sxfad/

gitee.com/sxfad/

ConfigKeeper QQ群:478814745

隨行付微服務之配置中心ConfigKeeper
本分類文章,與「隨行付研究院」微訊號文章同步,第一時間接收公眾號推送,請關注「隨行付研究院」公眾號。
複製程式碼
隨行付微服務之配置中心ConfigKeeper

相關文章