一、介绍:
在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。市面上开源的配置中心有很多,BAT每家都出过,360的QConf、淘宝的diamond、百度的disconf都是解决这类问题。国外也有很多开源的配置中心Apache的Apache Commons Configuration、owner、cfg4j等等。在Spring Cloud中,有分布式配置中心组件spring cloud config ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git仓库中。在spring cloud config 组件中,分两个角色,一是config server,二是config client。
一个配置中心提供的核心功能:
- 提供服务端和客户端支持
- 集中管理各环境的配置文件
- 配置文件修改之后,可以快速的生效
- 可以进行版本管理
- 支持大的并发查询
- 支持各种语言
Spring Cloud Config可以完美的支持以上所有的需求。
二、ConfigServer从本地读取配置文件
Config Server可以从本地仓库读取配置文件,也可以从远处Git仓库读取。本地仓库是指 将所有的配置文件统一写在ConfigServer工程目录下。ConfigSever暴露HttpAPI接口,ConfigClient通过调用ConfigSever的HttpAPI接口来读取配置文件 。
2.1构建ConfigServer
在主Maven工程下,创建一个Module工程,工程名为config-server,其pom文件继承主Maven工程的pom文件, 并在pom文件中引入ConfigServer的起步依赖sring-cloud-config-server。pom文件代码如下:
<parent>
<groupId>com.hand</groupId>
<artifactId>macro-service</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath/>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
</dependencies>
</parent>
在程序的启动类 ConfigServerApplication加上@EnableConfigServer 注解,开启ConfigServer的功能,代码如下:
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
在工程的配置文件application.yml中做相关的配置,包括指定程序名为config-server,端号为8678通过spring.profiles.active=native来配置ConfigServer从本地读取配置,读取配置的路径为classpath下的shared目录。 application.yml配置文件的代码如下:
spring:
cloud:
config:
server:
native:
search-locations: classpath:/shared
name: config-client
profiles:
active: dev
application:
name: config-server
server:
port: 8678
在工程的Resources目录下建一个shared文件夹,用于存放本地配置文件。在shared目录下,新建一个config-client-dev.yml文件,用作config-client工程的dev(开发环境)的配置文件。在config-client-dev.yml配置文件中,指定程序的端口号为8673,并定义一个变量foo,变量的值为foo version 1。 代码如下:
server:
port: 8673
foo: foo version 1
2.2 构建ConfigClient
新建Module一个工程,取名为config-client,该工程作为ConfigClient从ConfigServer读取配置文件,该工程的pom文件继承了主Maven工程的porn文件,并在其pom文件引入Config的起步依赖spring-cloud-starter-config和web功能的起步依赖spring-boot-starter-web。代码如下:
<parent>
<groupId>com.hand</groupId>
<artifactId>macro-service</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
</dependencies>
在其配置文件bootstrap.yml中做程序的配置,注意这里用的是bootstrap.yml,而不是application.yml, bootstrap相对于application具有优先的执行顺序。在bootstrap.yml配置文件中指定了程序名为config-client,向Uri地址为htψ://localhost:8678的ConfigSerever读取配置文件。 如果没有读取成功,则执行快速失败(fail-fast),读取的是dev文件。 bootstrap.yml配置文件中的变量{spring. application.name}和变量 {spring.profiles.active},两者以“·”相连,构成了 向ConfigServer读取的配置文件名,所以本案例在配置中心读取的配置文件名为config-client-dev.yml文件 。 配置文件bootstrap.yml 的代码如下:
spring:
application:
name: config-client
cloud:
config:
uri: http://localhost:8678
fail-fast: true
profiles:
active: dev
management:
security:
enabled: false
失败快速响应
不作任何额外配置的情况下,失败响应有点迟钝,举个简单的例子,关掉config-server,我们直接启动config-client,此时启动会报错,但是报错时间较晚,报错的时候系统已经打印了许多启动日志了,如果我们希望在启动失败时能够快速响应,方式很简单,config-client中添加如下配置即可:
spring:
cloud:
config:
fail-fast: true
config-server工程启动成功后,启动config-client工程,你会在控制台的日志中发现eureka-client向Uri地址为http://localhost:8678的ConfigServer读取了配置文件。最终程序启动的端口为8763,这个端口是在eureka-server的Resouces/shared目录中的eureka-client-dev.yml的配置文件中配置的,可见eureka-client成功地向eureka-server读取了配置文件。
为了进一步验证, 在config-client工程写一个API接口,读取配置文件的foo变量, 并通过API 接口返回,代码如下:
@RefreshScope
@RestController
@SpringBootApplication
public class ConfigClientApplication {
@Value("${foo}")
String foo;
public static void main(String[] args) {
SpringApplication.run(ConfigClientApplication.class, args);
}
@RequestMapping("/foo")
public String foo(){
return foo;
}
}
打开浏览器,访问 http://localhost:8673/foo,浏览器显示:
三、 ConfigServer从远程git仓库读取配置文件
SpringCloudConfig支持从远程 Git仓库读取配置文件,即ConfigServer可以不从本地的仓库读取,而是从远程Git仓库读取。这样做的好处就是将配置统一管理,并且可以通过SpringCloudBus在不人工启动程序的情况下对ConfigClient的配置进行刷新。这里采用GitHub作为远程Git仓库。
首先,修改ConfigServer的配置文件application.yml,代码如下 :
server:
port: 8678
spring:
cloud:
config:
server:
git:
uri: https://github.com/Cheerman/mypro #配置git仓库的地址
search-paths: config-respo
username: #自己的github用户名
password: #自己的github密码
name: config-client
label: master
application:
name: config-server
其中,uri为远程Git仓库的地址,serachPaths为搜索远程仓库的文件夹地址,usemame和password为Git仓库的登录名和密码。如果是私人Git仓库,登录名和密码是必须的;如果是公开的Git仓库,可以不需要。label为git仓库的分支名,本例从master读取。
将上一节的config-client-dev.yml上传到远程仓库中,上传的路径为https://github.com/Cheerman/mypro。可以自己申请GitHub账号,并在GitHub上创建代码仓库,将config-client-dev.yml上传到自己的仓库。这里我使用的是我自己创建的仓库。重新启动config-server, config-server启动成功后,启动config-client,可以发现config-client的端口为8673。 像上一节一样,访问http://localhost:8673/foo,浏览器显示 :
Git仓库的配置文件信息:
可见,config-server从远程Git仓库读取了配置文件,config-client从config-server读取了配置文件。
四、 搭建高可用的Config Server
在上一节讲解了 Config Client如何从配置中心 Config Server读取配置文件 ,配置 中心如 何从远程 Git 仓库读取配置文件。当服务实例很多时,所有的服务实例需要同时从配置中心 Config Server读取配置文件,这时可以考虑将配置中心 Config Server做成一个微服务,并且将 其集群化,从而达到高可用。配置中心 Config Server 高可用的架构图如下图所示。 ConfigServer和ConfigClient向EurekaServer注册,且将ConfigServer多实例集群部署。
4.1 构建EurekaServer
这里直接使用第三章创建的eureka-server项目。
4.2 改造ConfigServer
(1)pom文件引入eureka的依赖,代码如下:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
(2)在工程的启动类 ConfigServerApplication加上@EnableEurekaClient注解,开启 EurekaClient 的功能,代码如下 :
@SpringBootApplication
@EnableConfigServer
@EnableEurekaClient
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
(3)在工程的配置文件 application.yml 文件中添加服务注册的地址,代码如下 :
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8671/eureka/
4.3 改造Config Client
和ConfigServer一样作为EurekaClient, 在pom文件加上spring-cloud-starter-eureka起步依赖,在工程的启动类加上@EnableEurekaClient注解,开启EurekaClient的功能。在工程的配置文件application.yml加上相关配置,指定服务注册中心的地址为http://localhost:8671/eureka/,向ServiceId为config-server的配置服务读取配置文件,代码如下:
spring:
application:
name: config-client
cloud:
config:
fail-fast: true
discovery:
enabled: true #开启配置服务发现
serviceId: config-server #配置服务实例名称
profiles:
active: dev
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8671/eureka/
依次启动 eureka-server、config-server和config-client工程,注意这里需要config-server启动成功井且向eureka-server注册完成后,才能启动config-client,否则config-client找不到config-server。
通过控制台可以发现,config-client向地址为http即://localhost:8679的config-server读取了配置文件。访问http://localhost:8673/foo,浏览器显示:
那么如何搭建高可用的ConfigServer呢?只需要将 ConfigServer多实例部署,用IDEA开启多个ConfigServer实例,端口分别为8678和8679。在浏览器上访问EurekaServer的主页http://localhost:8761/,如下图所示:
多次启动 config-client 工程,从控制台可以发现它会轮流地从 http://localhost:8768和
http://localhost:8679的ConfigServer读取配置文件,并且做了负载均衡。
五、使用SpringCloudBus刷新配置
SpringCloudBus是用轻量的消息代理将分布式的节点连接起来,可以用于广播配置文件 的更改或者服务的监控管理。一个关键的思想就是,消息总线可以为微服务做监控,也可以实现应用程序之间相互通信。 SpringCloudBus可选的消息代理组建包括RabbitMQ、AMQP和Kafka等。这里是用RabbitMQ作为SpringCloud的消息组件去刷新更改微服务的配置文件。
如果有几十个微服务,而每一个服务又是多实例,当更改配置时,需要重新启动多个微服务实例,会非常麻烦。SpringCloudBus的一个功能就是让这个过程变得简单,当远程Git仓库的配置更改后,只需要向某一个微服务实例发送一个Post请求,通过消息组件通知其他微服务实例重新拉取配置文件。 如下所示,当远程Git仓库的配置更改后,通过发送“/bus/refresh”Post请求给某一个微服务实例,通过消息组件,通知其他微服务实例,更新配置文件。
根据此图我们可以看出利用Spring Cloud Bus做配置更新的步骤:
- 1、提交代码触发post给客户端A发送bus/refresh
- 2、客户端A接收到请求从Server端更新配置并且发送给Spring Cloud Bus
- 3、Spring Cloud bus接到消息并通知给其它客户端
- 4、其它客户端接收到通知,请求Server端获取最新配置
- 5、全部客户端均获取到最新的配置
5.1 代码实现
5.11 添加依赖
这里是在上面的代码基础上进行改造的 ,只需要改造config-client工程。首先,需要在pom文件中引入用RabbitMQ实现的SpringCloudBus的起步依赖spring-cloud-starter-bus-amqp。 如 果读者需要自己实践,则需要安装RabbitMQ服务器。 pom文件添加的依赖如下:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
5.12 配置信息
在工程的配置文件application.yml添加RabbitMQ的相关配置, host为RabbitMQ服务器的IP地址, port为RabbitMQ服务器的端口,usemame和password为RabbitMQ服务器的用户名和密码。通过消息总线更改配置,需要经过安全验证,为了方便讲述 ,先把安全验证屏蔽掉,也就是将management.security.enabled改为false。代码清单如下 :
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
management:
security:
enabled: false
5.13 开启更新机制
需要在更新的配置类上加@RefreshScope注解,只有加上了该注解,才会在不重启服务的情况下更新配置,如本例中更新配置文件foo变量的值。代码清单如下:
@RefreshScope
@RestController
@SpringBootApplication
@EnableEurekaClient
public class ConfigClientApplication {
@Value("${foo}")
String foo;
public static void main(String[] args) {
SpringApplication.run(ConfigClientApplication.class, args);
}
@RequestMapping("/foo")
public String foo(){
return foo;
}
}
依次启动工程,其中config-client开启两个实例,端口分别为8672和8673。启动完成后,在浏览器上访问http://localhost:8672/foo或者http://localhost:8673/foo , 浏览器显示“ foo version 2”,如下图:
更改远程 Git仓库,将 foo的值改为“ foo version 3”。通过Postman或者其他工具发送一个Post请求http://localhost:8672/bus/refresh,请求发送成功,再访问http://localhost:8672/foo或者http://localhost:8673/foo,浏览器都会显示“ foo version 3”,如下图:
5.2 刷新方式优化
由于每次手动刷新客户端也很麻烦,这里考虑使用自动触发的方式刷新客户端或服务端。由于这里我们使用的是github作为配置中心,故使用github的webhook就可以解决自动刷新的目的。
5.21 webhook
WebHook是当某个事件发生时,通过发送http post请求的方式来通知信息接收方。Webhook来监测你在Github.com上的各种事件,最常见的莫过于push事件。如果你设置了一个监测push事件的Webhook,那么每当你的这个项目有了任何提交,这个Webhook都会被触发,这时Github就会发送一个HTTP POST请求到你配置好的地址。
如此一来,你就可以通过这种方式去自动完成一些重复性工作,比如,你可以用Webhook来自动触发一些持续集成(CI)工具的运作,比如Travis CI;又或者是通过Webhook去部署你的线上服务器。下图就是github上面的webhook配置。
5.22 配置域名
由于webhook的触发后回调的URL需要使用域名形式。我们将使用ngrok来做到这一点,Ngrok作为内网穿透的工具,适用于所有主流操作系统。由于使用国内的Ngrok可以免费使用自定义域名,网速更快,而且配置更加方便,这里直接介绍国内的Sunny-Ngrok使用教程:
1、打开官网,下载客户端。国内Ngrok官网
2、接着注册账号,添加隧道。关于开通隧道的教程
下图为我的隧道配置:
3、配置好隧道信息,在控制台的ngrok目录执行下面代码
./sunny clientid 你的隧道id
这里就能看到自定义域名映射成功了。如下图:
至此,webhook和ngrok的配置完成了,下面就可以通过push配置信息的修改,来触发刷新客户端配置的功能。
(1)访问http://localhost:8672/foo结果如下:
(2)修改配置文件内容为:foo version 3 提交并push到Github
(3)在webhook里看到请求push触发刷新请求成功
(4)刷新http://localhost:8672/foo结果如下:
至此,就实现了使用webhook和ngrok动态刷新配置文件的功能。
5.3 改进版本
在上面的流程中,我们已经到达了利用消息总线触发一个客户端bus/refresh,而刷新所有客户端的配置的目的。但这种方式并不优雅。原因如下:
- 打破了微服务的职责单一性。微服务本身是业务模块,它本不应该承担配置刷新的职责。
- 破坏了微服务各节点的对等性。
这时SpringCloudBus做配置更新步骤如下:
- 1、提交代码触发post请求给bus/refresh
- 2、server端接收到请求并发送给Spring Cloud Bus
- 3、Spring Cloud bus接到消息并通知给其它客户端
- 4、其它客户端接收到通知,请求Server端获取最新配置
- 5、全部客户端均获取到最新的配置
5.3.1 修改config-server
pom添加依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
修改application.yml:
spring:
rabbitmq:
host: 192.168.0.6
port: 5672
username: guest
password: guest
management:
security:
enabled: false
配置文件增加RebbitMq的相关配置,关闭安全验证。这样server端代码就改造完成了。重复刷新config-client端的操作,就会发现服务加载了更新的配置信息。这里就不一一展示了。
5.4 局部刷新
某些场景下(例如灰度发布),我们可能只想刷新部分微服务的配置,此时可通过/bus/refresh端点的destination参数来定位要刷新的应用程序。
例如:/bus/refresh?destination=customers:8000,这样消息总线上的微服务实例就会根据destination参数的值来判断是否需要要刷新。其中,customers:8000指的是各个微服务的ApplicationContext ID。
destination参数也可以用来定位特定的微服务。例如:/bus/refresh?destination=customers:**,这样就可以触发customers微服务所有实例的配置刷新。
总结:这一章节主要学习了SpringCloudConfig配置中心的搭建及应用、以及高可用配置中心的搭建,还有如何使用SpringCloudBus自动、手动刷新配置以及ngrok内网穿透配置等内容。
github源码地址