ZooKeeper是一个分布式的,开放源码的分布式应用程序协同服务。是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件
简介
曾是Hadoop子项目,现为顶级项目
ZooKeeper是协同服务
ZooKeeper为分布式应用提供服务
ZooKeeper支持Java和C语言
提供服务
配置维护
名字服务
分布式同步
组服务等。
目标
封装复杂、易错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户
ZooKeeper流程
1.选举Leader
2.同步数据
3.选举Leader过程中算法有很多,但要达到的选举标准是一致的
4.Leader要具有最高的zxid
5.集群中大多数的机器得到响应并follow选出的Leader
存放数据的数据结构
通过/分隔开路径名
每个路径代表一个节点Znode(zookeeper node)
子节点–Znode
每个Znode有自身信息,数据、长度、创建时间、修改时间。
Znode维护数据、ACL(access control list,访问控制列表)、时间戳等交换版本号等数据结构,它通过对这些数据的管理来让缓存生效并且令协调更新。每当Znode中的数据更新后版本号将增加。
读写操作
读写数据原子性,读就读取所有数据,写入时完全覆盖。Znode的ACL存储用户操作权限。
临时节点
和session相关,session结束,节点删除
ZooKeeper安装
略
ZooKeeper 常用四字命令
ZooKeeper 支持某些特定的四字命令字母与其的交互。它们大多是查询命令,用来获取ZooKeeper 服务的当前状态及相关信息。用户在客户端可以通过 telnet 或 nc 向 ZooKeeper 提交相应的命令
1. 可以通过命令:echo stat|nc 127.0.0.1 2181 来查看哪个节点被选择作为follower或者leader
2. 使用echo ruok|nc 127.0.0.1 2181 测试是否启动了该Server,若回复imok表示已经启动。
3. echo dump| nc 127.0.0.1 2181 ,列出未经处理的会话和临时节点。
4. echo kill | nc 127.0.0.1 2181 ,关掉server
5. echo conf | nc 127.0.0.1 2181 ,输出相关服务配置的详细信息。
6. echo cons | nc 127.0.0.1 2181 ,列出所有连接到服务器的客户端的完全的连接 / 会话的详细信息。
7. echo envi |nc 127.0.0.1 2181 ,输出关于服务环境的详细信息(区别于 conf 命令)。
8. echo reqs | nc 127.0.0.1 2181 ,列出未经处理的请求。
9. echo wchs | nc 127.0.0.1 2181 ,列出服务器 watch 的详细信息。
10. echo wchc | nc 127.0.0.1 2181 ,通过 session 列出服务器 watch 的详细信息,它的输出是一个与 watch 相关的会话的列表。
11. echo wchp | nc 127.0.0.1 2181 ,通过路径列出服务器 watch 的详细信息。它输出一个与 session 相关的路径。
Zookeeper的shell操作
启动Zookeeper服务之后,输入以下命令,连接到Zookeeper服务:
zkCli.sh -server localhost:2181
执行结果如下所示:
[root@master ~]# zkCli.sh -server master:2181
Connecting to master:2181
2017-07-18 03:00:34,503 [myid:] - INFO [main:Environment@100] - Client environment:zookeeper.version=3.4.6-1569965, built on 02/20/2014 09:09 GMT
2017-07-18 03:00:34,508 [myid:] - INFO [main:Environment@100] - Client environment:host.name=master
2017-07-18 03:00:34,509 [myid:] - INFO [main:Environment@100] - Client environment:java.version=1.8.0_131
2017-07-18 03:00:34,514 [myid:] - INFO [main:Environment@100] - Client environment:java.vendor=Oracle Corporation
2017-07-18 03:00:34,514 [myid:] - INFO [main:Environment@100] - Client environment:java.home=/usr/local/java/jre
2017-07-18 03:00:34,514 [myid:] - INFO [main:Environment@100] - Client environment:java.class.path=/usr/local/zookeeper/bin/../build/classes:/usr/local/zookeeper/bin/../build/lib/*.jar:/usr/local/zookeeper/bin/../lib/slf4j-log4j12-1.6.1.jar:/usr/local/zookeeper/bin/../lib/slf4j-api-1.6.1.jar:/usr/local/zookeeper/bin/../lib/netty-3.7.0.Final.jar:/usr/local/zookeeper/bin/../lib/log4j-1.2.16.jar:/usr/local/zookeeper/bin/../lib/jline-0.9.94.jar:/usr/local/zookeeper/bin/../zookeeper-3.4.6.jar:/usr/local/zookeeper/bin/../src/java/lib/*.jar:/usr/local/zookeeper/bin/../conf:
2017-07-18 03:00:34,514 [myid:] - INFO [main:Environment@100] - Client environment:java.library.path=/usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib
2017-07-18 03:00:34,515 [myid:] - INFO [main:Environment@100] - Client environment:java.io.tmpdir=/tmp
2017-07-18 03:00:34,515 [myid:] - INFO [main:Environment@100] - Client environment:java.compiler=
2017-07-18 03:00:34,516 [myid:] - INFO [main:Environment@100] - Client environment:os.name=Linux
2017-07-18 03:00:34,516 [myid:] - INFO [main:Environment@100] - Client environment:os.arch=amd64
2017-07-18 03:00:34,517 [myid:] - INFO [main:Environment@100] - Client environment:os.version=2.6.32-696.el6.x86_64
2017-07-18 03:00:34,517 [myid:] - INFO [main:Environment@100] - Client environment:user.name=root
2017-07-18 03:00:34,517 [myid:] - INFO [main:Environment@100] - Client environment:user.home=/root
2017-07-18 03:00:34,517 [myid:] - INFO [main:Environment@100] - Client environment:user.dir=/root
2017-07-18 03:00:34,521 [myid:] - INFO [main:ZooKeeper@438] - Initiating client connection, connectString=master:2181 sessionTimeout=30000 watcher=org.apache.zookeeper.ZooKeeperMain$MyWatcher@531d72ca
Welcome to ZooKeeper!
2017-07-18 03:00:34,572 [myid:] - INFO [main-SendThread(master:2181):ClientCnxn$SendThread@975] - Opening socket connection to server master/192.168.1.151:2181. Will not attempt to authenticate using SASL (unknown error)
JLine support is enabled
2017-07-18 03:00:34,770 [myid:] - INFO [main-SendThread(master:2181):ClientCnxn$SendThread@852] - Socket connection established to master/192.168.1.151:2181, initiating session
[zk: master:2181(CONNECTING) 0] 2017-07-18 03:00:34,856 [myid:] - INFO [main-SendThread(master:2181):ClientCnxn$SendThread@1235] - Session establishment complete on server master/192.168.1.151:2181, sessionid = 0x5d51e6be550002, negotiated timeout = 30000
WATCHER::
WatchedEvent state:SyncConnected type:None path:null
连接成功之后,系统会输出Zookeeper的相关环境及配置信息,并在屏幕输出“welcome to Zookeeper!”等信息。输入help之后,屏幕会输出可用的Zookeeper命令
[zk: master:2181(CONNECTED) 3] help
ZooKeeper -server host:port cmd args
stat path [watch]
set path data [version]
ls path [watch]
delquota [-n|-b] path
ls2 path [watch]
setAcl path acl
setquota -n|-b val path
history
redo cmdno
printwatches on|off
delete path [version]
sync path
listquota path
rmr path
get path [watch]
create [-s] [-e] path data acl
addauth scheme auth
quit
getAcl path
close
connect host:port
(1) 使用ls命令查看当前Zookeeper中所包含的内容:ls /
[zk: master:2181(CONNECTED) 4] ls /
[zookeeper, hbase]
[zk: master:2181(CONNECTED) 5]
(2) 创建一个新的Znode节点”zk”,以及和它相关字符,执行命令:create /zk myData
[zk: master:2181(CONNECTED) 5] create /zk myData
Created /zk
(3) 使用ls命令来查看现在Zookeeper的中所包含的内容:ls /
[zk: master:2181(CONNECTED) 0] ls /
[zk, zookeeper, hbase]
(4) 使用get命令来确认所创建的Znode是否包含创建的字符串,执行命令:get /zk
[zk: master:2181(CONNECTED) 1] get /zk
myData
cZxid = 0x100000055
ctime = Tue Jul 18 03:16:47 CST 2017
mZxid = 0x100000055
mtime = Tue Jul 18 03:16:47 CST 2017
pZxid = 0x100000055
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 6
numChildren = 0
(5) 通过set命令来对zk所关联的字符串进行设置,执行命令:set /zk hello
[zk: master:2181(CONNECTED) 2] set /zk hello
cZxid = 0x100000055
ctime = Tue Jul 18 03:16:47 CST 2017
mZxid = 0x100000058
mtime = Tue Jul 18 03:19:33 CST 2017
pZxid = 0x100000055
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
(6) 使用get命令来查看,上次修改的内容,执行命令:get /zk
[zk: master:2181(CONNECTED) 3] get /zk
hello
cZxid = 0x100000055
ctime = Tue Jul 18 03:16:47 CST 2017
mZxid = 0x100000058
mtime = Tue Jul 18 03:19:33 CST 2017
pZxid = 0x100000055
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
(7) 将刚才创建的Znode删除,执行命令:delete /zk
(8) 最后再次使用ls命令查看Zookeeper中的内容,执行命令:ls /
[zk: master:2181(CONNECTED) 5] ls /
[zookeeper, hbase]
Zookeeper的api的简单使用
Zookeeper API共包含五个包,分别为:
(1)org.apache.zookeeper
(2)org.apache.zookeeper.data
(3)org.apache.zookeeper.server
(4)org.apache.zookeeper.server.quorum
(5)org.apache.zookeeper.server.upgrade
其中org.apache.zookeeper,包含Zookeeper类,他是编程时最常用的类文件。这个类是Zookeeper客户端的主要类文件。如果要使用Zookeeper服务,应用程序首先必须创建一个Zookeeper实例,这时就需要使用此类。一旦客户端和Zookeeper服务建立起了连接,Zookeeper系统将会给次连接会话分配一个ID值,并且客户端将会周期性的向服务器端发送心跳来维持会话连接。只要连接有效,客户端就可以使用Zookeeper API来做相应处理了。
Zookeeper类提供了如下图所示的几类主要方法
简单状态查询,stat对象为放回状态对象
public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
String connectString = "master:2181,slave1:2181,slave2:2181";
/**
* 异步完成
* connectString 连接串
* sessionTimeout 超时时间
* watcher 不检查null
*/
ZooKeeper zk = new ZooKeeper(connectString, 2000, null);
Stat stat = new Stat();
zk.getData("/", null, stat);
}
创建路径
//创建路径
@Test
public void createPath() throws Exception{
ZooKeeper zk = new ZooKeeper(connectString, 2000, null);
String path ="/hello";
String data = "hello_data";
//Ids访问控制列表 控制权限
String retPath=zk.create(path, data.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println(retPath);
}
删除路径
//删除路径
@Test
public void deletePath() throws Exception{
ZooKeeper zk = new ZooKeeper(connectString, 2000, null);
String path ="/hello";
//version要删除的版本号dataVersion,数据版本
zk.delete(path, 0);
}
设置数据
// 设置数据 添加子节点只需向path路径下添加路径
@Test
public void setData() throws Exception {
ZooKeeper zk = new ZooKeeper(connectString, 2000, null);
String path = "/hello/hello_data";
Stat stat = zk.setData(path, "t1".getBytes(), 0);
System.out.println(stat.getVersion());
}
获取子节点
@Test
public void getChildren() throws Exception {
ZooKeeper zk = new ZooKeeper(connectString, 2000, null);
List list = zk.getChildren("/", null);
for (String string : list) {
System.out.println(string);
}
}
观察者
// 观察者 zk发生变化时触发
@Test
public void testWatcher() throws Exception {
// 内部类
// zk事件激活是一次性触发
Watcher w = new Watcher() {
@Override
public void process(WatchedEvent event) {
// TODO Auto-generated method stub
System.out.println("有事情发生" + event.getType());
}
};
ZooKeeper zk = new ZooKeeper(connectString, 2000, w);
String path = "/hello";
zk.getData(path, w, null);
zk.setData(path, "t2".getBytes(), 1);
while (true) {
Thread.sleep(5000);
}
}
假设一组服务器,用于为客户端提供一些服务。我们希望每个客户端都能够能够找到其中一台服务器,使其能够使用这些服务,挑战之一就是维护这组服务器列表。这组服务器的成员列表明显不能存在网络中的单个节点上,因为如果那个节点发生故障,就意味着是整个系统的故障(我们希望这个列表有很高的可用性)。假设我们有了一个可靠的方法解决了这个成员列表的存储问题。如果其中一台服务器出现故障,我们仍然需要解决如何从服务器成员列表中将它删除的问题。某个进程需要负责删除故障服务器,但注意不能由故障服务器自己来完成,因为故障服务器已经不再运行。
我们所描述的不是一个被动的分布式数据结构,而是一个主动的、能够在某个外部事件发生时修改数据项状态的数据结构。ZooKeeper提供这种服务,所以让我们看看如何使用它来实现这种众所周知的组成员管理应用。
ZooKeeper中的组成员关系
理解ZooKeeper的一种方法就是将其看作一个具有高可用性的文件系统。但这个文件系统中没有文件和目录,而是统一使用“节点”(node)的概念,称为znode。znode既可以作为保存数据的容器(如同文件),也可以作为保存其他znode的容器(如同目录)。所有的znode构成一个层次化的命名空间。一种自然的建立组成员列表的方式就是利用这种层次结构,创建一个以组名为节点名的znode作为父节点,然后以组成员名(服务器名)为节点名来创建作为子节点的znode。如下图给出了一组具有层次结构的znode。
创建组
//为组名为/zoo的组创建一个znode
public class CreateGroup implements Watcher {
private static final String connectString = "master:2181";
private static final String groupName = "zoo";
private static final int SESSION_TIMEOUT = 5000;
private ZooKeeper zk;
// 使用Java的CountDownLatch类来阻止使用新建的ZooKeeper对象,直到这个ZooKeeper对象已经准备就绪
private CountDownLatch connectedSignal = new CountDownLatch(1);
/**
* 客户端已经与ZooKeeper建立连接后,Watcher的process()方法会被调用 参数是一个表示该连接的事件
* SyncConnected连接事件
*/
@Override
public void process(WatchedEvent event) {
// TODO Auto-generated method stub
if (event.getState() == KeeperState.SyncConnected) {
/**
* 调用CountDownLatch的countDown()方法来递减它的计数器
* 锁存器(latch)被创建时带有一个值为1的计数器,用于表示在它释放所有等待线程之前需要发生的事件数。
* 在调用一欢countDown()方法之后,计数器的值变为0,则await()方法返回
*/
connectedSignal.countDown();
}
}
public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
/**
* connect方法实例化了一个新的ZooKeeper类的对象,这个类是客户端API中的主要类,
* 并且负责维护客户端和ZooKeeper服务之间的连接
*
*/
CreateGroup createGroup = new CreateGroup();
createGroup.connect(connectString);
createGroup.create(groupName);
createGroup.close();
}
private void close() throws InterruptedException {
zk.close();
}
private void create(String groupName) throws KeeperException, InterruptedException {
String path = "/" + groupName;
if (zk.exists(path, false) == null) {
/**
* 使用ZooKeeper实例中的create()方法来创建一个新的ZooKeeper的znode 路径:用字符串表示。
* znode的内容:字节数组,本例中使用空值。
* 访问控制列表:简称ACL,本例中使用了完全开放的ACL,允许任何客户端对znode进行读写。
* 创建znode的类型:有两种类型的znode:短暂的和持久的。 PERSISTENT为持久
*/
zk.create(path, null/* data */, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
System.out.println("Created:" + path);
}
private void connect(String hosts) throws IOException, InterruptedException {
/**
* ZooKeeper类的构造函数有三个参数 第一个是:ZooKeeper服务的主机地址,可指定端口,默认端口是2181。
* 第二个是:以毫秒为单位的会话超时参数,这里我们设成5秒。 第三个是:参数是一个Watcher对象的实例。
* Watcher对象接收来自于ZooKeeper的回调,以获得各种事件的通知
* CreateGroup是一个Watcher对象,因此我们将它传递给ZooKeeper的构造函数
*/
zk = new ZooKeeper(hosts, SESSION_TIMEOUT, this);
connectedSignal.await();
}
}
ConnectionWatcher基类
public class ConnectionWatcher implements Watcher {
private static final int SESSION_TIMEOUT = 5000;
protected ZooKeeper zk;
CountDownLatch connectedSignal = new CountDownLatch(1);
public void connect(String host) throws IOException, InterruptedException {
zk = new ZooKeeper(host, SESSION_TIMEOUT, this);
connectedSignal.await();
}
@Override
public void process(WatchedEvent event) {
// TODO Auto-generated method stub
if (event.getState() == KeeperState.SyncConnected) {
connectedSignal.countDown();
}
}
public void close() throws InterruptedException {
zk.close();
}
}
加入组
public class JoinGroup extends ConnectionWatcher {
public void join(String groupName, String memberName) throws KeeperException, InterruptedException {
String path = "/" + groupName + "/" + memberName;
/**
* EPHEMERAL为短暂
* 创建短暂znode,作为组znode的子节点,然后通过休眠来模拟正在做某种工作,直到该进程被强行终止
* 随着进程终止,这个短暂znode被ZooKeeper删除
*/
String createdPath = zk.create(path, null, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
System.out.println("Created:" + createdPath);
}
private static final String connectString = "master:2181";
private static final String groupName = "zoo";
//private static final String memberName = "duck";
//private static final String memberName = "cow";
private static final String memberName = "goat";
public static void main(String[] args) throws InterruptedException, IOException, KeeperException {
JoinGroup joinGroup = new JoinGroup();
joinGroup.connect(connectString);
joinGroup.join(groupName, memberName);
// stay alive until process is killed or thread is interrupted
Thread.sleep(Long.MAX_VALUE);
}
}
显示成员列表
public class ListGroup extends ConnectionWatcher {
public void list(String groupNmae) throws KeeperException, InterruptedException {
String path = "/" + groupNmae;
try {
/**
* 调用了getChildren()方法来检索并打印输出一个znode的子节点列表
* 用参数为:该znode的路径和设为false的观察标志
*/
List children = zk.getChildren(path, false);
if (children.isEmpty()) {
System.out.printf("No memebers in group %s\n", groupNmae);
System.exit(1);
}
for (String child : children) {
System.out.println(child);
}
} catch (KeeperException.NoNodeException e) {
System.out.printf("Group %s does not exist \n", groupNmae);
System.exit(1);
}
}
private static final String connectString = "master:2181";
private static final String groupName = "zoo";
public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
ListGroup listGroup = new ListGroup();
listGroup.connect(connectString);
listGroup.list(groupName);
listGroup.close();
}
}
删除组
public class DeleteGroup extends ConnectionWatcher {
private static final String connectString = "master:2181";
private static final String groupName = "zoo";
public void delete(String groupName) throws InterruptedException, KeeperException {
String path = "/" + groupName;
List children;
try {
children = zk.getChildren(path, false);
for (String child : children) {
zk.delete(path + "/" + child, -1);
}
zk.delete(path, -1);
} catch (KeeperException.NoNodeException e) {
System.out.printf("Group %s does not exist\n", groupName);
System.exit(1);
}
}
public static void main(String[] args) throws InterruptedException, IOException, KeeperException {
DeleteGroup deleteGroup = new DeleteGroup();
deleteGroup.connect(connectString);
deleteGroup.delete(groupName);
deleteGroup.close();
}
}