ZooKeeper私人学习笔记

俗话说“好记性不如烂笔头”,编程的海洋如此的浩大,养成做笔记的习惯是成功的一步!


此笔记主要是ZooKeeper3.4.9版本的笔记,并且笔记都是博主自己一字一字编写和记录,有错误的地方欢迎大家指正。




一、基础知识:
1、ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,它包含一个简单的原语集,分布式应用程序可以基于它实现同步
  服务,配置维护和命名服务等。Zookeeper是hadoop的一个子项目,由于功能卓越,现已是apache的一个顶级子项目,应用于各种分布
  式场景。官方网站:http://zookeeper.apache.org。
  
  
2、在分布式应用中,由于工程师不能很好地使用锁机制,以及基于消息的协调机制不适合在某些应用中使用,因此需要有一种可靠的、
  可扩展的、分布式的、可配置的协调机制来统一系统的状态,Zookeeper的目的就在于此。Zookeeper的节点和数据操作,都能保证
  在并发情况下的同步性和安全性,因此Zookeeper经常用于分布式锁的操作,同时也用于集群数据共享的情形。
  
  
3、Zookeeper是通过的连接是长连接的,通过定时的发送心跳来检测服务器的有效性,当检测到服务器故障后,会直接操作该服务器
  在Zookeeper上的节点状态,当节点发生变化时,会通知对当前节点进行检测的客户端(即观察者模式)。
  
  
  
4、Zookeeper的设计特点:
(1)最终一致性:client不论连接到哪个Server,展示给它都是同一个视图,这是zookeeper最重要的性能。


(2)可靠性:具有简单、健壮、良好的性能,如果消息m被到一台服务器接受,那么它将被所有的服务器接受。


(3)实时性:Zookeeper保证客户端将在一个时间间隔范围内获得服务器的更新信息,或者服务器失效的信息。
但由于网络延时等原因,Zookeeper不能保证两个客户端能同时得到刚更新的数据,如果需要最新数据,
应该在读数据之前调用sync()接口。


(4)等待无关(wait-free):慢的或者失效的client不得干预快速的client的请求,使得每个client都能有效的等待。


(5)原子性:更新只能成功或者失败,没有中间状态。


(6)顺序性:包括全局有序和偏序两种:全局有序是指如果在一台服务器上消息a在消息b前发布,则在所有Server上消息a
 都将在消息b前被发布;偏序是指如果一个消息b在消息a后被同一个发送者发布,a必将排在b前面。
 
 
 
5、Zookeeper集群的master选举,必须要要超过半数以上的服务器同意,故Zookeeper集群的服务器初始数量应为2n+1,即为奇数台。
例如:
如果有2台服务器A和B,假设A要选举为master服务器,但此时只有一台B服务器,即使B服务器同意,也只有1台服务器,
因为不满足n/2 + 1的数量,即 1 < 2 没有超过半数,因此会出现选举失败。



6、Zookeeper的使用场景:
(1)统一命名服务。
分布式应用中,通常需要有一套完整的命名规则,既能够产生唯一的名称又便于人识别和记住,Name Service 已经是 
Zookeeper 内置的功能,通过调用API的create创建子节点。

(2)配置管理。
配置的管理在分布式应用环境中很常见,例如同一个应用系统需要多台 PC Server 运行,如果要修改这些相同的配置
项,那么就必须同时修改每台运行这个应用系统的 PC Server。像这样的配置信息完全可以交给 Zookeeper 来管理,将配
置信息保存在 Zookeeper 的某个目录节点中,然后将所有需要修改的应用机器监控配置信息的状态,一旦配置信息发生变
化,每台应用机器就会收到 Zookeeper 的通知,然后从 Zookeeper 获取新的配置信息应用到系统中。

(3)集群管理。
它们的实现方式都是在 Zookeeper 上创建一个 EPHEMERAL 类型的目录节点,然后每个 Server 在它们创建目录节点的
父目录节点上调用 getChildren(String path, boolean watch) 方法并设置 watch 为 true,由于是 EPHEMERAL 目录节点,
当创建它的 Server 死去,这个目录节点也随之被删除,所以 Children 将会变化,这时 getChildren上的 Watch 将会被
调用,所以其它 Server 就知道已经有某台 Server 死去了。新增 Server 也是同样的原理。

(4)共享锁。
实现方式也是需要获得锁的 Server 创建一个 EPHEMERAL_SEQUENTIAL 目录节点,然后调用 getChildren方法获取当前
的目录节点列表中最小的目录节点是不是就是自己创建的目录节点,如果正是自己创建的,那么它就获得了这个锁,如果不
是那么它就调用 exists(String path, boolean watch) 方法并监控 Zookeeper 上目录节点列表的变化,一直到自己创建
的节点是列表中最小编号的目录节点,从而获得锁,释放锁很简单,只要删除前面它自己所创建的目录节点就行了。

