Zookeeper的数据发布与订阅模式

Zookeeper作为配置中心,采用推拉结合的发布订阅模式,实现数据的动态更新。客户端注册关注节点,服务端在数据变更时发送Watcher事件通知,客户端主动获取最新数据。常见应用包括配置管理和服务发现,例如全局配置信息的更新和分布式服务地址的动态获取。工作服务器向服务器列表注册信息,配置节点存储全局配置,变化时通知订阅客户端更新配置。

  数据发布/订阅(Publish/Subscribe)系统,即所谓的配置中心,即发布者将数据发布到ZooKeeper的一个或一系列节点上,供订阅者进行数据订阅,进而达到动态获取数据的目的,实现配置信息的集中式管理和数据的动态更新。
  Zookeeper 作为一个分布式的服务框架,主要用来解决分布式集群中应用系统的一致性问题,它能提供基于类似于文件系统的目录节点树方式的数据存储, Zookeeper 作用主要是用来维护和监控存储的数据的状态变化,通过监控这些数据状态的变化,从而达到基于数据的集群管理。
  发布/订阅系统一般有两种设计模式,分别是推(Push)模式和拉(Pull)模式。ZooKeeper采用的是推拉相结合的方式:客户端向服务端注册自己需要关注的节点,一旦该节点的数据发生变更,那么服务端就会向相应的客户端发送Watcher事件通知,客户端接收到这个消息通知后,需要主动到服务端获取最新的数据。
   ZooKeeper的消息订阅及发布最典型的应用实例是作为系统的配置中心,发布者将数据发布到ZooKeeper节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新。例如全局的配置信息,服务式服务框架的服务地址列表等就非常适合使用。
   ZooKeeper主要有两种监听方式,监听子节点状态,监听节点数据。work server节点可存储应用服务器元数据(如:服务器ip和端口等信息),查询server节点可获取服务器列表,创建ZooKeeper客户端监听server节点的子节点状态,在删除对应work server子节点或增加对应work server 子节点时,ZooKeeper客户端可及时获得通知并进行处理。config节点可存储分布式集群的全局配置信息,在全局配置信息需要修改时,可将配置信息发布到config节点下,所有对config节点数据进行监听的ZooKeeper客户端可及时收到通知并对服务器的配置信息进行修改。

  1. 发布订阅模式可以看成一对多的关系:多个订阅者对象同时监听一个主题对象,这个主题对象在自身状态发生变化时,会通知所有的订阅者对象,使他们能够自动的更新自己的状态
  2. 发布订阅模式,可以让发布方和订阅方,独立封装,独立改变,当一个对象的改变,需要同时改变其他的对象,而且它不知道有多少个对象需要改变时,可以使用发布订阅模式
  3. 发布订阅模式在分布式系统的典型应用有,配置管理和服务发现。
       配置管理:是指如果集群中机器拥有某些相同的配置,并且这些配置信息需要动态的改变,可以使用发布订阅模式,对配置文件做统一的管理,让这些机器各自订阅配置文件的改变,当配置文件发生改变的时候这些机器就会得到通知,把自己的配置文件更新为最新的配置
       服务发现:是指对集群中的服务上下线做统一的管理,每个工作服务器都可以作为数据的发布方,向集群注册自己的基本信息,而让模型机器作为订阅方,订阅工作服务器的基本信息,当工作服务器的基本信息发生改变时如上下线,服务器的角色和服务范围变更,监控服务器就会得到通知,并响应这些变化。

发布订阅的架构图
在这里插入图片描述
Manager Server的工作流程
在这里插入图片描述
Work Server的工作流程
在这里插入图片描述
发布订阅程序的结构图
在这里插入图片描述
程序代码实现:
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<String> 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<String> 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<ZkClient>  clients = new ArrayList<ZkClient>();  
        //用来存储所有的workservers  
        List<WorkServer>  workServers = new ArrayList<WorkServer>();  
        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();
			}
		}
    }
}
  • 应用中用到的一些配置信息放到ZK上进行集中管理。这类场景通常是这样:应用在启动的时候会主动来获取一次配置,同时,在节点上注册一个Watcher,这样一来,以后每次配置有更新的时候,都会实时通知到订阅的客户端,从来达到获取最新配置信息的目的
  • 分布式搜索服务中,索引的元信息和服务器集群机器的节点状态存放在ZK的一些指定节点,供各个客户端订阅使用
  • 分布式日志收集系统。这个系统的核心工作是收集分布在不同机器的日志。收集器通常是按照应用来分配收集任务单元,因此需要在ZK上创建一个以应用名作为path的节点P,并将这个应用的所有机器ip,以子节点的形式注册到节点P上,这样一来就能够实现机器变动的时候,能够实时通知到收集器调整任务分配
  • 系统中有些信息需要动态获取,并且还会存在人工手动去修改这个信息的发问。通常是暴露出接口,例如JMX接口,来获取一些运行时的信息。引入ZK之后,就不用自己实现一套方案了,只要将这些信息存放到指定的ZK节点上即可
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值