1. 程式人生 > 程式設計 >分散式配置中心--Apollo

分散式配置中心--Apollo

Apollo(阿波羅)是攜程開源的分散式配置中心,能夠集中化管理應用不同環境、不同叢集的配置,支援配置熱釋出並實時推送到應用端,並且具備規範的許可權及流程治理等特性,適用於分散式微服務配置管理場景

Apollo配置中心介紹

程式功能日益複雜,程式配置日益增多:各種功能開關、引數配置、伺服器地址...對程式配置的期望也越來越高:熱部署並實時生效、灰度釋出、分環境分叢集管理配置、完善的許可權稽核機制...在這樣的背景下,Apollo配置中心應運而生。Apollo支援四個維度Key-Value格式的配置* Application(應用) 實際使用配置的應用,Apollo客戶端在執行時需要知道當前應用是誰,從而可以去獲取對應的配置。每個應用都有對應的身份標識--appId,需要在程式碼中配置

  • Environment(環境) 配置對應的環境,Apollo客戶端需要知道當前應用出於哪個環境,,從而可以去獲取應用的配置;環境和程式碼無關,同一份程式碼部署在不同的環境就應該獲取不同環境的配置;環境預設是通過讀取機器上的配置(server.properties的env屬性)指定的
  • Cluster(叢集) 一個應用下不同例項的分組,例如按照不同資料中心劃分,把上海機房的例項分為一個叢集、把深圳機房的例項分為一個叢集;對於不同的Cluster,同一個配置可以有不一樣的值;叢集預設是通過讀取機器上的配置指定的(server.properties的idc屬性)
  • Namespace(名稱空間)
    一個應用下不同配置的分組,是配置項的集合,可以簡單地把Namespace類別為(配置)檔案,不同型別的配置存放在不同的檔案中,例如資料庫配置檔案、RPC配置檔案、應用自身的配置檔案等;應用可以直接讀取到公共元件的配置namespace,例如DAL、RPC等;應用也可以通過繼承公共元件的配置namespace來對公共元件的配置做調整,如DAL的初始資料庫連線數

Apollo在建立專案的時候,都會預設建立一個"application"的Namespace,"application"是個應用自身使用的。例如Spring Boot中專案的預設配置檔案application.yaml,這裡application.yaml就等同於"application"的Namespace。對於大多數應用來說,"application"Namespace已經能滿足日常配置使用場景

客戶端獲取"application"Namespace的程式碼如下

    Config config = ConfigService.getAppConfig()複製程式碼

客戶端獲取非"application"Namespace的程式碼如下

    Config config = ConfigService.getConfig(namespaceName)複製程式碼

Namespace的格式 配置檔案有多種格式,properties、xml、yml、yaml、json等,同樣Namespace也具有這些格式tips: 非properties格式的namespace,在客戶端使用時需要呼叫ConfigService.getConfigFile(String namespace,ConfigFileFormat configFileFormat)來獲取,如果使用Htpp介面直接呼叫時,對應的namespace引數需要傳入namespace的名字加上字尾名,如datasource.jsonNamespace的獲取許可權分類 此處許可權相是對於Apollo客戶端來說的private(私有的)許可權 private許可權的Namespace,只能被所屬的應用獲取到。一個應用嘗試獲取其他應用private的Namespace,Apollo客戶端會報"404"異常

public(公共的)許可權 具有public許可權的Namespace,能被任何應用獲取

Namespace的型別

  • 私有型別 具有private許可權,例如上文中提到的"application"Namespace就是私有型別
  • 公共型別 具有public許可權,公共型別的Namespace相當於遊離於應用之外的配置,且通過Namespace的名稱去標識公共Namespace,所以公共Namespace的名稱必須全域性唯一

