ZooKeeper 典型应用场景-数据发布与订阅
ZooKeeper 是一个高可用的分布式数据管理与系统协调框架。基于对 Paxos 算法的实现,使该框架保证了分布式环境中数据的强一致性,也正是基于这样的特性,使得 ZooKeeper 可以解决很多分布式问题。
随着互联网系统规模的不断扩大,大数据时代飞速到来,越来越多的分布式系统将 ZooKeeper 作为核心组件使用,如 Hadoop、Hbase、Kafka、Storm等,因此,正确理解 ZooKeeper 的应用场景,对于 ZooKeeper 的使用者来说显得尤为重要。本节主要将重点围绕数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master选举、分布式锁和分布式队列等方面来讲解 ZooKeeper 的典型应用场景及实现。
1、数据发布/订阅
发布/订阅模式是一对多的关系,多个订阅者对象同时监听某一主题对象,这个主题对象在自身状态发生变化时会通知所有的订阅者对象。使它们能自动的更新自己的状态。发布/订阅可以使得发布方和订阅方独立封装、独立改变。当一个对象的改变需要同时改变其他对象,而且它不知道具体有多少对象需要改变时可以使用发布/订阅模式。发布/订阅模式在分布式系统中的典型应用有配置管理和服务发现、注册。
配置管理是指如果集群中的机器拥有某些相同的配置并且这些配置信息需要动态的改变,我们可以使用发布/订阅模式把配置做统一集中管理,让这些机器格子各自订阅配置信息的改变,当配置发生改变时,这些机器就可以得到通知并更新为最新的配置。
服务发现、注册是指对集群中的服务上下线做统一管理。每个工作服务器都可以作为数据的发布方向集群注册自己的基本信息,而让某些监控服务器作为订阅方,订阅工作服务器的基本信息,当工作服务器的基本信息发生改变如上下线、服务器角色或服务范围变更,监控服务器可以得到通知并响应这些变化。
1.1、配置管理
所谓的配置中心,顾名思义就是发布者将数据发布到 ZooKeeper 的一个或一系列节点上,供订阅者进行数据订阅,进而达到动态获取数据的目的,实现配置信息的集中式管理和数据的动态更新。
发布/订阅系统一般有两种设计模式,分别是推(Push)模式和拉(Pull)模式。
- 推模式
服务端主动将数据更新发送给所有订阅的客户端。
- 拉模式
客户端通过采用定时轮询拉取。
ZooKeeper采用的是推拉相结合的方式:客户端向服务端注册自己需要关注的节点,一旦该节点的数据发生变更,那么服务端就会向相应的客户端发送Watcher事件通知,客户端接收到这个消息通知之后,需要主动到服务端获取最新的数据。
如果将配置信息存放到ZK上进行集中管理,那么通常情况下,应用在启动的时候会主动到ZK服务器上进行一次配置信息的获取,同时,在指定上注册一个Watcher监听,这样一来,但凡配置信息发生变更,服务器都会实时通知所有订阅的客户端,从而达到实时获取最新配置信息的目的。
下面我们通过一个“配置管理”的实际案例来展示ZK在“数据发布/订阅”场景下的使用方式。
在我们平常的应用系统开发中,经常会碰到这样的需求:系统中需要使用一些通用的配置信息,例如机器列表信息、运行时的开关配置、数据库的配置信息等。这些全局配置信息通常具备以下特性:
1)、数据量通常比较小
2)、数据内容在运行时会发生变化
3)、集群中各机器共享、配置一致
对于这类配置信息,一般的做法通常可以选择将其存储的本地配置文件或是内存变量中。无论采取哪种配置都可以实现相应的操作。但是一旦遇到集群规模比较大的情况的话,两种方式就不再可取。而我们还需要能够快速的做到全部配置信息的变更,同时希望变更成本足够小,因此我们需要一种更为分布式的解决方案。
接下来我们以“数据库切换”的应用场景展开,看看如何使用ZK来实现配置管理。
配置存储
在进行配置管理之前,首先我们需要将初始化配置存储到ZK上去,一般情况下,我们可以在ZK上选取一个数据节点用于配置的存储,我们将需要集中管理的配置信息写入到该数据节点中去。
配置获取
集群中每台机器在启动初始化阶段,首先会从上面提到的ZK的配置节点上读取数据库信息,同时,客户端还需要在该配置节点上注册一个数据变更的Watcher监听,一旦发生节点数据变更,所有订阅的客户端都能够获取数据变更通知。
配置变更
在系统运行过程中,可能会出现需要进行书局切换的情况,这个时候就需要进行配置变更。借助ZK,我们只需要对ZK上配置节点的内容进行更新,ZK就能够帮我们将数据变更的通知发送到各个客户端,每个客户端在接收到这个变更通知后,就可以重新进行最新数据的获取。
1.2、服务发现、注册
1.3、综合例子
架构图:

