一: 概述
背景:多个项目,多个配置,需要重启多次。为了解决这个问题,出现了集群配置中心Spring Cloud Config
二: Spring Cloud Config
2.1 介绍
配置服务器功能
- 提供访问配置的服务接口
- 对属性进行加密和解密
- 方便嵌入Spring boot项目中
配置客户端功能:
- 绑定配置服务器,使用远程的属性初始化spring 容器
- 对属性进行加密和解密
- 属性改变时,可以对他们进行重新加载
- 提供了与配置相关的几个管理端点
- 在初始化引导程序的上下文时,进行绑定配置服务器和属性解密等工作,当然,也可以实现其他工作
2.2 应用结构
SpringCloud Config为分布式集群系统提供了配置服务器与配置客户端,通过对这两者的配置,可以实现对集群中的配置进行管理,在实际应用中,我们会将配置文件放置外部系统(Git、SVN等),SpringCloud Config服务器会到Git/SVN读取配置文件并且使用,Config服务器和客户端可以很容易通过增加依赖集成到系统中。
2.3 引导程序介绍
Spring Cloud 的程序在进行容器初始化时会先创建一个“引导上下文”(Bootstrap Context),再去创建主应用程序的上下文。
- 引导上下文读取的配置文件是:bootstrap.yml文件,
- 主应用程序读取的配置文件是:application.yml
若两者的配置有相同时,后者会覆盖前者的配置。
三:搭建SVN环境
spring cloud 默认为Git,
四:Spring Cloud Config例子(SVN)
4.1 Config 服务端
4.1.1 依赖(server)
<!-- Config-Server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<!-- SVN-Kit -->
<dependency>
<groupId>org.tmatesoft.svnkit</groupId>
<artifactId>svnkit</artifactId>
<version>1.9.0</version>
</dependency>
4.1.2 application.yml配置文件(Server)
server:
port: 8888
##为了方便测试,关闭安全管理机制
management:
security:
enabled: false
##配置SVN仓库
spring:
profiles:
active: subversion
cloud:
config:
server:
svn:
uri: https://localhost/svn/test-project
username: aitemi
password: aitemi
##默认到SVN根目录下的trunk目录中读取配置文件
default-label: wps
配置说明spring -cloud -config -server有四种配置
- git:默认值,表示去Git仓库读取配置文件
- subversion:表示去SVN仓库读取配置文件
- native:将会去本地的文件系统中读取配置文件
- vault:去Vault中读取配置文件,Vault是一款资源控制工具,可对资源实现安全访问
4.1.3 启动类(server)
package com.atm.cloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
@SpringBootApplication
@EnableConfigServer//开启服务器
public class ConfigServerApp {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApp.class, args);
}
}
4.1.4 测试
在svn中创建test -project项目。在项目中创建wps目录,并创建first-test.yml文件,
浏览器访问http://127.0.0.1:8888/first-test.yml可以看到first-test.yml的文件
4.2 Config客户端
4.2.1 依赖(Client)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>1.5.4.RELEASE</version>
</dependency>
<!-- 查看集群实例端点 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>1.5.4.RELEASE</version>
</dependency>
4.2.2 控制层、启动类(Client)
package com.atm.cloud;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 读取远程的配置
*/
@RestController
public class ReadConfigController {
@Autowired
private Environment environment;
@GetMapping("/read")
public String readConfig() {
return environment.getProperty("test.user.name");
}
}
public static void main(String [] args){
........
}
4.2.3 配置(Client)
在配置客户端新建 bootstrap.yml文件,配置连接SVN仓库
spring:
## 配置了spring.application.name,和cloud.config.profile,则读取atm-config-client-dev.yml
application:
name: atm-config-client
cloud:
config:
url: http://localhost:8888
## 读取的文件是 atm-config-client-dev.yml
profile: dev
在bootstrap.yml文件中
- spring.cloud.config.url:指定配置服务器的地址
- spring.cloud.config.profile:指定读取的配置文件(atm-config-client-dev.yml)
通过上面的配置,最终配置客户端(Client)先去配置服务器(Server),然后再去SVN服务器寻找svn/test-project/wps/atm-config-client-dev.yml文件
所以在SVN服务器中,建立对应目录下的配置文件(atm-config-client-dev.yml)
个人理解:
4.3 目录配置总结
前面对服务器和客户端的配置进行简单的配置,总结如下
-
服务端
- spring.cloud.config.server.svn.uri=http://localhost/svn/test-project 配置需要连接的SVN地址
- spring.cloud.config.server.default-label=default-config 到SVN下的指定目录中读取
-
客户端
- spring.application.name=atm-config-client,读取的配置文件的名称,
- spring.cloud.config.profile=dev
4.4 刷新配置
远程SVN中的配置更新后,需要通知客户端该表配置。
解决方法:访问客户端的“/refresh”进行刷新
模拟POST请求
package com.atm.cloud;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
public class RefreshClientMain {
public static void main(String[] args) throws Exception {
CloseableHttpClient client = HttpClients.createDefault();
// 发送post请求
HttpPost post = new HttpPost("http://localhost:8080/refresh");
HttpResponse response = client.execute(post);
System.out.println(EntityUtils.toString(response.getEntity()));
}
}
4.5 刷新Bean
在实际应用中,往往不仅之刷新一个配置的值那么简单。在spring容器中,有很多Bean会根据某个属性值进行初始化,配置一旦更新,需要重建这个Bean实例。为了解决上述的问题,spring Cloud提供了@RefreshBean注解。
刷新流程
在spring容器中,有一个类型为RefreshBean的Bean。当/refresh端点被访问时,负责处理刷新的ContextRefresher类会先去远程的配置服务器刷新新配置,然后再调用RefreshBean的refreshAll方法来处理实例,容器中使用了@RefreshBean注解进行修饰的Bean,都会在缓存中进行销毁,当这些Bean被再次引用时,就会创建新的实例,以达到“刷新”的目的
package com.atm.cloud;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
@Configuration
public class MyConfig {
@Bean
@RefreshScope
public Person person(Environment env) {
// 读取名字创建Person
String name = env.getProperty("test.user.name");
// 输出Person名字
System.out.println("初始化Person bean:" + name);
// 创建一个Bean
Person p = new Person();
p.setName(name);
return p;
}
}
五: 配置的加密和解密
- 配置客户端到服务端读取配置时,会涉及到敏感的信息,如数据库的密码。所以应该对这些数据进行加密和解密。对加密后的密文传输给客户端之前会进行解密
- 配置服务器支持对称(AES)/非对称加密(RSA)
- 服务器的加密和解密依赖JCE(Java Cryptography Extension)
- /encrypt 加密的服务端点
- /decrypt 解密的服务端点
5.1 对称加密
5.1.1 修改配置服务器(spring-config-server)的application.yml
encrypt:
key: aitemi
## 为了方便测试,关闭安全管理
management:
security:
enabled: false
5.1.2 加密测试(spring-config-server)
加下来编写一个HttpClient客户端去请求加密接口,并且传入需要加密的内容
package com.atm.cloud;
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
public class EncryptMain {
public static void main(String[] args) throws Exception {
CloseableHttpClient client = HttpClients.createDefault();
// 发送post请求
HttpPost post = new HttpPost("http://localhost:8888/encrypt");
// 设置请求的参数,对20180323进行加密,编码格式为UTF-8
HttpEntity entity = new StringEntity("20180323", Consts.UTF_8);
post.setEntity(entity);
HttpResponse response = client.execute(post);
System.out.println(EntityUtils.toString(response.getEntity()));
}
}
5.1.3 解密(spring-config-server)
package com.atm.cloud;
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
public class DecryptMain {
public static void main(String[] args) throws Exception {
CloseableHttpClient client = HttpClients.createDefault();
// 发送post请求
HttpPost post = new HttpPost("http://localhost:8888/decrypt");
// 设置请求的参数,对2cdf324e7d8c6271d883a7a9bdcac532d027141545f1fed273f8c2b803bc3e9d进行解密,编码格式为UTF-8
HttpEntity entity = new StringEntity("2cdf324e7d8c6271d883a7a9bdcac532d027141545f1fed273f8c2b803bc3e9d", Consts.UTF_8);
post.setEntity(entity);
HttpResponse response = client.execute(post);
System.out.println(EntityUtils.toString(response.getEntity()));
}
}
加密和解密测试,属于同一个项目,但是在不同类中。
5.2 SVN存储加密数据
在SVN仓库的yml配置文件中,使用“{ciper}密文”的格式来保存加密后的数据。在SVN仓库中test-project/default-config/sping-book-server-dev.yml文件进行下面的配置
test:
user:
name: aitemi
## 使用'{cipher} xxxx',xxxx代表需要解密的内容
## 在properties文件中不需要单引号
password: '{cipher}fca358013a71b250c4a4a40cd844fdd6d47f8ddc13fb366893fa1ef29c79d55c'
5.3 非对称加密
对称加密和解密使用同一个秘钥,而非对称加密和解密使用不同的秘钥---------公钥和私钥。
使用JDK自带keytool工具生成密钥对
keytool -genkeypair -alias "myKey" -keyalg "RSA" -keystore "D:\keys\mykey.keystore"
将密钥对拷贝到配置服务器(spring-config-server)项目resources目录下 。并修改同目录下的application.yml文件,
encrypt:
keyStore:
location: classpath:/myKey.keystore # 密钥对的别名
password: 123456 # 密钥对的别名
alias: myKey # 密钥对的别名
secret: 123456 # 密钥口令
使用方法和上面的对称加密解密一样,
- 访问http://localhost:8888/encrypt可以得到加密后的密文
- http://localhost:8888/decrypt可以得到解密后的值。
- 在SVN的配置文件中同样使用“{cipher}密文”的格式,访问相应配置时,配置服务器会自动进行解密。
六 其他配置
除了上面的配置外,接下来对服务端和客户端的其他功能进行介绍
6.1 服务器健康指示器
- 配置服务器访问SVN仓库的uri时,当无法访问时,状态设置为DOWN
- 除了检测SVN的URI是否连接正常,还可以检测URI下的目录是否可以连接
spring:
cloud:
config:
server:
svn:
uri: http://localhost/svn/test-project
username:admin
password:123456
default-label:default-config
health:
repositories:
## 名称
test-service:
## 是否有这个文件
label: health-test
配置服务器会向SVN中连接http://localhost/svn/test-project/health-test目录,如果svn连接不上这个目录,在访问服务器的/health端点时,则会出现状态为DOWN的信息。
6.2 客户端的错误提前与重试机制
- 错误提前
- 实际应用中,客户端比较关心配置服务器是否能连接上(因为客户端在启动之前会去配置服务器读取配置文件,然后启动自身容器,如果配置服务器连接不上,那么客户端则无法启动,为了防止配置客户端带着错误启动,这里就引入了错误提前机制,在启动失败的时候直接抛出错误)
- 遇到无法连接时,宁可自己启动失败,也不能带着“错误”启动容器。
基于上述错误,可以使用Spring Cloud Config的错误提前机制。在客户端的bootstrap.yml中,设置
sping.cloud.config.failFast=true
通过上面的设置,即可实现错误提前,只要在启动时无法连接(一次)配置服务器,则会中止容器的启动
- 重试机制
- 如果连接错误,就会有与之配套的重试机制。现将
spring.cloud.config.failFast=true
再在客户端添加依赖
<!-- 重试 -->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>1.5.3.RELEASE</version>
</dependency>
- 重试机制的主要配置
- spring.cloud.config.retry.initial-interval:初始的重试间隔,默认为1000毫秒
- spring.cloud.config.retry.max-attempts:最大重试次数,默认为6次
- spring.cloud.config.retry.max-interval:最大的重试间隔,默认为2000毫秒
- spring.cloud.config.retry.multiplier:重试间隔的递增系数,默认为1.1,第二次和第三次之间的重试间隔就会 *递增系数
6.3 安全配置
- 之前都是配置客户端直接连接服务器,来抓取配置的,在实际环境中,不可能任何客户端连接过来,服务器均接受
- 就实现一个访问的机制:客户端登录服务器,获取配置
6.3.1 配置服务器端,添加依赖
pom.xml
<!-- 安全机制 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>1.5.3.RELEASE</version>
</dependency>
application.yml添加配置
security:
user:
name: root
password: 123456
6.3.2 客户端,修改配置项
bootstrap.yml
spring:
cloud:
config:
username: root
password: 123456