前言
众所周知,分布式应用中有许多应用节点,这些节点实现不同的功能,最终组合成我们需要的业务系统,正常情况下,每个节点都拥有不少于一份配置文件,随着开发、测试、用户验收等环境的完善,对应的多个节点同时也将维护许多份配置文件,而且对于一些数据库连接信息等敏感的信息如果直接存放在静态文件中,将会增加降低系统的安全级别,那么这里我们可以将所有配置文件全部都存放到 统一配置中心,由该配置中心进行统一管理
实现方案
技术选型
- SpringBoot 1.5.2
- Zookeeper 3.4.6
- curator 2.1
配置Zookeeper
首先使用 MicZkConfigProperties 类接收 zk 连接信息
@ConfigurationProperties("mic.zk")
public class MicZkConfigProperties {
private boolean enable = false;
/**
* 连接Zookeeper服务器的列表
* 包括IP地址和端口号
* 多个地址用逗号分隔
* 如: host1:2181,host2:2181
*/
private String addressList = "localhost:2181";
/**
* 等待重试的间隔时间的初始值
* 单位:毫秒
*/
private int baseSleepTimeMilliseconds = 1000;
/**
* 等待重试的间隔时间的最大值
* 单位:毫秒
*/
private int maxSleepTimeMilliseconds = 3000;
。。。。。。
同时我们要使用一个类 ZkRegisterOperation 与Zookeeper 建立连接,并且能在 zk 中操作数据
public class ZkRegisterOperation implements ZkOperation {
private static volatile ZkRegisterOperation zkRegisterOperation;
private CuratorFramework cf;
private ZkRegisterOperation() {
}
public static ZkRegisterOperation init(MicZkConfigProperties config) {
if (zkRegisterOperation == null) {
synchronized (ZkRegisterOperation.class) {
if (zkRegisterOperation == null) {
zkRegisterOperation = new ZkRegisterOperation();
zkRegisterOperation.cf = CuratorFrameworkFactory.builder()
.connectString(config.getAddressList())
.namespace(config.getNamespace())
.sessionTimeoutMs(config.getSessionTimeoutMilliseconds())
.connectionTimeoutMs(config.getConnectionTimeoutMilliseconds())
.retryPolicy(new ExponentialBackoffRetry
(
config.getBaseSleepTimeMilliseconds(),
config.getMaxRetries(),
config.getMaxSleepTimeMilliseconds()))
.build();
}
}
}
zkRegisterOperation.cf.start();
CountDownLatch countDownLatch = new CountDownLatch(1);
zkRegisterOperation.cf.getConnectionStateListenable().addListener(new ConnectionStateListener() {
@Override
public void stateChanged(CuratorFramework client, ConnectionState newState) {
if (newState.isConnected()) {
countDownLatch.countDown();
}
}
});
try {
if (!zkRegisterOperation.cf.blockUntilConnected(config.getMaxSleepTimeMilliseconds() * config.getMaxRetries(), TimeUnit.MILLISECONDS)) {
zkRegisterOperation.cf.close();
throw new KeeperException.OperationTimeoutException();
}
countDownLatch.await(10, TimeUnit.SECONDS);
} catch (final Exception e) {
e.printStackTrace();
}
return zkRegisterOperation;
}
public static ZkRegisterOperation getInstance() {
return zkRegisterOperation;
}
@Override
public String getData(String path) {
String data;
try {
data = new String(cf.getData().forPath(path), Charsets.UTF_8);
} catch (Exception e) {
e.printStackTrace();
return null;
}
return data;
}
这里将 ZkRegisterOperation 类设计成为单例,只允许一个应用节点与 zookeeper 只建立一个连接。
配置 SpringBoot
既然是配置中心,那么需要在应用启动寻找 enviroment 配置时,将保存在 zk 中的环境变量加入到 enviroment 中。这里通过继承了 ApplicationContextInitializer<ConfigurableApplicationContext> 类,当在调用 refreshContext() 前执行(该方法会调用 AbstractApplicationContext 中的 refresh() 方法),恰好可以满足我们的需求。
@Configuration
@EnableConfigurationProperties(MicZkConfigProperties.class)
@ConditionalOnProperty(value = "mic.zk.enable", havingValue = "true")
public class RegisterPropertyConfiguration implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
public static final String BOOTSTRAP_NAME = "bootstrap";
public static final String ZK_PREFIX = "mic.zk.";
// 将环境变量中的 zk 配置加载到 MicZkConfigProperties 中
private MicZkConfigProperties properties;
// 使用 MicConfigLocator 来寻找配置
private List<MicConfigLocator> micConfigLocators;
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
// 加载 相关依赖
loadDependence(environment);
MutablePropertySources mutablePropertySources = environment.getPropertySources();
CompositePropertySource composite = new CompositePropertySource(BOOTSTRAP_NAME);
//使用 micConfigLocators 来加载配置
micConfigLocators.forEach(e -> {
composite.addPropertySource(e.locate(environment));
});
//如果包含配置则更新
if (!CollectionUtils.isEmpty(composite.getPropertySources())) {
mutablePropertySources.remove(BOOTSTRAP_NAME);
}
mutablePropertySources.addFirst(composite);
}
/**
* 加载所有依赖项入口
*
* @param environment
*/
private void loadDependence(ConfigurableEnvironment environment) {
// 加载 zk 相关配置
initZkProperties(environment);
if (properties.isEnable()) {
Collection<MicConfigLocator> classCollection = ClassUtils.getClassCollection(MicConfigLocator.class);
classCollection.add(micConfigLocator());
//加载所有 MicConfigLocator
setMicConfigLocator(classCollection);
}
}
/**
* 初始化配置中心zk
*
* @param environment
*/
private void initZkProperties(ConfigurableEnvironment environment) {
//RelaxedPropertyResolver
MicZkConfigProperties properties = new MicZkConfigProperties();
RelaxedDataBinder binder = new RelaxedDataBinder(properties, ZK_PREFIX);
binder.bind(new PropertySourcesPropertyValues(environment.getPropertySources()));
// RelaxedPropertyResolver relaxedPropertyResolver = new RelaxedPropertyResolver(environment,"mic.zk.");
this.properties = properties;
ZkRegisterOperation.init(properties);
}
public void setMicConfigLocator(
Collection<? extends MicConfigLocator> initializers) {
this.micConfigLocators = new ArrayList<MicConfigLocator>();
this.micConfigLocators.addAll(initializers);
}
@Bean
public MicConfigLocator micConfigLocator() {
return new ZkConfigLocator();
}
}
使用示例
搭建好 zk 环境之后,在 /services/mic-center/data 位置下加入所需数据
在 示例项目中加入配置:
#
spring.application.name=mic-center
#
mic.zk.enable=true
mic.zk.address-list=192.168.248.129:2181
mic.zk.namespace=services
mic.zk.data=/${spring.application.name}/data