springCloud 之 springconfig使用

前言

本篇主要介绍的是SpringCloud中的分布式配置中心(SpringCloud Config)的相关使用教程。

SpringCloud Config介绍

Spring Cloud Config项目是一个解决分布式系统的配置管理方案。它包含了Client和Server两个部分,server提供配置文件的存储、以接口的形式将配置文件的内容提供出去,client通过接口获取数据、并依据此数据初始化自己的应用。

Spring Cloud Config 是 Spring Cloud 家族中最早的配置中心,虽然后来又发布了 Consul 可以代替配置中心功能,但是 Config 依然适用于 Spring Cloud 项目,通过简单的配置即可实现功能。

配置文件是我们再熟悉不过的了,尤其是 Spring Boot 项目,除了引入相应的 maven 包之外,剩下的工作就是完善配置文件了,例如 mysql、redis 、security 相关的配置。除了项目运行的基础配置之外,还有一些配置是与我们业务有关系的,比如说七牛存储、短信相关、邮件相关,或者一些业务上的开关。

对于一些简单的项目来说,我们一般都是直接把相关配置放在单独的配置文件中,以 properties 或者 yml 的格式出现,更省事儿的方式是直接放到 application.properties 或 application.yml 中。但是这样的方式有个明显的问题,那就是,当修改了配置之后,必须重启服务,否则配置无法生效。

目前有一些用的比较多的开源的配置中心,比如携程的 Apollo、蚂蚁金服的 disconf 等,对比 Spring Cloud Config,这些配置中心功能更加强大。有兴趣的可以拿来试一试。

接下来,我们开始在 Spring Boot 项目中集成 Spring Cloud Config,并以 github 作为配置存储。除了 git 外,还可以用数据库、svn、本地文件等作为存储。主要从以下三块来说一下 Config 的使用。

1.基础版的配置中心(不集成 Eureka);

2.结合 Eureka 版的配置中心;

3.实现配置的自动刷新;

实现最简单的配置中心

最简单的配置中心,就是启动一个服务作为服务方,之后各个需要获取配置的服务作为客户端来这个服务方获取配置。

先在 github 中建立配置文件

注意文件的名称不是乱起的,例如上面的 config-single-client-dev.yml 和 config-single-client-prod.yml 这两个是同一个项目的不同版本,项目名称为 config-single-client, 一个对应开发版,一个对应正式版。config-eureka-client-dev.yml 和 config-eureka-client-prod.yml 则是另外一个项目的,项目的名称就是 config-eureka-client 。

创建配置中心服务端

1、新建 Spring Boot 项目,引入 config-server 和 starter-web

这里我用的是2.3.2.RELEASE 的版本,可以根据自己的版本自由调整

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>

application.yml

server:
  port: 9000

bootstrap.yml

spring:
  application:
    name: config-single-server  # 应用名称
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/OneStepDaily/demo.git #配置文件所在仓库
          username: github //登录账号
          password: github //登录密码
          default-label: master #配置文件分支
          search-paths: config  #配置文件所在根目录
 

启动类

主要是加上@EnableConfigServer

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

@SpringBootApplication
@EnableConfigServer
public class ConfigSingleCenterApplication {

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

}

 

启动后就可以页面访问.

说明:

  1、如果在GitHub上建立的仓库是私有的,那么还要加上spring.cloud.config.server.git.username和spring.cloud.config.server.git.password 这两个配置

  2、springcloud config 的URL与配置文件的映射关系如下:

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

{application} 就是应用名称,对应到配置文件上来,就是配置文件的名称部分,例如我上面创建的配置文件。

{profile} 就是配置文件的版本,我们的项目有开发版本、测试环境版本、生产环境版本,对应到配置文件上来就是以 application-{profile}.yml 加以区分,例如application-dev.yml、application-sit.yml、application-prod.yml。

{label} 表示 git 分支,默认是 master 分支,如果项目是以分支做区分也是可以的,那就可以通过不同的 label 来控制访问不同的配置文件了。

这里我的访问地址为:

http://127.0.0.1:9000/config-single-server/dev

http://127.0.0.1:9000/master/config-single-server-dev.yml

以及其他几种可以自己尝试

创建配置中心客户端,使用配置 

配置中心服务端好了,配置数据准备好了,接下来,就要在我们的项目中使用它了。

1、引用相关的 maven 包。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- spring cloud config 客户端包 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!-- 自动刷新配置文件需要使用 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

2、初始化配置文件

spring:
  profiles:
    active: dev

