微服務中集成分佈式配置中心 Apollo

背景

隨着業務的發展、微服務架構的升級,服務的數量、程序的配置日益增多(各類微服務、各類服務器地址、各類參數),傳統的配置文件方式和數據庫的方式已沒法知足開發人員對配置管理的要求:配置修改後實時生效,灰度發佈,分環境、分集羣管理配置,完善的權限、審覈機制。分佈式環境下,這些配置更加複雜。java

所以,咱們須要配置中心來統一管理配置!把業務開發者從複雜以及繁瑣的配置中解脫出來,只需專一於業務代碼自己,從而可以顯著提高開發以及運維效率。同時將配置和發佈包解藕也進一步提高發布的成功率,併爲運維的細力度管控、應急處理等提供強有力的支持。mysql

在以前的文章中,咱們介紹過 Spring Cloud 中的分佈式配置中心組件:Spring Cloud Config。本文將會介紹功能更爲強大的 Apollo。git

分佈式配置中心

在一個分佈式環境中,同類型的服務每每會部署不少實例。這些實例使用了一些配置,爲了更好地維護這些配置就產生了配置管理服務。經過這個服務能夠輕鬆地管理成千上百個服務實例的配置問題。配置中心的特色:github

  • 配置的增刪改查;
  • 不一樣環境配置隔離(開發、測試、預發佈、灰度/線上);
  • 高性能、高可用性;
  • 請求量多、高併發;
  • 讀多寫少;

現有的配置中心組件有:Spring Cloud Config、Apollo、Disconf、Diamond 等等,這些組件在功能上有或多或少的差別,可是都具備基本的配置中心的功能。spring

Apollo 簡介

Apollo(阿波羅)是攜程框架部門研發的開源配置管理中心,可以集中化管理應用不一樣環境、不一樣集羣的配置,配置修改後可以實時推送到應用端,而且具有規範的權限、流程治理等特性。目前的有超過 14k 的 star,使用普遍。Apollo基於開源模式開發,開源地址:github.com/ctripcorp/a…sql

圖片來源 Apollo
圖片來源 Apollo

首先用戶在配置中心對配置進行修改併發布;配置中心通知Apollo客戶端有配置更新;Apollo客戶端從配置中心拉取最新的配置、更新本地配置並通知到應用。數據庫

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

  • application (應用):實際使用配置的應用,Apollo客戶端在運行時須要知道當前應用是誰,從而能夠去獲取對應的配置;每一個應用都須要有惟一的身份標識 -- appId,應用身份是跟着代碼走的,因此須要在代碼中配置。
  • environment (環境):配置對應的環境,Apollo客戶端在運行時須要知道當前應用處於哪一個環境,從而能夠去獲取應用的配置。
  • cluster (集羣):一個應用下不一樣實例的分組,好比典型的能夠按照數據中心分,把上海機房的應用實例分爲一個集羣,把北京機房的應用實例分爲另外一個集羣。對不一樣的cluster,同一個配置能夠有不同的值,如zookeeper地址。
  • namespace (命名空間):一個應用下不一樣配置的分組,能夠簡單地把namespace類比爲文件,不一樣類型的配置存放在不一樣的文件中,如數據庫配置文件,RPC配置文件,應用自身的配置文件等;應用能夠直接讀取到公共組件的配置namespace,如DAL,RPC等;應用也能夠經過繼承公共組件的配置namespace來對公共組件的配置作調整,如DAL的初始數據庫鏈接數。

咱們在集成 Apollo 時,能夠根據須要建立相應的維度。bash

快速入門

下面咱們搭建一個基於 Spring Boot 的微服務,集成 Apollo。服務器

啓動服務端

Apollo配置中心包括:Config Service、Admin Service 和 Portal。

  • Config Service:提供配置獲取接口、配置推送接口,服務於Apollo客戶端;
  • Admin Service:提供配置管理接口、配置修改發佈接口,服務於管理界面Portal;
  • Portal:配置管理界面,經過MetaServer獲取AdminService的服務列表,並使用客戶端軟負載SLB方式調用AdminService。

官網準備好了一個Quick Start安裝包,你們只須要下載到本地,就能夠直接使用,免去了編譯、打包過程。也能夠自行編譯,較爲繁瑣。

