演示地址:http://139.196.87.48:9002/kittyhtml
用户名:admin 密码:admin前端
现在微服务架构盛行,在分布式系统中,项目日益庞大,子项目日益增多,每一个项目都散落着各类配置文件,且随着服务的增长而不断增多。此时,每每某一个基础服务信息变动,都会致使一系列服务的更新和重启,运维也是苦不堪言,并且还很容易出错。因而,配置中心便由此应运而生了。java
目前市面上开源的配置中心有不少,像Spring家族的Spring Cloud Config, Apache的Apache Commons Configuration,淘宝的diamond, 百度的disconf, 360的QConf等等,都是为了解决这类问题。当下Spring体系大行其道,咱们固然也优先选择Spring Cloud Config了。git
Spring Cloud Config 是一套为分布式系统中的基础设施和微服务应用提供集中化配置的管理方案,它分为服务端与客户端两个部分。服务端也称为分布式配置中心,它是一个独立的微服务应用,用来链接配置仓库并为客户端提供获取配置信息。客户端则是微服务架构中的各个微服务应用或基础设施,它们经过指定的配置中心来管理服务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息。web
Spring Cloud Config对服务端和客户端中的环境变量和属性配置 实现了抽象映射,因此它除了适用于 Spring 应用,也是能够在任何其余语言应用中使用的。Spring Cloud Config 实现的配置中心默认采用 Git 来存储配置信息,因此使用 Spring Cloud Config 构建的配置服务器,自然就支持对微服务应用配置信息的版本管理,而且能够经过 Git 客户端工具很是方便的管理和访问配置内容。固然它也提供了对其余存储方式的支持,好比:SVN 仓库、本地化文件系统等。spring
首先在GIT下,新建config-repo目录,用来存放配置文件,以下图所示,分别模拟了三个环境的配置文件。docker
分别编辑三个文件,配置 comsumer.hello 属性的值为 comsumer.hello=hello, this is xx configurations.bootstrap
新建 kitty-conifg 工程,做为配置中心的服务端,负责把GIT仓库的配置文件发布为RESTFul接口。后端
除了Spring Cloud依赖以外,添加配置中心依赖包。跨域
pom.xml
<!--spring config--> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> </dependencies>
启动类添加注解 @EnableConfigServer,开启配置服务支持。
KittyConfigApplication.java
package com.louis.kitty.config; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.config.server.EnableConfigServer; @EnableConfigServer @EnableDiscoveryClient @SpringBootApplication public class KittyConfigApplication { public static void main(String[] args) { SpringApplication.run(KittyConfigApplication.class, args); } }
修改配置文件,添加以下内容。若是是私有仓库须要填写用户名密码,若是是公开仓库,能够不配置密码。
application.yml
server: port: 8020 spring: application: name: kitty-config cloud: consul: host: localhost port: 8500 discovery: serviceName: ${spring.application.name} # 注册到consul的服务名称 config:
label: master # git仓库分支 server: git: uri: https://gitee.com/liuge1988/kitty.git # 配置git仓库的地址 search-paths: config-repo # git仓库地址下的相对地址,能够配置多个,用,分割。 username: username # git仓库的帐号 password: password # git仓库的密码
Spring Cloud Config也提供本地存储配置的方式,只需设置属性spring.profiles.active=native
,Config Server会默认从应用的src/main/resource
目录下检索配置文件。另外也能够经过spring.cloud.config.server.native.searchLocations=file:D:/properties/
属性来指定配置文件的位置。虽然Spring Cloud Config提供了这样的功能,可是为了更好的支持内容管理和版本控制,仍是比较推荐使用GIT的方式。
启动注册中心,配置中心,访问 http://localhost:8020/kitty-consumer/dev,返回结果以下。
{ "name": "kitty-consumer", "profiles": ["dev"], "label": null, "version": "1320259308dfdf438f5963f95cbce9e0a76997b7", "state": null, "propertySources": [{ "name": "https://gitee.com/liuge1988/kitty.git/config-repo/kitty-consumer-dev.properties", "source": { "consumer.hello": "hello, this is dev configurations. " } }] }
访问 http://localhost:8020/kitty-consumer/pro,返回结果以下。
{ "name": "kitty-consumer", "profiles": ["pro"], "label": null, "version": "1320259308dfdf438f5963f95cbce9e0a76997b7", "state": null, "propertySources": [{ "name": "https://gitee.com/liuge1988/kitty.git/config-repo/kitty-consumer-pro.properties", "source": { "consumer.hello": "hello, this is pro configurations. " } }] }
上述的返回的信息包含了配置文件的位置、版本、配置文件的名称以及配置文件中的具体内容,说明server端已经成功获取了git仓库的配置信息。
访问:http://localhost:8020/kitty-consumer-dev.properties,返回结果以下。
修改一下dev配置文件内容以下(末尾加了一个 2):
再次访问:http://localhost:8020/kitty-consumer-dev.properties,返回结果以下。
发现读取的是修改后提交的信息,说明服务端会自动读取最新提交的数据。
仓库中的配置文件会被转换成相应的WEB接口,访问能够参照如下的规则:
以kitty-consumer-dev.properties为例子,它的application是kitty-consumer,profile是dev。client会根据填写的参数来选择读取对应的配置。
打开kitty-consumer工程,添加相关依赖。
pom.xml
<!-- spring-cloud-config --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency>
添加一个bootstrap.yml配置文件,添加配置中心,并把注册中心的配置移到这里,由于在经过配置中心查找配置时须要经过注册中心的发现服务。
bootstrap.yml
spring: cloud: consul: host: localhost port: 8500 discovery: serviceName: ${spring.application.name} # 注册到consul的服务名称 config: discovery: enabled: true # 开启服务发现 serviceId: kitty-config # 配置中心服务名称 name: kitty-consumer # 对应{application}部分 profile: dev # 对应{profile}部分 label: master # 对应git的分支,若是配置中心使用的是本地存储,则该参数无用
配置说明:
特别注意:
上面这些与spring cloud相关的属性必须配置在bootstrap.yml中,这样config部份内容才能被正确加载。
由于config的相关配置会先于application.yml,而bootstrap.yml的加载也是先于application.yml文件的。
application.yml
server: port: 8005 spring: application: name: kitty-consumer boot: admin: client: url: "http://localhost:8000" zipkin: base-url: http://localhost:9411/ sleuth: sampler: probability: 1 #样本采集量,默认为0.1,为了测试这里修改成1,正式环境通常使用默认值 # 开放健康检查接口 management: endpoints: web: exposure: include: "*" endpoint: health: show-details: ALWAYS #开启熔断器 feign: hystrix: enabled: true
添加一个 SpringConfigController 控制器, 添加注解 @Value("${comsumer.hello}"),声明hello属性从配置文件读取。
SpringConfigController.java
package com.louis.kitty.consumer.controller; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController class SpringConfigController { @Value("${comsumer.hello}") private String hello; @RequestMapping("/hello") public String from() { return this.hello; } }
启动注册中心、配置中心,访问: http://localhost:8500,确认相关服务启动并注册。
访问 http://localhost:8005/hello,返回结果。
说明客户端已经成功从服务端读取了配置信息。
如今手动修改一下仓库配置文件的内容,移除末尾数字 2,修改完成并提交。
再次访问 http://localhost:80052/hello,效果以下。
咱们发现返回结果并无读取最新提交的内容,这是由于Spring Boot项目只有在启动的时候才会获取配置文件的内容,虽然GIT配置信息被修改了,可是客户端并无从新去获取,因此致使读取的信息仍然是旧配置。那么该如何去解决这个问题呢?这就是咱们下一章要讲的 Spring Cloud Bus。
咱们在上面讲到,Spring Boot程序只在启动的时候加载配置文件信息,这样在GIT仓库配置修改以后,虽然配置中心服务器可以读取最新的提交信息,可是配置中心客户端却不会从新读取,以致于不能及时的读取更新后的配置信息。这个时候就须要一种通知刷新机制来支持了。
refresh机制是Spring Cloud Config提供的一种刷新机制,它容许客户端经过POST方法触发各自的/refresh,只要依赖spring-boot-starter-actuator包就拥有了/refresh的功能,下面咱们为咱们的客户端加上刷新功能,以支持更新配置的读取。
咱们的 kitty-consumer 在以前已经添加过actuator依赖,因此这里就不用添加了,若是以前没有添加须要加上。actuator是健康检查依赖包,依赖包里携带了 /refresh 的功能。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
在使用配置属性的类型加上 @RefreshScope 注解,这样在客户端执行 /refresh 的时候就会刷新此类下面的配置属性了。
package com.louis.kitty.consumer.controller; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RefreshScope @RestController class SpringConfigController { @Value("${consumer.hello}") private String hello; @RequestMapping("/hello") public String from() { return this.hello; } }
健康检查接口开放须要在配置文件添加如下内容,开放refresh的相关接口,由于这个咱们在以前也配置过了,因此也不需添加了。
management:
endpoints:
web:
exposure:
include: "*"
经过上面的接口开放配置,之后以post请求的方式访问 http://localhost:8005/actuator/refresh 时,就会更新修改后的配置文件了。
特别注意:
这里存在着版本大坑,1.x跟2.x的配置不太同样,咱们用的是2.0+版本,务必注意。
1.安全配置变动
新版本
management.endpoints.web.exposure.include="*"
老版本
management.security.enabled=false
2.访问地址变动
新版本
http://localhost:8005/actuator/refresh
老版本
http://localhost:8005/refresh
这里仍是解释一下上面这个配置起到了什么具体做用,其实actuator是一个健康检查包,它提供了一些健康检查数据接口,refresh功能也是其中的一个接口,可是为了安全起见,它默认只开放了health和info接口(启动信息会包含以下图所示信息),而上面的配置就是设置要开放哪些接口, 咱们设置成 “*”,是开放全部接口。你也能够指定开发几个,好比: health,info,refresh,而这里由于咱们须要用的refresh功能,因此须要把refresh接口开放出来。
设置成 “*” 后,启动信息会包含如下信息,而这个叫refresh的post方法,就是咱们须要的,上面说的接口地址变动从这里也能够看得出来。
从新启动服务,访问 http://localhost:8005/hello,返回结果以下。
修改仓库配置内容,末尾加个数字 5,以下图所示。
再次访问 http://localhost:8005/hello,如咱们所料,结果并无更新,由于咱们尚未调refresh方法。
经过工具或自写代码发送post请求 http://localhost:8005/actuator/refresh,刷新配置。
这里经过在线测试网站发送,地址:https://getman.cn/Mo2FX 。
注意:先让你的Chrome支持跨域。设置方法:在快捷方式的target后加上 --disable-web-security --user-data-dir,重启便可。
刷新以后,再次访问 http://localhost:8005/hello,返回结果以下。
查看返回结果,刷新以后已经能够获取最新提交的配置内容,可是每次都须要手动刷新客户端仍是很麻烦,若是客户端数量一多就简直难以忍受了,有没有什么比较好的办法来解决这个问题呢,那是固然的,答案就是:Spring Cloud Bus。
Spring Cloud Bus,被你们称为消息总线,它经过轻量级的消息代理来链接各个分布的节点,能够利用像消息队列的广播机制在分布式系统中进行消息传播,经过消息总线能够实现不少业务功能,其中对于配置中心客户端刷新,就是一个很是典型的使用场景。
下面这张图能够很好的解释消息总线的做用流程(图片描述来源:纯洁的微笑:配置中心博文)。
Spring Cloud Bus 进行配置更新步骤以下:
一、提交代码触发post请求给/actuator/bus-refresh
二、server端接收到请求并发送给Spring Cloud Bus
三、Spring Cloud bus接到消息并通知给其它客户端
四、其它客户端接收到通知,请求Server端获取最新配置
五、所有客户端均获取到最新的配置
由于咱们须要用到消息队列,咱们这里选择RabbitMQ,使用Docker进行安装。
执行如下命令,拉取镜像。
docker pull rabbitmq:management
完成以后执行如下命令查看下载镜像。
docker images
执行如下命令,建立docker容器。
docker run -d --name rabbitmq -p 5671:5671 -p 5672:5672 -p 4369:4369 -p 25672:25672 -p 15671:15671 -p 15672:15672 rabbitmq:management
启动成功以后,能够执行如下命令查看启动容器。
docker ps
容器启动以后就能够访问web管理界面了,访问 http://宿主机IP:15672。
系统提供了默认帐号。 用户名:guest 密码: guest
管理界面
打开客户端 kitty-consumer,添加消息总线相关依赖。
pom.xml
<!-- bus-amqp --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency>
修改配置,添加RebbitMq的相关配置,这样客户端代码就改造完成了。
bootstrap.yml
spring: cloud: consul: host: localhost port: 8500 discovery: serviceName: ${spring.application.name} # 注册到consul的服务名称 config: discovery: enabled: true # 开启服务发现 serviceId: kitty-config # 配置中心服务名称 name: kitty-consumer # 对应{application}部分 profile: dev # 对应{profile}部分 label: master # 对应git的分支,若是配置中心使用的是本地存储,则该参数无用 rabbitmq: host: localhost port: 5672 username: guest password: guest
修改 kitty-conifg,添加相关依赖。
pom.xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency>
修改配置,添加RabbitMq的和接口开放相关配置,这样服务端代码也改造完成了。
application.yml
server: port: 8020 spring: application: name: kitty-config cloud: consul: host: localhost port: 8500 discovery: serviceName: ${spring.application.name} # 注册到consul的服务名称 config: label: master # git仓库分支 server: git: uri: https://gitee.com/liuge1988/kitty.git # 配置git仓库的地址 search-paths: config-repo # git仓库地址下的相对地址,能够配置多个,用,分割。 username: username # git仓库的帐号 password: password # git仓库的密码 rabbitmq: host: localhost port: 5672 username: guest password: guest management: endpoints: web: exposure: include: "*"
1.启动服务端,成功集成消息总线后,启动信息中能够看到以下图中的信息。
2.启动客户端,发现竟然报错了,网上也找不到相关资料,也没见其余人提过相关问题。猜想是网上教程可能是使用Euraka,而这里用的时Consul,瞎鼓捣了很久,反正是不想换回Euraka,2.0中止开发消息出来之后,未来还不定什么状况,只能硬着头皮解决了。
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'configServerRetryInterceptor' available at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:685) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1210) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:291) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:204) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE] at org.springframework.retry.annotation.AnnotationAwareRetryOperationsInterceptor.getDelegate(AnnotationAwareRetryOperationsInterceptor.java:180) ~[spring-retry-1.2.2.RELEASE.jar:na] at org.springframework.retry.annotation.AnnotationAwareRetryOperationsInterceptor.invoke(AnnotationAwareRetryOperationsInterceptor.java:151) ~[spring-retry-1.2.2.RELEASE.jar:na] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) ~[spring-aop-5.0.8.RELEASE.jar:5.0.8.RELEASE] at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688) ~[spring-aop-5.0.8.RELEASE.jar:5.0.8.RELEASE] at org.springframework.cloud.config.client.ConfigServerInstanceProvider$$EnhancerBySpringCGLIB$$dd44720b.getConfigServerInstances(<generated>) ~[spring-cloud-config-client-2.0.0.RELEASE.jar:2.0.0.RELEASE] at org.springframework.cloud.config.client.DiscoveryClientConfigServiceBootstrapConfiguration.refresh(DiscoveryClientConfigServiceBootstrapConfiguration.java:84) [spring-cloud-config-client-2.0.0.RELEASE.jar:2.0.0.RELEASE] at org.springframework.cloud.config.client.DiscoveryClientConfigServiceBootstrapConfiguration.startup(DiscoveryClientConfigServiceBootstrapConfiguration.java:69) [spring-cloud-config-client-2.0.0.RELEASE.jar:2.0.0.RELEASE] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_131] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_131] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_131] at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_131]
而后就跟踪代码,发现是在下图中的位置找不到相应的Bean,那么答案就比较明显了,要么是程序有BUG,不过可能性不大,那应该是就是缺包了,在缺失的包里有这个Bean。可是这个Bean是在哪一个包?排查了半天也没找到,网上也没有想过资料,对比了一下网上消息总线的配置,依赖也没有少加什么。
没有办法,最后只能本身上手了,不就是在刷新的时候缺乏一个拦截器吗,本身给他弄一个试试呗。
使用就加了一个配置类,并在resources下新建了META-INF目录和一个spring。factories文件。
RetryConfiguration.java
package com.louis.kitty.consumer; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.retry.interceptor.RetryInterceptorBuilder; import org.springframework.retry.interceptor.RetryOperationsInterceptor; public class RetryConfiguration { @Bean @ConditionalOnMissingBean(name = "configServerRetryInterceptor") public RetryOperationsInterceptor configServerRetryInterceptor() { return RetryInterceptorBuilder.stateless().backOffOptions(1000, 1.2, 5000).maxAttempts(10).build(); } }
spring.factories
org.springframework.cloud.bootstrap.BootstrapConfiguration=com.louis.kitty.consumer.RetryConfiguration
在这里指定新建的拦截器,这样系统初始化时会加载这个Bean。
而后重启启动,果真没有报错了,仍是先别高兴,看看能不能用先。
4.先访问一下 http://localhost:8005/hello,效果以下图所示。
5.修改仓库配置文件,把数字5改为15,修改完成提交。
再次访问发现仍是旧信息。
6.再用工具发送post请求 http://localhost:8020/actuator/bus-refresh 。
注意此次是向注册中心服务端发送请求,发送成功以后服务端会经过消息总线通知全部的客户端进行刷新。
另外开启消息总线后的请求地址是 /actuator/bus-refresh,再也不是refresh了。
7.给服务端发送刷新请求以后,再次访问 http://localhost:8005/hello,结果以下(须要一点刷新时间)。
咱们愉快的发现客户端已经可以经过消息总线获取最新配置了。
后端:https://gitee.com/liuge1988/kitty
前端:https://gitee.com/liuge1988/kitty-ui.git
做者:朝雨忆轻尘
出处:https://www.cnblogs.com/xifengxiaoma/ 版权全部,欢迎转载,转载请注明原文做者及出处。