Disconf使用方法(2) —— 基于XML的分布式配置文件

本文将新建一个SpringBoot示例工程,然后整合Disconf的客户端功能,最后以Redis配置为例,讲解如何通过XML实现配置文件的托管功能,包括普通托管和自动重载。

一、环境描述

1. Disconf服务器

2. Spring Tool Suite

  • 版本:STS 3.9.2.RELEASE

3. Redis

二、建立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包,如下图所示:

下载Disconf客户端的依赖关系

创建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中获取的值,如下图所示:

从Redis获取测试键值

从上图可以看到,交给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

若有不清楚的地方,可以参考示例代码,再结合实际项目进行修改!