最近在工作中,為了完善公司集群服務的架構,提高可用性,降低運維成本,因此開始學習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
>
|