分佈式配置中心--Apollo

Apollo(阿波羅)是攜程開源的分佈式配置中心,可以集中化管理應用不一樣環境、不一樣集羣的配置,支持配置熱發佈並實時推送到應用端,而且具有規範的權限及流程治理等特性,適用於分佈式微服務配置管理場景java

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已經能知足平常配置使用場景json

客戶端獲取"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); // k6 = v6
        publicConfig.getProperty("k7", null); // k7 = v7複製代碼
應用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配置變動的監聽器是註冊在Config對象上app

在應用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機制實時推送,實現應用感知配置更新

相關文章
相關標籤/搜索