美文网首页
ZooKeeper高级特性

ZooKeeper高级特性

作者: 伊凡的一天 | 来源:发表于2019-09-29 14:27 被阅读0次

    1. ZooKeeper应用场景

    ZooKeeper的应用场景主要包括:分布式协调,分布式锁,分布式元数据存储以及HA高可用。

    分布式协调

    通过ZooKeeper watch机制进行分布式服务间的通信。例如A系统发送请求到mq,然后B系统消息消费之后处理了。那 A 系统如何知道 B 系统的处理结果?使用Zookeeper就可以实现分布式系统之间的协调工作。A 系统发送请求之后可以在 ZooKeeper 上对某个节点的值注册个监听器,一旦 B 系统处理完了就修改 ZooKeeper那个节点的值,A 系统立马就可以收到通知,完美解决。

    分布式锁

    通过Zookeeper的原子性创建node或者临时序号节点的特性可以用于实现分布式锁。具体实现请参考文章第2小节:ZooKeeper分布式锁。

    元数据存储

    Zookeeper 可以用作分布式系统的配置信息的管理,比如 kafka、storm 等等很多分布式系统都会选用 Zookeeper来做一些元数据、配置信息的管理。

    HA高可用

    可以通过ZooKeeper临时节点watch功能来实现主备高可用。如下图所示:


    HA高可用.PNG

    2. ZooKeeper分布式锁

    通过临时序号节点实现分布式锁:

    public class ZooKeeperDistributedLock implements Watcher {
    
        private ZooKeeper zk;
        private String locksRoot = "/locks";
        private String productId;
        private String waitNode;
        private String lockNode;
        private CountDownLatch latch;
        private CountDownLatch connectedLatch = new CountDownLatch(1);
        private int sessionTimeout = 30000;
    
        public ZooKeeperDistributedLock(String productId) {
            this.productId = productId;
            try {
                String address = "192.168.31.187:2181,192.168.31.19:2181,192.168.31.227:2181";
                zk = new ZooKeeper(address, sessionTimeout, this);
                connectedLatch.await();
            } catch (IOException e) {
                throw new LockException(e);
            } catch (KeeperException e) {
                throw new LockException(e);
            } catch (InterruptedException e) {
                throw new LockException(e);
            }
        }
    
        public void process(WatchedEvent event) {
            if (event.getState() == KeeperState.SyncConnected) {
                connectedLatch.countDown();
                return;
            }
    
            if (this.latch != null) {
                this.latch.countDown();
            }
        }
    
        public void acquireDistributedLock() {
            try {
                if (this.tryLock()) {
                    return;
                } else {
                    waitForLock(waitNode, sessionTimeout);
                }
            } catch (KeeperException e) {
                throw new LockException(e);
            } catch (InterruptedException e) {
                throw new LockException(e);
            }
        }
    
        public boolean tryLock() {
            try {
                 // 传入进去的locksRoot + “/” + productId
                // 假设productId代表了一个商品id,比如说1
                // locksRoot = locks
                // /locks/10000000000,/locks/10000000001,/locks/10000000002
                lockNode = zk.create(locksRoot + "/" + productId, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
    
                // 看看刚创建的节点是不是最小的节点
                 // locks:10000000000,10000000001,10000000002
                List<String> locks = zk.getChildren(locksRoot, false);
                Collections.sort(locks);
    
                if(lockNode.equals(locksRoot+"/"+ locks.get(0))){
                    //如果是最小的节点,则表示取得锁
                    return true;
                }
    
                //如果不是最小的节点,找到比自己小1的节点
          int previousLockIndex = -1;
                for(int i = 0; i < locks.size(); i++) {
            if(lockNode.equals(locksRoot + “/” + locks.get(i))) {
                         previousLockIndex = i - 1;
                break;
            }
           }
    
           this.waitNode = locks.get(previousLockIndex);
            } catch (KeeperException e) {
                throw new LockException(e);
            } catch (InterruptedException e) {
                throw new LockException(e);
            }
            return false;
        }
    
        private boolean waitForLock(String waitNode, long waitTime) throws InterruptedException, KeeperException {
            Stat stat = zk.exists(locksRoot + "/" + waitNode, true);
            if (stat != null) {
                this.latch = new CountDownLatch(1);
                this.latch.await(waitTime, TimeUnit.MILLISECONDS);
                this.latch = null;
            }
            return true;
        }
    
        public void unlock() {
            try {
                // 删除/locks/10000000000节点
                // 删除/locks/10000000001节点
                System.out.println("unlock " + lockNode);
                zk.delete(lockNode, -1);
                lockNode = null;
                zk.close();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (KeeperException e) {
                e.printStackTrace();
            }
        }
    
        public class LockException extends RuntimeException {
            private static final long serialVersionUID = 1L;
    
            public LockException(String e) {
                super(e);
            }
    
            public LockException(Exception e) {
                super(e);
            }
        }
    }
    

    redis 分布式锁和 zk 分布式锁的对比:

    • redis 分布式锁,其实需要自己不断去尝试获取锁,比较消耗性能。另外,如果redis 获取锁的那个客户端挂了,那么只能等待超时时间之后才能释放锁。
    • zk 分布式锁,获取不到锁,注册个监听器即可,不需要不断主动尝试获取锁,性能开销较小。而 zk 的话,由于创建的是临时znode,只要客户端挂了,znode 就没了,此时就自动释放锁。

    3. ZooKeeper Watcher机制

    ZooKeeper的Watcher机制主要包括客户端线程、客户端WatchManager和ZooKeeper服务器三部分。在具体工作流程上,简单地讲,客户端在向 ZooKeeper 服务器注册 Watcher 的同时,会将 Watcher 对象存储在客户端的 WatchManager 中。当 ZooKeeper 服务器端触发 Watcher 事件后,会向客户端发送通知,客户端线程从 WatchManager 中取出对应的 Watcher 对象来执行回调逻辑。

    更多关于watch机制的原理,请参考:https://www.jianshu.com/p/4c071e963f18

    4. etcd和zookeeper对比

    etcd和zookeeper的功能非常相似,都提供了线性一致性的分布式存储,watch机制,原子性读写操作等操作。使用etcd或zookeeper,我们可以轻松的实现以下功能:

    • 分布式锁
    • 分布式元数据存储
    • 服务发现
    • 发布订阅

    两者的相似点和区别:

    1. ZooKeeper会将所有znode的数据缓存进内存,每一个znode的大小限制在1M,因此适合用于分布式元数据存储,而非分布式文件存储系统。另外,ZooKeeper适合读多写少的场景(写入性能低,为保证一致性,每次需要n/2+1的写入完成才算完成)。
    2. etcd和ZooKeeper一样,都用于解决分布式系统的协调和元数据的存储,所以它们都不是一个存储组件,或者说都不是一个分布式数据库。ZooKeeper的存储结构是树状的,而etcd则是key/value类型的。
      3. ZooKeeper采用ZAB协议进行一致性保证,而etcd采用更易实现的raft协议。ZAB选举用的是zxid和serverId来进行投票, 而raft则是根据term和index来进行投票。另外,ZAB协议一轮选举会进行多次投票,一轮选举一定能够产生一个Leader。而RAFT协议一轮选举则只进行一次投票,如果出现平票(没有Candidate收到大多数投票),则自动开始下一轮选举。
      4. RAFT协议的心跳是从leader到follower,而zab协议则相反。ZAB协议中Leader如果在timeout时间内没有收到超过一半Follower发送的心跳,就会自动放弃Leader身份,并停止给Follower发送心跳。Follower在一段时间内没有收到Leader的心跳后,则会开始一轮新的Leader选举。
      5. ZooKeeper的watch机制是一次性的,watch事件触发后就不会再触发第二次。另外,ZooKeeper的wach只能watch子节点的创建和删除,不能watch子节点值的变化,并且无法watch孙节点。而Etcd的watch channel是可以重复利用的,并且可以watch到所以子孙节点的变化。

    更多ZooKeeper和etcd的对比,请参考:

    参考文章

    相关文章

      网友评论

          本文标题:ZooKeeper高级特性

          本文链接:https://www.haomeiwen.com/subject/rgwsyctx.html