Apollo配置中心介紹

一、What is Apollo

1.1 背景

隨着程序功能的日益複雜,程序的配置日益增多:各類功能的開關、參數的配置、服務器的地址……java

對程序配置的指望值也愈來愈高:配置修改後實時生效,灰度發佈,分環境、分集羣管理配置,完善的權限、審覈機制……git

在這樣的大環境下,傳統的經過配置文件、數據庫等方式已經愈來愈沒法知足開發人員對配置管理的需求。github

Apollo配置中心應運而生!spring

1.2 Apollo簡介

Apollo(阿波羅)是攜程框架部門研發的開源配置管理中心,可以集中化管理應用不一樣環境、不一樣集羣的配置,配置修改後可以實時推送到應用端,而且具有規範的權限、流程治理等特性。數據庫

Apollo支持4個維度管理Key-Value格式的配置:api

  1. application (應用)
  2. environment (環境)
  3. cluster (集羣)
  4. namespace (命名空間)

同時,Apollo基於開源模式開發,開源地址:https://github.com/ctripcorp/apollo緩存

1.3 配置基本概念

既然Apollo定位於配置中心,那麼在這裏有必要先簡單介紹一下什麼是配置。服務器

按照咱們的理解,配置有如下幾個屬性:網絡

  • 配置是獨立於程序的只讀變量
    • 配置首先是獨立於程序的,同一份程序在不一樣的配置下會有不一樣的行爲。
    • 其次,配置對於程序是隻讀的,程序經過讀取配置來改變本身的行爲,可是程序不該該去改變配置。
    • 常見的配置有:DB Connection Str、Thread Pool Size、Buffer Size、Request Timeout、Feature Switch、Server Urls等。
  • 配置伴隨應用的整個生命週期
    • 配置貫穿於應用的整個生命週期,應用在啓動時經過讀取配置來初始化,在運行時根據配置調整行爲。
  • 配置能夠有多種加載方式
    • 配置也有不少種加載方式,常見的有程序內部hard code,配置文件,環境變量,啓動參數,基於數據庫等
  • 配置須要治理
    • 權限控制
      • 因爲配置能改變程序的行爲,不正確的配置甚至能引發災難,因此對配置的修改必須有比較完善的權限控制
    • 不一樣環境、集羣配置管理
      • 同一份程序在不一樣的環境(開發,測試,生產)、不一樣的集羣(如不一樣的數據中心)常常須要有不一樣的配置,因此須要有完善的環境、集羣配置管理
    • 框架類組件配置管理
      • 還有一類比較特殊的配置 - 框架類組件配置,好比CAT客戶端的配置。
      • 雖然這類框架類組件是由其餘團隊開發、維護,可是運行時是在業務實際應用內的,因此本質上能夠認爲框架類組件也是應用的一部分。
      • 這類組件對應的配置也須要有比較完善的管理方式。

二、Why Apollo

