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 典型应用场景-数据发布与订阅的更多相关文章

  1. Zookeeper应用之一:数据发布与订阅初体验

    Zookeeper到底是什么?可以从Zookeeper提供的功能来理解.本篇小作文就是使用其提供的功能之一:数据发布与订阅. 需求:服务端开启多个实例提供服务,客户端使用服务.如果服务端某个服务下线或 ...

  2. ZooKeeper典型应用场景

    ZooKeeper典型应用场景一览 数据发布与订阅(配置中心) 发布与订阅模型,即所谓的配置中心,顾名思义就是发布者将数据发布到ZK节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新.例 ...

  3. ZooKeeper典型应用场景一览

    原文地址:http://jm-blog.aliapp.com/?p=1232 ZooKeeper典型应用场景一览 数据发布与订阅(配置中心) 发布与订阅模型,即所谓的配置中心,顾名思义就是发布者将数据 ...

  4. ZooKeeper典型应用场景(转)

    ZooKeeper是一个高可用的分布式数据管理与系统协调框架.基于对Paxos算法的实现,使该框架保证了分布式环境中数据的强一致性,也正是基于这样的特性,使得ZooKeeper解决很多分布式问题.网上 ...

  5. ZooKeeper典型应用场景概览

    ZooKeeper是一个高可用的分布式数据管理与系统协调框架.基于对Paxos算法的实现,使该框架保证了分布式环境中数据的强一致性,也正是基于这样的特性,使得ZooKeeper解决很多分布式问题.网上 ...

  6. ZooKeeper 典型应用场景

    Zookeeper基础知识 1.zookeeper是一个类似hdfs的树形文件结构,zookeeper可以用来保证数据在(zk)集群之间的数据的事务性一致. 2.zookeeper有watch事件,是 ...

  7. ZooKeeper典型使用场景一览

    场景类别 典型场景描述(ZK特性,使用方法) 应用中的具体使用 数据发布与订阅 发布与订阅即所谓的配置管理,顾名思义就是将数据发布到zk节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新 ...

  8. 搞懂分布式技术6:Zookeeper典型应用场景及实践

    搞懂分布式技术6:Zookeeper典型应用场景及实践 一.ZooKeeper典型应用场景实践 ZooKeeper是一个高可用的分布式数据管理与系统协调框架.基于对Paxos算法的实现,使该框架保证了 ...

  9. ZOOKEEPER典型应用场景解析

    zookeeper实现了主动通知节点变化,原子创建节点,临时节点,按序创建节点等功能.通过以上功能的组合,zookeeper能够在分布式系统中组合出很多上层功能.下面就看几个常用到的场景,及使用方式和 ...

随机推荐

  1. 单个html使用axios调用接口传参

    单个html页面使用axios调用接口传参(没有使用v-cli搭建框架,也没有使用qs等等) 1.使用 URLSearchParams的方法 var params = new URLSearchPar ...

  2. 事件获取目标 currentTarget target srcElement 三者之间的区别和联系

    currentTarget   指的是触发事件的当前对象,可以是冒泡和捕获的对象,不一定是点击或者鼠标移入等事件的直接触发对象.可以是他的父元素等. target 指的是事件触发的直接对象.IE有兼容 ...

  3. Selenium WebDriver的简单操作说明

    [From] http://blog.csdn.net/xiao190128/article/details/49784121 1.打开一个测试浏览器 对浏览器进行操作首先需要打开一个浏览器,接下来才 ...

  4. Linux误挂载到根目录出现问题!!!!!!!!!!!!!!!

    一.背景: 因根目录/空间不大,故而想将另一硬盘挂载到根目录下(后发现此想法很是幼稚): 二.过程: 1.成功输入命令挂载后,发现出现/上被挂了两个东西,且/下剩余空间还是原来一样大,才发现大错特错: ...

  5. vue中使用对非表单元素使用contenteditable的问题

    先说下问题,再上解决方案: span编辑时有多余空格和回车会影响样式(我用的是span便以此为例) 代码:(有换行符) 效果图: 代码:(无换行符) 效果图: 当在span标签的contentedit ...

  6. python 管理多版本之pyenv

    一, [root@management ~]# pyenv install -listAvailable versions:  3.3.0  3.3.1  3.3.2  3.3.3  3.3.4  3 ...

  7. SpringMVC 源码阅读

  8. 如何在vue && webpack 项目中的单文件组件中引入css

    引入方式很简单,就是在script下使用require()即可. 因为import 是import...from 的形式,所以是不需要的. <script> import {mapStat ...

  9. cloudemanager安装时出现ProtocolError: <ProtocolError for 127.0.0.1/RPC2: 401 Unauthorized>问题解决方法(图文详解)

    不多说,直接上干货! 问题详情 查看日志/var/log/cloudera-scm-agent/,得知 解决办法 $> ps -ef | grep supervisord $> kill ...

  10. java里面的package/import 和PHP里面的namespace/use 是一模一样的吗

    java里面的package/import 和PHP里面的namespace/use 是一模一样的吗? java: php package mypage; namespace mypage; impo ...