( 十四 )、 SpringBoot 集成 Apollo 配置中心

( 十四 )、 SpringBoot 集成 Apollo 配置中心

 

 

 

参考地址:

Apollo 文档知识

Apollo 文档2

Apollo Github 地址

快速开始 : https://github.com/ctripcorp/apollo/wiki/Quick-Start

一、简介

      Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。服务端基于Spring Boot和Spring Cloud开发,打包后可以直接运行,不需要额外安装Tomcat等应用容器。Java客户端不依赖任何框架,能够运行于所有Java运行时环境,同时对Spring/Spring Boot环境也有较好的支持。

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

  • application (应用)
  • environment (环境)
  • cluster (集群)
  • namespace (命名空间)

(1)、application

  • Apollo 客户端在运行时需要知道当前应用是谁,从而可以根据不同的应用来获取对应应用的配置。
  • 每个应用都需要有唯一的身份标识,可以在代码中配置 app.id 参数来标识当前应用,Apollo 会根据此指来辨别当前应用。

(2)、environment

在实际开发中,我们的应用经常要部署在不同的环境中,一般情况下分为开发、测试、生产等等不同环境,不同环境中的配置也是不同的,在 Apollo 中默认提供了四种环境:

  • FAT(Feature Acceptance Test):功能测试环境
  • UAT(User Acceptance Test):集成测试环境
  • DEV(Develop):开发环境
  • PRO(Produce):生产环境

在程序中如果想指定使用哪个环境,可以配置变量 env 的值为对应环境名称即可。

(3)、cluster

  • 一个应用下不同实例的分组,比如典型的可以按照数据中心分,把上海机房的应用实例分为一个集群,把北京机房的应用实例分为另一个集群。
  • 对不同的集群,同一个配置可以有不一样的值,比如说上面所指的两个北京、上海两个机房设置两个集群,两个集群中都有 mysql 配置参数,其中参数中配置的地址是不一样的。

(4)、namespace

一个应用中不同配置的分组,可以简单地把 namespace 类比为不同的配置文件,不同类型的配置存放在不同的文件中,如数据库配置文件,RPC 配置文件,应用自身的配置文件等。

熟悉 SpringBoot 的都知道,SpringBoot 项目都有一个默认配置文件 application.yml,如果还想用多个配置,可以创建多个配置文件来存放不同的配置信息,通过指定 spring.profiles.active 参数指定应用不同的配置文件。这里的 namespace 概念与其类似,将不同的配置放到不同的配置 namespace 中。

Namespace 分为两种权限,分别为:

  • public(公共的): public权限的 Namespace,能被任何应用获取。
  • private(私有的): 只能被所属的应用获取到。一个应用尝试获取其它应用 private 的 Namespace,Apollo 会报 "404" 异常。

Namespace 分为三种类型,分别为:

  • 私有类型: 私有类型的 Namespace 具有 private 权限。例如 application Namespace 为私有类型。
  • 公共类型: 公共类型的 Namespace 具有 public 权限。公共类型的N amespace 相当于游离于应用之外的配置,且通过 Namespace 的名称去标识公共 Namespace,所以公共的 Namespace 的名称必须全局唯一。
  • 关联类型(继承类型): 关联类型又可称为继承类型,关联类型具有 private 权限。关联类型的 Namespace 继承于公共类型的 Namespace,将里面的配置全部继承,并且可以用于覆盖公共 Namespace 的某些配置。
2、本地缓存

Apollo客户端会把从服务端获取到的配置在本地文件系统缓存一份,用于在遇到服务不可用,或网络不通的时候,依然能从本地恢复配置,不影响应用正常运行。

本地缓存路径默认位于以下路径,所以请确保/opt/data或C:\opt\data\目录存在,且应用有读写权限。

  • Mac/Linux: /opt/data/{appId}/config-cache
  • Windows: C:\opt\data{appId}\config-cache