正是基於配置的特殊性,因此Apollo從設計之初就立志於成爲一個有治理能力的配置管理平臺,目前提供瞭如下的特性:併發

  • 統一管理不一樣環境、不一樣集羣的配置
    • Apollo提供了一個統一界面集中式管理不一樣環境(environment)、不一樣集羣(cluster)、不一樣命名空間(namespace)的配置。
    • 同一份代碼部署在不一樣的集羣,能夠有不一樣的配置,好比zk的地址等
    • 經過命名空間(namespace)能夠很方便的支持多個不一樣應用共享同一份配置,同時還容許應用對共享的配置進行覆蓋
  • 配置修改實時生效(熱發佈)
    • 用戶在Apollo修改完配置併發布後,客戶端能實時(1秒)接收到最新的配置,並通知到應用程序
  • 版本發佈管理
    • 全部的配置發佈都有版本概念,從而能夠方便地支持配置的回滾
  • 灰度發佈
    • 支持配置的灰度發佈,好比點了發佈後,只對部分應用實例生效,等觀察一段時間沒問題後再推給全部應用實例
  • 權限管理、發佈審覈、操做審計
    • 應用和配置的管理都有完善的權限管理機制,對配置的管理還分爲了編輯和發佈兩個環節,從而減小人爲的錯誤。
    • 全部的操做都有審計日誌,能夠方便的追蹤問題
  • 客戶端配置信息監控
    • 能夠在界面上方便地看到配置在被哪些實例使用
  • 提供Java和.Net原生客戶端
    • 提供了Java和.Net的原生客戶端,方便應用集成
    • 支持Spring Placeholder, Annotation和Spring Boot的ConfigurationProperties,方便應用使用(須要Spring 3.1.1+)
    • 同時提供了Http接口,非Java和.Net應用也能夠方便的使用
  • 提供開放平臺API
    • Apollo自身提供了比較完善的統一配置管理界面,支持多環境、多數據中心配置管理、權限、流程治理等特性。
    • 不過Apollo出於通用性考慮,對配置的修改不會作過多限制,只要符合基本的格式就可以保存。
    • 在咱們的調研中發現,對於有些使用方,它們的配置可能會有比較複雜的格式,並且對輸入的值也須要進行校驗後方可保存,如檢查數據庫、用戶名和密碼是否匹配。
    • 對於這類應用,Apollo支持應用方經過開放接口在Apollo進行配置的修改和發佈,而且具有完善的受權和權限控制
  • 部署簡單
    • 配置中心做爲基礎服務,可用性要求很是高,這就要求Apollo對外部依賴儘量地少
    • 目前惟一的外部依賴是MySQL,因此部署很是簡單,只要安裝好Java和MySQL就可讓Apollo跑起來
    • Apollo還提供了打包腳本,一鍵就能夠生成全部須要的安裝包,而且支持自定義運行時參數

三、Apollo at a glance

3.1 基礎模型

以下便是Apollo的基礎模型:

  1. 用戶在配置中心對配置進行修改併發布
  2. 配置中心通知Apollo客戶端有配置更新
  3. Apollo客戶端從配置中心拉取最新的配置、更新本地配置並通知到應用

basic-architecture

3.2 界面概覽

portal-overview

上圖是Apollo配置中心中一個項目的配置首頁

  • 在頁面左上方的環境列表模塊展現了全部的環境和集羣,用戶能夠隨時切換。
  • 頁面中央展現了兩個namespace(application和FX.apollo)的配置信息,默認按照表格模式展現、編輯。用戶也能夠切換到文本模式,以文件形式查看、編輯。
  • 頁面上能夠方便地進行發佈、回滾、灰度、受權、查看更改歷史和發佈歷史等操做

3.3 添加/修改配置項

用戶能夠經過配置中心界面方便的添加/修改配置項:

edit-item-1

輸入配置信息:

edit-item

3.4 發佈配置

經過配置中心發佈配置:

publish-items-1

填寫發佈信息:

publish-items

3.5 客戶端獲取配置(Java API樣例)

配置發佈後,就能在客戶端獲取到了,以Java API方式爲例,獲取配置的示例代碼以下。更多客戶端使用說明請參見Java客戶端使用指南

Config config = ConfigService.getAppConfig(); Integer defaultRequestTimeout = 200; Integer requestTimeout = config.getIntProperty("request.timeout",defaultRequestTimeout); 

3.6 客戶端監聽配置變化(Java API樣例)

經過上述獲取配置代碼,應用就能實時獲取到最新的配置了。

不過在某些場景下,應用還須要在配置變化時得到通知,好比數據庫鏈接的切換等,因此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 { @Value("${request.timeout:200}") private int timeout; @ApolloConfigChangeListener private void someChangeHandler(ConfigChangeEvent changeEvent) { if (changeEvent.isChanged("request.timeout")) { refreshTimeout(); } } } 

四、Apollo in depth

經過上面的介紹,相信你們已經對Apollo有了一個初步的瞭解,而且相信已經覆蓋到了大部分的使用場景。

接下來會主要介紹Apollo的cluster管理(集羣)、namespace管理(命名空間)和對應的配置獲取規則。

