前言
之前对于zookeeper的接触只限于注册中心,对于其内容并没有实质性的了解,刚毕业的时候,写过几个关于zookeeper简单应用的博客,如今再回头看,感觉就是小孩子的涂鸦式作品一样,对于zookeeper的理解根本就不系统。对它的理解也就局限于几个命令而已,实在惭愧。之前的博客地址:zookeeper集群搭建,zookeeper的几个简单命令
zookeeper是什么
他不止是注册中心,工作中用的较多的就是ZK+DUBBO,但我接触最多的依旧是注册中心,但是zookeeper由于自己本身节点的相关特性,它可以做的事情远不止注册中心这么简单,大牛的博客如下:zookeeper能做什么。
简单点说:zookeeper由于本身数据结构的特性,除了注册中心以外,还可以用作配置中心,集群管理(帮助选举集群的master),命名服务以及分布式锁。
zookeeper的数据结构
其实我个人理解,从某一种程度上来说,zookeeper能做的很多事,跟其数据结构有关。
zookeeper集群中的角色
leader角色是zookeeper集群中的核心角色,主要的工作任务有两项,1:事务请求的唯一调度和处理者,保证集群事务处理的正确性,2:集群内部各服务器的调度者。
follower角色,1:处理客户端的非事务请求,转发事务请求给leader。2:参与事务请求Proposal的投票。3:参与leader选举的投票
observer角色,在follower角色过多时,参与选举较消耗性能,为了提升性能,新增了Observer角色,只处理非事务请求,并转发非事务请求,不参与投票。
zookeeper中的节点类型与结构
其实zookeeper中的数据结构和文件系统结构类似,唯一不同的就是每一个zookeeper中的ZNode节点可以挂载数据。如下所示(图片来源于大牛的博客):
图中的每一个节点就成为znode,与文件系统不同的是znode可以设置数据。znode有常见的四种类型,这四种类型从某一种程度上来说帮助zookeeper实现了很丰富的功能。
1、PERSISTENT-持久化目录节点
客户端与zookeeper断开连接后,该节点依旧存在
2、 PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点
客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号
3、EPHEMERAL-临时目录节点
客户端与zookeeper断开连接后,该节点被删除
4、EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点
客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号。常用于实现分布式锁或者服务注册。
5、CONTAINER-容器节点
目录下是一串连续的临时节点,临时节点删除之后容器节点也会被删除。
curator操作zookeeper的节点
一般企业操作ZooKeeper的客户端都会使用Apache Curator。和ZkClient一样,Curator解决了很ZooKeeper客户端非常底层的细节开发工作,包括连接重连、反复注册Watcher和NodeExistsException异常等,目前已经成为了Apache的顶级项目,是全世界范围内使用最广泛的ZooKeeper客户端之一。
除了封装一些开发人员不需要特别关注的底层细节之外,Curator还在ZooKeeperAPI的基础上进行了包装,提供了一套易用性和可读性更强的Fluent风格的客户端API框架。除此之外,Curator中还提供了ZooKeeper各种应用场景(Recipe,如共享锁服务、Master选举机制和分布式计数器等)的抽象封装。
连接
在建立连接的时候,先需要初始化重试策略,这里先从重试策略开始介绍。
//简单的重试策略,第一个参数:重试之间最长的等待时间,最大的重试次数
new ExponentialBackoffRetry(1000,3)
//第一个参数,重试的次数,第二个参数:每次重试间隔的时间(ms)
RetryPolicy retryPolicy = new RetryNTimes(3, 5000);
//只重试一次,参数为重试间隔的时间
RetryPolicy retryPolicy2 = new RetryOneTime(3000);
//永远需要重试,这里不推荐使用
RetryPolicy retryPolicy3 = new RetryForever(retryIntervalMs)
//第一个参数:最大重试时间,第二个参数:每次重试间隔时间
RetryPolicy retryPolicy4 = new RetryUntilElapsed(2000, 3000);
构建连接依旧采用较熟悉的工厂模式,只需要在常用的基础上指定重试策略即可,如下所示:
CuratorFramework curatorFramework = CuratorFrameworkFactory
.builder().connectString(connStr).sessionTimeoutMs(5000)//设置会话时间
.namespace("learn")//设置namespace,这个就不解释了,与包名的作用一样
.retryPolicy(new ExponentialBackoffRetry(1000,3))//指定重试策略
.build();
启动与关闭连接
curatorFramework.start();//启动连接
curatorFramework.close();//关闭连接
新增
创建节点较为简单,也都是一些简单的api操作。
/**
* 创建节点
* @param curatorFramework
* @throws Exception
*/
public static void createNode(CuratorFramework curatorFramework) throws Exception {
String nodePath = "/node/create";
byte[] data = "createData".getBytes();
curatorFramework.create().creatingParentsIfNeeded()//递归创建节点
.withMode(CreateMode.PERSISTENT)//创建持久性节点
.forPath(nodePath,data);//指定创建的内容
}
运行之后的结果为:
删除
直接上代码。
/**
* 删除节点数据
* @param curatorFramework
*/
public static void deleteNodeData(CuratorFramework curatorFramework) throws Exception {
String nodePath = "/";
Stat stat = new Stat();
byte[] bytes = curatorFramework.getData().storingStatIn(stat).forPath(nodePath);//查询出来获得版本号
System.out.println("获取到的节点的数据为:"+new String(bytes));
curatorFramework.delete()
.guaranteed()//保证删除
.deletingChildrenIfNeeded()//递归删除子节点
.withVersion(stat.getVersion())//指定删除的版本号
.forPath(nodePath);
}
运行后的结果:learn的namespace下没有了数据。
修改
修改分为两种,一种是直接修改,另一种是指定版本号修改,下述示例中有一个睡眠5秒的操作,如果在这5S的过程中修改了zk指定的节点,下面的操作会抛出异常,其实这就是个乐观锁的机制。
/**
* 修改节点数据
* @param curatorFramework
*/
public static void updateNodeData(CuratorFramework curatorFramework) throws Exception {
String nodePath = "/node/create";
Stat stat = curatorFramework.setData().forPath(nodePath, "update01".getBytes());
System.out.println("第一次修改后的数据为:"+new String(curatorFramework.getData().forPath(nodePath)));
Thread.sleep(50000);
Stat stat02 = curatorFramework.setData()
.withVersion(stat.getVersion())//指定版本信息修改节点的数据
.forPath(nodePath, "update02".getBytes());
System.out.println("利用版本号修改后的数据为:"+new String(curatorFramework.getData().forPath(nodePath)));
}
正常运行结果:
如果在中途中修改了该数据,则会抛出异常——BadVersion
查询
获取指定节点的数据。
/**
* 获取指定节点的数据
* @param curatorFramework
* @throws Exception
*/
public static void getNode(CuratorFramework curatorFramework) throws Exception {
String nodePath = "/node/create";
byte[] bytes = curatorFramework.getData().forPath(nodePath);
System.out.println("第一次获得节点的数据为:"+new String(bytes));
Stat stat = new Stat();//新建一个Stat对象,查询出来的节点属性会保存在这个对象中
byte[] statBytes = curatorFramework
.getData()
.storingStatIn(stat)//传入一个旧的stat对象,用来存储服务端返回的最新节点的状态信息
.forPath(nodePath);
System.out.println("第二次获取节点的数据,包括状态数据:"+new String(statBytes));
System.out.println("获得到的Stat为:"+stat.toString());
}
运行结果如下:
获取直接子节点的数据
/**
* 获取子节点数据
* @param curatorFramework
* @throws Exception
*/
public static void getChildNodeData(CuratorFramework curatorFramework) throws Exception {
String nodePath = "/node";
List<String> children = curatorFramework.getChildren().forPath(nodePath);//getChildren返回一个list集合
System.out.println("开始打印子节点信息:");
children.forEach(System.out::println);
System.out.println("打印结束");
}
运行结果如下:
总结
这篇博客总结了zk的基本认识,并利用curator完成了zookeeper的crud的实例,但是zookeeper的作用远不止于这些,后续会利用zookeeper完成分布式锁,完成leader选举的实例。