Zookeeper知识点总结
基础
-
Zookeeper = 文件系统 + 通知机制
-
Apach Hbase和 Apache solr 以及 Dubbo等项目都采用了Zookeeper
-
Zookeeper是一个分布式的、高性能的,开源的分布式系统的协调服务,是Google的Chubby一个开源的实现,是Hadoop 和 Hbase 的重要组件,是一个为分布式应用提供数据一致性服务的软件。具有严格顺序访问控制能力的分布式协调存储服务
-
是一个基于观察者模式设计的分布式服务管理框架,负责存储和管理大家都关心的数据,然后接收观察者Watch的注册,一旦这些数据的状态发生变化,Zookeeper负责通知已经在Zookeeper上注册的那些观察者做出相应的反应,从而实现集群中类似Master/Slave管理模式
-
Zookeeper应用场景:
- 维护配置信息:作为配置中心,保证配置项配置信息一致性,从而达到维护配置信息的效果
- 分布式锁服务:多台服务器运行同一种服务,协调个服务的进度。为了保证当某个服务需要进行同步操作时,Zookeeper可以对该操作加锁
- 集群管理:集群管理各种服务器,当一些服务器加入/移出时会通知给其他正常工作的服务器,以即使调整和分配任务。还对故障服务器诊断并尝试修复
- 生成分布式唯一ID:分库分表后无法再以靠数据库的auto_increment自动生成唯一ID,此时Zookeeper可以在生成新id时创建持久顺序节点,创建操作返回的节点符号,即为新Id,然后把比自己节点小的删除即可。
-
Zookeeper提供了一套分布式集群管理机制,基于层次性的目录树的数据结构(文件系统),并对树中的结点进行有效管理,从而设计出各种分布式数据管理模型,作为分布式系统的沟通调度桥梁。
-
Zookeeper Service看作是班主任,client看作学生,watch看作是微信,config Data看作是通知信息,一处更新处处更新
能干吗:
- 命名服务
- 配置维护
- 集群管理、
- 分布式消息同步和协调机制
- 负载均衡(大多还是用nginx)
- 对Dubbo的支持
怎么玩
- 统一命名服务(Name Service 如 Dubbo服务注册中心)
- 配置管理(Configuration Management,如淘宝考员配置管理框架Diamond)
- 集群管理(Group Membership, 如Hadoop分布式集群管理)
zookeeper配置文件zoo.cfg
-
ls -ltr :该linux命令工作中很常用,作用是按创建顺序显示当前目录下的文件和目录
-
5大参数:
- tickTime=2000:通信心跳数,Zookeeper服务器心跳时间,单位为毫秒。服务器与客户端或服务器之间维持心跳的时间间隔
- initLimit=10:集群中follower跟随着服务器(F) 与leader领导者服务器(L)之间初始连接时能容忍的最多心跳书(tickTime的数量)。即主机和从机连接所需的时间
- syncLimit=5:LF同步通信时限,Leader和Follower之间的最大响应时间单位,加入超过 syncLimit * tickTime,Leader认为Follower死掉,从服务器列表删除Follower
- dataDir=/tmp/zookeeper:数据存放的目录,默认在临时目录temp,一般需要修改到指定目录。
- clientPort=2182 :默认端口为2182
使用
- 进入bin:
./zkServer.sh start
,启动zookeeper服务 -
./zkCli.sh
开启客户端 - zookeeper服务器端的四字命令(在linux下使用而不是zookeeper下):
-
echo ruok | nc 127.0.0.1 2181
:查看zookeeper的服务器端是否准备就绪 -
echo stat | nc 127.0.0.1 2181
:查看zookeeper服务器端状态 -
echo envi | nc 127.0.0.1 2181
:查看zookeeper服务器端环境
-
- create /testNode v1:创建节点 get /testNode :获取节点的data。
- set /testNode v2:覆盖 testNode的value
- get /testNode :查询该节点的数据
- ls/ls2 /testNode :查询该节点信息,ls只显示子节点,ls2显示所有信息
- 删除节点:delete/rmr /testNode
zookeeper数据模型
- Zookeeper所使用的数据模型风格很像文件系统的目录树结构。有点类似windows中注册表的结构
- 有名称、有树节点,有键值对的关系
- 可以看作是一个树形结构的数据库,但又不能存放数据,作用是对分布在不同机器上做名称管理
- Stat结构体:
数据模型znode节点
-
Zookeeper数据模型结构与Unix文件系统很类似,整体看作一棵树,自身维护一套存储数据结构,每个节点是一个ZNode
-
每一个znode默认能存储1MB数据,每个ZNode都可以通过其路径唯一标识
-
create /node1/node2 val :报错,不支持多级创建节点
-
Znode = path + nodeValue + Stat 。即 set /path nodeValue,自带Stat,存放该节点的一些信息
-
znode中的存在类型:持久,临时。但细分要有四种
-
create -s /myNode v2 : -s 表示节点自动从myNode后面添加***,确保该节点不重复
-
create -e /myNode v2 : -e 代表临时节点,重启将失效,默认-p(可不写)表示持久化。-s和-e可同时用。
-
一个节点对应一个应用,节点存储的数据就是应用所需要的配置信息
通话机制:Session + Watch
-
客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变,被删除、子目录节点增加删除)时,zookeeper会通知客户端。
-
Session :通过Java建立Zookeeper会话,此时处于CONNECTING状态,当客户端连接Zookeeper服务成功后进入CONNECTED状态 ,结束状态CLOSED
-
Watch(观察者)
- 异步+回调+的触发机制
- 异步和回调的理解:假如面试我一结束,面试官口渴托我买瓶可乐,如果在我去买可乐过程中面试官一直在等而不继续面试,这叫同步;若在我买水的过程中面试继续进行,这是异步;若买水时发现没有可乐,这时打电话询问面试官说明情况的这个过程,就叫异步回调。
- 客户端 可以在每个znode节点上设置一个Watcher,如果被观察的服务端的znode节点有变更,watch会触发,这和watch所属的客户端将接收到一个通知包被告知节点已经变化,把响应的事件通知给设置Watcher的Client端
- zookeeper里所有读取操作:getData() , getCildren() 和 exists() 都有设置watch的选项
-
watch事件理解
- 1.一次性触发:只监控一次,触发一次后不再有效。一般不多用
- 2.发送客户端
- 3.为数据设置watch
- 4.时序性和一致性
-
Java操作Zookeeper
Maven工程和配置POM
-
依赖
<!-- 注册中心使用的是zookeeper,引入操作zookeeper的客户端 --> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>2.12.0</version> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>RELEASE</version> </dependency>
-
WatchOne 一次性触发watch,数据监控
public class WatchOne {
private static final Logger logger = LoggerFactory.getLogger(WatchOne.class);
private final static String CONNECTSTRING = "192.168.137.133:2181";
private final static int SESSION_TIMEOUT = 50 * 1000;
private final static String PATH = "/atguigu";
private ZooKeeper zk = null;
public ZooKeeper startZK() throws Exception {
return new ZooKeeper(CONNECTSTRING, SESSION_TIMEOUT, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
}
});
}
public void stopZK() throws Exception {
if(zk != null) zk.close();
}
public void createZnode(String nodePath, String nodeValue) throws Exception {
// OPEN_ACL_UNSAFE:类似关闭防火墙
// CreateMode.PERSISTENT: 创建的节点由Java控制是持久的
zk.create(nodePath, nodeValue.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
public String getZnode(String nodePath) throws Exception {
String result = null;
byte[] byteArray = zk.getData(PATH, new Watcher() { // 一次性触发
@Override
public void process(WatchedEvent event) {
try {
trigerValue(PATH);
} catch (Exception e) {
e.printStackTrace();
}
}
}, new Stat());
result = new String(byteArray); // 转为字符串
return result;
}
public String trigerValue(String nodePath) throws Exception {
String result = null;
byte[] byteArray = zk.getData(PATH, false, new Stat());
result = new String(byteArray); // 转为字符串
return result;
}
public static void main(String[] args) throws Exception {
WatchOne watchOne = new WatchOne();
watchOne.setZk(watchOne.startZK());
if(watchOne.getZk().exists(PATH, false) == null){ // 没有该节点才能创建,不然会报错,节点是不能覆盖的
watchOne.createZnode( PATH, "AAA");
String retValue = watchOne.getZnode(PATH);
logger.info("retValue="+retValue);
Thread.sleep(Long.MAX_VALUE);
}else{
logger.info("I have no znode");
}
}
public ZooKeeper getZk() {
return zk;
}
public void setZk(ZooKeeper zk) {
this.zk = zk;
}
}
WatchMore 长久数据监控(常用)
public class WatchMore {
private static final Logger logger = LoggerFactory.getLogger(WatchMore.class);
private final static String CONNECTSTRING = "192.168.137.133:2181";
private final static int SESSION_TIMEOUT = 50 * 1000;
private final static String PATH = "/atguigu";
private ZooKeeper zk = null;
private String oldValue = null;
public ZooKeeper startZK() throws Exception {
return new ZooKeeper(CONNECTSTRING, SESSION_TIMEOUT, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
}
});
}
public void stopZK() throws Exception {
if(zk != null) zk.close();
}
public void createZnode(String nodePath, String nodeValue) throws Exception {
// OPEN_ACL_UNSAFE:类似关闭防火墙
// CreateMode.PERSISTENT: 创建的节点由Java控制是持久的
zk.create(nodePath, nodeValue.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
public String getZnode(String nodePath) throws Exception {
String result = null;
byte[] byteArray = zk.getData(PATH, new Watcher() { // 一次性触发
@Override
public void process(WatchedEvent event) {
try {
trigerValue(PATH);
} catch (Exception e) {
e.printStackTrace();
}
}
}, new Stat());
result = new String(byteArray); // 转为字符串
oldValue = result; // 一开始初始值
return result;
}
// 每次所观察的节点改变就触发
public boolean trigerValue(String nodePath) throws Exception {
String result = null;
byte[] byteArray = zk.getData(PATH, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
try {
trigerValue(nodePath);
} catch (Exception e) {
e.printStackTrace();
}
}
}, new Stat());
result = new String(byteArray); // 转为字符串
String newValue = result;
if(oldValue.equals(newValue)){
logger.info("*********no changes");
return false;
}else{
logger.info("************oldValue:"+oldValue+"\t newValue:"+newValue);
oldValue = newValue;
return true;
}
}
// 监控节点 /atguigu下的数据
public static void main(String[] args) throws Exception {
WatchMore watchOne = new WatchMore();
watchOne.setZk(watchOne.startZK());
if(watchOne.getZk().exists(PATH, false) == null){ // 没有该节点才能创建,不然会报错,节点是不能覆盖的
watchOne.createZnode( PATH, "AAA");
String retValue = watchOne.getZnode(PATH);
logger.info("retValue="+retValue);
Thread.sleep(Long.MAX_VALUE);
}else{
logger.info("I have no znode");
}
}
public ZooKeeper getZk() {
return zk;
}
public void setZk(ZooKeeper zk) {
this.zk = zk;
}
public String getOldValue() {
return oldValue;
}
public void setOldValue(String oldValue) {
this.oldValue = oldValue;
}
}
子节点变化监控(不常用)
package com.xuecheng.manage_course;
import org.apache.zookeeper.*;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
public class WatchChild {
private static final Logger logger = LoggerFactory.getLogger(WatchChild.class);
private final static String CONNECTSTRING = "192.168.137.133:2181";
private final static int SESSION_TIMEOUT = 50 * 1000;
private final static String PATH = "/atguigu";
private ZooKeeper zk = null;
public ZooKeeper startZK() throws Exception {
return new ZooKeeper(CONNECTSTRING, SESSION_TIMEOUT, new Watcher() { // 开启监控
@Override
public void process(WatchedEvent event) {
// 子节点没改变
if(event.getType() == EventType.NodeChildrenChanged && event.getPath().equals(PATH)){
showChildNode(PATH); // 子节点每次改变都会执行该方法
}else{
showChildNode(PATH); // 一开始会注册父亲节点并打印初始子节点
}
}
});
}
public void showChildNode(String nodePath) {
List<String> list = null;
try {
// 获取该节点所有子节点
list = zk.getChildren(PATH, true); // true表示该节点下全部监控,自带连续监控
logger.info("***********"+list);
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
// 监控子节点
public static void main(String[] args) throws Exception {
WatchChild watchChild = new WatchChild();
watchChild.setZk(watchChild.startZK());
Thread.sleep(Long.MAX_VALUE);
}
public ZooKeeper getZk() {
return zk;
}
public void setZk(ZooKeeper zk) {
this.zk = zk;
}
}
Zookeeper集群
-
分为服务器端和客户端,客户端之连接到某个服务器上,客户端使用并维护一个TCP连接,第一次连接会建立会话,当这个客户端连接到另外服务器时,这个会话会被新的服务器重新建立
-
配置项书写格式:server.N = YYY:A:B,其中,N为服务器编号,YYY表示服务器IP地址,A为LF(主从机)通信端口,即该服务器与集群中的leader交换的信息的端口。B为选举端口,即选举新leader时服务器间相互通信的端口(当leader挂掉后,其余服务器会相互通信并选举出新的leader)
-
真集群中每个服务器A端口和B端口都是一样的。
-
伪集群中每个服务器A端口和B端口都是不一样的。但IP一样
-
伪集群做法:
- 分别复制zookeeper目录为zk01、zk02、zk03并分别在配置文件修改以下:
此时使用zk01和zk02作为服务器,zk03连接server作为客户端:./zkCli.sh -server 127.0.0.1:2193
。
此时使用zk03改变节点数据,zk01和zk02便可查到刚改变的数据,完成伪集群
上一篇: 26. for 空行的秘密
下一篇: MySQL-优化order by