Apollo(阿波羅)是攜程開源的分布式配置中心,能夠集中化管理應(yīng)用不同環(huán)境、不同集群的配置,支持配置熱發(fā)布并實時推送到應(yīng)用端,并且具備規(guī)范的權(quán)限及流程治理等特性,適用于分布式微服務(wù)配置管理場景
Apollo配置中心介紹
程序功能日益復(fù)雜,程序配置日益增多:各種功能開關(guān)、參數(shù)配置、服務(wù)器地址...
對程序配置的期望也越來越高:熱部署并實時生效、灰度發(fā)布、分環(huán)境分集群管理配置、完善的權(quán)限審核機制...
在這樣的背景下,Apollo配置中心應(yīng)運而生。Apollo支持四個維度Key-Value格式的配置
*** Application(應(yīng)用)** 實際使用配置的應(yīng)用,Apollo客戶端在運行時需要知道當(dāng)前應(yīng)用是誰,從而可以去獲取對應(yīng)的配置。每個應(yīng)用都有對應(yīng)的身份標(biāo)識--appId,需要在代碼中配置
- **Environment(環(huán)境) ** 配置對應(yīng)的環(huán)境,Apollo客戶端需要知道當(dāng)前應(yīng)用出于哪個環(huán)境,,從而可以去獲取應(yīng)用的配置;環(huán)境和代碼無關(guān),同一份代碼部署在不同的環(huán)境就應(yīng)該獲取不同環(huán)境的配置;環(huán)境默認(rèn)是通過讀取機器上的配置(server.properties的env屬性)指定的
- Cluster(集群) 一個應(yīng)用下不同實例的分組,例如按照不同數(shù)據(jù)中心劃分,把上海機房的實例分為一個集群、把深圳機房的實例分為一個集群;對于不同的Cluster,同一個配置可以有不一樣的值;集群默認(rèn)是通過讀取機器上的配置指定的(server.properties的idc屬性)
- **Namespace(命名空間) ** 一個應(yīng)用下不同配置的分組,是配置項的集合,可以簡單地把Namespace類別為(配置)文件,不同類型的配置存放在不同的文件中,例如數(shù)據(jù)庫配置文件、RPC配置文件、應(yīng)用自身的配置文件等;應(yīng)用可以直接讀取到公共組件的配置namespace,例如DAL、RPC等;應(yīng)用也可以通過繼承公共組件的配置namespace來對公共組件的配置做調(diào)整,如DAL的初始數(shù)據(jù)庫連接數(shù)
Apollo在創(chuàng)建項目的時候,都會默認(rèn)創(chuàng)建一個"application"的Namespace,"application"是個應(yīng)用自身使用的。例如Spring Boot中項目的默認(rèn)配置文件application.yaml,這里application.yaml就等同于"application"的Namespace。對于大多數(shù)應(yīng)用來說,"application"Namespace已經(jīng)能滿足日常配置使用場景
客戶端獲取"application"Namespace的代碼如下
Config config = ConfigService.getAppConfig()
客戶端獲取非"application"Namespace的代碼如下
Config config = ConfigService.getConfig(namespaceName)
Namespace的格式 配置文件有多種格式,properties、xml、yml、yaml、json等,同樣Namespace也具有這些格式
tips: 非properties格式的namespace,在客戶端使用時需要調(diào)用ConfigService.getConfigFile(String namespace, ConfigFileFormat configFileFormat)來獲取,如果使用Htpp接口直接調(diào)用時,對應(yīng)的namespace參數(shù)需要傳入namespace的名字加上后綴名,如datasource.json
Namespace的獲取權(quán)限分類 此處權(quán)限相是對于Apollo客戶端來說的
private(私有的)權(quán)限 private權(quán)限的Namespace,只能被所屬的應(yīng)用獲取到。一個應(yīng)用嘗試獲取其他應(yīng)用private的Namespace,Apollo客戶端會報"404"異常
public(公共的)權(quán)限 具有public權(quán)限的Namespace,能被任何應(yīng)用獲取
Namespace的類型
私有類型 具有private權(quán)限,例如上文中提到的"application"Namespace就是私有類型
公共類型 具有public權(quán)限,公共類型的Namespace相當(dāng)于游離于應(yīng)用之外的配置,且通過Namespace的名稱去標(biāo)識公共Namespace,所以公共Namespace的名稱必須全局唯一
使用場景 部門級別共享的配置、小組級別共享的配置、幾個項目之間共享的配置、中間件客戶端的配置
-
關(guān)聯(lián)類型(繼承類型) 具有private權(quán)限,關(guān)聯(lián)類型的Namespace繼承于公共類型的Namespace,用于覆蓋公共Namespace的某些配置。例如公共Namespace有兩個配置項
k1 = v1 k2 = v2然后應(yīng)用A有一個關(guān)聯(lián)類型的Namespace關(guān)聯(lián)此公共Namespace,且以新值v3覆蓋配置項k1。那么在應(yīng)用A實際運行時,獲取到的公共Namespace的配置為
k1 = v3 k2 = v2使用場景 假設(shè)RPC框架的配置(如:timeout)有以下要求
提供一份全公司默認(rèn)的配置,且可動態(tài)調(diào)整
-
RPC客戶端項目可以自定義某些配置項且可動態(tài)調(diào)整
結(jié)合Apollo的公共類型的Namespace和關(guān)聯(lián)類型的Namespace。RPC團隊在Apollo上維護一個叫“rpc-client”的公共Namespace,在"rpc-client"Namespace上配置默認(rèn)的參數(shù)值。rpc-client.jar里的代碼讀取"rpc-client"Namespace的配置即可;如需要調(diào)整默認(rèn)的配置,只需要修改公共類型"rpc-client"Namespace的配置;如果客戶端項目想要自定義或動態(tài)修改某些配置項,只需要在Apollo自己項目下關(guān)聯(lián)"rpc-client",就能創(chuàng)建關(guān)聯(lián)類型"rpc-client"的Namespace,然后在關(guān)聯(lián)類型下修改配置項即可。這里rpc-client.jar是在應(yīng)用容器里運行的,所以rpc-client獲取到"rpc-client"Namespace的配置是應(yīng)用的關(guān)聯(lián)類型的Namespace加上公共類型的Namespace
例子 如下圖,有三個應(yīng)用:應(yīng)用A、應(yīng)用B、應(yīng)用C
應(yīng)用A有兩個私有類型的Namespace:application和NS-Private,以及一個關(guān)聯(lián)類型的Namespace:NS-Public
應(yīng)用B有一個私有類型的Namespace:application,以及一個公共類型的Namespace:NS-Public
應(yīng)用C只有一個私有類型的Namespace:application
應(yīng)用A獲取Apollo配置 //application Config appConfig = ConfigService.getAppConfig(); appConfig.getProperty("k1", null); // k1 = v11 appConfig.getProperty("k2", null); // k2 = v21 //NS-Private Config privateConfig = ConfigService.getConfig("NS-Private"); privateConfig.getProperty("k1", null); // k1 = v3 privateConfig.getProperty("k3", null); // k3 = v4 //NS-Public,覆蓋公共類型配置的情況,k4被覆蓋 Config publicConfig = ConfigService.getConfig("NS-Public"); publicConfig.getProperty("k4", null); // k4 = v6 cover publicConfig.getProperty("k6", null); // k6 = v6 publicConfig.getProperty("k7", null); // k7 = v7 應(yīng)用B獲取Apollo配置 //application Config appConfig = ConfigService.getAppConfig(); appConfig.getProperty("k1", null); // k1 = v12 appConfig.getProperty("k2", null); // k2 = null appConfig.getProperty("k3", null); // k3 = v32 //NS-Private,由于沒有NS-Private Namespace 所以獲取到default value Config privateConfig = ConfigService.getConfig("NS-Private"); privateConfig.getProperty("k1", "default value"); //NS-Public Config publicConfig = ConfigService.getConfig("NS-Public"); publicConfig.getProperty("k4", null); // k4 = v5 publicConfig.getProperty("k6", null); // k6 = v6 publicConfig.getProperty("k7", null); // k7 = v7 應(yīng)用C獲取Apollo配置 //application Config appConfig = ConfigService.getAppConfig(); appConfig.getProperty("k1", null); // k1 = v12 appConfig.getProperty("k2", null); // k2 = null appConfig.getProperty("k3", null); // k3 = v33 //NS-Private,由于沒有NS-Private Namespace 所以獲取到default value Config privateConfig = ConfigService.getConfig("NS-Private"); privateConfig.getProperty("k1", "default value"); //NS-Public,公共類型的Namespace,任何項目都可以獲取到 Config publicConfig = ConfigService.getConfig("NS-Public"); publicConfig.getProperty("k4", null); // k4 = v5 publicConfig.getProperty("k6", null); // k6 = v6 publicConfig.getProperty("k7", null); // k7 = v7
ChangeListener 以上代碼可以看出,在客戶端Namespace映射成一個Config對象,Namespace配置變更的監(jiān)聽器是注冊在Config對象上
## 在應(yīng)用A中監(jiān)聽application的Namespace代碼如下
Config appConfig = ConfigService.getAppConfig();
appConfig.addChangeListener(new ConfigChangeListener() {
public void onChange(ConfigChangeEvent changeEvent) {
//do something
}
})
在應(yīng)用A中監(jiān)聽 NS-Private 的 Namespace代碼如下
Config privateConfig = ConfigService.getConfig("NS-Private");
privateConfig.addChangeListener(new ConfigChangeListener() {
public void onChange(ConfigChangeEvent changeEvent) {
//do something
}
})
## 在應(yīng)用A、應(yīng)用B和應(yīng)用C中監(jiān)聽NS-Public Namespace代碼如下
Config publicConfig = ConfigService.getConfig("NS-Public");
publicConfig.addChangeListener(new ConfigChangeListener() {
public void onChange(ConfigChangeEvent changeEvent) {
//do something
}
})
配置的幾大屬性
- 配置是獨立于程序的只讀變量
- 配置首先是獨立于程序的,同一份程序在不同的配置下會有不同的行為
- 配置對于程序是只讀的,程序通過讀取配置來改變自己的行為,程序不應(yīng)該去改變配置
- 配置伴隨應(yīng)用的整個生命周期
- 配置貫穿于應(yīng)用的整個生命周期,應(yīng)用在啟動時通過讀取配置來初始化,在運行時根據(jù)配置來調(diào)整行為
- 配置可以有多種加載方式
- 常見的配置加載方式有程序內(nèi)部hard code、配置文件、環(huán)境變量、啟動參數(shù)、基于數(shù)據(jù)庫等
- 配置需要治理
- 權(quán)限控制 由于配置能改變程序行為,不正確的配置甚至能引起災(zāi)難,所以對配置的修改必須有比較完善的權(quán)限控制
- 不同環(huán)境、集群配置管理 同一份程序在不同的環(huán)境(開發(fā)、測試、生產(chǎn))、不同的集群(如不同的數(shù)據(jù)中心)可能有不同的配置,所以需要有完善的環(huán)境、集群配置管理
- 框架類組件配置管理 一類比較特殊的配置,通常是由其他團隊開發(fā)、維護,但是運行時是在業(yè)務(wù)實際應(yīng)用內(nèi)的,所以本質(zhì)上可以認(rèn)為框架類組件也是應(yīng)用的一部分,也需要比較完善的管理方式
基于配置的特殊性,Apollo從設(shè)計之初就立志于成為一個有治理能力的配置發(fā)布平臺,目前提供了以下的特性
- 統(tǒng)一管理不同環(huán)境、不同集群的配置
- Apollo提供了一個統(tǒng)一界面集中式管理不同環(huán)境(Environment)、不同集群(Cluster)、不同命名空間(Namespace)的配置
- 同一份代碼部署在不同的集群,可以有不同的配置
- 通過命名空間可以很方便地支持多個不同應(yīng)用共享同一份配置,同時還允許應(yīng)用對共享的配置進行覆蓋
- 熱發(fā)布--配置修改實時生效 用戶在Apollo修改完配置并發(fā)布后,客戶端能實時(1秒)接收到最新的配置,并通知到應(yīng)用程序
- 版本發(fā)布管理 所有的配置發(fā)布都有版本概念,從而可以方便地支持配置的回滾
- 灰度發(fā)布 支持配置的灰度發(fā)布,比如點了發(fā)布后,只對部分應(yīng)用實例生效,等觀察一段時間沒問題后再推給所有應(yīng)用實例
- 權(quán)限管理、發(fā)布審核、操作審計
- 應(yīng)用和配置的管理都有完善的權(quán)限管理機制,對配置的管理還分為了編輯和發(fā)布兩個環(huán)節(jié),從而減少人為的錯誤
- 所有的操作都有審計日志,可以方便地追蹤問題
- 客戶端配置信息監(jiān)控 可以在界面上方便地看到配置在被哪些實例使用
- 提供Java和.Net原生客戶端
- 提供了Java和.Net的原生客戶端,方便應(yīng)用集成
- 支持Spring Placeholder, Annotation和Spring Boot的ConfigurationProperties,方便應(yīng)用使用(需要Spring 3.1.1+)
- 同時提供了Http接口,非Java和.Net應(yīng)用也可以方便地使用
- 提供開放平臺API
- 部署簡單
配置獲取規(guī)則 僅當(dāng)應(yīng)用自定義了集群或namespace才需要。有了cluster概念后,配置的規(guī)則就顯得重要了,比如應(yīng)用部署在A機房,但是并沒有在Apollo新建cluster或者在運行時指定了cluster=SomeCluster,但是并沒有在Apollo新建cluster,這時候Apollo的行為是怎樣的?下面介紹配置獲取的規(guī)則
應(yīng)用自身配置的獲取規(guī)則
當(dāng)應(yīng)用使用下面的語句獲取配置時,稱之為獲取應(yīng)用自身的配置,也就是應(yīng)用自身的application namespace的配置
Config config = ConfigService.getAppConfig();
這種情況的配置獲取規(guī)則簡而言之如下
首先查找運行時cluster的配置(通過apollo.cluster指定)
如果沒有找到,則查找數(shù)據(jù)中心cluster的配置
-
如果還是沒有找到,則返回默認(rèn)cluster的配置
圖示如下
配置查找順序所以,如果應(yīng)用部署在A數(shù)據(jù)中心,但是用戶沒有在Apollo創(chuàng)建cluster,那么獲取的配置就是默認(rèn)cluster(default)的;如果應(yīng)用部署在A數(shù)據(jù)中心,同時在運行時指定了apollo.cluster=SomeCluster,但是沒有在Apollo創(chuàng)建cluster,那么獲取的配置就是A數(shù)據(jù)中心cluster的配置,如果A數(shù)據(jù)中心cluster沒有配置的話,那么獲取的配置就是默認(rèn)cluster(default)的
-
公共組件配置的獲取規(guī)則
以FX.Hermes.Producer為例,hermes producer是hermes發(fā)布的公共組件。當(dāng)使用下面的語句獲取配置時,稱之為獲取公共組件的配置Config config = ConfigService.getConfig("FX.Hermes.Producer")
對于這種情況獲取配置規(guī)則,簡而言之如下
- 首先獲取當(dāng)前應(yīng)用下的
FX.Hermes.Producernamespace的配置 - 然后獲取hermes應(yīng)用下
FX.Hermes.Producernamespace的配置 -
上面兩部分配置的并集就是最終使用的配置,如有key一樣的部分,應(yīng)當(dāng)以應(yīng)用優(yōu)先
圖示如下
公共組件配置獲取規(guī)則
通過這種方式實現(xiàn)對框架組件的配置管理,框架組件提供方提供配置的默認(rèn)值,應(yīng)用如果有特殊需求可以自行覆蓋
Apollo配置中心的設(shè)計
總體設(shè)計
- 基礎(chǔ)模型
- 用戶在配置中心對配置進行修改并發(fā)布
- 配置中心通知Apollo客戶端有配置更新
- Apollo客戶端從配置中心拉取最新的配置、更新本地配置并通知到應(yīng)用

