一、Zookeeper实现配置中心原理和操作
二、POM配置文件
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>edu.dongnao.zk</groupId>
<artifactId>zk-study</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>zk-study</name>
<url>http://maven.apache.org</url>
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency> <groupId>net.killa.kept</groupId>
<artifactId>KeptCollections</artifactId>
<version>1.0.0</version>
</dependency>
<dependency> <groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.2.0</version>
</dependency>
<dependency> <groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.13</version>
</dependency>
<dependency> <groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.10</version>
</dependency>
<dependency> <groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.2</version>
<scope>test</scope>
</dependency> </dependencies>
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration> <source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration> </plugin> </plugins> </build></project>
三、配置中心实现类
package edu.dongnao.zk.zkclient;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.stream.Collectors;
import org.I0Itec.zkclient.IZkChildListener;
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.exception.ZkNoNodeException;
import org.I0Itec.zkclient.exception.ZkNodeExistsException;
public class ZkConfigureCenter implements ConfigureWriter, ConfigureReader {
private String confRootPath;
private String confFilePath;
private String fileLockPath;
private static final String default_confRootPath = "/distributeConfigure";
private ZkClient client;
public ZkConfigureCenter() {
this(default_confRootPath);
}
public ZkConfigureCenter(String path) {
if(path == null || path.trim().equals("")) {
throw new IllegalArgumentException("patch不能为空字符串");
}
confRootPath = path;
confFilePath = confRootPath+"/cnfFile";
fileLockPath = confRootPath+"/writeLock";
client = new ZkClient("localhost:2181");
client.setZkSerializer(new MyZkSerializer());
if (!this.client.exists(confFilePath)) {
try {
this.client.createPersistent(confFilePath, true);
} catch (ZkNodeExistsException e) {
}
}
}
private void checkElement(String v) {
if (v == null) throw new NullPointerException();
if("".equals(v.trim())) {
throw new IllegalArgumentException("不能使用空格");
}
if(v.startsWith(" ") || v.endsWith(" ")) {
throw new IllegalArgumentException("前后不能包含空格");
}
}
@Override
public String createCnfFile(String fileName, Properties items) {
checkElement(fileName);
String cfgNode = confFilePath+"/"+fileName;
if(client.exists(cfgNode)) {
throw new IllegalArgumentException("["+fileName+"]文件已存在!");
}
client.createPersistent(cfgNode, true);
if(items == null) {return cfgNode;}
Lock distributeWriteLock = new ZkDistributeImproveLock(fileLockPath+"/"+fileName);
distributeWriteLock.lock();
try {
items.keySet().iterator();
Set<Map.Entry<Object, Object>> entrySet = items.entrySet();
for (Map.Entry<Object, Object> entry : entrySet) {
System.out.println(entry.getKey() + "=" + entry.getValue());
String cfgItemNode = cfgNode +"/"+ entry.getKey().toString();
client.createPersistent(cfgItemNode, entry.getValue());
}
} finally {
distributeWriteLock.unlock();
}
return cfgNode;
}
@Override
public void deleteCnfFile(String fileName) {
checkElement(fileName);
String cfgNode = confFilePath+"/"+fileName;
Lock distributeWriteLock = new ZkDistributeImproveLock(fileLockPath+"/"+fileName);
distributeWriteLock.lock();
try {
client.deleteRecursive(cfgNode);
} finally {
distributeWriteLock.unlock();
}
}
@Override
public void modifyCnfItem(String fileName, Properties items) {
checkElement(fileName);
String cfgNode = confFilePath+"/"+fileName;
if(items == null) {throw new NullPointerException("要修改的配置项不能为空");}
items.keySet().iterator();
Set<Map.Entry<Object, Object>> entrySet = items.entrySet();
Lock distributeWriteLock = new ZkDistributeImproveLock(fileLockPath+"/"+fileName);
distributeWriteLock.lock();
try {
List<String> itemNodes = client.getChildren(cfgNode);
Set<String> existentItemSet = itemNodes.stream().collect(Collectors.toSet());
for (Map.Entry<Object, Object> entry : entrySet) {
System.out.println(entry.getKey() + "=" + entry.getValue());
String itemName = entry.getKey().toString();
String itemData = entry.getValue().toString();
String cfgItemNode = cfgNode + "/" + itemName;
if(existentItemSet.contains(itemName)) {
String itemNodeData = client.readData(cfgItemNode);
if(! eql(itemNodeData, itemData)) {
client.writeData(cfgItemNode, itemData);
}
existentItemSet.remove(itemName);
} else {
client.createPersistent(cfgItemNode, itemData);
}
}
if(!existentItemSet.isEmpty()) {
for(String itemName : existentItemSet) {
String cfgItemNode = cfgNode + "/" + itemName;
client.delete(cfgItemNode);
}
}
} finally {
distributeWriteLock.unlock();
}
}
private boolean eql(String a, String b) {
if(a == null && b == null) {
return true;
}
if(a != null) {
return a.equals(b);
}
if(b != null) {
return b.equals(a);
}
return false;
}
@Override
public Properties loadCnfFile(String fileName) {
if(! fileName.startsWith("/")) {
fileName = confFilePath+"/"+fileName;
}
return loadNodeCnfFile(fileName);
}
private Properties loadNodeCnfFile(String cfgNode) {
checkElement(cfgNode);
if(! client.exists(cfgNode)) {
throw new ZkNoNodeException(cfgNode);
}
List<String> itemNodes = client.getChildren(cfgNode);
if(itemNodes == null || itemNodes.isEmpty()) {
return new Properties();
}
Properties file = new Properties();
itemNodes.stream().forEach((e)->{
String itemNameNode = cfgNode + "/" + e;
String data = client.readData(itemNameNode, true);
file.put(e, data);
});
return file;
}
@Override
public void watchCnfFile(String fileName, ChangeHandler changeHandler) {
if(! fileName.startsWith("/")) {
fileName = confFilePath+"/"+fileName;
}
final String fileNodePath = fileName;
Properties p = loadNodeCnfFile(fileNodePath);
if(p != null) {
int waitTime = 5;
final ScheduledThreadPoolExecutor scheduled = new ScheduledThreadPoolExecutor(1);
scheduled.setRemoveOnCancelPolicy(true);
final List<ScheduledFuture<?>> futureList = new ArrayList<ScheduledFuture<?>>();
Set<Map.Entry<Object, Object>> entrySet = p.entrySet();
for (Map.Entry<Object, Object> entry : entrySet) {
System.out.println("监控:"+fileNodePath+"/"+entry.getKey().toString());
client.subscribeDataChanges(fileNodePath+"/"+entry.getKey().toString(), new IZkDataListener() {
@Override
public void handleDataDeleted(String dataPath) throws Exception {
System.out.println("触发删除:"+dataPath);
triggerHandler(futureList, scheduled, waitTime, fileNodePath, changeHandler);
}
@Override
public void handleDataChange(String dataPath, Object data) throws Exception {
System.out.println("触发修改:"+dataPath);
triggerHandler(futureList, scheduled, waitTime, fileNodePath, changeHandler);
}
});
}
client.subscribeChildChanges(fileNodePath, new IZkChildListener() {
@Override
public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
System.out.println("触发子节点:"+parentPath);
triggerHandler(futureList, scheduled, waitTime, fileNodePath, changeHandler);
}
});
}
}
private void triggerHandler(List<ScheduledFuture<?>> futureList, ScheduledThreadPoolExecutor scheduled, int waitTime, String fileName, ChangeHandler changeHandler) {
if(futureList != null && !futureList.isEmpty()) {
for(int i = 0 ; i < futureList.size(); i++) {
ScheduledFuture<?> future = futureList.get(i);
if(future != null && !future.isCancelled() && !future.isDone()) {
future.cancel(true);
futureList.remove(future);
i--;
}
}
}
ScheduledFuture<?> future = scheduled.schedule(()->{
Properties p = loadCnfFile(fileName);
changeHandler.itemChange(p);
}, waitTime, TimeUnit.SECONDS);
futureList.add(future);
}
}
四、配置读取器
package edu.dongnao.zk.zkclient;
import java.util.Properties;
public interface ConfigureReader {
Properties loadCnfFile(String fileName);
void watchCnfFile(String fileName, ChangeHandler changeHandler);
interface ChangeHandler {
void itemChange(Properties newProp);
}
}
五、配置写入器
package edu.dongnao.zk.zkclient;
import java.util.Properties;
public interface ConfigureWriter {
String createCnfFile(String fileName, Properties items);
void deleteCnfFile(String fileName);
void modifyCnfItem(String fileName, Properties items);
Properties loadCnfFile(String fileName);
}
六、测试类
package edu.dongnao.zk.test.zkclient;
import java.util.Properties;
import edu.dongnao.zk.zkclient.ConfigureReader;
import edu.dongnao.zk.zkclient.ConfigureReader.ChangeHandler;
import edu.dongnao.zk.zkclient.ConfigureWriter;
import edu.dongnao.zk.zkclient.ZkConfigureCenter;
public class ConfigureTest {
public static void main(String[] args) {
ConfigureWriter writer = new ZkConfigureCenter();
String fileName = "trade-application.properties";
writer.deleteCnfFile(fileName);
Properties items = new Properties();
items.put("abc.gc.a", "123");
items.put("abc.gc.b", "3456");
String znodePath = writer.createCnfFile(fileName, items);
System.out.println("new file: "+znodePath);
new Thread(()->{
readCnf();
}).start();
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
items.put("abc.gc.a", "haha");
items.put("abc.gc.c", "xx");
items.remove("abc.gc.b");
writer.modifyCnfItem(fileName, items);
try {
Thread.currentThread().join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void readCnf() {
System.out.println("读取并监听配置文件");
ConfigureReader reader = new ZkConfigureCenter();
String fileName = "trade-application.properties";
Properties p = reader.loadCnfFile(fileName);
System.out.println(p);
reader.watchCnfFile(fileName, new ChangeHandler() {
@Override
public void itemChange(Properties newProp) {
System.out.println("发现数据发生变化:"+ newProp);
}
});
try {
Thread.currentThread().join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}