背景
隨着程序功能的日益複雜,程序的配置日益增多:各種功能的開關、參數的配置、服務器的地址等等。
對程序配置的期望值也越來越高:配置修改後實時生效,灰度發佈,分環境、分集羣管理配置,完善的權限、審覈機制等等。
在這樣的大環境下,傳統的通過配置文件、數據庫等方式已經越來越無法滿足開發人員對配置管理的需求。
Apollo 配置中心應運而生!Apollo - 一個可靠的配置管理系統。
Apollo 介紹
Apollo(阿波羅)是攜程框架部門研發的分佈式配置中心,能夠集中化管理應用不同環境、不同集羣的配置,配置修改後能夠實時推送到應用端,並且具備規範的權限、流程治理等特性,適用於微服務配置管理場景。服務端基於 Spring Boot 和 Spring Cloud 開發,打包後可以直接運行,不需要額外安裝 Tomcat 等應用容器。
Apollo 支持 4 個維度管理 Key-Value 格式的配置:
- application (應用)
- environment (環境)
- cluster (集羣)
- 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 核心概念
- application (應用)
- 這個很好理解,就是實際使用配置的應用,Apollo客戶端在運行時需要知道當前應用是誰,從而可以去獲取對應的配置
- 每個應用都需要有唯一的身份標識 – appId,我們認爲應用身份是跟着代碼走的,所以需要在代碼中配置,具體信息請參見Java客戶端使用指南。
- environment (環境)
- 配置對應的環境,Apollo客戶端在運行時需要知道當前應用處於哪個環境,從而可以去獲取應用的配置
- 我們認爲環境和代碼無關,同一份代碼部署在不同的環境就應該能夠獲取到不同環境的配置
- 所以環境默認是通過讀取機器上的配置(server.properties中的env屬性)指定的,不過爲了開發方便,我們也支持運行時通過System Property等指定,具體信息請參見Java客戶端使用指南。
- cluster (集羣)
- 一個應用下不同實例的分組,比如典型的可以按照數據中心分,把上海機房的應用實例分爲一個集羣,把北京機房的應用實例分爲另一個集羣。
- 對不同的cluster,同一個配置可以有不一樣的值,如zookeeper地址。
- 集羣默認是通過讀取機器上的配置(server.properties中的idc屬性)指定的,不過也支持運行時通過System Property指定,具體信息請參見Java客戶端使用指南。
- 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
- 基於Eureka和Spring 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、錯誤重試
服務端
上圖簡要描述了配置發佈的大致過程:
- 用戶在Portal操作配置發佈
- Portal調用Admin Service的接口操作發佈
- Admin Service發佈配置後,發送ReleaseMessage給各個Config Service
- Config Service收到ReleaseMessage後,通知對應的客戶端
客戶端
上圖簡要描述了Apollo客戶端的實現原理:
- 客戶端和服務端保持了一個長連接,從而能第一時間獲得配置更新的推送。(通過Http Long Polling實現)
- 客戶端還會定時從Apollo配置中心服務端拉取應用的最新配置。
- 這是一個fallback機制,爲了防止推送機制失效導致配置不更新
- 客戶端定時拉取會上報本地版本,所以一般情況下,對於定時拉取的操作,服務端都會返回304 - Not Modified
- 定時頻率默認爲每5分鐘拉取一次,客戶端也可以通過在運行時指定System Property:
apollo.refreshInterval
來覆蓋,單位爲分鐘。
- 客戶端從Apollo配置中心服務端獲取到應用的最新配置後,會保存在內存中
- 客戶端會把從服務端獲取到的配置在本地文件系統緩存一份
- 在遇到服務不可用,或網絡不通的時候,依然能從本地恢復配置
- 應用程序可以從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網速不給力的話,可以從百度網盤下載。
- 從Github下載
- checkout或下載apollo-build-scripts項目
- 由於Quick Start項目比較大,所以放在了另外的repository,請注意項目地址
- https://github.com/nobodyiam/apollo-build-scripts
- 從百度網盤下載
- 通過https://github.com/ctripcorp/apollo/wiki/Quick-Start#13-下載quick-start安裝包頁面的網盤鏈接下載
- 下載到本地後,在本地解壓apollo-quick-start.zip
- 爲啥安裝包要58M這麼大?
- 因爲這是一個可以自啓動的jar包,裏面包含了所有依賴jar包以及一個內置的tomcat容器
安裝 Apollo
創建數據庫
Apollo 服務端共需要兩個數據庫:ApolloPortalDB
和ApolloConfigDB
,我們把數據庫、表的創建和樣例數據都分別準備了 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-service
和 order-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-service
和 order-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-service
和 order-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-service
和 order-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 結果如下:
新增配置信息
進入項目後點擊右上角的 新增配置
。
添加配置項 name
、mysql.username
、mysql.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
的文章。
🤗 您的點贊
和轉發
是對我最大的支持。
📢 掃碼關注 哈嘍沃德先生
「文檔 + 視頻」每篇文章都配有專門視頻講解,學習更輕鬆噢 ~