1 搭建一个svn仓库,存放springboot客户端的配置文件
配置文件命名方式参考官网,这里取其中一种:{application}-{profile}.yml
2 服务端搭建
新建一个springboot项目,添加依赖:
<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-bus-amqp</artifactId>
</dependency>
bootstrap.yml文件配置如下:
server:
port: 8087
spring:
application:
name: config-server-svn
profiles:
active: subversion #使用svn作为配置仓库,必须显示声明profiles.active=subversion,不然还是用的git
cloud:
config:
server:
svn:
uri: http://xxx.xx.xxx.xxx/springconfig/trunk/SpringCloudConfig #配置svn仓库地址
searchPaths: respo #配置仓库路径
username: xxxxxx #访问svn仓库的用户名
password: xxxxxx #访问svn仓库的用户密码
basedir: /data #默认在系统临时目录下面,需要调整一下避免临时文件被系统自动清理
label: trunk #配置svn的分支
rabbitmq:
host: xxx.xx.xxx.xxx
port: 5672
username: xxxxxxx
password: xxxxxxxx
configServer的工作流程:
1 服务端注册客户端获取配置接口
2 健康检查时刷新svn仓库到本地
3 客户端启动和每次健康检查时调用该接口获取配置.
3 客户端搭建
另起一个客户端,依赖config,actuator以及bus-amqp
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
bootstrap.yml文件配置如下:
server:
port: 8120
spring:
application:
name: microservice
cloud:
config:
uri: http://config.xxxxxxxxx.com/ #指明配置服务中心的网址
profile: test #dev开发环境配置文件,test测试环境配置文件,prod生产环境配置文件
label: trunk
fail-fast: true #拉取配置失败时快速退出,否则有时项目依旧会启动,造成某些依赖的组件为空
rabbitmq:
host: xxx.xxx.xx.xx
port: 5672
username: xxxx
password: xxxxxxx
4.客户端配置热更新
至此仅实现客户端初始化时从配置中心拉取配置。修改配置需重启应用.如何实现客户端配置的热更新?主要是@RefreshScope这个注解,热更新redis示例:
/**
* 因为@RefreshScope注解的组件是懒加载的,所以如果有starter的话,可
* 能上下文中已经有了一个redisConnectionFactory,所以建议不要依赖自动* 装配的包,自己定义一个redis配置的组件
*/
@Configuration
@RefreshScope
public class RedisConnectionFactoryConf {
@Value("${spring.redis.database}")
private int database;
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.timeout:2000}")
private long timeout;
@Value("${spring.redis.lettuce.pool.max-idle:5}")
private int maxIdle;
@Value("${spring.redis.lettuce.pool.min-idle:0}")
private int minIdle;
@Value("${spring.redis.lettuce.pool.max-active:10}")
private int maxActive;
@Value("${spring.redis.lettuce.pool.max-wait:2000}")
private long maxWait;
private LettuceConnectionFactory lettuceConnFactory() {
// 单机版配置
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
redisStandaloneConfiguration.setDatabase(database);
redisStandaloneConfiguration.setHostName(host);
redisStandaloneConfiguration.setPort(port);
redisStandaloneConfiguration.setPassword(RedisPassword.of(password));
// 集群版配置
// RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration();
// String[] serverArray = clusterNodes.split(",");
// Set<RedisNode> nodes = new HashSet<RedisNode>();
// for (String ipPort : serverArray) {
// String[] ipAndPort = ipPort.split(":");
// nodes.add(new RedisNode(ipAndPort[0].trim(), Integer.valueOf(ipAndPort[1])));
// }
// redisClusterConfiguration.setPassword(RedisPassword.of(password));
// redisClusterConfiguration.setClusterNodes(nodes);
// redisClusterConfiguration.setMaxRedirects(maxRedirects);
//连接池配置
GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();
genericObjectPoolConfig.setMaxIdle(maxIdle);
genericObjectPoolConfig.setMinIdle(minIdle);
genericObjectPoolConfig.setMaxTotal(maxActive);
genericObjectPoolConfig.setMaxWaitMillis(maxWait);
LettuceClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder()
.commandTimeout(Duration.ofMillis(timeout))
.poolConfig(genericObjectPoolConfig)
.build();
LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(redisStandaloneConfiguration, clientConfig);
lettuceConnectionFactory.afterPropertiesSet();
return lettuceConnectionFactory;
}
@RefreshScope
@Bean("lettuceRedisTemplate")
public RedisTemplate<String, String> myTemplate() {
LettuceConnectionFactory factory = this.lettuceConnFactory();
RedisTemplate<String, String> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
return template;
}
}
配置文件为bootstrap.yml,配置基本数据,主要包括config客户端配置。svn配置hooks到config的/actuator/bus-refresh这个接口。当有svn push事件发生时,会调用该接口,从而通过spring bus通知到客户端,客户端删除@RefreshScope的Bean缓存,以重新创建基于新配置的bean。
流程如下:
1.svn仓库push事件
2.hooks->POST configServer/actuator/bus-refresh
3.configServer publish>>Bus
4.Client subscribe>>Bus
5.refresh
这里需要注意,如果是svn配置的hooks,因其body的payload的原因,config服务端可能会解析失败,此时有如下解决方案,配置一个filter,使此request的body为空:
@WebFilter(urlPatterns = "/actuator/bus-refresh")
public class RefreshFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
String url = httpServletRequest.getRequestURI();
if (!url.endsWith("/actuator/bus-refresh")) {
filterChain.doFilter(servletRequest, servletResponse);
} else {
EmptyRequestWrapper requestWrapper = new EmptyRequestWrapper(httpServletRequest);
filterChain.doFilter(requestWrapper, servletResponse);
}
return;
}
@Override
public void destroy() {
}
/**
* 返回自定义的空request
*/
private class EmptyRequestWrapper extends HttpServletRequestWrapper {
public EmptyRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public ServletInputStream getInputStream() throws IOException {
byte[] bytes = new byte[0];
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
return new ServletInputStream() {
@Override
public boolean isFinished() {
return byteArrayInputStream.read() == -1 ? true : false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
};
}
}
}