Manage Server 程序主体流程:

Work Server 程序主体流程:

系统的核心类:

1.4、程序代码实现
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 org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.exception.ZkNoNodeException; import com.alibaba.fastjson.JSON; public class WorkServer { private ZkClient zkClient;
private String configPath;
private String serversPath;
private ServerData serverData;
private ServerConfig serverConfig;
private IZkDataListener dataListener; public WorkServer(String configPath, String serversPath, ServerData serverData, ZkClient zkClient, ServerConfig initConfig) {
this.zkClient = zkClient;
this.configPath = configPath;
this.serversPath = serversPath;
this.serverData = serverData;
this.serverConfig = initConfig;
this.dataListener = new IZkDataListener() {
public void handleDataDeleted(String dataPath) throws Exception { } public void handleDataChange(String dataPath, Object data)
throws Exception {
String retJson = new String((byte[]) data);
ServerConfig serverConfigLocal = (ServerConfig) JSON.parseObject(retJson, ServerConfig.class);
updateConfig(serverConfigLocal);
System.out.println("new Work server config is:" + serverConfig.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() {
registMe();
zkClient.subscribeDataChanges(configPath, dataListener);
} private void registMe() {
String mePath = serversPath.concat("/").concat(serverData.getAddress()); try {
zkClient.createEphemeral(mePath, JSON.toJSONString(serverData)
.getBytes());
} catch (ZkNoNodeException e) {
zkClient.createPersistent(serversPath, true);
registMe();
}
} private void updateConfig(ServerConfig serverConfig) {
this.serverConfig = serverConfig;
}
}
import java.util.List; 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; import com.alibaba.fastjson.JSON; public class ManageServer { private String serversPath;
private String commandPath;
private String configPath;
private ZkClient zkClient;
private ServerConfig config;
private IZkChildListener childListener;
private IZkDataListener dataListener;
private List<String> workServerList; public ManageServer(String serversPath, String commandPath,
String configPath, ZkClient zkClient, ServerConfig config) {
this.serversPath = serversPath;
this.commandPath = commandPath;
this.zkClient = zkClient;
this.config = config;
this.configPath = configPath;
this.childListener = new IZkChildListener() { public void handleChildChange(String parentPath,
List<String> currentChilds) throws Exception {
workServerList = currentChilds; System.out.println("work server list changed, new list is ");
execList();
}
};
this.dataListener = new IZkDataListener() {
public void handleDataDeleted(String dataPath) throws Exception {
// ignore;
} public void handleDataChange(String dataPath, Object data)
throws Exception {
String cmd = new String((byte[]) data);
System.out.println("cmd:" + cmd);
exeCmd(cmd);
}
};
} private void initRunning() {
zkClient.subscribeDataChanges(commandPath, dataListener);
zkClient.subscribeChildChanges(serversPath, childListener);
} /*
* 1: list 2: create 3: modify
*/
private void exeCmd(String 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);
}
} private void execList() {
System.out.println(workServerList.toString());
} private void execCreate() {
if (!zkClient.exists(configPath)) {
try {
zkClient.createPersistent(configPath, JSON.toJSONString(config)
.getBytes());
} catch (ZkNodeExistsException e) {
zkClient.writeData(configPath, JSON.toJSONString(config)
.getBytes());
} catch (ZkNoNodeException e) {
String parentDir = configPath.substring(0,
configPath.lastIndexOf('/'));
zkClient.createPersistent(parentDir, true);
execCreate();
}
}
} private void execModify() {
config.setDbUser(config.getDbUser() + "_modify"); try {
zkClient.writeData(configPath, JSON.toJSONString(config).getBytes());
} catch (ZkNoNodeException e) {
execCreate();
}
} public void start() {
initRunning();
} public void stop() {
zkClient.unsubscribeChildChanges(serversPath, childListener);
zkClient.unsubscribeDataChanges(commandPath, dataListener);
}
}
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List; import com.sql.zookeeper.common.ZookeeperConstant;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.serialize.BytesPushThroughSerializer; public class SubscribeZkClient { private static final int CLIENT_QTY = 5; 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) throws Exception { List<ZkClient> clients = new ArrayList<ZkClient>();
List<WorkServer> workServers = new ArrayList<WorkServer>();
ManageServer manageServer = null; try {
ServerConfig initConfig = new ServerConfig();
initConfig.setDbPwd("123456");
initConfig.setDbUrl("jdbc:mysql://localhost:3306/mydb");
initConfig.setDbUser("root"); ZkClient clientManage = new ZkClient(ZookeeperConstant.ZK_CONNECTION_STRING, 5000, 5000, new BytesPushThroughSerializer());
manageServer = new ManageServer(SERVERS_PATH, COMMAND_PATH, CONFIG_PATH, clientManage, initConfig);
manageServer.start(); for (int i = 0; i < CLIENT_QTY; ++i) {
ZkClient client = new ZkClient(ZookeeperConstant.ZK_CONNECTION_STRING, 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(CONFIG_PATH, SERVERS_PATH, serverData, client, initConfig);
workServers.add(workServer);
workServer.start();
}
System.out.println("敲回车键退出!\n");
new BufferedReader(new InputStreamReader(System.in)).readLine(); } finally {
System.out.println("Shutting down..."); for (WorkServer workServer : workServers) {
try {
workServer.stop();
} catch (Exception e) {
e.printStackTrace();
}
}
for (ZkClient client : clients) {
try {
client.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
启动SubscribeZkClient
在zookeeper客户端上输出命令
managerServer订阅了commod的变化后,输出变化
ZooKeeper 典型应用场景-数据发布与订阅的更多相关文章
- Zookeeper应用之一:数据发布与订阅初体验
Zookeeper到底是什么?可以从Zookeeper提供的功能来理解.本篇小作文就是使用其提供的功能之一:数据发布与订阅. 需求:服务端开启多个实例提供服务,客户端使用服务.如果服务端某个服务下线或 ...
- ZooKeeper典型应用场景
ZooKeeper典型应用场景一览 数据发布与订阅(配置中心) 发布与订阅模型,即所谓的配置中心,顾名思义就是发布者将数据发布到ZK节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新.例 ...
- ZooKeeper典型应用场景一览
原文地址:http://jm-blog.aliapp.com/?p=1232 ZooKeeper典型应用场景一览 数据发布与订阅(配置中心) 发布与订阅模型,即所谓的配置中心,顾名思义就是发布者将数据 ...
- ZooKeeper典型应用场景(转)
ZooKeeper是一个高可用的分布式数据管理与系统协调框架.基于对Paxos算法的实现,使该框架保证了分布式环境中数据的强一致性,也正是基于这样的特性,使得ZooKeeper解决很多分布式问题.网上 ...
- ZooKeeper典型应用场景概览
ZooKeeper是一个高可用的分布式数据管理与系统协调框架.基于对Paxos算法的实现,使该框架保证了分布式环境中数据的强一致性,也正是基于这样的特性,使得ZooKeeper解决很多分布式问题.网上 ...
- ZooKeeper 典型应用场景
Zookeeper基础知识 1.zookeeper是一个类似hdfs的树形文件结构,zookeeper可以用来保证数据在(zk)集群之间的数据的事务性一致. 2.zookeeper有watch事件,是 ...
- ZooKeeper典型使用场景一览
场景类别 典型场景描述(ZK特性,使用方法) 应用中的具体使用 数据发布与订阅 发布与订阅即所谓的配置管理,顾名思义就是将数据发布到zk节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新 ...
- 搞懂分布式技术6:Zookeeper典型应用场景及实践
搞懂分布式技术6:Zookeeper典型应用场景及实践 一.ZooKeeper典型应用场景实践 ZooKeeper是一个高可用的分布式数据管理与系统协调框架.基于对Paxos算法的实现,使该框架保证了 ...
- ZOOKEEPER典型应用场景解析
zookeeper实现了主动通知节点变化,原子创建节点,临时节点,按序创建节点等功能.通过以上功能的组合,zookeeper能够在分布式系统中组合出很多上层功能.下面就看几个常用到的场景,及使用方式和 ...
随机推荐
- ASP.Net Core 发布ABP项目遇到的错误
1.HTTP 错误 500.19 - Internal Server Error 无法访问请求的页面,因为该页的相关配置数据无效. 与ASP.NET时代不同,ASP.NET Core不再是由IIS工作 ...
- 打ms15-034补丁出现“此更新 不适用于您的计算机”
1.MS15-034漏洞的补丁是KB3042553; 2.如果在一台Windows Server 2012 R2的服务器上直接安装补丁文件KB3042553,可能会出现“此更新 不适用于您的计算机”的 ...
- sublime Text 3 官方版 3114 注册码
—– BEGIN LICENSE —– Anthony Sansone Single User License EA7E-878563 28B9A648 42B99D8A F2E3E9E0 16DE0 ...
- 深入理解JavaScript系列(11):执行上下文(Execution Contexts)
简介 从本章开始,我将陆续(翻译.转载.整理)http://dmitrysoshnikov.com/网站关于ECMAScript标标准理解的好文. 本章我们要讲解的是ECMAScript标准里的执行上 ...
- 【Java集合】LinkedList详解前篇
[Java集合]LinkedList详解前篇 一.背景 最近在看一本<Redis深度历险>的书籍,书中第二节讲了Redis的5种数据结构,其中看到redis的list结构时,作者提到red ...
- ubuntu系统没有声音解决方法
好像装了个放视频的软件,就没有声音了.后面网上搜到了一个简单粗暴的办法,效果很明显,改变权限后直接就有声音了. -------------------------------------------- ...
- spring集成JPA的三种方法配置
JPA是Java EE5规范之一,是一个orm规范,由厂商来实现该规范.目前有hibernate,OpenJPA,TopLink和EclipseJPA等实现 spring提供三种方法集成JPA:1.L ...
- 多结果集IMultipleResult接口
在某些任务中,需要执行多条sql语句,这样一次会返回多个结果集,在应用程序就需要处理多个结果集,在OLEDB中支持多结果集的接口是IMultipleResult. 查询数据源是否支持多结果集 并不是所 ...
- 【代码笔记】Java连连看项目的实现(2)——JTable 、TableModel的使用
博客有时间就写写,所以一篇可能会拆成很多篇,写完后计划再合在一起. 首先肯定是要实现连连看的界面. 先准备连连看要的图片.. “LianLianKan”就是项目名称. 当然,如果小白看我的博客想学到什 ...
- 关于i 标签盛放背景图像
1.html部分 <div class="hover right"> <i class="log_change state_psd">& ...