Spring Cloud Config - 统一配置中心

2020-09-24 11:17:16 浏览数 (1)

统一配置中心概述

如果微服务架构中没有使用统一配置中心时,所存在的问题:

  • 配置文件分散在各个项目里,不方便维护
  • 配置内容安全与权限,实际开发中,开发人员是不知道线上环境的配置的
  • 更新配置后,项目需要重启

在SpringCloud中我们使用config组件来作为统一配置中心:


Config Server

废话不多说,本小节我们来开发统一配置中心的server端,在IDEA中新建一个Spring Initializr项目,并选择相应的模块:

项目的pom.xml文件配置的依赖如下:

代码语言:javascript复制
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-config-monitor</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-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-config-server</artifactId>
   </dependency>
</dependencies>

因为config server是需要到git上拉取配置文件的,所以还需要在远程的git上新建一个存放配置文件的仓库,我这里使用的是码云:

创建好后,新建一个文件,然后把订单服务的配置文件内容粘贴进来:

注:我这里事先已经存在一个商品服务和订单服务

回到config项目中,编辑application.yml配置文件内容如下:

代码语言:javascript复制
spring:
  application:
    name: config
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/Zero-One/config-repo  # 远程git仓库的地址
          username: username  # 以及相应的账户名
          password: password  # 和密码
          basedir: E:Java_IDEAconfigbasedir  # 可以使用这个配置项来指定本地git仓库的路径
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
server:
  port: 8974

在启动类上,加上@EnableConfigServer注解,声明这是一个config-server。代码如下:

代码语言:javascript复制
package org.zero.springcloud.config;

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