Apollo服務端共須要兩個數據庫:ApolloPortalDB和ApolloConfigDB。建立的語句見安裝包,建立好以後須要配置啓動的腳本,即 demo.sh 腳本:

#apollo config db info
apollo_config_db_url=jdbc:mysql://localhost:3306/ApolloConfigDB?characterEncoding=utf8
apollo_config_db_username=用戶名
apollo_config_db_password=密碼(若是沒有密碼,留空便可)

# apollo portal db info
apollo_portal_db_url=jdbc:mysql://localhost:3306/ApolloPortalDB?characterEncoding=utf8
apollo_portal_db_username=用戶名
apollo_portal_db_password=密碼(若是沒有密碼,留空便可)
複製代碼

腳本會在本地啓動3個服務,分別使用8070, 8080, 8090端口,請確保這3個端口當前沒有被使用。執行

./demo.sh start
複製代碼

看到輸出以下的日誌信息:

==== starting service ====
Service logging file is ./service/apollo-service.log
Started [10768]
Waiting for config service startup.......
Config service started. You may visit http://localhost:8080 for service status now!
Waiting for admin service startup....
Admin service started
==== starting portal ====
Portal logging file is ./portal/apollo-portal.log
Started [10846]
Waiting for portal startup......
Portal started. You can visit http://localhost:8070 now!
複製代碼

Apollo 服務端啓動成功。

客戶端應用

搭建好 Apollo 服務器以後,接下來將咱們的應用接入 Apollo。

引入依賴
<dependency>
            <groupId>com.ctrip.framework.apollo</groupId>
            <artifactId>apollo-client</artifactId>
            <version>1.1.0</version>
        </dependency>
複製代碼

在依賴中只須要增長 apollo-client 的引用。

入口程序
@SpringBootApplication
@EnableApolloConfig("TEST1.product")
public class ApolloApplication {

    public static void main(String[] args) {
        SpringApplication.run(ApolloApplication.class, args);
    }
}
複製代碼

咱們經過 @EnableApolloConfig("TEST1.product") 註解開啓註冊到 Apollo 服務端,並指定了 namespace 爲 TEST1.product

配置文件
app.id: spring-boot-logger
  # set apollo meta server address, adjust to actual address if necessary
apollo.meta: http://localhost:8080
server:
 port: 0
複製代碼

配置文件中指定了appid 和 Apollo 服務器的地址。

測試應用

咱們經過動態設置輸出的日誌等級來測試接入的配置中心。

@Service
public class LoggerConfiguration {
    private static final Logger logger = LoggerFactory.getLogger(LoggerConfiguration.class);
    private static final String LOGGER_TAG = "logging.level.";

    @Autowired
    private LoggingSystem loggingSystem;

    @ApolloConfig
    private Config config;

    @ApolloConfigChangeListener
    // 監聽 Apollo 配置中心的刷新事件
    private void onChange(ConfigChangeEvent changeEvent) {
        refreshLoggingLevels();
    }

    @PostConstruct
    // 設置刷新以後的日誌級別
    private void refreshLoggingLevels() {
        Set<String> keyNames = config.getPropertyNames();
        for (String key : keyNames) {
            if (containsIgnoreCase(key, LOGGER_TAG)) {
                String strLevel = config.getProperty(key, "info");
                LogLevel level = LogLevel.valueOf(strLevel.toUpperCase());
                loggingSystem.setLogLevel(key.replace(LOGGER_TAG, ""), level);
                logger.info("{}:{}", key, strLevel);
            }
        }
    }

    private static boolean containsIgnoreCase(String str, String searchStr) {
        if (str == null || searchStr == null) {
            return false;
        }
        int len = searchStr.length();
        int max = str.length() - len;
        for (int i = 0; i <= max; i++) {
            if (str.regionMatches(true, i, searchStr, 0, len)) {
                return true;
            }
        }
        return false;
    }
}
複製代碼

如上的配置類用於根據 Apollo 配置中心的日誌等級配置,設置本地服務的日誌等級,並監聽刷新事件,將刷新後的配置及時應用到本地服務,其中 @PostConstruct 註解用於在完成依賴項注入以執行任何初始化以後須要執行的方法。

