一、简介
Spring Cloud Confg 是用来为分布式系统中的基础设施和微服务应用提供集中化的外部配置支持,它分为服务端与客户端两个部分。其中服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置仓库并为客户端提供获取配置信息、加密/解密信息等访问接口;而客户端则是微服务架构中的各个微服务应用或基础设施,它们通过指定的配置中心来管理应用资源与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息。
二、Spring Config Server
搭建一个 Config Server,首先需要一个仓库,作为分布式配置中心的存储。这里我们选择了 Github 作为我们的仓库:https://github.com/JMCuixy/cloud-config-server/tree/master/config-repo
1. pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
2. application.yml
server:
port: 7001
spring:
application:
name: cloud-config-server
# 配置完成后可访问的 url 如下,比如:http://localhost:7001/env/default
# /{application}/{profile} [/{label}]
# /{application}-{profile}.yml
# /{label}/{application}-{profile}.yml
# /{application}-{profile}.properties
# /{label}/{application}-{profile}.properties
cloud:
config:
# 为配置中心提供安全保护
username: user
password: password
server:
git:
# 仓库地址
uri: https://github.com/JMCuixy/cloud-config-server.git
# 搜索路径
search-paths: config-repo
# 访问 http://localhost:7001/actuator/health 可以获取配置中心健康指标
health:
repositories:
env:
name: env
profiles: default
label: master
management:
endpoint:
health:
enabled: true
show-details: always
eureka:
client:
service-url:
defaultZone: http://user:password@localhost:1111/eureka/
这里我没有配置 Github 的 username 和 password,用的是 SSH key 的方式。
3. ConfigApplication.java
// 开启 Spring Cloud Config 的 Server 功能
@EnableConfigServer
@EnableDiscoveryClient
@SpringBootApplication
public class ConfigApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigApplication.class, args);
}
}
至此,一个 Spring Cloud Config Server 就搭建完成了。上面的配置中,我们将 Config Server 注册到 Eureka Server 中,当作整个系统服务的一部分,所以 Config Client 只要利用 Eureka 的服务发现维持与 Config Server 通信就可以了。
在Config Server 的文件系统中,每次客户端请求获取配置信息时,Confg Server 从 Git 仓库中获取最新配置到本地,然后在本地 Git 仓库中读取并返回。当远程仓库无法获取时,直接将本地内容返回。
三、Spring Config Client
Spring Cloud Confg 的客户端在启动的时候,默认会从工程的 classpath 中加载配置信息并启动应用。只有当我们配置 spring.cloud.config.uri(或者spring.cloud.config.discovery) 的时候,客户端应用才会尝试连接 Spring Cloud Confg 的服务端来获取远程配置信息并初始化 Spring 环境配置。同时,我们必须将该参数配置在bootstrap.yml、环境变量或是其他优先级高于应用 Jar 包内的配置信息中,才能正确加载到远程配置。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 当连接 config-server 失败的时候,可增加重试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<!--配置动态刷新-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
2. bootstrap.yml 和 application.yml
- bootstrap.yml
spring:
application:
# 对应配置文件规则中的 {application} 部分
name: env
cloud:
config:
name: env
# uri: http://localhost:7001
discovery:
enabled: true
service-id: cloud-config-server
# 环境变量
profile: default
# 分支
label: master
# config Server 配置的安全信息
username: user
password: password
# 快速失败响应(当发现 config-server 连接失败时,就不做连接的准备工作,直接返回失败)
fail-fast: true
# 失败重试
retry:
# 初始重试间隔时间,毫秒
initial-interval: 1000
# 下一间隔的乘数
multiplier: 1.1
# 最大间隔时间
max-interval: 2000
# 最多重试次数
max-attempts: 6
bootstrap 配置会系统会优先加载,加载优先级比 application 高。
- application.yml
server:
port: 7002
spring:
application:
name: cloud-config-client
eureka:
client:
service-url:
defaultZone: http://user:password@localhost:1111/eureka/
management:
endpoints:
web:
exposure:
# 开启指定端点
# 配置刷新地址:POST http://127.0.0.1:7002/actuator/refresh
include: 'refresh'
3. ConfigClientApplication.java
@EnableDiscoveryClient
@SpringBootApplication
public class ConfigClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigClientApplication.class, args);
}
}
4. 应用
接下来瞅瞅客户端要怎么读到服务器的配置项呢?
@RefreshScope
@RestController
public class ConfigClientAdmin {
@Value("${from:default}")
private String from;
@Autowired
private Environment environment;
@RequestMapping("/from")
public String from() {
String fromEnv = environment.getProperty("from");
return from + "_" + fromEnv;
}
}
如上,我们可以使用 @Value 注解注入配置信息,或者使用 Environment Bean 来获取配置项。
需要注意的是,当服务端的配置项更新的时候,客户端并不会同步获得更新,需要 Post 方法执行 "/actuator/refresh" 来刷新配置项。
@RefreshScope 注解使配置的内容动态化,当使用 http://127.0.0.1:7002/actuator/refresh 刷新配置的时候,会刷新带有 @RefreshScope 的 Bean。
四、动态路由
上一篇文章 我们尝试用 Spring Cloud Zuul 搭建了网关服务,但是我们发现路由信息都配置在 application.yml 中,这对网关的高可用是个不小的打击,因为网关作为系统流量的路口,总不能因为改个路由信息天天重启网关吧?所以动态路由的实现,就变得迫不及待了,好在我们现在有了 Spring Cloud Config。
首先,我们将 Spring Cloud Zuul 的路由信息,配置在 Config Server 的 env.yml 中:
zuul:
routes:
client-1:
# ?:匹配任意单个数量字符;*:匹配任意多个数量字符;**:匹配任意多个数量字符,支持多级目录
# 使用 url 的配置没有线程隔离和断路器的自我保护功能,不推荐使用
path: /client-1/**
url: http://localhost:2222/
# 敏感头信息设置为空,表示不过滤敏感头信息,允许敏感头信息渗透到下游服务器
sensitiveHeaders: ""
customSensitiveHeaders: true
client-2:
path: /client-2/**
serviceId: cloud-eureka-client
# zuul.routes.<serviceid> = <path>
cloud-eureka-client: /client-3/**
client-4:
path: /client-4/**
# 请求转发 —— 仅限转发到本地接口
url: forward:/local
# Zuul 将对所有的服务都不自动创建路由规则
ignored-services: "*"
# 对某些 url 设置不经过路由选择
ignored-patterns: {"/**/world/**","/**/zuul/**"}
# Spring Cloud Zuul在请求路由时,会过滤掉 HTTP 请求头(Cookie、Set-Cookie、Authorization)信息中的一些敏感信息,
sensitive-headers: {"Cookie", "Set-Cookie", "Authorization"}
# 网关在进行路由转发时为请求设置 Host 头信息(保持在路由转发过程中 host 头信息不变)
add-host-header: true
# 请求转发时加上 X-Forwarded-*头域
add-proxy-headers: true
# 是否开启重试,默认关闭
retryable: true
# 通过 /zuul 路径访问的请求会绕过 dispatcherServlet, 被 Zuu1Servlet 处理,主要用来应对处理大文件上传的情况。
servlet-path: /zuul
# 禁用某个过滤器 zuul.<SimpleClassName>.<filterTye>.disable=true
TokenFilter:
pre:
disable: true
然后,我们将网关服务注册为 Config Client(配置项与上面类似,就不赘述了),从 Config Server 获取路由信息:
@EnableZuulProxy
@EnableDiscoveryClient
@SpringBootApplication
public class DynamicRouteApplication {
public static void main(String[] args) {
SpringApplication.run(DynamicRouteApplication.class, args);
}
/**
* 刷新地址:POST http://127.0.0.1:5006/actuator/refresh
* 路由查看地址:GET http://127.0.0.1:5006/actuator/routes
*
* @return
*/
@Bean
@Primary
//该注解来使 zuul 的配置内容动态化
@RefreshScope
@ConfigurationProperties(prefix = "zuul")
public ZuulProperties zuulProperties() {
return new ZuulProperties();
}
}
这样,就把我们的路由信息交给 Config Server 去管理了~~
五、Spring Cloud Bus
上面的内容我们学习到:当 Config Server 配置变更了以后,Config Client 通过执行 “/actuator/refresh” 来刷新配置项。一两个实例的话,倒不算什么。实例一旦多了,这将是一个非常繁琐的工作。怎么办呢?就要说到 Spring Cloud Bus。
微服务架构的系统中,我们通常会使用轻量级的消息代理来构建一个共用的消息主题让系统中所有微服务实例都连接上来,由于该主题中产生的消息会被所有实例监听和消费,所以我们称它为消息总线。在总线上的各个实例都可以方便地广播一些需要让其他连接在该主题上的实例都知道的消息,例如配置信息的变更或者其他一些管理操作等。Bus 就是 Spring Cloud 中的消息总线。
那么 Spring Cloud Bus 怎么刷新我们的配置项呢?
- 当 Config Server 配置项发生变更的时候,发送一个消息到消息代理的 Topic 下。
- Config Client 订阅 Topic 接收到消息后,主动执行 “/actuator/refresh” 刷新配置。
Spring Cloud Bus 的集成(基于 RabbitMQ 消息代理)也非常简单,只需要我们在 Config Server 中引用:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
然后,在 application.yml 中配置 RabbitMQ ,并暴露 “bus-refresh” 端点:
management:
endpoints:
web:
exposure:
# 执行 http://127.0.0.1:7001/actuator/bus-refresh,把配置内容修改的消息发布到服务总线。
# 客户端收到订阅消息,自动执行 /actuator/refresh,刷新配置
include: bus-refresh
客户端的配置和服务端类似,也只要引入 jar 包,配置 RabbitMQ 即可(不用暴露 “bus-refresh” 端点)。
篇幅有限,不细讲了,大家可以看我 GitHub 上的内容,包括 RabbitMQ 的集成和 Kafka 的集成。