@SpringBootApplication
@EnableEurekaClient
@EnableConfigServer
public class ConfigApplication {

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

启动项目,访问如下地址,可以看到能够访问到配置文件的内容:

如果访问.properties格式的,还会自动进行转换:

.json格式的也能够进行转换:

注:如果配置文件的内容格式有问题的话,访问会报500错误。我们可以利用这个特性,来检查我们的配置文件是否正确

在上图访问的地址中可以发现,访问的配置文件名后面还有一个-a,这其实是config的访问规则。后面必须要跟个-xxx,所以在创建文件的时候,最好是按这种命名规则来创建。配置文件的访问规则如下:

代码语言:javascript复制
/{name}-{profiles}.yml
/{label}/{name}-{profiles}.yml

name : 文件名,一般以服务名来命名
profiles : 一般作为环境标识
lable : 分支(branch),指定访问某分支下的配置文件

有一点值得注意的是,如果有两个前缀名相同文件,例如一个order.yml,一个order-dev.yml。那么在访问相同前缀的文件时,config-server会对这两个文件进行一个合并。例如order.yml有一段配置是order-dev.yml没有的,理应访问order-dev.yml的时候是没有那段配置的,但访问的结果却是它俩合并之后的内容,即order-dev.yml会拥有order.yml里所配置的内容。


除此之外还有一点就是,随着后期微服务数量的增加,配置文件的数量自然也会随着增加,而且实际的企业项目中都会在不同的部署环境使用不同的配置文件,例如开发环境(dev)、测试环境(test)、生产环境(product)等。所以一个服务至少会有三个以上的配置文件,如果我们将这些配置文件直接放在git仓库的根目录下的话,就会显得很杂乱,不便于查看、修改。如下示例:

这时我们很自然的会想到将不同服务的配置文件放到以服务名命名的目录下,例如:

这样感觉就好多了,想找哪个服务的配置文件直接去相应的目录下找就可以了。但是当你开开心心的将配置文件整理到一个个的目录里并重启了config server后,就会发现这些配置文件全都加载不到了。这是因为config server默认情况下只会搜索git仓库根路径下的配置文件,所以我们还需要加上一个配置项:search-paths,该配置项用于指定config server搜索哪些路径下的配置文件,需要注意的是这个路径是相对于git仓库的,并非是项目的路径。如下示例:

代码语言:javascript复制
spring:
  application:
    name: config
  cloud:
    config:
      server:
        git:
          ...
          search-paths: /**  # 指定搜索根路径下的所有目录,若有多个路径使用逗号隔开

Config Client

在上一小节中,我们介绍了config-server的使用以及配置文件的访问规则,本小节将介绍config-client端的使用,我们以订单服务为例。

在订单服务工程的pom.xml文件中,增加如下依赖配置:

代码语言:javascript复制
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-client</artifactId>
</dependency>

注:商品服务工程中也增加这个依赖,这样两个服务都可以从config-server中读取配置了

然后将application.yml重命名为bootstrap.yml,并修改内容如下:

代码语言:javascript复制
spring:
  application:
    name: order
  cloud:
    config:
      discovery:
        enabled: true
        service-id: CONFIG  # 注册中心的服务名
      profile: dev  # 指定配置文件的环境

注:之所以要用bootstrap.yml,是因为启动SpringBoot项目时,会优先读取bootstrap.yml里的配置,然后才会读取application.yml。如果不通过bootstrap.yml里的配置,先从配置中心拉下相应的配置文件,就会报错

重启项目,使用创建订单接口,测试一下是否正常:

统一配置中心和服务注册中心一样,都是需要高可用的,不然配置文件都没有的话,项目自然没法跑起来了。所以我们来看看如何使config-server能够高可用。

config-server也属于是一个微服务,所以让其高可用很简单,只需要启动多个服务实例即可。首先我们来复制几个config-server的实例,跑在不同的端口上:

启动后,到eureka上可以看到也都注册成功了,这样我们就有了三个config-server实例:

其他服务通过负载均衡策略,就能够调用这几个config-server实例,轻松实现高可用

还有一个需要注意的点是服务注册中心地址端口的问题,我们都知道eureka-server的默认端口是8761,如果我们现在将eureka-server的端口改成8762,那么订单服务就会启动不了。因为在bootstrap.yml配置文件中,并没有配置eureka-server的地址。

项目启动的时候会优先读取bootstrap.yml,按照配置的内容去配置中心拉取配置文件,但是在此之前订单服务需要先去注册中心上找配置中心的调用地址,如果eureka-server端口更改了的话,就会访问不到配置中心,自然也就无法调用配置中心拉取配置文件了,现在我们之所以可以访问是因为SpringBoot默认访问的是本地的8761端口。

所以我们需要修改远程git仓库上的配置文件内容如下:

然后在bootstrap.yml中增加eureka-server的配置才对:

代码语言:javascript复制
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

这也是一个小细节,如果没有注意的话,容易掉进这个坑。


Spring Cloud Bus

在上两个小节中,我们学习了统一配置中心的server端以及client端的使用,也成功拉取了相应的配置文件。但是这样仍然不够,因为还不能做到自动刷新配置文件,例如我在git上更改了配置文件,还需要重启服务才能够读取到最新的配置。所以本小节将介绍一下如何使用Spring Cloud Bus实现自动刷新配置,Bus在这里是总线的意思。

示意图:

Spring Cloud Bus会向外提供一个http接口,即图中的/actuator/bus-refresh。我们将这个接口配置到远程的git上,当git上的文件内容发生变动时,就会自动调用/bus-refresh接口。Bus就会通知config-server,config-server会发布更新消息到消息队列中,其他服务订阅到该消息就会信息刷新,从而实现整个微服务进行自动刷新。


RabbitMQ的安装

由于实现配置自动刷新,需要用到消息中间件,所以还得安装,我这里使用RabbitMQ。并且是在CentOS上使用docker进行安装,docker的版本如下:

代码语言:javascript复制
[root@01server ~]# docker info |grep "Server Version"
Server Version: 18.03.1-ce
[root@01server ~]# 

安装并启动rabbitmq:

代码语言:javascript复制
[root@01server ~]# docker run -d --hostname my-rabbit -p 5672:5672 -p 15672:15672 rabbitmq:3.7.3-management
...
[root@01server ~]#

进入容器里,添加超级管理员用户:

代码语言:javascript复制
[root@01server ~]# docker exec -it 1ca60f11d6d9 bash  # 进入容器
root@my-rabbit:/# rabbitmqctl add_user admin password  # 添加用户,用户名为admin,密码为password
Adding user "admin" ...
root@my-rabbit:/# rabbitmqctl set_user_tags admin administrator  # 设置用户权限为超级管理员
Setting tags for user "admin" to [administrator] ...
root@my-rabbit:/# rabbitmqctl  set_permissions -p /${user_name}  admin '.*' '.*' '.*' # 设置远程登录权限
root@my-rabbit:/# 

如果你的系统防火墙没有关闭的话,还需要开放相应的端口:

代码语言:javascript复制
[root@01server ~]# firewall-cmd --zone=public --add-port=15672/tcp --permanent
success
[root@01server ~]# firewall-cmd --zone=public --add-port=5672/tcp --permanent
success
[root@01server ~]# firewall-cmd --reload
success
[root@01server ~]# 

使用浏览器访问${ip}:15672 ,进入登录界面:

登录之后才能进入到管理页面:


实现刷新配置

安装好RabbitMQ后,我们就可以着手实现配置的刷新了。首先我们需要在config项目中,增加Spring Cloud Bus依赖:

代码语言:javascript复制
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

注:商品服务以及订单服务也需要加入这个依赖

然后在配置文件中,配置rabbitmq的地址以及用户密码,修改config服务的配置如下:

代码语言:javascript复制
spring:
  application:
    name: config
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/Zero-One/config-repo
          username: username
          password: password
          basedir: E:Java_IDEAconfigbasedir
  # 配置rabbitmq的地址以及用户密码
  rabbitmq:  
    host: 192.168.190.129
    port: 5672
    username: admin
    password: admin
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

# 允许/actuator/bus-refresh接口被外部调用
management:  
  endpoints:
    web:
      exposure:
        include: "*"

修改商品服务的配置如下:

代码语言:javascript复制
spring:
  application:
    name: product
  cloud:
    config:
      discovery:
        enabled: true
        service-id: CONFIG
      profile: dev
  rabbitmq:
    host: 192.168.190.129
    port: 5672
    username: admin
    password: admin
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

修改订单服务的配置如下:

代码语言:javascript复制
spring:
  application:
    name: order
  cloud:
    config:
      discovery:
        enabled: true
        service-id: CONFIG
      profile: dev
  rabbitmq:
    host: 192.168.190.129
    port: 5672
    username: admin
    password: admin
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

配置好后,将以上项目都重启,然后到RabbitMQ上,可以看到注册上来的队列:

确认都能够正常注册到rabbitmq后,我们到码云上,规范配置文件的名称。修改之前的order.yml为order-dev.yml,并且增加商品服务的配置文件,如下:

并在order-dev.yml文件里,增加一段env配置:

完成配置文件的修改后,再到订单服务项目里,增加一个 EnvController 类,用来测试配置刷新是否正常。代码如下:

代码语言:javascript复制
package org.zero.springcloud.order.server.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @program: sell_order
 * @description:
 * @author: 01
 * @create: 2018-08-20 23:17
 **/
@RestController
@RequestMapping("/env")
@RefreshScope  // 这个注解声明了刷新配置的范围,如果使用config配置类的话,就声明到配置类上即可
public class EnvController {

