zookeeper(一)——使用curator操作zookeeper

前言

之前对于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选举的实例。

发布了129 篇原创文章 · 获赞 37 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/liman65727/article/details/100987822