spring cloud config 应用场景
作为一个开发而言,知道每个项目都有其需要维护的配置文件,如果项目量小而言,以人力尚可以接受。项目量一但增多,传统的维护方式就变的困难,所以需要一个统一的配置中心来维护所有服务的配置文件。再言,传统的项目配置文件配置数据发生改变,需要重启服务使其生效,spring cloud config 可以不需要进行重启对应的服务。
多样化仓库
spring cloud config 支持配置文件库除了大家所知的git,svn外,spring cloud config还支持valut,credhub,composite以及本地文件仓库。当你在配置服务中需要可以对配置中心的仓库进行显示配置,不对其配置的话默认仓库是git。
实例搭建
接下来给大家介绍一些spring cloud config 的实例搭建。当前笔者使用的spring boot版本为2.1.3.RELEASE, spring cloud 版本为Greenwich.SR1,与目前多数文章使用的spring boot1.x版本还是有些许不同的。
config-server 模块的构建
pom引入的依赖
1 2 3 4 5 6 7 8 | < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-web</ artifactId > </ dependency > < dependency > < groupId >org.springframework.cloud</ groupId > < artifactId >spring-cloud-config-server</ artifactId > </ dependency > |
只需引入web和config-server的依赖就可以,如果是通过idea进行搭建的话,可以通过spring initializr ,勾选web ,以及spring cloud config 模块自动导入pom依赖
application.properties其中字段的配置
1 2 3 4 5 6 7 8 9 10 11 12 | # 服务名称 spring.application.name=config-server # 启动端口 server.port=7001 # 仓库地址,HTTP方式 spring.cloud.config.server.git.uri=*** # 仓库搜索路径 spring.cloud.config.server.git.search-paths=*** # 访问仓库的用户名 spring.cloud.config.server.git.username=**** # 访问仓库的密码 spring.cloud.config.server.git.password=*** |
大部分配置大家都熟悉,主要介绍几个重要配置,一个是spring.cloud.config.server.git.uri,这个指定了你的git仓库地址,属于必填值,不填的话或者地址错误的话启动失败会报错。username, password根据你的git仓库而言,如果是公共仓库不需要用户及密码不填也可以。其他不填的话也会报错。spring cloud config 除了支持username, password 认证外,也可以通过公私钥文件进行git认证。
1 2 3 4 | spring.cloud.config.server.git.ignore-local-ssh-settings=true spring.cloud.config.server.git.host-key=someHostKey spring.cloud.config.server.git.host-key-algorithm=ssh-rsa spring.cloud.config.server.git.private-key=*** |
使用私钥来代替用户名密码的安全验证,配置案例由官网提供的,不过大部分开发更优先于使用用户名密码配置。
启动类注解
启动一个spring cloud config server端项目只需要加入一个@EnableConfigServer注解就可以了
1 2 3 4 5 6 7 | @EnableConfigServer @SpringBootApplication public class SpringCloudConfigServerApplication { public static void main(String[] args) { SpringApplication.run(SpringCloudConfigServerApplication. class , args); } } |
接下来研究一下@EnableConfigServer这个注解做了什么操作
1 2 3 4 5 6 | @Target ({ElementType.TYPE}) @Retention (RetentionPolicy.RUNTIME) @Documented @Import ({ConfigServerConfiguration. class }) public @interface EnableConfigServer { } |
进入到这个EnableConfigServer类,发现引入了ConfigServerConfiguration类。
1 2 3 4 5 6 7 8 9 10 11 12 13 | @Configuration public class ConfigServerConfiguration { public ConfigServerConfiguration() { } @Bean public ConfigServerConfiguration.Marker enableConfigServerMarker() { return new ConfigServerConfiguration.Marker(); } class Marker { Marker() { } } } |
进入ConfigServerConfiguration类,发现创建了一个内部类Marker,并将其注解成一个bean注入到spring容器。
1 2 3 4 5 6 7 8 9 10 11 12 13 | import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.config.server.config.ConfigServerConfiguration.Marker; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @Configuration @ConditionalOnBean ({Marker. class }) @EnableConfigurationProperties ({ConfigServerProperties. class }) @Import ({EnvironmentRepositoryConfiguration. class , CompositeConfiguration. class , ResourceRepositoryConfiguration. class , ConfigServerEncryptionConfiguration. class , ConfigServerMvcConfiguration. class }) public class ConfigServerAutoConfiguration { public ConfigServerAutoConfiguration() { } } |
在ConfigServerAutoConfiguration类里发现maker这个bean被注入进来,并引进了更多文件,简单的介绍一下各类的作用。
EnvironmentRepositoryConfiguration: 环境变量存储相关的配置类
CompositeConfiguration:组合方式的环境仓库配置类
ResourceRepositoryConfiguration:资源仓库相关的配置类
ConfigServerEncryptionConfiguration:加密断点相关的配置类
ConfigServerMvcConfiguration:对外暴露的MVC端点控制器的配置类
现在主要研究一下EnvironmentRepositoryConfiguration这个类,官方文档中也标明Environment相关为核心类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | @Configuration @EnableConfigurationProperties ({SvnKitEnvironmentProperties. class , CredhubEnvironmentProperties. class , JdbcEnvironmentProperties. class , NativeEnvironmentProperties. class , VaultEnvironmentProperties. class }) @Import ({CompositeRepositoryConfiguration. class , JdbcRepositoryConfiguration. class , VaultRepositoryConfiguration. class , CredhubConfiguration. class , CredhubRepositoryConfiguration. class , SvnRepositoryConfiguration. class , NativeRepositoryConfiguration. class , GitRepositoryConfiguration. class , DefaultRepositoryConfiguration. class }) public class EnvironmentRepositoryConfiguration { public EnvironmentRepositoryConfiguration() { } @Bean @ConditionalOnProperty ( value = { "spring.cloud.config.server.health.enabled" }, matchIfMissing = true ) public ConfigServerHealthIndicator configServerHealthIndicator(EnvironmentRepository repository) { return new ConfigServerHealthIndicator(repository); } @Bean @ConditionalOnMissingBean ( search = SearchStrategy.CURRENT ) public MultipleJGitEnvironmentProperties multipleJGitEnvironmentProperties() { return new MultipleJGitEnvironmentProperties(); } |
代码太长,所以只贴部分代码,从代码中可以了解到他引入了很多的仓库类,说明了spring cloud config
支持以及各种远程库的具体实现。介绍一个git仓库,对于其它仓库,本文就不再叙述
1 2 3 4 5 6 | @Configuration @Profile ({ "git" }) class GitRepositoryConfiguration extends DefaultRepositoryConfiguration { GitRepositoryConfiguration() { } } |
从代码中可以观察到git仓库还继承了默认的仓库配置,证实了spring cloud config默认配置是git。git仓库的实现类JGitEnvironmentRepository,JGitEnvironmentRepository实现类为MultipleJGitEnvironmentProperties。此类做了什么呢
从图中可知获得配置信息。因此,可以总结spring cloud config server启动的时候根据profile值启动对应的环境库去加载和获取配置信息。
启动准备与日志分析
启动之前要在对应的git上创建一个文件,用来存储客户端需要的配置信息。对于读者而言,创建文件轻而易举,所以笔者不做描述,运行启动类。
1 2 3 4 5 6 7 8 9 | . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.1.3.RELEASE) 2019-04-11 15:47:14.912 INFO 25554 --- [ main] s.s.s.SpringCloudConfigServerApplication : No active profile set, falling back to default profiles: default 2019-04-11 15:47:16.742 INFO 25554 --- [ main] o.s.cloud.context.scope.GenericScope : BeanFactory id=02d0bffd-a788-3d22-8702-495331a8c7d5 |
日志并没有什么强调的,阐述的就是根据spring检查你的profile值去生成对应的仓库bean。
主要查看一下Endpoints
从endpoints可以了解到加载bean的数量名称,bean的健康状况,以及适配的访问方式。
列举了常用的几种格式,考虑到开发的习惯不同,所以做了多种展示风格,除了properties格式,还支持yml,yaml,json格式展示。打开浏览器,访问http://localhost:7001/spring-cloud-config-client/dev/master。
可以看到正确获取到git仓库中文件的值,看下启动日志发现一条消息
换一种方式展示,看各个读者喜好。虽spring cloud config 支持properties,yml,yaml,json四种展示格式。读取文件只支持properties,yml,yaml文件格式。
远程仓库更新的时候,server端监听器监听到变化,会进行数据的同步,至此,server端算是告一段落。
spring cloud config client端搭建
pom依赖
创建一个新的模块,pom依赖相比于server端而言,只是从服务端变成client端引用。
spring.application.name与git仓库中文件的名称对应,接下来解释一下为何需要创建bootstrap配置文件。在启动spring boot项目时候,项目会加载application配置文件,而bootstrap文件会优于application加载。
启动分析
client端启动不需要加任何注解,spring boot启动的@configuration里包含了对client的端注解。
这个类的用来读取bootstrap.properties 配置文件信息并通过HTTP方式从server端拉取文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 | . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.1.4.RELEASE) 2019-04-11 17:32:36.501 INFO 26542 --- [ main] c.c.c.ConfigServicePropertySourceLocator : Multiple Config Server Urls found listed. 2019-04-11 17:32:36.502 INFO 26542 --- [ main] c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at : http://localhost:7001 2019-04-11 17:32:37.288 INFO 26542 --- [ main] c.c.c.ConfigServicePropertySourceLocator : Located environment: name=spring-cloud-config-client, profiles=[dev], label=master, version=789bfe0c437dc1676a8b75b7de74dbf13dc7b52a, state=null 2019-04-11 17:32:37.288 INFO 26542 --- [ main] b.c.PropertySourceBootstrapConfiguration : Located property source: CompositePropertySource {name='configService', propertySources=[MapPropertySource {name='configClient'}, MapPropertySource {name='http://gitlab.alibaba-inc.com/wb-wxj443666/spring-cloud-study.git/config/spring-cloud-config-client-dev.properties'}]} 2019-04-11 17:32:37.298 INFO 26542 --- [ main] s.s.s.SpringCloudConfigClientApplication : No active profile set, falling back to default profiles: default 2019-04-11 17 |
刷新按钮点爆了值也不会发生变化。这就问题所在,只有重启client端才能看到最新值,不过这样的话就与预期结果相差太多。接下来就介绍两种动态刷新client端方案。
spring boot actuator执行器刷新
简单介绍一下spring boot actuator,是spring boot项目运行的一个监视器服务,启动项目的endpoints就是由spring boot actuator输出的,包含了对spring boot的bean的监视,健康状况的管理,可以通过/actuator 查看各种项目运行的信息。
首先,在client端的pom文件加入spring boot actuator的引用
这样就实现了动态刷新,但是存在一个问题。每次更新文件都需要子服务手动刷新来获取最新值,服务量一旦过大对于维护以及体验都很糟糕。所以接下来介绍第二种与spring cloud bus搭配实现无需子服务手动刷新即可动态获取服务端最新值。
spring cloud bus组合动态获取server端最新值
spring cloud bus 目前支持者rabbit以及kafka两种MQ,kafka主要针对大数据,读者若想了解spring cloud bus 可以参考这篇文章 spring cloud bus介绍与源码分析所以笔者目前所用的MQ是rabbit,从官方上pull下rabbit docker镜像并启动。目前spring cloud bus动态刷新获取server的最新文件有两种方式,分别是针对client端以及server端的两种方案。
client端
添加pom依赖
之前在bootstrap的配置文件中management.endpoints.web.exposure.include=*暴露了所有endpoint,如果没有配置的话,需要进行配置management.endpoints.web.exposure.include=bus-refresh暴露出bus-refresh节点。
观察下启动日志
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 2019-04-15 15:47:33.198 WARN 35860 --- [ main] o.s.boot.actuate.endpoint.EndpointId : Endpoint ID 'bus-env' contains invalid characters, please migrate to a valid format. 2019-04-15 15:47:33.205 WARN 35860 --- [ main] o.s.boot.actuate.endpoint.EndpointId : Endpoint ID 'bus-refresh' contains invalid characters, please migrate to a valid format. 2019-04-15 15:47:33.961 INFO 35860 --- [ main] o.s.cloud.context.scope.GenericScope : BeanFactory id=baac4e8e-51e6-329d-9818-ccb41443f5f8 2019-04-15 15:47:33.979 INFO 35860 --- [ main] faultConfiguringBeanFactoryPostProcessor : No bean named 'errorChannel' has been explicitly defined. Therefore, a default PublishSubscribeChannel will be created. 2019-04-15 15:47:34.019 INFO 35860 --- [ main] faultConfiguringBeanFactoryPostProcessor : No bean named 'taskScheduler' has been explicitly defined. Therefore, a default ThreadPoolTaskScheduler will be created. 2019-04-15 15:47:34.040 INFO 35860 --- [ main] faultConfiguringBeanFactoryPostProcessor : No bean named 'integrationHeaderChannelRegistry' has been explicitly defined. Therefore, a default DefaultHeaderChannelRegistry will be created. 2019-04-15 15:47:36.006 INFO 35860 --- [ main] o.s.s.c.ThreadPoolTaskScheduler : Initializing ExecutorService 'taskScheduler' 2019-04-15 15:47:36.575 INFO 35860 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor' 2019-04-15 15:47:37.511 INFO 35860 --- [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 22 endpoint(s) beneath base path '/actuator' 2019-04-15 15:47:37.635 INFO 35860 --- [ main] o.s.c.s.m.DirectWithAttributesChannel : Channel 'spring-cloud-config-client-1.springCloudBusInput' has 1 subscriber(s). 2019-04-15 15:47:37.919 INFO 35860 --- [ main] o.s.i.monitor.IntegrationMBeanExporter : Registering MessageChannel errorChannel 2019-04-15 15:47:38.102 INFO 35860 --- [ main] o.s.i.monitor.IntegrationMBeanExporter : Registering MessageChannel springCloudBusInput 2019-04-15 15:47:38.153 INFO 35860 --- [ main] o.s.i.monitor.IntegrationMBeanExporter : Registering MessageChannel nullChannel 2019-04-15 15:47:38.206 INFO 35860 --- [ main] o.s.i.monitor.IntegrationMBeanExporter : Registering MessageChannel springCloudBusOutput 2019-04-15 15:47:38.231 INFO 35860 --- [ main] o.s.i.monitor.IntegrationMBeanExporter : Registering MessageHandler errorLogger 2019-04-15 15:47:38.266 INFO 35860 --- [ main] o.s.i.monitor.IntegrationMBeanExporter : Registering MessageHandler org.springframework.cloud.stream.binding.StreamListenerMessageHandler@124d26ba 2019-04-15 15:47:38.358 INFO 35860 --- [ main] o.s.i.endpoint.EventDrivenConsumer : Adding {logging-channel-adapter:_org.springframework.integration.errorLogger} as a subscriber to the 'errorChannel' channel 2019-04-15 15:47:38.359 INFO 35860 --- [ main] o.s.i.channel.PublishSubscribeChannel : Channel 'spring-cloud-config-client-1.errorChannel' has 1 subscriber(s). 2019-04-15 15:47:38.359 INFO 35860 --- [ main] o.s.i.endpoint.EventDrivenConsumer : started _org.springframework.integration.errorLogger 2019-04-15 15:47:43.762 INFO 35860 --- [ main] c.c.c.ConfigServicePropertySourceLocator : Located environment: name=spring-cloud-config-client, profiles=[dev], label=master, version=f249fd7d30cd33be291ab1c0d82b6047f6695a4c, state=null 2019-04-15 15:47:43.763 INFO 35860 --- [ main] b.c.PropertySourceBootstrapConfiguration : Located property source: CompositePropertySource {name='configService', propertySources=[MapPropertySource {name='configClient'}, MapPropertySource {name='http://gitlab.alibaba-inc.com/wb-wxj443666/spring-cloud-study.git/config/spring-cloud-config-client-dev.properties'}]} 2019-04-15 15:47:43.975 INFO 35860 --- [ main] o.s.a.r.c.CachingConnectionFactory : Attempting to connect to: [localhost:8088] 2019-04-15 15:47:44.109 INFO 35860 --- [ main] o.s.a.r.c.CachingConnectionFactory : Created new connection: rabbitConnectionFactory#6f2e1024:0/SimpleConnection@74ba6ff5 [delegate=amqp://guest@127.0.0.1:8088/, localPort= 64876] 2019-04-15 15:47:44.227 INFO 35860 --- [ main] o.s.c.s.m.DirectWithAttributesChannel : Channel 'spring-cloud-config-client-1.springCloudBusOutput' has 1 subscriber(s). 2019-04-15 15:47:44.263 INFO 35860 --- [ main] c.s.b.r.p.RabbitExchangeQueueProvisioner : declaring queue for inbound: springCloudBus.anonymous.3hRrK5n5R_Cf4QbeWTpCTQ, bound to: springCloudBus 2019-04-15 15:47:44.318 INFO 35860 --- [ main] o.s.i.monitor.IntegrationMBeanExporter : Registering MessageChannel springCloudBus.anonymous.3hRrK5n5R_Cf4QbeWTpCTQ.errors 2019-04-15 15:47:44.442 INFO 35860 --- [ main] o.s.c.stream.binder.BinderErrorChannel : Channel 'spring-cloud-config-client-1.springCloudBus.anonymous.3hRrK5n5R_Cf4QbeWTpCTQ.errors' has 1 subscriber(s). 2019-04-15 15:47:44.443 INFO 35860 --- [ main] o.s.c.stream.binder.BinderErrorChannel : Channel 'spring-cloud-config-client-1.springCloudBus.anonymous.3hRrK5n5R_Cf4QbeWTpCTQ.errors' has 2 subscriber(s). 2019-04-15 15:47:44.512 INFO 35860 --- [ main] o.s.i.a.i.AmqpInboundChannelAdapter : started inbound.springCloudBus.anonymous.3hRrK5n5R_Cf4QbeWTpCTQ 2019-04-15 15:47:44.650 INFO 35860 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 2019-04-15 15:47:44.654 INFO 35860 --- [ main] s.s.s.SpringCloudConfigClientApplication : Started SpringCloudConfigClientApplication in 26.703 seconds (JVM running for 32.515) |
了解到spring cloud bus已经正常运行了。同理,可以在写一个client模块也用同样方式去启动,
笔者让client second端指定的配置文件为spring-cloud-config-client-second-dev.properties ,相应的在git仓库创建这个文件。
只发送了一次请求,可以看到所有子服务都更新了。如果只想一次刷新部分子服务怎办呢,
client ip:port/actuator/destination=param 的请求就可以, http://127.0.0.1:8080/actuator/bus-refresh/destination=customers:7005,比如这个post请求指定了只更新port等于7005的服务项目。
接下来介绍第二种改变server端来实现子服务动态刷新获取最新值的方法。
server端
在现有client端都加入了spring cloud bus 的基础上。server端和client类似,也加入一样的依赖和配置消息。唯一的差异应该在localhost:7001/actuator/bus-refresh post请求上,server端只需向自己发送一个post请求,就可以让所有子服务获取最新的文件。
这两种解决方案没多大区别,孰优孰劣也没法确定,看个人使用喜好吧。
spring cloud config的高可用配置
关于网上所用的大部分spring cloud config高可用都是通过和eureka搭配使用,让其成为一项服务注册上去。从笔者而言,因为eureka2之后的版本闭源,放弃维护后。现在很多企业也不愿采用eureka,而选择继续维护的zoomkeeper。spring zoomkepper集成了自己的配置中心,不需要专门去构建spring cloud config,所以目前用eureka去做spring cloud config高可用的话有些鸡肋,不过针对目前部分企业还在使用eureka暂时无法更换注册中心的话,还是有存在必要的,不过笔者更推荐第二种更改客户端配置实现高可用。
修改client端配置文件实现高可用
spring cloud config 在2之后的版本已经支持高可用了,只需要更改客户端的spring.cloud.config.uri=http://localhost:7001, http://localhost:7002就可以。启动和运行时会自动扫描所配置的server端是否可用,如果第一个无法使用就继续扫描下一个,直到可用为止。
1 2 3 4 5 | main] c.c.c.ConfigServicePropertySourceLocator : Multiple Config Server Urls found listed. 2019-04-15 15:47:28.824 INFO 35860 --- [ main] c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at : http://localhost:7001 2019-04-15 15:47:29.197 INFO 35860 --- [ main] c.c.c.ConfigServicePropertySourceLocator : Connect Timeout Exception on Url - http://localhost:7001. Will be trying the next url if available 2019-04-15 15:47:29.197 INFO 35860 --- [ main] c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at : http://localhost:7002 2019-04-15 15:47:32.274 INFO 35860 --- [ main] c.c.c.ConfigServicePropertySourceLocator : Located environment: name=spring-cloud-config-client, profiles=[dev], label=master, version=f249fd7d30cd33be291ab1c0d82b6047f6695a4c, state=null |
从日志可以确认到client端启动时候的server端选择,相比erueka服务注册, 更简洁也无需更多的配置。