zookeeper学习笔记-04


五 、开源客户端(ZkClient + Curator)

1.ZkClient
ZkClient是由Datameer的工程师开发的开源客户端,对Zookeeper的原生API进行了包装,实现了超时重连、Watcher反复注册等功能。

想要使用ZkClient必须相应的jar包,由于我的工程使用maven构建的,故在此首先要引入maven依赖:

 <dependency>
      <groupId>com.github.sgroschupf</groupId>
      <artifactId>zkclient</artifactId>
      <version>0.1</version>
    </dependency>

a.连接

ZkClient(String zkServers, int sessionTimeout, int connectionTimeout, ZkSerializer zkSerializer);
//
zkServers:    连接字符串
sessionTimeOut:    会话超时时间
connectionTimeout:    连接超时时间
ZkSerializer:    自定义序列化器,使得zookeeper将设置节点内容为java对象。(原生仅支持byte[])

b.创建节点
ZkClient提供了较多的创建节点的API接口,且很多都跟原生API类似,但是有一个参数createParent比较有趣

void createPersistent(String path, boolean createParents);
//createParents: 是否递归创建父节点

c.删除节点
zookeeper原生API只允许删除叶子节点, ZkClient提供了一个新方法:deleteRecursive(),这个方法帮助我们逐层遍历删除叶子节点。

boolean deleteRecursive(String path)

d.读取数据(getChildren)
ZkClient只对外提供一个getChildren的API,而且该API没有了Watcher的功能。ZkClient提供了subscribeChildChanges方法来监听子节点的变更事件。

List<String> subscribeChildChanges(String path, IZkChildListener listener) ;

IZkChildListener主要可以监听到如下一下四种事件:

* path节点的创建(即可以监听一个不存在的节点,待该节点创建时,触发此事件)
* path节点的删除
* path新增子节点
* path删除子节点

需实现IZkChildListener并重写handleChildChange方法,注意此方法跟原生的Watcher不同,此方法是注册一次一直生效。

void handleChildChange(String parentPath, List<String> currentChilds);

e.读取数据(readData)

T readData(String path);
T readData(String path, boolean returnNullIfPathNotExists); //如果节点不存在返回null
T readData(String path, Stat stat);

ZkClient通过subscribeDataChanges来监听节点内容的变化:

subscribeDataChanges(String path, IZkDataListener listener)

需实现IZkDataListener 并重写handleDataChange和handleDataDelete方法
handleDataChange监听到:

* 该节点初次创建(即可以创建一个不存在的节点)
* 节点数据的变化

handleDataDelete可以监听到:节点的删除。

f.更新数据(writeData)

void writeData(final String path, Object datat, final int expectedVersion)

g.检测节点是否存在(exists)

boolean exists(final String path)

2.Curator
Curator是Netflix开源的一套ZooKeeper客户端框架.它解决了很多ZooKeeper客户端非常底层的操作如:连接重连,反复注册和NodeExistsExceptiion异常等,它目前已经成为了Apache的顶级项目。使用它,需要引入它的maven依赖:

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

a.创建会话
通过CuratorFrameworkFactory.newInstance(),创建CuratorFramework,最终调用CuratorFrameworkFactory.start()方法创建连接。

CuratorFramework newClient(String connectString, int sessionTimeoutMs, int connectionTimeoutMs, RetryPolicy retryPolicy)

其中,RetryPolicy为重试策略主要包含以下四种重试策略:

* ExponentialBackoffRetry :基于backoff的重连策略 
ExponentialBackoffRetry(int baseSleepTimeMs, int maxRetries, int maxSleepMs)  

baseSleepTimeMs:初始sleep时间,
maxRetries: 最大重试次数
maxSleepMs:最大sleep时间

当前sleep时间 = baseSleepTimeMs * Math.max(1,random.nextInt( 1 << (retryCount + 1)));
所以sleep时间会越来越长,当这个时间大于maxSleepMs时,则取maxSleepMs
* RetryNTimes(int n, int sleepMsBetweenRetries): 重连N次策略
* RetryOneTime(int sleepMsBetweenRetry):重连一次
* RetryUntilElapsed(int maxElapsedTimeMs, int sleepMsBetweenRetries):以sleepMsBetweenRetries的间隔重连,直到超过maxElapsedTimeMs的时间设置
* RetryForever:永远重试策略

通过两种方式创建会话:

//1.工厂类创建
CuratorFramework client = CuratorFrameworkFactory.newClient(IZooKeeperConfig.CONNECT_STRING,
        5000,
        5000,
        retryPolicy);
client.start();

//2.Fluent风格
client = CuratorFrameworkFactory.builder()
    .connectString(IZooKeeperConfig.CONNECT_STRING)
    .sessionTimeoutMs(5000)
    .connectionTimeoutMs(5000)
    .retryPolicy(retryPolicy)
    .namespace("base") //创建base节点
.build();
client.start();

同时支持namespace属性来对各个不同的业务进行隔离,若使用namespace属性,则再在该客户端上的操作节点都是基于namespace为相对目录进行的。

b.创建节点
使用create().forPath(path)创建一个空节点,额外属性的使用creatingParentsIfNeeded递归创建父节点,withMode指定模式;