4.1 Core Concepts

在介紹高級特性前,咱們有必要先來了解一下Apollo中的幾個核心概念:

  1. application (應用)
    • 這個很好理解,就是實際使用配置的應用,Apollo客戶端在運行時須要知道當前應用是誰,從而能夠去獲取對應的配置
    • 每一個應用都須要有惟一的身份標識 - appId,咱們認爲應用身份是跟着代碼走的,因此須要在代碼中配置,具體信息請參見Java客戶端使用指南
  2. environment (環境)
    • 配置對應的環境,Apollo客戶端在運行時須要知道當前應用處於哪一個環境,從而能夠去獲取應用的配置
    • 咱們認爲環境和代碼無關,同一份代碼部署在不一樣的環境就應該可以獲取到不一樣環境的配置
    • 因此環境默認是經過讀取機器上的配置(server.properties中的env屬性)指定的,不過爲了開發方便,咱們也支持運行時經過System Property等指定,具體信息請參見Java客戶端使用指南
  3. cluster (集羣)
    • 一個應用下不一樣實例的分組,好比典型的能夠按照數據中心分,把上海機房的應用實例分爲一個集羣,把北京機房的應用實例分爲另外一個集羣。
    • 對不一樣的cluster,同一個配置能夠有不同的值,如zookeeper地址。
    • 集羣默認是經過讀取機器上的配置(server.properties中的idc屬性)指定的,不過也支持運行時經過System Property指定,具體信息請參見Java客戶端使用指南
  4. namespace (命名空間)
    • 一個應用下不一樣配置的分組,能夠簡單地把namespace類比爲文件,不一樣類型的配置存放在不一樣的文件中,如數據庫配置文件,rpc配置文件,應用自身的配置文件等
    • 應用能夠直接讀取到公共組件的配置namespace,如DAL,RPC等
    • 應用也能夠經過繼承公共組件的配置namespace來對公共組件的配置作調整,如DAL的初始數據庫鏈接數

4.2 自定義Cluster

【本節內容僅對應用須要對不一樣集羣應用不一樣配置才須要,如沒有相關需求,能夠跳過本節】

好比咱們有應用在A數據中心和B數據中心都有部署,那麼若是但願兩個數據中心的配置不同的話,咱們能夠經過新建cluster來解決。

4.2.1 新建Cluster

新建Cluster只有項目的管理員纔有權限,管理員能夠在頁面左側看到「添加集羣」按鈕。

create-cluster

點擊後就進入到集羣添加頁面,通常狀況下能夠按照數據中心來劃分集羣,如SHAJQ、SHAOY等。

不過也支持自定義集羣,好比能夠爲A機房的某一臺機器和B機房的某一臺機建立一個集羣,使用一套配置。

create-cluster-detail

4.2.2 在Cluster中添加配置併發布

集羣添加成功後,就能夠爲該集羣添加配置了,首選須要按照下圖所示切換到SHAJQ集羣,以後配置添加流程和3.2添加/修改配置項同樣,這裏就再也不贅述了。

cluster-created

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」按鈕。

create-namespace

點擊後就進入namespace添加頁面,Apollo會把應用所屬的部門做爲namespace的前綴,如FX。

create-namespace-detail

4.3.2 關聯到環境和集羣

Namespace建立完,須要選擇在哪些環境和集羣下使用

link-namespace-detail

4.3.3 在Namespace中添加配置項

接下來在這個新建的namespace下添加配置項

add-item-in-new-namespace

添加完成後就能在FX.Hermes.Producer的namespace中看到配置。

item-created-in-new-namespace

4.3.4 發佈namespace的配置

publish-items-in-new-namespace

4.3.5 客戶端獲取Namespace配置

對自定義namespace的配置獲取,稍有不一樣,須要程序傳入namespace的名字。更多客戶端使用說明請參見Java客戶端使用指南

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 { @Value("${request.timeout:200}") private int timeout; @ApolloConfigChangeListener("FX.Hermes.Producer") private void someChangeHandler(ConfigChangeEvent changeEvent) { if (changeEvent.isChanged("request.timeout")) { refreshTimeout(); } } } 

