使用 Spring Cloud Config 我们能实现服务配置的集中化管理, 在服务启动时从 Config Server 获取需要的配置属性. 但如果在服务运行过程中, 我们需要将某个配置属性进行修改, 比如将验证码的失效时间从五分钟调整为十分钟, 如何将这个更新在服务端不重启服务就能动态生效, 是本文讨论的内容.
Spring Cloud Bus
Spring Cloud Bus 可以理解为 Spring Cloud 体系架构中的消息总线, 通过一个轻量级的 Message Broker 来将分布式系统中的节点连接起来. 可用来实现广播状态更新(如配置更新), 或其它管理指令.
Spring Cloud Bus 就像是一个分布式的 Spring Boot Actuator, 目前提供了两种类型的消息队列中间件支持: RabbitMQ 与 Kafka(对应的 pom 依赖分别为 spring-cloud-starter-bus-amqp, spring-cloud-starter-bus-kafka).
Spring Cloud 在 spring-cloud-context 中添加了两个 actuator 管理接口(POST 请求): /actuator/env 与 /actuator/refresh, 前者可用于更新当前服务实例 Environment 对象中的配置属性, 后者可用于刷新当前服务实例的配置信息.
Spring Cloud Bus 也提供了两个对应的接口
/actuator/bus-env, 相对于 / actuator/env , 使用键值对更新每个实例的 Environment, 默认不暴露, 需配置 management.endpoints.web.exposure.include=bus-env 来开放接口访问
/actuator/bus-refresh
, 相对于 / actuator/refresh, 对每个实例, 清空 RefreshScope 缓存, 重新绑定 @ConfigurationProperties, 默认不暴露, 可通过配置
management.endpoints.Web.exposure.include=bus-refresh 来开放接口访问
综上,/actuator/env 与 /actuator/refresh 是针对单个服务实例修改或刷新其配置信息, 而 /actuator/bus-env 与 /actuator/bus-refresh 则是借助于 Spring Cloud Bus 的消息机制作用于分布式系统中的所有服务实例, 因此前面有 Spring Cloud Bus 就像是一个分布式的 Spring Boot Actuator 的说法.
使用 Spring Cloud Bus 来实现服务配置动态更新的结构图如下
更新配置仓库中的配置文件, push 到远程 Git 仓库
远程 Git 仓库通过 Webhook 调用配置服务器的通知更新接口
配置服务器发送配置更新消息到消息总线
其它服务节点监听到配置服务器发送的配置更新消息
其它服务节点向配置服务器发送拉取最新配置的请求
配置服务器向配置仓库拉取最新的配置返回给其它服务节点
案例演示
我们还是以前面的 springcloud-config, springcloud-eureka, springcloud-eureka-client 三个项目来完成本文的案例演示. 源码地址 https://github.com/ronwxy/springcloud-demos
使用 Actuator
在不引入 Spring Cloud Bus 的情况下, 我们可以通过 Spring Cloud 提供的 actuator 接口来实现单个实例的配置动态更新.
依次启动 springcloud-eureka, springcloud-config, springcloud-eureka-client 项目, 然后修改 springcloud-eureka-client 的启动端口, 将 8080 改为 8081, 再启动一个 springcloud-eureka-client 的服务实例.
springcloud-eureka-client 的测试接口代码如下
- @RestController
- @RefreshScope
- public class HelloController {
- @Autowired
- private Environment env;
- @Value("${app}")
- private String App;
- @RequestMapping("/hello")
- public String hello(){
- return "Hello, welcome to spring cloud 2. env:" + env.getProperty("app") + ", value:" + App;
- }
- }
此时依次请求两个实例的 hello 接口, 得到结果如下
我们通过 / actuator/env 接口来修改端口 8080 实例的属性 App 的值, 使用 postman 操作如图
此时再请求接口返回结果如下
可以看到 Environment 对象中 App 属性的值已更新, 但是 @Value 注解的属性值未变, 可见 /actuator/env 接口只是更新了 Environment 对象, 并不负责刷新其它方式引用的属性值. 此时请求另一个端口为 8081 的实例接口, 其属性值都未更新, 也可见 /actuator/env 只作用于当前实例本身.
如果要让 8080 实例的 @Value 属性也动态更新, 则可再调用 / actuator/refresh 接口, 如图
此时再请求测试接口, 得到结果如下(@Value 注解的属性也已经更新了)
使用 Spring Cloud Bus
前面我们使用 /actuator/env 与 /actuator/refresh 两个接口可以实现单个服务实例配置的动态更新, 但在微服务架构中, 服务实例可能达几十甚至几百个, 一个个调用来做动态更新就有点太不方便了. 这时就该 Spring Cloud Bus 登场了.
1. 添加依赖与配置
在 springcloud-config, 与 springcloud-eureka-client 两个项目中, 添加 spring cloud bus 的依赖与配置.
在 pom.xml 文件中添加依赖
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-bus-amqp</artifactId>
- </dependency>
在 application.YAML 配置文件中添加 RabbitMQ 的相关配置
- spring:
- rabbitmq:
- host: 127.0.0.1
- port: 5672
- username: rabbitmq
- password: passw0rd
2. 依次启动 springcloud-eureka, springcloud-config, springcloud-eureka-client 项目, 并以 8081 端口再启动一个 springcloud-eureka-client 的服务实例.
3. 我们使用 postman 对配置服务器调用 / actuator/bus-env 接口,
请求两个服务实例的测试接口, 得到结果
两个实例的 Environment 对象都已经更新, 如果要将 @Value 注解的属性也更新, 则可再调用配置服务器的 / actuator/bus-refresh 接口.
/actuator/bus-env 接口是直接更新的内存 Environment 实例属性, 如果服务重启, 则又还原到之前的配置了, 所以还是需要借助配置仓库来永久更新. 配置更新后还需要手动调用接口使其生效? DevOps 时代了, 能自动化的就自动化吧, 我们可以借助 Git 的 webhook 机制来实现自动化.
自动化
本文开头的 "使用 Spring Cloud Bus 来实现服务配置动态更新的结构图" 已经示例了使用 Git 仓库的 webhook 来触发自动更新配置的流程. 但是在 Git(如 GitHub)中, 我们不能直接使用 / actuator/bus-refresh 接口来作为 webhook(因为接口协议不一致, 会出现解析异常), 也有人通过提供自己的接口来作为 webhook, 在自己接口中再转发请求到 / actuator/bus-refresh 来实现. 但实际上, spring-cloud-config-monitor 已经提供了对 Git webhook 的支持.
如下图, spring-cloud-config-monitor 提供了对 GitHub,GitLab,Gitee,BitBucket 等的支持
1. 在配置服务器 springcloud-config 的 pom.xml 文件中添加依赖
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-config-monitor</artifactId>
- </dependency>
2. 在配置仓库的设置页面配置 webhook, 比如 GitHub 的配置如图
Payload URL 配置为配置服务器的 monitor 接口地址, path 参数必须. 如果你的配置服务器在内网, 比如做本地测试时, 还需要实现一下内网穿透(如 frp).
在配置仓库项目中修改配置属性, 提交代码, GitHub webhook 就会触发自动更新, 上图下方红色框为触发自动更新的记录.
自动更新配置未生效排查
如果出现 GitHub 触发了自动更新, 但服务的配置更新未生效的情况, 则需要查看 webhook 的匹配规则与服务实例的 ServiceID 是否匹配, webhook 的匹配规则为 spring.application.name:spring.cloud.config.profile:**, 服务实例的 ServiceID 可通过 spring.cloud.bus.id 配置, 如果没有配置, 则默认为
${vcap.application.name:${spring.application.name:application}}:${vcap.application.instance_index:${spring.application.index:${local.server.port:${server.port:0}}}}:${vcap.application.instance_id:${random.value}}
遵循 App:index:id 的格式,
App: 如果 vcap.application.name 存在, 使用 vcap.application.name, 否则使用 spring.application.name, 默认值为 application
index: 优先使用 vcap.application.instance_index, 如果不存在则依次使用 spring.application.index,local.server.port,server.port, 默认值为 0
id: 如果 vcap.application.instance_id 存在, 使用 vcap.application.instance_id, 否则给一个随机值
我们可以在服务项目中打开 spring cloud bus 的 debug 日志
- logging:
- level:
- org.springframework.cloud.bus: debug
通过 DefaultBusPathMatcher 的 debug 日志来查看是否匹配, 如
DEBUG 286196 --- [7O8XC9KNWbyDA-1] o.s.cloud.bus.DefaultBusPathMatcher : In match: hello-service:8081:c96f04c81dfce6dffaa9d116811d127c, hello-service:8081:c96f04c81dfce6dffaa9d116811d127c
如果没有匹配则可以按照 webhook 的匹配规则设置 spring.cloud.bus.id 值或 vcap.application.instance_index 值, 如
- spring:
- application:
- name: hello-service
- cloud:
- config:
- discovery:
- service-id: config-server
- enabled: true
- profile: ${spring.profiles.active:default}
- bus:
- id: ${spring.application.name}:${spring.cloud.config.profile}:${random.value}
- # 或
- vcap:
- application:
- instance_index: ${spring.cloud.config.profile}
配置更新未生效的另一个情况是查看是否用了 @RefreshScope 注解.
@RefreshScope
细心的人会发现本文开头的测试接口类上加了 @RefreshScope 注解. @RefreshScope 是 Spring Cloud 提供的用来实现配置, 实例热加载的注解. 被 @RefreshScope 修饰的 @Bean 都是延迟加载的, 即在第一次访问 (调用方法) 时才会被初始化, 并且这些 bean 存于缓存中. 当收到配置更新的消息时, 缓存中的 @RefreshScope bean 会被清除, 这样下次访问时将会重新创建 bean, 此时使用的就是最新的配置信息, 从而实现配置的热加载.
总结
本文分别示例了使用 spring boot actuator 与 spring cloud bus 来实现服务配置的更新及两者之间的区别, spring cloud bus 一定程度上像是一个分布式的 spring boot actuator. 同时演示了使用 webhook 与 spring cloud bus,monitor 结合来实现配置自动更新的具体流程及可能遇到的问题.
认真生活, 快乐分享! 如果你觉得文章对你有帮助, 欢迎分享转发!
来源: https://www.cnblogs.com/spec-dog/p/12371899.html