• ZOOKEEPER解惑


    现在网上关于ZooKeeper的文章很多,有介绍Leader选举算法的,有介绍ZooKeeper Server内部原理的,还有介绍ZooKeeper Client的。本文不打算再写类似的内容,而专注与解答读者对ZooKeeper的相关疑问。

    ZOOKEEPER在客户端究竟做了什么事情

    使用过ZooKeeper的读者都知道,初始化客户端的代码如下:

    1
    2
    3
    System.out.println("Starting ZK:");
    zk = new ZooKeeper(address, 3000, this);
    System.out.println("Finished starting ZK: " + zk);

    完成客户段的初始化之后,就可以对ZooKeeper进行相应的操作了:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    if (zk != null) {
        try {
            Stat s = zk.exists(root, false);
            if (s == null) {
                zk.create(root, new byte[0], Ids.OPEN_ACL_UNSAFE,
                        CreateMode.PERSISTENT);
            }
        } catch (KeeperException e) {
            System.out
                    .println("Keeper exception when instantiating queue: "
                            + e.toString());
        } catch (InterruptedException e) {
            System.out.println("Interrupted exception");
        }
    }

    虽然上面的代码看起来简单明了,但是ZooKeeper的客户端在后台默默做了许多事情:

      1 与ZooKeeper服务端进行通信,包括:连接,发送消息,接受消息。

      2 发送心跳信息,保持与ZooKeeper服务端的有效连接与Session的有效性。

      3 错误处理,如果客户端当前连接的ZooKeeper服务端失效,自动切换到另一台有效的ZooKeeper服务端。

      4 管理Watcher,处理异常调用和Watcher。

    WATCHER的事件通知机制是如何实现的

    看过Google的分布式锁机制Chubby论文会发现,ZooKeeper中多了一个事件订阅机制:Watcher。那么Watcher内部究竟是如何实现的呢?

    其实,在ZooKeeper客户端中,有一个成员变量(ZKWatchManager)专门负责管理所有的Watcher,当用户使用如下代码时:

    1
    List<String> list = zk.getChildren(path, watcher);

    ZooKeeper会将这个Watcher存储在ZKWatchManager中,同时通知ZooKeeper服务器记录该Client对应的Session中的Path下注册的事件类型。当ZooKeeper服务器发生了指定的事件后,ZooKeeper服务器将通知ZooKeeper客户端,ZooKeeper客户端再从ZKWatchManager中找到对应的回调函数,并予以执行。

    整个过程中,客户端存储事件的信息和Watcher的执行逻辑,服务端只存储事件的信息。

    如何用好ZOOKEEPER客户端

    每实例化一个ZooKeeper客户端,就开启了一个Session。ZooKeeper客户端是线程安全的,也可以认为它实现了连接池。

    因此,每一个应用只需要实例化一个ZooKeeper客户端即可,同一个ZooKeeper客户端实例可以在不同的线程中使用。

    除非你想同一个应用中开启多个Session,使用不同的Watcher,在这种情况下,才需要实例化多个ZooKeeper客户端。

    ZOOKEEPER是否对ZNODE有大小限制

    如果你仔细看过ZooKeeper的文档,会发现文档中对ZNode的大小做了限制,最大不能超过1M。

    这个1M的大小限制在ZooKeeper的客户端和服务端都有限制:

    客户端:

    1
    2
    3
    4
    5
    6
    packetLen = Integer.getInteger("jute.maxbuffer", 4096 * 1024);
     
    int len = incomingBuffer.getInt();
    if (len < 0 || len >= packetLen) {
        throw new IOException("Packet len" + len + " is out of range!");
    }

    服务端:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    static public final int maxBuffer = determineMaxBuffer();
    private static int determineMaxBuffer() {
        String maxBufferString = System.getProperty("jute.maxbuffer");
        try {
            return Integer.parseInt(maxBufferString);
        } catch(Exception e) {
            return 0xfffff;
        }
         
    }
     
    if (len < 0 || len > maxBuffer) {
        throw new IOException("Unreasonable length = " + len);
    }

    可以看出,ZooKeeper确实对数据的大小有限制,默认就是1M,如果希望传输超过1M的数据,可以修改环境变量“jute.maxbuffer”即可。

    为什么要限制ZOOKEEPER中ZNODE的大小

    ZooKeeper是一套高吞吐量的系统,为了提高系统的读取速度,ZooKeeper不允许从文件中读取需要的数据,而是直接从内存中查找。

    还句话说,ZooKeeper集群中每一台服务器都包含全量的数据,并且这些数据都会加载到内存中。同时ZNode的数据并支持Append操作,全部都是Replace。

    所以从上面分析可以看出,如果ZNode的过大,那么读写某一个ZNode将造成不确定的延时;同时ZNode过大,将过快地耗尽ZooKeeper服务器的内存。这也是为什么ZooKeeper不适合存储大量的数据的原因。

    如何提升ZOOKEEPER集群的性能

    我们说性能,可以从两个方面去考虑:写入的性能与读取的性能。

    由于ZooKeeper的写入首先需要通过Leader,然后这个写入的消息需要传播到半数以上的Fellower通过才能完成整个写入。所以整个集群写入的性能无法通过增加服务器的数量达到目的,相反,整个集群中Fellower数量越多,整个集群写入的性能越差。

    ZooKeeper集群中的每一台服务器都可以提供数据的读取服务,所以整个集群中服务器的数量越多,读取的性能就越好。但是Fellower增加又会降低整个集群的写入性能。为了避免这个问题,可以将ZooKeeper集群中部分服务器指定为Observer。

  • 相关阅读:
    CF1082E Increasing Frequency
    CF1083B The Fair Nut and String
    week2
    CF1082G Petya and Graph
    后缀数组学习笔记
    单纯形法
    验证rbd的缓存是否开启
    如何删除一台OSD主机
    Mon失效处理方法
    查询osd上的pg数
  • 原文地址:https://www.cnblogs.com/405845829qq/p/4564910.html
Copyright © 2020-2023  润新知