client.create() 
    .creatingParentsIfNeeded() 
    .withMode(CreateMode.PERSISTENT)
    .forPath("/book/ch5","haha".getBytes());

c.删除节点
使用delete().forPath(path)删除一个节点,额外属性的使用deletingChildrenIfNeeded递归删除子节点。

client.delete()
    .deletingChildrenIfNeeded()
    .withVersion(-1)
    .forPath("/book/ch5");

d.读取数据

 client.getData()
        .storingStatIn(new Stat() )
        .forPath("/book/ch5");

e.设置数据

client.setData()
    .withVersion(stat.getVersion())
    .forPath("/book/ch5", "adadfafa".getBytes());

f.异步接口
之前所介绍的api都是同步接口,Curator引入了BackgroundCallback接口,来处理异步调用服务端返回的的结果信息。BackgroundCallback只有一个processResult方法:

void processResult(CuratorFramework curatorFramework, CuratorEvent curatorEvent);

CuratorEvent的事件类型主要有GET_DATA,SET_DATA,CHILDREN,SYNC,GET_ACL,WATCHED,CLOSING类型。

响应码:

   0:    ok
  -4:    connectionloss
-110:    node not exists
-112:    sessionexpired

Curator提供了如下API来支持异步操作:

public T inBackground(BackgroundCallback callback);
public T inBackground(BackgroundCallback callback, Object context);
public T inBackground(BackgroundCallback callback, Executor executor);
public T inBackground(BackgroundCallback callback, Object context, Executor executor);

重点关注下executor,zookeeper的所有异步操作都是由EventThread这个线程来处理的,当碰上一个复杂的处理单元,就会消耗过长的处理时间, 就会影响到后面的时间的处理。所以在此处允许用户传递一个Executor实例,可以把一些复杂的处理时间专门放到一个线程池中。

client.setData().withVersion(stat.getVersion())
    .inBackground(new BackgroundCallback(){
        @Override
        public void processResult(CuratorFramework client, CuratorEvent event) throws Exception {
        }
    }, Executors.newFixedThreadPool(2)) //使用异步方式获取数据
    .forPath("/book/ch5", "adadfafa".getBytes());

g.NodeCache和NodeCacheListener
1).使用样例

NodeCache cache = new NodeCache(client, "/chap05", false);
cache.start(true);
cache.getListenable().addListener(()->{
    System.out.println("变化了::"+cache.getCurrentData().getData());
});

2).NodeCache
想要使用必须先调用start方法,不用之后调用close方法.

NodeCache(CuratorFramework client, String path, boolean dataIsCompressed);

void start(boolean buildInitial); //若为true ,nodecache第一次启动时就从zookeeper上读取对应节点数据,并保存到cache中;

3).NodeCacheListener

cache.getListenable().addListener(XXNodeCacheListener);//使用此种方式,注册监听

需要实现NodeCacheListener 并重写nodeChanged()方法,

cache.getCurrentData().getData();//获取变更后的数据

可以监听到事件:

* 节点创建(即可以监听一个不存在的节点)
* 节点数据变化    

h.PathChildrenCache和PathChildrenCacheListener
1).使用样例

PathChildrenCache childrenCache = new PathChildrenCache(client, "/chap05", true);
childrenCache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
childrenCache.getListenable().addListener((CuratorFramework cc, PathChildrenCacheEvent event) -> {
            switch (event.getType()) {
                case CHILD_ADDED:
                    System.out.println("新增子节点:"+event.getData().getPath());
                    break;

                case CHILD_REMOVED:
                    System.out.println("删除子节点:"+event.getData().getPath());
                    break;

                case CHILD_UPDATED:
                    System.out.println("更新子节点:"+event.getData().getPath());
                    break;

                default:
                    System.out.println(event.getType().toString()+":::::"+event.getData().getPath());
            }
        });

2).PathChildrenCache
想要使用必须先调用start方法,不用之后调用close方法.
start有两个, 其中一个可以传入StartMode,用来为初始的cache设置暖场方式(warm):

PathChildrenCache(CuratorFramework client, String path, boolean cacheData);

void start(StartMode mode);

NORMAL: 初始时为空。
BUILD_INITIAL_CACHE: 在这个方法返回之前调用rebuild()。
POST_INITIALIZED_EVENT: 当Cache初始化数据后发送一个

3).PathChildrenCacheListener

cache.getListenable().addListener(XXPathChildrenCacheListener);//使用此种方式,注册监听

实现PathChildrenCacheListener重写childEvent方法:

void childEvent(CuratorFramework client, PathChildrenCacheEvent event)

可以监听到:

* 子节点新增、删除
* 子节点数据变更(不监听子节点添加or删除孙子节点)

3.Curator的典型应用场景

a.master选举-LeaderSelector

b.分布式锁–InterProcessMutex

void acquire(); //获得锁
void release();//释放锁

c.分布式计数器–DistributedAtomicInteger

d.分布式Barrier —DistributedBarrier

e.ZKPaths 和 EnsurePath

猜你喜欢

转载自blog.csdn.net/it_freshman/article/details/79262217