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