    @Value("${env}")
    private String env;

    @GetMapping("/print")
    public String print(){
        return env;
    }
}

重启订单服务项目,访问/env/print接口,返回的结果如下:

这时,我们再到码云上修改order-dev.yml文件里的env配置为beat,如下:

然后访问bus用于刷新配置的接口:

稍等一会,控制台应该会打印刷新日志,接着再访问之前的测试接口,返回beta则说明刷新是有效的。因为这个过程中,我们并没有重启订单服务或配置中心服务:


集成WebHooks实现动态更新

到了本小节,就代表我们已经成功集成了RabbitMQ以及Spring Cloud Bus进行配置文件的动态刷新,但是我们目前依旧需要手动去访问Bus用于刷新配置的接口,才能完成配置文件的动态刷新。我们希望的是,当git仓库的文件更新时就能够实现动态刷新配置文件。要实现这个功能就需要Git仓库能够在配置文件更新后,自动调用Bus用于刷新配置的接口。那么要怎么实现这个功能呢?这就需要用到WebHooks了,好在码云和GitHub都支持WebHooks,我们只需要配置一下接口地址即可。这也是我们本小节需要演示的。

注:SpringCloud需要2.0.0以上的版本才开始支持码云的WebHooks,低版本对码云的WebHooks不兼容

首先打开仓库的管理界面,选择WebHooks,并点击右上角的添加:

然后输入相应的配置信息,注意这里不是配置/actuator/bus-refresh接口了 ,而是配置 spring cloud config 里特定给WebHooks调用的/monitor接口。至于域名,我这里使用了内网穿透的地址:

添加完成后,点击右上角的测试,返回结果如下,则代表测试通过:

现在我们用之前的order-dev.yml配置文件进行一个测试,把env的值改为test,如下:

然后在不重启任何项目的情况下,访问之前打印env配置的接口。返回结果如下代表自定刷新成功了:

0 人点赞