SpringBoot 项目通常将一些重要的参数配置在application.yml或者application.properites中,譬如外部服务连接、数据库地址及账号信息、某些业务变量。随着业务的开展,实现一个完整的业务流程通常需要开发并管理多个微服务,一旦这些参数调整,需要修改多个微服务的配置并重启,这将给运维及生产带来额外的工作和影响。为了解决这个问题,我们可以将这些配置抽取到一个公共的地方,可参照另一篇文章“Spring Cloud 微服务公共配置处理请添加链接描述”,将微服务的公共配置抽取出来。完成抽取只是第一步,一旦数据库的账号或者外部服务连接修改,服务还是需要逐个重启,如何解决这个问题呢?
Spring Cloud的message和integration机制,允许我们通过cloud bus,将变更通知到每一个服务,实现动态刷新,其架构设计如下(摘自网络)
实现这个过程,需要引入spring cloud的相关组件:注册服务、消息服务、集成服务、配置服务等,以及一个可用的消息队列。下面我将以Springboot2.1.8,cloud版本Greenwich.SR3为例,介绍如何实现。
服务清单
- EurekaServer - 注册服务,实际项目可选其他,此处仅作示例。
- ConfigServer - 配置服务
- ConfigClient1 - 微服务应用1,通过config server获取配置
- ConfigClient2 - 微服务应用2,通过config server获取配置
- RabbitMQ - 消息服务,用来接收和转发 bus消息
- Git服务 - 配置文件的存储和修改,实际项目中可选其他替代方案,此处仅作示例。
1. EurekaServer
Eureka服务比较简单,配置和启动效果如下。
server:
port: 7777
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false
fetch-registry: false
service-url:
default-zone: http://${eureka.instance.hostname}:${server.port}/eureka/
2. ConfigServer
Configserver需要引入bus-amqp,pom配置如下:
- boot相关
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
- cloud相关
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-bus</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-monitor</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
</dependency>
application的配置如下,
-
spring config相关:
-
management相关:
3. ConfigClient01 和ConfigClient02
- bootstrap.yml配置
management的配置是为了支持bus-fresh,rabbitmq需要同时配置在bootstrap和application中。
- application配置
主要包括eureka配置,rabbitmq配置及用来测试ext.key。configclient02的配置和01几乎一致,仅端口和应用名称不同。
所以服务启动后,可以看到eureka的管理界面如下:
4. git仓库中配置服务application文件的替代版
内容分别如下:
ext.key=hello client 111
ext.key=hello client 222
这个时候就可以测试了。测试方式可以通过浏览器访问configserver,直接请求配置文件,也可以通过程序测试,先说通过浏览器访问。
springboot项目启动时,如果引入了config-client和context包,则会自动根据bootstrap指定的地址获取配置,请求方式如下: http://${spring.cloud.config.uri}/${spring.cloud.config.name}/<spring.cloud.config.profile>/<lspring.cloud.config.label>, 参照bootstrap.yml的定义。
这个项目直接请求:http://localhost:8021/cfg_client01/dev/aliyun_dev, 返回如下: 这种请求方式,也是springboot项目启动时初始化的请求方式。
下面以更实际的方式测试,在controller中引用一个变量,ext.key, 查看属性的变化。
@RestController
@RequestMapping("/test")
@RefreshScope
public class IndexController {
@Value("${ext.key}")
private String key;
@GetMapping("/key")
public String printKey() {
return key;
}
}
controller很简单,唯一需要注意的是,一定要加上@RefreshScope,因为spring在刷新时,会基于这个注解,确定要不要刷新bean或者方法内的引用属性。 第一次直接请求:
现在修改git上的配置文件,将ext.key改成111000和222000。
这个时候就可以调用spring的bus-refresh了。调用refresh的时候,可以直接调用server的,也可以调用某个具体的client,我希望一次刷新,所有的变更都生效,所以直接调用configserver的bus-refresh。当然,也有人将git的commit配置成hookurl,但这种方式在生产环境会有些风险,我们的做法是,配置文件修改并确认提交后,手动执行一次bus-refrehs。
刷新完成后,分别在请求extkey,查看返回:
可以看到两个应用服务的属性都已经刷新。实现刷新的关键有以下几个:
- 消息队列的在bootstrap和applicaiton中同时配置
- management.endpoints.web.exposure.include属性需要配成"*"或者 bus-refresh,因为这个地址默认不支持,直接调用会出现404.
- 验证服务,网上很多介绍都需要关闭,我测试过程中,没有关闭,但是可以直接访问。实践者可以根据自己的服务配置调整。
基于此,我们就实践了配置文件中属性的动态刷新,实现一次刷新,所有服务生效。这种@Value引用的属性可以这么刷新,那么数据库连接参数呢?下一篇文章,我将介绍如何通过刷新机制,实现在不重启服务的前提下,动态修改数据库的连接参数或者密码。