Zookeeper学习(转自知乎)

https://zhuanlan.zhihu.com/p/24996631

众所周知,zookeeper与分布式有着千丝万缕的联系。它虽然源自于hadoop,但目前zookeeper脱离hadoop的范畴开发分布式应用越来越普遍,想到这里,不禁为hadoop扼腕叹息。

那么Zookeeper究竟是什么呢?

从他的名字就可以看出来 ------ 动物园管理员,就相当于是把集群中的客户端当作许多小动物,管理员的作用就是维持他们的秩序,提供他们食物,当某小动物出现状况,如生病、脱毛等一系列现象,管理员自然会知道,并传达给其他动物这个消息,其他动物就不会再拉着这个小动物玩,会让它好好休息。

好吧,这个例子十分生硬。

简单来说,zookeeper = 通知机制 + 文件系统。

  • 通知机制

相当于上面那个例子,客户端注册监听它关心的目录节点,当目录节点发生变化,如数据改变、被删除、子目录节点增加删除时,zookeeper会通知客户端。 这时客户端就可以根据传过来的信息采取一系列的操作。

  • 文件系统

zookeeper维护一个如下图的文件结构

  1. 每个子目录项如 NameService 都被称作为 znode,有如下四种类型:
  • PERSISTENT - 持久化目录节点:客户端与zookeeper断开连接后,该节点依旧存在
  • PERSISTENT_SEQUENTIAL - 持久化顺序编号目录节点:客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号
  • EPHEMERAL - 临时目录节点:客户端与zookeeper断开连接后,该节点被删除
  • EPHEMERAL_SEQUENTIAL - 临时顺序编号目录节点:客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号

2. znode 可以有子节点目录 (临时节点除外),并且每个 znode 可以存储数据zookeeper 的客户端和服务器通信采用长连接方式,每个客户端和服务器通过心跳来保持连接,这个连接状态称为 session,如果 znode 是临时节点,这个 session 失效时,znode 也就删除了znode可以被监控,实现上述通知机制

我们能用Zookeeper做什么?

  1. 统一命名服务:说白了,zookeeper会帮我们的文件起名,起的名字还挺好听,还不会重复,便于识别跟记忆,是不是很棒
  2. 配置管理:简单点,改变一台机器的配置,其他机器也会跟着改变
  3. 集群管理:监听是否有机器退出和加入、动态选举Master(最小节点法,最大数据法)
  4. 队列管理

    1、同步队列,当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达,本文最后将实现此队列demo

    2、队列按照 FIFO 方式进行入队和出队操作。

  5. 实现分布式锁,流程如下

Zookeeper的基本概念

角色

1.领导者(Leader):进行投票的发起和决议,更新系统状态

2.学习者(Learner)

  • 跟随者(Follower):接受客户端请求并向客户端返回结果,在选主过程中参与投票
  • 观察者(Observer):接收客户端的连接,将写请求转发给leader节点。但Observer不参加投票,只同步leader状态。Observer的目的是为了扩展系统,提高读取速度

3.客户端(Client):请求发起方

session

zookeeper会为每个client分配一个session,类似于web服务器一样。针对session可以有保存一些关联数据

  • Global session 全局session,在每个server上都存在
  • local session 只在当前请求的server上存在,但只能进行读操作,要是要进行写操作,就得升级为全局session

Zookeeper的工作原理

  1. zookeeper集群上每个server数据一致,leader在集群启动时选举,如图
  2. 写操作时,请求发给某server,再由server转发给leader,leader给每个server发送投票消息,每个server把投票结果传给leader,要是有半数server同意此请求,leader就会commit到每个服务器执行写操作,流程如下:
  3. 写操作流程中,observer角色只负责转发请求,不参与投票,如图:
  4. 一个follower挂了,修复好之后会和leader通过一致性协议修复follower数据,达到每个server上数据最终一致
  5. 存储数据时,过一段时间,zookeeper就会把所有server的数据镜像写出,然后把每个server上的数据删除,保证了每个server的容量
  6. 在某一台follower写入了某数据的同时,读另一台follower刚刚写入的信息不一定成功,因为每台server数据同步会有少许间隔,所以说是最终一致性。不过session肯定是强一致性,通过修改数据后传回lastZxid来判断。若要实现强一致读,sync读两次实现实现,原理如下:
  7. Watcher的特性:只通知改变事一次性、触发后失效、session内有效

