Zookeeper(七)开源客户端

经过上面两节博客的介绍,朋友们应该会开始简单地使用ZooKeeper了。

在这一偏文章中,我们将围绕ZkClient和Curator这两个开源的ZooKeeper客户端产品,再来进一步看看如何更好地使用ZooKeeper。

ZkClient

ZkClient是Github上一个开源的Zookeeper客户端,是由Datameer的工程师StefanGroschupf和Peter Voss一起开发的。ZkClient在ZooKeeper原生API接口之上进行了包装,是一个更易用的Zookeeper客户端。同时,ZkClient在内部实现了诸如Session超时重连/Watcher反复注册等功能,是的ZooKeeper客户端的这些繁琐的细节工作对开发人员透明。
在本节中,我们将从创建会话,创建节点,读取数据,更新数据,删除节点和检测节点是否存在等方面来介绍如何使用ZkClient这个ZooKeeper客户端。当然,由于底层实现还是对ZooKeeper原生API的包装,因此本节不会进行太多原理性的描述。
我们先看下ZkClient的Maven依赖:
<dependencies>
       <dependency>
              <groupId>org.apache.zookeeper</groupId>
               <artifactId>zookeeper<artifactId>
               <version>${zookeeper.version}</version>
       </dependency>
       <dependency>
               <groupId>com.github.sgroschupf</groupId>
               <artifactId>zkclient</artifactId>
               <version>${zkclient.version}</version>
       </dependency>
</dependencies>

创建会话

上面两节中,我们已经介绍了如何通过实例化一个ZooKeeper对象来完成会话的创建。在本节中,我们将介绍如何使用ZkClient来完成同样的操作。在ZkClient中,有如下7种构造方法:
public ZkClient(String serverstring)
public ZkClient(String zkServers,int connectionTimeout)
public ZkClient(String zkServers,int sessionTimeout,int connectionTimeout)
public ZkClient(String zkServers,int sessionTimeout,int connectionTimeout,ZkSerializer zkSerializer)
public ZkClient(IZkConnection connection)
public ZkClient(IZkConnection connection,int connectionTimeout)
public ZkClient(IZkConnection zkConnection,int connectinTimeout,ZkSerializer zkSerializer)

ZkClient构造方法参数说明,表5-11
参数名说明
zkServers指ZooKeeper服务器列表,由英文状态逗号分开的host:port字符串组成,每一个代表一台ZooKeeper机器,例如192.168.1.1:2181,192.168.1.2:2181,192.168.1.3:2181
sessionTimeout会话超时时间,单位为毫秒。默认是30000ms
connectionTimeout链接创建超时时间,单位为毫秒。此参数表明如果在这个时间段内还是无法和ZooKeeper建立连接,那么就放弃连接,直接抛出异常
connectionIZkConnection接口的实现类
zkSerializer自定义序列化器

注意,在ZkClient的构造方法中,很多参数和ZooKeeper原生的构造方法中的参数一致,所以表5-11只是坐简要介绍,具体可以参见表5-12中的相关介绍。

在讲解使用ZooKeeper原生APi创建会话的时候,我们提到:ZooKeeper会话的创建是一个异步的过程。对于ZooKeeper客户端的这个特点,开发人员需要自己来进行等待处理。而ZkClient通过内部包装,将这个异步的会话创建同步化了,这对于开发者的使用来说非常方便。

接下来看看IZkConnection接口。org.IOItec.zkclient,IZkConnection接口是对ZooKeeper原生接口最直接的包装,也是和ZooKeeper最直接的交互层,里面包含了添,删,改,查等一些列接口的定义。ZkClient默认提供对IZkConnection接口的两种实现,ZkConnection和InMemoryConnection,前者是我们最常用的实现方式。通常开发人员不需要对IZkConnection进行改造,直接使用ZkConnection这个实现就可以完成绝大部分的业务贡献。

