Spring Cloud 系列之 Apollo 配置中心(一)

背景

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

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

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

Apollo 配置中心應運而生!Apollo - 一個可靠的配置管理系統。

Apollo 介紹

Apollo(阿波羅)是攜程框架部門研發的分佈式配置中心,能夠集中化管理應用不同環境、不同集羣的配置,配置修改後能夠實時推送到應用端,並且具備規範的權限、流程治理等特性,適用於微服務配置管理場景。服務端基於 Spring Boot 和 Spring Cloud 開發,打包後可以直接運行,不需要額外安裝 Tomcat 等應用容器。

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

  1. application (應用)
  2. environment (環境)
  3. cluster (集羣)
  4. namespace (命名空間 Namespace 是配置項的集合,類似於一個配置文件的概念)

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

官網文檔:https://github.com/ctripcorp/apollo/wiki/Quick-Start

演示環境(Demo):

  • http://106.54.227.205/
  • 賬號/密碼:apollo/admin

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

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

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的初始數據庫連接數

Apollo 特性

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

Apollo 總體設計

官方文檔:https://github.com/ctripcorp/apollo/wiki/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進程中

1.3 各模塊概要介紹

1.3.1 Config Service

  • 提供配置獲取接口
  • 提供配置更新推送接口(基於Http long polling)
    • 服務端使用Spring DeferredResult實現異步化,從而大大增加長連接數量
    • 目前使用的tomcat embed默認配置是最多10000個連接(可以調整),使用了4C8G的虛擬機實測可以支撐10000個連接,所以滿足需求(一個應用實例只會發起一個長連接)。
  • 接口服務對象爲Apollo客戶端

1.3.2 Admin Service

  • 提供配置管理接口
  • 提供配置修改、發佈等接口
  • 接口服務對象爲Portal

1.3.3 Meta Server

  • Portal通過域名訪問Meta Server獲取Admin Service服務列表(IP+Port)
  • Client通過域名訪問Meta Server獲取Config Service服務列表(IP+Port)
  • Meta Server從Eureka獲取Config Service和Admin Service的服務信息,相當於是一個Eureka Client
  • 增設一個Meta Server的角色主要是爲了封裝服務發現的細節,對Portal和Client而言,永遠通過一個Http接口獲取Admin Service和Config Service的服務信息,而不需要關心背後實際的服務註冊和發現組件
  • Meta Server只是一個邏輯角色,在部署時和Config Service是在一個JVM進程中的,所以IP、端口和Config Service一致

1.3.4 Eureka

  • 基於EurekaSpring Cloud Netflix提供服務註冊和發現
  • Config Service和Admin Service會向Eureka註冊服務,並保持心跳
  • 爲了簡單起見,目前Eureka在部署時和Config Service是在一個JVM進程中的(通過Spring Cloud Netflix)

1.3.5 Portal

  • 提供Web界面供用戶管理配置
  • 通過Meta Server獲取Admin Service服務列表(IP+Port),通過IP+Port訪問服務
  • 在Portal側做load balance、錯誤重試

1.3.6 Client

  • Apollo提供的客戶端程序,爲應用提供配置獲取、實時更新等功能
  • 通過Meta Server獲取Config Service服務列表(IP+Port),通過IP+Port訪問服務
  • 在Client側做load balance、錯誤重試

服務端

上圖簡要描述了配置發佈的大致過程:

  1. 用戶在Portal操作配置發佈
  2. Portal調用Admin Service的接口操作發佈
  3. Admin Service發佈配置後,發送ReleaseMessage給各個Config Service
  4. 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 搭建服務端視頻(獲取更多請關注公衆號「哈嘍沃德先生」)

Java

  • Apollo 服務端:1.8+
  • Apollo 客戶端:1.7+

由於需要同時啓動服務端和客戶端,所以建議安裝Java 1.8+。

MySQL

  • 版本要求:5.6.5+

Apollo的表結構對timestamp使用了多個default聲明,所以需要5.6.5以上版本。

下載Quick Start安裝包