@Service
public class PrintLogger {
    private static Logger logger = LoggerFactory.getLogger(PrintLogger.class);

    @ApolloJsonValue("${kk.v}")
    private String v;

    @PostConstruct
    public void printLogger() throws Exception {
        Executors.newSingleThreadExecutor().submit(() -> {
            while (true) {
                logger.error("=========" + v);
                logger.info("我是info級別日誌");
                logger.error("我是error級別日誌");
                logger.warn("我是warn級別日誌");
                logger.debug("我是debug級別日誌");
                TimeUnit.SECONDS.sleep(1);
            }
        });
    }
}
複製代碼

起一個線程,輸出不一樣級別的日誌。根據配置的日誌等級,過濾後再打印。咱們在如上的程序中,還自定義了一個字段,一樣用以測試隨機打印最新的值。

測試

咱們在 Apollo 的配置界面中,增長以下的配置:

並將配置發佈,啓動咱們本地的 SpringBoot 服務:

2019-05-28 20:31:36.688 ERROR 44132 --- [pool-1-thread-1] com.blueskykong.apollo.PrintLogger       : =========log-is-error-level.
2019-05-28 20:31:36.688 ERROR 44132 --- [pool-1-thread-1] com.blueskykong.apollo.PrintLogger       : 我是error級別日誌
複製代碼

咱們將調整日誌的級別爲warn,只須要在界面上編輯。

將編輯好的配置發佈,應用服務將會收到刷新事件。

能夠看到,服務刷新了日誌的級別,打印 warn 的日誌信息。

2019-05-28 20:35:56.819  WARN 44132 --- [pool-1-thread-1] com.blueskykong.apollo.PrintLogger       : 我是warn級別日誌
2019-05-28 20:36:06.823 ERROR 44132 --- [pool-1-thread-1] com.blueskykong.apollo.PrintLogger       : =========log-is-warn-level.
複製代碼

原理細究

在體驗了 Apollo 做爲配置中心以後,咱們將瞭解下 Apollo 的整體設計和實現的原理。

Apollo 總體架構

圖片來源 Apollo
圖片來源 Apollo

上圖簡要描述了 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進程中

ConfigService、AdminService、Portal 屬於 Apollo 服務端的模塊,其中提到的 Eureka 是爲了保證高可用,Config 和 Admin 都是無狀態以集羣方式部署的,Client 怎麼找到 Config?Portal 怎麼找到 Admin?爲了解決這個問題,Apollo在其架構中引入了Eureka服務註冊中心組件,實現微服務間的服務註冊和發現用於服務發現和註冊,Config 和 Admin Service註冊實例並按期報心跳, Eureka 與 ConfigService 一塊兒部署。

MetaServer 實際上是一個Eureka的Proxy,將Eureka的服務發現接口以更簡單明確的HTTP接口的形式暴露出來,方便 Client/Protal 經過簡單的 HTTPClient 就能夠查詢到 Config/Admin 的地址列表。獲取到服務實例地址列表以後,再以簡單的客戶端軟負載(Client SLB)策略路由定位到目標實例,併發起調用。

客戶端實現

在配置中心中,一個重要的功能就是配置發佈後實時推送到客戶端。下面咱們簡要看一下這塊是怎麼設計實現的。

上圖簡要描述了配置發佈的大體過程:用戶在Portal操做配置發佈;Portal調用Admin Service的接口操做發佈;Admin Service發佈配置後,發送ReleaseMessage給各個Config Service;Config Service收到ReleaseMessage後,通知對應的客戶端。

如何通知客戶端呢?咱們看到 Apollo 的實現步驟以下:

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

小結

本文首先介紹分佈式配置中心的概念和 Apollo 接入的實踐,而後深刻介紹了 Apollo 的整體架構和實現的一些細節。總得來講, Apollo 是現有配置中心組件中,功能最全的一個。可以集中化管理應用不一樣環境、不一樣集羣的配置,配置修改後可以實時推送到應用端,而且具有規範的權限、流程治理等特性,適用於微服務配置管理場景。

本文對應的代碼地址: github.com/keets2012/S…

訂閱最新文章,歡迎關注個人公衆號

微信公衆號

參考

  1. apollo
  2. 微服務架構~攜程Apollo配置中心架構剖析
相關文章
相關標籤/搜索