-
架構(gòu)模塊
- Apollo包含七個模塊:四個功能相關(guān)的核心模塊和三個輔助服務(wù)發(fā)現(xiàn)的模塊
- 四個核心模塊及其主要功能
- ConfigService 提供配置獲取接口、提供配置推送接口、服務(wù)于Apollo客戶端
- AdminService 提供配置管理接口、提供配置修改發(fā)布接口、服務(wù)于管理界面Portal
- Client 為應(yīng)用獲取配置,支持實時更新、通過MetaServer獲取ConfigService的服務(wù)列表、使用客戶端軟負(fù)載SLB方式調(diào)用ConfigService
- Portal 配置管理界面、通過MetaServer獲取AdminService的服務(wù)列表、使用客戶端軟負(fù)載SLB的方式調(diào)用AdminService
- 三個輔助服務(wù)發(fā)現(xiàn)模塊
- Eureka 用于服務(wù)發(fā)現(xiàn)和注冊、ConfigService和AdminService注冊實例并定期上報心跳、和ConfigService部署于同一個進程
- MetaServer Portal通過域名訪問MetaServer獲取AdminService的地址列表、Client通過域名訪問MetaServer獲取ConfigService的地址列表、相當(dāng)于一個EurekaProxy、是一個邏輯角色和ConfigService部署于同一個進程
-
NginxLB 和域名系統(tǒng)配合,協(xié)助Portal訪問MetaServer獲取AdminService地址列表、和域名系統(tǒng)配合,協(xié)助Client訪問MetaServer獲取ConfigService地址列表、和域名系統(tǒng)配合,協(xié)助用戶訪問Portal進行配置管理
Apollo架構(gòu)
-
架構(gòu)剖析
Apollo架構(gòu)V1 如果不考慮分布式微服務(wù)架構(gòu)中的服務(wù)發(fā)現(xiàn)問題,Apollo的最簡架構(gòu)如下圖所示
Apollo架構(gòu)V1
要點- ConfigService是一個獨立的微服務(wù),服務(wù)于Client進行配置獲取
- Client和ConfigService保持長連接,通過一種推拉結(jié)合(push & pull)的模式,在實現(xiàn)配置實時更新的同時,保證配置更新不丟失
- AdminService是一個獨立的微服務(wù),服務(wù)于Portal進行配置管理。Portal通過調(diào)用AdminService進行配置管理和發(fā)布
- ConfigService和AdminService共享ConfigDB,ConfigDB中存放項目在某個環(huán)境中的配置信息。ConfigService/AdminService/ConfigDB三者在每個環(huán)境(DEV/FAT/UAT/PRO)中都要部署一份
- Protal有一個獨立的PortalDB,存放用戶權(quán)限、項目和配置的元數(shù)據(jù)信息。Protal只需部署一份,它可以管理多套環(huán)境
Apollo架構(gòu) V2 為了保證高可用,ConfigService和AdminService都是無狀態(tài)以集群方式部署的,這時候就存在一個服務(wù)發(fā)現(xiàn)的問題:Client怎么找到ConfigService?Portal怎么找到AdminService?為了解決這個問題,Apollo在其架構(gòu)中引入Eureka服務(wù)注冊中心組件,實現(xiàn)微服務(wù)間的服務(wù)注冊和發(fā)現(xiàn),更新后的架構(gòu)如下圖所示
Apollo架構(gòu)V2
要點- ConfigService和AdminService啟動后都會注冊到Eureka服務(wù)注冊中心,并定期發(fā)送存活心跳
- Eureka采用集群方式部署,使用分布式一致性協(xié)議保證每個實例的狀態(tài)最終一致
Apollo架構(gòu)V3 Eureka是自帶服務(wù)發(fā)現(xiàn)的Java客戶端的,如果Apollo只支持Java客戶端接入,不支持其它語言客戶端接入的話,那么Client和Portal只需要引入Eureka的Java客戶端,就可以實現(xiàn)服務(wù)發(fā)現(xiàn)功能。發(fā)現(xiàn)目標(biāo)服務(wù)后,通過客戶端軟負(fù)載(SLB,例如Ribbon)就可以路由到目標(biāo)服務(wù)實例。這是一個經(jīng)典的微服務(wù)架構(gòu),基于Eureka實現(xiàn)服務(wù)注冊發(fā)現(xiàn)+客戶端Ribbon配合實現(xiàn)軟路由,如下圖所示

