对于传统的单体应用,通常使用配置文件管理所有配置。比如Spring Boot开发的单体应用,配置内容可以放在application.yml文件,需要切换环境的话可以设置多个Profile并在启动应用时指定spring.pfofiles.active={profile}。
然而,在微服务架构中,配置管理一般有以下需求
所以,对于微服务架构而言,一个通用的配置管理机制是必不可少的,常见做法是使用配置服务器管理配置。
Spring Cloud Config为分布式系统外部化配置提供了服务端和客户端的支持,它包括Config Server和Config Client两部分。
Config Server是一个可横向扩展、集中式的配置服务器,它用于集中管理应用程序各个环境下的配置,默认使用Git存储配置内容(也可使用Subversion、本地文件系统或Vault存储配置),因此可以很方便地实现对配置的版本控制与内容审计。
Config Client是Config Server的客户端,用于操作存储在Config Server中的配置属性。
Spring Cloud Config的架构图如下
所有微服务都指向Config Server。各个微服务在启动时,会请求Config Server以获取所需要的配置属性,然后缓存这些属性以提高性能。
1)在码云仓库https://gitee.com/thunderclap/spring-cloud-config-repo中新建一个文件夹order,在里面新建几个配置文件。
通常,配置文件的名称格式为“服务名称-环境.properties”(或者“服务名称-环境.yml”),其中环境常用的有以下几种
例如
config-client-dev.properties
config-client-prd.properties
config-client-pre.properties
config-client-sit.properties
其中config-client为服务名称。
内容分别是
prifile=dev-1.0
prifile=prd-1.0
prifile=pre-1.0
prifile=sit-1.0
效果如下
2)创建一个maven工程microservice-config-server,pom.xml文件添加如下依赖
4.0.0
cn.zhh
microservice-config-server
1.0
org.springframework.boot
spring-boot-starter-parent
2.0.1.RELEASE
org.springframework.cloud
spring-cloud-dependencies
Finchley.M7
pom
import
org.springframework.cloud
spring-cloud-config-server
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
spring-milestones
Spring Milestones
https://repo.spring.io/libs-milestone
false
org.springframework.boot
spring-boot-maven-plugin
为了利用服务发现组件的优势,需要将该服务注册到Eureka Server上。
3)编写配置文件application.yml,并在其中添加如下内容
server:
port: 8085
spring:
application:
# 服务名称,客户端需要使用此名称查询配置中心服务端
name: config-server
cloud:
config:
server:
git:
# 配置码云仓库地址
uri: https://gitee.com/thunderclap/spring-cloud-config-repo.git
# 搜索路径(文件夹,可以多个)
search-paths:
- order
# 分支
label: master
# eureka server
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8080/eureka/
因为是测试,所以没有设置用户名密码。
4)在启动类上添加注解@EnableConfigServer,声明这是一个Config Server,同时也要加上@EnableDiscoveryClient,将服务注册到Eureka Server。
@SpringBootApplication
@EnableDiscoveryClient
@EnableConfigServer
public class MicroserviceConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(MicroserviceConfigServerApplication.class, args);
}
}
这样,一个Config Server就完成了。
下面讲一下Config Server的端点
可以使用Config Server的端点获取配置文件的内容。端点与配置文件的映射规则如下
/{application}/{profile}/[{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
以上端点都可以映射到{application}-{profile}.properties这个配置文件,{application}表示微服务的名称,{label}表示仓库分支,默认是master,{profile}表示环境。
启动服务,访问http://localhost:8085/config-client/dev得到如下结果
可以直观地看到应用名称、项目profile、仓库分支、仓库版本、配置文件URL、配置详情等信息。
访问http://localhost:8085/config-client-dev.properties,返回配置文件中的属性
1)创建一个maven工程microservice-config-client,pom.xml文件添加如下依赖
4.0.0
cn.zhh
microservice-config-client
1.0
org.springframework.boot
spring-boot-starter-parent
2.0.1.RELEASE
org.springframework.cloud
spring-cloud-dependencies
Finchley.M7
pom
import
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-config
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
spring-milestones
Spring Milestones
https://repo.spring.io/libs-milestone
false
org.springframework.boot
spring-boot-maven-plugin
2)编写配置文件application.yml,并在其中添加如下内容
server:
port: 8086
3)编写配置文件bootstrap.yml,并在其中添加如下内容
spring:
application:
name: config-client
cloud:
config:
# 环境
profile: pre
# 分支
label: master
discovery:
enabled: true
service-id: config-server
# eureka server
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8080/eureka/
Config Client必须在bootstrap.yml或bootstrap.properties中配置。
原因:Spring Cloud有一个“引导上下文”的概念,这是主应用程序的父上下文,负责从配置服务器加载配置属性,以及解密外部配置文件中的属性。引导上下文加载bootstrap.*中的属性,比主应用程序加载application.*中的属性有更高的优先级,并且默认情况下不能被本地配置覆盖。
4)在启动类上添加@EnableDiscoveryClient,将服务注册到Eureka Server。
@SpringBootApplication
@EnableDiscoveryClient
public class MicroserviceConfigClientApplication {
public static void main(String[] args) {
SpringApplication.run(MicroserviceConfigClientApplication.class, args);
}
}
5)添加一个读取配置的Controller类用来测试。
@RestController
public class ConfigClientController {
@Value("${profile}")
private String profile;
@GetMapping("profile")
public String getProfile() {
return profile;
}
}
6)先启动Eureka Server和Config Server,然后启动该应用。访问http://localhost:8086/profile,返回以下结果
说明获得了仓库中配置文件的信息。
Config Server的占位符支持{application}、{profile}和{label}。
如spring.cloud.config.server.git.uri:https://gitee.com/thunderclap/{application}.git。
模式匹配指的是带有通配符的{application}/{profile}名称的列表。
spring:
cloud:
config:
server:
git:
uri: https://gitee.com/thunderclap/spring-cloud-config-repo.git
# 搜索路径(文件夹,可以多个),支持占位符如bar*
search-paths:
- order
spring:
cloud:
config:
server:
git:
# 配置码云仓库地址
uri: https://gitee.com/thunderclap/spring-cloud-config-repo.git
clone-on-start: true
以上是全局配置,可以单个仓库独自配置的。
// TODO
// TODO
很多场景下,如果配置发生了修改,需要微服务在运行期间动态刷新调整。
怎么做呢?利用spring-boot-starter-actuator的/refresh端点可以做到,其原理是重新将bean注册到IOC容器。
1)pom.xml文件引入actuator的依赖。
org.springframework.boot
spring-boot-starter-actuator
2)application.yml配置文件开启所有服务端点权限。
management:
endpoints:
web:
exposure:
# 开启所有服务端点权限
include: "*"
3)注入配置属性的类上面增加注解@RefreshScope。
@RestController
@RefreshScope
public class ConfigClientController {
@Value("${profile}")
private String profile;
@GetMapping("profile")
public String getProfile() {
return profile;
}
}
4)修改仓库上的配置属性,post请求方式调用http://localhost:8086/actuator/refresh接口。
5)重新访问http://localhost:8086/profile,可以看到配置的值发生了改变。
使用/refresh端点手动刷新配置,只能刷新单个JVM的,每个微服务都这么去搞,工作量很大。随着系统的不断扩张,越来越难维护。这时候,实现配置的自动刷新就很有必要了。Spring Cloud Bus可以做到这些。
Spring Cloud Bus使用轻量级的消息代理(例如RabbitMQ、Kafka等)连接分布式系统的节点,这样就可以广播传播状态的更改(例如配置的更新)或者其它的管理命令。可将Spring Cloud Bus想象成一个分布式的Spring Boot Actuator。是哟个Spring Cloud Bus后的架构如下图所示。
由图可知,微服务A的所有实例都通过消息总线连接到了一起,每个实例都会订阅配置更新事件。当其中一个微服务节点的/bus/refresh端点被请求时,该实例就会向消息总线发送一个配置更新时间,其它实例获得该事件后也会更新配置。
安装RabbitMQ,参考教程https://www.cnblogs.com/ericli-ericli/p/5902270.html。
1)pom.xml文件添加以下依赖
org.springframework.cloud
spring-cloud-starter-bus-amqp
2)bootstrap.yml配置文件中添加以下内容
spring
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
3)启动项目,然后修改端口再启动一次。修改仓库文件内容后,发送POST请求到其中一个Config Client实例的/bus/refresh端点,然后访问两个Config Client节点的/profile端点,会发现两个节点都会返回修改后的值,说明配置内容已被刷新。
如果使用Git仓库,可以借助WebHooks监听文件变化自动发送请求,轻松实现配置的自动更新。
某些场景下(例如灰度发布等),若只想刷新部分微服务的配置,可通过/bus/refresh端点的destination参数来定位要刷新的应用程序。
例如:/bus/refresh?destination=consumers:9000,这样消息总线上的微服务实例就会根据destination参数的值来判断是否需要刷新。其中,consumers:9000指的是各个微服务的ApplicationContextID。
destination参数也可以用来定位特定的微服务。例如:/bus/refresh?destination=consumers:**,这样就可以触发customers微服务所有实例的配置刷新。
配置的局部刷新与ApplicationContextID有关。默认情况下,ApplicationContextID是spring.application.name:server.port。
通过请求某个微服务/bus/refresh端点来实现配置刷新的方式并不优雅。原因如下
破坏了微服务的职责是单一原则。业务微服务只应关心自身业务,不应承担配置刷新的职责。
破坏了微服务各节点的对等性。
有一定的局限性。例如,微服务在迁移时,网络地址经常会发生变化。此时如果想自动刷新配置,就要修改WebHook的配置。
如果将Config Server也加入到消息总线中,并使用Config Server的/bus/refresh端点来实现配置的刷新。这样,各个微服务只需要关心自身的业务,而不再承担配置刷新的职责。
一些场景下如果希望知道Spring Cloud Bus事件传播的细节,可以跟踪总线事件(RemoteApplicationEvent的子类都是总线事件)。
只需设置spring.cloud.bus.trace.enabled=true,就可以在/bus/refresh端点被请求后,访问/trace端点获得事件的传播细节了。
在Config Server添加以下依赖
org.springframework.boot
spring-boot-starter-security
在application.yml添加以下配置
security:
basic:
enabled: true # 开启基于HTTP basic的认证
# 如果不设置,账号默认是user,密码是一个随机值,该值会在启动时打印出来
user:
name: user # 配置登录的账号是user
password: 123456 # 配置登录的密码是123456
Config Client连接需要认证的Config Server
方式一:使用curl风格的URL,例如
spring:
cloud:
config:
url: http://user:123456@localhost:8080/
方式二:指定Config Server的账号与密码,例如
spring:
cloud:
config:
url: http://localhost:8080/
username: user
password: 123456
方式二的优先级比方式一高。
1)使用第三方Git仓库。比如使用GitHub、BitBucket、get@osc、Coding等提供的仓库托管服务。
2)自建Git仓库管理系统。
使用消息总线实现自动刷新时用到的消息中间件也需要保证高可用。
1)借助一个负载均衡器来实现:各个微服务将请求发送到负载均衡器(比如Nginx),负载均衡器将请求转发到其代理的其中一个Config Server节点。
2)Config Server注册到Eureka Server上:将多个配置服务节点注册到Eureka。