最后我们来看看ZkClient和ZooKeeper原生构造方法的最大区别,那就是在ZkClient的构造方法中,不再提供传入Watcher对象的参数了。那么,客户端如何去监听服务端的相关事件呢?别担心,ZkClient引入了大多数java程序员都使用过的Listener来实现Watcher注册。值得一提的是,ZkClient从API级别来支持Watcher监听的注册,这样的用法更贴近Java工程师的习惯。关于事件监听的注册方法,在后面会做详细讲解。

清单5-17,使用ZkClient创建会话

public class Create_Session_Sample{
      public static void main(String[] args) throws IOException,InterruptedException{
           ZkClient zkClient = new ZkClient("domain1.book.zookeeper:2181",5000);
           System.out.println("ZooKeeper session established.");  
      }
}

运行程序,输出结果如下:

ZooKeeper session established

上面这个示例展示了如何使用ZkClient来创建会话。

创建节点


ZkClient中提供了以下一系列接口来创建节点,开发者可以通过这些接口来进行各种类型的节点创建:


清单5-18 使用ZkClient创建节点

packege book.chapter
import org.IOItec.zkclient.ZkClient;

public class Create_Node_Sample{
      public static void main(String[] args) throws Excpetion{
           ZkClient zkClient = new ZkClient("domain1.book.zookeeper:2181",5000);
           String path = "/zk-book/c1";
           zkClient.createPersistent(path,true); 
      }
}

在上面这个示例程序中,我们使用ZkClient的createPersisten接口创建节点,并且设置createParents参数为true,表明需要递归创建父节点。很显然,使用ZkClient省去了很多繁琐的工作。

另外,ZkClient的API中还提供了支持异步创建节点的方法,鉴于异步方式的使用和上文中讲解的非常类似,这里不再赘述。

关于读取,修改节点,操作都是类似,下面我们主要来看看ZooKeeper的另一种开源开户端:Curator。

Curator


Curator是Netflix公司开源的一套ZooKeeper客户端框架,作者是Jordan Zimmerman。和ZiClient一样,Curator解决了很多ZooKeeper客户端非常底层的细节开发工作,包括连接重连,反复注册Watcher和NodeExistsExceptino异常等,目前已经成为了Apathe的顶级项目,是全世界使用最广泛的ZooKeeper客户端之一,Patrick Hunt(ZooKeeper代码的核心提交者)以一句“Guava is头Java what Curator is to ZooKeeper“(Curator对于ZooKeeper,可以说就像Guava工具集对于java平台一样,作用巨大)对其进行了高度评价。

除了封装一些开发人员不需要特别关注的底层细节之外,Curator还在ZooKeeper原生API的基础上进行了包装,提供了一套易用性和可读性更强的Fluent缝合的客户端API框架。

除此之外,Curator中还提供了ZooKeeper各种应用场景(Recipe,如共享锁服务/Master选举机制和分布式计数器等)的抽象封装。

在讲解API之前,首先来看看Curator的Maven依赖:

<dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-framework</artifactId>
        <version>2.4.2</version>
</dependency>

创建会话



使用Curator客户端创建会话的过程驭ZooKeeper的原生API和ZkClient两种创建会话的方式有很大的不同。具体如下。


1.使用CuratorFrameworkFactory这个工厂类的两个静态方法来创建一个客户端:


static CuratorFramework newClient(String connectionString,RetryPolicy retyrPolicy);

static CuratorFramework newClient(String conectString,int sessionTimeoutMs,int connectionTimeoutMs,RetryPolicy retryPolicy);

2.通过调用CuratorFramework中的start()方法来启动会话。

表5-20对构造方法中的各参数进行了说明。

表5-20

