spring cloud config server使用svn来作为外部配置中心

默认情况下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>


我们可以试验一下,将config-server停掉,然后将config-client的logging.level.root=debug,启动client,在控制台将会看到类似重试信息

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将成功启动。

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值