ZooKeeper(面试必问)(java编程)
一、概述
Apache ZooKeeper是一个可靠高效的分布式应用的协调服务系统,主要解决分布式系统存在的一些通用问题。比如分布式系统集群管理、集群选举、配置中心、统一的命名服务、分布式同步(分布式锁)等问题。
分布式集群的优点:数据的分布式存储,分布式的并行计算
分布式集群的问题:映射产生的中间节点之间的网络传输,Yarn的HA集群为了不发生单点故障的问题,主备的切换问题
架构
ZooKeeper的特点:一致、有头、数据树
Leader:ZooKeeper集群的主节点
Follower:ZooKeeper集群的从节点
ZAB(ZooKeeper Atomic【原子】 BroadCast【广播】)协议:强一致性协议(不管访问哪个节点都可以拿到相同的数据)
客户端进行一个写操作,可以连接ZooKeeper集群中的任意节点,若Follower接收到写请求,Follower会转发给Leader;若Follower收到读请求,可直接执行。
Leader接收到写请求,会将其包装成一个提案(proposal)对象,并且会给这个提案分配一个事务ID(ZXID)(64位全局递增的整数值),将提案发送给集群中的所有Follower。Follower进行表决,同意返回给Leader一个ack。若半数以上的节点同意提案Leader会进行commit提交操作(将写数据保存到Leader中,再保存到Follower)
Leader挂掉了,【要求半数以上的节点可用】会触发集群选举(选举ZXID最大的Follower作为新的Leader(有头)),节点的数量建议为奇数
CAP定理(分布式系统的基石):一致性,可用性,分区容错(三者只能满足两个,nosql满足2,3;普通关系型数据库满足1,2,ZooKeeper满足1,2)
特点(三个):
数据树指的在ZooKeeper在维护了一个类似于Linux文件系统树形的层次空间
- ZooKeeper的数据树中的一个节点,都称为ZNode
- ZooKeeper的ZNode是一个标识,即是路径标识又可以存放一个简单的数据(1MB)
- ZooKeeper维护一个树形的层次化空间(数据树)
ZNode的类型
- 永久节点(Persistent Node):当ZooKeeper客户端在ZooKeeper服务器写入(创建)一个永久节点,永久存在,只有当客户端在调用删除指令时,永久节点销毁。一旦创建永久存在
- 临时节点【重要】(Ephemeral Node):临时节点的生命周期依赖于创建他的会话(session),一旦会话结束,临时节点自动销毁。临时节点下不允许有子节点
- 顺序节点(Sequential Node):不能单独使用,需要搭配永久或者临时节点使用。创建顺序节点可以自动在节点名后面加一个10位的有序的序号(原子递增的方式生成)。如:创建一个永久顺序节点,节点名叫/app1,会添加有序的序号/app100000000
Watches(监视器)
是ZooKeeper中非常重要的一个组件,可以监视ZNode的改变
- 监视指定的ZNode上的数据的改变
- 监视指定的ZNode的子节点数量的改变
- 状态信息的改变
二、基本使用
环境搭建
准备工作
- 安装并配置JDK8.0
- 准备ZooKeeper的安装包
单机版
配置双网卡
#右键虚拟机点击设置
#点击添加
#点击网络适配器
#点击下一步
#NAT模式 点击完成
#点击确定
#ip a//出现两个网卡,一个ens33,一个ens37,将ens33设置成静态IP,ens37设置成动态IP
#vi /etc/sysconfig/network-scripts/ifcfg-ens33
#将dhcp改成static 添加IPADDR=192.168.108.146 添加NETMASK=255.255.255.0 :x保存退出
#重启服务 systemctl restart network
安装
[root@hadoop ~]# tar -zxf zookeeper-3.4.6.tar.gz -C /usr
[root@hadoop ~]# cd /usr/zookeeper-3.4.6/
[root@hadoop zookeeper-3.4.6]# ll
总用量 1528
drwxr-xr-x. 2 1000 1000 149 2月 20 2014 bin
-rw-rw-r--. 1 1000 1000 82446 2月 20 2014 build.xml
-rw-rw-r--. 1 1000 1000 80776 2月 20 2014 CHANGES.txt
drwxr-xr-x. 2 1000 1000 77 2月 20 2014 conf # zoo.cfg
drwxr-xr-x. 10 1000 1000 130 2月 20 2014 contrib
drwxr-xr-x. 2 1000 1000 4096 2月 20 2014 dist-maven
drwxr-xr-x. 6 1000 1000 4096 2月 20 2014 docs
-rw-rw-r--. 1 1000 1000 1953 2月 20 2014 ivysettings.xml
-rw-rw-r--. 1 1000 1000 3375 2月 20 2014 ivy.xml
drwxr-xr-x. 4 1000 1000 235 2月 20 2014 lib
-rw-rw-r--. 1 1000 1000 11358 2月 20 2014 LICENSE.txt
-rw-rw-r--. 1 1000 1000 170 2月 20 2014 NOTICE.txt
-rw-rw-r--. 1 1000 1000 1770 2月 20 2014 README_packaging.txt
-rw-rw-r--. 1 1000 1000 1585 2月 20 2014 README.txt
drwxr-xr-x. 5 1000 1000 47 2月 20 2014 recipes
drwxr-xr-x. 8 1000 1000 154 2月 20 2014 src
-rw-rw-r--. 1 1000 1000 1340305 2月 20 2014 zookeeper-3.4.6.jar
-rw-rw-r--. 1 1000 1000 836 2月 20 2014 zookeeper-3.4.6.jar.asc
-rw-rw-r--. 1 1000 1000 33 2月 20 2014 zookeeper-3.4.6.jar.md5
-rw-rw-r--. 1 1000 1000 41 2月 20 2014 zookeeper-3.4.6.jar.sha1
配置
[root@hadoop zookeeper-3.4.6]# cd conf
[root@hadoop conf]# cp zoo_sample.cfg zoo.cfg
[root@hadoop conf]# vi zoo.cfg
# 数据存放目录
dataDir=/root/zkdata
启动服务
[root@hadoop zookeeper-3.4.6]# bin/zkServer.sh start conf/zoo.cfg
验证服务是否正常
[root@hadoop zookeeper-3.4.6]# jps
2548 QuorumPeerMain # zk java进程
2597 Jps
[root@hadoop zookeeper-3.4.6]# bin/zkServer.sh status conf/zoo.cfg//查看zookeeper的状态
JMX enabled by default
Using config: conf/zoo.cfg
Mode: standalone # 独立
分布式集群
安装ZooKeeper
[root@node1 ~]# scp zookeeper-3.4.6.tar.gz root@node2:~
zookeeper-3.4.6.tar.gz 100% 17MB 84.3MB/s 00:00
[root@node1 ~]# scp zookeeper-3.4.6.tar.gz root@node3:~
zookeeper-3.4.6.tar.gz 100% 17MB 73.4MB/s 00:00
[root@nodex ~]# tar -zxf zookeeper-3.4.6.tar.gz -C /usr
[root@nodex ~]# vi /usr/zookeeper-3.4.6/conf/zoo.cfg
tickTime=2000
dataDir=/root/zkdata
clientPort=2181
initLimit=5
syncLimit=2
# server.myid=ip地址:zk集群服务间的通信端口:zk集群集群选举的通信端口
server.1=node1:2887:3887
server.2=node2:2887:3887
server.3=node3:2887:3887
[root@nodex ~]# mkdir -p /root/zkdata
# node1执行此指令
[root@node1 ~]# cd zkdata/
[root@node1 zkdata]# vi myid
1
# node2执行此指令
[root@node2 ~]# cd zkdata/
[root@node2 zkdata]# vi myid
2
# node3执行此指令
[root@node3 ~]# cd zkdata/
[root@node3 zkdata]# vi myid
3
# 启动ZooKeeper集群
[root@nodex ~]# /usr/zookeeper-3.4.6/bin/zkServer.sh start /usr/zookeeper-3.4.6/conf/zoo.cfg
JMX enabled by default
Using config: /usr/zookeeper-3.4.6/conf/zoo.cfg
Starting zookeeper ... STARTED
# 确认zookeper服务是否正常:方法一
[root@nodex ~]# jps
1777 QuorumPeerMain
1811 Jps
# 确认zookeper服务是否正常:方法二
[root@nodex ~]# /usr/zookeeper-3.4.6/bin/zkServer.sh status /usr/zookeeper-3.4.6/conf/zoo.cfg
JMX enabled by default
Using config: /usr/zookeeper-3.4.6/conf/zoo.cfg
Mode: leader
指令操作
使用客户端指令连接ZK
[root@hadoop zookeeper-3.4.6]# bin/zkCli.sh -server hadoop:2181
使用帮助命令查看指令列表
[zk: hadoop:2181(CONNECTED) 0] help
ZooKeeper -server host:port cmd args
stat path [watch] # 展示指定路径的znode的状态
set path data [version] # 修改指定路径的znode数据
ls path [watch] # 展示zookeeper的数据树
delquota [-n|-b] path
ls2 path [watch] # 展示指定路径的znode的子节点列表和状态信息
setAcl path acl # acl(access cotrol list) 略
setquota -n|-b val path
history # 历史操作指令
redo cmdno # 重新执行指定编号的指令
printwatches on|off
delete path [version] # 删除指定路径的znode(不能有子znode)
sync path
listquota path
rmr path # 递归删除所有的znode
get path [watch] # 获取指定路径的znode数据
create [-s] [-e] path data acl # 创建zk节点指令(默认类型为永久节点) -s 顺序 -e 临时节点
addauth scheme auth
quit # 退出
getAcl path
close # 关闭连接
connect host:port # 连接指定地址的zk server
Java Driver操作
-
三个比较常用的Java驱动
-
Apache ZooKeeper Java Driver
-
ZKClient
-
Apache Curator
-
-
创建Maven项目并导入Maven依赖
<dependency> <groupId>com.101tec</groupId> <artifactId>zkclient</artifactId> <version>0.8</version> </dependency>
测试代码
基本测试代码
import org.I0Itec.zkclient.ZkClient;
import java.util.Date;
public class ZKClientTest {
public static void main(String[] args) throws InterruptedException {
//1.创建客户端对象
ZkClient client = new ZkClient("hadoop:2181");
//2.调用客户端对象方法完成各种操作
// client.createPersistent("/baizhi/c1/t1", true);
// client.createEphemeral("/baizhi/c2", new Date());
// client.createEphemeralSequential("/baizhi/c3", new Date());
// 判断znode是否存在
// System.out.println(client.exists("/baizhi/c2") ? "/c2存在" : "/c2不存在");
// client.delete("/baizhi/c2");
// System.out.println(client.exists("/baizhi/c2") ? "/c2存在" : "/c2不存在");
// 等价于rmr指令 递归删除
// client.deleteRecursive("/baizhi/c1");
// 等价于get指令
// Object value = client.readData("/baizhi/c2");
// System.out.println("/baizhi/c2:" + value);
// 等价于set指令
// client.writeData("/baizhi/c2",123456);
// 监视器测试(注意:数据改变的监视器 必须通过java api进行写操作才能够正确触发 原因java driver和命令窗口采用的序列化机制不一样的)
client.writeData("/baizhi","ccc");
Thread.sleep(5000);
//3. 释放连接
client.close();
}
}
监视器的测试代码
import org.I0Itec.zkclient.IZkChildListener;
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import java.io.IOException;
import java.util.List;
/**
* zk监视器: 监视节点改变
* 1. 数据改变
* 2. 子节点数量的改变
* 3. 状态信息的改变(略)
*/
public class ZKWatches {
public static void main(String[] args) throws IOException {
//1. 创建zkclient对象
ZkClient client = new ZkClient("hadoop:2181");
// 第一种监视器
// dataChanageWathes(client);
// 第二种监视器
childrenNumChanageWatches(client);
//3. 为了保证主线程不退出
System.in.read();
}
public static void childrenNumChanageWatches(ZkClient client) {
/**
* path: 监视的节点
* childrenList: 改变后的子节点的列表
*/
// client.subscribeChildChanges("/baizhi",(path,childrenList) -> {
//
// });
client.subscribeChildChanges("/baizhi", new IZkChildListener() {
@Override
public void handleChildChange(String s, List<String> list) throws Exception {
System.out.println("监视的节点:" + s);
for (String child : list) {
System.out.println("子节点:" + child);
}
}
});
}
public static void dataChanageWathes(ZkClient client) {
//2. 使用监视器: 数据改变的监视器
client.subscribeDataChanges("/baizhi", new IZkDataListener() {
/**
* 处理数据改变的方法(终端工具上的修改不行,必须是通过javaAPI操作才可以)
* @param s 监视的节点path
* @param o 改变后的数据
* @throws Exception
*/
public void handleDataChange(String s, Object o) throws Exception {
System.out.println("监视的节点为:" + s + ", 改变后的数据为:" + o);
}
/**
* 处理数据删除的方法
* @param s 监视的节点的path
* @throws Exception
*/
public void handleDataDeleted(String s) throws Exception {
System.out.println("删除的节点为:" + s);
}
});
}
}
三、应用场景
统一的命名服务
国产的RPC框架Dubbo,使用ZooKeeper作为服务的注册中心。首先服务提供者在启动时会启动一个Server,并且会将服务的信息以临时节点的形式注册到ZooKeeper中,一旦服务提供者出现意外,它注册临时节点会自动删除。服务消费者在初始化是会自动在指定的ZNode注册子节点数量改变的监视器,一旦发现子节点数量发生改变,即触发通知。使用最新的调用地址列表替换历史的调用地址列表。结论:Apache Dubbo
框架引入注册中心的原因就是为了保证服务消费者能够自动感知服务提供方信息的改变。
RPC远程程序调用框架
Apache Dubbo阿里给Apache 基金会,现在是Apache 顶级框架
服务化系统(微服务):服务和服务之间,面临数据通信的问题
跨虚拟机或者跨服务器都称为RPC
阿里RPC框架:Dubbo,HSF,spring Cloud
百度RPC框架:BRPC
Dubbo架构:(面试重点)
-
Provider(服务提供者):Provider上Container(容器)在启动的时候发布一个服务,将服务信息注册到Registry(注册中心即ZooKeeper);Consumer订阅(绑定一个监视器:监视子节点数量的改变,一旦监视的子ZNode数量发生改变,触发notify(通知)【用最新的调用列表替换历史的调用列表】)Registry,Registry一旦有风吹草动,通知Consumer,服务消费方通过注册中心自动感知服务提供方信息的改变,获得最新的调用地址列表。随意调用(负载均衡),消费者Client(Request调用信息【参数】),Provider通过反射调用本地方法,响应给Consumer
-
Consumer(服务消费者):
-
Registry(注册中心即ZooKeeper):/dubbo/服务接口的全限定名/providers
/ip1:端口(临时节点)
/ip2:端口
-
Dubbo RPC框架使用注册中心的原因:
服务消费方通过注册中心自动感知服务提供方信息的改变
-
国产的RPC框架Dubbo,使用Zookeeper作为注册中心
四、分布式配置中心
tomcat集群:每个web程序都要管理一个数据源配置文件(100个就有100个数据源配置文件)
-
数据源配置信息存放
- 数据库(单点)中,不支持分布式集群存在单点故障问题
- Zookeeper中(永久节点+数据改变的监视器) ,实现配置信息的实时同步(携程的阿波罗在GitHub开源了,热度很高)
- web应用充当一个监视器,监视Zookeeper中某个ZNode数据的改变,返回结果是最新的连接参数
集群的选举(基于Zookeeper的集群选举)
(节点+监视器)
利用ZooKeeper有两个特性,就可以实时另一种集群机器器存活性监控系统:
- 客户端在节点 x 上注册一个Watcher,那么如果 x上的子节点变化了,会通知该客户端。
- 创建EPHEMERAL类型的节点,一旦客户端和服务器的会话结束或过期,那么该节点就会消失
每个Node在启动时都在Zookeeper集群中创建一个临时顺序节点
临时顺序节点+监视器(子节点数量改变的监视器)
选举规则:谁的临时顺序节点的序号最小作为Master(主)
分布式锁(面试)(Curator,zookeeper的另外一个JavaDriver)
Nginx:反向代理服务器(负载均衡处理)
互斥锁(同步锁):只能够在同一个JVM容器中保证临界资源的处理串行化处理,不能够在分布式系统中保证临界资源的处理串行化处理,会导致商品卖超的问题
分布式锁主要是为了解决在分布式系统中对临界资源处理的线程安全问题。
分布式锁实现(面试重点):
- 1、数据库实现:
- 竞争锁,谁抢到锁标记谁就拥有了操作临界资源的锁标记。在数据库的锁表里面插入一条唯一数据,谁插入成功谁就获得了操作临界资源的锁标记(第一次插入可插入,再插就插不进去了)
- 操作完成之后,释放锁标记,保证其他人也能获得锁标记
- 优点:实现简单
- 缺点:效率低(操作数据库),单点故障,死锁(在删除锁时操作失败,锁一致没释放)
- 2、redis实现(基于内存实现,效率高):
- 竞争锁:采用String类型的 setnx(k,v)【k不存在操作成功,k存在不做修改】
- 释放锁:调用String类型的 del(k,v)
- 优点:效率高(内存存储)),不会出现单点故障(哨兵集群),不会出现死锁问题(键值设置生命周期expire time设定)
- 缺点:没有有效的队列机制(一堆人到一个窗口抢,没有等待排队机制)
- 3、Zookeeper实现(企业用的最多)
- 临时顺序节点+子节点数量改变的监视器
- 竞争锁:请求来了在某个节点创建临时顺序节点,谁的临时顺序节点序号最小,谁就获得了锁标记(队列机制)
- 释放锁:会话结束,临时顺序节点被删除,锁释放
- 优点:排队等待机制,不容易死锁(出现问题,会话结束,释放锁标记),不容易出现单点故障,效率仅次于Redis
集群管理
- http://jm.taobao.org/2011/10/08/1232/
- https://www.ibm.com/developerworks/cn/opensource/os-cn-zookeeper/
k不存在操作成功,k存在不做修改】
- 释放锁:调用String类型的 del(k,v)
- 优点:效率高(内存存储)),不会出现单点故障(哨兵集群),不会出现死锁问题(键值设置生命周期expire time设定)
- 缺点:没有有效的队列机制(一堆人到一个窗口抢,没有等待排队机制)
- 3、Zookeeper实现(企业用的最多)
- 临时顺序节点+子节点数量改变的监视器
- 竞争锁:请求来了在某个节点创建临时顺序节点,谁的临时顺序节点序号最小,谁就获得了锁标记(队列机制)
- 释放锁:会话结束,临时顺序节点被删除,锁释放
- 优点:排队等待机制,不容易死锁(出现问题,会话结束,释放锁标记),不容易出现单点故障,效率仅次于Redis
集群管理
- http://jm.taobao.org/2011/10/08/1232/
- https://www.ibm.com/developerworks/cn/opensource/os-cn-zookeeper/