聊聊 分佈式配置中心 Apollo

大家好,我是不才陳某~

今天深入聊一聊攜程開源的一款分佈式配置中心 Apollo,在功能上和 Nacos 不相上下。

1. 基本概念

由於 Apollo 概念比較多,剛開始使用比較複雜,最好先過一遍概念再動手實踐嘗試使用。

1、背景

隨着程序功能的日益複雜,程序的配置日益增多,各種功能的開關、參數的配置、服務器的地址…… 對程序配置的期望值也越來越高,配置修改後實時生效,灰度發佈,分環境、分集羣管理配置,完善的權限、審覈機制…… 在這樣的大環境下,傳統的通過配置文件、數據庫等方式已經越來越無法滿足開發人員對配置管理的需求。因此 Apollo 配置中心應運而生!

2、簡介

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

3、特點

4、基礎模型

如下即是 Apollo 的基礎模型:

5、Apollo 的四個維度

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

(1)、application

(2)、environment

在實際開發中,我們的應用經常要部署在不同的環境中,一般情況下分爲開發、測試、生產等等不同環境,不同環境中的配置也是不同的,在 Apollo 中默認提供了四種環境:

在程序中如果想指定使用哪個環境,可以配置變量 env 的值爲對應環境名稱即可。

(3)、cluster

(4)、namespace

一個應用中不同配置的分組,可以簡單地把 namespace 類比爲不同的配置文件,不同類型的配置存放在不同的文件中,如數據庫配置文件,RPC 配置文件,應用自身的配置文件等。

熟悉 SpringBoot 的都知道,SpringBoot 項目都有一個默認配置文件 application.yml,如果還想用多個配置,可以創建多個配置文件來存放不同的配置信息,通過指定 spring.profiles.active 參數指定應用不同的配置文件。這裏的 namespace 概念與其類似,將不同的配置放到不同的配置 namespace 中。

Namespace 分爲兩種權限,分別爲:

Namespace 分爲三種類型,分別爲:

6、本地緩存

Apollo 客戶端會把從服務端獲取到的配置在本地文件系統緩存一份,用於在遇到服務不可用,或網絡不通的時候,依然能從本地恢復配置,不影響應用正常運行。

本地緩存路徑默認位於以下路徑,所以請確保 / opt/data 或 C:\opt\data \ 目錄存在,且應用有讀寫權限。

本地配置文件會以下面的文件名格式放置於本地緩存路徑下:

{appId}+{cluster}+{namespace}.properties

7、客戶端設計

上圖簡要描述了 Apollo 客戶端的實現原理

配置更新推送實現

前面提到了 Apollo 客戶端和服務端保持了一個長連接,從而能第一時間獲得配置更新的推送。長連接實際上我們是通過 Http Long Polling 實現的,具體而言:

8、總體設計

上圖簡要描述了 Apollo 的總體設計,我們可以從下往上看:

9、可用性考慮

配置中心作爲基礎服務,可用性要求非常高,下面的表格描述了不同場景下 Apollo 的可用性:

nzoMMq

2. Apollo 配置中心創建項目與配置

接下來我們將創建一個 Apollo 的客戶端項目,引用 Apollo 來實現配置動態更新,不過在此之前我們需要提前進入 Apollo Portal 界面,在裏面提前創建一個項目並在其配置一個參數,方便後續客戶端引入該配置參數,測試是否能動態變化。

1、登錄 Apollo

我這裏是部署到 Kubernetes 中,通過 NodePort 方式暴露出一個端口,打開這個地址登錄 Apollo:

2、修改與增加部門數據

在登錄後創建項目時,選擇部門默認只能選擇 Apollo 自帶的 測試部門 1 與測試部門 2 兩個選項。

開始這真讓人迷糊,原來 Apoloo 沒有修改或新增部門信息的管理節目,只能通過修改數據庫,來新增或者修改數據,這裏打開 Portal 對月的數據庫中的表 ApolloPortalDB 修改 keyorganizationsvalue 的 json 數據,改成自己對於的部門信息。

3、創建一個項目

修改完數據庫部門信息後,重新登錄 Apollo Portal,然後創建項目,這時候選擇部門可以看到已經變成我們自己修改後的部門信息了,選擇我們自定義部門,然後設置應用 ID 爲 apollo-test,應用名爲 apollo-demo

創建完成後進入配置管理界面

4、創建一個配置參數

創建一個配置參數,方便後續 Apollo 客戶端項目引入該參數,進行動態配置測試。

設置 key 爲 test value 爲 123456 然後設置一個備註,保存。

創建完成後可以看到配置管理節目新增了一條配置。

