文章目录
- 前言
- 正文
- 搭建 Config Server
- 搭建 Config Client
- 配置热刷新
- 消息总线实现热刷新
前言
在常规的开发中,每个微服务都包含代码和配置,其配置包含服务配置、各类开关和业务配置。
如果系统结构中的微服务节点较少,那么常规的代码 + 配置的开发方式足以解决问题。
但当系统逐步迭代,其微服务会越来越复杂,慢慢演化成网状依赖结构,这个时候常规的代码 + 配置的开发方式就并不合适了,因为还要考虑整体系统的扩展性、伸缩性和耦合性等。这些问题中,配置的管理也变得越来越麻烦。
如果还是以常规开发形式管理配置,则要承担反复修改编译代码、重启系统、重新打包等风险。所以,一个可以集中管理,带有版本控制的配置中心应运而生。
Spring Cloud Config 就是一个分布式配置中心解决方案,其采用集中式管理每个微服务的配置信息,并使用 Git 等版本仓库统一存储配置内容,实现版本化管理控制。总的来说,其有如下优点:
- 提供服务端和客户端支持(spring cloud config server 和 spring cloud config client);
- 集中式管理分布式环境中的配置信息(例如所有配置文件统一放在 gitee 平台中);
- 基于 Spring 环境提供配置管理,与 Spring 系列框架无缝结合;
- 可用于任何语言开发环境,基于 Http 协议;
- 默认基于 Git 仓库实现版本控制。
正文
在 Spring Cloud Config 中包含两个角色:
- Spring Cloud Config Server:负责接收 Config Client 请求,根据请求从远程仓库中获取到配置文件;
- Spring Cloud Config Client:所有包含配置文件的项目都可以是 Config Client。
其结构如图所示:
需要把项目的配置文件上传到远程仓库中,然后 Config Client 通过 REST 访问 Config Server,指定自己要使用的配置信息,Config Server 再去访问远程仓库,拉取相应的信息给 Config Client 使用。
注意:本次示例假定在远程仓库:https://gitee.com/CoderGeshu/configdemo 中存在配置文件 application-dev.yml,内容如下:
server:
port: 8899
my:
content: hello
说明:配置文件结构为 name-profile.yml(或 name.yml,最多只支持二级结构)。在本例中,配置文件名为 application-dev.yml,那么其中 application 是 name,dev 是 profile。
搭建 Config Server
Config Server 主要的任务是从 Git(远程或本地仓库)获取到配置文件。
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
编写配置文件
application.yml 文件内容:
server:
port: 8888
spring:
application:
name: config-server
cloud:
config:
label: master
server:
git:
uri: https://gitee.com/CoderGeshu/configdemo.git
# searchPaths: config
# username: # 私有仓库需要加账号密码,公开仓库不必
# password:
创建启动类
@EnableConfigServer
@SpringBootApplication
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
访问测试
在浏览器地址栏输入 http://ip:port/name/profile/label
地址即可进行访问获取配置文件信息。基于本例,可访问 http://localhost:8888/application/dev/master
来进行测试。
注意:
- 如果路径中 profile 字段不存在,可省略不写;
- 如果远程仓库没有新建分支,则 master 为默认分支名(主分支)。
- 如果访问的是不带 profile 的配置文件,则不能省略 master。
- 如果访问的是带有 profile 的配置文件,master 可以省略,省略后返回 json 字符串中 label 属性为 null,正常应该为 master。
搭建 Config Client
Config Client 对于 Spring Cloud Config 而言是客户端,对于 Eureka 来说可以是 Application Server 也可以是 Application Client。
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
编写配置文件
Config Client 读取的是远程仓库中的配置信息,所以本地配置的是它如何从 Config Server 中获取远程的配置信息。
创建 bootstrap.yml,该配置文件是 Spring Cloud 提供的一个专门加载外部资源的配置文件,位置在 classpath 中(如:resources),其加载顺序高于 application.yml 配置文件,但是 application.yml 配置文件中内容会覆盖 bootstrap.yml 中内容。
bootstrap.yml 内容如下:
spring:
cloud:
config:
uri: http://localhost:8888
name: application # 远程配置中心中的配置文件名称
profile: dev
label: master
# discovery: # 如果读取远程配置的服务注册在注册中心中的话,可以使用服务调用而不使用 url
# enabled: true
# service-id: config-server
配置说明:
spring.cloud.config.discovery.enabled
:是否开启 Config 服务发现支持。
spring.cloud.config.discovery.service-id
:配置中心在 Eureka Server 上注册的名称。
spring.cloud.config.uri
:如果 Config Server 没有在注册中心中注册,则可以直接调用其 url。
spring.cloud.config.name
:远程仓库的配置文件名(name-profile)对应。
spring.cloud.config.profile
:远程仓库的配置文件名(name-profile)对应。
spring.cloud.config.label
:Git 仓库中配置文件所在的分支。如果是 master 默认分支,则可写可不写,其他分支要写明。
创建启动类
@SpringBootApplication
public class ConfigClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigClientApplication.class,args);
}
}
创建测试接口
控制层:
@RestController
@RequestMapping("/demo")
public class DemoController {
@Autowired
DemoService demoService;
@GetMapping("/port")
public String getPort() {
return demoService.getPort();
}
@GetMapping("/content")
public String getContent() {
return demoService.getContent();
}
}
服务层:
public interface DemoService {
String getPort();
String getContent();
}
服务实现:
@Service
public class DemoServiceImpl implements DemoService {
@Value("${server.port}")
private String port;
@Value("${my.content}")
private String content;
@Override
public String getPort() {
return port;
}
@Override
public String getContent() {
return content;
}
}
之后启动项目访问 http://localhost:8899/demo/content
即可观察到返回的 hello 内容,这是当前项目从远程获取到的 my.content 内容信息,并且路径的端口 8899 也是在远程仓库中配置的内容。
配置热刷新
Config Client 服务在刚启动的时候,会根据 bootstrap.yml 中的配置内容去访问 Config Server,从而获取当时的最新的配置内容。
如果 Config Client 在运行过程中,远程仓库中的配置内容发生变更,此时 Config Client 并不会自动地加载刷新配置内容,而是需要人为干预:重启服务,刷新配置。但是这就出现了问题——重启服务代价太大。所以这时就出现了配置热刷新。
热刷新指的是服务不用重启就可以重新加载远程配置内容、初始化应用环境,从而实现不间断地对外提供服务。
因为 Spring Boot 只有在启动时才加载一次配置文件,所以如果在项目运行过程中,修改了远程仓库中配置文件,此时项目也不会实时跟随变化。
既然是 Spring Boot 的问题,那么就可以使用 Spring Boot 提供的 Actuator 来实现刷新项目的功能。
在需要热刷新的项目中,即本例的 Config Client 微服务项目中,进行如下操作。
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!-- 使具有热刷新功能 (post)ip:port/actuator/refresh -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
修改远程配置信息
server:
port: 8899
my:
content: hello
management:
endpoints:
web:
exposure:
include: refresh # 暴漏 actuator 的 refresh 接口
在使用远程配置内容的类上(本例中的 DemoServiceImpl,放在 Controller 上无效)增加新注解:
@Service
@RefreshScope
public class DemoServiceImpl implements DemoService {
// …… 其余保持不变
}
这样当远程配置中心的信息发生改变后,发送 post 请求至接口 http://ip:port/actuator/refresh
即可实现项目配置信息的热刷新。
在本例中,如果修改了远程配置中心的 my.content 信息,之后使用 post 请求至 http://localhost:8899/actuator/refresh
,在项目不停止的情况下,即可刷新当前项目中 my.content 的内容。
消息总线实现热刷新
上述使用 Spring Boot 提供的 actuator 实现了项目配置信息的热刷新,但是如果同时存在多个需要热刷新的微服务项目,那么就要去一个一个的发送 post 请求,就会导致特别繁琐。
此时,要是有一个专门用来热刷新的服务就好了,只要发送一次请求,就可以让所有微服务项目都实现热刷新,由此,消息总线 Spring Cloud Bus 就诞生了。
Spring Cloud Bus 集成了市面上常见的 RabbitMQ 和 Kafka 等消息代理,其会连接微服务系统中所有拥有 Bus 总线机制的节点,当有数据变更的时候,会通过消息中间件使用消息广播的方式通知所有的微服务节点同步更新数据(如:微服务配置更新等)。
热刷新 Config-Client(这里是Service-A)的配置信息时,消息总线会通知其他集成 Bus 的节点去获取配置信息。
热刷新 Config-Server 时,消息总线会通知其他集成 Bus 的节点去获取配置信息。
基于 Bus 消息总线实现热刷新功能,需要在所有的 Eureka/Config Client 端应用中增加 spring-cloud-starter-bus-amqp 依赖,这个依赖是消息总线集成的 RabbitMQ 消息同步组件。
消息总线的热刷新同样是基于 actuator 实现的,所以还要引入 spring-boot-starter-actuator 依赖(有些版本的 amqp 启动器中会包括 actuator 启动器,具体版本具体看一下)。
具体在 Eureka Client 中实现消息总线的步骤如下。
添加依赖
在本例配置 actuator 热刷新原有的依赖基础上增加 Bus 总线依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<!-- 使具有热刷新功能 (post)ip:port/actuator/refresh -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
修改配置文件
因为 Config Client 的配置文件是放在远程仓库的,所以修改远程仓库的配置信息,在原有基础上增加如下内容:
server:
port: 8899
spring:
application: config-client
rabbitmq: # 配置 RabbitMQ 信息
host: localhost
port: 5672
username: guest
password: guest
management:
endpoints:
web:
exposure:
include: "bus-refresh"
my:
content: hello
需要注意的是,使用消息总线需要安装开启 RabbitMQ 服务,然后如上所示配置其 IP 及端口等信息。
当远程配置信息发生改变时,发送 post 请求至 http://ip:port/actuator/bus-refresh
,即可完成热刷新,当然因为本次示例只有一个 Config Server,所以效果不明显,如果是多个 Config Server 都配置了消息总线,那么就可以使用这一个请求让所有的微服务都实现配置信息的热刷新。