Zookeeper概念

Zookeeper是一个树形目录服务,简称zk。

Zookeeper是一个分布式的、开源的分布式应用程序的协调服务

Zookeeper提供主要的功能包括:配置管理,分布式锁,集群管理

Zookeeper命令操作

zk数据模型

zk中的每一个节点都被称为:ZNode,每个节点上都会保存自己的数据和节点信息。节点可以拥有子节点,同时也允许少量(1MB)数据存储在该节点之下

节点可以分为四大类:

PERSISTENT 持久化节点EPHEMERAL 临时节点:-ePERSISTENT_SEQUENTIAL持久化顺序节点:-sEPHEMERAL_SEQUENTIAL临时顺序节点:-es

zk服务端命令

启动zk服务

./zkServer.sh start

zk查看zk服务状态

./zkServer.sh status

停止zk服务

./zkServer.sh stop

重启zk服务

./zkServer.sh restart

zk客户端命令

zkCli.sh连接ZooKeeper服务命令

./zkCli.sh [-server ip地址:2181] (如果是连接本机zk服务,可以省略[]中的内容)

查看zk中的节点

ls /[节点名称]

创建节点方法

create [节点类型] /父节点 [存储信息] (存储信息可以不写)

临时节点创建 -e(服务器关闭,下次连接就会删除)

顺序节点创建 -s

临时顺序节点创建 -es

查看节点存储信息的方法

get /目录

ls -s 是查询结点状态信息

设置节点数据

set /目录 存储信息

删除节点

delete /目录(只能删除空目录)

deleteall /目录(有子结点也可以删除)

在创建节点时,如果没有指定存储数据,那么默认存储的数据是当前服务器的ip地址

Curator介绍

CUrator是Zookeeper的Java客户端库,常见的Zookeeper Java API有三种:

原生Java APIZkClientCurator

相较于原生Java API,Curator使用较为简单,其次,高版本的Curator可以向下兼容zk,但zk无法向下兼容Curator

Curator API常用操作

连接zk服务

一共有两种连接客户端的方式,一种是通过newClient(),一种是通过Builder链式编程创建客户端对象

@Test

public void test() throws Exception {

//指定重试策略

RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 10);

//第一种连接zk方式

/*

参数1:指定需要连接的zk地址与端口,如果是集群模式,使用,分隔开

参数2:会话超时时间

参数3:连接超时时间

参数4:重试策略

*/

CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.116.131:2181", 60 * 1000, 15 * 1000, retryPolicy);

client.start();

//第二种连接方式,可以指定namespace,其实是指定根目录(此后该客户端对象的curd操作都会在指定的根目录下进行)

CuratorFramework client2 = CuratorFrameworkFactory.builder().connectString("192.168.116.131:2181")

.sessionTimeoutMs(60 * 1000)

.connectionTimeoutMs(15 * 1000)

.retryPolicy(retryPolicy)

.namespace("zmt").build();

client2.start();

}

创建节点

创建节点默认创建节点类型为持久化,返回结果为路径,如果不指定节点存储类型,那么默认存储客户端的ip地址

@Test

public void testCreate() throws Exception {

//如果不指定参数2,那么该节点的存储数据为客户端的ip地址

String path = client.create().forPath("/app1","zhangsan".getBytes());

System.out.println(path);

//我们可以通过指定withMode来确定需要创建的节点类型,参数为枚举类

client.create().withMode(CreateMode.EPHEMERAL).forPath("/app2");

//创建多级节点,创建父结点,如果需要

client.create().creatingParentsIfNeeded().forPath("/app3/p1");

}

查询节点

我们可以查询节点的存储信息,也可以查询节点的子节点信息,也可以查询节点的状态信息

@Test

public void testGet() throws Exception {

//获取节点存储信息

byte[] data = client.getData().forPath("/app1");

System.out.println(new String(data));

//获取子节点信息

List list = client.getChildren().forPath("/app3");

System.out.println(list);

//获取节点状态信息

Stat status = new Stat();

client.getData().storingStatIn(status).forPath("/app1");

System.out.println(status);

}

修改节点

因为可能存在多个客户端连接同一个zookeeper,因此可能会出现修改节点数据时,其他节点也在修改的问题,因此为了避免出现同时修改同一个数据的情况发生,我们应该先获取数据的版本,然后修改数据时根据数据的版本是否一致再决定是否进行修改。

@Test

public void testSetForVersion() throws Exception {

int version;

Stat stat = new Stat();

client.getData().storingStatIn(stat).forPath("/app1");

version = stat.getVersion();

System.out.println(version);

client.setData().withVersion(version).forPath("/app1","lisi".getBytes());

}

删除节点

一般我们都要添加guaranteed()方法来避免网络问题产生的删除失败的问题

@Test

