# spring-cloud-config

  • 如果不在spring-cloud中,不使用配置中心的话,会面临什么问题?

    当我们编写大量的服务之后,需要在application.yml中,配置各种配置,但是对于一些服务,他们的配置中,有很多都是相同的,如果在加上集群的话,当哪一天我们需要修改这些服务中的配置项的时候,那么是不是需要一个一个的去修改这些配置?

    如果是这样的话,那真是一件大工程^_^,所以为了解决这个问题,spring就引入了ConfigServer这个配置中心的

Spring Cloud Config为微服务架构中的微服务提供集中化的外部配置支持(也就是github,gitee等),配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置。

从上图我们能够知道,spring-cloud-config分为服务端和客户端两部分

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

    配置中心,也就是我们将配置放在github,gitee上后,配置中心通过这些git链接,链接到仓库,从仓库中,读取我们需要的内容,可以是本地的git仓库或者是远程的git仓库

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

    简而言之就是从配置中心中,拿配置

# 能够干嘛

当我们为微服务配置了配置中心之后,那么我们想要修改这些服务相同的配置,都可以直接在github等上进行修改,然后配置中心就会自动获取到最新的配置信息,如果我们使用spring-cloud-busrabbitmq之后,还能够使得客户端的配置也做到动态加载的效果,也就是同步git上的配置

将配置信息以REST接口的形式暴露,我们可以使用postman,curl等进行刷新

修改github等中的配置信息之后,并不是立刻就生效,我们需要发送一个post请求,通知配置中心刷新这些配置信息

Spring Cloud Config默认使用Git来存储配置文件(也有其它方式,比如支持SVN和本地文件),但最推荐的还是Git,而且使用的是http/https访问的形式

但是推荐使用gitee来存储配置文件,访问快,还有就是我使用github的时候,无论使用https还是git,都无法链接到github,而且国内,访问github可能直接连不上,推荐的还是gitee

# 搭建

这里先搭建配置中心,这里搭建的是动态刷新的,有一个配置中心(3344端口),两个客户端(3355,3366),还有一个eureka的注册中心(7001)

# 配置push到gitee

将所需要的配置文件,push到gitee,可以看我的 (opens new window)

我们可以放置开发时依赖,生产依赖,测试依赖,这里无论我们放入多少,在配置中心中,也不需要写controller,但是我们可以直接通过localhost:3344/branch-name/config-file-name获取gitee上的内容,比如http://config-3344.com:3344/master/config-dev.yml

一共有三种方式,能够在浏览器中,读取到仓库中配置文件的内容

  1. ip:端口/{label}/{application}-{profile}.yml

    http://config-3344.com:3344/master/config-dev.yml

  2. /{application}-{profile}.yml

    http://config-3344.com:3344/config-dev.yml

  3. /{application}/{profile}[/{label}]

    http://config-3344.com:3344/config/dev/master

WARNING

上面的application,label,profile都是配置文件名中的每一部分,所以对仓库中的配置文件命名的时候,一定要按照官网的推荐来,也就是下面这种格式

application-profile.yml

  • application对应上图的config

  • profile对应dev,prod,test等

# Config客户端之动态刷新

加入现在有配置中心3344,和两个客户端3355,3366,那么当我们修改gitee仓库中的配置项时,因为3344直接链接到gitee,所以直接刷新,就可以得到最新的结果,但是客户端不一样,客户端是从配置中心中去拿配置信息,所以这里有两种,一种是一个一个的刷新,一种是动态刷新

# 一个一个的刷新

一个一个的刷新,就是在配置文件中,暴露所有端口后,在你需要刷新的controller类上,添加@RefreshScope注解,然后发送一个post请求到curl -X POST "http://localhost:3355/actuator/refresh"便可以刷新了,这样刷新之后,你客户端的配置就能够和gitee同步

