說明:此文章來自Apollo的github,僅以此收藏,方便日後溫習,附上地址:
https://github.com/ctripcorp/apollo/wiki/Apollo%E9%85%8D%E7%BD%AE%E4%B8%AD%E5%BF%83%E4%BB%8B%E7%BB%8D
1、What is Apollo
1.1 背景
隨着程序功能的日益複雜,程序的配置日益增多:各種功能的開關、參數的配置、服務器的地址……
對程序配置的期望值也越來越高:配置修改後實時生效,灰度發佈,分環境、分集羣管理配置,完善的權限、審覈機制……
在這樣的大環境下,傳統的通過配置文件、數據庫等方式已經越來越無法滿足開發人員對配置管理的需求。
Apollo配置中心應運而生!
1.2 Apollo簡介
Apollo(阿波羅)是攜程框架部門研發的開源配置管理中心,能夠集中化管理應用不同環境、不同集羣的配置,配置修改後能夠實時推送到應用端,並且具備規範的權限、流程治理等特性。
Apollo支持4個維度管理Key-Value格式的配置:
- application (應用)
- environment (環境)
- cluster (集羣)
- namespace (命名空間)
同時,Apollo基於開源模式開發,開源地址:https://github.com/ctripcorp/apollo
1.2 配置基本概念
既然Apollo定位於配置中心,那麼在這裏有必要先簡單介紹一下什麼是配置。
按照我們的理解,配置有以下幾個屬性:
-
配置是獨立於程序的只讀變量
- 配置首先是獨立於程序的,同一份程序在不同的配置下會有不同的行爲。
- 其次,配置對於程序是隻讀的,程序通過讀取配置來改變自己的行爲,但是程序不應該去改變配置。
- 常見的配置有:DB Connection Str、Thread Pool Size、Buffer Size、Request Timeout、Feature Switch、Server Urls等。
-
配置伴隨應用的整個生命週期
- 配置貫穿於應用的整個生命週期,應用在啓動時通過讀取配置來初始化,在運行時根據配置調整行爲。
-
配置可以有多種加載方式
- 配置也有很多種加載方式,常見的有程序內部hard code,配置文件,環境變量,啓動參數,基於數據庫等
-
配置需要治理
- 權限控制
- 由於配置能改變程序的行爲,不正確的配置甚至能引起災難,所以對配置的修改必須有比較完善的權限控制
- 不同環境、集羣配置管理
- 同一份程序在不同的環境(開發,測試,生產)、不同的集羣(如不同的數據中心)經常需要有不同的配置,所以需要有完善的環境、集羣配置管理
- 框架類組件配置管理
- 還有一類比較特殊的配置 - 框架類組件配置,比如CAT客戶端的配置。
- 雖然這類框架類組件是由其他團隊開發、維護,但是運行時是在業務實際應用內的,所以本質上可以認爲框架類組件也是應用的一部分。
- 這類組件對應的配置也需要有比較完善的管理方式。
- 權限控制
2、Why Apollo
正是基於配置的特殊性,所以Apollo從設計之初就立志於成爲一個有治理能力的配置發佈平臺,目前提供了以下的特性:
-
統一管理不同環境、不同集羣的配置
- Apollo提供了一個統一界面集中式管理不同環境(environment)、不同集羣(cluster)、不同命名空間(namespace)的配置。
- 同一份代碼部署在不同的集羣,可以有不同的配置,比如zookeeper的地址等
- 通過命名空間(namespace)可以很方便地支持多個不同應用共享同一份配置,同時還允許應用對共享的配置進行覆蓋
-
配置修改實時生效(熱發佈)
- 用戶在Apollo修改完配置併發布後,客戶端能實時(1秒)接收到最新的配置,並通知到應用程序
-
版本發佈管理
- 所有的配置發佈都有版本概念,從而可以方便地支持配置的回滾
-
灰度發佈
- 支持配置的灰度發佈,比如點了發佈後,只對部分應用實例生效,等觀察一段時間沒問題後再推給所有應用實例
-
權限管理、發佈審覈、操作審計
- 應用和配置的管理都有完善的權限管理機制,對配置的管理還分爲了編輯和發佈兩個環節,從而減少人爲的錯誤。
- 所有的操作都有審計日誌,可以方便地追蹤問題
-
客戶端配置信息監控
- 可以在界面上方便地看到配置在被哪些實例使用
-
提供Java和.Net原生客戶端
- 提供了Java和.Net的原生客戶端,方便應用集成
- 支持Spring Placeholder, Annotation和Spring Boot的ConfigurationProperties,方便應用使用(需要Spring 3.1.1+)
- 同時提供了Http接口,非Java和.Net應用也可以方便地使用
-
提供開放平臺API
- Apollo自身提供了比較完善的統一配置管理界面,支持多環境、多數據中心配置管理、權限、流程治理等特性。不過Apollo出於通用性考慮,不會對配置的修改做過多限制,只要符合基本的格式就能保存,不會針對不同的配置值進行針對性的校驗,如數據庫用戶名、密碼,Redis服務地址等
- 對於這類應用配置,Apollo支持應用方通過開放平臺API在Apollo進行配置的修改和發佈,並且具備完善的授權和權限控制
-
部署簡單
- 配置中心作爲基礎服務,可用性要求非常高,這就要求Apollo對外部依賴儘可能地少
- 目前唯一的外部依賴是MySQL,所以部署非常簡單,只要安裝好Java和MySQL就可以讓Apollo跑起來
- Apollo還提供了打包腳本,一鍵就可以生成所有需要的安裝包,並且支持自定義運行時參數
3、Apollo at a glance
3.1 基礎模型
如下即是Apollo的基礎模型:
- 用戶在配置中心對配置進行修改併發布
- 配置中心通知Apollo客戶端有配置更新
- Apollo客戶端從配置中心拉取最新的配置、更新本地配置並通知到應用
3.2 界面概覽
上圖是Apollo配置中心中一個項目的配置首頁
- 在頁面左上方的環境列表模塊展示了所有的環境和集羣,用戶可以隨時切換。
- 頁面中央展示了兩個namespace(application和FX.apollo)的配置信息,默認按照表格模式展示、編輯。用戶也可以切換到文本模式,以文件形式查看、編輯。
- 頁面上可以方便地進行發佈、回滾、灰度、授權、查看更改歷史和發佈歷史等操作
3.3 添加/修改配置項
用戶可以通過配置中心界面方便的添加/修改配置項,更多使用說明請參見應用接入指南
輸入配置信息:
3.4 發佈配置
通過配置中心發佈配置:
填寫發佈信息:
3.5 客戶端監聽配置變化(Java API樣例)
配置發佈後,就能在客戶端獲取到了,以Java爲例,獲取配置的示例代碼如下。Apollo客戶端還支持和Spring整合,更多客戶端使用說明請參見Java客戶端使用指南和.Net客戶端使用指南。
Config config = ConfigService.getAppConfig(); Integer defaultRequestTimeout = 200; Integer requestTimeout = config.getIntProperty("requestTimeout", defaultRequestTimeout);
3.6 客戶端監聽配置變化
通過上述獲取配置代碼,應用就能實時獲取到最新的配置了。
不過在某些場景下,應用還需要在配置變化時獲得通知,比如數據庫連接的切換等,所以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())); } } });
3.7 Spring集成樣例
Apollo和Spring也可以很方便地集成,只需要標註@EnableApolloConfig
後就可以通過@Value
獲取配置信息:
@Configuration @EnableApolloConfig public class AppConfig {}
@Component public class SomeBean { //timeout的值會自動更新 @Value("${request.timeout:200}") private int timeout; }
4、Apollo in depth
通過上面的介紹,相信大家已經對Apollo有了一個初步的瞭解,並且相信已經覆蓋到了大部分的使用場景。
接下來會主要介紹Apollo的cluster管理(集羣)、namespace管理(命名空間)和對應的配置獲取規則。
4.1 Core Concepts
在介紹高級特性前,我們有必要先來了解一下Apollo中的幾個核心概念:
-
application (應用)
- 這個很好理解,就是實際使用配置的應用,Apollo客戶端在運行時需要知道當前應用是誰,從而可以去獲取對應的配置
- 每個應用都需要有唯一的身份標識 -- appId,我們認爲應用身份是跟着代碼走的,所以需要在代碼中配置,具體信息請參見Java客戶端使用指南。
-
environment (環境)
- 配置對應的環境,Apollo客戶端在運行時需要知道當前應用處於哪個環境,從而可以去獲取應用的配置
- 我們認爲環境和代碼無關,同一份代碼部署在不同的環境就應該能夠獲取到不同環境的配置
- 所以環境默認是通過讀取機器上的配置(server.properties中的env屬性)指定的,不過爲了開發方便,我們也支持運行時通過System Property等指定,具體信息請參見Java客戶端使用指南。
-
cluster (集羣)
- 一個應用下不同實例的分組,比如典型的可以按照數據中心分,把上海機房的應用實例分爲一個集羣,把北京機房的應用實例分爲另一個集羣。
- 對不同的cluster,同一個配置可以有不一樣的值,如zookeeper地址。
- 集羣默認是通過讀取機器上的配置(server.properties中的idc屬性)指定的,不過也支持運行時通過System Property指定,具體信息請參見Java客戶端使用指南。
-
namespace (命名空間)
- 一個應用下不同配置的分組,可以簡單地把namespace類比爲文件,不同類型的配置存放在不同的文件中,如數據庫配置文件,RPC配置文件,應用自身的配置文件等
- 應用可以直接讀取到公共組件的配置namespace,如DAL,RPC等
- 應用也可以通過繼承公共組件的配置namespace來對公共組件的配置做調整,如DAL的初始數據庫連接數
4.2 自定義Cluster
【本節內容僅對應用需要對不同集羣應用不同配置才需要,如沒有相關需求,可以跳過本節】
比如我們有應用在A數據中心和B數據中心都有部署,那麼如果希望兩個數據中心的配置不一樣的話,我們可以通過新建cluster來解決。
4.2.1 新建Cluster
新建Cluster只有項目的管理員纔有權限,管理員可以在頁面左側看到“添加集羣”按鈕。
點擊後就進入到集羣添加頁面,一般情況下可以按照數據中心來劃分集羣,如SHAJQ、SHAOY等。
不過也支持自定義集羣,比如可以爲A機房的某一臺機器和B機房的某一臺機創建一個集羣,使用一套配置。
4.2.2 在Cluster中添加配置併發布
集羣添加成功後,就可以爲該集羣添加配置了,首先需要按照下圖所示切換到SHAJQ集羣,之後配置添加流程和3.2添加/修改配置項一樣,這裏就不再贅述了。
4.2.3 指定應用實例所屬的Cluster
Apollo會默認使用應用實例所在的數據中心作爲cluster,所以如果兩者一致的話,不需要額外配置。
如果cluster和數據中心不一致的話,那麼就需要通過System Property方式來指定運行時cluster:
- -Dapollo.cluster=SomeCluster
- 這裏注意
apollo.cluster
爲全小寫
4.3 自定義Namespace
【本節僅對公共組件配置或需要多個應用共享配置才需要,如沒有相關需求,可以跳過本節】
如果應用有公共組件(如hermes-producer,cat-client等)供其它應用使用,就需要通過自定義namespace來實現公共組件的配置。
4.3.1 新建Namespace
以hermes-producer爲例,需要先新建一個namespace,新建namespace只有項目的管理員纔有權限,管理員可以在頁面左側看到“添加Namespace”按鈕。
點擊後就進入namespace添加頁面,Apollo會把應用所屬的部門作爲namespace的前綴,如FX。
4.3.2 關聯到環境和集羣
Namespace創建完,需要選擇在哪些環境和集羣下使用
4.3.3 在Namespace中添加配置項
接下來在這個新建的namespace下添加配置項
添加完成後就能在FX.Hermes.Producer的namespace中看到配置。
4.3.4 發佈namespace的配置
4.3.5 客戶端獲取Namespace配置
對自定義namespace的配置獲取,稍有不同,需要程序傳入namespace的名字。Apollo客戶端還支持和Spring整合,更多客戶端使用說明請參見Java客戶端使用指南和.Net客戶端使用指南。
Config config = ConfigService.getConfig("FX.Hermes.Producer"); Integer defaultSenderBatchSize = 200; Integer senderBatchSize = config.getIntProperty("sender.batchsize", defaultSenderBatchSize);
4.3.6 客戶端監聽Namespace配置變化
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())); } } });
4.3.7 Spring集成樣例
@Configuration @EnableApolloConfig("FX.Hermes.Producer") public class AppConfig {}
@Component public class SomeBean { //timeout的值會自動更新 @Value("${request.timeout:200}") private int timeout; }
4.4 配置獲取規則
【本節僅當應用自定義了集羣或namespace才需要,如無相關需求,可以跳過本節】
在有了cluster概念後,配置的規則就顯得重要了。
比如應用部署在A機房,但是並沒有在Apollo新建cluster,這個時候Apollo的行爲是怎樣的?
或者在運行時指定了cluster=SomeCluster,但是並沒有在Apollo新建cluster,這個時候Apollo的行爲是怎樣的?
接下來就來介紹一下配置獲取的規則。
4.4.1 應用自身配置的獲取規則
當應用使用下面的語句獲取配置時,我們稱之爲獲取應用自身的配置,也就是應用自身的application namespace的配置。
Config config = ConfigService.getAppConfig();
對這種情況的配置獲取規則,簡而言之如下:
- 首先查找運行時cluster的配置(通過apollo.cluster指定)
- 如果沒有找到,則查找數據中心cluster的配置
- 如果還是沒有找到,則返回默認cluster的配置
圖示如下:
所以如果應用部署在A數據中心,但是用戶沒有在Apollo創建cluster,那麼獲取的配置就是默認cluster(default)的。
如果應用部署在A數據中心,同時在運行時指定了SomeCluster,但是沒有在Apollo創建cluster,那麼獲取的配置就是A數據中心cluster的配置,如果A數據中心cluster沒有配置的話,那麼獲取的配置就是默認cluster(default)的。
4.4.2 公共組件配置的獲取規則
以FX.Hermes.Producer
爲例,hermes producer是hermes發佈的公共組件。當使用下面的語句獲取配置時,我們稱之爲獲取公共組件的配置。
Config config = ConfigService.getConfig("FX.Hermes.Producer");
對這種情況的配置獲取規則,簡而言之如下:
- 首先獲取當前應用下的
FX.Hermes.Producer
namespace的配置 - 然後獲取hermes應用下
FX.Hermes.Producer
namespace的配置 - 上面兩部分配置的並集就是最終使用的配置,如有key一樣的部分,以當前應用優先
圖示如下:
通過這種方式,就實現了對框架類組件的配置管理,框架組件提供方提供配置的默認值,應用如果有特殊需求,可以自行覆蓋。
4.5 總體設計
上圖簡要描述了Apollo的總體設計,我們可以從下往上看:
- Config Service提供配置的讀取、推送等功能,服務對象是Apollo客戶端
- Admin Service提供配置的修改、發佈等功能,服務對象是Apollo Portal(管理界面)
- Config Service和Admin Service都是多實例、無狀態部署,所以需要將自己註冊到Eureka中並保持心跳
- 在Eureka之上我們架了一層Meta Server用於封裝Eureka的服務發現接口
- Client通過域名訪問Meta Server獲取Config Service服務列表(IP+Port),而後直接通過IP+Port訪問服務,同時在Client側會做load balance、錯誤重試
- Portal通過域名訪問Meta Server獲取Admin Service服務列表(IP+Port),而後直接通過IP+Port訪問服務,同時在Portal側會做load balance、錯誤重試
- 爲了簡化部署,我們實際上會把Config Service、Eureka和Meta Server三個邏輯角色部署在同一個JVM進程中
4.5.1 Why Eureka
爲什麼我們採用Eureka作爲服務註冊中心,而不是使用傳統的zk、etcd呢?我大致總結了一下,有以下幾方面的原因:
- 它提供了完整的Service Registry和Service Discovery實現
- 首先是提供了完整的實現,並且也經受住了Netflix自己的生產環境考驗,相對使用起來會比較省心。
- 和Spring Cloud無縫集成
- 我們的項目本身就使用了Spring Cloud和Spring Boot,同時Spring Cloud還有一套非常完善的開源代碼來整合Eureka,所以使用起來非常方便。
- 另外,Eureka還支持在我們應用自身的容器中啓動,也就是說我們的應用啓動完之後,既充當了Eureka的角色,同時也是服務的提供者。這樣就極大的提高了服務的可用性。
- 這一點是我們選擇Eureka而不是zk、etcd等的主要原因,爲了提高配置中心的可用性和降低部署複雜度,我們需要儘可能地減少外部依賴。
- Open Source
- 最後一點是開源,由於代碼是開源的,所以非常便於我們瞭解它的實現原理和排查問題。
4.6 客戶端設計
上圖簡要描述了Apollo客戶端的實現原理:
- 客戶端和服務端保持了一個長連接,從而能第一時間獲得配置更新的推送。
- 客戶端還會定時從Apollo配置中心服務端拉取應用的最新配置。
- 這是一個fallback機制,爲了防止推送機制失效導致配置不更新
- 客戶端定時拉取會上報本地版本,所以一般情況下,對於定時拉取的操作,服務端都會返回304 - Not Modified
- 定時頻率默認爲每5分鐘拉取一次,客戶端也可以通過在運行時指定System Property:
apollo.refreshInterval
來覆蓋,單位爲分鐘。
- 客戶端從Apollo配置中心服務端獲取到應用的最新配置後,會保存在內存中
- 客戶端會把從服務端獲取到的配置在本地文件系統緩存一份
- 在遇到服務不可用,或網絡不通的時候,依然能從本地恢復配置
- 應用程序從Apollo客戶端獲取最新的配置、訂閱配置更新通知
4.6.1 配置更新推送實現
前面提到了Apollo客戶端和服務端保持了一個長連接,從而能第一時間獲得配置更新的推送。
長連接實際上我們是通過Http Long Polling實現的,具體而言:
- 客戶端發起一個Http請求到服務端
- 服務端會保持住這個連接60秒
- 如果在60秒內有客戶端關心的配置變化,被保持住的客戶端請求會立即返回,並告知客戶端有配置變化的namespace信息,客戶端會據此拉取對應namespace的最新配置
- 如果在60秒內沒有客戶端關心的配置變化,那麼會返回Http狀態碼304給客戶端
- 客戶端在收到服務端請求後會立即重新發起連接,回到第一步
考慮到會有數萬客戶端向服務端發起長連,在服務端我們使用了async servlet(Spring DeferredResult)來服務Http Long Polling請求。
4.7 可用性考慮
配置中心作爲基礎服務,可用性要求非常高,下面的表格描述了不同場景下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自動切換到其它存活的數據中心 |
5、Contribute to Apollo
Apollo從開發之初就是以開源模式開發的,所以也非常歡迎有興趣、有餘力的朋友一起加入進來。
服務端開發使用的是Java,基於Spring Cloud和Spring Boot框架。客戶端目前提供了Java和.Net兩種實現。
Github地址:https://github.com/ctripcorp/apollo
歡迎大家發起Pull Request!