隨着程序功能的日益複雜,程序的配置日益增多:各類功能的開關、參數的配置、服務器的地址……java
對程序配置的指望值也愈來愈高:配置修改後實時生效,灰度發佈,分環境、分集羣管理配置,完善的權限、審覈機制……git
在這樣的大環境下,傳統的經過配置文件、數據庫等方式已經愈來愈沒法知足開發人員對配置管理的需求。github
Apollo配置中心應運而生!spring
Apollo(阿波羅)是攜程框架部門研發的開源配置管理中心,可以集中化管理應用不一樣環境、不一樣集羣的配置,配置修改後可以實時推送到應用端,而且具有規範的權限、流程治理等特性。數據庫
Apollo支持4個維度管理Key-Value格式的配置:api
同時,Apollo基於開源模式開發,開源地址:https://github.com/ctripcorp/apollo緩存
既然Apollo定位於配置中心,那麼在這裏有必要先簡單介紹一下什麼是配置。服務器
按照咱們的理解,配置有如下幾個屬性:網絡
正是基於配置的特殊性,因此Apollo從設計之初就立志於成爲一個有治理能力的配置管理平臺,目前提供瞭如下的特性:併發
以下便是Apollo的基礎模型:
上圖是Apollo配置中心中一個項目的配置首頁
用戶能夠經過配置中心界面方便的添加/修改配置項:
輸入配置信息:
經過配置中心發佈配置:
填寫發佈信息:
配置發佈後,就能在客戶端獲取到了,以Java API方式爲例,獲取配置的示例代碼以下。更多客戶端使用說明請參見Java客戶端使用指南。
Config config = ConfigService.getAppConfig(); Integer defaultRequestTimeout = 200; Integer requestTimeout = config.getIntProperty("request.timeout",defaultRequestTimeout);
經過上述獲取配置代碼,應用就能實時獲取到最新的配置了。
不過在某些場景下,應用還須要在配置變化時得到通知,好比數據庫鏈接的切換等,因此Apollo還提供了監聽配置變化的功能,Java示例以下:
Config config = ConfigService.getAppConfig(); config.addChangeListener(new ConfigChangeListener() { @Override public void onChange(ConfigChangeEvent changeEvent) { for (String key : changeEvent.changedKeys()) { ConfigChange change = changeEvent.getChange(key); System.out.println(String.format( "Found change - key: %s, oldValue: %s, newValue: %s, changeType: %s", change.getPropertyName(), change.getOldValue(), change.getNewValue(), change.getChangeType())); } } });
Apollo和Spring也能夠很方便地集成,只須要標註@EnableApolloConfig
後就能夠經過@Value
獲取配置信息:
@Configuration @EnableApolloConfig public class AppConfig {}
@Component public class SomeBean { @Value("${request.timeout:200}") private int timeout; @ApolloConfigChangeListener private void someChangeHandler(ConfigChangeEvent changeEvent) { if (changeEvent.isChanged("request.timeout")) { refreshTimeout(); } } }
經過上面的介紹,相信你們已經對Apollo有了一個初步的瞭解,而且相信已經覆蓋到了大部分的使用場景。
接下來會主要介紹Apollo的cluster管理(集羣)、namespace管理(命名空間)和對應的配置獲取規則。
在介紹高級特性前,咱們有必要先來了解一下Apollo中的幾個核心概念:
【本節內容僅對應用須要對不一樣集羣應用不一樣配置才須要,如沒有相關需求,能夠跳過本節】
好比咱們有應用在A數據中心和B數據中心都有部署,那麼若是但願兩個數據中心的配置不同的話,咱們能夠經過新建cluster來解決。
新建Cluster只有項目的管理員纔有權限,管理員能夠在頁面左側看到「添加集羣」按鈕。
點擊後就進入到集羣添加頁面,通常狀況下能夠按照數據中心來劃分集羣,如SHAJQ、SHAOY等。
不過也支持自定義集羣,好比能夠爲A機房的某一臺機器和B機房的某一臺機建立一個集羣,使用一套配置。
集羣添加成功後,就能夠爲該集羣添加配置了,首選須要按照下圖所示切換到SHAJQ集羣,以後配置添加流程和3.2添加/修改配置項同樣,這裏就再也不贅述了。
Apollo會默認使用應用實例所在的數據中心做爲cluster,因此若是二者一致的話,不須要額外配置。
若是cluster和數據中心不一致的話,那麼就須要經過System Property方式來指定運行時cluster:
apollo.cluster
爲全小寫【本節僅對公共組件配置或須要多個應用共享配置才須要,如沒有相關需求,能夠跳過本節】
若是應用有公共組件(如hermes-producer,cat-client等)供其它應用使用,就須要經過自定義namespace來實現公共組件的配置。
以hermes-producer爲例,須要先新建一個namespace,新建namespace只有項目的管理員纔有權限,管理員能夠在頁面左側看到「添加Namespace」按鈕。
點擊後就進入namespace添加頁面,Apollo會把應用所屬的部門做爲namespace的前綴,如FX。
Namespace建立完,須要選擇在哪些環境和集羣下使用
接下來在這個新建的namespace下添加配置項
添加完成後就能在FX.Hermes.Producer的namespace中看到配置。
對自定義namespace的配置獲取,稍有不一樣,須要程序傳入namespace的名字。更多客戶端使用說明請參見Java客戶端使用指南。
Config config = ConfigService.getConfig("FX.Hermes.Producer"); Integer defaultSenderBatchSize = 200; Integer senderBatchSize = config.getIntProperty("sender.batchsize", defaultSenderBatchSize);
Config config = ConfigService.getConfig("FX.Hermes.Producer"); config.addChangeListener(new ConfigChangeListener() { @Override public void onChange(ConfigChangeEvent changeEvent) { System.out.println("Changes for namespace " + changeEvent.getNamespace()); for (String key : changeEvent.changedKeys()) { ConfigChange change = changeEvent.getChange(key); System.out.println(String.format( "Found change - key: %s, oldValue: %s, newValue: %s, changeType: %s", change.getPropertyName(), change.getOldValue(), change.getNewValue(), change.getChangeType())); } } });
@Configuration @EnableApolloConfig("FX.Hermes.Producer") public class AppConfig {}
@Component public class SomeBean { @Value("${request.timeout:200}") private int timeout; @ApolloConfigChangeListener("FX.Hermes.Producer") private void someChangeHandler(ConfigChangeEvent changeEvent) { if (changeEvent.isChanged("request.timeout")) { refreshTimeout(); } } }
【本節僅當應用自定義了集羣或namespace才須要,如無相關需求,能夠跳過本節】
在有了cluster概念後,配置的規則就顯得重要了。
好比應用部署在A機房,可是並無在Apollo新建cluster,這個時候Apollo的行爲是怎樣的?
或者在運行時指定了cluster=SomeCluster,可是並無在Apollo新建cluster,這個時候Apollo的行爲是怎樣的?
接下來就來介紹一下配置獲取的規則。
當應用使用下面的語句獲取配置時,咱們稱之爲獲取應用自身的配置,也就是應用自身的application namespace的配置。
Config config = ConfigService.getAppConfig();
對這種狀況的配置獲取規則,簡而言之以下:
圖示以下:
因此若是應用部署在A數據中心,可是用戶沒有在Apollo建立cluster,那麼獲取的配置就是默認cluster(default)的。
若是應用部署在A數據中心,同時在運行時指定了SomeCluster,可是沒有在Apollo建立cluster,那麼獲取的配置就是A數據中心cluster的配置,若是A數據中心cluster沒有配置的話,那麼獲取的配置就是默認cluster(default)的。
以FX.Hermes.Producer爲例,hermes producer是hermes發佈的公共組件。當使用下面的語句獲取配置時,咱們稱之爲獲取公共組件的配置。
Config config = ConfigService.getConfig("FX.Hermes.Producer");
對這種狀況的配置獲取規則,簡而言之以下:
圖示以下:
經過這種方式,就實現了對框架類組件的配置管理,框架組件提供方提供配置的默認值,應用若是有特殊需求,能夠自行覆蓋。
上圖簡要描述了Apollo的整體設計,咱們能夠從下往上看:
爲何咱們採用Eureka做爲服務註冊中心,而不是使用傳統的zk、etcd呢?我大體總結了一下,有如下幾方面的緣由:
上圖簡要描述了Apollo客戶端的實現原理:
apollo.refreshInterval
來覆蓋,單位爲分鐘。前面提到了Apollo客戶端和服務端保持了一個長鏈接,從而能第一時間得到配置更新的推送。
長鏈接實際上咱們是經過Http Long Polling實現的,具體而言:
考慮到會有數萬客戶端向服務端發起長連,在服務端咱們使用了async servlet(Spring DeferredResult)來服務Http Long Polling請求。
配置中心做爲基礎服務,可用性要求很是高,下面的表格描述了不一樣場景下Apollo的可用性:
場景 | 影響 | 降級 | 緣由 |
---|---|---|---|
某臺config service下線 | 無影響 | Config service無狀態,客戶端重連其它config service | |
全部config service下線 | 客戶端沒法讀取最新配置,Portal無影響 | 客戶端重啓時,能夠讀取本地緩存配置文件 | |
某臺admin service下線 | 無影響 | Admin service無狀態,Portal重連其它admin service | |
全部admin service下線 | 客戶端無影響,portal沒法更新配置 | ||
某臺portal下線 | 無影響 | Portal域名經過slb綁定多臺服務器,重試後指向可用的服務器 | |
所有portal下線 | 客戶端無影響,portal沒法更新配置 | ||
某個數據中心下線 | 無影響 | 多數據中心部署,數據徹底同步,Meta Server/Portal域名經過slb自動切換到其它存活的數據中心 |
Apollo從開發之初就是以開源模式開發的,因此也很是歡迎有興趣、有餘力的朋友一塊兒加入進來。
服務端開發使用的是Java,基於Spring Cloud和Spring Boot框架。客戶端目前提供了Java和.Net兩種實現。
Github地址:https://github.com/ctripcorp/apollo
原文:http://nobodyiam.com/2016/07/09/introduction-to-apollo/