专栏首页程序猿的大杂烩Spring Cloud Config - 统一配置中心

Spring Cloud Config - 统一配置中心

统一配置中心概述

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

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

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


Config Server

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

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

<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配置文件内容如下:

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

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

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,所以在创建文件的时候,最好是按这种命名规则来创建。配置文件的访问规则如下:

/{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仓库的,并非是项目的路径。如下示例:

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

Config Client

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

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

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

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

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

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的配置才对:

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的版本如下:

[root@01server ~]# docker info |grep "Server Version"
Server Version: 18.03.1-ce
[root@01server ~]# 

安装并启动rabbitmq:

[root@01server ~]# docker run -d --hostname my-rabbit -p 5672:5672 -p 15672:15672 rabbitmq:3.7.3-management
...
[root@01server ~]#

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

[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:/# 

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

[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依赖:

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

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

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

spring:
  application:
    name: config
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/Zero-One/config-repo
          username: username
          password: password
          basedir: E:\Java_IDEA\config\basedir
  # 配置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: "*"

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

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/

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

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 类,用来测试配置刷新是否正常。代码如下:

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 条评论
登录 后参与评论

相关文章

  • ZooKeeper的伪分布式集群搭建以及真分布式集群搭建

    和其他大多数集群结构一样,zookeeper集群也是主从结构。搭建集群时,机器数量最低也是三台,因为小于三台就无法进行选举。选举就是当集群中的master节点挂...

    端碗吹水
  • 管道符和作业控制,shell变量,环境变量配置

      我们知道PATH是系统内置变量,还有HOME、PWD、LOGNAME等变量,这些变量是在系统里的配置文件规定的,env命令可以获取到系统变量,系统的变量一般...

    端碗吹水
  • docker通过模板创建镜像以及容器、仓库和数据管理

    笔记内容:docker通过模板创建镜像以及容器、仓库和数据管理 笔记日期:2018-02-05

    端碗吹水
  • 那个短命的一键“脱衣”软件,我在GitHub搜到了相关技术…

    文章中蓝色字体为链接,部分外部链接无法从文章中直接跳转,请点击阅读原文以访问链接。

    AiTechYun
  • 跨站点请求伪造(CSRF)攻击

    跨站点请求伪造(CSRF),也称为XSRF,Sea Surf或会话骑马,是一种攻击媒介,它会诱使Web浏览器在用户登录的应用程序中执行不需要的操作。

    周俊辉
  • linux基础分享一

    1991年10月5日,芬兰电脑程序员托瓦兹(Linus Benedict Torvalds)基于UNIX系统开发的Linux内核首个公开版本发布。

    吾非同
  • Effective Modern C++翻译(3)-条款2:明白auto类型推导

    条款2 明白auto类型推导 如果你已经读完了条款1中有关模板类型推导的内容,那么你几乎已经知道了所有关于auto类型推导的事情,因为除了一个古怪的例外,aut...

    magicsoar
  • Java 内部类种类及使用解析

      Java 内部类种类及使用解析 内部类Inner Class   将相关的类组织在一起,从而降低了命名空间的混乱。   一个内部类可以定义在另一个类里,可以...

    mukekeheart
  • 如何使用java代码通过JDBC连接Hive(附github源码)

    前面我们讲过《如何使用java代码通过JDBC连接Impala(附Github源码)》,本篇文章主要讲述如何使用Java代码通过JDBC的方式连接Hive。

    Fayson
  • C#3.0新增功能09 LINQ 基础05 使用 LINQ 进行数据转换

    语言集成查询 (LINQ) 不只是检索数据。 它也是用于转换数据的强大工具。 通过使用 LINQ查询,可以使用源序列作为输入,并通过多种方式对其进行修改,以创建...

    张传宁老师

扫码关注云+社区

领取腾讯云代金券