使用場景 部門級別共享的配置、小組級別共享的配置、幾個專案之間共享的配置、中介軟體客戶端的配置

  • 關聯型別(繼承型別) 具有private許可權,關聯型別的Namespace繼承於公共型別的Namespace,用於覆蓋公共Namespace的某些配置。例如公共Namespace有兩個配置項
        k1 = v1
        k2 = v2複製程式碼
    然後應用A有一個關聯型別的Namespace關聯此公共Namespace,且以新值v3覆蓋配置項k1。那麼在應用A實際執行時,獲取到的公共Namespace的配置為複製程式碼
        k1 = v3
        k2 = v2複製程式碼
    使用場景  假設RPC框架的配置(如:timeout)有以下要求
*     提供一份全公司預設的配置,且可動態調整
* RPC客戶端專案可以自定義某些配置項且可動態調整
        結合Apollo的公共型別的Namespace和關聯型別的Namespace。RPC團隊在Apollo上維護一個叫“rpc-client”的公共Namespace,在"rpc-client"Namespace上配置預設的引數值。rpc-client.jar裡的程式碼讀取"rpc-client"Namespace的配置即可;如需要調整預設的配置,只需要修改公共型別"rpc-client"Namespace的配置;如果客戶端專案想要自定義或動態修改某些配置項,只需要在Apollo自己專案下關聯"rpc-client",就能建立關聯型別"rpc-client"的Namespace,然後在關聯型別下修改配置項即可。這裡rpc-client.jar是在應用容器裡執行的,所以rpc-client獲取到"rpc-client"Namespace的配置是應用的關聯型別的Namespace加上公共型別的Namespace
        例子  如下圖,有三個應用:應用A、應用B、應用C
        應用A有兩個私有型別的Namespace:application和NS-Private,以及一個關聯型別的Namespace:NS-Public
        應用B有一個私有型別的Namespace:application,以及一個公共型別的Namespace:NS-Public
        應用C只有一個私有型別的Namespace:application
        ![](https://user-gold-cdn.xitu.io/2019/9/29/16d7d726928b404f?w=800&h=460&f=jpeg&s=22615)複製程式碼
        應用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複製程式碼
        應用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); // k7 = v7複製程式碼
        應用C獲取Apollo配置
        //application
        Config appConfig = ConfigService.getAppConfig();
        appConfig.getProperty("k1",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); // k7 = v7
        複製程式碼

ChangeListener 以上程式碼可以看出,在客戶端Namespace對映成一個Config物件,Namespace配置變更的監聽器是註冊在Config物件上

在應用A中監聽application的Namespace程式碼如下