参数名说明
connectString指ZooKeeper服务器列表,由英文状态逗号分开的host:port字符串组成,每一个都代表一台ZooKeeper机器,例如,192.168.1.1:2181,192.168.1.2:2181,192.168.1.3:2181
retryPolicy重试策略,默认主要有四种实现,分别是Exponential BackoffRetry,RetryNTimes,RetyrOneTime,RetryUnitElapsed
sessionTimeoutMs会话超时时间,单位为毫秒。默认是60000ms
connectionTimeoutMs连接创建超时时间,单位为毫秒。默认是15000ms

使用Curator创建会话


清单5-21

package book.chapter
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apche.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;

//使用Curator来创建一个ZooKeeper客户端
public class Create_Session_Sample{
      RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);
      CuratorFramework client = CuratorFrameworkFactory.newClient("domain1.book.zookeeper:2181",5000,3000,retryPolicy);
      client.start();
      Thread.sleep(Integer.MAX_VALUE);
}

在上面这个示例程序中,我们首先创建了一个名为ExponentialBackoffRetry的重试策略,该重试策略是Curator默认提供的几种重试策略之一,其构造方法如下:

ExponentialBackoffRetry(int baseSleepTimeMs,int maxRetries);

ExponetialBackoffRetry(int baseSleepTimeMs,int maxRetries,int maxSleepMs);

ExponetialBackoffRetry构造方法参数说明如表5-22所示

表5-22

参数名说明
baseSleepTimeMs初始sleep时间
maxRetries最大重试次数
maxSleepMs最大sleep时间
使用Fluent风格的API接口来创建会话

Curator提供的API接口在设计上最大的亮点在于其遵循了Fluent设计风格,这也是和ZooKeeper原生API以及ZkClient客户端有很大不同的地方。清单5-22展示了如何使用Fluent风格的API接口来创建会话。

清单5-22

package book.chapter;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
//使用Fluent风格的API接口来创建一个ZooKeeper客户端
public class Create_Session_Sample_fluent{
      public static void main(String[] args) throws Exception{
            RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);
            CuratorFrameworkFactory.builder().connectString("domain1.book.zookeeper:2181").sessionTimeoutMs(5000).retryPolicy(retryPolicy).build();
             client.start();
             Thread.sleep(Integer.MAX_VALUE);
      }
}
使用Curator创建含隔离命名空间的会话

为了实现不同的ZooKeeper也无语之间的隔离,往往会为每个业务分配一个独立的命名空间,即指定一个ZooKeeper根路径。例如,下面所示的代码片段中定义了某一个客户端的独立命名空间/base,那么该客户端对ZooKeeper上数据节点的任何操作,都是基于该相对目录进行的:

CuratorFrameworkFactory.builder().connectString("domain1.book.zookeeper:2181").sessionTimeoutMs(5000).retryPolicy(retryPolicy).namespace("base").build();

我们后续会介绍更多关于客户端隔离命名空间的内容。

创建节点

Curator中提供了一系列Fluent风格的接口,开发人员可以通过对其进行自由组合来完成各种类型节点的创建。

清单5-23

CuratorFramework
  --public CreateBuilder create();
CreateBuilder
  --public
ProtectACLCreateModePathAndBytesable<String>creatingParentsIfNeeded();
CreateModable
  --public T withMode(CreateMode mode);
PathAndBytesable<T>
  --public T forPath(String path,byte[] data) throws Exception;
  --public TforPath(String path) throws Exception;

以上就是一系列最常用的创建节点API,下面通过一些场景来说明如何使用这些API。

创建一个节点,初始内容为空

client.create().forPath(path);

注意,如果没有设置节点属性,那么Curator默认创建的是持久节点,内容默认是空。这里的client是指上文中提到的一个已经完成会话创建并启动的Curator客户端实例,即CuratorFramework对象实例。

创建一个节点,附带初始内容

client.create().forPath(path,"init".getBytes());

也可以在创建节点的时候写入初始节点内容。和ZkClient不同的是,Curator任然是按照ZooKeeper原生API风格,使用byte[]作为方法参数。

创建一个临时节点,并自动递归创建父节点

client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPaht(path);