Zookeeper实现同步队列Demo

Demo现实一种同步的分步式队列,当定义的队列成员都聚齐时,这个队列才可用,否则一直等待所有成员到达。

实现思路

创建一个父目录 /queue,每个成员都监控(Watch)标志位目录/queue/start 是否存在,然后每个成员都加入这个队列,加入队列的方式就是创建 /queue/x(i)的临时目录节点,然后每个成员获取 /queue 目录的所有目录节点,也就是 x(i)。判断 i 的值是否已经是成员的个数,如果小于成员个数等待 /queue/start 的出现,如果已经相等就创建 /queue/start。

    public static void main(String[] args) throws Exception {
        //模拟app1通过zk1提交x1,app2通过zk2提交x2,app3通过zk3提交x3
        doAction(1);
        doAction(2);
        doAction(3);
    }


    //以此集群的3台机器上加入某成员
    public static void doAction(int client) throws Exception {
        String host1 = "zookeeperServer1:2181";
        String host2 = "zookeeperServer2:2181";
        String host3 = "zookeeperServer3:2181";
        ZooKeeper zk = null;
        switch (client) {
            case 1:
                zk = connection(host1);
                initQueue(zk);
                joinQueue(zk, 1);
                break;
            case 2:
                zk = connection(host2);
                initQueue(zk);
                joinQueue(zk, 2);
                break;
            case 3:
                zk = connection(host3);
                initQueue(zk);
                joinQueue(zk, 3);
                break;
        }
    }

    // 创建一个与服务器的连接
    public static ZooKeeper connection(String host) throws IOException {
        ZooKeeper zk = new ZooKeeper(host, 60000, new Watcher() {
            // 监控所有被触发的事件
            public void process(WatchedEvent event) {
                if (event.getType() == Event.EventType.NodeCreated && event.getPath().equals("/queue/start")) {
                    System.out.println("Queue has Completed.Finish testing!!!");
                }
            }
        });
        return zk;
    }

    //初始化队列
    public static void initQueue(ZooKeeper zk) throws KeeperException, InterruptedException {

        System.out.println("WATCH => /queue/start");

        //当这个znode节点被改变时,将会触发当前Watcher
        zk.exists("/queue/start", true);

        //如果/queue目录为空,创建此节点
        if (zk.exists("/queue", false) == null) {
            System.out.println("create /queue task-queue");
            zk.create("/queue", "task-queue".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        } else {
            System.out.println("/queue is exist!");
        }
    }

    //成员加入队列
    public static void joinQueue(ZooKeeper zk, int x) throws KeeperException, InterruptedException {
        System.out.println("create /queue/x" + x + " x" + x);
        zk.create("/queue/x" + x, ("x" + x).getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        isCompleted(zk);
    }

    //判断队列是否已满
    public static void isCompleted(ZooKeeper zk) throws KeeperException, InterruptedException {
        //规定队列大小
        int size = 3;
        //查询成员数
        int length = zk.getChildren("/queue", true).size();
        System.out.println("Queue Complete:" + length + "/" + size);
        if (length >= size) {
            System.out.println("create /queue/start start");
            zk.create("/queue/start", "start".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }
    }

控制台打印:
在linux上查看queue节点:

因为我们创建的任务节点是临时节点,而start节点是持久节点,所以最终我们查看queue节点时,仅有start节点存在,临时节点已经被zookeeper自动删除。

posted @ 2017-07-31 11:34  Al J  阅读(950)  评论(0编辑  收藏  举报