通过名字很容易可以看出Spring Cloud Config的作用就是Config,为分布式系统中的外部配置提供服务器和客户端支持,方便部署与运维。Config组件分客户端、服务端。
服务端就是分布式配置中心,是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息。
客户端则是通过指定配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息。默认采用 git,并且可以通过 git 客户端工具来方便管理和访问配置内容。
通过Spring Cloud Config,我们的微服务上线后如果出现了诸如JDBC、MongoDB之类的连接配置参数变化的情况,就不再需要打开各个微服务的源码修改配置文件然后再重新部署上线了。Spring Cloud Config可以通过一个统一的存放了需要远程配置的所有微服务配置文件的Git配置仓库来完成各个微服务的配置,当有配置文件发生变化的时候,修改Git服务器上的文件就可以了。
首先我们需要创建一个配置中心的依赖仓库,没有私有Git的可以通过GitHub,Gitee之类的网站创建一个自己的仓库。国内GitHub经常无故404,因此这里我们选择使用Gitee。在Git上建立一个私有的仓库,并提交需要通过配置中心来控制的配置文件,完成后如下图所示。
上面提到服务端是一个独立的微服务应用,因此我们需要新建一个配置中心模块,在IDEA中新建一个Maven模块,命名为SpringCloudConfig。打开新模块的pom.xml,添加如下依赖
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.0.6.RELEASEversion>
parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Finchley.SR1version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-config-serverartifactId>
<version>2.1.3.RELEASEversion>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
然后在resource目录下新建application.yml来配置一下配置中心微服务,内容如下
server:
port: 7788 #服务端口
spring:
application:
name: app-config-server #指定服务名
cloud:
config:
server:
git: #配置git仓库地址
skipSslValidation: true #跳过HTTPS验证
uri: https://gitee.com/cypherfyc/SpringCloudConfig.git
# search-paths:
# - #配置文件目录地址
username: XXXXXX #GITEE账号(公有项目不需要设置)
password: XXXXXX #GITEE密码(公有项目不需要设置)
label: master #分支名称
跟其他的微服务一样,配置中心也需要启动类,在java下新建runner包,新建ConfigCenterApp.java,完整代码如下:
package runner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
@SpringBootApplication
@EnableConfigServer //开启配置服务 *******注意不要漏了这个注解*********
public class ConfigCenterApp {
public static void main(String[] args) {
SpringApplication.run(ConfigCenterApp.class, args);
}
}
此时如果你的代码仓库没有问题且网络畅通,配置中心应该就可以使用了。
启动配置中心微服务,访问http://localhost:7788/mircoservice-dev.properties,结果如下
配置中心已经可以从Gitee上拉取到配置文件的内容了,我们可以修改Gitee上的配置文件,再重新访问刚才的URL,会发现结果会随着自己的更改而变化,也就是说每次通过配置中心请求配置文件,配置中心都会连接到Gitee服务器下载最新的配置文件。
配置中心已经拉取到了Git上的配置文件,那么要怎么样才可以在别的微服务中得到应用呢,选取app-cargo微服务模块来举例,假设这一模块需要连接数据库来查询货物信息,需要从配置中心获取JDBC的连接信息。我们现在需要对这个模块进行一些改动。
在打开app-cargo的pom.xml,在其中新增如下的两个依赖:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-config-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
要做配置中心的客户端,需要对客户端进行配置,在resource下新建bootstrap.yml,内容如下:
spring:
cloud:
config:
uri: http://127.0.0.1:7788/ #配置中心的地址
profile: dev #对应配置服务中的{profile}
label: master #对应的分支
在这里我们设定了使用配置中心加载的配置文件名为app-cargo-dev,也就是{spring.application.name}-{profile},根据Spring Cloud Config的规则,会选取后缀为properties或yml的文件进行加载。
注意:bootstrap.yml是先于application.yml加载的,因此application.yml加载时需要的信息可以放在bootstrap.yml中。
连接JDBC需要有载体来承载JDBC的各个信息,因为配置文件不是存在本地的,我们就用一个JavaBean来承载从配置中心获取的JDBC配置,在app-cargo模块的java目录下新建一个包命名为config,在包下新建一个JdbcConfigBean,代码如下:
package config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
@Component
@RefreshScope //刷新配置文件
public class JdbcConfigBean {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Value("${jdbc.driverClassName}")
private String driverClassName;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getDriverClassName() {
return driverClassName;
}
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
@Override
public String toString() {
return "{\"JdbcConfigBean\":{"
+ "\"url\":\""
+ url + '\"'
+ ",\"username\":\""
+ username + '\"'
+ ",\"password\":\""
+ password + '\"'
+ ",\"driverClassName\":\""
+ driverClassName + '\"'
+ "}}";
}
}
这样在app-cargo启动的时候,就会像配置中心请求对应的配置文件并加载到这个Bean中,我们需要在controller开放一个验证的网络接口来确定一下加载是否成功了,在CargoController中新加如下的代码
import config.JdbcConfigBean;
//………省略……………
@RestController //RestController注解的类中的方法返回的值均默认采用了@ResponseBody注解
public class CargoController {
//………省略……………
@Autowired
private JdbcConfigBean jdbcConfigBean;
@GetMapping(value = "testconfig")
public String testconfig(){
return this.jdbcConfigBean.toString();
}
现在先启动配置中心,再启动app-cargo微服务,访问http://localhost:8090/testconfig就可以查看到结果了。
上面我们提到,app-cargo的配置文件是在启动的时候向配置中心请求的,如果app-cargo启动完成后,Git上的配置文件发生了变化,app-cargo中的配置是不会发生改变的。因此我们在修改这个模块的时候添加了spring-boot-starter-actuator依赖,通过actuator和JdbcConfigBean上的@RefreshScope注解,可以通过手动发请求来实现更新加载配置信息的JavaBean。
当Git上的配置文件发生变化之后,我们使用请求工具向http://localhost:8090/actuator/refresh发送一个post请求,再次访问http://localhost:8090/testconfig,会发现配置信息已经和Git上的同步了。
当然在实际的应用中不可能每次配置文件更新了就手动请求一次这个refresh接口,我们可以借助Git提供的Hook来完成更新后的自动请求,但这需要Git服务器能够访问到项目部署的服务器,由于我是在内网环境做的测试,这一部分就略过了。
配置中心也是一个SpringBoot项目,我们先前写死了app-cargo拉取配置信息的地址,如果配置中心ip发生了改变在实际项目中会涉及到非常大的变动,非常麻烦。如果我们将配置中心注册到eureka中就可以解决这一问题了。
我们在配置中心模块的pom.xml中新增eureka client的依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
<version>2.0.2.RELEASEversion>
dependency>
修改配置中心微服务的application.yml配置文件,在里面加上有关eureka的配置,内容如下:
#服务注册到eureka注册中心的地址
eureka:
instance:
instance-id: ${
spring.application.name} #指定实例id
prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
client:
service-url:
#注册中心地址
defaultZone: http://localhost:9090/eureka/
#需要注册到注册中心
register-with-eureka: true
#是否需要从eureka上检索服务
fetch-registry: true
在配置中心的启动类上加上@EnableEurekaClient注解,这样配置中心就注册到Eureka上了。
接下来需要修改app-cargo微服务的配置文件,先前我们将配置中心的ip写死在了bootstrap.yml中,现在我们将bootstrap.yml改写如下:
#服务注册到eureka注册中心的地址
eureka:
instance:
instance-id: ${
spring.application.name} #指定实例id
prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
client:
service-url:
#注册中心地址
defaultZone: http://localhost:9090/eureka/
#需要注册到注册中心
register-with-eureka: true
#是否需要从eureka上检索服务
fetch-registry: true
spring:
cloud:
config:
# uri: http://127.0.0.1:7788/ #配置中心的地址
profile: dev #对应配置服务中的{profile}
label: master #对应的分支
discovery:
enabled: true #启用服务发现
service-id: app-config-server #指定配置中心微服务名称
由于bootstrap.yml是先于application.yml加载的,原先配置在application.yml中的内容是无法被配置中心加载到的,访问配置中心服务依赖于eureka client,所以这里需要将eureka的配置加进来。
现在我们需要先启动Eureka注册中心和配置中心微服务,启动完成后等待几秒再启动app-cargo微服务。由于配置中心注册到Eureka需要数秒的时间来检测和发送心跳,如果app-cargo启动时没有在Eureka中获取到配置中心微服务,会出现app-cargo微服务启动失败的情况,因此我们要等到配置中心在Eureka上成功注册之后再启动app-cargo。
启动完成后,再次访问http://localhost:8090/testconfig,得到正确的返回值。
在app-zuul-gateway模块中的pom.xml加入如下两个依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-config-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
btw:我去掉了这两个依赖代码也能正常运行,有可能是依赖管理器导入的Jar包中集成了这一部分,为了防止环境不同出现不一样的情况,这个可以参考着导入。
然后我们打开application.yml,注释掉有关zuul的配置,新建一个bootstrap.yml文件,内容如下:
eureka:
client:
service-url:
defaultZone: http://localhost:9090/eureka/
###因为该应用为服务提供者,是eureka的一个客户端,需要注册到注册中心
register-with-eureka: true
###是否需要从eureka上检索服务
fetch-registry: true
instance:
prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
ip-address: 127.0.0.1
instance-id: ${
spring.application.name}###${server.port} #指定实例ID
spring:
cloud:
config:
name: ${
spring.application.name} #对应配置中心的应用名称,默认是本应用名,即spring.application.name,该名称要和git中的配置一致
profile: dev #对应配置服务中的{profile}
label: master #对应的分支
discovery:
enabled: true #启用发现服务功能
service-id: app-config-server #指定配置中心工程的名称
在zuul的启动类中加入如下的方法:
@RefreshScope
@ConfigurationProperties("zuul")
public ZuulProperties zuulProperties() {
ZuulProperties properties = new ZuulProperties();
return properties ;
}
将app-zuul-gateway-dev.yml上传到Git,文件内容如下(就是刚刚注释掉的内容):
zuul:
host:
connect-timeout-millis: 15000 #HTTP连接超时大于Hystrix的超时时间
socket-timeout-millis: 60000 #socket超时
routes:
cargo-service: #这是个随便取的名字,用于识别不同的路由,从原地址到目的地址
path: /cargo-service/** #访问的地址
serviceid: app-cargo #转发到哪个微服务
cargolist-service:
path: /cargolist-service/**
serviceid: app-cargo-list
启动Zuul微服务,配置是从Git上获取的,发现网关可以正常工作。以后我们修改Git上的Zuul配置,然后调用Zuul的/refresh接口就可以实现动态网关的配置了。