使用actuator功能,都需要加入spring-boot-starter-actuator依赖

  • 暴露所有端口

    # 暴露监控端点
    management:
      endpoints:
        web:
          exposure:
            include: "*"
    
    1
    2
    3
    4
    5
    6
  • 注解

    @RefreshScope
    @RestController
    public class ConfigClientController {
        @Value("${config.info}")
        private String configInfo;
    
        @GetMapping("/configInfo")
        public String getConfigInfo() {
            return configInfo;
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    此注解不是在主启动类上加的,是在controller上

如果使用这种方式的话,会存在什么问题

使用这种方式,如果客户端只有一个还好,但是如果客户端存在多个,集群方式,那发疯了^_^,所以我们需要使用动态刷新

# 动态刷新

这里需要使用Spring Cloud Bus配合Spring Cloud Config实现动态刷新,动态刷新就是我们通过消息中间件,通过广播的方式,可以通知所有的客户端进行刷新,也可以单独的指定某一个刷新

# Spring Cloud Bus

Spring Cloud Bus能管理和传播分布式系统间的消息,就像一个分布式执行器,可用于广播状态更改、事件推送等,也可以当作微服务间的通信通道。

一共有两种动态刷新的方式,图下

在这之前,你应该是知道什么是总线

TIP

在微服务架构的系统中,通常会使用轻量级的消息代理来构建一个共用的消息主题,并让系统中所有微服务实例都连接上来。由于该主题中产生的消息会被所有实例监听和消费,所以称它为消息总线。在总线上的各个实例,都可以方便地广播一些需要让其他连接在该主题上的实例都知道的消息。

基本原理 ConfigClient实例都监听MQ中同一个topic(默认是springCloudBus)。当一个服务刷新数据的时候,它会把这个信息放入到Topic中,这样其它监听同一Topic的服务就能得到通知,然后去更新自身的配置。

# 通过客户端通知

上图中的AppA,AppB,AppC都是客户端,这种刷新方式,就是我们发送post请求(/bus/refresh)后,会利用消息总线触发其中一个客户端,从而通过广播的方式通知其他的客户端刷新

  • 这种方式好么?

    这种方式不好,因为在微服务中,每一个服务,他们的功能都是单一的,比如上面的Config Server配置中心,其功能就是链接到gitee,github,从中获取配置信息,AppA等客户端的功能,便是服务的提供者(比如),这些服务提供者,在controller中,就需要配置中心提供的配置信息

    但是如果我们使用此方式的话,那么AppA就充当了两个角色,Config Server(动态刷新,都是通过Config Server去通知的)和服务提供者身份,这样的设计是不好的

缺点:

  • 打破了微服务的职责单一性,因为微服务本身是业务模块,它本不应该承担配置刷新的职责。
  • 破坏了微服务各节点的对等性。
  • 有一定的局限性。例如,微服务在迁移时,它的网络地址常常会发生变化,此时如果想要做到自动刷新,那就会增加更多的修改

# 通过Config Server通知

这种方式就很nice了,它的思想就是:利用消息总线触发一个服务端ConfigServer的/bus/refresh端点,而刷新所有客户端的配置

下面就是搭建一个通过Config Server的过程

# 配置中心Config Server

记住,配置中心不需要写controller,只需要有一个主启动类就行

# 依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</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-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

因为需要刷新,所以一定要加入spring-boot-starter-actuator依赖

# 配置中心配置文件

server:
  port: 3344

spring:
  application:
    name:  cloud-config-center #注册进Eureka服务器的微服务名
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/qsyyk_aurora_admin/spring-cloud-config.git
          ####搜索目录
          search-paths:
            - spring-cloud-config
          force-pull: true
          username: 你的gitee用户名
          password: 密码
      ####读取分支
      label: master
      
  #rabbitmq相关配置 15672是Web管理界面的端口;5672是MQ访问的端口
  rabbitmq:
    host: 192.168.86.142
    port: 5672
    username: admin
    password: 123456
# rabbitmq相关配置,暴露bus刷新配置的端点
management:
  endpoints: # 暴露bus刷新配置的端点
    web:
      exposure:
        include: "bus-refresh"
#服务注册到eureka地址
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

这里可以去官网看看,如果使用github,还可以在配置里面,配置ssh,这样应该能够链接上github,但是还是推荐gitee,官网中,还有很多的配置项,比如使用本地配置等等

  • search-paths这里推荐使用gitee中,仓库的名字

需要配置rabbitmq,因为最终通过Config Server去通知,其实就是rabbitmq的交换机类型是topic,那么和此交换机绑定的队列(这里可以指客户端),就能够收到Config Server发送的消息,从而达到刷新的功能,记住,这里的交换机类型不是扇出类型,不应该所有的客户端都收到,各司其职

因为我们需要通过actuator去刷新,也就是发送post请求(bus/refresh),所以我们需要暴露一个端口,这里可以是所有端口(*),也可以指定特定的

management:
  endpoints: # 暴露bus刷新配置的端点
    web:
      exposure:
        include: "bus-refresh"
1
2
3
4
5

现在启动注册中心7001,还有3344配置中心,然后直接在浏览器中,输入测试一下(配置中心不需要写controller)

# 客户端

这里搭建两个,集群模式3355,3366

# 依赖

<dependencies>
    <!--添加消息总线RabbitMQ支持-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bus-amqp</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-config</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-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# 配置文件

server:
  port: 3355

spring:
  application:
    name: config-client
  cloud:
    #Config客户端配置
    config:
      label: master #分支名称
      name: config #配置文件名称
      profile: dev #读取后缀名称   上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yml
      uri: http://localhost:3344 #配置中心地址k
  #rabbitmq相关配置 15672是Web管理界面的端口;5672是MQ访问的端口
  rabbitmq:
  	# 测试发现,绑定rabbitmq的时候,host前不能有https,http
    host: 192.168.86.142
    port: 5672
    username: admin
    password: 123456
#服务注册到eureka地址
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka
# 暴露监控端点
management:
  endpoints:
    web:
      exposure:
        include: "*"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

这里的配置文件名就不在是application.yml了,而是bootstrap.yml

这里引用一篇CSDN的文章 (opens new window)还有 (opens new window)

bootstrap.yml是定义系统级别参数配置; 应用于:

  1. Spring Cloud Config 配置中心配置,加载外部配置中心的配置信息;
  2. 某些固定不需要覆盖的属性;
  3. 某些加密/解密场景;

application.yml是定义应用级别参数配置;应用于:SpringBoot项目配置。

他们的加载顺序是bootstrap.yml > application.yml

为什么一定要bootstrap.yml

通过查看其他的博客,我给出的结论是,bootstrap.yml是先application.yml文件加载的,但是在启动config server client的时候,我们需要将此客户端和配置中心进行绑定,从配置中心中获取到配置文件的信息,这些配置文件的信息,会放在bootstrap.yml

记得要暴露所有的端口,也可以只暴露我们需要使用配置信息的那些url

config:
	label: master #分支名称
	name: config #配置文件名称
	profile: dev #读取后缀名称   上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yml
	uri: http://localhost:3344 #配置中心地址k
1
2
3
4
5

客户端和服务端的绑定是通过url进行绑定的,这里的name就对应仓库文件中的application项,3366也是一样的操作

# controller

演示一个

@RefreshScope
@RestController
public class ConfigClientController {
    @Value("${config.info}")
    private String configInfo;

    @GetMapping("/configInfo")
    public String getConfigInfo() {
        return configInfo;
    }
}
1
2
3
4
5
6
7
8
9
10
11

如果还有其他的controller,但是他们没有使用到配置信息,那么就不用加上@RefreshScope注解

这里的${config.info},config.info配置,我们并没有在bootstrap.yml中进行配置,这是在仓库中的配置文件的内容

启动7001,3344,3355,3366,启动成功之后,我们修改仓库中的内容,然后现在3344是能够看到修改的,但是3355,3366两个客户端,需要我们发送post请求去触发

curl -X POST "http://localhost:3344/actuator/bus-refresh"

这里是发送到Config Server

发送请求的过程,可能会出现链接失败情况,多试几次就行

那么现在访问3355,3366,他们的配置信息已经更新了

# SpringCloud Bus动态刷新定点通知

指定具体某一个实例生效而不是全部

上面那种方式是刷新全部

公式:

http://localhost:配置中心的端口号/actuator/bus-refresh/{destination}

比如:curl -X POST "http://localhost:3344/actuator/bus-refresh/config-client:3355"