Apollo 給我們準備好了一個Quick Start安裝包,大家只需要下載到本地,就可以直接使用,免去了編譯、打包過程。

安裝包共50M,如果訪問github網速不給力的話,可以從百度網盤下載。

  1. 從Github下載
    • checkout或下載apollo-build-scripts項目
    • 由於Quick Start項目比較大,所以放在了另外的repository,請注意項目地址
      • https://github.com/nobodyiam/apollo-build-scripts
  2. 從百度網盤下載
  3. 爲啥安裝包要58M這麼大?
    • 因爲這是一個可以自啓動的jar包,裏面包含了所有依賴jar包以及一個內置的tomcat容器

安裝 Apollo

創建數據庫

Apollo 服務端共需要兩個數據庫:ApolloPortalDBApolloConfigDB,我們把數據庫、表的創建和樣例數據都分別準備了 sql 文件,只需要導入數據庫即可。

注意:如果你本地已經創建過Apollo數據庫,請注意備份數據。我們準備的sql文件會清空Apollo相關的表。

創建 ApolloPortalDB 數據庫

通過各種MySQL客戶端導入sql/apolloportaldb.sql即可。

創建 ApolloConfigDB 數據庫

通過各種MySQL客戶端導入sql/apolloconfigdb.sql即可。

配置數據庫連接信息

Apollo 服務端需要知道如何連接到你前面創建的數據庫,所以需要編輯demo.sh,修改 ApolloPortalDB 和 ApolloConfigDB 相關的數據庫連接串信息。

注意:填入的用戶需要具備對 ApolloPortalDB 和 ApolloConfigDB 數據的讀寫權限。

#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=密碼(如果沒有密碼,留空即可)

注意:不要修改 demo.sh 的其它部分

搭建服務端

確保端口未被佔用

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

執行啓動腳本

./demo.sh start

Apollo 提供的腳本文件爲 .sh 文件,如果你的安裝環境是在 Linux 系統下直接運行以上命令即可,如果你想在 Windows 環境下運行該腳本,先安裝 Git 然後在 demo.sh 所在目錄下鼠標右鍵點擊 Git Bash Here,然後再通過以上命令運行腳本即可。

當看到如下輸出後,就說明啓動成功了!

==== 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!

異常排查

如果啓動遇到了異常,可以分別查看 service 和 portal 目錄下的 log 文件排查問題。

注:在啓動 apollo-configservice 的過程中會在日誌中輸出 eureka 註冊失敗的信息,如com.sun.jersey.api.client.ClientHandlerException: java.net.ConnectException: Connection refused。需要注意的是,這個是預期的情況,因爲 apollo-configservice 需要向 Meta Server(它自己)註冊服務,但是因爲在啓動過程中,自己還沒起來,所以會報這個錯。後面會進行重試的動作,所以等自己服務起來後就會註冊正常了。

訪問

訪問:http://localhost:8070/ Quick Start 集成了 Spring Security,輸入用戶名 apollo,密碼 admin 後登錄。

登錄成功後,首頁如下,Apollo 還提供了一個 SampleApp 樣本案例供我們學習使用。

創建項目

點擊對應按鈕創建項目。

這裏先通過默認的樣例部門演示(後面我會講如何添加部門),AppId 對應客戶端配置文件中 app.id。

創建成功如下圖。

客戶端接入服務端

點擊鏈接觀看:Apollo 客戶端接入服務端視頻(獲取更多請關注公衆號「哈嘍沃德先生」)

下面通過最常用、便捷的方式,即基於 Spring Boot 的集成方式來接入服務端。

apollo-demo 聚合工程。Spring Boot 2.2.4.RELEASE

  • order-service:訂單服務,端口 9090
  • order-service02:訂單服務,端口 9091

添加依賴

<!-- https://mvnrepository.com/artifact/com.ctrip.framework.apollo/apollo-client -->
<dependency>
    <groupId>com.ctrip.framework.apollo</groupId>
    <artifactId>apollo-client</artifactId>
    <version>1.6.0</version>
</dependency>

配置文件