#版本区分
---
spring:
  profiles: prod
  application:
    name: config-single-client
  cloud:
    config:
      uri: http://127.0.0.1:9000
      label: master
      profile: prod


---
spring:
  profiles: dev
  application:
    name: config-single-client
  cloud:
    config:
      uri: http://127.0.0.1:9000
      label: configBranch #可以根据需要调整分支
      profile: dev

配置了两个版本的配置,并通过 spring.profiles.active 设置当前使用的版本,例如本例中使用的 dev 版本。

application.yml

server:
  port: 9001

#其中 management 是关于 actuator 相关的,自动刷新配置的时候需要使用。#
management:
  endpoint:
    shutdown:
      enabled: false
  endpoints:
    web:
      exposure:
        include: "*"

#data 部分是当无法读取配置中心的配置时,使用此配置,以免项目无法启动。#
data:
  env: default-config-single-env
  user:
    username: defaultUserName
    password: defaultPassword

其中 management 是关于 actuator 相关的,接下来自动刷新配置的时候需要使用。

data 部分是当无法读取配置中心的配置时,使用此配置,以免项目无法启动。

3、要读取配置中心的内容,需要增加相关的配置类,Spring Cloud Config 读取配置中心内容的方式和读取本地配置文件中的配置是一模一样的。可以通过 @Value 或 @ConfigurationProperties 来获取。

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

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

要读取配置中心的内容,需要增加相关的配置类,Spring Cloud Config 读取配置中心内容的方式和读取本地配置文件中的配置是一模一样的。可以通过 @Value 或 @ConfigurationProperties 来获取。

GitAutoRefreshConfig


import org.springframework.boot.context.properties.ConfigurationProperties;
//import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "data")
public class GitAutoRefreshConfig {

    private String env;

    private UserInfo user;


    。。。省略setter getter 方法。。。

    public static class UserInfo {
        private String username;

        private String password;

       。。。省略setter getter 方法。。。

        @Override
        public String toString() {
            return "UserInfo{" +
                    "username='" + username + '\'' +
                    ", password='" + password + '\'' +
                    '}';
        }
    }

}

注意这里,我们使用了一个静态内部类,其实也可以定义在外部,这个跟配置文件中的层次结构有关,一层结构会调用一个getxxx,所以我们的username 调用我 x.getUser().setUsername(); 

Controller对外接口


import com.learn.springcloud.configsingleClient.config.GitAutoRefreshConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/config")
public class ConfigSingleClientController {

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

    @Value("${data.user.username}")
    private String username;


    @Value("${data.user.password}")
    private String password;

    @Autowired
    private GitAutoRefreshConfig gitAutoRefreshConfig;


    @GetMapping(value = "show")
    public Object show(){
        return String.format("env = %s,username = %s,password = %s",env,username,password);
    }

    @GetMapping(value = "autoShow")
    public Object autoShow(){
        return gitAutoRefreshConfig;
    }
}

启动后,接口访问

 两个都是没有问题的。

实现自动刷新

Spring Cloud Config 在项目启动时加载配置内容这一机制,导致了它存在一个缺陷,修改配置文件内容后,不会自动刷新。例如我们上面的项目,当服务已经启动的时候,去修改 github 上的配置文件内容,这时候,再次刷新页面,对不起,还是旧的配置内容,新内容不会主动刷新过来。
但是,总不能每次修改了配置后重启服务吧。如果是那样的话,还是不要用它了为好,直接用本地配置文件岂不是更快。

它提供了一个刷新机制,但是需要我们主动触发。那就是 @RefreshScope 注解并结合 actuator ,注意要引入 spring-boot-starter-actuator 包。

1、在 config client 端配置中增加 actuator 配置,上面大家可能就注意到了。

management:
  endpoint:
    shutdown:
      enabled: false
  endpoints:
    web:
      exposure:
        include: "*"

其实这里主要用到的是 refresh 这个接口

2、在需要读取配置的类上增加 @RefreshScope 注解,我们是 controller 中使用配置,所以加在 controller 中。

注意,以上都是在 client 端做的修改。

为了演示一些效果,我们这里再加入一个类


@Component
public class Config {
    @Value("${data.env}")
    private String env;

    @Value("${data.user.username}")
    private String username;

    @Value("${data.user.password}")
    private String password;
    
    ..省略setter getter方法...
}

@RestController
@RequestMapping("/config")
@RefreshScope
public class ConfigSingleClientController {

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

    @Value("${data.user.username}")
    private String username;

    @Value("${data.user.password}")
    private String password;