这个借口非常有用,在使用ZooKeeper的过程中,开发人员经常会碰到NoNodeException异常,其中一个可能的原因就是试图对一个不存在的父节点创建子节点。因此,开发人员不得不在每次创建节点之前,都判断一下该父节点是否存在——这个处理让人厌倦。在使用Curator之后,通过调用creatingParentsIfNeeded接口,Curator就能够自动地递归创建所有需要的父节点。

同时需要注意的一点是,由于在ZooKeeper中规定了所有非叶子节点必须为持久节点,调用上面这个API之后,只有paht参数对应的数据节点时临时节点,其父节点均为持久节点。

下面通过一个实际的例子来看看如何在代码中使用这些API

清单5-24

package book.chapter
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;

//使用Curator创建节点
public class Create_Node_Sample{
      static String path = "/zk-book/c1";
      static CuratorFramework client = CuratorFrameworkFactory.builder().connectString("domain1.book.zookeeper:2181").sessionTimeoutMs(5000).retryPolicy(new ExponentialBackoffRetry(1000,3)).build();

      public static void main(String[] args) throws Exception{
           client.start();
           client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(path,"init".getBytes());
       }
}

删除节点

在Curator中,可以通过以下API来删除指定节点:

清单5-25

CuratorFramework
  --pulbic DeleteBuilder delete();
Versionable<T>
   --public T withVersion(int version);
DeleteBuilder
  --public DeleteBuilderBase guaranteed();
PathAndBytesable<T>
 --public T forPath(String path,byte[] data) throws Exception;
  --public T forPath(String path) throws Exception;

以上就是一系列最常用的删除节点API,下面通过一些场景来说明如何使用这些API。

删除一个节点

client.delete().forPath(path);

注意,使用该接口,只能删除叶子节点。

删除一个节点,并递归删除其所有子节点

client.delete().deletingChildrenIfNeeded().forPath(path);

删除一个节点,强制指定版本进行删除

client.delete().withVersion(version).forPath(path);

删除一个节点,强制保证删除

client.delete().guaranteed().forPath(path);

注意,guaranteed()接口是一个保障措施,只要客户端会话有效,那么Curator会在后台持续进行删除操作,知道节点删除成功。

下面通过一个实际例子来看看如何在代码中使用这些API。

清单5-26 Curator删除节点API示例

package book.chapter;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;

//使用Curator删除节点
public class Del_Data_Sample{
      static String path = "/zk-book/c1";
      static CuratorFramework client = CuratorFrameworkFactory.builder().connectString("domain1.book.zookeeper:22181").sessionTimeoutMs(5000).retryPolicy(new ExponentialBackoffRetry(1000,3)).build();
    public static void main(String[] args) throws Exception{
        client.start();
        client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPaht(path,"init".getBytes());
         Stat stat = new Stat();
         client.getData().storingStatIn(stat).forPath(path);
          client.delete().deletingChildrenIfNeeded().withVersion(stat.getVersion()).forPath(path);
}
}

上面这个程序就是一个简单的节点删除实例。这里重点讲解guaranteed()这个方法。正如该接口的官方文档中所注明的,在ZooKeeper客户端使用过程中,可能会碰到这样的问题:客户端执行一个删除节点操作,但是由于一些网络原因,导致删除操作失败。对于这个异常,在有些场景中是致命的,如“Master选举”——在这个场景中,ZooKeeper客户端通常是通过节点的创建于删除来实现的。针对这个问题,Curator中引入了一种重试机制:如果我们调用了guaranteed()方法,那么当客户端碰到上面这些网络异常的时候,会记录下这次失败的删除操作,只要客户端会话有效,那么其就会在后台反复重试,知道节点删除成功。通过这样的措施,就可以保证节点删除操作一定会生效。

读取数据

下面来看看如何通过Curator接口来获取节点的数据内容。

清单5-27

CuratorFramework

  --public GetDataBuilder getData();

