1. 程式人生 > >攜程 Apollo 配置中心 | 學習筆記(一) Apollo配置中心簡單介紹

攜程 Apollo 配置中心 | 學習筆記(一) Apollo配置中心簡單介紹

本章將介紹如何在Apollo配置中心中刪除已經發布的專案。

專欄目錄:

歡迎關注個人公眾號:  Coder程式設計

歡迎關注個人網站:www.52melrin.com


一、前言

     之前一直學習SpringCloud, 對於配置中心,一直也是採用的Spring Cloud Config,但是用久了,發現很多地方滿足不了要求,同時也感覺很low(個人看法勿噴)。在學習Spring cloud config  的時候也有聽到過攜程的apollo,但一直沒時間去弄。直到昨天看了一張圖,如下:使我下定決心去看看攜程的apollo配置中心。

這張圖也算是綜合對比了spring cloud config,netflix archaius, ctrip apollo, disconf, hawk 等配置中心的功能點。綜合比較下來攜程apollo 更具有優勢。

二、簡單介紹攜程Apollo配置中心

1、What is Apollo

1.1 背景

隨著程式功能的日益複雜,程式的配置日益增多:各種功能的開關、引數的配置、伺服器的地址……

對程式配置的期望值也越來越高:配置修改後實時生效,灰度釋出,分環境、分叢集管理配置,完善的許可權、稽核機制……

在這樣的大環境下,傳統的通過配置檔案、資料庫等方式已經越來越無法滿足開發人員對配置管理的需求。

Apollo配置中心應運而生!

1.2 Apollo簡介

Apollo(阿波羅)是攜程框架部門研發的開源配置管理中心,能夠集中化管理應用不同環境、不同叢集的配置,配置修改後能夠實時推送到應用端,並且具備規範的許可權、流程治理等特性。

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

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

1.3 配置基本概念

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

按照我們的理解,配置有以下幾個屬性:

  • 配置是獨立於程式的只讀變數
    • 配置首先是獨立於程式的,同一份程式在不同的配置下會有不同的行為。
    • 其次,配置對於程式是隻讀的,程式通過讀取配置來改變自己的行為,但是程式不應該去改變配置。
    • 常見的配置有:DB Connection Str、Thread Pool Size、Buffer Size、Request Timeout、Feature Switch、Server Urls等。
  • 配置伴隨應用的整個生命週期
    • 配置貫穿於應用的整個生命週期,應用在啟動時通過讀取配置來初始化,在執行時根據配置調整行為。
  • 配置可以有多種載入方式
    • 配置也有很多種載入方式,常見的有程式內部hard code,配置檔案,環境變數,啟動引數,基於資料庫等
  • 配置需要治理
    • 許可權控制
      • 由於配置能改變程式的行為,不正確的配置甚至能引起災難,所以對配置的修改必須有比較完善的許可權控制
    • 不同環境、叢集配置管理
      • 同一份程式在不同的環境(開發,測試,生產)、不同的叢集(如不同的資料中心)經常需要有不同的配置,所以需要有完善的環境、叢集配置管理
    • 框架類元件配置管理
      • 還有一類比較特殊的配置 - 框架類元件配置,比如CAT客戶端的配置。
      • 雖然這類框架類元件是由其他團隊開發、維護,但是執行時是在業務實際應用內的,所以本質上可以認為框架類元件也是應用的一部分。
      • 這類元件對應的配置也需要有比較完善的管理方式。

2、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還提供了打包指令碼,一鍵就可以生成所有需要的安裝包,並且支援自定義執行時引數

3、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();
        }
    }
}

4、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自動切換到其它存活的資料中心

5、Contribute to Apollo

Apollo從開發之初就是以開源模式開發的,所以也非常歡迎有興趣、有餘力的朋友一起加入進來。

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