    @Autowired
    private GitAutoRefreshConfig gitAutoRefreshConfig;

    @Autowired
    private Config config;


    @GetMapping(value = "show")
    public Object show(){
        return String.format("env = %s,username = %s,password = %s",env,username,password);
    }

    @GetMapping(value = "showNoChange")
    public Object showNoChange(){
        return config;
    }

    @GetMapping(value = "autoShow")
    public Object autoShow(){
        return gitAutoRefreshConfig;
    }
}

之后,重启 client 端,

重启后,我们修改 github 上的配置文件内容,并提交更改,

再次刷新页面访问,这时候内容是没有变化的,这没没有问题。

接下来,我们发送 POST 请求到 http://localhost:3302/actuator/refresh 这个接口(GET请求时不支持的),用 postman 之类的工具即可。

 此接口就是用来触发加载新配置的,返回内容如下:

之后再次访问页面,开始使用的两种方式 都已经发生了内容变化 内容已经发生了变化

  

最后添加的方式没有发生变化

获取的还是旧数据,这与 @Value 注解的实现有关,所以,我们在项目中就不要使用这种方式加载配置了。 

在 github 中配置 Webhook

这就结束了吗,并没有,总不能每次改了配置后,就用 postman 访问一下 refresh 接口吧,还是不够方便呀。 github 提供了一种 webhook 的方式,当有代码变更的时候,会调用我们设置的地址,来实现我们想达到的目的。

使用 Spring Cloud Bus 来自动刷新多个端

Spring Cloud Bus 将分布式系统的节点与轻量级消息代理链接。这可以用于广播状态更改(例如配置更改)或其他管理指令。一个关键的想法是,Bus 就像一个扩展的 Spring Boot 应用程序的分布式执行器,但也可以用作应用程序之间的通信渠道。

—— Spring Cloud Bus 官方解释

如果只有一个 client 端的话,那我们用 webhook ,设置手动刷新都不算太费事,但是如果端比较多的话呢,一个一个去手动刷新未免有点复杂。这样的话,我们可以借助 Spring Cloud Bus 的广播功能,让 client 端都订阅配置更新事件,当配置更新时,触发其中一个端的更新事件,Spring Cloud Bus 就把此事件广播到其他订阅端,以此来达到批量更新。

1、Spring Cloud Bus 核心原理其实就是利用消息队列做广播,所以要先有个消息队列,目前官方支持 RabbitMQ 和 kafka。

这里用的是 RabbitMQ, 所以先要搭一套 RabbitMQ 环境。请自行准备环境,这里不再赘述。我是用 docker 直接创建的,然后安装了 rabbitmq-management 插件,这样就可以在浏览器访问 15672 查看 UI 管理界面了。

2、在 client 端增加相关的包,注意,只在 client 端引入就可以。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

3、在配置文件中增加 RabbitMQ 相关配置,默认的端口应该是 5672 ,因为我是用 docker 创建的,所以有所不同。

spring:
  rabbitmq:
    host: localhost
    port: 32775
    username: guest
    password: guest

4、启动两个或多个 client 端,准备来做个测试

在启动的时候分别加上 vm option:-Dserver.port=3302 和 -Dserver.port=3303 ,然后分别启动就可以了。

5、分别打开 http://localhost:3302/autoShow 和 http://localhost:3303/autoShow,查看内容,然后修改 github 上配置文件的内容并提交。再次访问这两个地址,数据没有变化。

6、访问其中一个的 actuator/bus-refresh 地址,注意还是要用 POST 方式访问。之后查看控制台输出,会看到这两个端都有一条这样的日志输出

o.s.cloud.bus.event.RefreshListener: Received remote refresh request. Keys refreshed

7、再次访问第 5 步的两个地址,会看到内容都已经更新为修改后的数据了。

综上所述,当我们修改配置后,使用 webhook ,或者手动触发的方式 POST 请求一个 client 端的 actuator/bus-refresh 接口,就可以更新给所有端了。

结合 Eureka 使用 Spring Cloud Config

以上讲了 Spring Cloud Config 最基础的用法,但是如果我们的系统中使用了 Eureka 作为服务注册发现中心,那么 Spring Cloud Config 也应该注册到 Eureka 之上,方便其他服务消费者使用,并且可以注册多个配置中心服务端,以实现高可用。

好的,接下来就来集成 Spring Cloud Config 到 Eureka 上。

启动 Eureka Server

Spring Cloud Eureka 实现服务注册发现,为了清楚,这里还是把配置列出来

1、pom 中引入相关包

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