(5)队列管理。
形式一:当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达,这种是同步队列。
形式二:队列按照 FIFO 方式进行入队和出队操作,例如实现生产者和消费者模型。


 
7、当客户端watch某个znode节点的时候,某个节点被改变时(例如数据改变或删除或子节点有变化等),Zookeeper会自动通知watch
  的客户端,然后清空掉此节点下所有的watch的客户端。因此,客户端向继续监听某个znode节点,则必须重新进行监视watch此节点。
  
  此模式其实就是观察者模式,当被观察的对象发生改变时,通知观察者。
  




8、Zookeeper服务器的使用需要注意的几个事项:
(1)watch监视是一次性的,再次监视需要重新设置监视节点。

(2)如果监视znode节点的客户端失去连接了,那么znode被改变后客户端是不会收到通知的,
即使后面客户端重新连接了Zookeeper。
 
(3)在删除znode节点的时候,只允许在空子节点的情况下删除,如果当前节点下有子节点,那么是不允许删除的。

(4)Zookeeper有种临时节点Ephemeral节点,在此连接断开后,就会被自动清除。因此临时节点不允许有子节点。

(5)Zookeeper的节点存储的数据不能大于1M,本身的设计就不是用来存储大数据,因此需要避免节点下的数据过大。


(6)相同的路径下,不允许有相同名称的节点存在,如果创建相同名字的节点会在创建时报错。如果基本名称都一样的,
可以创建序列节点Sequence节点,Zookeeper会自动在基本名称后面添加一个按顺序的数字标识。
 
 
 

9、每个znode节点的数据结构如下:
czxid:创建此节点时的zxid(Zookeeper Transition ID)
The zxid of the change that caused this znode to be created.

mzxid:最后更改此节点的zxid。
The zxid of the change that last modified this znode.

pzxid:最好更改此节点或子节点时的zxid。
The zxid of the change that last modified children of this znode.

ctime:创建此节点时的时间。
The time in milliseconds from epoch when this znode was created.

mtime:最终修改此节点的时间。
The time in milliseconds from epoch when this znode was last modified.

dataVersion:当前节点数据版本号,数据被修改版本号就会自增1。
The number of changes to the data of this znode.

cversion:当前节点下的子节点被修改时的版本号,子节点新增或删除时会自增1。
The number of changes to the children of this znode.

aclVersion:当前节点的ACL(访问控制列表)被修改时的版本号,每次修改会自增1。
The number of changes to the ACL of this znode.

ephemeralOwner:如果当前节点是临时节点,则存储session id,如果不是临时节点则为0.
he session id of the owner of this znode if the znode is an ephemeral node. 
If it is not an ephemeral node, it will be zero.

dataLength:当前节点下保存的数据长度。
The length of the data field of this znode.

numChildren:当前节点下的子节点个数。
The number of children of this znode.
 


10、Zookeeper的watch机制:
(1)服务端维护两个watch列表,一个是当前节点与数据watch列表,另外一个是子节点watch列表。
注意:
当前节点与数据watch列表:是当节点改变或者节点数据改变时,都会触发的列表。
子节点watch列表:是当子节点改变时触发,而子节点的数据改变时是不会触发的。


(2)设置监听者,即指定监听watch的znod节点的方式:
getData()和exists()设置当前节点与数据Watch,getChildren()设置子节点Watch。


(3)触发机制,即会触发watch的方式:
setData()触发内容Watch。即触发当前节点与数据watch列表。
create()触发其父节点的子节点Watch。子节点watch列表触发。
delete()同时触发父节点的子节点Watch和当前节点与数据Watch。当前节点与数据watch列表和子节点watch列表都有触发。



11、Zookeeper的ACL(访问控制列表):
(1)ACL的权限分为五类:
CREATE: 创建权限,允许在该节点下创建子节点。you can create a child node.

READ:读权限,允许读取该节点数据和查询他的所属子节点。 you can get data from a node and list its children.

WRITE: 写权限,允许在该节点下修改data数据。you can set data for a node.

DELETE: 删除权限,允许删除他的所属子节点。you can delete a child node.

ADMIN: 管理权限,允许再该节点下设置ACL。you can set permissions.


注意:CREATE和DELETE都是他所属的子节点进行权限控制的,并不是针对当前节点。
     即任何人都可以删除当前节点,如果你知道节点路径的话。


