一、前言

随着业务的扩展,为了方便开发和维护项目,我们通常会将大项目拆分成多个小项目做成微服务,每个微服务都会有各自配置文件,管理和修改文件起来也会变得繁琐。而且,当我们需要修改正在运行的项目的配置时,通常需要重启项目后配置才能生效。

上述的问题将是本篇需要解决的问题。

二、介绍

# 2.1 简单介绍

Spring Cloud Config 用于为分布式系统中的基础设施和微服务应用提供集中化的外部配置支持,它分为服务端和客户端两部分。服务端(config server)也称为分布式配置中心,是一个独立的微服务应用,用来连接配置仓库并为客户端提供获取配置信息,加密/解密信息等访问接口。而客户端(config client)则是微服务架构中各微服务应用或基础设施,通过指定的配置中心来管理应用资源与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息。

# 2.2 运行原理

如上图,当 Config Client 首次启动时会向 Config Server 获取配置信息,Config Server 接收到请求再从远程私有仓库获取配置(连接不上项目会报错),并保存到本地仓库中。

当 Config Client 再次启动时会向 Config Server 获取配置信息,Config Server 还是会先从远程私有仓库拉去数据。如果网络问题或认证问题导致无法连接远程私有库,Config Server 才会从本地仓库获取配置信息返回给 Config Client。

三、Config 实战

    本次实战基于 Eureka 篇的项目进行扩展演练。不清楚的读者请先转移至 《Spring Cloud 入门 之 Eureka 篇(一)》 进行浏览。

我们使用配置中心来维护 order-server 的配置数据(application.yml)。

测试场景:由于配置中心服务本身也是一个微服务,因此我们需要将配置中心注册到 Eureka 上,当 order-server 启动时先向 Eureka 获取配置中心的访问地址,然后从配置中心获取相应的配置信息进行正常启动。

本篇实战用到的项目列表:

服务实例 端口 描述
eureka-server 9000 注册中心(Eureka 服务端)
config-server 10000 配置中心(Eureka 客户端、Config 服务端)
order-server 8100 订单服务(Eureka 客户端、Config 客户端)

# 3.1 上传配置

在 GitHub 上新建一个私有仓库,名为 spring-cloud-config。

我们将 order-server 项目的配置文件放到改仓库中,如下图:

新建 2 个 yml 文件,内容为:

server:
port: 8100
eureka:
instance:
instance-id: order-api-8100
prefer-ip-address: true # 访问路径可以显示 IP
env: dev

ORDER-dev.yml 和 ORDER-test.yml 不同之处在于 env 的值,其中一个是 dev ,另一个是 test。

# 3.2 config 服务端

新建一个 spring boot 项目,名为 config-server(任意名字)。

  1. 添加依赖:
​
<dependencies>
<!-- eureka 客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency><!-- config 服务端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
</dependencies>​

2. application.yml

server:
port: 10000spring:
application:
name: CONFIG
cloud:
config:
server:
git:
uri: https://github.com/moonlightL/spring-cloud-config.git
username: moonlightL
password: xxx
basedir: d:/data # 本地库目录
eureka:
instance:
instance-id: config-api
client:
service-url:
defaultZone: http://localhost:9000/eureka/ # 注册中心访问地址

3. 启动类添加 @EnableConfigServer:

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

启动成功后,我们打开浏览器访问 http://localhost:10000/order-dev.yml 和 http://localhost:10000/order-test.yml,结果如下图:

config-server 服务成功拉去远程私有仓库的配置数据。

其中,访问规则如下:

<IP:PORT>/{name}-{profiles}.yml
<IP:PORT>/{label}/{name}-{profiles}.yml
  • name:文件名,可当作服务名称
  • profiles: 环境,如:dev,test,pro
  • lable: 分支,指定访问某分支下的配置文件,默认拉去 master 分支。

# 3.3 config 客户端

在 order-server 项目中。

  1. 添加依赖:
<!-- config 客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>

2.删除 application.yml,并新建 bootstrap.yml,保存如下内容:

