本文档主要阐述了版本 Disconf-Client 的设计。、
运行流程详细介绍:
[点击查看大图 ](http://ww3.sinaimg.cn/mw1024/60c9620fjw1eqj81no7shj20l50h2q65.jpg
运行流程详细介绍:
与2.0版本的主要区别是支持了:主备分配功能/主备切换事件。
Disconf-client包括的大模块有:
各个模块均采用以下设计模式来进设计:
启动分成两步,由两个Bean来实现
<bean id="disconfMgrBean" class="com.baidu.disconf.client.DisconfMgrBean"
destroy-method="destroy">
<property name="scanPackage" value="com.baidu.disconf.demo" />
</bean>
<bean id="disconfMgrBean2" class="com.baidu.disconf.client.DisconfMgrBeanSecond"
init-method="init" destroy-method="destroy">
</bean>
这里 com.baidu.disconf.dem 是要扫描的类。
第一步由Bean com.baidu.disconf.client.DisconfMgrBean 来控制。第二步由 com.baidu.disconf.client.DisconfMgrBeanSecond 控制。
此Bean实现了BeanFactoryPostProcessor和PriorityOrdered接口。它的Bean初始化Order是最高优先级的。
因此,当Spring扫描了所有的Bean信息后,在所有Bean初始化(init)之前,DisconfMgrBean的postProcessBeanFactory方法将被调用,在这里,Disconf-Client会进行第一次扫描。
扫描按顺序做了以下几个事情:
其中对配置的处理详细为:
DisconfMgrBean的扫描主要是静态数据的初始化,并未涉及到动态数据。DisconfMgrBeanSecond Bean则是将一些动态的数据写到仓库里。
本次扫描按顺序做了以下几个事情:
下面将 分别详细阐述 分布式配置文件 和 分布式配置项 的实现方式。
由于目前版本只支持 Spring编程方式,因此,以下均只阐述Spring编程下的实现方式。
定义分布式配置文件类
对于配置文件,我们必须实现一个Java类来表示此 分布式配置文件。如:
package com.example.disconf.demo.config;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import com.baidu.disconf.client.common.annotations.DisconfFile;
import com.baidu.disconf.client.common.annotations.DisconfFileItem;
/**
* Redis配置文件
*
* @author liaoqiqi
* @version 2014-6-17
*/
@Service
@Scope("singleton")
@DisconfFile(filename = "redis.properties")
public class JedisConfig {
// 代表连接地址
private String host;
// 代表连接port
private int port;
/**
* 地址, 分布式文件配置
*
* @return
*/
@DisconfFileItem(name = "redis.host", associateField = "host")
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
/**
* 端口, 分布式文件配置
*
* @return
*/
@DisconfFileItem(name = "redis.port", associateField = "port")
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
}
对于此Java类,它必须是Spring托管的。此配置文件是redis.properties。
此配置类必须标注为 @DisconfFile,标识它是一个分布式配置文件。且必须指定文件名。
此配置类含有两个配置项,分别是host和port。这两个变量必须有 get 方法。且get方法名必须是符合JavaBean规范的。
我们通过在这两个变量的 get 方法上添加 @DisconfFileItem 注解来标注它是分布式配置文件里的配置项。必须指定name参数,表示配置文件里的KEY值。associateField值是可选的,表示此get方法相对应的域的名字。
Disconf-client优先启动,并从平台上下载配置文件:
应用程序启动时,当Spring容器扫描了所有Java Bean却还未初始化这些Bean时,disconf-client 模块会优先开始初始化(最高优先级)。它会将 配置文件名、配置项名记录在配置仓库里,并去 disconf-web 平台下载配置文件至classpath目录下。并且,还会到ZK上生成相应的结点。
接着Spring开始初始化用户定义的SpringBean。由于配置文件已经被正确下载至Classpath路径下,因此,JavaBean的配置文件使用的是分布式配置文件,而非本地的配置文件。
待SpringBean初始化后,Disconf-client会获取配置更新回调类实例:
此时,Spring上的所有Bean均已被init。Disconf-client模块会再次运行,这时它会去获取用户撰写的配置更新回调函数类实例。
一个配置更新回调函数通常是这样撰写的:
package com.example.disconf.demo.service.callbacks;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import com.baidu.disconf.client.common.annotations.DisconfUpdateService;
import com.baidu.disconf.client.common.update.IDisconfUpdate;
import com.example.disconf.demo.config.Coefficients;
import com.example.disconf.demo.config.JedisConfig;
import com.example.disconf.demo.service.SimpleRedisService;
/**
* 更新Redis配置时的回调函数
*
* @author liaoqiqi
* @version 2014-6-17
*/
@Service
@Scope("singleton")
@DisconfUpdateService(classes = {JedisConfig.class}, itemKeys = {Coefficients.key})
public class SimpleRedisServiceUpdateCallback implements IDisconfUpdate {
protected static final Logger LOGGER = LoggerFactory.getLogger(SimpleRedisServiceUpdateCallback.class);
@Autowired
private SimpleRedisService simpleRedisService;
/**
*
*/
public void reload() throws Exception {
simpleRedisService.changeJedis();
}
}
此类必须实现接口IDisconfUpdate,它可以不必是Java托管的。如果是SpringBean,则disconf-client会从Spring容器里获取此Bean。如果它不是SpringBean,disconf-client就会new一个实例出来。
使用SpringBean来定义此类的好处是,我们可以在此类中使用@Autowired来使用其它SpringBean。比较方便些。
disconf-client根据注解@DisconfUpdateService 以配置文件为Key,将回调函数实例列表放在此Key的Map里。当配置文件更新时,这些回调函数实例就会被按顺序执行。
配置文件更新时,分布式配置文件会重新被下载:
当配置文件更新时,disconf-client便会重新从 disconf-web 平台下载配置文件,并重新将值放在配置仓库里。并按顺序进行调用回调函数类的 reload() 方法。
如何使用分布式配置文件类:
在上面我们说到,配置文件类中的配置项必须有 get 方法,并且必须有 @DisconfFileItem 注解。
在 get 上面添加注解的原因就是为了做切面。
disconf-cient使用Spring AOP拦截 系统里所有含有@DisconfFileItem注解的 get 方法,把所有此类请求都定向到用户程序的配置仓库中去获取。
通过这种方式,我们可以实现统一的、集中式的在配置仓库里去获取配置文件数据。这是一种简洁的实现方式。
配置项相对于配置文件,比较灵活。我们可以在任何SpringBean里添加配置项。
如以下是在一个配置文件类里添加配置项:
package com.example.disconf.demo.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.baidu.disconf.client.common.annotations.DisconfFile;
import com.baidu.disconf.client.common.annotations.DisconfFileItem;
import com.baidu.disconf.client.common.annotations.DisconfItem;
/**
* 金融系数文件
*/
@Service
@DisconfFile(filename = "coefficients.properties")
public class Coefficients {
public static final String key = "discountRate";
@Value(value = "2.0d")
private Double discount;
private double baiFaCoe;
private double yuErBaoCoe;
/**
* 阿里余额宝的系数, 分布式文件配置
*
* @return
*/
@DisconfFileItem(name = "coe.baiFaCoe")
public double getBaiFaCoe() {
return baiFaCoe;
}
public void setBaiFaCoe(double baiFaCoe) {
this.baiFaCoe = baiFaCoe;
}
/**
* 百发的系数, 分布式文件配置
*
* @return
*/
@DisconfFileItem(name = "coe.yuErBaoCoe")
public double getYuErBaoCoe() {
return yuErBaoCoe;
}
public void setYuErBaoCoe(double yuErBaoCoe) {
this.yuErBaoCoe = yuErBaoCoe;
}
/**
* 折扣率,分布式配置
*
* @return
*/
@DisconfItem(key = key)
public Double getDiscount() {
return discount;
}
public void setDiscount(Double discount) {
this.discount = discount;
}
}
或者,我们也可以在一个Service类里添加配置项:
package com.example.disconf.demo.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.baidu.disconf.client.common.annotations.DisconfItem;
import com.example.disconf.demo.config.Coefficients;
/**
* 金融宝服务,计算一天赚多少钱
*
* @author liaoqiqi
* @version 2014-5-16
*/
@Service
public class BaoBaoService {
protected static final Logger LOGGER = LoggerFactory.getLogger(BaoBaoService.class);
public static final String key = "moneyInvest";
@Value(value = "2000d")
private Double moneyInvest;
@Autowired
private Coefficients coefficients;
/**
* 计算百发一天赚多少钱
*
* @return
*/
public double calcBaiFa() {
return coefficients.getBaiFaCoe() * coefficients.getDiscount() * getMoneyInvest();
}
/**
* k 计算余额宝一天赚多少钱
*
* @return
*/
public double calcYuErBao() {
return coefficients.getYuErBaoCoe() * coefficients.getDiscount() * getMoneyInvest();
}
/**
* 投资的钱,分布式配置 <br/>
* <br/>
* 这里切面无法生效,因为SpringAOP不支持。<br/>
* 但是这里还是正确的,因为我们会将值注入到Bean的值里.
*
* @return
*/
@DisconfItem(key = key)
public Double getMoneyInvest() {
return moneyInvest;
}
public void setMoneyInvest(Double moneyInvest) {
this.moneyInvest = moneyInvest;
}
}
采用哪种方式,由用户选择。
值得注意的是,在第二种实现中,它的方法calcBaiFa() 时调用了 getMoneyInvest() 方法。 getMoneyInvest() 是配置项的get方法,它添加了@DisconfItem注解,表明它是一个配置项,并且会被切面拦截,moneyInvest的值会在配置仓库里获取。但是,可惜的是,SpringAOP是无法拦截"Call myself"方法的。也就是说getMoneyInvest()是无法被切面拦截到的。
为了解决此问题,在实现中,我们不仅将它的值 注入到配置仓库中,而且还注入到配置项所在类的实例里。因此,在上面第二种实现中,虽然 getMoneyInvest() 方法无法被拦截,但是它返回的还是正确的分布式值的。
配置文件也一样,配置值亦会注入到配置文件类实体中。
在非Spring方式下,无法使用AOP切面编程,因此无法统一的拦截配置数据请求。
在这种情况下,用户配置类的实现有两种方式:
注意:此两种方式均无法自动避免“配置读取不一致问题”。
当事件发生时,用户程序处理配置的方式是:
|----disconf
|----app1_version1_env1
|----file
|----confA.properties
|----item
|----keyA
|----app2_version2_env2
|----file
|----conf2.properties
|----item
|----key2
虽然注解式编程简单、直观,易维护,但是,它是具有一定的代码侵入性的。 disconf考虑到有些用户不想写代码,只想通过XML配置(可能是在旧项目中使用disconf)来实现分布式配置的需求。因此,disconf亦实现了基于XML分布式的实现方式。
ReloadablePropertiesFactoryBean继承了PropertiesFactoryBean类,它主要做到:
ReloadingPropertyPlaceholderConfigurer继承自Spring的配置类PropertyPlaceholderConfigurer,它会在Spring启动时将配置数据与Bean做映射,以便在检查到配置文件更改时,可以实现Bean相关域值的自动注入。
它是一个Timer类,定时校验配置是否有更改,进而促发 ReloadingPropertyPlaceholderConfigurer 类来分析要对哪些 Bean实例进行重新注入。
配置项 | 说明 | 是否必填 | 默认值 |
---|---|---|---|
conf_server_store_action | 仓库 URL | 是 | /api/config |
conf_server_zoo_action | zoo URL | 是 | /api/zoo |
conf_server_master_num_action | 获取远程主机个数的URL | 是 | /api/getmasterinfo |
zookeeper_url_prefix | zookeeper的前缀路径名 | 是 | /disconfserver2 |
local_dowload_dir | 下载文件夹, 远程文件下载后会放在这里 | 是 | ./disconf/download |
disconf将会为所有配置提供主备功能的开关,对于一个配置,多台实例机器可以进行竞争成为主机(使用主配置),竞争失败的实例将会成为备机(使用备配置)。基于zookeeper提供的分布式一致性锁,可以非常容易的达到此目的。