最近在工作中,为了完善公司集群服务的架构,提高可用性,降低运维成本,因此开始学习ZooKeeper。
至于什么是ZooKeeper?它能做什么?如何安装ZooKeeper?我就不一一介绍了,类似这些资料网上到处都是。我主要是把在开发过程中,以及个人对ZooKeeper的一些了解记录下来,大家如果遇到类似场景时,希望我的文章能够给你提供一些思路。
我使用的ZooKeeper(以下简称:ZK)客户端是Curator Framework,是Apache的项目,它主要的功能是为ZK的客户端使用提供了高可用的封装。在Curator Framework基础上封装的curator-recipes,实现了很多经典场景。比如:集群管理(Leader选举)、共享锁、队列、Counter等等。可以总结Curator主要解决以下三类问题:
- 封装ZK Client与Server之间的连接处理;
- 提供了一套Fluent风格的操作API;
- 提供ZK各种应用场景的抽象封装;
本文主要完成的目标是:Spring PropertyPlaceholderConfigurer配置文件加载器集成ZooKeeper来实现远程配置读取。
配置管理(Configuration Management)。
在集群服务中,可能都会遇到一个问题:那就是当需要修改配置的时候,必须要对每个实例都进行修改,这是一个很繁琐的事情,并且易出错。当然可以使用脚本来解决,但这不是最好的解决办法。
OK,Let's go!
我们先看看项目结构

ZooKeeperPropertyPlaceholderConfigurer.java
继承org.springframework.beans.factory.config.PropertyPlaceholderConfigurer,重写processProperties(beanFactoryToProcess, props)来完成远端配置加载的实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
package org.bigmouth.common.zookeeper.config.spring;
import java.io.UnsupportedEncodingException;
import java.util.Properties;
import org.apache.commons.lang.StringUtils;
import org.bigmouth.common.zookeeper.config.Config;
import org.bigmouth.common.zookeeper.config.ZooKeeperConfig;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
public class ZooKeeperPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
public static final String PATH = "zoo.paths" ;
@Override
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props)
throws BeansException {
super .processProperties(beanFactoryToProcess, props);
try {
fillCustomProperties(props);
System.out.println(props);
}
catch (Exception e) {
// Ignore
e.printStackTrace();
}
}
private void fillCustomProperties(Properties props) throws Exception {
byte [] data = getData(props);
fillProperties(props, data);
}
private void fillProperties(Properties props, byte [] data) throws UnsupportedEncodingException {
String cfg = new String(data, "UTF-8" );
if (StringUtils.isNotBlank(cfg)) {
// 完整的应该还需要处理:多条配置、value中包含=、忽略#号开头
String[] cfgItem = StringUtils.split(cfg, "=" );
props.put(cfgItem[ 0 ], cfgItem[ 1 ]);
}
}
private byte [] getData(Properties props) throws Exception {
String path = props.getProperty(PATH);
Config config = new ZooKeeperConfig();
return config.getConfig(path);
}
} |
Config.java
配置操作接口
1
2
3
4
5
6
7
|
package org.bigmouth.common.zookeeper.config;
public interface Config {
byte [] getConfig(String path) throws Exception;
} |
Startup.java
程序启动入口
1
2
3
4
5
6
7
8
9
10
11
12
|
package org.bigmouth.common.zookeeper.config;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Startup {
public static void main(String[] args) {
new ClassPathXmlApplicationContext( "classpath:/config/applicationContext.xml" );
}
} |
ZooKeeperConfig.java
配置操作接口ZooKeeper的实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
package org.bigmouth.common.zookeeper.config;
import org.apache.curator.framework.CuratorFramework;
import org.apache.zookeeper.data.Stat;
public class ZooKeeperConfig implements Config {
@Override
public byte [] getConfig(String path) throws Exception {
CuratorFramework client = ZooKeeperFactory.get();
if (!exists(client, path)) {
throw new RuntimeException( "Path " + path + " does not exists." );
}
return client.getData().forPath(path);
}
private boolean exists(CuratorFramework client, String path) throws Exception {
Stat stat = client.checkExists().forPath(path);
return !(stat == null );
}
} |
ZooKeeperFactory.java
管理ZooKeeper客户端连接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
package org.bigmouth.common.zookeeper.config;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
public class ZooKeeperFactory {
public static final String CONNECT_STRING = "172.16.3.42:2181,172.16.3.65:2181,172.16.3.24:2181" ;
public static final int MAX_RETRIES = 3 ;
public static final int BASE_SLEEP_TIMEMS = 3000 ;
public static final String NAME_SPACE = "cfg" ;
public static CuratorFramework get() {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(BASE_SLEEP_TIMEMS, MAX_RETRIES);
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString(CONNECT_STRING)
.retryPolicy(retryPolicy)
.namespace(NAME_SPACE)
.build();
client.start();
return client;
}
} |
applicationContext.xml
配置加载器使用我们自己创建的ZooKeeperPropertyPlaceholderConfigurer,因为它重写了processProperties方法。这个方法里会去读取远程配置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<? xml version = "1.0" encoding = "UTF-8" ?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> < beans >
< bean class = "org.bigmouth.common.zookeeper.config.spring.ZooKeeperPropertyPlaceholderConfigurer" >
< property name = "systemPropertiesModeName" value = "SYSTEM_PROPERTIES_MODE_OVERRIDE" />
< property name = "ignoreResourceNotFound" value = "true" />
< property name = "locations" >
< list >
< value >classpath:application.properties</ value >
</ list >
</ property >
</ bean >
</ beans >
|
application.properties
项目配置文件,里面除了配置ZooKeeper服务器地址和读取的节点以外,其他所有的配置都应该保存在ZooKeeper中。
1
|
zoo.paths=/properties |
设置ZooKeeper数据
登录ZooKeeper中为节点 /cfg/properties 添加一条配置项:
如图所示:我创建了一个节点 /cfg/properties 并设置内容为:jdbc.driver=org.postgresql.Driver
运行Startup.java
OK 了,zoo.paths是本地application.properties文件中的,jdbc.driver是远程ZooKeeper服务器中的。
项目中需要依赖的jar包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
< dependency >
< groupId >commons-lang</ groupId >
< artifactId >commons-lang</ artifactId >
< version >2.4</ version >
</ dependency >
< dependency >
< groupId >org.springframework</ groupId >
< artifactId >spring-core</ artifactId >
< version >3.0.3.RELEASE</ version >
</ dependency >
< dependency >
< groupId >org.springframework</ groupId >
< artifactId >spring-context</ artifactId >
< version >3.0.3.RELEASE</ version >
</ dependency >
< dependency >
< groupId >org.springframework</ groupId >
< artifactId >spring-tx</ artifactId >
< version >3.0.3.RELEASE</ version >
</ dependency >
< dependency >
< groupId >org.springframework</ groupId >
< artifactId >spring-context-support</ artifactId >
< version >3.0.3.RELEASE</ version >
</ dependency >
<!-- ZooKeeper --> < dependency >
< groupId >org.apache.zookeeper</ groupId >
< artifactId >zookeeper</ artifactId >
< version >3.4.6</ version >
</ dependency >
< dependency >
< groupId >org.apache.curator</ groupId >
< artifactId >curator-framework</ artifactId >
< version >2.4.2</ version >
</ dependency >
< dependency >
< groupId >org.apache.curator</ groupId >
< artifactId >curator-recipes</ artifactId >
< version >2.4.2</ version >
</ dependency >
|