Zookeeper 作为一个分布式的服务框架,主要用来解决分布式集群中应用系统的一致性问题,它能提供基于类似于文件系统的目录节点树方式的数据存储, Zookeeper 作用主要是用来维护和监控存储的数据的状态变化,通过监控这些数据状态的变化,从而达到基于数据的集群管理。
zookeeper以其强大的灵活性著称,广泛应用于分布式集群的协调服务,首先简单介绍下zookeeper 主要应用场景之一,消息订阅和发布,下篇文章中我会详细介绍zookeeper另一个应用场景,负载均衡。
消息的订阅和发布
zookeeper的消息订阅及发布最典型的应用实例是作为系统的配置中心,发布者将数据发布到ZK节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新。例如全局的配置信息,服务式服务框架的服务地址列表等就非常适合使用。
zookeeper 主要有两种监听方式,监听子节点状态,监听节点数据。下图中,work server节点可存储应用服务器元数据(如:服务器ip和端口等信息),查询server节点可获取服务器列表,创建zookeeper客户端监听server节点的字节点状态,在应用服务器暂停使用(删除对应work server子节点)或增加应用服务器时(增加对应work server 子节点)时,zookeeper客户端可获及时的通知并进行处理。config节点可存储分布式集群的全局配置信息,在全局配置信息需要修改时,可将配置信息发布到config节点下,所有对config节点数据进行监听的zookeeper客户端可及时收到通知并对服务器的配置信息进行修改。
代码
managerServer主要监听commend节点的数据,及servers子节点的状态,在commend节点数据发生变化时,
将commend节点的数据发布的config 节点上。
import java.util.List;
import org.I0Itec.zkclient.IZkChildListener;
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import com.alibaba.fastjson.JSON;
public class ManagerServer {
private String serversPath;
private String commendPath;
private String configPath;
private ZkClient zkClient;
private ServerConfig serverConfig;
//用于监听zookeeper中servers节点的子节点列表变化
private IZkChildListener childListener;
//用于监听zookeeper中command节点的数据变化
private IZkDataListener dataListener;
//工作服务器的列表
private List workServerList;
public ManagerServer(String serversPath,String commendPath,String configPath,ZkClient zkClient,ServerConfig serverConfig){
this.serversPath=serversPath;
this.commendPath=commendPath;
this.configPath=configPath;
this.zkClient=zkClient;
this.serverConfig=serverConfig;
this.childListener=new IZkChildListener() {
public void handleChildChange(String parentPath, List currentChilds)
throws Exception {
System.out.println("work服务器列表发生变化");
workServerList = currentChilds;
execList();
}
};
this.dataListener=new IZkDataListener() {
public void handleDataDeleted(String dataPath) throws Exception {
// TODO Auto-generated method stub
}
public void handleDataChange(String dataPath, Object data) throws Exception {
System.out.println("------------------command节点数据发生改变------------------");
String cmdType = new String((byte[]) data);
System.out.println("cmd:"+cmdType);
if ("list".equals(cmdType)) {
execList();
} else if ("create".equals(cmdType)) {
execCreate();
} else if ("modify".equals(cmdType)) {
execModify();
} else {
System.out.println("error command!" + cmdType);
}
}
};
}
public void start() {
initRunning();
}
public void stop() {
//取消订阅command节点数据变化和servers节点的列表变化
zkClient.unsubscribeChildChanges(serversPath, childListener);
zkClient.unsubscribeDataChanges(commendPath, dataListener);
}
/**
* 初始化
*/
private void initRunning() {
//执行订阅command节点数据变化和servers节点的列表变化
zkClient.subscribeDataChanges(commendPath, dataListener);
System.out.println("--------------managerServer监听command节点-------------");
zkClient.subscribeChildChanges(serversPath, childListener);
System.out.println("--------------managerServer监听服务器servers子节点-------------");
}
private void execModify() {
serverConfig.setDbUser(serverConfig.getDbUser() + "_modify");
zkClient.writeData(configPath, JSON.toJSONString(serverConfig).getBytes());
}
private void execCreate() {
if(!zkClient.exists(configPath)){
zkClient.createPersistent(configPath,JSON.toJSONString(serverConfig).getBytes());
}
}
private void execList() {
System.out.println(workServerList.toString());
}
}
workserver是监听config节点数据,在启动时到servers节点下注册服务器信息,并且监听config节点的数据变化.
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.exception.ZkException;
import org.I0Itec.zkclient.exception.ZkInterruptedException;
import org.I0Itec.zkclient.exception.ZkNoNodeException;
import com.alibaba.fastjson.JSON;
public class WorkServer {
private String serversPath;
private String configPath;
private ZkClient zkClient;
private ServerConfig config;
private ServerData serverData;
private IZkDataListener dataListener;//数据监听器
public WorkServer(String serversPath,String configPath,ZkClient zkClient,ServerConfig config,ServerData serverData){
this.serversPath=serversPath;
this.configPath=configPath;
this.zkClient=zkClient;
this.config=config;
this.serverData=serverData;
this.dataListener=new IZkDataListener() {
public void handleDataDeleted(String dataPath) throws Exception {
// TODO Auto-generated method stub
}
public void handleDataChange(String dataPath, Object data) throws Exception {
String retJson=new String((byte[])data);
ServerConfig serverConfigLocal = (ServerConfig)JSON.parseObject(retJson,ServerConfig.class);
updataConfig(serverConfigLocal);
System.out.println("new work server config is:"+serverConfigLocal.toString());
}
};
}
/**
* 服务的启动
*/
public void start(){
System.out.println("work server start...");
initRunning();
}
/**
* 服务的停止
*/
public void stop(){
System.out.println("work server stop...");
//取消监听
zkClient.unsubscribeDataChanges(configPath, dataListener);
}
/**
* 服务器的初始化
*/
private void initRunning(){
registMeToZookeeper();
//订阅config节点的改变
zkClient.subscribeDataChanges(configPath, dataListener);
System.out.println("------------------------------监听config节点------------------------------");
}
private void updataConfig(ServerConfig serverConfigLocal) {
this.config=serverConfigLocal;
}
private void registMeToZookeeper(){
//向zookeeper中注册自己的过程其实就是向servers节点下注册一个临时节点
//构造临时节点
String mePath = serversPath.concat("/").concat(serverData.getAddress());
//存入是将json序列化
try {
zkClient.createEphemeral(mePath, JSON.toJSONString(serverData).getBytes());
System.out.println("--------------------创建worker服务器子节点 "+serverData.getAddress()+" -----------------------");
} catch (ZkNoNodeException e) {
//没有父节点则创建
zkClient.createPersistent(serversPath, true);
registMeToZookeeper();
}
System.out.println("--------------------向子节点"+serverData.getAddress()+" 存入数据:"+JSON.toJSONString(serverData)+"-----------------------");
}
}
public class ServerConfig {
private String dbUrl;
private String dbPwd;
private String dbUser;
public String getDbUrl() {
return dbUrl;
}
public void setDbUrl(String dbUrl) {
this.dbUrl = dbUrl;
}
public String getDbPwd() {
return dbPwd;
}
public void setDbPwd(String dbPwd) {
this.dbPwd = dbPwd;
}
public String getDbUser() {
return dbUser;
}
public void setDbUser(String dbUser) {
this.dbUser = dbUser;
}
@Override
public String toString() {
return "ServerConfig [dbUrl=" + dbUrl + ", dbPwd=" + dbPwd
+ ", dbUser=" + dbUser + "]";
}
}
public class ServerData {
private String address;
private Integer id;
private String name;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "ServerData [address=" + address + ", id=" + id + ", name="
+ name + "]";
}
}
import java.util.ArrayList;
import java.util.List;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.serialize.BytesPushThroughSerializer;
public class SubscribeZkClient {
//需要多少个workserver
private static final int CLIENT_QTY=5;
private static final String ZOOKEEPER_SERVER="10.151.30.28:2181";
//节点的路径
private static final String CONFIG_PATH = "/config";//配置节点
private static final String COMMAND_PATH = "/command";//命令节点
private static final String SERVERS_PATH = "/servers";//服务器列表节点
public static void main(String[] args) {
//用来存储所有的clients
List clients = new ArrayList();
//用来存储所有的workservers
List workServers = new ArrayList();
ManagerServer manageServer = null;
try {
ServerConfig initConfig = new ServerConfig();
initConfig.setDbPwd("123456");
initConfig.setDbUrl("jdbc:mysql://localhost:3306/mydb");
initConfig.setDbUser("root");
ZkClient clientManage =new ZkClient(ZOOKEEPER_SERVER, 5000, 5000, new BytesPushThroughSerializer());
manageServer=new ManagerServer(SERVERS_PATH, COMMAND_PATH, CONFIG_PATH, clientManage, initConfig);
manageServer.start();
for( int i = 0; i < CLIENT_QTY; ++i ){
ZkClient client = new ZkClient(ZOOKEEPER_SERVER, 5000, 5000, new BytesPushThroughSerializer());
clients.add(client);
ServerData serverData = new ServerData();
serverData.setId(i);
serverData.setName("WorkServer#"+i);
serverData.setAddress("192.168.1."+i);
WorkServer workServer = new WorkServer(SERVERS_PATH, CONFIG_PATH, client, initConfig, serverData);
workServers.add(workServer);
workServer.start();
}
} catch (Exception e) {
e.printStackTrace();
}finally{
manageServer.stop();
for(WorkServer server:workServers){
server.stop();
}
}
// //向command节点下发指令
// ZkClient client=new ZkClient(ZOOKEEPER_SERVER, 5000, 5000, new BytesPushThroughSerializer());
client.createPersistent(COMMAND_PATH, true);
// ManagerServer manageServer=new ManagerServer(SERVERS_PATH, COMMAND_PATH, CONFIG_PATH, client, null);
// manageServer.start();
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
// Object olddata = client.readData(COMMAND_PATH);
// System.out.println(new String((byte[])olddata));
// client.writeData(COMMAND_PATH, "create".getBytes());
// Object data = client.readData(COMMAND_PATH);
// System.out.println(new String((byte[])data));
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
// manageServer.stop();
}
}
注意:在上面提到的应用场景中,有个默认前提是:数据量很小,但是数据更新可能会比较快的场景。