接下來我們將此配置通過發佈按鈕,進行發佈。

3. 創建 Apollo 客戶端測試項目

這裏創建一個 SpringBoot 項目,引入 Apollo 客戶端來來實現與 Apollo 配置中心服務端交互。

1、Mavne 添加 Apollo 依賴

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.8.RELEASE</version>
    </parent>

    <groupId>club.mydlq</groupId>
    <artifactId>apollo-demo</artifactId>
    <version>0.0.1</version>
    <name>apollo-demo</name>
    <description>Apollo Demo</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.ctrip.framework.apollo</groupId>
            <artifactId>apollo-client</artifactId>
            <version>1.4.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2、配置文件添加參數

在 application.yml 配置文件中添加下面參數,這裏簡單介紹下 Apollo 參數作用:

#應用配置
server:
  port: 8080
spring:
  application:
    name: apollo-demo

#Apollo 配置
app:
  id: apollo-test                            #應用ID
apollo:
  cacheDir: /opt/data/                       #配置本地配置緩存目錄
  cluster: default                           #指定使用哪個集羣的配置
  meta: http://192.168.2.11:30002            #DEV環境配置中心地址
  autoUpdateInjectedSpringProperties: true   #是否開啓 Spring 參數自動更新
  bootstrap:                                
    enabled: true                            #是否開啓 Apollo
    namespaces: application                  #設置 Namespace
    eagerLoad:
      enabled: false                         #將 Apollo 加載提到初始化日誌系統之前

3、創建測試 Controller 類

寫一個 Controller 類來輸出 test 變量的值,使用了 Spring@Value 註解,用於讀取配置文件中的變量的值,這裏來測試該值,項目啓動後讀取到的變量的值是設置在 application 配置文件中的默認值,還是遠程 Apollo 中的值,如果是 Apollo 中配置的值,那麼再測試在 Apollo 配置中心中改變該變量的值後,這裏是否會產生變化。

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

    @Value("${test:默認值}")
    private String test;

    @GetMapping("/test")
    public String test(){
        return "test的值爲:" + test;
    }
}

4、創建啓動類

SpringBoot 項目啓動類。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

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

}

5、JVM 啓動參數添加啓動參數

由於本人的 Apollo 是部署在 Kubernetes 環境中的,JVM 參數中必須添加兩個變量:

如果是在 Idea 中啓動,可以配置啓動參數,加上:

-Dapollo.configService=http://192.168.2.11:30002 -Denv=DEV

如果是 java 命令啓動程序,需要 JVM 加上:

$ java -Dapollo.configService=http://192.168.2.11:30002 -Denv=DEV -jar apollo-demo.jar

注意:上面 env 指定的環境,要和 apollo.meta 指定 Config 地址的環境一致,例如 -Denv=DEV 即使用開發環境,那麼 apollo.meta=http://xxx.xxx.xxx:8080 這個 url 的 Config 也是開發環境下的配置中心服務,而不能是 PRO 或者其它環境下的配置中心。

4. 啓動項目進行測試

1、測試是否能夠獲取 Apollo 中設置的值

啓動上面的測試用例,然後輸入地址 http://localhost:8080/test 查看:

test的值爲:123456

可以看到使用的是 Apollo 中配置的 test 參數的值 123456,而不是默認的值。

2、測試當 Apollo 中修改參數值後客戶端是否能及時刷新

修改 Apollo 配置中心參數 test 值爲 666666 ,然後再次發佈。

發佈完成後再次輸入地址 http://localhost:8080/test 查看:

test的值爲:666666

可以看到示例應用中的值已經改變爲最新的值。

3、測試當 Apollo 執行配置回滾操作時客戶端是否能及時改變

回滾完成後狀態將變爲未發佈狀態,則時候輸入地址 http://localhost:8080/test 查看:

test的值爲:123456

可以看到已經回滾到之前的 test 配置的值了。

4、測試當不能訪問 Apollo 時客戶端的變化

這裏我們將 JVM 參數中 Apollo 配置中心地址故意改錯:

-Dapollo.configService=http://192.168.2.100:30002 -Denv=DEV

然後輸入地址 http://localhost:8080/test 可以看到值爲:

test的值爲:123456

可以看到顯示的值並不是我們定義的默認值,而還是 Apollo 配置中心配置的 test 參數的值。考慮到由於 Apollo 會在本地將配置緩存一份,出現上面原因,估計是緩存生效。當客戶端不能連接到 Apollo 配置中心時候,默認使用本地緩存文件中的配置。

上面我們配置了本地緩存配置文件存放地址爲 "/opt/data/" ,接下來進入緩存目錄,找到對應的緩存配置文件,刪除緩存配置文件後,重啓應用,再次輸入地址查看:

test的值爲:默認值

刪除緩存配置文件後,可以看到輸出的值爲自己定義的默認值。

5、測試當 Apollo 中將參數刪除後客戶端的變化

這裏我們進入 Apollo 配置中心,刪除之前創建的 test 參數,然後發佈。

然後再次打開地址 http://localhost:8080/test 查看:

test的值爲:默認值

可以看到顯示的是應用程序中設置的默認值。

5. 對 Apollo 的 Cluster、Namespace 進行探究

在 Apollo 中,配置可以根據不同的環境劃分爲 Dev(開發)、Prod(生產) 等環境,又能根據區域劃分爲不同的 Cluster(集羣),還能根據配置參數作用功能的不同劃分爲不同的 Namespace(命名空間),這裏探究下,如何使用上述能力。

1、不同環境下的配置

(1)、Apollo 配置中心 PRO 環境添加參數

打開 Apollo 配置中心,環境列表點擊 PRO 環境,然後新增一條配置,和之前例子中參數保持一致,都爲 test 參數,創建完成後發佈。

然後修改上面的示例項目,將配置參數指定爲 PRO 環境:

(2)、示例項目修改 application.yml 配置文件

apollo.meta 參數改成 RPO 的配置中心地址

......

apollo:
  meta: http://192.168.2.11:30005            #RPO環境配置中心地址
  
......

(3)、示例項目修改 JVM 參數

apollo.configService 參數改成 PRO 配置中心地址,env 參數的值改爲 PRO

-Dapollo.configService=http://192.168.2.11:30005 -Denv=PRO

(4)、啓動示例項目觀察結果

啓動示例項目,然後接着輸入地址 http://localhost:8080/test 查看信息:

test的值爲:abcdefg

可以看到已經改成生成環境配置,所以在實際項目中,如果要更換環境,需要修改 JVM 參數 env(如果 Apollo 部署在 Kubernetes 環境中,還需要修改 apollo.configService 參數),和修改 application.yml 配置文件的參數 apollo.meta 值。

2、不同集羣下的配置

(1)、創建兩個集羣

例如在開發過程中,經常要將應用部署到不同的機房,這裏分別創建 beijingshanghai 兩個集羣。

(2)、兩個集羣都配置同樣的參數不同的值

在兩個集羣 beijingshanghai 中,都統一配置參數 test,並且設置不同的值。

(3)、示例項目 application.yml 修改集羣配置參數, 並啓動項目觀察結果

指定集羣爲 beijing:

......

apollo:
  cluster: beijing                      #指定使用 beijing 集羣

......

啓動示例項目,然後接着輸入地址 http://localhost:8080/test 查看信息:

test的值爲:Cluster-BeiJing

可以看到用的是 beijing 集羣的配置

指定集羣爲 shanghai:

......

apollo:
  cluster: shanghai                      #指定使用 shanghai 集羣

......

啓動示例項目,然後接着輸入地址 http://localhost:8080/test 查看信息:

test的值爲:Cluster-ShangHai

可以看到用的是 shanghai 集羣的配置

3、不同命名空間下的配置

(1)、創建兩個命名空間

命名空間有兩種,一種是 public(公開),一種是 private 私有,公開命名空間所有項目都能讀取配置信息,而私有的只能 app.id 值屬於該應用的才能讀取配置。

這裏創建 dev-1dev-2 兩個私有的命名空間,用於測試。

(2)、兩個集羣都配置同樣的參數不同的值

在兩個命名空間中,都統一配置參數 test,並且設置不同的值,設置完後發佈。

(3)、示例項目 application.yml 修改命名空間配置參數, 並啓動項目觀察結果

指定命名空間爲 dev-1:

......

apollo:
  bootstrap:
    namespaces: dev-1                   #設置 dev-1 命名空間

......

啓動示例項目,然後接着輸入地址 http://localhost:8080/test 查看信息:

test的值爲:dev-1 Namespace

可以看到用的是 dev-1 命名空間的配置

指定命名空間爲 dev-2:

......

apollo:
  bootstrap:
    namespaces: dev-2                   #設置 dev-1 命名空間

......

YAML

啓動示例項目,然後接着輸入地址 http://localhost:8080/test 查看信息:

test的值爲:dev-2 Namespace

HTML

可以看到用的是 dev-2 命名空間的配置

  1. Kubernetes 的 SpringBoot 應用使用 Apollo 配置中心

本人的 Apollo 和 SpringBoot 應用一般都是基於 Kubernetes 部署的,所以這裏簡單介紹下,如何在 Kubernetes 環境下部署 SpringBoot 應用且使用 Apollo 作爲配置中心。

