ZooKeeper高级特性

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的对比,请参考:

参考文章

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • Zookeeper客户端Curator高级特性 提醒:首先你必须添加curator-recipes依赖,下文仅仅对...
    史路比阅读 2,957评论 0 5
  • 自己没有尝试过Zookeeper(zk)的直接应用,但是在很多大数据栈/分布式系统当中,zk都充当着重要角色。如H...
    chenfh5阅读 266评论 0 1
  • 什么 1 zookeeper 与分布式系统 zookeeper 是一个中间件,为分布式系统提供协调(Coordin...
    憩在河岸上的鱼丶阅读 1,224评论 1 3
  • ZooKeeper-Dubbo 系统架构演变 传统单体架构 => 传统单体架构扩展 => 单体架构解耦 => 异...
    方穹轩阅读 701评论 0 5
  • Zookeeper 和 Etcd 都是非常优秀的分布式协调系统,zookeeper 起源于 Hadoop 生态系统...
    HelloWide阅读 422评论 0 5