对于一个微服务架构的应用来说,每一个服务都有自己的一个配置文件,当微服务的数量比较多时,如果修改一些公用的配置,则需要将所有的配置文件都单独进行修改,这对运维人员来说是非常耗时耗力的。那么有没有一种方法,可以将众多微服务中公用的配置信息抽取出来,放到一个地方集中管理,然后各个微服务再去这个地方拉取对应的配置信息,达到公用配置统一管理的目的。所以配置中心就出现了。
互联网行业常用的配置中心有SpringCloud Config、Alibaba Nacos、携程的Apollo等,这里只说明Config的使用方式。
SpringCloud Config采用C/S架构,为微服务架构中的微服务提供集中化的外部配置支持,分为服务端和客户端。官方文档:
https://cloud.spring.io/spring-cloud-static/spring-cloud-config/2.2.1.RELEASE/reference/html/
服务端:也称分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息的访问接口。配置服务器默认采用git来储存配置信息,这样有助于对环境配置进行版本控制,并且可以通过git客户端工具来方便的管理和访问配置内容。而Config Server只需要和git配置服务器连接就行了。
客户端:需要获取配置的各个微服务,在启动的时候从服务端获取指定的配置信息。
因为Config Server默认采用git来储存配置信息,所以此处选择github作为储存服务器,并与之整合。
第一步:环境准备
首先,在github上新建springcloud-config仓库并创建如下文件:
注意:一般情况下,我们都是使用application-profile.yml/properties的方式命名配置文件!
配置文件中书写一下测试内容:
#dev环境
config:
info: "master branch,springcloud-config/appname-dev.yml version=1"
#测试环境
config:
info: "master branch,springcloud-config/appname-test.yml version=1"
#生产环境
config:
info: "master branch,springcloud-config/appname-prod.yml version=1"
然后,从github上下载刚才创建好的仓库,命令(SSH方式):
git clone [email protected]:XXXXXX/springcloud-config.git
第二步:创建Config Server模块
pom:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-config-serverartifactId>
dependency>
application.yml:
server:
port: 3344
spring:
application:
name: cloud-config-server
cloud:
config:
server:
git:
#配置第一步中创建的仓库地址(http方式)
uri: https://github.com/dengbuquan/springcloud-config.git
#搜索目录
search-paths:
- springcloud-config
#读取分支
label: master
#注册进Eureka
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
主启动类:
@SpringBootApplication
@EnableEurekaClient
@EnableConfigServer
public class ConfigServerMain3344 {
public static void main(String[] args) {
SpringApplication.run(ConfigServerMain3344.class,args);
}
}
第三步:启动Config Server模块,在浏览器中输入http://localhost:3344/master/appname-dev.yml,得到:
config:
info: master branch,springcloud-config/appname-dev.yml version=1
更换请求地址:http://localhost:3344/master/appname-test.yml,得到:
config:
info: master branch,springcloud-config/appname-test.yml version=1
测试成功!!!,此时Config Server已经和线上的配置服务器连通,可以正常获得线上仓库中的配置文件信息了。
下一步我们要做的就是在Config Client中连通Config Server,将Config Server从线上获得的信息整合成Client服务的配置文件。
注意:在第三步中,请求的路径是有规定的,可供使用的有一下五种:
但是比较推荐的还是第三种,可以指明分支、文件名称以及环境信息。
第一步:新建Config Client模块
pom:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-configartifactId>
dependency>
bootstrap.yml:
server:
port: 3355
spring:
application:
name: config-client
cloud:
config:
#代表从http://localhost:3344/master/appname-dev.yml(properties)获取配置信息
uri: http://localhost:3344
label: master
name: appname
profile: dev
#注册进Eureka
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka
bootstrap.yml和application.yml:
因为我们要从配置中心获取通用的配置信息,在SpringCloud中,默认采用bootstrap作为云配置文件的名称,它的加载优先级比application.yml高,两个配置文件共同生效,application.yml中通常配置一些定制化的配置信息,当两个文件中的内容有冲突时,以bootstrap中的为准。
bootstrap的图标和application.yml不一样,是一朵云,代表“云配置”。
主启动类:
@SpringBootApplication
@EnableEurekaClient
//此处不需要开启任何Config的功能支持
public class ConfigClientMain3355 {
public static void main(String[] args) {
SpringApplication.run(ConfigClientMain3355.class,args);
}
}
测试用业务类:
@RestController
public class ConfigController {
//将配置文件中的config.info绑定给configInfo变量
//因为bootstrap.yml中没有显式定义config.info信息,如果绑定成功,代表云获取成功
@Value("${config.info}")
private String configInfo;
@GetMapping("/configInfo")
public String getConfigInfo(){
return configInfo;
}
}
第二步:启动3355模块,访问 http://localhost:3355/configInfo ,得到:
master branch,springcloud-config/appname-dev.yml version=1
测试成功,此时代表着,远程仓库中的配置信息已经被应用到了Config Client中了。
在实际开发中,配置文件中值是用来初始化bean的,只有在初始化的时候才能够把配置文件中的值应用到各个组件中。但是有一些值在运行过程中可能会发生变化,这时要在不重启服务的前提下使得最新的配置生效,就用到了配置的动态刷新功能。
首先,我们要知道Config Client→→Config Server→→github的同步时机:
Config Server本身是不保存github中配置文件的信息的,Config Server每一次收到外界关于配置信息的请求时,都会即时的向github发送获取信息的请求,所以Config Server每一次获取到的配置信息都是最新的。但是,Config Client只会在启动的时候才会向Config Server发送获取配置的请求,然后利用这些信息初始化bean。如果在Config Client运行期间,远端的配置信息发生了变化,因为所有的bean已经初始化完成,那么Config Client是没有办法及时更新自己的配置信息的。如果想要使用最新的配置,必须要重新启动Config Client,触发bean的初始化。但是当Config Client比较多,或者启动比较耗时时,重新启动Config Client显然是不可取的。
为了解决Config Client无法动态刷新配置的问题,Config给我们提供一套解决方案,可以帮助Config Client快速的应用最新配置,而不用重新启动。
第一步:修改3355模块
pom:添加actuator监控
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
yml:添加actuator配置,bootstrap.yml和application.yml都行
management:
endpoints:
web:
exposure:
include: "*"
第二步:预估程序运行期间需要重新初始化的bean,在@Controller类上或者注入bean的方法上使用@RefreshScope注解,表示此bean可以在程序运行时重新初始化,并重新装配所有依赖它的组件。
注意:这一步关键在于要预估那些bean需要在程序运行期间重新初始化,对代码有一定的侵入性。对于那些没有标注此注解的bean来讲,想要应用最新的配置信息,就只能重新启动服务了。
@RestController
//此控制器可以被动态的刷新,在运行期间重新绑定配置文件中的值
@RefreshScope
public class ConfigController {
@Value("${config.info}")
private String configInfo;
@GetMapping("/configInfo")
public String getConfigInfo(){
return configInfo;
}
}
第三步:发送post请求,刷新3355(这里使用curl)。相当于通知actuator拉取最新的配置并执行刷新操作,重新初始化@RefreshScope的Bean并重新装配依赖它们的组件。
curl -X POST "http://localhost:3355/actuator/refresh"
当需要刷新的客户端比较多时,可以编写一个脚本文件,批量执行刷新操作。
在Spring Cloud Config配置当中,如果我们需要让ConfigClient使用最新的配置,必须要手动的向每一个ConfigClient发送POST请求,虽然我们可以通过书写批处理文件来应对客户端较多的情况,但是归根节点还是对每一个客户端都发送了POST请求。
那么有没有一种方式,可以让我们只通知一个节点,然后让所有的节点自动的执行刷新呢?
Spring Cloud Bus就是这样的一门技术,它可以将分布式系统的微服务与消息中间件连接到一起,利用消息中间件完成消息的扩散,类似于广播技术。将Spring Cloud Bus和Spring Cloud Config配合使用,我们就可以达到通知一个节点,所有节点自动刷新的效果。
什么是消息总线:
在微服务系统中,通常会利用一些轻量级的消息中间件来构建一个公用的消息主题,并让系统中的所有微服务实例都连接到此主题,由于该主题产生的消息会被所有的实例监听和消费,所以称它为消息总线。可以通过总线上的任何一个实例方便地广播一些需要其他实例都知道的消息。
基本原理:
将Spring Cloud Bus安装到每一个微服务当中,并进行相关配置,微服务启动之后,在MQ服务器中就会创建一个默认为springCloudBus的topic,对于此topic来讲,每一个微服务实例既是生产者也持久订阅者,可以发布消息,也可以接收消息。
目前为止,Bus仅支持RabbitMQ和Kafka两种消息中间件,本次整合演示使用RabbitMQ。
第一步:安装Erlang
http://erlang.org/download/otp_win64_21.3.exe
第二步:安装RabbitMQ
https://dl.bintray.com/rabbitmq/all/rabbitmq-server/3.7.14/rabbitmq-server-3.7.14.exe
第三步:配置RabbitMQ可视化插件
使用DOS进入RabbitMQ的安装目录下的sbin目录,例如:D:\RabbitMQ\rabbitmq_server-3.7.14\sbin,
然后执行命令:rabbitmq-plugins enable rabbitmq_management
然后就可以在菜单中看到RabbitMQ的快捷操作插件了,可以方便我们启动或者关闭RabbitMQ。
第四步:访问RabbitMQ的控制台,http://localhost:15672,如果得到登录页面,代表RabbitMQ安装和启动成功。(安装成功后自动启动)
第五步:登录RabbitMQ,默认的账号密码均为guest,登录成功之后就可以进入RabbitMQ的主页面。
环境搭建:
在Spring Cloud Config搭建的案例基础上,拷贝cloud-config-client3355,新建一个cloud-config-client3366,这样就有了一个配置中心cloud-config-server3344,两个客户端cloud-config-client3355、cloud-config-client3366。接下来完成Bus和Config的整合:
第一步:启动RabbitMQ,Bus依赖RabbitMQ,必须优先启动它
第二步:修改配置中心cloud-config-server3344
pom:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-bus-amqpartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
yml:
#增加下面的配置
spring:
#配置RabbitMQ服务器地址和用户名密码
rabbitmq:
host: localhost
#RabbitMQ默认的JMS服务端口
port: 5672
username: guest
password: guest
#actuator配置
management:
endpoints:
web:
exposure:
include: "bus-refresh"
第三步:客户端cloud-config-client3355和cloud-config-client3366
pom:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-bus-amqpartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
yml:
#增加下面的配置
spring:
#配置RabbitMQ服务器地址和用户名密码
rabbitmq:
host: localhost
#RabbitMQ默认的JMS服务端口
port: 5672
username: guest
password: guest
#actuator配置
management:
endpoints:
web:
exposure:
#这里使用*,表示暴露所有端点,既能够实现手动刷新,又能够实现自动刷新
include: "*"
第四步:依次启动EurekaServer、配置中心cloud-config-server3344,两个客户端cloud-config-client3355、cloud-config-client3366。
第五步:访问RabbitMQ的控制台,http://localhost:15672,并登录,在Exchanges选项卡下可以看到有一个名为springCloudBus的topic产生了。
此时代表Bus整合成功,各个微服务已经与RabbitMQ连接到了一起,并共同订阅springCloudBus主题,并且可以向此主题发送消息。
第六步:完成上述几步后,就可以开始进行测试了,在github上更新配置文件,然后访问: http://localhost:3344/master/appname-dev.yml ,此时配置中心的配置会立刻得到更新,接着访问 http://127.0.0.1:3355/configInfo 和 http://127.0.0.1:3366/configInfo (客户端设置的测试接口),发现消息没有得到更新。
这是因为我们还没有刷新配置。接下来使用Bus为我们提供的自动刷新接口,向任意一个微服务实例发送post请求:
//bus-refresh是Bus为我们提供的自动刷新的接口,不可自定义,并且要在微服务配置中暴露此端点
//向配置中心发送请求
curl -X POST "http://localhost:3344/actuator/bus-refresh"
//或者向客户端发送请求:
curl -X POST "http://localhost:3355/actuator/bus-refresh"
curl -X POST "http://localhost:3366/actuator/bus-refresh"
发送成功之后再次请求 http://127.0.0.1:3355/configInfo 和 http://127.0.0.1:3366/configInfo,就可以得到最新的配置了,和github同步。
注意:因为每一个微服务实例都是springCloudBus主题的订阅者和生产者,所以我们对哪一个微服务实例发送post请求都是可以的,只要他们暴露了bus-refresh端点。但是为了保证单一职能原则,我们一般不向业务模块发送post请求,而是想配置中心发送。
通过上面的配置,我们已经完成了Config和Bus的整合,实现了一次post请求完成全部的刷新,是不是很方便,但是目前还只能进行全部的刷新,如果我们只想更新部分的微服务怎么办呢?
Bus给我们提供了定点广播的方式,可以在发送post请求时指定具体的微服务实例,来控制哪些微服务刷新,哪些不刷新。如下:
//仅刷新config-client:3355
//destination=服务名:端口号
curl -X POST "http://localhost:3344/actuator/bus-refresh/config-client:3355"
此时再次更新github上的配置文件,使用上面的方式执行刷新,然后依次访问 http://127.0.0.1:3355/configInfo 和 http://127.0.0.1:3366/configInfo,可以看到只有config-client:3355得到了更新,config-client:3366未更新。