Apollo架構(gòu)V4
為支持多語言客戶端接入,Apollo引入MetaServer角色,它其實是一個Eureka的Proxy,將Eureka的服務(wù)發(fā)現(xiàn)接口以更簡單明確的HTTP接口的形式暴露出來,方便Client/Protal通過簡單的HTTPClient就可以查詢到ConfigService/AdminService的地址列表。獲取到服務(wù)實例地址列表之后,再以簡單的客戶端軟負(fù)載(Client SLB)策略路由定位到目標(biāo)實例,并發(fā)起調(diào)用
另一個問題,MetaServer本身也是無狀態(tài)以集群方式部署的,那么Client/Protal該如何發(fā)現(xiàn)MetaServer呢?一種傳統(tǒng)的做法是借助硬件或者軟件負(fù)載均衡器,在攜程采用的是擴展后的NginxLB(Software Load Balancer),由運維為MetaServer集群配置一個域名,指向NginxLB集群,NginxLB再對MetaServer進行負(fù)載均衡和流量轉(zhuǎn)發(fā)。Client/Portal通過域名+NginxLB間接訪問MetaServer集群
引入MetaServer和NginxLB之后的架構(gòu)如下圖

Apollo架構(gòu)V5
還剩下最后一個環(huán)節(jié),Portal也是無狀態(tài)的以集群方式部署的,用戶如何發(fā)現(xiàn)和訪問Portal?答案也是簡單的傳統(tǒng)做法,用戶通過域名+NginxLB間接訪問Portal集群。所以V5版本是包括用戶端的最終的Apollo架構(gòu)全貌,如下圖所示

