Zookeeper是分布式应用程序的协调服务框架,是Hadoop的重要组件。ZK要解决的问题:
1.分布式环境下的数据一致性。
2.分布式环境下的统一命名服务
3.分布式环境下的配置管理
4.分布式环境下的分布式锁
单台机器使用的锁:同步代码块、重入锁。但是在分布式环境这个锁就发挥不出来作用。分布锁分为共享锁和排他锁。
5.集群管理问题
分布式的思想就是人多干活快,即用多台机器同时处理一个任务。分布式的编程和单机的编程 思想是不同的,随之也带来新的问题和挑战。
活锁定义:在程序里,由于某些条件的发生碰撞,导致重新执行,再碰撞=》再执 行,如此循环往复,就形成了活锁。活锁的危害:多个线程争用一个资源,但是没有任何一个 线程能拿到这个资源。(死锁是有一个线程拿到资源,但相互等待互不释放造成死锁),活锁 是死锁的变种。补充:活锁更深层次的危害,很耗尽Cpu资源(在做无意义的调度)
扩展:zk是根据Google的一篇论文
《The Chubby lock service for loosely coupled distributed systems》
3.需要考虑集群的管理问题,需要有一套机制来检测到集群里节点的状态变化。(可以用心跳 机制来做,但zk不是用心跳机制来做的)
4.如果用一台机器做集群管理,存在单点故障问题,所以针对集群管理,也需要形成一个集群
5.管理集群里Leader的选举问题(要根据一定的算法和规则来选举),包括要考虑Leader挂掉 之后,如何从剩余的follower里选出Leader
6.分布式锁的实现,用之前学的重入锁,同步代码块是做不了的
1.4 Zookeeper的名字
动物园管理员
实现步骤:
1. 准备虚拟机
克隆一个纯净的虚拟机
连接克隆占用的空间较少,但是母体坏掉的话,克隆出来的虚拟机将不能使用
2.安装和配置jdk
3.上传和安装zk
A.上传目录:/usr/soft 上传 rz 或者使用ssh工具
B.tar -zxvf zookeeper-3.4.7.tar.gz
4.配置zk的配置文件
目录结构:
bin 指令
conf 配置文件
lib 运行jar包库
进入conf目录,执行:
cp zoo_sample.cfg zoo.cfg
5.启动zk
进入bin目录,执行:
sh zkServer.sh start 或者./zkServer.sh start
可以通过jps指令查看活动的java进程
zk的进程是:QuorumPeerMain
6.进入zk客户端,操作zk
进入bin目录,执行: ./zkCli.sh
关闭zk服务:
sh zkServer.sh stop 或者
jps查看zk的进程id
kill -15 zk进程id
Zk数据结构
1. ZK有一个最开始的节点
2. ZK的节点叫做znode节点
3. 每个znode节点都可存储数据
4. 每个znode节点都可创建自己的子节点
5. 多个znode节点共同形成了znode树
6. Znode树的维系实在内存中,目的是供用户快速的查询
7. 每个znode节点都是一个路径(通过路径来定位这个节点)
8. 每个路径名都是唯一的。
ZK指令
指令 |
示例 |
ls查看指令 |
ls / |
create创建节点指令,注意,在创建节点时,要分配初始数据。 |
create /zk01 '' create /zk02 'hello' |
get查看节点数据指令 hello 数据 cZxid = 0x2 ctime = Mon May 15 05:58:32 PDT 2017创建节点的时间戳 mZxid = 0x2 mtime = Mon May 15 05:58:32 PDT 2017修改此节点数据的最新时间戳 pZxid = 0x2 cversion = 0 dataVersion = 0数据版本号,每当数据发生编号,版本号递增1 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 5数据大小 numChildren = 0子节点个数 |
get /zk02 |
set更新节点数据指令(执行后mtime、dataVersion可定会放生变化,dataLength可能会变化) |
set /zk01 hellozk |
delete删除节点:如果存在子节点,不让删除。 只有删除子节点后才能删除。 |
delete /zk01 |
create指令补充: 1. 创建子节点 2. Zk节点分四种类型:分别是: 普通持久节点: 普通临时节点:创建此临时节点的客户端失去和zk连接后,此节点消失.zk是通过临时节点监控哪个服务器挂掉的。
顺序持久节点:会根据用户指定的节点路径,自动分配一个递增的顺序号。(顺序节点实现分布式锁的效果,服务器1抢到zk05分配zk050001,服务器2抢到zk05分配zk050002) 顺序临时节点: |
1. create /zk01/node01 hello 2. 2.1.create /zk01 hello 2.2. create –e /zk02 abc 2.4.create –s -e /zk05 abcd zk050000000003 再创建一个就是: zk050000000004 |
quit退出zk客户端 |
|
实现步骤:
1. 关闭虚拟机的防火墙 service iptables stop(临时关闭)
永久关闭:chkconfigiptables off
2. 创建一个maven工程
修改maven工程的几个参数
(1).修改默认的jdk,从1.5改为1.7
(2).修改JavaCompiler版本,从1.5改为1.7
点击Apply->OK
(3).从提供的资料中将pom.xml文件拷贝过来替换项目中的同名文件。
zk需要的核心jar包
环境就搭建好了,接下来我们建测试类。(删除不需要的App.java)
学生机由于不能联网,所以maven可能不能用,如果maven不能用:
1、 将apache-maven-3.3.1.zip解压的D:\Tool
2、 将zebra_mvnrepository(私服)拷贝到D:\Tool
3、 修改D:\Tool\apache-maven-3.3.1\conf\settings.xml
修改该属性后,表示只要使用该maven的所有项目下载时全部使用该私服。
4、 修改Eclipse使用本地的maven
5、 修改maven使用的配置文件,指向私服
扩展阅读:maven超级pom(最顶层的父pom.xml)
maven-model-builder-3.3.1.jar->org\apache\maven\model\pom-4.0.0.xml
|
|
将以上两个节点指定默认的中央仓库,可以将之拷贝不同项目的pom.xml文件,分别修改,可以实现不同项目引用不同私服或公服。
/**connectString连接zk服务器的ip(192.168.80.50)和port (2181) * sessionTimeout:回话的超时时间,单位为毫秒 * watcher:监听器对象 * @throws Exception */ @Test public void testConect() throws Exception{ ZooKeeper zk = new ZooKeeper("192.168.80.50:2181", 3000, new Watcher() { @Override public void process(WatchedEvent event) { System.out.println("连接成功"); } }); while(true); } } |
为何添加while(true)就可以,原因是:
zk连接是一个非阻塞连接方法,连接还没有来的急建立,该线程已经结束。
在连接成功后,使用zk.create(…)或zk.get(),那么如何保证执行到该行时保证连接成功,可以使用闭锁。
public void testConect() throws Exception{ final CountDownLatch cdl = new CountDownLatch(1); ZooKeeper zk = new ZooKeeper("192.168.80.50:2181", 3000, new Watcher() { @Override public void process(WatchedEvent event) { System.out.println("连接成功"); cdl.countDown(); } }); //while(true); cdl.await(); } |
@Test publicvoid testCreate() throws Exception{ final CountDownLatch cdl = new CountDownLatch(1); ZooKeeper zk = new ZooKeeper("192.168.80.50:2181", 3000, new Watcher() { @Override publicvoid process(WatchedEvent event) { System.out.println("连接成功"); cdl.countDown(); } }); cdl.await(); //path:节点路径 //data:节点的内容 //acl:节点的权限 //createMode:节点的类型 //PERSISTENT(persistent):普通持久节点 //EPHEMERAL(ephemeral):普通临时节点 //PERSISTENT_SEQUENTIAL:顺序持久节点 //EPHEMERAL_SEQUENTIAL:顺序临时节点 zk.create("/zk02","hellozk".getBytes(), Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL); while(true); } |
如果创建不成功,有可能是该节点已经存在了。
@Test public void testGet() throws Exception{ ……//省略zk的创建。 //path:获取节点的地址 //watcher监听器 //stat:查询后,会将节点的其它信息封装stat中 Stat stat = new Stat(); byte[] data = zk.getData("/zk01", new Watcher() { @Override public void process(WatchedEvent event) { System.out.println("获取到数据"); } }, stat); System.out.println(new String(data)); System.out.println(stat); } |
@Test public void testSet() throws Exception{ …… //path:修改节点的地址 //data:修改后节点的内容。 //version:修改节点对应的版本号 zk.setData("/zk01", "you can't sleep".getBytes(), -1); while(true); } |
@Test publicvoid testGetChild() throws Exception{ …… //注意:stat是zk01的节点信息 Stat stat = new Stat(); List for (String path : paths) { System.out.println(path); } System.out.println(stat); } |
@Test publicvoid testDelete() throws Exception{ …… /**path:删除的节点路径 * version:节点的版本,-1支持各种版本号 * 删除节点使用的场景不是太多,可以创建临时节点,连接断了之后, * 节点会自动被删除。 * 注意:只用空节点才能删除。 */ zk.delete("/zk01", -1); } |
@Test public void testGetDataWatcher() throws Exception{ …… zk.getData("/zk01", new Watcher() { @Override public void process(WatchedEvent event) { System.out.println("数据放生变化"); } }, null); while(true); } |
运行程序后,在终端上执行以下命令:
set /zk01 newdata
控制台输出“数据发生变化”
目前只能监听一次,如何实现永久监听:
@Test publicvoid testGetDataWatcher() throws Exception{ ……
while(true){ final CountDownLatch cdl2 = new CountDownLatch(1); zk.getData("/zk01", new Watcher() { @Override publicvoid process(WatchedEvent event) { System.out.println("数据放生变化"); cdl2.countDown(); } }, null); cdl2.await(); } } |
@Test publicvoid testGetChildWatcher() throws Exception{ ……
zk.getChildren("/zk01",new Watcher(){ @Override publicvoid process(WatchedEvent event) { if(event.getType()==EventType.NodeChildrenChanged){ System.out.println("子节点发生了变化"); } } }); while(true); } |
运行程序,终端上执行以下命令(在/zk01下创建子节点)
create /zk01/zk02 jxf
控制台输出“子节点变化”
@Test publicvoid testDeleteWatcher() throws Exception{ …… zk.exists("/zk030000000004",new Watcher(){ @Override publicvoid process(WatchedEvent event) { if(event.getType()==EventType.NodeDeleted){ System.out.println("节点被删除。。。"); } } }); while(true); } |
运行程序,在终端上执行删除:delete /zk02
控制台输出:节点被删除
@Test publicvoid testCreateCallBack() throws Exception{ …… /**path:节点路径 * data:节点内容 * acl:节点的相关权限 * createMode:节点的类型 * ctx:传入的附件 */ zk.create("/zk04/node1", "Kill sleeper".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT, new StringCallback(){ @Override publicvoid processResult(intrc, String path, Object ctx, String name) { //int rc:状态码 0代表创建成功,-110 代表创建失败 //String path:用户指定的路径 //Object ctx:传入的附件 //String name:实际创建节点的路径名 System.out.println("rc:"+rc+",path:"+path+",ctx:"+ctx+",name:"+name); }
},"over" ); while(true); } |
集群搭建和配置
1. 克隆三台空虚拟机(含有lrzsz),修改网络ip,并关闭虚拟机的防火墙
临时关闭:service iptables stop
永久关闭:chkconfig iptables off
2. 安装和配置jdk
3. 安装和配置zookeeper
mkdir /use/soft
tar –zxvf /use/soft/zookeeper-3.4.7.tar.gz
[root@localhostconf]# cp zoo_sample.cfg zoo.cfg
4. 配置zoo.cfg
配置说明:
tickTime=2000 心跳间隔周期 毫秒。
initLimit=10初始连接超时阈值=10*tickTime。指的是follower初始连接leader的超时时间。 如果网络环境不好,适当调大。
syncLimit=5连接超时阈值=syncLimit*tickTime。指的是follower和leader做数据交互的超时时间。如果网络环境不好,适当调大。
dataDir=/usr/soft/zookeeper-3.4.7/tmp dataDir数据目录指的是zookeeper znode树的持久化目录,
server.1=192.168.80.51:2888:3888
server.2=192.168.80.52:2888:3888
server.3=192.168.80.53:2888:3888
server后的数字是选举id,在选举过程中会用到。注意:数字一定要能比较出大小。
2888 端口原子广播端口,可以自定义
3888 端口选举端口,可以自定义
在zk安装目录下创建tmp文件,创建myid文件(名字固定),并编辑当前虚拟机的选举id
Mkdir /usr/soft/zookeeper-3.4.7/tmp
tmp#vim myid (内容为1)
远程拷贝zk的安装目录到zk2、zk3上
[root@localhostsoftware]# scp -r zookeeper-3.4.7/ [email protected]:/home/software
[root@localhostsoftware]# scp -r zookeeper-3.4.7/ [email protected]:/home/software
分别修改myid,zk2->2,zk3->3
如果拷贝不过去或者拷贝太慢,可以先打包并压缩:
[root@localhostsoftware]# tar -zcvf zk.tar.gz zookeeper-3.4.7/
然后在scp
然后在解压:[root@localhost home]# tar -zxvf zk.tar.gz
5.启动zk集群测试
分别启动zk
[root@localhost bin]# ls README.txt zkCli.cmd zkEnv.cmd zkServer.cmd zkCleanup.sh zkCli.sh zkEnv.sh zkServer.sh [root@localhost bin]# ./zkServer.sh start ZooKeeper JMX enabled by default Using config: /usr/soft/zookeeper-3.4.7/bin/../conf/zoo.cfg Starting zookeeper ... STARTED [root@localhost bin]# jps 3490 Jps 3464 QuorumPeerMain |
[root@localhostbin]# ./zkServer.sh status
ZooKeeper JMX enabled by default Using config: /usr/soft/zookeeper-3.4.7/bin/../conf/zoo.cfg Mode: follower |
如果启动顺序为zk1->zk2->zk3,通常zk2为leader。
Leader是如何选举出来的?
1.每一个写操作都是一个事务,每一个事务都用一个事务id来代表,叫:zxid
2.zxid 是全局唯一,并且全局递增的。作用就是可以根据最大事务id,找到最新的事务
1. 数据恢复阶段:
当zk服务器启动时,会先从本地磁盘找到本机的最大事务id。
2. 选举阶段:
zk服务器会提交选举协议 1.Zxid(最大事务id) 2.本机的选举id(myid文件里的数字) 3.逻辑时钟值 记录当前的选举轮数,确保每个zk在同一轮选举中 4.当前zk服务器状态,分4种: Looking=>选举阶段 Following=>当小弟阶段 Leading=>当领导阶段 Observering=>观察者阶段
1.首先比较最大事务id,Zxid,谁大谁当领导
2.如果Zxid比较不出来,比较myid(选举id),谁大谁当领导
3.选举的前提满足过半同意
Leader身上肯定是有最新数据的(有最大事务id的),所以首先做的就是原子广播(通过原子 广播端口)。 原子广播的目的就是为了确保数据一致性(即客户端无论通过哪个zk服务器查看数据,数据都是一样的)
目的二就是为了防止leader挂掉之后,数据的丢失问题
对于事务更新,Leader会通过原子广播征询其他follower,只要满足过半同意机制,事务才能被更新
针对下图,Leader挂掉之后,第二个机器成为Leader
针对下图,不满足过半机制,集群就工作不了了
修改testCreateCallBack()方法
ZooKeeper zk=new ZooKeeper("192.168.80.51:2181, 192.168.80.52:2181,192.168.80.53:2181",3000,new Watcher(){… |
测试发现,三台zk上都创建了该节点。
Zxid 最大事务id 是全局的, cZxid、mZxid、pZxid是针对某个节点路径而言的。
扩展Zookeeper的选举机制根据Paxos 算法来实现。 Paxos算法解决的问题:在分布式环境下就某一个决议达成一致性问题。 Paxos算法存在活锁问题,Zk用的是FastPaxos算法,解决了活锁问题。
znode节点的状态信息中包含czxid和mzxid, 那么什么是zxid呢?
ZooKeeper状态的每一次改变, 都对应着一个递增的Transaction id
, 该id称为zxid. 由于zxid的递增性质, 如果zxid1小于zxid2, 那么zxid1肯定先于zxid2发生. 创建任意节点, 或者更新任意节点的数据, 或者删除任意节点, 都会导致Zookeeper状态发生改变, 从而导致zxid的值增加
6.2.5 session
在client和server通信之前, 首先需要建立连接, 该连接称为session.连接建立后, 如果发生连接超时, 授权失败, 或者显式关闭连接, 连接便处于CLOSED状态, 此时session结束.
讲述节点状态的ephemeralOwner字段时, 提到过有的节点是ephemeral节点, 而有的并不是. 那么节点都具有哪些类型呢? 每种类型的节点又具有哪些特点呢?persistent
.persistent节点不和特定的session绑定, 不会随着创建该节点的session的结束而消失, 而是一直存在, 除非该节点被显式删除.ephemeral
.ephemeral节点是临时性的, 如果创建该节点的session结束了, 该节点就会被自动删除. ephemeral节点不能拥有子节点. 虽然ephemeral节点与创建它的session绑定, 但只要该该节点没有被删除, 其他session就可以读写该节点中关联的数据. 使用-e参数指定创建ephemeral节点.