在单体式应用中,我们通常的做法是将配置文件和代码放在一起,这没有什么不妥。当你的应用变得越来越大从而不得不进行服务化拆分的时候,会发现各种 provider 实例越来越多,修改某一项配置越来越麻烦,你常常不得不为修改某一项配置而重启某个服务所有的 provider 实例,甚至为了灰度上线需要更新部分 provider 的配置。这个时候有一套配置文件集中管理方案就变得十分重要,SpringCloudConfig 和 SpringCloudBus 就是这种问题的解决方案之一,业界也有些知名的同类开源产品,比如百度的 disconf。
相比较同类产品,SpringCloudConfig 最大的优势是和 Spring 无缝集成,支持 Spring 里面
和
- Environment
的接口,对于已有的 Spring 应用程序的迁移成本非常低,在配置获取的接口上是完全一致,结合 SpringBoot 可使你的项目有更加统一的标准(包括依赖版本和约束规范),避免了应为集成不同开软件源造成的依赖版本冲突。
- PropertySource
SpringCloudConfig 就是我们通常意义上的配置中心,把应用原本放在本地文件的配置抽取出来放在中心服务器,从而能够提供更好的管理、发布能力。SpringCloudConfig 分服务端和客户端,服务端负责将 git(svn)中存储的配置文件发布成 REST 接口,客户端可以从服务端 REST 接口获取配置。但客户端并不能主动感知到配置的变化,从而主动去获取新的配置,这需要每个客户端通过 POST 方法触发各自的
。
- /refresh
SpringCloudBus 通过一个轻量级消息代理连接分布式系统的节点。这可以用于广播状态更改(如配置更改)或其他管理指令。SpringCloudBus 提供了通过 POST 方法访问的 endpoint
,这个接口通常由 git 的钩子功能调用,用以通知各个 SpringCloudConfig 的客户端去服务端更新配置。
- /bus/refresh
下图是 SpringCloudConfig 结合 SpringCloudBus 实现分布式配置的工作流
SpringCloudConfig 提供基于以下 3 个维度的配置管理:
服务端要在 pom 中依赖
、
- spring-cloud-config-server
- spring-cloud-starter-bus-kafka
- <dependency>
- <groupId>
- org.springframework.cloud
- </groupId>
- <artifactId>
- spring-cloud-config-server
- </artifactId>
- </dependency>
- <dependency>
- <groupId>
- org.springframework.cloud
- </groupId>
- <artifactId>
- spring-cloud-starter-bus-kafka
- </artifactId>
- </dependency>
- <dependency>
- <groupId>
- org.springframework.cloud
- </groupId>
- <artifactId>
- spring-cloud-config-monitor
- </artifactId>
- </dependency>
application.properties 中要配置仓库描述和消息队列地址,如果是私有项目还需要配置用户名密码
- spring.cloud.config.server.git.uri=https://github.com/seagrape/SpringCloudConfig.git
- spring.cloud.config.server.git.searchPaths=alan-config-repo
- #spring.cloud.config.server.git.username=sihan2
- #spring.cloud.config.server.git.password=MYPASSWORD
- spring.cloud.stream.kafka.binder.brokers=10.79.96.52:9092
- spring.cloud.stream.kafka.binder.zk-nodes=10.79.96.52:2182
启动类中要有
注解
- @EnableConfigServer
- @SpringBootApplication
- @EnableConfigServer
- public class ConfigServerApplication {
- public static void main(String[] args) {
- SpringApplication.run(ConfigServerApplication.class, args);
- }
- }
Server 端启动后,提供了如下的接口地址,参数说明
- /{application}/ {
- profile
- } [/{label}]
- / {
- application
- } - {
- profile
- }.yml / {
- label
- }
- /{application}-{profile}.yml
- / {
- application
- } - {
- profile
- }.properties / {
- label
- }
- /{application}-{profile}.properties/
接口返回样例 curl
- {
- "name": "alan-provider-data-config",
- "profiles": ["dev"],
- "label": "master",
- "version": "78dce2b71473749a5298e11ef0d004ffa8d26bd1",
- "propertySources": [{
- "name": "https://github.com/seagrape/SpringCloudConfig.git/alan-config-repo/alan-provider-data-config-dev.properties",
- "source": {
- "spring.datasource.driver-class-name": "com.mysql.jdbc.Driver",
- "spring.datasource.username": "username",
- "spring.datasource.password": "password",
- "spring.datasource.url": "jdbc:mysql://DEVIP:PORT/DBNAME?characterEncoding=UTF-8"
- }
- }]
- }
接口返回样例 curl
- spring.datasource.driver-class-name: com.mysql.jdbc.Driver
- spring.datasource.password: password
- spring.datasource.url: jdbc:mysql://DEVIP:PORT/DBNAME?characterEncoding=UTF-8
- spring.datasource.username: username
GIT 做文件系统,文件都会被 clone 到本地文件系统中,默认这些文件会被放置到以 config-repo - 为前缀的系统临时目录,在 linux 上应该是 /tmp/config-repo - 目录,如果你遇到了不可预知的问题出现,你可以通过设置
参数值为非系统临时目录。
- spring.cloud.config.server.git.basedir
Config Server 中, 还有一种从本地 classpath 或文件系统中加载配置文件的方式,可以通过
进行设置。但如果你连 GIT 环境都没有,你还是回去喝奶吧......
- spring.cloud.config.server.native.searchLocations
客户端要在 pom 中依赖
、
- spring-cloud-starter-config
- spring-cloud-starter-bus-kafka
- <dependency>
- <groupId>
- org.springframework.cloud
- </groupId>
- <artifactId>
- spring-cloud-starter-config
- </artifactId>
- </dependency>
- <dependency>
- <groupId>
- org.springframework.cloud
- </groupId>
- <artifactId>
- spring-cloud-starter-bus-kafka
- </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-actuator
- </artifactId>
- </dependency>
bootstrap.properties 中配置配置中心地址和消息队列地址
- spring.cloud.config.uri=http://127.0.0.1:${config.port:8888}
- spring.cloud.config.name=alan-provider-data-config
- spring.cloud.config.profile=${config.profile:dev}
- spring.cloud.stream.kafka.binder.brokers=10.79.96.52:9092
- spring.cloud.stream.kafka.binder.zk-nodes=10.79.96.52:2182
- @SpringBootApplication
- @RestController
- @RefreshScope
- public class ConfigClientApplication {
- @Value("${spring.datasource.username}")
- String name = "World";
- @RequestMapping("/")
- public String home() {
- System.out.println(name);
- return name;
- }
- public static void main(String[] args) {
- SpringApplication.run(ConfigClientApplication.class, args);
- }
- }
我们知道 Spring 原生提供了一些 scope,如 singleton,prototype,request 等。 为了实现配置更新后,已经注入 bean 的值也能更新的目的,Spring Cloud 提供了一个新的 scope - RefreshScope。
Spring Cloud 对 RefreshScope 的定义如下:
所以,对于那些有注入值的 bean,我们可以把它们标记为 RefreshScope,这样当运行时发现有配置更新的时候,通过调用 RefreshScope.refresh(beanName) 或 RefreshScope.refreshAll(),从而下次这些 bean 被使用时会被重新初始化,进而会被重新注入值,所以也就达到了更新的目的。
ConfigClient 最好要在 ConfigServer 之后启动,Spring 加载配置文件是有顺序的,靠前的配置文件会覆盖靠后的配置文件中相同键的值,如果 ConfigServer 先启动可以保证 ConfigClient 将远程的配置文件加载到最前面,如果使用中没有注意到这一点,有可能导致你本地的配置文件先于远程的加载,导致本地的配置覆盖远程配置。当然,你也可以让本地配置和远程配置完全不重复,这样也可以避免键 / 值覆盖的问题。
后面会进一步说明这部分相关的知识点。
在运行时的结构形如:
需要注意的是,PropertySource 之间是有优先级顺序的,如果有一个 Key 在多个 property source 中都存在,那么在前面的 property source 优先。所以对上图的例子:
- env.getProperty("key2") -> value2
在 ConfigClient 启动阶段,从 ConfigServer 获取配置,然后组装成 PropertySource 并插入到第一个, 在随后的获取配置过程中,来自 Config Server 的配置和其它本地的配置对使用者而言是没有任何差别的,从而实现了无缝集成。
/env 是
提供的一个接口,GET 方法调用可以查看系统环境变量,POST 调用可以更改环境变量的值,并且通过这种方式修改的变量值具有最最高优先级。通过观察了解这个接口数据的变化,对学习 SpringCloudConfig 有帮助。
- spring-boot-starter-actuator
- curl - X POST http: //localhost:8080/env -d spring.datasource.username=wsh
如果要使上述修改生效,an 类。
- curl - X POST http: //localhost:8080/refresh
如果要重置这些修改
- curl - X POST http: //localhost:8080/env/reset
置。
作用同 / env,区别是会对所有节点生效
- curl - X POST http: //localhost:8888/bus/env -d spring.datasource.username=wsh
作用同 / refresh,区别是会对所有节点生效
向消息 broker 发送一条信息,所有监听这个 broker 的应用会获得上述消息,并各自开始更新配置。每个 SpringCloudBus 的节点都有这个接口,并且这些接口是等效的,调用任何一个都可以起到相同的效果。但通常我们会调用在 ConfigServer 上配置的 Bus,这样从流程上更符合人们的理解习惯。
- curl - X POST http: //localhost:8888/bus/refresh
SpringCloudBus 并不是一个独立的服务,他配置在每个 ConfigClient,并通过消息队列使所有节点感知到状态变化。SpringCloudConfig 没有直接集成 bus 的功能是有好处的,bus 是可插拔设计并且目前并不完美,如果有个性需求完全可以用自己的方案替换 bus,这个剥离 bus 成本几乎等于零。
目前 Bus 有两种实现
或
- spring-cloud-starter-bus-amqp
,官网的例子是基于 amqp,需要运行 RabbitMQ,本文的例子用的是 Kafka。
- spring-cloud-starter-bus-kafka
现在我们已经有能力在无需重启的情况下对应用程序配置进行更新了。
GitHub
来源: http://www.bubuko.com/infodetail-1982864.html