-
服務(wù)端設(shè)計
配置發(fā)布后的實時推送設(shè)計 在配置中心中,一個重要的功能就是配置發(fā)布后實時推送到客戶端。下面我們簡要看一下這塊是怎么設(shè)計實現(xiàn)的
服務(wù)端設(shè)計上圖簡要描述了配置發(fā)布的大致過程 1. 用戶在Portal操作發(fā)布配置 2. Portal調(diào)用Admin Service的接口操作發(fā)布 3. Admin Service發(fā)布配置后,發(fā)送ReleaseMessage給各Config Service 4. Config Service收到ReleaseMessage后通知對應(yīng)的客戶端發(fā)送ReleaseMessage的實現(xiàn)方式 Admin Service在配置發(fā)布后,需要通知所有的Config Service有配置發(fā)布,從而Config Service可以通知對應(yīng)的客戶端來拉取最新的配置。從概念上看,這是一個典型的消息使用場景,Admin Service作為Producer發(fā)出消息,各個Config Service作為consumer消費消息。通過一個消息組件(Message Queue)就能很好地實現(xiàn)Admin Service和Config Service的解耦。在實現(xiàn)上,Apollo為盡量減少外部依賴,沒有采用外部的消息中間件,而是通過數(shù)據(jù)庫實現(xiàn)了一個簡單的消息隊列
實現(xiàn)方式如下
1. Admin Service在配置發(fā)布后會往ReleaseMessage表插入一條消息記錄,消息內(nèi)容就是配置發(fā)布的AppId+Cluster+Namespace
2. Config Service有一個線程會每秒掃描一次ReleaseMessage表,看是否有新的消息記
3. Config Service如果發(fā)現(xiàn)有新的消息記錄,那么會通知到所有的消息監(jiān)聽器(ReleaseMessageListener),例如NotificationControllerV2
4. 消息監(jiān)聽器得到配置發(fā)布的AppId+Cluster+Namespace后,會通知對應(yīng)的客戶端
示意圖如下
配置更新通知
Config Service通知客戶端的實現(xiàn)方式 消息監(jiān)聽器在得知有新的配置發(fā)布后是如何通知到客戶端的呢?其實現(xiàn)方式如下
- 客戶端會發(fā)起一個Http請求到Config Service的notifications/v2接口,也就是NotificationControllerV2
- NotificationControllerV2不會立即返回結(jié)果,而是通過Spring DeferredResult把請求掛起
- 如果在60秒內(nèi)沒有該客戶端關(guān)心的配置發(fā)布,那么會返回Http狀態(tài)碼304給客戶端
- 如果有該客戶端關(guān)心的配置發(fā)布,NotificationControllerV2會調(diào)用DeferredResult的setResult方法,傳入有配置變化的namespace信息,同時該請求會立即返回??蛻舳藦姆祷氐慕Y(jié)果中獲取到配置變化的namespace后,會立即請求Config Service獲取該namespace的最新配置
客戶端設(shè)計