spring:
application:
name: ORDER
cloud:
config:
discovery:
enabled: true
service-id: CONFIG # config-server 在注册中心的名称
profile: dev # 指定配置文件的环境
eureka:
client:
service-url:
defaultZone: http://localhost:9000/eureka/ # 注册中心访问地址

配置中,通过 spring.cloud.config.discovery.service-id 确定配置中心,再通过 spring.application.name 的值拼接 spring.cloud.config.profile 的值,从而确定需要拉去从配置中心获取的配置文件。(如:ORDER-dev)

注意:必须保留 eureka 注册中心的配置,否则 order-server 无法连接注册中心,也就无法获取配置中心(config-server)的访问信息。

3.测试

新建一个测试类:

@RestController
@RequestMapping("/test")
public class TestController {@Value("${env}")
private String env; // 从配置中心获取@RequestMapping("/getConfigInfo")
public String getConfigInfo() {
return env;
}
}

打开浏览器访问 http://localhost:8100/test/getConfigInfo,结果如下图:

成功获取 config-server 从远程私有仓库拉去的数据,由于在 bootstrap.yml 中配置了 spring.cloud.config.profile=dev,因此拉取到的数据就是 ORDER-dev.yml 中的数据。

引申问题:

当我们修改远程私有仓库的配置文件时,Config Server 如何知道是否该重新获取远程仓库数据呢?

现在已知唯一的解决方式就是重启 Config Client 项目,在项目启动时会请求 Config Server 重新拉去远程私有仓库数据。但是,如果是在生产环境下随便重启项目必定会影响系统的正常运行,那有没有更好的方式解决上述的问题呢?请读者继续阅读下文。

四、整合 Bus

Spring Cloud Bus 是 Spring Cloud 家族中的一个子项目,用于实现微服务之间的通信。它整合 Java 的事件处理机制和消息中间件消息的发送和接受,主要由发送端、接收端和事件组成。针对不同的业务需求,可以设置不同的事件,发送端发送事件,接收端接受相应的事件,并进行相应的处理。

# 4.1 配置中心

在 config-server 项目中:

  1. 添加依赖:
<!-- bus -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

2.修改 application.yml,添加如下配置:

server:
port: 10000spring:
application:
name: CONFIG
cloud:
config:
server:
git:
uri: https://github.com/moonlightL/spring-cloud-config.git
username: moonlightL
password: shijiemori960
rabbitmq:
host: 192.168.2.13
port: 5672
username: light
password: lighteureka:
instance:
instance-id: config-api
client:
service-url:
defaultZone: http://localhost:9000/eureka/ # 注册中心访问地址management:
endpoints:
web:
exposure:
include: "*" # 暴露接口

添加了 rabbitmq 配置和 management 的配置。

# 4.2 订单服务

在 order-server 项目中:

  1. 添加依赖:
<!-- bus -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

2. 修改 bootstrap.yml:

spring:
application:
name: ORDER
cloud:
config:
discovery:
enabled: true
service-id: CONFIG # config-server 在注册中心的名称
profile: dev # 指定配置文件的环境
rabbitmq:
host: 192.168.2.13
port: 5672
username: light
password: light
eureka:
client:
service-url:
defaultZone: http://localhost:9000/eureka/ # 注册中心访问地址

添加 rabbitmq 配置。

3. 获取数据的类上添加 @RefreshScope 注解:

@RestController
@RequestMapping("/test")
@RefreshScope
public class TestController {@Value("${env}")
private String env; // 从配置中心获取@RequestMapping("/getConfigInfo")
public String getConfigInfo() {
return env;
}
}

整合 Bus 后的原理图如下:

当我们修改远程私有仓库配置信息后,需要向 Config Server 发起 actuator/bus-refresh 请求。然后, Config Server 会通知消息总线 Bus,之后 Bus 接到消息并通知给其它连接到总线的 Config Client。最后,Config Client 接收到通知请求 Config Server 端重新访问远程私有仓库拉去最新数据。

4.测试:

修改远程私有仓库配置文件,使用 Postman 发起 POST 请求 http://localhost:10000/actuator/bus-refresh,最终配置中心重新拉去数据,最后再访问 order-server http://localhost:8100/test/getConfigInfo 获取最新数据,运行结果如下图:

如上图,我们实现了在不重启项目的情况下,获取变更数据的功能。

引申问题:

每次更新私有仓库中的配置文件都需要手动请求 actuator/bus-refresh,还是不够自动化。

下边我们来解决该问题。

五、集成 WebHook

远程私有仓库的提供 WebHook 配置,我们将 actuator/bus-refresh 配置上去,当远程私有仓库中的配置信息发生变动时,就会自动调用该接口最终实现自动刷新目的。

# 5.1 配置 WebHook 地址

登录 GitHub,点击 GitHub 的 WebHook 菜单,右侧面板中 Payload URL 填写 <配置中心 url>/actuator/bus-refresh, Content-type 选择 applicaton/json,保存即可。

由于笔者是本地测试,没有外网域名,因此借助 https://natapp.cn 做外网映射(操作简单,详情看官网教程),以下是笔者的外网信息:

设置 WebHook 操作如下图:

# 5.2 测试

预期效果:当我们修改 GitHub 上私有仓库的配置数据后,我们再访问 http://localhost:8100/test/getConfigInfo 应该展示最新的数据。

但是结果失败了。

原因: 回到 config-server 控制台查看日志发现报错了:

Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token
at [Source: (PushbackInputStream); line: 1, column: 68] (through reference chain: java.util.LinkedHashMap["hook"])]

这是因为,GitHub 在调用 <配置中心 url>/actuator/bus-refresh 时,往请求体添加了 payload 数据,但它不是一个标准的 JSON 数据。因此,config-server 在接收 GitHub 发送的请求获取,从请求体数据做转换时就报错了。

解决方案:

在 config-server 项目中,新建一个过滤器,用于过滤 actuator/bus-refresh 请求,将其请求体置空:

@Component
public class WebHookFilter implements Filter {@Override
public void init(FilterConfig filterConfig) throws ServletException {}@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String url = new String(httpServletRequest.getRequestURI());// 只过滤 /actuator/bus-refresh 请求
if (!url.endsWith("/actuator/bus-refresh")) {
chain.doFilter(request, response);
return;
}// 使用 HttpServletRequest 包装原始请求达到修改 post 请求中 body 内容的目的
CustometRequestWrapper requestWrapper = new CustometRequestWrapper(httpServletRequest);chain.doFilter(requestWrapper, response);}@Override
public void destroy() {}private class CustometRequestWrapper extends HttpServletRequestWrapper {
public CustometRequestWrapper(HttpServletRequest request) {
super(request);
}@Override
public ServletInputStream getInputStream() throws IOException {
byte[] bytes = new byte[0];
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);return new ServletInputStream() {
@Override
public boolean isFinished() {
return byteArrayInputStream.read() == -1 ? true : false;
}@Override
public boolean isReady() {
return false;
}@Override
public void setReadListener(ReadListener readListener) {}@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
};
}
}
}

完成如上配置后,再次测试,结果如下:

搞定! 由于网络问题,拉去最新数据时有点慢,需要多刷新几次。。。

六、案例源码

config demo 源码

