Zookeeper基本使用(转)
一、Zookeeper架构
云计算越来越流行的今天,单一机器处理能力已经不能满足我们的需求,不得不采用大量的服务集群。服务集群对外提供服务的过程中,有很多的配置需要随时更新,服务间需要协调工作,这些信息如何推送到各个节点?并且保证信息的一致性和可靠性?
众所周知,分布式协调服务很难正确无误的实现,它们很容易在竞争条件和死锁上犯错误。如何在这方面节省力气?Zookeeper是一个不错的选择。 Zookeeper背后的动机就是解除分布式应用在实现协调服务上的痛苦。本文在介绍Zookeeper的基本理论基础上,用Zookeeper实现了一 个配置管理中心,利用Zookeeper将配置信息分发到各个服务节点上,并保证信息的正确性和一致性。
Zookeeper是什么?
引用官方的说法:“Zookeeper是一个高性能,分布式的,开源分布式应用协调服务。它提供了简单原始的功能,分布式应用可以基于它实现更高级 的服务,比如同步,配置管理,集群管理,名空间。它被设计为易于编程,使用文件系统目录树作为数据模型。服务端跑在java上,提供java和C的客户端 API”。
Zookeeper总体结构
Zookeeper服务自身组成一个集群(2n+1个服务允许n个失效)。Zookeeper服务有两个角色,一个是leader,负责写服务和数据同步,剩下的是follower,提供读服务,leader失效后会在follower中重新选举新的leader。
Zookeeper逻辑图如下,
- 客户端可以连接到每个server,每个server的数据完全相同。
- 每个follower都和leader有连接,接受leader的数据更新操作。
- Server记录事务日志和快照到持久存储。
- 大多数server可用,整体服务就可用。
Zookeeper 特点
- 顺序一致性:按照客户端发送请求的顺序更新数据。
- 原子性:更新要么成功,要么失败,不会出现部分更新。
- 单一性 :无论客户端连接哪个server,都会看到同一个视图。
- 可靠性:一旦数据更新成功,将一直保持,直到新的更新。
- 及时性:客户端会在一个确定的时间内得到最新的数据。
二、Zookeeper数据模型
ZooKeeper的数据结构, 与普通的文件系统极为类似. 见下图:
图片引用自developerworks
图中的每个节点称为一个znode. 每个znode由3部分组成:
- stat. 此为状态信息, 描述该znode的版本, 权限等信息.
- data. 与该znode关联的数据.
- children. 该znode下的子节点.
三、Zookeeper应用场景
1、统一命名服务(Name Service)
分布式应用中,通常需要有一套完整的命名规则,既能够产生唯一的名称又便于人识别和记住,通常情况下用树形的名称结构是一个理想的选择,树形的名称结构是一个有层次的目录结构,既对人友好又不会重复。说到这里你可能想到了 JNDI,没错 Zookeeper 的 Name Service 与 JNDI 能够完成的功能是差不多的,它们都是将有层次的目录结构关联到一定资源上,但是 Zookeeper 的 Name Service 更加是广泛意义上的关联,也许你并不需要将名称关联到特定资源上,你可能只需要一个不会重复名称,就像数据库中产生一个唯一的数字主键一样。
Name Service 是 Zookeeper 内置的功能,你只要调用 Zookeeper 的 API 就能实现。如调用 create 接口就可以很容易创建一个目录节点。
以上摘自IMB Bluemix的分布式服务框架 Zookeeper – 管理分布式环境中的数据文章中,我作个总结:
1)提供类似JNDI的服务,这也是Zookeeper的基础,整个Zookeeper的功能就是围绕树形结构的内容进行展开的
2)提供临时类型(EPHEMERAL)的目录
3)提供顺序自动编号类型(SEQUENTIAL)的目录
关于2)、3)点会在后面作详细介绍,集群管理、共享锁就是基于以上特性进行实现的
2、统一配置管理(Configuration Management)
配置的管理在分布式应用环境中很常见,例如同一个应用系统需要多台 PC Server 运行,但是它们运行的应用系统的某些配置项是相同的,如果要修改这些相同的配置项,那么就必须同时修改每台运行这个应用系统的 PC Server,这样非常麻烦而且容易出错。
像这样的配置信息完全可以交给 Zookeeper 来管理,将配置信息保存在 Zookeeper 的某个目录节点中,然后将所有需要修改的应用机器监控配置信息的状态,一旦配置信息发生变化,每台应用机器就会收到 Zookeeper 的通知,然后从 Zookeeper 获取新的配置信息应用到系统中。
以上摘自IMB Bluemix的分布式服务框架 Zookeeper – 管理分布式环境中的数据文章中。
在我们使用JVM内存进行数据缓存的场景下,可以采用ZK的这种方式进行数据更新。有人说可以直接使用memcached、redis进行统一配置管理,这样直接修改redis中的数据就可以了,在对缓存数据的访问量不大的前提下,该设计是没有问题的,当数据访问量极大的时候该设计存在一个问题,就是频繁的访问memcached、redis造成大量的网络开销,进而影响系统性能。因此将数据缓存至JVM更合适一些。这里只是举了一个简单的例子,关于缓存的使用之后写一篇文章进行详细的分析。
3、集群管理(Cluster Management)
Zookeeper 能够很容易的实现集群管理的功能,如有多台 Server 组成一个服务集群,那么必须要一个“总管”知道当前集群中每台机器的服务状态,一旦有机器不能提供服务,集群中其它集群必须知道,从而做出调整重新分配服务策略。同样当增加集群的服务能力时,就会增加一台或多台 Server,同样也必须让“总管”知道。
Zookeeper 不仅能够帮你维护当前的集群中机器的服务状态,而且能够帮你选出一个“总管”,让这个总管来管理集群,这就是 Zookeeper 的另一个功能 Leader Election。
它们的实现方式都是在 Zookeeper 上创建一个 EPHEMERAL 类型的目录节点,然后每个 Server 在它们创建目录节点的父目录节点上调用 getChildren(String path, boolean watch) 方法并设置 watch 为 true,由于是 EPHEMERAL 目录节点,当创建它的 Server 死去,这个目录节点也随之被删除,所以 Children 将会变化,这时 getChildren上的 Watch 将会被调用,所以其它 Server 就知道已经有某台 Server 死去了。新增 Server 也是同样的原理。
Zookeeper 如何实现 Leader Election,也就是选出一个 Master Server。和前面的一样每台 Server 创建一个 EPHEMERAL 目录节点,不同的是它还是一个 SEQUENTIAL 目录节点,所以它是个 EPHEMERAL_SEQUENTIAL 目录节点。之所以它是 EPHEMERAL_SEQUENTIAL 目录节点,是因为我们可以给每台 Server 编号,我们可以选择当前是最小编号的 Server 为 Master,假如这个最小编号的 Server 死去,由于是 EPHEMERAL 节点,死去的 Server 对应的节点也被删除,所以当前的节点列表中又出现一个最小编号的节点,我们就选择这个节点为当前 Master。这样就实现了动态选择 Master,避免了传统意义上单 Master 容易出现单点故障的问题。
以上摘自IMB Bluemix的分布式服务框架 Zookeeper – 管理分布式环境中的数据文章中。
4、共享锁(Locks)
共享锁在同一个进程中很容易实现,但是在跨进程或者在不同 Server 之间就不好实现了。Zookeeper 却很容易实现这个功能,实现方式也是需要获得锁的 Server 创建一个 EPHEMERAL_SEQUENTIAL 目录节点,然后调用 getChildren方法获取当前的目录节点列表中最小的目录节点是不是就是自己创建的目录节点,如果正是自己创建的,那么它就获得了这个锁,如果不是那么它就调用 exists(String path, boolean watch) 方法并监控 Zookeeper 上目录节点列表的变化,一直到自己创建的节点是列表中最小编号的目录节点,从而获得锁,释放锁很简单,只要删除前面它自己所创建的目录节点就行了。
以上摘自IMB Bluemix的分布式服务框架 Zookeeper – 管理分布式环境中的数据文章中。
五、队列管理(Queue Management)
Zookeeper 可以处理两种类型的队列:
当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达,这种是同步队列。
队列按照 FIFO 方式进行入队和出队操作,例如实现生产者和消费者模型。
1)同步队列用 Zookeeper 实现的实现思路如下:
创建一个父目录 /synchronizing,每个成员都监控标志(Set Watch)位目录 /synchronizing/start 是否存在,然后每个成员都加入这个队列,加入队列的方式就是创建 /synchronizing/member_i 的临时目录节点,然后每个成员获取 / synchronizing 目录的所有目录节点,也就是 member_i。判断 i 的值是否已经是成员的个数,如果小于成员个数等待 /synchronizing/start 的出现,如果已经相等就创建 /synchronizing/start。
2)FIFO 队列用 Zookeeper 实现思路如下:
实现的思路也非常简单,就是在特定的目录下创建 SEQUENTIAL 类型的子目录 /queue_i,这样就能保证所有成员加入队列时都是有编号的,出队列时通过 getChildren( ) 方法可以返回当前所有的队列中的元素,然后消费其中最小的一个,这样就能保证 FIFO。
以上摘自IMB Bluemix的分布式服务框架 Zookeeper – 管理分布式环境中的数据文章中。
四、Zookeeper安装与使用
安装环境为centos6.5
172.16.80.177
172.16.80.178
1. 配置机器名。
vi /etc/hosts
172.16.80.177 zookeeper1 172.16.80.178 zookeeper2
2. 安装JDK并配置环境变量(JAVA_HOME、CLASSPATH、PATH)。
3、下载Zookeeper安装包
https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/
我下载3.4.8版本
4. 安装并配置(两台机器上都要做同样的配置)。
将zookeeper-3.4.8.tar.gz放到root目录下
mkdir -p /opt/app
tar zxvf zookeeper-3.4.8.tar.gz -C /opt/app/ cd /opt/app/zookeeper-3.4.8/ mkdir data/ logs/
cd /conf
mv zoo_sample.cfg zoo.cfg vi zoo.cfg # 集群每台机器的zoo.cfg配置必须一致。
tickTime=2000 dataDir=/opt/app/zookeeper-3.4.8/data/ dataLogDir=/opt/app/zookeeper-3.4.8/logs/ clientPort=2181 initLimit=5 syncLimit=2 server.1=zookeeper1:2888:3888 # 每台机器都要感知集群的机器组成,配置格式为“server.id=host:port:port”。id范围1~255。 server.2=zookeeper2:2888:3888
# 在data目录创建myid文件。根据zoo.cfg配置,id应与机器对应。如zookeeper1的id为1,zookeeper2的id为2. echo 1 > data/myid echo 2 > data/myid
echo 1是在机器1上执行,echo 2是要在机器2上执行
5. 启动、关闭。
/opt/app/zookeeper-3.4.8/bin/zkServer.sh start /opt/app/zookeeper-3.4.8/bin/zkServer.sh stop /opt/app/zookeeper-3.4.8/bin/zkServer.sh status
[root@host-172-16-80-178 bin]# /opt/app/zookeeper-3.4.8/bin/zkServer.sh status ZooKeeper JMX enabled by default Using config: /opt/app/zookeeper-3.4.8/bin/../conf/zoo.cfg Mode: leader
[root@host-172-16-80-177 zookeeper-3.4.8]# /opt/app/zookeeper-3.4.8/bin/zkServer.sh status ZooKeeper JMX enabled by default Using config: /opt/app/zookeeper-3.4.8/bin/../conf/zoo.cfg Mode: follower
6、连接zookeeper
[root@host-172-16-80-177 zookeeper-3.4.8]# /opt/app/zookeeper-3.4.8/bin/zkCli.sh -server zookeeper2:2181 Connecting to zookeeper1:2181 2017-10-19 04:29:07,456 [myid:] - INFO [main:Environment@100] - Client environment:zookeeper.version=3.4.8--1, built on 02/06/2016 03:18 GMT 2017-10-19 04:29:07,460 [myid:] - INFO [main:Environment@100] - Client environment:host.name=<NA> 2017-10-19 04:29:07,460 [myid:] - INFO [main:Environment@100] - Client environment:java.version=1.7.0_80 2017-10-19 04:29:07,463 [myid:] - INFO [main:Environment@100] - Client environment:java.vendor=Oracle Corporation 2017-10-19 04:29:07,463 [myid:] - INFO [main:Environment@100] - Client environment:java.home=/usr/java/jdk1.7.0_80/jre 2017-10-19 04:29:07,463 [myid:] - INFO [main:Environment@100] - Client environment:java.class.path=/opt/app/zookeeper-3.4.8/bin/../build/classes:/opt/app/zookeeper-3.4.8/bin/../build/lib/*.jar:/opt/app/zookeeper-3.4.8/bin/../lib/slf4j-log4j12-1.6.1.jar:/opt/app/zookeeper-3.4.8/bin/../lib/slf4j-api-1.6.1.jar:/opt/app/zookeeper-3.4.8/bin/../lib/netty-3.7.0.Final.jar:/opt/app/zookeeper-3.4.8/bin/../lib/log4j-1.2.16.jar:/opt/app/zookeeper-3.4.8/bin/../lib/jline-0.9.94.jar:/opt/app/zookeeper-3.4.8/bin/../zookeeper-3.4.8.jar:/opt/app/zookeeper-3.4.8/bin/../src/java/lib/*.jar:/opt/app/zookeeper-3.4.8/bin/../conf: 2017-10-19 04:29:07,463 [myid:] - INFO [main:Environment@100] - Client environment:java.library.path=/usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib 2017-10-19 04:29:07,463 [myid:] - INFO [main:Environment@100] - Client environment:java.io.tmpdir=/tmp 2017-10-19 04:29:07,463 [myid:] - INFO [main:Environment@100] - Client environment:java.compiler=<NA> 2017-10-19 04:29:07,464 [myid:] - INFO [main:Environment@100] - Client environment:os.name=Linux 2017-10-19 04:29:07,464 [myid:] - INFO [main:Environment@100] - Client environment:os.arch=amd64 2017-10-19 04:29:07,464 [myid:] - INFO [main:Environment@100] - Client environment:os.version=2.6.32-573.el6.x86_64 2017-10-19 04:29:07,464 [myid:] - INFO [main:Environment@100] - Client environment:user.name=root 2017-10-19 04:29:07,464 [myid:] - INFO [main:Environment@100] - Client environment:user.home=/root 2017-10-19 04:29:07,464 [myid:] - INFO [main:Environment@100] - Client environment:user.dir=/opt/app/zookeeper-3.4.8 2017-10-19 04:29:07,466 [myid:] - INFO [main:ZooKeeper@438] - Initiating client connection, connectString=zookeeper1:2181 sessionTimeout=30000 watcher=org.apache.zookeeper.ZooKeeperMain$MyWatcher@594b7042 Welcome to ZooKeeper! 2017-10-19 04:29:07,501 [myid:] - INFO [main-SendThread(zookeeper1:2181):ClientCnxn$SendThread@1032] - Opening socket connection to server zookeeper1/172.16.80.177:2181. Will not attempt to authenticate using SASL (unknown error) 2017-10-19 04:29:07,515 [myid:] - INFO [main-SendThread(zookeeper1:2181):ClientCnxn$SendThread@876] - Socket connection established to zookeeper1/172.16.80.177:2181, initiating session JLine support is enabled 2017-10-19 04:29:07,552 [myid:] - INFO [main-SendThread(zookeeper1:2181):ClientCnxn$SendThread@1299] - Session establishment complete on server zookeeper1/172.16.80.177:2181, sessionid = 0x15f33b62ee10001, negotiated timeout = 30000 WATCHER:: WatchedEvent state:SyncConnected type:None path:null
[zk: zookeeper1:2181(CONNECTED) 0] ls /
[zookeeper]
[zk: zookeeper1:2181(CONNECTED) 1] create /helloworld 123
Created /helloworld
[zk: zookeeper1:2181(CONNECTED) 2] ls /
[helloworld, zookeeper]
[zk: zookeeper1:2181(CONNECTED) 3] quit
Quitting...
2017-10-19 04:33:01,671 [myid:] - INFO [main:ZooKeeper@684] - Session: 0x15f33b62ee10002 closed
2017-10-19 04:33:01,675 [myid:] - INFO [main-EventThread:ClientCnxn$EventThread@519] - EventThread shut down for session: 0x15f33b62ee10002
/opt/app/zookeeper-3.4.8/bin/zkCli.sh -server zookeeper2:2181
[zk: zookeeper1:2181(CONNECTED) 0] ls / [helloworld, zookeeper] [zk: zookeeper1:2181(CONNECTED) 1] get /helloworld 123 cZxid = 0x100000005 ctime = Thu Oct 19 04:32:43 EDT 2017 mZxid = 0x100000005 mtime = Thu Oct 19 04:32:43 EDT 2017 pZxid = 0x100000005 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 3 numChildren = 0 [zk: zookeeper1:2181(CONNECTED) 2]
help命令
显示客户所支持的所有命令,如:
ZooKeeper -server host:port cmd args
connecthost:port
getpath [watch]
lspath [watch]
setpath data [version]
rmrpath
delquota[-n|-b] path
quit
printwatcheson|off
create[-s] [-e] path data acl
statpath [watch]
close
ls2path [watch]
history
listquotapath
setAclpath acl
getAclpath
syncpath
redocmdno
addauthscheme auth
deletepath [version]
setquota-n|-b val path
connect命令
连接zk服务端,与close命令配合使用可以连接或者断开zk服务端。
如connect 127.0.0.1:2181
get命令
获取节点信息,注意节点的路径皆为绝对路径,也就是说必要要从/(根路径)开始。
如get /
hello world
cZxid = 0x0
ctime = Thu Jan 01 08:00:00 CST 1970
mZxid = 0x5
mtime = Thu Apr 27 15:09:00 CST 2017
pZxid = 0xc
cversion = 1
dataVersion = 2
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 11
numChildren = 1
详解:
hello world为节点数据信息
cZxid节点创建时的zxid
ctime节点创建时间
mZxid节点最近一次更新时的zxid
mtime节点最近一次更新的时间
cversion子节点数据更新次数
dataVersion本节点数据更新次数
aclVersion节点ACL(授权信息)的更新次数
ephemeralOwner如果该节点为临时节点,ephemeralOwner值表示与该节点绑定的session id. 如果该节点不是临时节点,ephemeralOwner值为0
dataLength节点数据长度,本例中为hello world的长度
numChildren子节点个数
ls命令
获取路径下的节点信息,注意此路径为绝对路径,类似于linux的ls命令。
如ls /zookeeper
set命令
设置节点的数据。
如set /zookeeper "hello world"
rmr命令
删除节点命令,此命令与delete命令不同的是delete不可删除有子节点的节点,但是rmr命令可以删除,注意路径为绝对路径。
如rmr /zookeeper/znode
delquota命令
删除配额,-n为子节点个数,-b为节点数据长度。
如delquota –n 2,请参见listquota和setquota命令。
quit命令
退出。
printwatches命令
设置和显示监视状态,on或者off。
如printwatches on
create命令
创建节点,其中-s为顺序充点,-e临时节点。
如create /zookeeper/node1"test_create" world:anyone:cdrwa
其中acl处,请参见getAcl和setAcl命令。
stat命令
查看节点状态信息。如stat /
cZxid = 0x0
ctime = Thu Jan 01 08:00:00 CST 1970
mZxid = 0x1f
mtime = Thu Apr 27 16:05:14 CST 2017
pZxid = 0xc
cversion = 1
dataVersion = 3
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 1
与get命令大体相同,请参见get命令。
close命令
断开客户端与服务端的连接。
ls2命令
ls2为ls命令的扩展,比ls命令多输出本节点信息。
如 ls /zookeeper
history命令
列出最近的历史命令。
如history
0 - ls /
1 - ls /
2 - ls2 /
3 - history
4 - listquota /zookeeper
5 – history
基本格式为:命令ID-命令,可以与redo命令配合使用。
listquota命令
显示配额。
如listquota /zookeeper
absolute path is/zookeeper/quota/zookeeper/zookeeper_limits
Output quota for /zookeepercount=2,bytes=-1
解释:
/zookeeper节点个数限额为2,长度无限额。
setAcl命令
设置节点Acl。
此处重点说一下acl,acl由大部分组成:1为scheme,2为user,3为permission,一般情况下表示为scheme:id:permissions。
其中scheme和id是相关的,下面将scheme和id一起说明。
scheme和id
world: 它下面只有一个id, 叫anyone, world:anyone代表任何人,zookeeper中对所有人有权限的结点就是属于world:anyone的
auth: 它不需要id, 只要是通过authentication的user都有权限(zookeeper支持通过kerberos来进行authencation, 也支持username/password形式的authentication)
digest: 它对应的id为username:BASE64(SHA1(password)),它需要先通过username:password形式的authentication
ip: 它对应的id为客户机的IP地址,设置的时候可以设置一个ip段,比如ip:192.168.1.0/16, 表示匹配前16个bit的IP段
super: 在这种scheme情况下,对应的id拥有超级权限,可以做任何事情(cdrwa)
permissions
CREATE(c): 创建权限,可以在在当前node下创建child node
DELETE(d): 删除权限,可以删除当前的node
READ(r): 读权限,可以获取当前node的数据,可以list当前node所有的child nodes
WRITE(w): 写权限,可以向当前node写数据
ADMIN(a): 管理权限,可以设置当前node的permission
综上,一个简单使用setAcl命令,则可以为:
setAcl /zookeeper/node1 world:anyone:cdrw
getAcl命令
获取节点Acl。
如getAcl /zookeeper/node1
'world,'anyone
: cdrwa
注:可参见setAcl命令。
sync命令
强制同步。
如sync /zookeeper
由于请求在半数以上的zk server上生效就表示此请求生效,那么就会有一些zk server上的数据是旧的。sync命令就是强制同步所有的更新操作。
redo命令
再次执行某命令。
如redo 10
其中10为命令ID,需与history配合使用。
addauth命令
节点认证。
如addauth digest username:password,可参见setAcl命令digest处。
使用方法:
一、通过setAcl设置用户名和密码
setAcl pathdigest:username:base64(sha1(password)):crwda
二、认证
addauth digest username:password
delete命令
删除节点。
如delete /zknode1
setquota命令
设置子节点个数和数据长度配额。
如setquota –n 4 /zookeeper/node 设置/zookeeper/node子节点个数最大为4
setquota –b 100 /zookeeper/node 设置/zookeeper/node节点长度最大为100