上圖簡要描述了Apollo客戶端的實現(xiàn)原理
- 客戶端和服務(wù)端保持了一個長連接(通過Http Long Polling實現(xiàn)),從而能第一時間獲得配置更新的推送
- 客戶端還會定期從Apollo配置中心服務(wù)端拉取應(yīng)用的最新配置
- 這是一個fallback機制,為了防止推送機制失效導(dǎo)致配置不更新
- 客戶端定時拉取會上報本地版本,所有一般情況下,對于定時拉取的操作,服務(wù)端都會返回304-Not Modified
- 定時頻率默認(rèn)為每5分鐘拉取一次,客戶端也可以通過在運行時指定System Property: apollo.refreshInterval來覆蓋,單位為分鐘
- 客戶端從Apollo配置中心服務(wù)端獲取到應(yīng)用的最新配置后,會保存在內(nèi)存中
- 客戶端會把從服務(wù)端獲取到的配置在本地文件系統(tǒng)緩存一份,在遇到服務(wù)不可以或網(wǎng)絡(luò)不通時,依然能從本地恢復(fù)配置
- 應(yīng)用程序可以從Apollo客戶端獲取最新的配置、訂閱配置更新通知
Apollo使用指南
名稱解析
- 普通應(yīng)用 獨立運行的程序,如Web應(yīng)用程序、帶有main函數(shù)的程序
- 公共組件 指發(fā)布的類庫、客戶端程序,不會自己獨立運行,如Java的jar包
普通應(yīng)用接入指南
-
創(chuàng)建項目
- 1.進入apollo-portal主頁
- 2.點擊"創(chuàng)建項目"
- 3.輸入項目信息
部門 選擇應(yīng)用所在的部門
應(yīng)用AppId 用來表示應(yīng)用身份的唯一id,格式為string,需要和客戶端app.properties中配置的app.id對應(yīng)
應(yīng)用名稱 應(yīng)用名,僅用于界面展示
應(yīng)用負(fù)責(zé)人 選擇的人默認(rèn)會成為該項目的管理員,具備項目權(quán)限管理、集群創(chuàng)建、Namespace創(chuàng)建等權(quán)限 - 4.點擊提交 創(chuàng)建成功后默認(rèn)會跳轉(zhuǎn)到,項目首頁
項目權(quán)限分配
項目管理員權(quán)限 項目管理員可以管理項目的權(quán)限分配、可以創(chuàng)建集群、可以創(chuàng)建Namespace配置編輯發(fā)布權(quán)限
編輯權(quán)限允許用戶在Apollo界面上創(chuàng)建、修改、刪除配置
發(fā)布權(quán)限允許用戶在Apollo界面上發(fā)布、回滾配置添加配置項 編輯配置需要擁有這個Namespace的編輯權(quán)限,如果發(fā)現(xiàn)沒有新增配置按鈕,可以找項目管理員授權(quán)
發(fā)布配置 配置只有在發(fā)布后才會真的被應(yīng)用使用到,所以在編輯完配置后,需要發(fā)布配置。發(fā)布配置需要擁有這個Namespace的發(fā)布權(quán)限,如果發(fā)現(xiàn)沒有發(fā)布按鈕,可以找項目管理員授權(quán)
應(yīng)用讀取配置 配置發(fā)布成功后就可以通過Apollo客戶端讀取到配置了。Apollo目前提供Java客戶端,如果使用其他語言,也可以通過直接訪問Http接口獲取配置
回滾已發(fā)布配置 如果發(fā)現(xiàn)已發(fā)布的配置有問題,可以通過點擊"回滾"按鈕來將客戶端讀取到的配置回滾到上一個發(fā)布版本。這里的回滾機制類似于發(fā)布系統(tǒng),發(fā)布系統(tǒng)中的回滾操作是將部署到機器上的安裝包回滾到上一個部署的版本,但代碼倉庫中的代碼是不會回滾的,從而開發(fā)可以在修復(fù)代碼后重新發(fā)布。Apollo中的回滾也是類似的機制,點擊回滾后是將發(fā)布到客戶端的配置回滾到上一個已發(fā)布版本,也就是說客戶端讀取到的配置會恢復(fù)到上一個版本,但頁面上編輯狀態(tài)的配置是不會回滾的,從而開發(fā)可以在修復(fù)配置后重新發(fā)布
公共組件接入步驟
公共組件接入步驟幾乎與普通應(yīng)用接入一致,唯一的區(qū)別是公共組件需要建立自己的唯一Namespace
1.創(chuàng)建項目
2.項目管理員權(quán)限
3.創(chuàng)建Namespace
4.添加配置項
5.發(fā)布配置
6.應(yīng)用讀取配置
應(yīng)用覆蓋公共組件配置步驟
1.關(guān)聯(lián)公共組件Namespace
2.覆蓋公共組件配置
3.發(fā)布配置
多個AppId共享同一份配置
在一些情況下,盡管應(yīng)用本身不是公共組件,但還是需要在多個AppId之間共用同一份配置,這種情況下如果希望實現(xiàn)多個AppId使用同一份配置的話,基本概念和公共組件的配置是一致的。具體來說,就是在其中一個AppId下創(chuàng)建一個namespace,寫入公共的配置信息,然后在各個項目中讀取該namespace的配置即可;如果某個AppId需要覆蓋公共的配置信息,那么在該AppId下關(guān)聯(lián)公共的namespace并寫入需要覆蓋的配置即可
應(yīng)用接入策略
這里考慮非Java語言客戶端接入--直接通過Http接口獲取配置
-
通過帶緩存的HTTP接口從Apollo讀取配置
該接口會從緩存中獲取配置,適合頻率較高的配置拉取請求,如簡單的30秒輪詢一次配置。由于緩存最多會有一秒的延遲,所以如果需要配合配置推送通知實現(xiàn)實時更新配置的話,請參考不帶緩存的HTTP接口從Apollo讀取配置HTTP接口說明
URL {config_server_url}/configfiles/json/{appId}/{clusterName}/{namespaceName}?ip={clientIp}
Method GET
**參數(shù)說明 **
file**HTTP接口返回格式** 該HTTP接口返回的是JSON格式、UTF-8編碼,包含了對應(yīng)namespace中所有的配置項。返回內(nèi)容Sample如下 { "portal.elastic.document.type":"biz", "portal.elastic.cluster.name":"hermes-es-fws" } *TIPS 通過{configserverurl}/configfiles/{appId}/{clusterName}/{namespaceName}?ip={clientIp}可以獲取到properties形式的配置* -
不帶緩存的HTTP接口從Apollo讀取配置
該接口會直接從數(shù)據(jù)庫中獲取配置,可以配合配置推送通知實現(xiàn)實時更新配置URL {config_server_url}/configs/{appId}/{clusterName}/{namespaceName}?releaseKey={releaseKey}&ip={clientIp}
Method GET
參數(shù)說明
file
該HTTP接口返回的是JSON格式、UTF-8編碼。如果配置沒有變化(傳入的releaseKey和服務(wù)端的相等),則返回HttpStatus 304,Response Body為空;如果配置有變化,則會返回HttpStatus 200,Response Body為對應(yīng)namespace的meta信息以及其中所有的配置項。返回內(nèi)容Sample如下
{
"appId": "100004458",
"cluster": "default",
"namespaceName": "application",
"configurations": {
"portal.elastic.document.type":"biz",
"portal.elastic.cluster.name":"hermes-es-fws"
},
"releaseKey": "20170430092936-dee2d58e74515ff3"
}
-
應(yīng)用感知配置更新
Apollo提供了基于Http long polling的配置更新推送通知,第三方客戶端可以看自己實際的需求決定是否需要使用這個功能。如果對配置更新時間不是那么敏感的話,可以通過定時刷新來感知配置更新,刷新頻率可以視應(yīng)用自身情況來定,建議在30秒以上。如果需要做到實時感知配置更新(1秒)的話,可以參考下面的文檔實現(xiàn)配置更新推送的功能配置更新推送實現(xiàn)思路 建議參考Apollo的Java實現(xiàn)RemoteConfigLongPollService.java
初始化 首先需要確定哪些namespace需要配置更新推送,Apollo的實現(xiàn)方式是程序第一次獲取某個namespace的配置時就會來注冊一下,我們就知道有哪些namespace需要配置更新推送了。初始化后的結(jié)果就是得到一個notifications的Map,內(nèi)容是namespaceName -> notificationId(初始值為-1)。運行過程中如果發(fā)現(xiàn)有新的namespace需要配置更新推送,直接塞到notifications這個Map里面即可
請求服務(wù) 有了notifications這個Map之后,就可以請求服務(wù)了。這里先描述一下請求服務(wù)的邏輯,具體的URL參數(shù)和說明請參見后面的接口說明
1.請求遠端服務(wù),帶上自己的應(yīng)用信息以及notifications信息
2.服務(wù)端針對傳過來的每一個namespace和對應(yīng)的notificationId,檢查notificationId是否是最新的
3.如果都是最新的,則保持住請求60秒,如果60秒內(nèi)沒有配置變化,則返回HttpStatus 304。如果60秒內(nèi)有配置變化,則返回對應(yīng)namespace的最新notificationId, HttpStatus 200
4.如果傳過來的notifications信息中發(fā)現(xiàn)有notificationId比服務(wù)端老,則直接返回對應(yīng)namespace的最新notificationId, HttpStatus 200
5.客戶端拿到服務(wù)端返回后,判斷返回的HttpStatus
6.如果返回的HttpStatus是304,說明配置沒有變化,重新執(zhí)行第1步
7.如果返回的HttpStauts是200,說明配置有變化,針對變化的namespace重新去服務(wù)端拉取配置,參見1.3 通過不帶緩存的Http接口從Apollo讀取配置。同時更新notifications map中的notificationId。重新執(zhí)行第1步
HTTP接口說明
URL {config_server_url}/notifications/v2?appId={appId}&cluster={clusterName}¬ifications={notifications}
Method GET
參數(shù)說明