public void testDelete() throws Exception {

//删除单个节点

client.delete().forPath("/app1");

//删除多级节点

client.delete().deletingChildrenIfNeeded().forPath("/app3");

//删除失败进行重试,直到删除成功

//比如说网络不好,导致删除失败,可以重复进行删除

client.delete().guaranteed().forPath("/app2");

//删除回调

client.delete().guaranteed().inBackground(new BackgroundCallback() {

//执行回调方法

@Override

public void processResult(CuratorFramework client, CuratorEvent event) throws Exception {

System.out.println("节点被删除");

System.out.println(event);

}

}).forPath("/app4");

}

Watch事件监听

zk运行用户在指定节点上注册一些Watcher,并且在一些特定事件触发的时候,zk服务端会将事件通知到感兴趣的客户端上去,该机制是zk实现分布式协调服务的重要特性。

zk中引入了Watch机制来实现了发布订阅功能,能够让多个订阅者同时监听某一个对象,当一个对象自身状态发生变化时,会通知所有的订阅者

zk提供了3种Watcher:

NodeCache:只监听一个特定结点PathChildrenCache:监控一个Znode的子节点TreeCache:前两个结合,监听自己和自己所有子节点

NodeCache的简单使用

@Test

public void testNodeCache() throws Exception {

NodeCache nodeCache = new NodeCache(client, "/app1");

nodeCache.getListenable().addListener(new NodeCacheListener() {

@Override

public void nodeChanged() throws Exception {

System.out.println("节点发生变化");

//获取节点变化后的值

byte[] data = nodeCache.getCurrentData().getData();

System.out.println(new String(data));

}

});

nodeCache.start();

while (true){

}

}

PathChildrenCache的简单使用

@Test

public void testChildrenCache() throws Exception {

PathChildrenCache pathChildrenCache = new PathChildrenCache(client, "/", true);

pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() {

@Override

public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception {

System.out.println("节点发生变化");

System.out.println(pathChildrenCacheEvent);

PathChildrenCacheEvent.Type type = pathChildrenCacheEvent.getType();

if (type.equals(PathChildrenCacheEvent.Type.CHILD_UPDATED)){

//如果是update事件

byte[] data = pathChildrenCacheEvent.getData().getData();

System.out.println(new String(data));

}

}

});

pathChildrenCache.start();

while (true) {

}

}

TreeCache的简单使用

@Test

public void testTreeCache() throws Exception {

TreeCache treeCache = new TreeCache(client,"/");

treeCache.getListenable().addListener(new TreeCacheListener() {

@Override

public void childEvent(CuratorFramework curatorFramework, TreeCacheEvent treeCacheEvent) throws Exception {

System.out.println("数据发生变化");

System.out.println(treeCacheEvent);

}

});

treeCache.start();

while (true) {

}

}

分布式锁

作用于多个JVM环境,保证多线程安全。

实现分布式锁的原理:当客户端要获取锁时,创建节点,使用完锁,删除该节点

客户端获取锁时,在lock节点下创建临时顺序结点然后客户端获取lock下面所有的子节点,如果发现自己创建的子节点序号最小,那么就认为该客户端获取到了锁。使用完后把节点删除如果不是最小的结点,说明此时自己还没有获取到锁,此时客户端需要找到比自己小的哪个结点,同时对其注册事件监听器,监听删除事件。如果比自己小一个的结点被删除,则客户端的Watcher会收到通知,此时再次判断自己的结点是否最小,如果不是,重复以上步骤

采取临时节点是为了避免客户端在获取到锁之后处理业务时宕机,当客户端宕机之后,会话结束,临时节点会自动删除

Curator实现分布式锁API

一共有五种方案:

分布式排他锁分布式可重入排他锁分布式读写锁将多个锁作为单个实体管理的容器共享信号量

一个简单的售票案例

public class Ticket12306 implements Runnable {

//模拟十张票

private int num = 10;

//分布式可重入式锁

private InterProcessMutex lock;

public Ticket12306() {

RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 2);

CuratorFramework client = CuratorFrameworkFactory.builder().connectString("192.168.116.131:2181")

.connectionTimeoutMs(60*1000)

.sessionTimeoutMs(15*1000)

.retryPolicy(retryPolicy)

.build();

client.start();

lock = new InterProcessMutex(client, "/lock");

}

@Override

public void run() {

while (true) {

try {

lock.acquire(3, TimeUnit.SECONDS);

if (num > 0) {

Thread.sleep(100);

System.out.println(Thread.currentThread().getName() + "出售了一张票:" + num);

num--;

}

} catch (Exception e) {

System.out.println("我出错了??");

e.printStackTrace();

} finally {

try {

lock.release();

} catch (Exception e) {

e.printStackTrace();

}

}

}

}

}

测试 

public class TestTicket {

@Test

public void testTicket() throws Exception {

Ticket12306 ticket = new Ticket12306();

Thread t1 = new Thread(ticket, "携程");

Thread t2 = new Thread(ticket, "飞猪");

t1.start();

t2.start();

while(true){

}

}

}

实现分布式锁的核心代码如下

new 一个锁类型对象lock

lock.acquire()//获取锁

需要上锁的功能

lock.release();//释放锁

精彩文章

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。