1.概述

微服务架构意味着将会产生越来越多的单体服务,每个业务模块都被拆成了一个微服务模块,每个微服务模块中,都有各自的配置文件,随着模块的增多,配置文件越来越多,因此,需要有一个集中式的、动态配置的管理来解决这个问题。于是Spring Cloud提供了Config类解决这个问题,它为微服务中的模块提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供一个中心化的外部配置。

Spring Cloud Config分为服务端和客户端两部分。

服务端称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口。

客户端通过指定配置中心来管理应用资源以及与业务相关的配置内容,在启动的时候,从配置中心读取加载配置信息,配置服务器默认采用git来存储配置信息,有助于对环境配置进行版本管理,可以通过git客户端工具方便管理和访问配置内容。

Config的功能:

  • 集中管理配置文件
  • 不同环境可以使用不同的配置,动态配置更新
  • 运行期间,动态调整配置,不再需要在每个服务部署的机器上修改配置文件,服务回向配置中心统一拉取配置信息
  • 当配置发生变化,服务不需要重启即可感知到配置的变化,并应用新的配置
  • 将配置信息以REST接口形式暴露

官方文档:https://cloud.spring.io/spring-cloud-config/reference/html/

2.Config服务端配置与测试

在自己账号GitHub上新建一个Spring-Cloud的仓库,把自己本地的代码push上去,此时本地仓库和远程仓库就建立联系了。

添加cloud-config-center-3344模块,修改pom.xml,添加spring-cloud-config-server坐标。

<?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">
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.atguigu.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>cloud-config-center-3344</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

添加application.yml配置文件。

server:
  port: 3344
spring:
  application:
    name: cloud-config-center
  cloud:
    config:
      server:
        git:
          uri: https://github.com/WangShaoYang/Spring-Cloud.git # Github上的仓库地址
          search-paths: # 搜索目录,即配置文件的目录
            - cloud-config-center-3344/
      label: master # 读取分支
eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/

添加主启动类,使用注解@EnableConfigServer激活配置中心。

package com.atguigu.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;

@SpringBootApplication
@EnableConfigServer
public class ConfigCenterMain3344 {
    public static void main(String[] args) {
        SpringApplication.run(ConfigCenterMain3344.class, args);
    }
}

先说一下url地址构造规则,通过官方文档的Quick Start可知,访问路径有如下5种形式:

/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties

再来看application、profile、label的含义,在这里可以看到{application}对应的是spring.application.name的值,{profile}对应的是spring.profiles.active的值(如果有多个,用逗号分隔),{label}对应的是spring.cloud.config.label的值。

在cloud-config-center-3344模块下(和pom.xml同级别,不要和application.yml放在一起,后面会报错,我一开始就和application.yml放在同一层级了),添加3个配置文件,分别代表开发环境、生产环境、测试环境,并把它们push到GitHub上,因为后面我们访问的时候,是去GitHub上读取它们的。

config-dev.yml的内容如下,类似可以写出config-prod.yml和config-test.yml。

config:
  info: "This is config-dev.yml configuration from master branch in cloud-config-center-3344 module with version=1"

启动Eureka注册中心模块,启动配置中心模块,在控制台可以看到如下内容,来到这个文件夹,可以看到和GitHub地址相同的内容,表明配置中心在启动的时候,会把GitHub地址的内容拉下来一份,作为缓存,防止每次都从GitHub上访问速度慢的问题。

2020-06-22 21:31:00.700  INFO 14016 --- [)-192.168.122.1] o.s.c.c.s.e.NativeEnvironmentRepository  : Adding property source: file:/C:/Users/WANGSH~1/AppData/Local/Temp/config-repo-6815306617511593504/cloud-config-center-3344/src/main/resources/application.yml

这时候,我们通过浏览器访问http://localhost:3344/master/config-dev.yml,发现页面上显示出了config-dev.yml的内容。在GitHub上,将config-dev.yml的内容做一下简单的改动并提交,无需重启配置中心模块,再次通过浏览器访问http://localhost:3344/master/config-dev.yml,浏览器上展示的内容是我们修改后的,在控制台可以看到类似的info输出,回到本地缓存文件夹,对比config-dev.yml的修改日期和其他配置文件的修改日期,只有config-dev.yml的日期时间是最新的,其他的配置文件还是配置中心启动时候的日期时间。