2、设置配置文件内容

bootstrap.yml

spring:
  application:
    name: eureka-center
  security:
    user:
      name: test  # 用户名
      password: 123456   # 密码
  cloud:
    inetutils: ## 网卡设置
      ignoredInterfaces:  ## 忽略的网卡
        - docker0
        - veth.*
        - VM.*
      preferredNetworks:  ## 优先的网段
        - 192.168

application.yml

server:
  port: 9003
eureka:
  instance:
    hostname: eureka-center
    appname: 注册中心
  client:
    registerWithEureka: false # 单点的时候设置为 false 禁止注册自身
    fetchRegistry: false
    serviceUrl:
      defaultZone: http://test:123456@localhost:9003/eureka
  server:
    enableSelfPreservation: false
    evictionIntervalTimerInMs: 4000

3、Application 启动类 

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

4、启动服务,在浏览器访问 3000 端口,并输出用户名 test,密码 123456 即可进入 Eureka UI

服务端和前面的相比也就是多了注册到 Eureka 的配置,其他地方都是一样的。

1、在 pom 中引入相关的包

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- spring cloud config 服务端包 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-server</artifactId>
</dependency>

<!-- eureka client 端包 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

2、配置文件做配置

application.yml

server:
  port: 9004

eureka:
  client:
    serviceUrl:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://test:123456@localhost:9003/eureka/
  instance:
    preferIpAddress: true

spring:
  application:
    name: config-eureka-server
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/OneStepDaily/demo.git #配置文件所在仓库
          username: github //登录账号
          password: github //登录密码
          default-label: master #配置默认文件分支
          search-paths: config  #配置文件所在根目录

相比于不加 Eureka 的版本,这里仅仅是增加了 Eureka 的配置,将配置中心注册到 Eureka ,作为服务提供者对外提供服务。

3、启动类,在 @EnableConfigServer 的基础上增加了 @EnableEurekaClient,另外也可以用 @EnableDiscoveryClient 代替 @EnableEurekaClient

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

4、启动服务,之后访问 Eureka ,可以看到服务已注册成功

 

 

配置 Spring Cloud Config 客户端

客户端的配置相对来说变动大一点,加入了 Eureka 之后,就不用再直接和配置中心服务端打交道了,要通过 Eureka 来访问。另外,还是要注意客户端的 application 名称要和 github 中配置文件的名称一致。

1、pom 中引入相关的包

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</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>

2、配置文件

bootstrap.yml

eureka:
  client:
    serviceUrl:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://test:123456@localhost:9003/eureka/
  instance:
    preferIpAddress: true

spring:
  profiles:
    active: dev

---
spring:
  profiles: prod
  application:
    name: config-eureka-client
  cloud:
    config:
      label: master
      profile: prod
      discovery:
        enabled: true
        service-id: config-eureka-server


---
spring:
  profiles: dev
  application:
    name: config-eureka-client
  cloud:
    config:
      label: master  #指定仓库分支
      profile: dev   #指定版本 本例中建立了dev 和 prod两个版本
      discovery:
        enabled: true  # 开启
        service-id: config-eureka-server # 指定配置中心服务端的server-id

除了注册到 Eureka 的配置外,就是配置和配置中心服务端建立关系。

其中 service-id 也就是服务端的application name。 

application.yml

server:
  port: 9005
management:
  endpoint:
    shutdown:
      enabled: false
  endpoints:
    web:
      exposure:
        include: "*"

data:
  env: defualt
  user:
    username: defualt
    password: defualt

3、启动类,增加了 @EnableEurekaClient 注解,可以用 @EnableDiscoveryClient 代替

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

4、另外的配置实体类和 RESTController 和上面的一样,没有任何更改,直接参考即可。

5、启动 client 端,访问 http://localhost:3011/autoShow,即可看到配置文件内容。

这个例子只是介绍了和 Eureka 结合的最基础的情况,还可以注册到高可用的 Eureka 注册中心,另外,配置中心服务端还可以注册多个实例,同时保证注册中心的高可用。

注意事项

1. 在 git 上的配置文件的名字要和 config 的 client 端的 application name 对应;

2. 在结合 eureka 的场景中,关于 eureka 和 git config 相关的配置要放在 bootstrap.yml 中,否则会请求默认的 config server 配置,这是因为当你加了配置中心,服务就要先去配置中心获取配置,而这个时候,application.yml 配置文件还没有开始加载,而 bootstrap.yml 是最先加载的。

猜你喜欢

转载自blog.csdn.net/wangxuelei036/article/details/107696511