Apollo(阿波羅)是攜程開源的分布式配置中心蹭越,能夠集中化管理應(yīng)用不同環(huán)境汁汗、不同集群的配置,支持配置熱發(fā)布并實(shí)時(shí)推送到應(yīng)用端振劳,并且具備規(guī)范的權(quán)限及流程治理等特性,適用于分布式微服務(wù)配置管理場(chǎng)景
Apollo配置中心介紹
程序功能日益復(fù)雜油狂,程序配置日益增多:各種功能開關(guān)历恐、參數(shù)配置寸癌、服務(wù)器地址...
對(duì)程序配置的期望也越來越高:熱部署并實(shí)時(shí)生效、灰度發(fā)布弱贼、分環(huán)境分集群管理配置蒸苇、完善的權(quán)限審核機(jī)制...
在這樣的背景下,Apollo配置中心應(yīng)運(yùn)而生吮旅。Apollo支持四個(gè)維度Key-Value格式的配置
*** Application(應(yīng)用)** 實(shí)際使用配置的應(yīng)用溪烤,Apollo客戶端在運(yùn)行時(shí)需要知道當(dāng)前應(yīng)用是誰,從而可以去獲取對(duì)應(yīng)的配置庇勃。每個(gè)應(yīng)用都有對(duì)應(yīng)的身份標(biāo)識(shí)--appId檬嘀,需要在代碼中配置
- **Environment(環(huán)境) ** 配置對(duì)應(yīng)的環(huán)境,Apollo客戶端需要知道當(dāng)前應(yīng)用出于哪個(gè)環(huán)境责嚷,鸳兽,從而可以去獲取應(yīng)用的配置;環(huán)境和代碼無關(guān)罕拂,同一份代碼部署在不同的環(huán)境就應(yīng)該獲取不同環(huán)境的配置揍异;環(huán)境默認(rèn)是通過讀取機(jī)器上的配置(server.properties的env屬性)指定的
- Cluster(集群) 一個(gè)應(yīng)用下不同實(shí)例的分組,例如按照不同數(shù)據(jù)中心劃分爆班,把上海機(jī)房的實(shí)例分為一個(gè)集群衷掷、把深圳機(jī)房的實(shí)例分為一個(gè)集群;對(duì)于不同的Cluster蛋济,同一個(gè)配置可以有不一樣的值棍鳖;集群默認(rèn)是通過讀取機(jī)器上的配置指定的(server.properties的idc屬性)
- **Namespace(命名空間) ** 一個(gè)應(yīng)用下不同配置的分組,是配置項(xiàng)的集合碗旅,可以簡(jiǎn)單地把Namespace類別為(配置)文件渡处,不同類型的配置存放在不同的文件中,例如數(shù)據(jù)庫配置文件祟辟、RPC配置文件医瘫、應(yīng)用自身的配置文件等;應(yīng)用可以直接讀取到公共組件的配置namespace旧困,例如DAL醇份、RPC等;應(yīng)用也可以通過繼承公共組件的配置namespace來對(duì)公共組件的配置做調(diào)整吼具,如DAL的初始數(shù)據(jù)庫連接數(shù)
Apollo在創(chuàng)建項(xiàng)目的時(shí)候僚纷,都會(huì)默認(rèn)創(chuàng)建一個(gè)"application"的Namespace,"application"是個(gè)應(yīng)用自身使用的拗盒。例如Spring Boot中項(xiàng)目的默認(rèn)配置文件application.yaml怖竭,這里application.yaml就等同于"application"的Namespace。對(duì)于大多數(shù)應(yīng)用來說,"application"Namespace已經(jīng)能滿足日常配置使用場(chǎng)景
客戶端獲取"application"Namespace的代碼如下
Config config = ConfigService.getAppConfig()
客戶端獲取非"application"Namespace的代碼如下
Config config = ConfigService.getConfig(namespaceName)
Namespace的格式 配置文件有多種格式陡蝇,properties痊臭、xml哮肚、yml、yaml广匙、json等允趟,同樣Namespace也具有這些格式
tips: 非properties格式的namespace,在客戶端使用時(shí)需要調(diào)用ConfigService.getConfigFile(String namespace, ConfigFileFormat configFileFormat)
來獲取鸦致,如果使用Htpp接口直接調(diào)用時(shí)潮剪,對(duì)應(yīng)的namespace參數(shù)需要傳入namespace的名字加上后綴名,如datasource.json
Namespace的獲取權(quán)限分類 此處權(quán)限相是對(duì)于Apollo客戶端來說的
private(私有的)權(quán)限 private權(quán)限的Namespace蹋凝,只能被所屬的應(yīng)用獲取到鲁纠。一個(gè)應(yīng)用嘗試獲取其他應(yīng)用private的Namespace,Apollo客戶端會(huì)報(bào)"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)識(shí)公共Namespace捍壤,所以公共Namespace的名稱必須全局唯一
使用場(chǎng)景 部門級(jí)別共享的配置、小組級(jí)別共享的配置鞍爱、幾個(gè)項(xiàng)目之間共享的配置鹃觉、中間件客戶端的配置
-
關(guān)聯(lián)類型(繼承類型) 具有private權(quán)限,關(guān)聯(lián)類型的Namespace繼承于公共類型的Namespace睹逃,用于覆蓋公共Namespace的某些配置盗扇。例如公共Namespace有兩個(gè)配置項(xiàng)
k1 = v1 k2 = v2
然后應(yīng)用A有一個(gè)關(guān)聯(lián)類型的Namespace關(guān)聯(lián)此公共Namespace,且以新值v3覆蓋配置項(xiàng)k1沉填。那么在應(yīng)用A實(shí)際運(yùn)行時(shí)疗隶,獲取到的公共Namespace的配置為
k1 = v3 k2 = v2
使用場(chǎng)景 假設(shè)RPC框架的配置(如:timeout)有以下要求
提供一份全公司默認(rèn)的配置,且可動(dòng)態(tài)調(diào)整
-
RPC客戶端項(xiàng)目可以自定義某些配置項(xiàng)且可動(dòng)態(tài)調(diào)整
結(jié)合Apollo的公共類型的Namespace和關(guān)聯(lián)類型的Namespace翼闹。RPC團(tuán)隊(duì)在Apollo上維護(hù)一個(gè)叫“rpc-client”的公共Namespace斑鼻,在"rpc-client"Namespace上配置默認(rèn)的參數(shù)值。rpc-client.jar里的代碼讀取"rpc-client"Namespace的配置即可猎荠;如需要調(diào)整默認(rèn)的配置坚弱,只需要修改公共類型"rpc-client"Namespace的配置;如果客戶端項(xiàng)目想要自定義或動(dòng)態(tài)修改某些配置項(xiàng)关摇,只需要在Apollo自己項(xiàng)目下關(guān)聯(lián)"rpc-client"荒叶,就能創(chuàng)建關(guān)聯(lián)類型"rpc-client"的Namespace,然后在關(guān)聯(lián)類型下修改配置項(xiàng)即可输虱。這里rpc-client.jar是在應(yīng)用容器里運(yùn)行的些楣,所以rpc-client獲取到"rpc-client"Namespace的配置是應(yīng)用的關(guān)聯(lián)類型的Namespace加上公共類型的Namespace
例子 如下圖,有三個(gè)應(yīng)用:應(yīng)用A、應(yīng)用B戈毒、應(yīng)用C
應(yīng)用A有兩個(gè)私有類型的Namespace:application和NS-Private,以及一個(gè)關(guān)聯(lián)類型的Namespace:NS-Public
應(yīng)用B有一個(gè)私有類型的Namespace:application横堡,以及一個(gè)公共類型的Namespace:NS-Public
應(yīng)用C只有一個(gè)私有類型的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胸蛛,任何項(xiàng)目都可以獲取到 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映射成一個(gè)Config對(duì)象,Namespace配置變更的監(jiān)聽器是注冊(cè)在Config對(duì)象上
## 在應(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
}
})
配置的幾大屬性
- 配置是獨(dú)立于程序的只讀變量
- 配置首先是獨(dú)立于程序的泞当,同一份程序在不同的配置下會(huì)有不同的行為
- 配置對(duì)于程序是只讀的,程序通過讀取配置來改變自己的行為民珍,程序不應(yīng)該去改變配置
- 配置伴隨應(yīng)用的整個(gè)生命周期
- 配置貫穿于應(yīng)用的整個(gè)生命周期襟士,應(yīng)用在啟動(dòng)時(shí)通過讀取配置來初始化,在運(yùn)行時(shí)根據(jù)配置來調(diào)整行為
- 配置可以有多種加載方式
- 常見的配置加載方式有程序內(nèi)部hard code嚷量、配置文件陋桂、環(huán)境變量、啟動(dòng)參數(shù)蝶溶、基于數(shù)據(jù)庫等
- 配置需要治理
- 權(quán)限控制 由于配置能改變程序行為嗜历,不正確的配置甚至能引起災(zāi)難,所以對(duì)配置的修改必須有比較完善的權(quán)限控制
- 不同環(huán)境抖所、集群配置管理 同一份程序在不同的環(huán)境(開發(fā)梨州、測(cè)試、生產(chǎn))部蛇、不同的集群(如不同的數(shù)據(jù)中心)可能有不同的配置摊唇,所以需要有完善的環(huán)境、集群配置管理
- 框架類組件配置管理 一類比較特殊的配置涯鲁,通常是由其他團(tuán)隊(duì)開發(fā)巷查、維護(hù),但是運(yùn)行時(shí)是在業(yè)務(wù)實(shí)際應(yīng)用內(nèi)的抹腿,所以本質(zhì)上可以認(rèn)為框架類組件也是應(yīng)用的一部分岛请,也需要比較完善的管理方式
基于配置的特殊性,Apollo從設(shè)計(jì)之初就立志于成為一個(gè)有治理能力的配置發(fā)布平臺(tái)警绩,目前提供了以下的特性
- 統(tǒng)一管理不同環(huán)境崇败、不同集群的配置
- Apollo提供了一個(gè)統(tǒng)一界面集中式管理不同環(huán)境(Environment)、不同集群(Cluster)、不同命名空間(Namespace)的配置
- 同一份代碼部署在不同的集群后室,可以有不同的配置
- 通過命名空間可以很方便地支持多個(gè)不同應(yīng)用共享同一份配置缩膝,同時(shí)還允許應(yīng)用對(duì)共享的配置進(jìn)行覆蓋
- 熱發(fā)布--配置修改實(shí)時(shí)生效 用戶在Apollo修改完配置并發(fā)布后,客戶端能實(shí)時(shí)(1秒)接收到最新的配置岸霹,并通知到應(yīng)用程序
- 版本發(fā)布管理 所有的配置發(fā)布都有版本概念疾层,從而可以方便地支持配置的回滾
- 灰度發(fā)布 支持配置的灰度發(fā)布,比如點(diǎn)了發(fā)布后贡避,只對(duì)部分應(yīng)用實(shí)例生效痛黎,等觀察一段時(shí)間沒問題后再推給所有應(yīng)用實(shí)例
- 權(quán)限管理、發(fā)布審核刮吧、操作審計(jì)
- 應(yīng)用和配置的管理都有完善的權(quán)限管理機(jī)制湖饱,對(duì)配置的管理還分為了編輯和發(fā)布兩個(gè)環(huán)節(jié),從而減少人為的錯(cuò)誤
- 所有的操作都有審計(jì)日志杀捻,可以方便地追蹤問題
- 客戶端配置信息監(jiān)控 可以在界面上方便地看到配置在被哪些實(shí)例使用
- 提供Java和.Net原生客戶端
- 提供了Java和.Net的原生客戶端井厌,方便應(yīng)用集成
- 支持Spring Placeholder, Annotation和Spring Boot的ConfigurationProperties,方便應(yīng)用使用(需要Spring 3.1.1+)
- 同時(shí)提供了Http接口致讥,非Java和.Net應(yīng)用也可以方便地使用
- 提供開放平臺(tái)API
- 部署簡(jiǎn)單
配置獲取規(guī)則 僅當(dāng)應(yīng)用自定義了集群或namespace才需要旗笔。有了cluster概念后,配置的規(guī)則就顯得重要了拄踪,比如應(yīng)用部署在A機(jī)房蝇恶,但是并沒有在Apollo新建cluster或者在運(yùn)行時(shí)指定了cluster=SomeCluster,但是并沒有在Apollo新建cluster惶桐,這時(shí)候Apollo的行為是怎樣的撮弧?下面介紹配置獲取的規(guī)則
應(yīng)用自身配置的獲取規(guī)則
當(dāng)應(yīng)用使用下面的語句獲取配置時(shí),稱之為獲取應(yīng)用自身的配置姚糊,也就是應(yīng)用自身的application namespace的配置
Config config = ConfigService.getAppConfig();
這種情況的配置獲取規(guī)則簡(jiǎn)而言之如下
首先查找運(yùn)行時(shí)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ù)中心擎淤,同時(shí)在運(yùn)行時(shí)指定了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)使用下面的語句獲取配置時(shí)赌结,稱之為獲取公共組件的配置Config config = ConfigService.getConfig("FX.Hermes.Producer")
對(duì)于這種情況獲取配置規(guī)則,簡(jiǎn)而言之如下
- 首先獲取當(dāng)前應(yīng)用下的
FX.Hermes.Producer
namespace的配置 - 然后獲取hermes應(yīng)用下
FX.Hermes.Producer
namespace的配置 -
上面兩部分配置的并集就是最終使用的配置孝冒,如有key一樣的部分柬姚,應(yīng)當(dāng)以應(yīng)用優(yōu)先
圖示如下
通過這種方式實(shí)現(xiàn)對(duì)框架組件的配置管理,框架組件提供方提供配置的默認(rèn)值庄涡,應(yīng)用如果有特殊需求可以自行覆蓋
Apollo配置中心的設(shè)計(jì)
總體設(shè)計(jì)
- 基礎(chǔ)模型
- 用戶在配置中心對(duì)配置進(jìn)行修改并發(fā)布
- 配置中心通知Apollo客戶端有配置更新
- Apollo客戶端從配置中心拉取最新的配置伤靠、更新本地配置并通知到應(yīng)用
-
架構(gòu)模塊
- Apollo包含七個(gè)模塊:四個(gè)功能相關(guān)的核心模塊和三個(gè)輔助服務(wù)發(fā)現(xiàn)的模塊
- 四個(gè)核心模塊及其主要功能
- ConfigService 提供配置獲取接口、提供配置推送接口啼染、服務(wù)于Apollo客戶端
- AdminService 提供配置管理接口、提供配置修改發(fā)布接口焕梅、服務(wù)于管理界面Portal
- Client 為應(yīng)用獲取配置迹鹅,支持實(shí)時(shí)更新、通過MetaServer獲取ConfigService的服務(wù)列表贞言、使用客戶端軟負(fù)載SLB方式調(diào)用ConfigService
- Portal 配置管理界面斜棚、通過MetaServer獲取AdminService的服務(wù)列表、使用客戶端軟負(fù)載SLB的方式調(diào)用AdminService
- 三個(gè)輔助服務(wù)發(fā)現(xiàn)模塊
- Eureka 用于服務(wù)發(fā)現(xiàn)和注冊(cè)该窗、ConfigService和AdminService注冊(cè)實(shí)例并定期上報(bào)心跳弟蚀、和ConfigService部署于同一個(gè)進(jìn)程
- MetaServer Portal通過域名訪問MetaServer獲取AdminService的地址列表、Client通過域名訪問MetaServer獲取ConfigService的地址列表酗失、相當(dāng)于一個(gè)EurekaProxy义钉、是一個(gè)邏輯角色和ConfigService部署于同一個(gè)進(jìn)程
-
NginxLB 和域名系統(tǒng)配合,協(xié)助Portal訪問MetaServer獲取AdminService地址列表规肴、和域名系統(tǒng)配合捶闸,協(xié)助Client訪問MetaServer獲取ConfigService地址列表、和域名系統(tǒng)配合拖刃,協(xié)助用戶訪問Portal進(jìn)行配置管理
-
架構(gòu)剖析
Apollo架構(gòu)V1 如果不考慮分布式微服務(wù)架構(gòu)中的服務(wù)發(fā)現(xiàn)問題删壮,Apollo的最簡(jiǎn)架構(gòu)如下圖所示
要點(diǎn)- ConfigService是一個(gè)獨(dú)立的微服務(wù),服務(wù)于Client進(jìn)行配置獲取
- Client和ConfigService保持長(zhǎng)連接兑牡,通過一種推拉結(jié)合(push & pull)的模式央碟,在實(shí)現(xiàn)配置實(shí)時(shí)更新的同時(shí),保證配置更新不丟失
- AdminService是一個(gè)獨(dú)立的微服務(wù)均函,服務(wù)于Portal進(jìn)行配置管理亿虽。Portal通過調(diào)用AdminService進(jìn)行配置管理和發(fā)布
- ConfigService和AdminService共享ConfigDB,ConfigDB中存放項(xiàng)目在某個(gè)環(huán)境中的配置信息苞也。ConfigService/AdminService/ConfigDB三者在每個(gè)環(huán)境(DEV/FAT/UAT/PRO)中都要部署一份
- Protal有一個(gè)獨(dú)立的PortalDB经柴,存放用戶權(quán)限、項(xiàng)目和配置的元數(shù)據(jù)信息墩朦。Protal只需部署一份坯认,它可以管理多套環(huán)境
Apollo架構(gòu) V2 為了保證高可用,ConfigService和AdminService都是無狀態(tài)以集群方式部署的,這時(shí)候就存在一個(gè)服務(wù)發(fā)現(xiàn)的問題:Client怎么找到ConfigService牛哺?Portal怎么找到AdminService陋气?為了解決這個(gè)問題,Apollo在其架構(gòu)中引入Eureka服務(wù)注冊(cè)中心組件引润,實(shí)現(xiàn)微服務(wù)間的服務(wù)注冊(cè)和發(fā)現(xiàn)巩趁,更新后的架構(gòu)如下圖所示
要點(diǎn)- ConfigService和AdminService啟動(dòng)后都會(huì)注冊(cè)到Eureka服務(wù)注冊(cè)中心,并定期發(fā)送存活心跳
- Eureka采用集群方式部署淳附,使用分布式一致性協(xié)議保證每個(gè)實(shí)例的狀態(tài)最終一致
Apollo架構(gòu)V3 Eureka是自帶服務(wù)發(fā)現(xiàn)的Java客戶端的议慰,如果Apollo只支持Java客戶端接入,不支持其它語言客戶端接入的話奴曙,那么Client和Portal只需要引入Eureka的Java客戶端别凹,就可以實(shí)現(xiàn)服務(wù)發(fā)現(xiàn)功能。發(fā)現(xiàn)目標(biāo)服務(wù)后洽糟,通過客戶端軟負(fù)載(SLB炉菲,例如Ribbon)就可以路由到目標(biāo)服務(wù)實(shí)例。這是一個(gè)經(jīng)典的微服務(wù)架構(gòu)坤溃,基于Eureka實(shí)現(xiàn)服務(wù)注冊(cè)發(fā)現(xiàn)+客戶端Ribbon配合實(shí)現(xiàn)軟路由拍霜,如下圖所示
Apollo架構(gòu)V4
為支持多語言客戶端接入,Apollo引入MetaServer角色薪介,它其實(shí)是一個(gè)Eureka的Proxy祠饺,將Eureka的服務(wù)發(fā)現(xiàn)接口以更簡(jiǎn)單明確的HTTP接口的形式暴露出來,方便Client/Protal通過簡(jiǎn)單的HTTPClient就可以查詢到ConfigService/AdminService的地址列表汁政。獲取到服務(wù)實(shí)例地址列表之后吠裆,再以簡(jiǎn)單的客戶端軟負(fù)載(Client SLB)策略路由定位到目標(biāo)實(shí)例,并發(fā)起調(diào)用
另一個(gè)問題烂完,MetaServer本身也是無狀態(tài)以集群方式部署的试疙,那么Client/Protal該如何發(fā)現(xiàn)MetaServer呢?一種傳統(tǒng)的做法是借助硬件或者軟件負(fù)載均衡器抠蚣,在攜程采用的是擴(kuò)展后的NginxLB(Software Load Balancer)祝旷,由運(yùn)維為MetaServer集群配置一個(gè)域名,指向NginxLB集群嘶窄,NginxLB再對(duì)MetaServer進(jìn)行負(fù)載均衡和流量轉(zhuǎn)發(fā)怀跛。Client/Portal通過域名+NginxLB間接訪問MetaServer集群
引入MetaServer和NginxLB之后的架構(gòu)如下圖
Apollo架構(gòu)V5
還剩下最后一個(gè)環(huán)節(jié),Portal也是無狀態(tài)的以集群方式部署的柄冲,用戶如何發(fā)現(xiàn)和訪問Portal吻谋?答案也是簡(jiǎn)單的傳統(tǒng)做法,用戶通過域名+NginxLB間接訪問Portal集群现横。所以V5版本是包括用戶端的最終的Apollo架構(gòu)全貌漓拾,如下圖所示
-
服務(wù)端設(shè)計(jì)
配置發(fā)布后的實(shí)時(shí)推送設(shè)計(jì) 在配置中心中阁最,一個(gè)重要的功能就是配置發(fā)布后實(shí)時(shí)推送到客戶端。下面我們簡(jiǎn)要看一下這塊是怎么設(shè)計(jì)實(shí)現(xiàn)的
上圖簡(jiǎn)要描述了配置發(fā)布的大致過程 1. 用戶在Portal操作發(fā)布配置 2. Portal調(diào)用Admin Service的接口操作發(fā)布 3. Admin Service發(fā)布配置后骇两,發(fā)送ReleaseMessage給各Config Service 4. Config Service收到ReleaseMessage后通知對(duì)應(yīng)的客戶端
發(fā)送ReleaseMessage的實(shí)現(xiàn)方式 Admin Service在配置發(fā)布后速种,需要通知所有的Config Service有配置發(fā)布,從而Config Service可以通知對(duì)應(yīng)的客戶端來拉取最新的配置低千。從概念上看配阵,這是一個(gè)典型的消息使用場(chǎng)景,Admin Service作為Producer發(fā)出消息示血,各個(gè)Config Service作為consumer消費(fèi)消息棋傍。通過一個(gè)消息組件(Message Queue)就能很好地實(shí)現(xiàn)Admin Service和Config Service的解耦。在實(shí)現(xiàn)上难审,Apollo為盡量減少外部依賴瘫拣,沒有采用外部的消息中間件,而是通過數(shù)據(jù)庫實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的消息隊(duì)列
實(shí)現(xiàn)方式如下
1. Admin Service在配置發(fā)布后會(huì)往ReleaseMessage表插入一條消息記錄剔宪,消息內(nèi)容就是配置發(fā)布的AppId+Cluster+Namespace
2. Config Service有一個(gè)線程會(huì)每秒掃描一次ReleaseMessage表,看是否有新的消息記
3. Config Service如果發(fā)現(xiàn)有新的消息記錄壹无,那么會(huì)通知到所有的消息監(jiān)聽器(ReleaseMessageListener)葱绒,例如NotificationControllerV2
4. 消息監(jiān)聽器得到配置發(fā)布的AppId+Cluster+Namespace后,會(huì)通知對(duì)應(yīng)的客戶端
示意圖如下
Config Service通知客戶端的實(shí)現(xiàn)方式 消息監(jiān)聽器在得知有新的配置發(fā)布后是如何通知到客戶端的呢斗锭?其實(shí)現(xiàn)方式如下
- 客戶端會(huì)發(fā)起一個(gè)Http請(qǐng)求到Config Service的notifications/v2接口地淀,也就是NotificationControllerV2
- NotificationControllerV2不會(huì)立即返回結(jié)果,而是通過Spring DeferredResult把請(qǐng)求掛起
- 如果在60秒內(nèi)沒有該客戶端關(guān)心的配置發(fā)布岖是,那么會(huì)返回Http狀態(tài)碼304給客戶端
- 如果有該客戶端關(guān)心的配置發(fā)布帮毁,NotificationControllerV2會(huì)調(diào)用DeferredResult的setResult方法,傳入有配置變化的namespace信息豺撑,同時(shí)該請(qǐng)求會(huì)立即返回烈疚。客戶端從返回的結(jié)果中獲取到配置變化的namespace后聪轿,會(huì)立即請(qǐng)求Config Service獲取該namespace的最新配置
客戶端設(shè)計(jì)
上圖簡(jiǎn)要描述了Apollo客戶端的實(shí)現(xiàn)原理
- 客戶端和服務(wù)端保持了一個(gè)長(zhǎng)連接(通過Http Long Polling實(shí)現(xiàn))爷肝,從而能第一時(shí)間獲得配置更新的推送
- 客戶端還會(huì)定期從Apollo配置中心服務(wù)端拉取應(yīng)用的最新配置
- 這是一個(gè)fallback機(jī)制,為了防止推送機(jī)制失效導(dǎo)致配置不更新
- 客戶端定時(shí)拉取會(huì)上報(bào)本地版本陆错,所有一般情況下灯抛,對(duì)于定時(shí)拉取的操作,服務(wù)端都會(huì)返回304-Not Modified
- 定時(shí)頻率默認(rèn)為每5分鐘拉取一次音瓷,客戶端也可以通過在運(yùn)行時(shí)指定System Property: apollo.refreshInterval來覆蓋对嚼,單位為分鐘
- 客戶端從Apollo配置中心服務(wù)端獲取到應(yīng)用的最新配置后,會(huì)保存在內(nèi)存中
- 客戶端會(huì)把從服務(wù)端獲取到的配置在本地文件系統(tǒng)緩存一份绳慎,在遇到服務(wù)不可以或網(wǎng)絡(luò)不通時(shí)纵竖,依然能從本地恢復(fù)配置
- 應(yīng)用程序可以從Apollo客戶端獲取最新的配置漠烧、訂閱配置更新通知
Apollo使用指南
名稱解析
- 普通應(yīng)用 獨(dú)立運(yùn)行的程序,如Web應(yīng)用程序磨确、帶有main函數(shù)的程序
- 公共組件 指發(fā)布的類庫沽甥、客戶端程序,不會(huì)自己獨(dú)立運(yùn)行乏奥,如Java的jar包
普通應(yīng)用接入指南
-
創(chuàng)建項(xiàng)目
- 1.進(jìn)入apollo-portal主頁
- 2.點(diǎn)擊"創(chuàng)建項(xiàng)目"
- 3.輸入項(xiàng)目信息
部門 選擇應(yīng)用所在的部門
應(yīng)用AppId 用來表示應(yīng)用身份的唯一id摆舟,格式為string,需要和客戶端app.properties中配置的app.id對(duì)應(yīng)
應(yīng)用名稱 應(yīng)用名邓了,僅用于界面展示
應(yīng)用負(fù)責(zé)人 選擇的人默認(rèn)會(huì)成為該項(xiàng)目的管理員恨诱,具備項(xiàng)目權(quán)限管理、集群創(chuàng)建骗炉、Namespace創(chuàng)建等權(quán)限 - 4.點(diǎn)擊提交 創(chuàng)建成功后默認(rèn)會(huì)跳轉(zhuǎn)到照宝,項(xiàng)目首頁
項(xiàng)目權(quán)限分配
項(xiàng)目管理員權(quán)限 項(xiàng)目管理員可以管理項(xiàng)目的權(quán)限分配、可以創(chuàng)建集群句葵、可以創(chuàng)建Namespace配置編輯發(fā)布權(quán)限
編輯權(quán)限允許用戶在Apollo界面上創(chuàng)建厕鹃、修改、刪除配置
發(fā)布權(quán)限允許用戶在Apollo界面上發(fā)布乍丈、回滾配置添加配置項(xiàng) 編輯配置需要擁有這個(gè)Namespace的編輯權(quán)限剂碴,如果發(fā)現(xiàn)沒有新增配置按鈕,可以找項(xiàng)目管理員授權(quán)
發(fā)布配置 配置只有在發(fā)布后才會(huì)真的被應(yīng)用使用到轻专,所以在編輯完配置后忆矛,需要發(fā)布配置。發(fā)布配置需要擁有這個(gè)Namespace的發(fā)布權(quán)限请垛,如果發(fā)現(xiàn)沒有發(fā)布按鈕催训,可以找項(xiàng)目管理員授權(quán)
應(yīng)用讀取配置 配置發(fā)布成功后就可以通過Apollo客戶端讀取到配置了。Apollo目前提供Java客戶端宗收,如果使用其他語言漫拭,也可以通過直接訪問Http接口獲取配置
回滾已發(fā)布配置 如果發(fā)現(xiàn)已發(fā)布的配置有問題,可以通過點(diǎn)擊"回滾"按鈕來將客戶端讀取到的配置回滾到上一個(gè)發(fā)布版本混稽。這里的回滾機(jī)制類似于發(fā)布系統(tǒng)嫂侍,發(fā)布系統(tǒng)中的回滾操作是將部署到機(jī)器上的安裝包回滾到上一個(gè)部署的版本,但代碼倉庫中的代碼是不會(huì)回滾的荚坞,從而開發(fā)可以在修復(fù)代碼后重新發(fā)布挑宠。Apollo中的回滾也是類似的機(jī)制,點(diǎn)擊回滾后是將發(fā)布到客戶端的配置回滾到上一個(gè)已發(fā)布版本颓影,也就是說客戶端讀取到的配置會(huì)恢復(fù)到上一個(gè)版本各淀,但頁面上編輯狀態(tài)的配置是不會(huì)回滾的,從而開發(fā)可以在修復(fù)配置后重新發(fā)布
公共組件接入步驟
公共組件接入步驟幾乎與普通應(yīng)用接入一致诡挂,唯一的區(qū)別是公共組件需要建立自己的唯一Namespace
1.創(chuàng)建項(xiàng)目
2.項(xiàng)目管理員權(quán)限
3.創(chuàng)建Namespace
4.添加配置項(xiàng)
5.發(fā)布配置
6.應(yīng)用讀取配置
應(yīng)用覆蓋公共組件配置步驟
1.關(guān)聯(lián)公共組件Namespace
2.覆蓋公共組件配置
3.發(fā)布配置
多個(gè)AppId共享同一份配置
在一些情況下碎浇,盡管應(yīng)用本身不是公共組件临谱,但還是需要在多個(gè)AppId之間共用同一份配置,這種情況下如果希望實(shí)現(xiàn)多個(gè)AppId使用同一份配置的話奴璃,基本概念和公共組件的配置是一致的悉默。具體來說,就是在其中一個(gè)AppId下創(chuàng)建一個(gè)namespace苟穆,寫入公共的配置信息抄课,然后在各個(gè)項(xiàng)目中讀取該namespace的配置即可;如果某個(gè)AppId需要覆蓋公共的配置信息雳旅,那么在該AppId下關(guān)聯(lián)公共的namespace并寫入需要覆蓋的配置即可
應(yīng)用接入策略
這里考慮非Java語言客戶端接入--直接通過Http接口獲取配置
-
通過帶緩存的HTTP接口從Apollo讀取配置
該接口會(huì)從緩存中獲取配置跟磨,適合頻率較高的配置拉取請(qǐng)求,如簡(jiǎn)單的30秒輪詢一次配置攒盈。由于緩存最多會(huì)有一秒的延遲抵拘,所以如果需要配合配置推送通知實(shí)現(xiàn)實(shí)時(shí)更新配置的話,請(qǐng)參考不帶緩存的HTTP接口從Apollo讀取配置HTTP接口說明
URL {config_server_url}/configfiles/json/{appId}/{clusterName}/{namespaceName}?ip={clientIp}
Method GET
**參數(shù)說明 **
**HTTP接口返回格式** 該HTTP接口返回的是JSON格式型豁、UTF-8編碼僵蛛,包含了對(duì)應(yīng)namespace中所有的配置項(xiàng)。返回內(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讀取配置
該接口會(huì)直接從數(shù)據(jù)庫中獲取配置迎变,可以配合配置推送通知實(shí)現(xiàn)實(shí)時(shí)更新配置URL {config_server_url}/configs/{appId}/{clusterName}/{namespaceName}?releaseKey={releaseKey}&ip={clientIp}
Method GET
參數(shù)說明
該HTTP接口返回的是JSON格式充尉、UTF-8編碼。如果配置沒有變化(傳入的releaseKey和服務(wù)端的相等)氏豌,則返回HttpStatus 304喉酌,Response Body為空热凹;如果配置有變化泵喘,則會(huì)返回HttpStatus 200,Response Body為對(duì)應(yīng)namespace的meta信息以及其中所有的配置項(xiàng)般妙。返回內(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的配置更新推送通知纪铺,第三方客戶端可以看自己實(shí)際的需求決定是否需要使用這個(gè)功能。如果對(duì)配置更新時(shí)間不是那么敏感的話碟渺,可以通過定時(shí)刷新來感知配置更新鲜锚,刷新頻率可以視應(yīng)用自身情況來定,建議在30秒以上苫拍。如果需要做到實(shí)時(shí)感知配置更新(1秒)的話芜繁,可以參考下面的文檔實(shí)現(xiàn)配置更新推送的功能配置更新推送實(shí)現(xiàn)思路 建議參考Apollo的Java實(shí)現(xiàn)RemoteConfigLongPollService.java
初始化 首先需要確定哪些namespace需要配置更新推送,Apollo的實(shí)現(xiàn)方式是程序第一次獲取某個(gè)namespace的配置時(shí)就會(huì)來注冊(cè)一下绒极,我們就知道有哪些namespace需要配置更新推送了骏令。初始化后的結(jié)果就是得到一個(gè)notifications的Map,內(nèi)容是namespaceName -> notificationId(初始值為-1)垄提。運(yùn)行過程中如果發(fā)現(xiàn)有新的namespace需要配置更新推送榔袋,直接塞到notifications這個(gè)Map里面即可
請(qǐng)求服務(wù) 有了notifications這個(gè)Map之后周拐,就可以請(qǐng)求服務(wù)了。這里先描述一下請(qǐng)求服務(wù)的邏輯凰兑,具體的URL參數(shù)和說明請(qǐng)參見后面的接口說明
1.請(qǐng)求遠(yuǎn)端服務(wù)妥粟,帶上自己的應(yīng)用信息以及notifications信息
2.服務(wù)端針對(duì)傳過來的每一個(gè)namespace和對(duì)應(yīng)的notificationId,檢查notificationId是否是最新的
3.如果都是最新的吏够,則保持住請(qǐng)求60秒勾给,如果60秒內(nèi)沒有配置變化,則返回HttpStatus 304稿饰。如果60秒內(nèi)有配置變化锦秒,則返回對(duì)應(yīng)namespace的最新notificationId, HttpStatus 200
4.如果傳過來的notifications信息中發(fā)現(xiàn)有notificationId比服務(wù)端老,則直接返回對(duì)應(yīng)namespace的最新notificationId, HttpStatus 200
5.客戶端拿到服務(wù)端返回后喉镰,判斷返回的HttpStatus
6.如果返回的HttpStatus是304旅择,說明配置沒有變化,重新執(zhí)行第1步
7.如果返回的HttpStauts是200侣姆,說明配置有變化生真,針對(duì)變化的namespace重新去服務(wù)端拉取配置,參見1.3 通過不帶緩存的Http接口從Apollo讀取配置捺宗。同時(shí)更新notifications map中的notificationId柱蟀。重新執(zhí)行第1步
HTTP接口說明
URL {config_server_url}/notifications/v2?appId={appId}&cluster={clusterName}¬ifications={notifications}
Method GET
參數(shù)說明
TIPS 由于服務(wù)端會(huì)hold住60秒,所以請(qǐng)確毖晾鳎客戶端訪問服務(wù)端的超時(shí)時(shí)間要大于60秒长已;記得對(duì)參數(shù)進(jìn)行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)單獨(dú)部署MetaServer+AdminService+ConfigService辜伟,使用獨(dú)立數(shù)據(jù)庫ApolloConfigDB及應(yīng)用服務(wù);MetaServer和Config Service部署在同一個(gè)JVM進(jìn)程內(nèi)脊另,Admin Service部署在同一臺(tái)服務(wù)器的另一個(gè)JVM進(jìn)程內(nèi)导狡。部署示例如下圖
網(wǎng)絡(luò)策略 分布式部署的時(shí)候,apollo-configservice和apollo-adminservice需要把自己的IP和端口注冊(cè)到Meta Server(apollo-configservice本身)偎痛。Apollo客戶端和Portal會(huì)從Meta Server獲取服務(wù)的地址(IP+PORT)旱捧,然后通過服務(wù)地址直接訪問。apollo-configservice和apollo-adminservice是基于內(nèi)網(wǎng)可信網(wǎng)絡(luò)設(shè)計(jì)的看彼,所以出于安全考慮廊佩,請(qǐng)不要將apollo-configservice和apollo-adminservice直接暴露在公網(wǎng)
部署步驟
創(chuàng)建數(shù)據(jù)庫 Apollo服務(wù)端依賴于MYSQL數(shù)據(jù)庫囚聚,所以需要事先創(chuàng)建并完成初始化
獲取安裝包 Apollo服務(wù)端安裝包共3個(gè): Apollo-AdminService、Apollo-ConfigService标锄、Apollo-Portal
部署Apollo服務(wù)端 獲取安裝包后就可以部署到測(cè)試和生產(chǎn)環(huán)境
小結(jié)
文章較為全面介紹開源分布式配置中心Apollo的設(shè)計(jì)顽铸、使用、應(yīng)用接入及部署方法料皇,目前客戶端只有Java和.Net版本谓松,其他語言客戶端的接入可以通過HTTP接口的方式定時(shí)拉取更新配置或通過Http Long Polling機(jī)制實(shí)時(shí)推送挟裂,實(shí)現(xiàn)應(yīng)用感知配置更新