TIPS 由于服務(wù)端會hold住60秒,所以請確??蛻舳嗽L問服務(wù)端的超時時間要大于60秒;記得對參數(shù)進行URL Encode
HTTP返回格式 該Http接口返回的是JSON格式、UTF-8編碼,包含了有變化的namespace和最新的notificationId。返回內(nèi)容Sample如下
[{
"namespaceName": "application",
"notificationId": 101
}]
分布式部署指南
官方展示的部署策略,生產(chǎn)環(huán)境部署一套Apollo-Portal+ApolloPortalDB,其他環(huán)境(PRO、UAT、FAT、DEV)單獨部署MetaServer+AdminService+ConfigService,使用獨立數(shù)據(jù)庫ApolloConfigDB及應(yīng)用服務(wù);MetaServer和Config Service部署在同一個JVM進程內(nèi),Admin Service部署在同一臺服務(wù)器的另一個JVM進程內(nèi)。部署示例如下圖

網(wǎng)絡(luò)策略 分布式部署的時候,apollo-configservice和apollo-adminservice需要把自己的IP和端口注冊到Meta Server(apollo-configservice本身)。Apollo客戶端和Portal會從Meta Server獲取服務(wù)的地址(IP+PORT),然后通過服務(wù)地址直接訪問。apollo-configservice和apollo-adminservice是基于內(nèi)網(wǎng)可信網(wǎng)絡(luò)設(shè)計的,所以出于安全考慮,請不要將apollo-configservice和apollo-adminservice直接暴露在公網(wǎng)
部署步驟
創(chuàng)建數(shù)據(jù)庫 Apollo服務(wù)端依賴于MYSQL數(shù)據(jù)庫,所以需要事先創(chuàng)建并完成初始化
獲取安裝包 Apollo服務(wù)端安裝包共3個: Apollo-AdminService、Apollo-ConfigService、Apollo-Portal
部署Apollo服務(wù)端 獲取安裝包后就可以部署到測試和生產(chǎn)環(huán)境
小結(jié)
文章較為全面介紹開源分布式配置中心Apollo的設(shè)計、使用、應(yīng)用接入及部署方法,目前客戶端只有Java和.Net版本,其他語言客戶端的接入可以通過HTTP接口的方式定時拉取更新配置或通過Http Long Polling機制實時推送,實現(xiàn)應(yīng)用感知配置更新