Spring Cloud 入门 之 Config 篇(六)相关推荐

  1. Spring Cloud 入门 之 Zuul 篇(五)

    一.前言 随着业务的扩展,微服务会不对增加,相应的其对外开放的 API 接口也势必增多,这不利于前端的调用以及不同场景下数据的返回,因此,我们通常都需要设计一个 API 网关作为一个统一的 API 入 ...

  2. Spring Cloud 入门 之 Hystrix 篇(四)

    一.前言 在微服务应用中,服务存在一定的依赖关系,如果某个目标服务调用慢或者有大量超时造成服务不可用,间接导致其他的依赖服务不可用,最严重的可能会阻塞整条依赖链,最终导致业务系统崩溃(又称雪崩效应). ...

  3. Spring Cloud 入门 之 Feign 篇(三)

    一.前言 在上一篇文章<Spring Cloud 入门 之 Ribbon 篇(二)> 中介绍了 Ribbon 使用负载均衡调用微服务,但存在一个问题:消费端每个请求方法中都需要拼接请求服务 ...

  4. Spring Cloud 入门 之 Ribbon 篇(二)

    一.前言 上一篇<Spring Cloud 入门 之 Eureka 篇(一)> 介绍了微服务的搭建,服务注册与发现.但在文章中留了一个小尾巴--如何正确使用 Eureka 进行服务发现并调 ...

  5. Spring Cloud 入门 之 Eureka 篇(一)

    一.前言 Spring Cloud 是一系列框架的有序集合.它利用 Spring Boot 的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册.配置中心.消息总线.负载均衡.断路器.数据 ...

  6. Spring Cloud入门-Config分布式配置中心(Hoxton版本)

    文章目录 Spring Cloud入门系列汇总 摘要 Spring Cloud Config 简介 在Git仓库中准备配置信息 配置仓库目录结构 master分支下的配置信息 dev分支下的配置信息 ...

  7. eureka集群只注册一个_Spring cloud系列教程第十篇- Spring cloud整合Eureka总结篇

    Spring cloud系列教程第十篇- Spring cloud整合Eureka总结篇 本文主要内容: 1:spring cloud整合Eureka总结 本文是由凯哥(凯哥Java:kagejava ...

  8. Spring Cloud入门-Admin服务监控中心(Hoxton版本)

    文章目录 Spring Cloud入门系列汇总 摘要 Spring Boot Admin 简介 创建admin-server模块 创建admin-client模块 监控信息演示 结合注册中心使用 修改 ...

  9. Spring Cloud入门-Sentinel实现服务限流、熔断与降级(Hoxton版本)

    文章目录 Spring Cloud入门系列汇总 摘要 Sentinel简介 安装Sentinel控制台 创建sentinel-service模块 限流功能 创建RateLimitController类 ...

最新文章

  1. 保证计算机网络的稳定运行,厦门大学校园网管理保证网络稳定运行
  2. WSGI和PASTE
  3. 对四象限法则的一点思考
  4. 它是光荣的象征, 得到它一定没有Bug!
  5. 基于PYNQ-Z2实现BNN硬件加速
  6. centos 安装低版本内核_Docker安装教程
  7. c语言定义不定长数组初始化_大学C语言期末考试练习题(带详解答案)(1)
  8. MATLAB计算卷积幂函数,数论小记(示例代码)
  9. 区块链技术视频网站EthCast.com上线
  10. MOSFET | 如何看懂MOSFET手册?①
  11. The RSpec Book笔记《二》Describing Features描述功能
  12. 深度学习实战7-电商产品评论的情感分析
  13. SX1278 Lora网关
  14. 算术左、右移位与逻辑左、右移位,右移一位和除二的区别、算术溢出
  15. Linux下QT安装
  16. 新装的mysql数据库默认密码是多少_数据库的设置及其初始密码
  17. 神经网络与深度学习学习笔记(一)——基本概念
  18. ubuntu破解密码方法
  19. 关于zigbee的一些术语
  20. 计算机学院 统计学博士点,重大喜讯:统计学科获批一级学科博士授权点

热门文章

  1. css里slidebottom,jquery - 从CSS“top”到“bottom”的jQuery动画 - 堆栈内存溢出
  2. android opengl es 粒子效果实例代码
  3. h5如何上传文件二进制流_HTML5新特性之文件和二进制数据的操作
  4. 计算机网络之网络概述:3、性能指标(速率、带宽、吞吐量、时延、RTT、利用率)
  5. (计算机组成原理)第三章存储系统-第四节:主存储器和CPU的连接
  6. (计算机组成原理)第三章存储系统-第三节2:ROM芯片
  7. [Python]网络爬虫(九):百度贴吧的网络爬虫(v0.4)源码及解析
  8. C/C++日志写入系统log(/var/log/syslog)
  9. Python3 获取当前路径,当前文件名,当前文件名路径、指定import的文件路径、程序路径
  10. win32diskimager报错:An error occured when attempting to XXX, Error 5: Access is Denied