我们之前介绍并且搭建过eureka
、zuul
、hystrix
组件。本节介绍的config
是SpringCloud五大组件的最后一个,还有一个是Ribbon ----- 客服端负载均衡
,之前我们有简单介绍过☞Eureka、Nacos注册中心及负载均衡原理,直接使用注解@LoadBalanced
就可以实现负载均衡或者是网关zuul默认带有负载均衡策略、gateway使用lb
。本节我们来介绍下config作用及环境搭建。
微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能运行,所以也面临了一些问题:
所以一套集中式的、动态的配置管理设施是必不可少的。
目前使用较多的三套配置:
Config+Bus
Nacos(后起之秀,我们之前已经介绍过了)
携程阿波罗(Apollo)
集中管理配置
Spring Cloud Config 可以将应用程序的配置文件集中管理起来,以便在不同环境下轻松部署和管理应用程序。开发人员可以将配置文件存储在 Git、SVN 或本地文件系统等存储库中,并使用 Spring Cloud Config 服务器来访问和管理这些文件。
多环境支持
Spring Cloud Config 支持多环境配置文件的管理,开发人员可以将不同环境下的配置文件分别存储在不同的存储库中,并在应用程序启动时根据不同的环境加载相应的配置文件,分环境部署比如dev/test/prod/beta/release
。
动态刷新配置
Spring Cloud Config 还支持动态刷新配置,当应用程序运行时,开发人员可以通过调用 REST 接口来刷新配置,或者在配置中心修改配置文件后自动更新应用程序的配置。
安全性控制
Spring Cloud Config 提供了基于角色的安全性控制,开发人员可以通过配置用户角色来限制对配置文件的访问权限,确保只有授权用户才能访问敏感信息。
SpringCloud Config分为服务端和客户端两部分。
服务端也称为分布式配置中心,它是一个独立的微服务应用,为客户端提供配置信息,加密/解密信息等访问接口。服务器为外部配置(名称值对或等效的YAML内容)提供了基于资源的HTTP(REST接口)。
客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息。
简单说明一下流程:
在我们之前的eureka
或者zuul
、hystrix
项目上进行开发,省去繁琐的项目创建步骤。但各个组件之间并没有依赖关系,config也一样,完全可以新建一个项目按我的步骤开发。
项目中使用到的版本:
Spring Boot:2.3.12.RELEASE(2.2.X版本或者2.3.X版本)
Spring Cloud:Hoxton.SR1
首先我们需要一个创建一个配置中心仓库。可以使用github或者是码云gitee。
1、在码云创建springcloud-config仓库,并创建以下两个文件:
文件命名规格:{项目名}-{配置环境版本}.yml。比如config-dev.yml,表示的是config项目的开发环境配置。
新建springcloud-config
模块:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-config-serverartifactId>
dependency>
bootstrap.yml【或者是application.yml】
spring:
application:
name: config-server # 应用名称
cloud:
config:
server:
git:
# 配置中心仓库地址
uri: https://gitee.com/lin-shen/springcloud-config.git #配置文件所在仓库
username: 码云用户名
password: 码云密码
default-label: master #配置文件分支
server:
port: 9999
bootstrap.yml文件
applicaiton.yml是用户级的资源配置项,bootstrap.yml是系统级的,优先级更加高
要将Client模块下的application.yml文件改为bootstrap.yml,这是很关键的。因为bootstrap.yml是比application.yml先加载的。
Spring Cloud会创建一个“Bootstrap Context”,作为Spring应用的Application Context的父上下文。初始化的时候,Bootstrap Context负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的’Environment’。
Bootstrap属性有高优先级,默认情况下,它们不会被本地配置覆盖。Bootstarp context 和 Application Context 有着不同的约定,所以新增了一个bootstrap.yml文件,保证Bootstrap Context和Application Context配置的分离。
在 Application 启动类上增加相关注解 @EnableConfigServer
@SpringBootApplication
@EnableConfigServer
public class SpringcloudConfigApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudConfigApplication.class, args);
}
}
启动SpringBoot服务,测试一下。
SpringCloud Config 有它的一套访问规则,我们通过这套规则在浏览器上直接访问就可以。
/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
application
:应用名,这里是configprofile
:配置文件版本,比如dev、prodlabel
:git的分支,默认master
客户端获取配置中心的配置内容。
在之前项目的provider-server
模块或者是创建一个新的模块获取配置中心的相关配置:
dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-configartifactId>
dependency>
dependencies>
注意,客户端和服务端引入的config依赖是不一样的。
由于配置文件的加载优先级不同,Spring Boot会尝试加载以下配置文件(按照优先级从高到低):
bootstrap.yml
application-dev.yml
application-dev.yaml
application.yml
application.yaml
高的配置生效后,低的不会生效。获取配置中心的配置,应该在bootstrap.yml
配置文件中进行配置才能确保获取得到配置中心的数据。
---
spring:
profiles:
active: dev
---
spring:
profiles: dev
application:
name: config
cloud:
config:
uri: http://localhost:9999
label: master
profile: dev
---
spring:
profiles: prod
application:
name: config
cloud:
config:
uri: http://localhost:9999
label: master
profile: prod
该配置文件指定了加载dev
配置信息,会向配置中心服务端即http://locahost:9999
服务发起请求获取配置,服务端则发起请求http://localhost:9999/config/dev
从远程仓库获取数据返回给客户端。
这里遇到了一个奇怪的错误,有时候启动时,当resources文件下有其他配置文件时会干扰从远程仓库获取配置数据,即使远程仓库有
member.name="张三-dev"
配置也会报错,解决方法就是resources目录删除到只剩下bootstrap.yml文件。
我们上面提到的配置文件方式是多个配置环境写到同一个文件中,但这样维护非常繁琐,不同的环境就应该有对应的一个配置文件,但这种配置方式下配置中心的配置不生效,必须要本地文件配置member.name
,不配置会报以下错误:找不到member.name属性。
Error creating bean with name ‘userController’: Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder ‘member.name’ in value “${member.name}”
Spring Cloud Config是在项目启动的时候加载的配置内容,导致了它存在一个缺陷,配置文件修改后,需要重启服务才能生效。我们在码云上修改config-dev.yml
配置:
member:
name: "王五-dev"
虽然访问http://localhost:9999/config/dev
从之前的张三变成了王五,但是访问http://localhost:8082/user/login
还是张三,说明配置没有自动刷新,可以通过 @RefreshScope
注解并结合 actuator
实现自动刷新。
在客户端加入下面依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
在application.yml中添加如下actuator的配置
management:
endpoints:
web:
exposure:
include: refresh
在Controller上添加@RefreshScope
注解
@RestController
@RequestMapping("/user")
@RefreshScope
public class UserController {
@Value("${member.name}")
private String name;
@GetMapping("/login")
public String login(){
return "8080 " + name + " login success";
}
}
当配置文件发生更改时,可以通过向应用程序发送POST请求来触发配置刷新。默认情况下,刷新端点的路径为/actuator/refresh。可以在cmd窗口使用curl命令发送POST请求:
注意:要向客户端发起请求,端口是8082,而不是服务端:9999
curl -X POST http://localhost:8082/actuator/refresh
上面提到的actuator方式,如果每次配置文件修改后,都需要我们主动发送post请求触发更新,这明显有点不太方便。而且如何客户端比较多的话,一个一个的手动刷新也很耗时。这个时候,我们可以借助Spring Cloud Bus的广播功能,让client端都订阅配置更新事件,当配置更新时,触发其中一个端的更新事件,Spring Cloud Bus就把此事件广播到其他订阅端,以此来达到批量更新。
Spring Cloud Bus 核心原理其实就是利用消息队列做广播,所以要先有个消息队列,目前官方支持 RabbitMQ 和 kafka。我们这里以RabbitMQ 为例。
在actuator方式配置下进行以下配置的修改
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-bus-amqpartifactId>
dependency>
在配置文件中进行以下配置,我们在上面说过使用RabbitMQ作为广播,rabbitmq可以参考:rabbitmq安装。
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
management:
endpoints:
web:
exposure:
include: bus-refresh
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: ldh
password: ldh
cloud:
bus:
enabled: true
启动多个客户端进行测试:
复制原来客户端:
端口配置:8088
现在我们就启动两个相同的客户端,他们的端口不同:
分别打开http://localhost:8099/user/login
和http://localhost:8082/user/login
,查看内容,然后修改码云上配置文件的内容并提交。再次访问这两个地址,数据没有变化。
在cmd窗口访问访问其中一个的 actuator/bus-refresh 地址:curl -X POST http://localhost:8082/actuator/bus-refresh
再次访问这两个地址,会看到内容都已经更新为修改后的数据了,相比actuator方式只需一次post请求即可刷新各个客户端访问服务端config的相关配置信息。