4.4 配置獲取規則

【本節僅當應用自定義了集羣或namespace才須要,如無相關需求,能夠跳過本節】

在有了cluster概念後,配置的規則就顯得重要了。

好比應用部署在A機房,可是並無在Apollo新建cluster,這個時候Apollo的行爲是怎樣的?

或者在運行時指定了cluster=SomeCluster,可是並無在Apollo新建cluster,這個時候Apollo的行爲是怎樣的?

接下來就來介紹一下配置獲取的規則。

4.4.1 應用自身配置的獲取規則

當應用使用下面的語句獲取配置時,咱們稱之爲獲取應用自身的配置,也就是應用自身的application namespace的配置。

Config config = ConfigService.getAppConfig(); 

對這種狀況的配置獲取規則,簡而言之以下:

  1. 首先查找運行時cluster的配置(經過apollo.cluster指定)
  2. 若是沒有找到,則查找數據中心cluster的配置
  3. 若是仍是沒有找到,則返回默認cluster的配置

圖示以下:

application-config-precedence

因此若是應用部署在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"); 

對這種狀況的配置獲取規則,簡而言之以下:

  1. 首先獲取當前應用下的FX.Hermes.Producer namespace的配置
  2. 而後獲取hermes應用下FX.Hermes.Producer namespace的配置
  3. 上面兩部分配置的並集就是最終使用的配置,若有key同樣的部分,以當前應用優先

圖示以下:

public-namespace-config-precedence

經過這種方式,就實現了對框架類組件的配置管理,框架組件提供方提供配置的默認值,應用若是有特殊需求,能夠自行覆蓋。

4.5 整體設計

overall-architecture

上圖簡要描述了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 客戶端設計

client-architecture

上圖簡要描述了Apollo客戶端的實現原理:

  1. 客戶端和服務端保持了一個長鏈接,從而能第一時間得到配置更新的推送。
  2. 客戶端還會定時從Apollo配置中心服務端拉取應用的最新配置。
    • 這是一個fallback機制,爲了防止推送機制失效致使配置不更新
    • 客戶端定時拉取會上報本地版本,因此通常狀況下,對於定時拉取的操做,服務端都會返回304 - Not Modified
    • 定時頻率默認爲每5分鐘拉取一次,客戶端也能夠經過在運行時指定System Property: apollo.refreshInterval來覆蓋,單位爲分鐘。
  3. 客戶端從Apollo配置中心服務端獲取到應用的最新配置後,會保存在內存中
  4. 客戶端會把從服務端獲取到的配置在本地文件系統緩存一份
    • 在遇到服務不可用,或網絡不通的時候,依然能從本地恢復配置
  5. 應用程序能夠從Apollo客戶端獲取最新的配置、訂閱配置更新通知

4.6.1 配置更新推送實現

前面提到了Apollo客戶端和服務端保持了一個長鏈接,從而能第一時間得到配置更新的推送。

長鏈接實際上咱們是經過Http Long Polling實現的,具體而言:

  • 客戶端發起一個Http請求到服務端
  • 服務端會保持住這個鏈接30秒
  • 若是在30秒內有客戶端關心的配置變化,被保持住的客戶端請求會當即返回,並告知客戶端有配置變化的namespace信息,客戶端會據此拉取對應namespace的最新配置
  • 若是在30秒內沒有客戶端關心的配置變化,那麼會返回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自動切換到其它存活的數據中心

五、Contribute to Apollo

Apollo從開發之初就是以開源模式開發的,因此也很是歡迎有興趣、有餘力的朋友一塊兒加入進來。

服務端開發使用的是Java,基於Spring Cloud和Spring Boot框架。客戶端目前提供了Java和.Net兩種實現。

Github地址:https://github.com/ctripcorp/apollo

原文:http://nobodyiam.com/2016/07/09/introduction-to-apollo/

相關文章
相關標籤/搜索