order-serviceorder-service02 的配置信息除端口外一致。

server:
  port: 9090 # 端口

spring:
  application:
    name: order-service # 應用名稱

# apollo 相關配置
app:
  id: order-service # 與 Apollo 配置中心中的 AppId 一致

apollo:
  meta: http://localhost:8080 # Apollo 中的 Eureka 註冊中心地址
  #cluster:  # 指定 Apollo 集羣,相同集羣實例使用對應集羣的配置
  #cacheDir:  # 配置緩存目錄,網絡不可用時任然可提供配置服務
  bootstrap:
    enable: true # 啓用 apollo

env: DEV # 指定環境

# 自定義配置
name: order-service-dev
mysql:
  host: localhost
  port: 3306
  username: root
  password: root

配置文件實體類

order-serviceorder-service02 的配置文件實體類代碼一致。

package com.example.config;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ConfigProperties {

    @Value("${name}")
    private String name;
    @Value("${mysql.host}")
    private String mysqlHost;
    @Value("${mysql.port}")
    private Integer mysqlPort;
    @Value("${mysql.username}")
    private String mysqlUsername;
    @Value("${mysql.password}")
    private String mysqlPassword;

}

控制層

order-serviceorder-service02 的控制層代碼一致。

package com.example.controller;

import com.example.config.ConfigProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@RestController
public class ConfigController {

    @Autowired
    private ConfigProperties configProperties;

    @Value("${name}")
    private String name;

    @GetMapping("/name")
    public String getName() {
        return configProperties.getName();
    }

    @GetMapping("/mysql")
    public Map<Object, Object> getMySQLProperties() {
        // JDK9中的新特性,快速創建只讀集合。
        return Map.of("host", configProperties.getMysqlHost(),
                "port", configProperties.getMysqlPort(),
                "username", configProperties.getMysqlUsername(),
                "password", configProperties.getMysqlPassword());
    }

}

啓動類

啓動類需要添加 @EnableApolloConfig 註解。

order-serviceorder-service02 的啓動類代碼一致。

package com.example;

import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@EnableApolloConfig
@SpringBootApplication
public class OrderServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }

}

測試

修改配置信息前

訪問:http://localhost:9090/name 和 http://localhost:9091/name 結果如下:

訪問:http://localhost:9090/mysql 和 http://localhost:9091/mysql 結果如下:

新增配置信息

進入項目後點擊右上角的 新增配置

添加配置項 namemysql.usernamemysql.password

發佈配置信息

將剛纔添加的配置信息批量發佈至應用。

修改配置信息後

控制檯打印信息如下:

c.f.a.s.p.AutoUpdateConfigChangeListener : Auto update apollo changed value successfully, new value: order-service-dev-2.0, key: name, beanName: configController, field: com.example.controller.ConfigController.name
c.f.a.s.p.AutoUpdateConfigChangeListener : Auto update apollo changed value successfully, new value: root123, key: mysql.password, beanName: configProperties, field: com.example.config.ConfigProperties.mysqlPassword
c.f.a.s.p.AutoUpdateConfigChangeListener : Auto update apollo changed value successfully, new value: root123, key: mysql.username, beanName: configProperties, field: com.example.config.ConfigProperties.mysqlUsername

訪問:http://localhost:9090/name 和 http://localhost:9091/name 結果如下:

訪問:http://localhost:9090/mysql 和 http://localhost:9091/mysql 結果如下:

以上只是 Apollo 的入門教程,後面我們會學習 Apollo 的更多高級玩法,比如多環境部署,高可用環境搭建等等。

下一篇我們講解 Apollo 部門管理、用戶管理、配置管理、集羣管理,記得關注噢~

本文采用 知識共享「署名-非商業性使用-禁止演繹 4.0 國際」許可協議

大家可以通過 分類 查看更多關於 Spring Cloud 的文章。


🤗 您的點贊轉發是對我最大的支持。

📢 掃碼關注 哈嘍沃德先生「文檔 + 視頻」每篇文章都配有專門視頻講解,學習更輕鬆噢 ~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章