默认情况下config server是使用git来作为配置中心的,由于还是有许多公司还是用svn来作为版本管理工具,幸好config server服务也支持svn配置,下面就来试验一下
服务端配置:
新建一个config-server项目,pom文件:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>config-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>config-server</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Dalston.SR3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.tmatesoft.svnkit/svnkit -->
<dependency>
<groupId>org.tmatesoft.svnkit</groupId>
<artifactId>svnkit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
这里要注意由于使用的是svn来保存配置,所以还要引入svnkit依赖,这里还引入了eureka依赖,因为我们将config-server配置成了高可用,即config-server也会作为一个服务注册到注册中心去,到时候客户端可以通过服务Id来自动发现一组config-server服务负载均衡的访问。
启动类:
package com.example.configserver;
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;
@SpringBootApplication
@EnableConfigServer
@EnableDiscoveryClient //整合eureka,实现config server高可用,将config-server作为服务注册
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
加了两个注解,一个是@EnableConfigServer,将该服务作为一个config server服务启动,另一个@EnableDiscoveryClient作为一个服务注册到eureka
application.properties配置文件:
spring.application.name=config-server
server.port=7001
eureka.client.service-url.defaultZone=http://peer1:8001/eureka/,http://peer2:8002/eureka/
#关闭配置仓库的健康检查
#spring.cloud.config.server.health.enabled=false
#配置属性覆盖,定义了两个属性,name和from
#spring.cloud.config.server.overrides.name=xiaojun
#spring.cloud.config.server.overrides.from=shanghai
#spring.cloud.config.server.git.uri=https://github.com/hupuxiaojun/spring-cloud/
#spring.cloud.config.server.git.uri=https://github.com/hupuxiaojun/{application}-config/ #通过占位符动态管理配置仓库,将获取客户端的spring.application.name作为{application}值去获取配置
#spring.cloud.config.server.git.uri=file:///D:/config-repo/resources/ #使用本地git clone目录,需要先从git上拉到本地来,windows下面用file:///作为schema
# 配置仓库路径下的相对搜索位置,可以配置多个
#spring.cloud.config.server.git.search-paths=resources
#spring.cloud.config.server.git.username=
#spring.cloud.config.server.git.password=
#spring.cloud.config.server.git.basedir=/data #默认在系统临时目录下面,需要调整一下避免临时文件被系统自动清理
#使用svn作为配置仓库,必须显示声明profiles.active=subversion,不然还是用的git
spring.profiles.active=subversion
spring.cloud.config.server.svn.uri=http://192.168.78.239/usvn/svn/datadev/docs/config/
spring.cloud.config.server.svn.username=xiaoj
spring.cloud.config.server.svn.password=xxxxxx
spring.cloud.config.server.svn.search-paths={application} #使用{application}占位符
spring.cloud.config.server.svn.default-label=trunk
spring.cloud.config.server.svn.basedir=/data #默认在系统临时目录下面,需要调整一下避免临时文件被系统自动清理
management.security.enabled=false
这里要注意的是要使用svn作为配置中心,必须显示声明spring.profiles.active=subversion,不然还是用的git,svn默认的lable是trunk,git默认的是master,客户端在使用的时候注意指定label,这里search-paths使用了{application}占位符,用来根据客户端的spring.application.name来动态搜索配置目录,即按照每个应用一个配置目录的形式来组织配置。
这个demo的svn目录结构如下:
每个文件里面都定义了一个from属性,例如mapp-dev.properties中定义的是from=svn-dev-1.0,其他类似。
启动服务端,访问http://localhost:7001/myapp/dev,能够得到如下结果:
{
"name": "myapp",
"profiles": [
"dev"
],
"label": null,
"version": "606",
"state": null,
"propertySources": [
{
"name": "http://192.168.78.239/usvn/svn/datadev/docs/config/trunk/myapp/myapp-dev.properties",
"source": {
"from": "svn-dev-1.0"
}
},
{
"name": "http://192.168.78.239/usvn/svn/datadev/docs/config/trunk/myapp/myapp.properties",
"source": {
"from": "svn-default-1.0"
}
}
]
}
客户端配置:
接下来新建一个config-client客户端,尝试获取一下config-server上的配置,pom文件:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>config-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>config-client</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Dalston.SR3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
客户端也引入了eureka依赖,由于客户端直接从config-server上获取配置而不是从svn上获取,所以不需要引入svnkit依赖
客户端启动类:
package com.example.configclient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class ConfigClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigClientApplication.class, args);
}
}
添加了@EnableDiscoveryClient注解,用来注册和发现服务
客户端为了能动态加载外部配置,需要将配置信息卸载bootstrap.properties中而不是application.properties中,在resources目录下面新建一个bootstrap.properties文件:
spring.application.name=myapp
eureka.client.service-url.defaultZone=http://peer1:8001/eureka/,http://peer2:8002/eureka/
#高可用配置中心通过服务Id去自动发现config-server服务组
spring.cloud.config.discovery.enabled=true
spring.cloud.config.discovery.service-id=config-server
spring.cloud.config.profile=dev
#spring.cloud.config.label=master
#使用svn配置时lable是trunk
spring.cloud.config.label=trunk
#高可用模式下配置了service-id就不用指定uri了
#spring.cloud.config.uri=http://localhost:7001/
server.port=7002
management.security.enabled=false
高可用配置中心是通过服务id去自动发现config-server服务地址的,所以不用配置spring.cloud.config.uri指向具体的地址了,直接通过spring.cloud.config.discovery.service-id来指定config server的服务名,这里是config-server,同时需要将spring.cloud.config.discovery.enable=true(默认为false,使用uri方式),声明通过服务发现的方式来获取config server地址。
最后我们在config-client服务上写一个controller类:
package com.example.configclient.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RefreshScope //允许动态刷新配置
@RestController
public class MyController {
@Value("${from}")
private String from;
@Autowired
private Environment env;
@RequestMapping("/from")
public String from() {
return this.from;
}
@RequestMapping("/from-env")
public String fromEnv() {
//也可以通过env来获取
return env.getProperty("from", "undefined");
}
}
为了能动态刷新配置,需要在允许动态刷新的类上注解@RefreshScope,这里我们定义了一个变量from,值是通过@Value("${from}")注解从配置中获取的值自动注入的,也可以从Environment中获取
接下来我们启动客户端,然后访问http://localhost:7002/from,将显示svn-dev-1.0。说明正确从config server上获取了配置信息
接下来我们修改一下svn上的myapp-dev.properties,将from属性值改成svn-dev-2.0,然后commit提交svn。访问config server的http://localhost:7001/myapp/dev,属性已经变成了svn-dev-2.0,但是访问http://localhost:7002/from显示的还是svn-dev-1.0。
我们需要手动刷新一下配置,post调用acturator提供的/refresh端点来刷新配置, post http://localhost:7002/refresh。
然后再请求一下http://localhost:7002/from,将显示最新的svn-dev-2.0。
由于是外部集中式配置中心,所以我们希望将一些敏感的信息加密处理,config server也支持加密解密,首先生成一个keystore文件:
keytool -genkeypair -alias config-server -keyalg RSA -dname "CN=xiaojun, OU=kingnet, O=kdc, L=shanghai, ST=shanghai, C=china" -keypass 222222 -keystore config-server.keystore -storepass 111111
会在当前目录下面生成一个config-server.keystore文件,我们可以将它放到config-server项目的resources目录下面去.
然后在config-server项目中resources目录下面创建bootstrap.yml配置文件:
#encrypt相关的配置必须配置在bootstrap配置文件中,不然没法解密
# encrypt.key这个版本有BUG还没修复,导致无法使用加解密 https://github.com/spring-cloud/spring-cloud-config/issues/767
#encrypt.key: xiaojun
##下面除了location之外的其他三个属性建议设置到部署机的环境变量当中去更安全,对应的是ENCRYPT_KEY_STORE_ALIAS,ENCRYPT_KEY_STORE_PASSWORD,ENCRYPT_KEY_STORE_SECRET
encrypt:
key-store:
location: config-server.keystore
password: 111111
secret: 222222
alias: config-server
这里注意一点, encrypt相关的配置必须配置在bootstrap配置文件中,不然没法解密属性,另外password,secret,alias这三个属性最好以环境变量的形式配置到config-server部署机器上,不要直接写到配置文件中,这样做更安全一些。
然后我调整了一下config-server的application.yml配置:
spring:
profiles:
active: subversion
cloud:
config:
server:
svn:
uri: '{cipher}AQAl5kbzrB6ghxjyQ760VzyxsXKiWJ58oXXOvIUw9B6bHf0+0kfxyIMw1XjL0ISqGfmvjW/BN6o1Y0l6NaYaT1sMcplUkzQgewELJYSLkAjBpAfWjsg+F0kQSLHxvyCYciZharikZ7IUHEYdB2iSe/YcL8g28WMG4jFEiqg4nRaMb8retOMa/7YI6mMLwKH8FevEmXlqo7gzoJwfx/uPdBhLWyJL1ABOZMA++1K5dhYVVTelAMDcg49r2s1bDotqcDIP7ngUrVjpJsdIfCKWcYVmVHBBpGeFcgnBLkYh5WAneeSckeZ+4EhPl+6JbnUVlSLJoxI3P7B1bSTDgHfyzxWEwX9Tbq+8ZQff65ad38mVx9wz/6c8XyS9gEaVwM6OAdZpoSoyJNaxaWZ66rzRXiTHefE716m8DGRbzZViy7IMJY2w0C1RIcWjnhbJGjZvTEI='
username: '{cipher}AQBubzgpKhIQ/qSaOKx5PdHUX7K8eSETovb1QaHUNyx5k12RCozVFvkeT3P41V9bYpSvEaWLF2s+1rBoXjPjBXvdoCxXU//S4DpD95SF+ZudsVzH7CktSesE1IX/ViZbBuAQ/NuGBAJvoBN530TL8ny0OS9g6DWX6d9ilZPis2/rTTH3U0EJ7qpFNA8w7LfZbAUhOog8s+CACj7f8eWDmdM7kKojVV5O+Ms3QxCmJWZUQSlv2C/IQpQjoiuFTeYRedyHS1p0UN40E3/ywpIUMWFdz8NY/gOo2nW3b/HfP6ZGDAQIirCghNdOzD+rHncrnoC2IXT3Y7Gj+rpQp2m5hwt8ySCEFltuGuH9Mp3JNAVt4Z9YAy2kRx+SF5MwxCFwlLg='
password: '{cipher}AQAoFnasNP9emLuGNt77NILtksE74KoIfWsSAAOMO8A9l1DjoSQFI5CMzcKNDNBZ+ES5yKrq8IpVkLNsKg3AMpAlfX7zHnk4XKzbYPZetheAlYGhDWJacTy6BKzsj0sIytu6lPfZ/erJCnEHG94CcKRLheRKHmhbwt4nDB/VBpIkDaAmVdjRTyYk9jPZ+ZgO53qrO1gAsdww81JkqPaFNL3yql9wksXdRd/JRh4z+aCXi52VpPN3hPo3Tx2Ikt8p4AbYu9uNLNpDLLSvOihPmMUUB09lZEh3sQFbsVheVS72/xNM2ha0x+nSZKL8V3EQn7XN46HfvQ5csvLVRunov8NmC/5glNVH+l3OLDmyYAutkHGMVoFQVbGkL1U64uTvYzw='
search-paths: '{application}'
default-label: trunk
basedir: /data/config
application:
name: config-server
server:
port: 7001
eureka:
client:
service-url:
defaultZone: http://peer1:8001/eureka/,http://peer2:8002/eureka/
management:
security:
enabled: false
将svn地址、用户名、密码都加密处理了,通过加上{cipher}这个hint,在装载配置的时候会自动解密。yml文件中如果属性值用了{},必须用引号括起来,如上的'{application}'和'{cipher}',不然无法识别。
加解密功能同样适用在具体服务的配置上面,比如给上面的myapp-dev.yml中增加一个配置my.password,原始内容是test-dev,外面通过config-server的/encrypt端点生成加密字符串: curl http://localhost:7001/encrypt -d xiaojun,生成的字符串如下写在myapp-dev.yml中:
from: svn-dev-1.0
my:
password: '{cipher}AQBkRH5IfW9koXtci/3tGyEG7LJgH4TGCTQ8fLBfdOB3Bq2DRZHEkW5FAtATB/OmCk+PEYgC4q3YuHsc6BP0gG/S5hVmZwCxyg4jpgD7LooVjW8hfJekYlqD/qgJvjx2z11AhLFiw7nfdwTqEXGforf0UPXIgS4bN2WVaAqFjbORqXazVOwAS8SggP32bnWyWc3Ezi1fXuqQp8UK0ej7xhwVM+S1UrcTTfJho6lPiDvPhjq7+6x3vYWUP2lQHD4tY9Q0+v8r62Oo/K2Gp7gu+LO+vC1BfDfUJZW1hhoWJ22OipAnjBgJLPJyWaK60MeovSe9ylnd2c7G39faa1pnXZUYRXdfuhxZvVKaTu1aoeWQQPmLfBp9qyHYdIZ+aQXbmFc='
然后修改一些config-client的controller:
package com.example.configclient.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RefreshScope //允许动态刷新配置
@RestController
public class MyController {
@Value("${from}")
private String from;
@Autowired
private Environment env;
@RequestMapping("/from")
public String from() {
return this.from;
}
@RequestMapping("/from-env")
public String fromEnv() {
//也可以通过env来获取
return env.getProperty("my.password", "undefined");
}
}
访问config-client的/from-env:http://localhost:7002/from-env,返回的内容是解密后的内容,由config server直接解密返回给客户端了。
有时候由于网络不稳定导致与客户端无法成功获取到配置,可以在客户端bootstrap配置中增加重试机制:
#允许失败快速响应
spring.cloud.config.fail-fast=true
#允许重试,需要添加spring-retry和spirng-aop依赖,下面配置的都是和默认值一样
spring.cloud.config.retry.max-attempts=6
spring.cloud.config.retry.initial-interval=1000
spring.cloud.config.retry.multiplier=1.1
spring.cloud.config.retry.max-interval=2000
添加依赖:
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2017-09-20 18:00:22.644 INFO 15036 --- [ main] c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at: http://peer1:7001/
2017-09-20 18:00:22.664 DEBUG 15036 --- [ main] o.s.web.client.RestTemplate : Created GET request for "http://peer1:7001/myapp/dev/trunk"
2017-09-20 18:00:22.676 DEBUG 15036 --- [ main] o.s.web.client.RestTemplate : Setting request Accept header to [application/json, application/*+json]
2017-09-20 18:00:23.680 DEBUG 15036 --- [ main] o.s.r.backoff.ExponentialBackOffPolicy : Sleeping for 1000
2017-09-20 18:00:24.680 DEBUG 15036 --- [ main] o.s.retry.support.RetryTemplate : Checking for rethrow: count=1
2017-09-20 18:00:24.680 DEBUG 15036 --- [ main] o.s.retry.support.RetryTemplate : Retry: count=1
当server起来之后,自动重试成功,client将成功启动。