(2)ACL的权限是不递归的。例如:假设给一个节点设置了读权限的控制,一个用户即使没有读取当前节点的权限,
  但是如果知道当前节点下的子节点路径,依旧可以读取子节点的数据,父节点的权限不会递归到子节点。
  
  
  
(3)ACL的控制策略有如下几类:
world: 它下面只有一个id, 叫anyone, world:anyone代表任何人。

auth: 它不需要id, 只要是通过authentication的user都有权限,也就是说默认采用的username:password就是
     当前用户认证使用的用户名和密码,如果当前用户没有认证过,使用auth策略会报错。

digest: 它对应的id为username:BASE64(SHA1(password)),它需要先通过username:password形式的进行认证。

ip: 它对应的id为客户机的IP地址,设置的时候可以设置一个ip段,比如ip:192.168.1.0/16, 表示匹配前16个bit的IP段。

x509: 客户端使用X500的规则来认证。


提示:比较常用的是digest和auth的形式。



 
 
 
二、安装部署:
1、Zookeeper是用Java语言实现的,因此必须要先安装JDK。

2、Zookeeper单台服务器的安装(基于Linux系统):
步骤一:解压zookeeper-3.4.9.tar.gz目录,执行命令 tar -zxvf zookeeper-3.4.9.tar.gz -C /usr/user


步骤二:进入加压后的目录/usr/user/zookeeper-3.4.9 ,在该目录下创建data和log目录用于存放数据和日志,
执行命令 mkdir data log


步骤三:在当前Zookeeper目录下,进入conf目录进行配置。新建zoo.cfg文件(Zookeeper默认会加载此配置),然后输入如下配置:
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/usr/users/zookeeper/data
dataLogDir=/usr/users/zookeeper/log
clientPort=2181


 

步骤四:在当前Zookeeper目录下,进入bin目录。执行命令来操作Zookeeper服务。
sh  zkServer.sh start #启动Zookeeper服务
sh  zkServer.sh stop #停止Zookeeper服务
sh  zkServer.sh status #查看Zookeeper服务的运行状态。



步骤五:通过Zookeeper自带的客户端操作Zookeeper服务。执行命令  sh zkCli.sh -server localhost:2181 
如果是连接本地Zookeeper服务,并且使用的是默认的2181端口,则可以简写 sh zkCli.sh 





3、Zookeeper的集群安装(基于Linux系统):
因为Zookeeper本身就是为集群提供服务的,在设计时就考虑Zookeeper的集群,因此Zookeeper的集群非常简单。基于单台服务器
的安装后,增加如下步骤:假设有三台服务器 192.168.1.2:2181、192.168.1.3:2181、192.168.1.4:2181

续步骤一:在当前Zookeeper目录下,进入data目录,创建myid文件(用于给当前Zookeeper服务器添加id标识,范围为1~255)。
  此处设置标识如下(myid只需输入数字)
  192.168.1.2服务器的id为 1
  192.168.1.3服务器的id为 2
  192.168.1.4服务器的id为 3
  
  
  
继步骤二:在conf/zoo.cfg配置文件下,添加如下配置信息:
server.1=192.168.1.2:2888:3888
server.2=192.168.1.3:2888:3888
server.3= 92.168.1.4:2888:3888

#1,2,3分别是每个服务器的id,后面是对应的ip地址,2888:3888是可以用于Zookeeper服务器之间内部通信使用的端口。



注意:使用Zookeeper集群时,注意防火墙配置,可能会引起Zookeeper无法探测到其他服务器,导致启动失败。




三、java 连接Zookeeper的使用笔记:
1、java连接Zookeeper的开源框架有三种:
第一种是Zookeeper官方提供的原生java api。

第二种是由datameer的工程师开发的zkClient,源码在github上可下载。修复了原生java api的一些bug和简化api操作。

第三种是curator框架,也是apache开发的。curator的api更简单,更能更强大,是最受欢迎的java版Zookeeper连接框架。

注意:本笔记默认是使用官方提供的原生api来连接Zookeeper。




2、zookeeper-3.4.9.jar包是java连接Zookeeper服务端的原生jar包,依赖于slfj日志门面框架的jar包,需要同时引入,
  具体的jar包在lib_jar目录下。



3、原生api使用了java的非阻塞NIO来连接,并且是开启新的线程来去连接服务端的。因此,主线程必须要监控连接成功事件,
  在连接成功后才开始操作Zookeeper,以免抛出异常。
  
  提示:可以借助JDK并发库的CountDownLatch 对象来实现主线程的阻塞,直到连接成功。
  
  代码示例:
final CountDownLatch countDownLatch = new CountDownLatch(1);
//创建Zookeeper的核心对象,此对象在创建时就开启新的线程去连接服务端。
ZooKeeper zk = new ZooKeeper("10.17.2.7:2181", 3000, new Watcher() {
@Override
public void process(WatchedEvent event) {
//连接成功的状态,进行通知。
if (event.getType() == EventType.None && event.getState() == KeeperState.SyncConnected) {
countDownLatch.countDown();
}
}
});
//等待连接成功。
countDownLatch.await();
//连接成功,开始执行对zk操作的业务逻辑。
System.out.println("ZooKeeper 连接成功!");
  
  
  
  
4、原生api可以定制默认的watcher监视者,就是在创建核心操作类ZooKeeper的时候,在构造方法中指定默认的watcher,
  此方法的所有api中凡是通过boolean值来指定是否监视的,如果为true则都是使用默认的watcher进行监视。如果为false,
  则不进行监视。
  
  例如:zk.getData()、zk.getChildren()等方法。



5、Zookeeper服务器是支持事务的,因此java版的原生api也支持事务的操作。实例代码如下:
//创建连接
ZooKeeper zk = new ZooKeeper("10.17.2.7:2181", 3000, null);
//获得事务对象
Transaction ts = zk.transaction();
//通过事务对象来操作数据。如果是用zk对象来操作数据,那么是不在此事务范围内的。如zk.create()操作是不受事务影响的。
ts.create("/java/a7", "节点a".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
//必须提交事务,否则数据修改无效。
ts.commit();
zk.close();





6、使用Zookeeper获得分布式锁的基本思想:
  先创建好一个获得锁的父节点,然后每个线程在该父节点下创建自己的EPHEMERAL_SEQUENTIAL节点(临时队列节点),然后获取
该父节点下的所有子节点,使用TreeSet进行自然排序,然后获取首个元素(即序列最小的元素),判断最小元素的名称是否是当前
创建的最小元素名称,如果是则表名当前线程获取了锁,可以执行业务逻辑。如果不是,则获取此元素前面的一个元素,然后进行
watch监视,当监视节点被删除或,获取获取所有的节点进行判断,重复执行此逻辑,直到当前最小元素为当前线程创建的最小元素。

注意:当获取锁的线程操作完成后,必须删除当前节点,以便让后续监视者执行监视逻辑的方法,从而让监视者得到锁。
  
说明:每个线程创建的节点必须是EPHEMERAL_SEQUENTIAL节点。因为临时节点会在当前连接断开后会自动删除,这样可以在当前线程
 的客户端宕机后,及时的处理掉他所拥有的节点,避免线程死锁。同时,创建队列类型的节点,在创建时服务端会按创建顺序
 在节点名称后面加一串序列数字,可以通过此数字来确定获取锁的节点是哪个。
 
 
 
附加:除了使用Zookeeper获取分布式锁以外,还可以使用redis来获取,但Zookeeper获取分布式锁更方便。
 redis获取分布式锁的基本思想:借助setnx和getset命令来实现。首先通过setnx存储一个时间戳值,此时间戳表示锁的有效
 时间。如果设置成功,则表名是获取锁,当前线程可获得锁。如果设置失败,则开始每隔一段时间循环读取。使用get读取当
 前key的value值,value值存的是锁的有效时间,判断value值是否小于当前时间,如果是,则说明锁已经失效,则根据当前时
 间计算出锁的有效时间,然后使用getset方法,更新旧的锁时间,如果返回的旧值与之前get读取的值一致,则说明获取锁成
 功。如果不一致,则说明被其他线程先执行getset操作了,则获取循环尝试获取锁。
 
 
 setnx命令:是当key不存在时才会设置成功并返回1,否则设置失败并返回0。
 getset命令:更新当前key的value值,并返回旧的value值,如果当前没有旧值,则返回nil。
 get命令:根据key读取value值,如果没有则返回nil。





/***************************************************************附加**********************************************************/
附加1、使用zkCli.sh 脚本进入Zookeeper的客户端,可以使用的命令有:
[zkshell: 0] help
ZooKeeper host:port cmd args
get path [watch]
ls path [watch]
set path data [version]
delquota [-n|-b] path
quit
printwatches on|off
create path data acl
stat path [watch]
listquota path
history
setAcl path acl
getAcl path
sync path
redo cmdno
addauth scheme auth
delete path [version]
deleteall path
setquota -n|-b val path













  



原文地址:https://www.cnblogs.com/catgwj/p/7492824.html