Config appConfig = ConfigService.getAppConfig();appConfig.addChangeListener(new ConfigChangeListener() {public void onChange(ConfigChangeEvent changeEvent) {//do something}})

        在應用A中監聽 NS-Private 的 Namespace程式碼如下
        Config privateConfig = ConfigService.getConfig("NS-Private");
        privateConfig.addChangeListener(new ConfigChangeListener() {
        public void onChange(ConfigChangeEvent changeEvent) {
        //do something
        }
        })
        ## 在應用A、應用B和應用C中監聽NS-Public Namespace程式碼如下
        Config publicConfig = ConfigService.getConfig("NS-Public");
        publicConfig.addChangeListener(new ConfigChangeListener() {
            public void onChange(ConfigChangeEvent changeEvent) {
                //do something
                }
        })
        複製程式碼

配置的幾大屬性

  • 配置是獨立於程式的只讀變數
    • 配置首先是獨立於程式的,同一份程式在不同的配置下會有不同的行為
    • 配置對於程式是隻讀的,程式通過讀取配置來改變自己的行為,程式不應該去改變配置
  • 配置伴隨應用的整個生命週期
    • 配置貫穿於應用的整個生命週期,應用在啟動時通過讀取配置來初始化,在執行時根據配置來調整行為
  • 配置可以有多種載入方式
    • 常見的配置載入方式有程式內部hard code、配置檔案、環境變數、啟動引數、基於資料庫等
  • 配置需要治理
    • 許可權控制 由於配置能改變程式行為,不正確的配置甚至能引起災難,所以對配置的修改必須有比較完善的許可權控制
    • 不同環境、叢集配置管理 同一份程式在不同的環境(開發、測試、生產)、不同的叢集(如不同的資料中心)可能有不同的配置,所以需要有完善的環境、叢集配置管理
    • 框架類元件配置管理 一類比較特殊的配置,通常是由其他團隊開發、維護,但是執行時是在業務實際應用內的,所以本質上可以認為框架類元件也是應用的一部分,也需要比較完善的管理方式
      基於配置的特殊性,Apollo從設計之初就立志於成為一個有治理能力的配置釋出平臺,目前提供了以下的特性
  • 統一管理不同環境、不同叢集的配置
    • Apollo提供了一個統一介面集中式管理不同環境(Environment)、不同叢集(Cluster)、不同名稱空間(Namespace)的配置
    • 同一份程式碼部署在不同的叢集,可以有不同的配置
    • 通過名稱空間可以很方便地支援多個不同應用共享同一份配置,同時還允許應用對共享的配置進行覆蓋
  • 熱釋出--配置修改實時生效 使用者在Apollo修改完配置併發布後,客戶端能實時(1秒)接收到最新的配置,並通知到應用程式
  • 版本釋出管理 所有的配置釋出都有版本概念,從而可以方便地支援配置的回滾
  • 灰度釋出 支援配置的灰度釋出,比如點了釋出後,只對部分應用例項生效,等觀察一段時間沒問題後再推給所有應用例項
  • 許可權管理、釋出稽核、操作審計
    • 應用和配置的管理都有完善的許可權管理機制,對配置的管理還分為了編輯和釋出兩個環節,從而減少人為的錯誤
    • 所有的操作都有審計日誌,可以方便地追蹤問題
  • 客戶端配置資訊監控 可以在介面上方便地看到配置在被哪些例項使用
  • 提供Java和.Net原生客戶端
    • 提供了Java和.Net的原生客戶端,方便應用整合
    • 支援Spring Placeholder,Annotation和Spring Boot的ConfigurationProperties,方便應用使用(需要Spring 3.1.1+)
    • 同時提供了Http介面,非Java和.Net應用也可以方便地使用
  • 提供開放平臺API
  • 部署簡單

配置獲取規則 僅當應用自定義了叢集或namespace才需要。有了cluster概念後,配置的規則就顯得重要了,比如應用部署在A機房,但是並沒有在Apollo新建cluster或者在執行時指定了cluster=SomeCluster,但是並沒有在Apollo新建cluster,這時候Apollo的行為是怎樣的?下面介紹配置獲取的規則應用自身配置的獲取規則當應用使用下面的語句獲取配置時,稱之為獲取應用自身的配置,也就是應用自身的application namespace的配置

        Config config = ConfigService.getAppConfig();複製程式碼

這種情況的配置獲取規則簡而言之如下

  1. 首先查詢執行時cluster的配置(通過apollo.cluster指定)
  2. 如果沒有找到,則查詢資料中心cluster的配置
  3. 如果還是沒有找到,則返回預設cluster的配置
    圖示如下
    配置查詢順序
    所以,如果應用部署在A資料中心,但是使用者沒有在Apollo建立cluster,那麼獲取的配置就是預設cluster(default)的;如果應用部署在A資料中心,同時在執行時指定了apollo.cluster=SomeCluster,但是沒有在Apollo建立cluster,那麼獲取的配置就是A資料中心cluster的配置,如果A資料中心cluster沒有配置的話,那麼獲取的配置就是預設cluster(default)的
* 公共元件配置的獲取規則
    以`FX.Hermes.Producer`為例,hermes producer是hermes釋出的公共元件。當使用下面的語句獲取配置時,稱之為獲取公共元件的配置複製程式碼
            Config config = ConfigService.getConfig("FX.Hermes.Producer")複製程式碼

對於這種情況獲取配置規則,簡而言之如下

  1. 首先獲取當前應用下的FX.Hermes.Producernamespace的配置
  2. 然後獲取hermes應用下FX.Hermes.Producernamespace的配置
  3. 上面兩部分配置的並集就是最終使用的配置,如有key一樣的部分,應當以應用優先
    圖示如下
    公共元件配置獲取規則

通過這種方式實現對框架元件的配置管理,框架元件提供方提供配置的預設值,應用如果有特殊需求可以自行覆蓋

Apollo配置中心的設計

總體設計

  • 基礎模型
    • 使用者在配置中心對配置進行修改併發布
    • 配置中心通知Apollo客戶端有配置更新
    • Apollo客戶端從配置中心拉取最新的配置、更新本地配置並通知到應用

基礎模型

  • 架構模組
    • Apollo包含七個模組:四個功能相關的核心模組和三個輔助服務發現的模組
    • 四個核心模組及其主要功能
      • ConfigService 提供配置獲取介面、提供配置推送介面、服務於Apollo客戶端
      • AdminService 提供配置管理介面、提供配置修改釋出介面、服務於管理介面Portal
      • Client 為應用獲取配置,支援實時更新、通過MetaServer獲取ConfigService的服務列表、使用客戶端軟負載SLB方式呼叫ConfigService
      • Portal 配置管理介面、通過MetaServer獲取AdminService的服務列表、使用客戶端軟負載SLB的方式呼叫AdminService
    • 三個輔助服務發現模組
      • Eureka 用於服務發現和註冊、ConfigService和AdminService註冊例項並定期上報心跳、和ConfigService部署於同一個程式
      • MetaServer Portal通過域名訪問MetaServer獲取AdminService的地址列表、Client通過域名訪問MetaServer獲取ConfigService的地址列表、相當於一個EurekaProxy、是一個邏輯角色和ConfigService部署於同一個程式
      • NginxLB 和域名系統配合,協助Portal訪問MetaServer獲取AdminService地址列表、和域名系統配合,協助Client訪問MetaServer獲取ConfigService地址列表、和域名系統配合,協助使用者訪問Portal進行配置管理
        Apollo架構
  • 架構剖析
    **Apollo架構V1**  如果不考慮分散式微服務架構中的服務發現問題,Apollo的最簡架構如下圖所示
    ![Apollo架構V1](https://user-gold-cdn.xitu.io/2019/9/29/16d7d7275299a778?w=800&h=315&f=jpeg&s=16632)
    要點
    * ConfigService是一個獨立的微服務,服務於Client進行配置獲取
    * Client和ConfigService保持長連線,通過一種推拉結合(push & pull)的模式,在實現配置實時更新的同時,保證配置更新不丟失
    * AdminService是一個獨立的微服務,服務於Portal進行配置管理。Portal通過呼叫AdminService進行配置管理和釋出
    * ConfigService和AdminService共享ConfigDB,ConfigDB中存放專案在某個環境中的配置資訊。ConfigService/AdminService/ConfigDB三者在每個環境(DEV/FAT/UAT/PRO)中都要部署一份
    * Protal有一個獨立的PortalDB,存放使用者許可權、專案和配置的元資料資訊。Protal只需部署一份,它可以管理多套環境複製程式碼
    **Apollo架構 V2**   為了保證高可用,ConfigService和AdminService都是無狀態以叢集方式部署的,這時候就存在一個服務發現的問題:Client怎麼找到ConfigService?Portal怎麼找到AdminService?為瞭解決這個問題,Apollo在其架構中引入Eureka服務註冊中心元件,實現微服務間的服務註冊和發現,更新後的架構如下圖所示
        ![Apollo架構V2](https://user-gold-cdn.xitu.io/2019/9/29/16d7d727764634a6?w=800&h=502&f=jpeg&s=23979)
        要點
    * ConfigService和AdminService啟動後都會註冊到Eureka服務註冊中心,並定期傳送存活心跳
    * Eureka採用叢集方式部署,使用分散式一致性協議保證每個例項的狀態最終一致
複製程式碼

Apollo架構V3 Eureka是自帶服務發現的Java客戶端的,如果Apollo只支援Java客戶端接入,不支援其它語言客戶端接入的話,那麼Client和Portal只需要引入Eureka的Java客戶端,就可以實現服務發現功能。發現目標服務後,通過客戶端軟負載(SLB,例如Ribbon)就可以路由到目標服務例項。這是一個經典的微服務架構,基於Eureka實現服務註冊發現+客戶端Ribbon配合實現軟路由,如下圖所示Apollo架構V3

Apollo架構V4
為支援多語言客戶端接入,Apollo引入MetaServer角色,它其實是一個Eureka的Proxy,將Eureka的服務發現介面以更簡單明確的HTTP介面的形式暴露出來,方便Client/Protal通過簡單的HTTPClient就可以查詢到ConfigService/AdminService的地址列表。獲取到服務例項地址列表之後,再以簡單的客戶端軟負載(Client SLB)策略路由定位到目標例項,併發起呼叫

另一個問題,MetaServer本身也是無狀態以叢集方式部署的,那麼Client/Protal該如何發現MetaServer呢?一種傳統的做法是藉助硬體或者軟體負載均衡器,在攜程採用的是擴充套件後的NginxLB(Software Load Balancer),由運維為MetaServer叢集配置一個域名,指向NginxLB叢集,NginxLB再對MetaServer進行負載均衡和流量轉發。Client/Portal通過域名+NginxLB間接訪問MetaServer叢集

引入MetaServer和NginxLB之後的架構如下圖Apollo架構V4

Apollo架構V5
還剩下最後一個環節,Portal也是無狀態的以叢集方式部署的,使用者如何發現和訪問Portal?答案也是簡單的傳統做法,使用者通過域名+NginxLB間接訪問Portal叢集。所以V5版本是包括使用者端的最終的Apollo架構全貌,如下圖所示Apollo架構V5

  • 服務端設計
    配置釋出後的實時推送設計  在配置中心中,一個重要的功能就是配置釋出後實時推送到客戶端。下面我們簡要看一下這塊是怎麼設計實現的
![服務端設計](https://user-gold-cdn.xitu.io/2019/9/29/16d7d7280f721f5f?w=800&h=280&f=jpeg&s=15776)複製程式碼
        上圖簡要描述了配置釋出的大致過程
        1. 使用者在Portal操作釋出配置
        2. Portal呼叫Admin Service的介面操作釋出
        3. Admin Service釋出配置後,傳送ReleaseMessage給各Config Service
        4. Config Service收到ReleaseMessage後通知對應的客戶端複製程式碼
    **傳送ReleaseMessage的實現方式**   Admin Service在配置釋出後,需要通知所有的Config Service有配置釋出,從而Config Service可以通知對應的客戶端來拉取最新的配置。從概念上看,這是一個典型的訊息使用場景,Admin Service作為Producer發出訊息,各個Config Service作為consumer消費訊息。通過一個訊息元件(Message Queue)就能很好地實現Admin Service和Config Service的解耦。在實現上,Apollo為儘量減少外部依賴,沒有采用外部的訊息中介軟體,而是通過資料庫實現了一個簡單的訊息佇列複製程式碼
    實現方式如下
        1. Admin Service在配置釋出後會往ReleaseMessage表插入一條訊息記錄,訊息內容就是配置釋出的AppId+Cluster+Namespace
        2. Config Service有一個執行緒會每秒掃描一次ReleaseMessage表,看是否有新的訊息記
        3. Config Service如果發現有新的訊息記錄,那麼會通知到所有的訊息監聽器(ReleaseMessageListener),例如NotificationControllerV2
        4. 訊息監聽器得到配置釋出的AppId+Cluster+Namespace後,會通知對應的客戶端
示意圖如下
![配置更新通知](https://user-gold-cdn.xitu.io/2019/9/29/16d7d728386cd0b0?w=800&h=788&f=jpeg&s=29770)複製程式碼

Config Service通知客戶端的實現方式 訊息監聽器在得知有新的配置釋出後是如何通知到客戶端的呢?其實現方式如下

  1. 客戶端會發起一個Http請求到Config Service的notifications/v2介面,也就是NotificationControllerV2
  2. NotificationControllerV2不會立即返回結果,而是通過Spring DeferredResult把請求掛起
  3. 如果在60秒內沒有該客戶端關心的配置釋出,那麼會返回Http狀態碼304給客戶端
  4. 如果有該客戶端關心的配置釋出,NotificationControllerV2會呼叫DeferredResult的setResult方法,傳入有配置變化的namespace資訊,同時該請求會立即返回。客戶端從返回的結果中獲取到配置變化的namespace後,會立即請求Config Service獲取該namespace的最新配置

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

  1. 客戶端和服務端保持了一個長連線(通過Http Long Polling實現),從而能第一時間獲得配置更新的推送
  2. 客戶端還會定期從Apollo配置中心服務端拉取應用的最新配置
    • 這是一個fallback機制,為了防止推送機制失效導致配置不更新
    • 客戶端定時拉取會上報本地版本,所有一般情況下,對於定時拉取的操作,服務端都會返回304-Not Modified
    • 定時頻率預設為每5分鐘拉取一次,客戶端也可以通過在執行時指定System Property: apollo.refreshInterval來覆蓋,單位為分鐘
    • 客戶端從Apollo配置中心服務端獲取到應用的最新配置後,會儲存在記憶體中
    • 客戶端會把從服務端獲取到的配置在本地檔案系統快取一份,在遇到服務不可以或網路不通時,依然能從本地恢復配置
    • 應用程式可以從Apollo客戶端獲取最新的配置、訂閱配置更新通知

Apollo使用指南

名稱解析

  • 普通應用 獨立執行的程式,如Web應用程式、帶有main函式的程式
  • 公共元件 指釋出的類庫、客戶端程式,不會自己獨立執行,如Java的jar包

普通應用接入指南

  • 建立專案
    • 1.進入apollo-portal主頁
    • 2.點選"建立專案"
    • 3.輸入專案資訊
      部門 選擇應用所在的部門
      應用AppId 用來表示應用身份的唯一id,格式為string,需要和客戶端app.properties中配置的app.id對應
      應用名稱 應用名,僅用於介面展示
      應用負責人 選擇的人預設會成為該專案的管理員,具備專案許可權管理、叢集建立、Namespace建立等許可權
    • 4.點選提交 建立成功後預設會跳轉到,專案首頁
  • 專案許可權分配
    專案管理員許可權 專案管理員可以管理專案的許可權分配、可以建立叢集、可以建立Namespace
  • 配置編輯釋出許可權
    編輯許可權允許使用者在Apollo介面上建立、修改、刪除配置
    釋出許可權允許使用者在Apollo介面上釋出、回滾配置
  • 新增配置項 編輯配置需要擁有這個Namespace的編輯許可權,如果發現沒有新增配置按鈕,可以找專案管理員授權
  • 釋出配置 配置只有在釋出後才會真的被應用使用到,所以在編輯完配置後,需要釋出配置。釋出配置需要擁有這個Namespace的釋出許可權,如果發現沒有釋出按鈕,可以找專案管理員授權
  • 應用讀取配置 配置釋出成功後就可以通過Apollo客戶端讀取到配置了。Apollo目前提供Java客戶端,如果使用其他語言,也可以通過直接訪問Http介面獲取配置
  • 回滾已釋出配置 如果發現已釋出的配置有問題,可以通過點選"回滾"按鈕來將客戶端讀取到的配置回滾到上一個釋出版本。這裡的回滾機制類似於釋出系統,釋出系統中的回滾操作是將部署到機器上的安裝包回滾到上一個部署的版本,但程式碼倉庫中的程式碼是不會回滾的,從而開發可以在修復程式碼後重新發布。Apollo中的回滾也是類似的機制,點選回滾後是將釋出到客戶端的配置回滾到上一個已釋出版本,也就是說客戶端讀取到的配置會恢復到上一個版本,但頁面上編輯狀態的配置是不會回滾的,從而開發可以在修復配置後重新發布

公共元件接入步驟公共元件接入步驟幾乎與普通應用接入一致,唯一的區別是公共元件需要建立自己的唯一Namespace

        1.建立專案
        2.專案管理員許可權
        3.建立Namespace
        4.新增配置項
        5.釋出配置
        6.應用讀取配置複製程式碼

應用覆蓋公共元件配置步驟

    1.關聯公共元件Namespace
    2.覆蓋公共元件配置
    3.釋出配置複製程式碼

多個AppId共享同一份配置在一些情況下,儘管應用本身不是公共元件,但還是需要在多個AppId之間共用同一份配置,這種情況下如果希望實現多個AppId使用同一份配置的話,基本概念和公共元件的配置是一致的。具體來說,就是在其中一個AppId下建立一個namespace,寫入公共的配置資訊,然後在各個專案中讀取該namespace的配置即可;如果某個AppId需要覆蓋公共的配置資訊,那麼在該AppId下關聯公共的namespace並寫入需要覆蓋的配置即可

應用接入策略這裡考慮非Java語言客戶端接入--直接通過Http介面獲取配置

  • 通過帶快取的HTTP介面從Apollo讀取配置
    該介面會從快取中獲取配置,適合頻率較高的配置拉取請求,如簡單的30秒輪詢一次配置。由於快取最多會有一秒的延遲,所以如果需要配合配置推送通知實現實時更新配置的話,請參考不帶快取的HTTP介面從Apollo讀取配置
    **HTTP介面說明**複製程式碼
    **URL** {config_server_url}/configfiles/json/{appId}/{clusterName}/{namespaceName}?ip={clientIp}複製程式碼
    **Method**  GET複製程式碼
    **引數說明 ** 
![file](https://user-gold-cdn.xitu.io/2019/9/29/16d7d728a24bb557?w=800&h=682&f=jpeg&s=68943)複製程式碼
        **HTTP介面返回格式**  該HTTP介面返回的是JSON格式、UTF-8編碼,包含了對應namespace中所有的配置項。返回內容Sample如下
        {
        "portal.elastic.document.type":"biz","portal.elastic.cluster.name":"hermes-es-fws"
        }
        *TIPS 通過{configserverurl}/configfiles/{appId}/{clusterName}/{namespaceName}?ip={clientIp}可以獲取到properties形式的配置*
* 不帶快取的HTTP介面從Apollo讀取配置  
    該介面會直接從資料庫中獲取配置,可以配合配置推送通知實現實時更新配置複製程式碼
    **URL**  {config_server_url}/configs/{appId}/{clusterName}/{namespaceName}?releaseKey={releaseKey}&ip={clientIp}複製程式碼
    **Method** GET複製程式碼
    **引數說明**
![file](https://user-gold-cdn.xitu.io/2019/9/29/16d7d728d1fafe5a?w=800&h=753&f=jpeg&s=77670)複製程式碼

該HTTP介面返回的是JSON格式、UTF-8編碼。如果配置沒有變化(傳入的releaseKey和服務端的相等),則返回HttpStatus 304,Response Body為空;如果配置有變化,則會返回HttpStatus 200,Response Body為對應namespace的meta資訊以及其中所有的配置項。返回內容Sample如下

            {
            "appId": "100004458","cluster": "default","namespaceName": "application","configurations": {
            "portal.elastic.document.type":"biz","portal.elastic.cluster.name":"hermes-es-fws"
            },"releaseKey": "20170430092936-dee2d58e74515ff3"
            }
            複製程式碼
  • 應用感知配置更新
    Apollo提供了基於Http long polling的配置更新推送通知,第三方客戶端可以看自己實際的需求決定是否需要使用這個功能。如果對配置更新時間不是那麼敏感的話,可以通過定時重新整理來感知配置更新,重新整理頻率可以視應用自身情況來定,建議在30秒以上。如果需要做到實時感知配置更新(1秒)的話,可以參考下面的檔案實現配置更新推送的功能
    **配置更新推送實現思路**  建議參考Apollo的Java實現RemoteConfigLongPollService.java複製程式碼

初始化 首先需要確定哪些namespace需要配置更新推送,Apollo的實現方式是程式第一次獲取某個namespace的配置時就會來註冊一下,我們就知道有哪些namespace需要配置更新推送了。初始化後的結果就是得到一個notifications的Map,內容是namespaceName -> notificationId(初始值為-1)。執行過程中如果發現有新的namespace需要配置更新推送,直接塞到notifications這個Map裡面即可

請求服務 有了notifications這個Map之後,就可以請求服務了。這裡先描述一下請求服務的邏輯,具體的URL引數和說明請參見後面的介面說明

        1.請求遠端服務,帶上自己的應用資訊以及notifications資訊複製程式碼
        2.服務端針對傳過來的每一個namespace和對應的notificationId,檢查notificationId是否是最新的複製程式碼
        3.如果都是最新的,則保持住請求60秒,如果60秒內沒有配置變化,則返回HttpStatus 304。如果60秒內有配置變化,則返回對應namespace的最新notificationId,HttpStatus 200複製程式碼
        4.如果傳過來的notifications資訊中發現有notificationId比服務端老,則直接返回對應namespace的最新notificationId,HttpStatus 200複製程式碼
        5.客戶端拿到服務端返回後,判斷返回的HttpStatus複製程式碼
        6.如果返回的HttpStatus是304,說明配置沒有變化,重新執行第1步複製程式碼
        7.如果返回的HttpStauts是200,說明配置有變化,針對變化的namespace重新去服務端拉取配置,參見1.3 通過不帶快取的Http介面從Apollo讀取配置。同時更新notifications map中的notificationId。重新執行第1步複製程式碼
    HTTP介面說明複製程式碼
    URL {config_server_url}/notifications/v2?appId={appId}&cluster={clusterName}&notifications={notifications}複製程式碼
    Method GET複製程式碼
    引數說明複製程式碼

file

TIPS 由於服務端會hold住60秒,所以請確保客戶端訪問服務端的超時時間要大於60秒;記得對引數進行URL Encode

    HTTP返回格式  該Http介面返回的是JSON格式、UTF-8編碼,包含了有變化的namespace和最新的notificationId。返回內容Sample如下
        [{
            "namespaceName": "application","notificationId": 101
        }]複製程式碼

分散式部署指南

官方展示的部署策略,生產環境部署一套Apollo-Portal+ApolloPortalDB,其他環境(PRO、UAT、FAT、DEV)單獨部署MetaServer+AdminService+ConfigService,使用獨立資料庫ApolloConfigDB及應用服務;MetaServer和Config Service部署在同一個JVM程式內,Admin Service部署在同一臺伺服器的另一個JVM程式內。部署示例如下圖file網路策略 分散式部署的時候,apollo-configservice和apollo-adminservice需要把自己的IP和埠註冊到Meta Server(apollo-configservice本身)。Apollo客戶端和Portal會從Meta Server獲取服務的地址(IP+PORT),然後通過服務地址直接訪問。apollo-configservice和apollo-adminservice是基於內網可信網路設計的,所以出於安全考慮,請不要將apollo-configservice和apollo-adminservice直接暴露在公網

    部署步驟
        建立資料庫  Apollo服務端依賴於MYSQL資料庫,所以需要事先建立並完成初始化
        獲取安裝包  Apollo服務端安裝包共3個: Apollo-AdminService、Apollo-ConfigService、Apollo-Portal
        部署Apollo服務端  獲取安裝包後就可以部署到測試和生產環境複製程式碼

小結

文章較為全面介紹開源分散式配置中心Apollo的設計、使用、應用接入及部署方法,目前客戶端只有Java和.Net版本,其他語言客戶端的接入可以通過HTTP介面的方式定時拉取更新配置或通過Http Long Polling機制實時推送,實現應用感知配置更新