本地配置文件会以下面的文件名格式放置于本地缓存路径下:
{appId}+{cluster}+{namespace}.properties

 

3、客户端设计

上图简要描述了Apollo客户端的实现原理

  • 客户端和服务端保持了一个长连接,从而能第一时间获得配置更新的推送。
  • 客户端还会定时从 Apollo 配置中心服务端拉取应用的最新配置。客户端从 Apollo 配置中心服务端获取到应用的最新配置后,会保存在内存中。
    • 这是一个 fallback 机制,为了防止推送机制失效导致配置不更新
    • 客户端定时拉取会上报本地版本,所以一般情况下,对于定时拉取的操作,服务端都会返回 304 - Not Modified
    • 定时频率默认为每 5 分钟拉取一次,客户端也可以通过在运行时指定 apollo.refreshInterval 来覆盖,单位为分钟。
  • 客户端会把从服务端获取到的配置在本地文件系统缓存一份 在遇到服务不可用,或网络不通的时候,依然能从本地恢复配置。
  • 应用程序从 Apollo 客户端获取最新的配置、订阅配置更新通知。
  • 用户名:apollo
  • 密   码:admin

 

在登录后创建项目时,选择部门默认只能选择 Apollo 自带的 测试部门1与测试部门2两个选项。

 

 开始这真让人迷糊,原来 Apoloo 没有修改或新增部门信息的管理节目,只能通过修改数据库,来新增或者修改数据,这里打开 Portal 对月的数据库中的表 ApolloPortalDB 修改 keyorganizationsvalue 的 json 数据,改成自己对于的部门信息。

修改完数据库部门信息后,重新登录 Apollo Portal,然后创建项目,这时候选择部门可以看到已经变成我们自己修改后的部门信息了,选择我们自定义部门,然后设置应用 ID 为 apollo-test,应用名为 apollo-demo

 

 创建完成后进入配置管理界面

创建一个配置参数,方便后续 Apollo 客户端项目引入该参数,进行动态配置测试。

 

 设置 key 为 test value 为 123456 然后设置一个备注,保存。

 

 创建完成后可以看到配置管理节目新增了一条配置。接下来我们将此配置通过发布按钮,进行发布。

 

1、引入maven 依赖

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

在 application.yml 配置文件中添加下面参数,这里简单介绍下 Apollo 参数作用:

  • apollo.meta: Apollo 配置中心地址。
  • apollo.cluster: 指定使用某个集群下的配置。
  • apollo.bootstrap.enabled: 是否开启 Apollo。
  • apollo.bootstrap.namespaces : 指定使用哪个 Namespace 的配置,默认 application。
  • apollo.cacheDir=/opt/data/some-cache-dir: 为了防止配置中心无法连接等问题,Apollo 会自动将配置本地缓存一份。
  • apollo.autoUpdateInjectedSpringProperties: Spring应用通常会使用 Placeholder 来注入配置,如${someKey:someDefaultValue},冒号前面的是 key,冒号后面的是默认值。如果想关闭 placeholder 在运行时自动更新功能,可以设置为 false。
  • apollo.bootstrap.eagerLoad.enabled : 将 Apollo 加载提到初始化日志系统之前,如果设置为 false,那么将打印出 Apollo 的日志信息,但是由于打印 Apollo 日志信息需要日志先启动,启动后无法对日志配置进行修改,所以 Apollo 不能管理应用的日志配置,如果设置为 true,那么 Apollo 可以管理日志的配置,但是不能打印出 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 加载提到初始化日志系统之前

写一个 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;
    }
}

1、启动上面的测试用例,然后输入地址 http://localhost:8080/test 查看:

修改 Apollo 配置中心参数 test 值为 666666 ,然后再次发布。

发布完成后再次输入地址 http://localhost:8080/test 查看:

 

 回滚完成后状态将变为未发布状态,则时候输入地址 http://localhost:8080/test 查看:

这里我们将 JVM 参数中 Apollo 配置中心地址故意改错:

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

然后输入地址 http://localhost:8080/test 可以看到值为:

可以看到显示的值并不是我们定义的默认值,而还是 Apollo 配置中心配置的 test 参数的值。考虑到由于 Apollo 会在本地将配置缓存一份,出现上面原因,估计是缓存生效。当客户端不能连接到 Apollo 配置中心时候,默认使用本地缓存文件中的配置。

上面我们配置了本地缓存配置文件存放地址为 "/opt/data/" ,接下来进入缓存目录,找到对应的缓存配置文件,删除缓存配置文件后,重启应用,再次输入地址查看:

 

 删除缓存配置文件后,可以看到输出的值为自己定义的默认值。

这里我们进入 Apollo 配置中心,删除之前创建的 test 参数,然后发布。

 

 然后再次打开地址 http://localhost:8080/test 查看:

可以看到显示的是应用程序中设置的默认值。

 

在 Apollo 中,配置可以根据不同的环境划分为 Dev(开发)、Prod(生产) 等环境,又能根据区域划分为不同的 Cluster(集群),还能根据配置参数作用功能的不同划分为不同的 Namespace(命名空间),这里探究下,如何使用上述能力。

(1)、Apollo 配置中心 PRO 环境添加参数

打开 Apollo 配置中心,环境列表点击 PRO 环境,然后新增一条配置,和之前例子中参数保持一致,都为 test 参数,创建完成后发布。

 

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

apollo.meta 参数改成 RPO 的配置中心地址

......

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

(3)、启动示例项目观察结果

启动示例项目,然后接着输入地址 http://localhost:8080/test 查看信息:

 

 可以看到已经改成生成环境配置,所以在实际项目中,如果要更换环境,需要修改 JVM 参数 env(如果 Apollo 部署在 Kubernetes 环境中,还需要修改 apollo.configService 参数),和修改 application.yml 配置文件的参数 apollo.meta 值。

 

(1)、创建两个集群

例如在开发过程中,经常要将应用部署到不同的机房,这里分别创建 beijingshanghai 两个集群。

 

 

 

 

 

 (2)、两个集群都配置同样的参数不同的值

在两个集群 beijingshanghai 中,都统一配置参数 test,并且设置不同的值。

 

 (3)、示例项目 application.yml 修改集群配置参数,并启动项目观察结果

指定集群为 beijing:

......

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

......

启动示例项目,然后接着输入地址 http://localhost:8080/test 查看信息:

 

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

指定集群为 shanghai:

......

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

......

启动示例项目,然后接着输入地址 http://localhost:8080/test 查看信息:

 

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

(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 查看信息:

 

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

指定命名空间为 dev-2:

......

apollo:
  bootstrap:
    namespaces: dev-2                   #设置 dev-1 命名空间

......

启动示例项目,然后接着输入地址 http://localhost:8080/test 查看信息:

 

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

 

四、使用 @ConfigurationProperties 注解 无法动态刷新, 需要添加如下配置

import com.ctrip.framework.apollo.model.ConfigChangeEvent;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * Apollo配置监听*/
@Component
@Slf4j
public class ApolloChangeListener implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * apollo动态更新配置监听
     *
     * @param changeEvent
     */
    @ApolloConfigChangeListener(value = "application.yml", interestedKeyPrefixes = "my.config.")
    public void onApolloChange(ConfigChangeEvent changeEvent) {
        log.info("Apollo配置发生改变");
        changeEvent.changedKeys().stream().forEach(key -> {
            log.info("Apollo change: {}={}", key, changeEvent.getChange(key).getNewValue());
        });
        //rebind configuration beans(配置类无需要添加@RefreshScope)
        this.applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys()));
    }
}

 

posted @ 2021-11-14 11:44  邓维-java  阅读(475)  评论(0编辑  收藏  举报