所以,当我们给配置中心发送一个获取配置文件请求的时候时,配置中心先去看下GitHub地址有没有最新的提交记录,如果有,先pull下来,覆盖本地缓存,再响应请求,如果没有,直接把本地缓存的内容作为响应。

3.Config客户端配置与测试

新建cloud-config-client-3355模块,修改pom.xml,添加spring-cloud-starter-config坐标。

<?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">
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.atguigu.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>cloud-config-client-3355</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

在创建application.yml配置文件之前,先说一个知识点。

Spring Boot默认是支持*.properties和*.yml配置文件的,使用Spring Boot构建Spring Cloud应用的时候,Spring Boot有两种上下文环境,一个是Bootstrap,一个是Application,当application.yml和bootstrap.yml在同一目录下的时候,bootstrap.yml先加载,application.yml后加载,Bootstrap Context是Application Context的父级上下文。初始化的时候,Bootstrap Context负责从外部配置文件中加载配置文件,bootstrap.yml是系统级别的,Application Context负责应用内部加载配置文件,application.yml是用户级别的。因为bootstrap.yml的级别高于application.yml,所以,bootstrap.yml的配置不会被application.yml所覆盖。对于一个Spring Boot模块,此时,它的配置文件来源可能有两个,一个是bootstrap.yml,一个是application.yml(当然,也可以没有application.yml,只走bootstrap.yml如果能满足需求也行)。

因为要读取配置中心的配置信息,所以这里使用bootstrap.yml,添加bootstrap.yml。

server:
  port: 3355
spring:
  application:
    name: config-client
  cloud:
    # Config客户端配置
    config:
      label: master # 分支名称
      name: config # 配置文件名称
      profile: dev # 读取后缀名称
      uri: http://localhost:3344 # 上诉3个综合就是master分支上config-dev.yml
eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/

添加主启动类。

package com.atguigu.springcloud;

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

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

添加业务类,用于验证客户端是否真的从配置中心拿到配置信息了。

package com.atguigu.springcloud.controller;

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

@RestController
public class ConfigClientController {
    // 因为config仓库以rest形式暴露,所以客户端可以通过config服务端访问到GitHub上对应的文件信息
    @Value("${config.info}")
    private String configInfo;

    @GetMapping("/configInfo")
    public String getConfigInfo() {
        return configInfo;
    }
}

启动cloud-config-client-3355模块,浏览器访问http://localhost:3355/configInfo,可以拿到cloud-config-center-3344模块下config-dev.yml的配置,这里我们读取config.info属性的值。

在GitHub上直接修改config-dev.yml的内容,直接访问配置中心http://localhost:3344/master/config-dev.yml改动立刻生效,访问http://localhost:3355/configInfo改动并没有更新,重启cloud-config-client-3355后,再次访问生效了,可是这并不是最好的方式,因为重启服务代价太大了。

4.Config客户端之动态刷新

为了避免每次都重启cloud-config-client-3355服务,现在要实现cloud-config-client-3355的动态刷新。修改cloud-config-client-3355的pom.xml,添加spring-boot-starter-actuator坐标。修改bootstrap.yml,暴露所有监控端点,默认情况下,访问http://localhost:3355/actuator只会暴露部分端点,使用"*"让actuator把所有端点都暴露出来。

# 暴露所有监控端点
management:
  endpoints:
    web:
      exposure:
        include: "*"

在业务类(ConfigClientController)上添加@RefreshScope注解。重启cloud-config-client-3355模块,在GitHub上修改config-dev.yml的内容。访问http://localhost:3344/master/config-dev.yml可以看到配置信息随之修改,访问http://localhost:3355/configInfo发现配置信息并没有改变,此时我们还需要一步操作:cmd中通过curl发送一个post形式的refresh请求给3355,再次访问http://localhost:3355/configInfo,即可拿到最新的配置信息了,避免了3355服务的重启。

C:\Users\WangShaoYang>curl -X POST "http://localhost:3355/actuator/refresh"
["config.client.version","spring.cloud.client.hostname","config.info"]

其实这种方式还是有缺陷的,如果有非常多的微服务客户端,每一个都去发送请求执行刷新,也是很麻烦的,下面我们引入Spring Cloud Bus消息总线,采用广播的形式,一旦配置中心的配置发生变更,监听配置中心的各个微服务客户端就能收到消息,于是,refresh操作就可以自动完成了。