Statable<T>

  --public T storingStatIn(Stat stat);

Pathable<T>

  --public T forPath(String path) throws Exception;

以上就是一系列最常用的读取数据节点内容的API接口,下面通过一些场景来说明如何使用这些API。

读取一个节点的数据内容

client.getData().forPath(path);

注意,该接口调用后的返回值是byte[]。

读取一个节点的数据内容,同时获取到该节点的stat

client.getData().storingStatIn(stat).forPath(path);

Curator通过传入一个旧的stat变量的方式来存储服务端返回的最新的节点状态信息。

下面通过一个实际例子来看看如何在代码中使用这些API。

清单5-28 Curator读取数据API、实例

paclage bppl/cja[ter;
import org.apache.curator.framework.CuratorFramework;
import org.apche.curator.framewor.CuratorFrameworkFactory;
import org.apche.curator.retry.ExponentialBackofRetyr;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;

//使用Curatro获取数据内容
public class Get_Data_Sample{
     static String path = "/zk-book";
     static CuratorFramework client = CuratorFrameworkFactory.builder().connectString("domain1.book.zookeeper:2181").sessionTimeoutMs(5000).retryPolicy(new ExponentialBackoffRetry(1000,3)).build();
      public static main(String[] args) throws Exception{
           client.start();
           client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(path,"init".getBytes());
            Stat stat = new Stat();
            System.out.println(new String(client.getDate().storingStatIn().forPath(path)));
       }
}

更新数据

在Curator中,可以通过以下API来更新指定节点的数据。

清单5-29 Curator更新数据API

CuratorFramework

    --public SetDateBuilder setData();

Versionable<T>

   --public T withVersion(int version);

pathAndBytesable<T>

   --public T forpath(String path,byte[] data) throws Exception;

    pubic T forPath(String path) throws Exception;

以上就是一系列最常用的更新数据API,下面通过一些具体场景来说明如何使用这些API。

更新一个节点的数据内容

client.setData().forPath(path);

调用该接口后,会返回一个stat对象。

更新一个节点的数据内容,强制指定版本进行更新

client.setData().withVersion(version).forPath(path);

注意,withVersion接口就是用来实现CAS(Compare and Swap)的,version(版本信息)通常从一个旧的stat对象中获取到的。

下面通过一个实际例子来看看如何在代码中使用这些API。

清单5-30 Curator更新数据API实例

package book.chapter;
import org.apache.curator.framework.CuratorFramework;
improt org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curatro.retyr.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;

//使用Curator更新数据内容
public class Set_Data_Sample{
     static String path = "/zk-book";
     static CuratorFramework client = CuratorFrameworkFactory.builder().connectString("domain1.book.zookeeper:2181").sessionTimeoutMs(5000).retryPolicy(new ExponnentialBackoffRetry(1000,3)).build();
     public static void main(String[] args) throws Exception{
           client.start();
           client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(path,"init".getBytes());
           Stat stat = new Stat();
           client.getData().storingStatIn(stat).forPath(path);
           System.out.println("Success set node for:"+path+",new version:"+client.setData().withVersion(stat.getVersion()).forPath(path).getVersion());
            try{
                client.setData().withVersion(stat.getVersion()).forPath(path);
            }catch(Exception e){
                   System.out.printlin("Fail set node due to"+ e.getMessage());
            }
     }
}

上面的示例程序演示了如何使用Curator的API来进行ZooKeeper数据节点的内容更新。该程序前后进行了两次更新操作,第一次使用最新的stat变量进行更新操作。更新成功;第二次使用了过期的stat变量进行更新操作,抛出异常:KeeperErrorCode = BadVersion。


关于Curator的普通用法我们先说到这里,下面一节我们一起来研究Curator的异步接口,master选举,分布式锁以及分布式计数器。下面是更高级而且非常常用的内容。尤其是zookee的分布式锁,其应用程度比redis的分布式锁永盈更为广泛。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值