這裏項目依舊使用上面的示例,不過首先要將其編譯成 Docker 鏡像,方便後續部署到 Kubernetes 環境下。

1、構建 Docker 鏡像

(1)、執行 Maven 編譯

首先執行 Maven 命令,將項目編譯成一個可執行 JAR。

$ mvn clean install

BASH

(2)、準備 Dockerfile

創建構建 Docker 鏡像需要的 Dockerfile 文件,將 Maven 編譯的 JAR 複製到鏡像內部,然後設置兩個變量,分別是:

Dockerfile:

FROM openjdk:8u222-jre-slim
VOLUME /tmp
ADD target/*.jar app.jar
RUN sh -c 'touch /app.jar'
ENV JAVA_OPTS="-XX:MaxRAMPercentage=80.0 -Duser.timezone=Asia/Shanghai"
ENV APP_OPTS=""
ENTRYPOINT [ "sh""-c""java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar $APP_OPTS" ]

(3)、構建 Docker 鏡像

執行 Docker Build 命令構建 Docker 鏡像。

$ docker build -t mydlqclub/springboot-apollo:0.0.1 .

BASH

2、Kubernetes 部署示例應用

(1)、創建 SpringBoot 且使用 Apollo 配置中心的 Kubernetes 部署文件

這裏創建 Kubernetes 下的 SpringBoot 部署文件 apollo-demo-example.yaml。在之前 Dockerfile 中設置了兩個環境變量,JAVA_OPTSAPP_OPTS。其中 JAVA_OPTS 變量的值將會作爲 JVM 啓動參數,APP_OPTS 變量的值將會作爲應用的配置參數。所以,這裏我們將 Apollo 配置參數放置到變量中,這樣一來就可以方便修改與維護 Apollo 的配置信息。

在下面配置的環境變量參數中,設置的配置中心地址爲 http://service-apollo-config-server-dev.mydlqclub:8080,這是因爲 Apollo 部署在 K8S 環境中,且可以使用域名方式訪問,service-apollo-config-server-dev 是應用的 Service 名稱,mydlqcloud 是 K8S 下的 Namespace 名稱。

springboot-apollo.yaml

apiVersion: v1
kind: Service
metadata:
  name: springboot-apollo
spec:
  type: NodePort
  ports:
    - name: server
      nodePort: 31080
      port: 8080
      targetPort: 8080
    - name: management
      nodePort: 31081
      port: 8081
      targetPort: 8081
  selector:
    app: springboot-apollo
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: springboot-apollo
  labels:
    app: springboot-apollo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: springboot-apollo
  template:
    metadata:
      name: springboot-apollo
      labels:
        app: springboot-apollo
    spec:
      restartPolicy: Always
      containers:
        - name: springboot-apollo
          image: mydlqclub/springboot-apollo:0.0.1
          imagePullPolicy: Always
          ports:
            - containerPort: 8080
              name: server
          env:
            - name: JAVA_OPTS
              value: "-Denv=DEV"
              ##注意修改此處的 mydlqcloud 爲你自己的 Namespace 名稱
            - name: APP_OPTS
              value: "
                     --app.id=apollo-demo
                     --apollo.bootstrap.enabled=true
                     --apollo.bootstrap.eagerLoad.enabled=false
                     --apollo.cacheDir=/opt/data/
                     --apollo.cluster=default
                     --apollo.bootstrap.namespaces=application
                     --apollo.autoUpdateInjectedSpringProperties=true
                     --apollo.meta=http://service-apollo-config-server-dev.mydlqcloud:8080    
                     "
          resources:
            limits:
              memory: 1000Mi
              cpu: 1000m
            requests:
              memory: 500Mi
              cpu: 500m

(2)、部署 SpringBoot 應用到 Kubernetes

-n:創建應用到指定的 Namespace 中。

$ kubectl apply -f springboot-apollo.yaml -n mydlqcloud

BASH

3、測試部署的應用接口

上面的應用配置了 NodePort 端口,可以通過此端口訪問 Kubernetes 集羣內的應用接口,本人 Kubernetes 集羣地址爲 192.168.2.11 且 NodePort 端口爲 31081,所以瀏覽器訪問地址 http://192.168.2.11:31081/test 來測試接口,顯示:

test的值爲:123456

可以看到能通過 Apollo 獲取參數值,此文章到此結束。

碼猿技術專欄 前螞蟻 P8,純粹的技術人,掘金優秀作者,以專欄的形式分享技術,只寫外面看不到的乾貨,你想要的都在這裏……

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/JbWu0ouLT-UX5RrXdlFNEw