本文将新建一个SpringBoot示例工程,然后整合Disconf的客户端功能,最后以Redis配置为例,讲解如何通过XML实现配置文件的托管功能,包括普通托管和自动重载。
一、环境描述
1. Disconf服务器
- 控制台:http://10.15.1.21:8080/
- 安装方法:参考《如何容器化部署Disconf统一配置服务》
2. Spring Tool Suite
- 版本:STS 3.9.2.RELEASE
3. Redis
- IP:192.168.190.128
- 端口(实例-1):6379
- 端口(实例-2):6380
- 安装方法:参考《Redis的Docker镜像制作详解》
二、建立SpringBoot示例工程
通过STS建立SpringBoot的示例工程,名称为disconf-xml-demo
。本文采用的配置如下:
- SpringBoot版本为1.5.12
- 项目依赖关系选择Web
注意,由于Disconf通过XML托管配置文件的功能和2.0版本的SpringBoot不兼容,因此本文使用1.5.12版本。具体的创建步骤,本文不再详述。
三、整合Disconf客户端
打开pom.xml
文件,添加disconf-client的依赖关系,如下所示:
<dependency>
<groupId>com.baidu.disconf</groupId>
<artifactId>disconf-client</artifactId>
<version>2.6.36</version>
</dependency>
示例工程会自动下载Disconf客户端的jar包,如下图所示:
创建spring-disconf.xml
文件,让Spring管理Disconf管理器的Bean实例,必须添加以下配置:
<bean id="disconfMgrBean" class="com.baidu.disconf.client.DisconfMgrBean"
destroy-method="destroy">
<property name="scanPackage"
value="com.example.demo.config,com.example.demo.callbacks" />
</bean>
<bean id="disconfMgrBean2" class="com.baidu.disconf.client.DisconfMgrBeanSecond"
init-method="init" destroy-method="destroy">
</bean>
其中,属性“scanPackage”的值是“com.example.demo.config,com.example.demo.callbacks”,
这里需要填写工程里需要扫描的Package名。“scanPackage”属性支持扫描多个包,使用逗号分隔。
在disconf-xml-demo
工程的启动类DisconfXmlDemoApplication
中引入spring-disconf.xml
配置文件,关键代码如下所示:
@SpringBootApplication
@ImportResource({"classpath:spring-disconf.xml"})
public class DisconfXmlDemoApplication {
public static void main(String[] args) {
SpringApplication.run(DisconfXmlDemoApplication.class, args);
}
}
创建disconf.properties
文件,指定Disconf的一些配置,内容如下所示:
disconf.enable.remote.conf=true
disconf.conf_server_host=10.15.1.21:8080
disconf.version=1_0_0_0
disconf.app=disconf-xml-demo
disconf.env=rd
disconf.ignore=
disconf.conf_server_url_retry_times=2
disconf.conf_server_url_retry_sleep_seconds=1
disconf.enable_local_download_dir_in_class_path=true
各个配置项的意义,请参考《Disconf使用方法(1) —— 基于注解的分布式配置文件和配置项》的第六章。
最后,在Disconf服务器上创建一个名为disconf-xml-demo
的应用,这个名称和上面配置文件中的disconf.app
的取值相同。
四、准备配置文件
在Disconf服务器的disconf-xml-demo
应用的rd环境中,新建两个配置文件:
- redis.properties
这是Jedis连接的配置文件,指定Redis服务器的IP地址和端口号,内容如下所示:
redis.host=192.168.190.128
redis.port=6379
- message.properties
这是在Redis中找不到结果时的警告消息,内容如下所示:
warn.message=redis cannot find this k-v pair ...
五、托管配置文件
Disconf支持托管式的分布式配置管理,配置文件没有相应的配置注解类,配置文件也不会被注入至配置类中,Disconf只是简单的对其进行“托管”。 工程启动时自动下载配置文件;配置文件变化时,Disconf负责动态推送。注意,使用托管式时,程序不会自动重新加载配置,需要自己编写回调方法。
托管式具有两种工作模式:
-
普通托管:Disconf只负责托管配置文件,当修改配置文件之后,会将该文件推送给业务系统,但业务系统不能重新加载更新的配置。
-
自动重载:Disconf不仅负责托管配置文件,当修改配置文件之后,会将该文件推送给业务系统,然后触发业务系统的回调方法,重新加载更新的配置。
1. 普通托管
在disconf-xml-demo
工程的spring-disconf.xml
配置文件中,增加如下配置:
<bean id="configproperties_no_reloadable_disconf"
class="com.baidu.disconf.client.addons.properties.ReloadablePropertiesFactoryBean">
<property name="locations">
<list>
<value>message.properties</value>
</list>
</property>
</bean>
<bean id="propertyConfigurerForProject1"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="ignoreResourceNotFound" value="true" />
<property name="ignoreUnresolvablePlaceholders" value="true" />
<property name="propertiesArray">
<list>
<ref bean="configproperties_no_reloadable_disconf" />
</list>
</property>
</bean>
上述配置会将message.properties
文件交给Disconf托管。当该文件发生变化时,Disconf会将其推送给示例工程,但示例工程并不会重新加载更新的配置。
示例工程的RedisService
类会用到message.properties
文件,关键代码如下所示:
@Value("${warn.message}")
private String warnMessage
public String get(String key) {
if (jedis != null) {
String value = jedis.get(key);
if (StringUtils.isEmpty(value))
value = warnMessage;
return value;
}
return null;
}
2. 自动重载
在disconf-xml-demo
工程的spring-disconf.xml
配置文件中,增加如下配置:
<bean id="configproperties_disconf"
class="com.baidu.disconf.client.addons.properties.ReloadablePropertiesFactoryBean">
<property name="locations">
<list>
<value>redis.properties</value>
</list>
</property>
</bean>
<bean id="propertyConfigurer"
class="com.baidu.disconf.client.addons.properties.ReloadingPropertyPlaceholderConfigurer">
<property name="ignoreResourceNotFound" value="true" />
<property name="ignoreUnresolvablePlaceholders" value="true" />
<property name="propertiesArray">
<list>
<ref bean="configproperties_disconf" />
</list>
</property>
</bean>
上述配置会将redis.properties
文件交给Disconf托管。当示例工程启动时,会从Disconf下载这个配置文件,然后将其中的配置项注入至JedisConfig
配置类,如下所示:
@Component
@Scope("singleton")
public class JedisConfig {
@Value("${redis.host}")
private String host;
@Value("${redis.port}")
private int port;
public String getHost() {
return host;
}
public int getPort() {
return port;
}
public void setHost(String host) {
this.host = host;
}
public void setPort(int port) {
this.port = port;
}
}
当该文件发生变化时,Disconf会将其推送给示例工程,并且触发RedisServiceUpdateCallback
类实现的回调方法,如下所示:
@Component
@Scope("singleton")
@DisconfUpdateService(confFileKeys = { "redis.properties" })
public class RedisServiceUpdateCallback implements IDisconfUpdate {
protected static final Logger LOGGER = LoggerFactory.getLogger(RedisServiceUpdateCallback.class);
@Autowired
private RedisService redisService;
public void reload() throws Exception {
redisService.changeJedis();
}
}
上述代码有两点需要注意:
- @DisconfUpdateService注解必须使用confFileKeys参数,不能使用classes和itemKeys参数,否则不能触发回调方法。
- 必须实现IDisconfUpdate接口,该接口的reload方法就是配置更新的回调方法,否则不能触发回调方法。
这个回调方法会调用redisService
实例的changeJedis
方法,重新加载Jedis的连接配置,如下所示:
public void changeJedis() {
PropertyUtil.loadProps("redis.properties");
jedisConfig.setHost(PropertyUtil.getProperty("redis.properties", "redis.host"));
jedisConfig.setPort(Integer.parseInt(PropertyUtil.getProperty("redis.properties", "redis.port")));
LOGGER.info("start to change jedis hosts to: " + jedisConfig.getHost() + " : " + jedisConfig.getPort());
jedis = JedisUtil.createJedis(jedisConfig.getHost(), jedisConfig.getPort());
LOGGER.info("change ok.");
}
上述方法会重新读取和解析最新的redis.properties
文件,然后更新Jedis的连接配置,最后重新连接Redis服务器。
六、验证测试
在Redis-1实例中新建一个Key-Value对:
Key=test_key
value=test_value
启动disconf-xml-demo
工程,在浏览器中访问:
http://127.0.0.1:8080/get.do?key=test_key
可以在浏览器上看到从Redis-1中获取的值,如下图所示:
从上图可以看到,交给Disconf托管的redis.properties
配置文件生效了,能够顺利地从Redis中取出值。
此时,若由于一些原因(例如宕机)导致需要切换到另一个Redis实例上,那么就需要修改Redis的连接配置。传统的方法有两个缺点:
- 需要把所有应用服务器的Redis配置文件都修改一遍,效率很低。
- Redis配置文件修改完成之后,需要重启应用才能生效,导致业务不连续。
通过Disconf的分布式配置文件和配置更新回调功能,我们可以做到:
- 只需要在Disconf中修改配置文件,然后自动推送至每个应用服务器。
- 不用重启应用服务器,通过回调方法重新加载配置,使得更新配置即刻生效。
在Disconf服务器中,修改redis.properties
文件的redis.port
配置项,改为Redis-2实例的端口,也就是6380。注意,Redis-2实例没有存储test_key键值对。修改并保存之后,可以通过日志看到触发了回调的reload方法,如下图所示:
再次在浏览器中访问:
http://127.0.0.1:8080/get.do?key=test_key
此时可以看到无法从Redis获取值,并且显示交给Disconf托管的message.properties
文件中的警告消息,如下图所示:
这就表示配置更新回调生效了,不用重启服务就能即时生效Redis连接配置,业务不会中断!
在Disconf服务器中,修改message.properties
文件的warn.message
配置项,修改后如下所示:
warn.message=redis cannot find this k-v pair - modified version
再次在浏览器中访问:
http://127.0.0.1:8080/get.do?key=test_key
此时发现显示的警告信息并不是最新的,这就说明更新的警告信息并没有加载至系统中!
综上所述,托管式Disconf的两种工作模式均可正常工作!
七、结语
1. 注解式和托管式
1.1 注解式的优点和缺点
Disconf注解式配置管理具有以下优点:
- 支持.properties配置文件
- 支持配置项
- 通过撰写配置类,代码结构清晰
- 配置更新时,自动注入
- 支持并发时配置更新统一生效
- 没有额外的XML配置,不需要通过xml定义JavaBean(config类)
- 代码风格简单易懂
具有以下缺点:
- 需要编写配置类和回调方法,还要添加Disconf注解
1.2 托管式的优点和缺点
Disconf托管式配置管理具有以下优点:
- 支持任意类型的配置文件
- 对于.properties配置文件,能够通过回调注入更新的配置项
- 若不需要配置更新回调,则没有代码侵入
具有以下缺点:
- 需要通过xml定义JavaBean,配置较为繁琐
- 不能兼容Spring 5.0和SpringBoot 2.0
- 不支持独立配置项
1.3 使用建议
-
业务配置文件和某些轻量级的基础设施配置文件通常是.properties格式的,这类文件可以通过Disconf的注解进行配置管理。
-
对于比较重型的资源,比如xml格式的JDBC配置文件,可以通过XML的方式交给Disconf进行托管。
2. 二次开发
Disconf是开源组件,它的自定义和扩展性比较强,可以根据自己的实际情况进行定制化的二次开发。目前可以想到以下的优化点:
- 解决Spring 5.0和SpringBoot 2.0的兼容性问题。
- 优化权限系统,实现不同的角色能够看到不同的环境和配置文件。
本文的disconf-xml-demo
示例工程,已经上传至GitLab:
http://10.15.1.248/yangbin/disconf-xml-demo.git
若有不清楚的地方,可以参考示例代码,再结合实际项目进行修改!