zoukankan      html  css  js  c++  java
  • 分布式服务框架 Zookeeper -- 管理分布式环境中的数据

        Zookeeper 分布式服务框架是 Apache Hadoop 的一个子项目,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理、Leader 选举、队列管理等。本文将从使用者角度详细介绍 Zookeeper 的安装和配置文件中各个配置项的意义,例举分析 Zookeeper 的典型的应用场景,用 Java 实现它们并给出示例代码。

    一、安装和配置详解

        本文介绍的 Zookeeper 是以 3.4.5 这个稳定版本为基础,最新的版本可以通过官网 http://zookeeper.apache.org/来获取,Zookeeper 的安装非常简单,下面将从单机模式、伪集群模式集群模式个方面介绍 Zookeeper Linux下的安装和配置。

    1、单机模式

        单机安装非常简单,只要获取到 Zookeeper 的压缩包并解压到某个目录如:/home/fenglibin/soft/zookeeper-3.4.5 下,Zookeeper 的启动脚本在 bin 目录下,配置文件在安装目录的conf目录下。

    1.1、配置文件介绍

        在我们启动Zookeeper之前,首先我们需要编写Zookeeper的配置文件conf/zoo.cfg,Zookeeper启动的时候会从该文件中读取属性,如果不存在就会报错误,这个文件不用我们重头开始写,只需要将conf目录下面的zoo_sample.cfg拷贝一份并重命名为zoo.cfg,然后修改其中的属性即可。下面详细介绍一下,这个配置文件中各个配置项的意义。

    这个时间是作为 Zookeeper 服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个 tickTime 时间就会发送一个心跳

    tickTime=2000

    这个配置项是用来配置 Zookeeper 接受客户端(这里所说的客户端不是用户连接 Zookeeper 服务器的客户端,而是 Zookeeper 服务器集群中连接到 Leader 的 Follower 服务器)初始化连接时最长能忍受多少个心跳时间间隔数。当已经超过 10 个心跳的时间(也就是 tickTime)长度后 Zookeeper 服务器还没有收到客户端的返回信息,那么表明这个客户端连接失败。总的时间长度就是 initLimit * tickTime = 10*2000=20 秒

    initLimit=10

    这个配置项标识 Leader 与 Follower 之间发送消息,请求和应答时间长度,最长不能超过多少个 tickTime 的时间长度,总的时间长度就是 syncLimit * tickTime =  5*2000=10 秒

    syncLimit=5

    是 Zookeeper 保存数据快照的目录,默认情况下,Zookeeper 将写数据的日志文件也保存在这个目录里。注:此目录不要是临时目录/tmp,因为大部份的Linux系统会在重启后清除该目录的内容,造成数据丢失

    dataDir=/home/fenglibin/soft/zookeeper-3.4.5/data

    这个端口就是客户端连接 Zookeeper 服务器的端口,Zookeeper 会监听这个端口,接受客户端的访问请求。

    clientPort=2181

    # Be sure to read the maintenance section of the  administrator guide before turning on autopurge.

    # http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance

    # 这是个3.4.0中才引入的新特性,当启用该配置时,ZooKeeper将执行保留最新快照以及自动执行清除dataDir以及dataLogDir下面的旧的事务日志,只保留指定数量的最新事务日志数据,默认值为3,且最小值也必须为3。

    #autopurge.snapRetainCount=3

    # Purge task interval in hours

    自动清除的时间间隔,以小时为单,如果设置为0,将不会执行该任务。默认值为0。

    #autopurge.purgeInterval=1

        所有的配置,可参看这里:http://zookeeper.apache.org/doc/trunk/zookeeperAdmin.html#sc_advancedConfiguration


    1.2、Bin目录介绍及Zookeeper的启动

        现在介绍bin,在该目录下有四个shell文件:

    文件名

    说明

    zkCleanup.sh

    用于清除旧的事务日志及快照

    zkEnv.sh or zkEnv.bat

    调用于运行时的环境变量,如classpath的设置,堆的大小设置等

    zkServer.sh or zkServer.bat

    用于执行Zookeeper的启动、停止及状态查看等操作

    zkCli.sh or zkCli.bat

    Zookeeper的shell客户端,如果是连接本机启动为默认端口2181的的Zookeeper,直接打入zkCli.sh命令,不需要任何参数

        常用的肯定是zkServer.shzkCli.sh两个了,以下为使用介绍。

    1.2.1、zkServer.sh的使用

    #启动Zookeeper

    ./bin/zkServer.sh start

     

    停止Zookeeper

    ./bin/zkServer.sh stop

    #查看Zookeeper的状态

    ./bin/zkServer.sh status

    zkServer.sh还有其它几个参数:start-foreground、restart、upgrade、print-cmd,分别用于在前台启动、重启、更新、操作。

    1.2.2、zkCli.sh的使用

    连接本机的Zookeeper

    ./bin/zkCli.sh

    连接非本机的Zookeeper

    ./bin/zkCli.sh -server nginx2:2181

    查看服务端的启动状态

     

    表示服务端启动成功

    1.2.3、命令行验证

        请查看此文档中的示例:http://www.ibm.com/developerworks/library/bd-zookeeper/#N100BD

     2、集群模式

        Zookeeper 不仅可以单机提供服务,同时也支持多机组成集群来提供服务。实际上 Zookeeper 还支持另外一种伪集群的方式,也就是可以在一台物理机上运行多个 Zookeeper 实例,下面将介绍集群模式的安装和配置。

        Zookeeper 的集群模式的安装和配置也不是很复杂,所要做的就是增加几个配置项。集群模式除了上面的单机的配置项外,还要增加下面几个配置项:

    server.1=nginx1:2888:3888

    server.2=nginx2:2888:3888

        server.A=B:C:D:其中 A 是一个数字,表示这个是第几号服务器;B 是这个服务器的 ip 地址;C 表示的是这个服务器与集群中的 Leader 服务器交换信息的端口;D 表示的是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的 Leader,而这个端口就是用来执行选举时服务器相互通信的端口。如果是伪集群的配置方式,由于 B 都是一样,所以不同的 Zookeeper 实例通信端口号不能一样,所以要给它们分配不同的端口号。

        除了修改 zoo.cfg 配置文件,集群模式下还要配置一个文件 myid,这个文件在 dataDir 目录下,这个文件里面就有一个数据就是 A 的值,用来表示当前主机,如在server.1上面该值为1,在server.2上面,该值就是2

    二、控制集群中同时只有一台服务器“说话”的示例

        场景一:有些时候需要控制集群中的服务器做缓存预热的时候,只需要其中一台服务器去做这个事情就可以了,因为缓存是类似Memcached的中央集群Cache,只需要有一台将数据成功加载了,那么集群中的其它服务器都可以访问,多台同时加台可能会造成数据冲突以及资源的浪费。

        场景二:在共享存储的情况下,就是集群中的所有服务器都访问的相同的挂载存储,而此时应用需要执行JAR包动态加载,即有新的JAR包上传上来后各个应用服务器就立即加载,但是在笔者的场景下,JAR包的上传是单独的第三方应用去完成的,并且上传过去的JAR的名称不是以.jar结尾的,在各应用加载之前需要将其改名为.jar文件,如果不控制应用去改名,那么就会出现冲突,或者存在多个拷贝的情况下,虽然说不会存在问题,但是毕竟让人不舒服。

        上面的两种场景,当然还可以其它的方式解决,如类似于Lucene的文件锁,或者使用Memcached锁,不一定非得使用ZooKeeper,不过在本例中是以ZooKeeper来解决问题的。

        本例使用的是控制集群中的服务器同一时刻只会有一台“说话”,此时集群中的其它服务器是不能够“说话”的,以达到控制集群中的服务器同一时刻只会有一台在做此“说话”的任务,本例适合于集群中伪分布应用的布署的情况,以及全集群的布署的情况,并可在与ZooKeeper连接Session过期后,采用自动重连,下面是运行截图:

       

       源码:   

    package org.apache.zookeeper.test.copy;
    
    import java.io.File;
    import java.io.IOException;
    import java.net.InetAddress;
    import java.net.UnknownHostException;
    import java.util.List;
    
    import org.apache.commons.io.FileUtils;
    import org.apache.zookeeper.CreateMode;
    import org.apache.zookeeper.KeeperException;
    import org.apache.zookeeper.KeeperException.NodeExistsException;
    import org.apache.zookeeper.KeeperException.SessionExpiredException;
    import org.apache.zookeeper.WatchedEvent;
    import org.apache.zookeeper.Watcher;
    import org.apache.zookeeper.ZooDefs.Ids;
    import org.apache.zookeeper.ZooKeeper;
    import org.apache.zookeeper.data.Stat;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    /**
     * 使用Zookeeper来控制集群中的服务器,同一时刻只会有一台做指定的事情,名称多台同时做会产生冲突的情况。<rb> 这个示例演示的是集群中的服务器,同一时刻只会有一台说话,当这台服务服务器说话时,其它的服务器不可以说话。<br>
     * 如果在操作的过程中,连接Zookeeper过期了,会自动进行重连;并确保其在同一个服务器上面布署多个示例也可以正常运行。<br>
     * 运行该示例:导入Zookeeper下面的Jar包,并且其依赖commons-io,其它就不需要了
     * 
     * @author fenglibin 2014-6-14 下午10:21:15
     */
    public class SpeakOneByOneInCluster implements Watcher {
    
        // 连接Zookeeper Server的客户端连接
        static ZooKeeper            zk       = null;
        // 锁
        static Object               LOCK     = new Object();
        // 用户操作的根节点,与“/”是不同的,用户可以控制
        String                      root;
        // 当前节点的名称,由服务器的名称加随机数据组成,之所以使用这种方式,那是考虑到同一台服务器上可能会布署多个应用示例,而不一定是每个应用示例布署到集群的不同服务器上
        private static String       nodeName = "";
        // 节点锁,只有能够创建该节点的应用示例,才可以做指定的事情,如本例中的说话
        private static String       LOCK_NODE;
        // 用于表示当前应用是否拿到了节点锁,然后根据是否拿到节点锁,才可以做指定的事情,如本例中的说话
        static boolean              getLock;
        String                      address;
        // 日志文件
        File                        logFile  = new File("D:/test/logFile.txt");
        private static final Logger LOG;
        static {
            // Keep these two lines together to keep the initialization order explicit
            LOG = LoggerFactory.getLogger(SpeakOneByOneInCluster.class);
        }
    
        SpeakOneByOneInCluster(String address){
            this.address = address;
            if (zk == null) {
                try {
                    connect(address);
                } catch (IOException e) {
                    writeLog(e.toString());
                    zk = null;
                }
            }
        }
    
        void connect(String address) throws IOException {
            writeLog("Starting ZK:");
            zk = new ZooKeeper(address, 2181, this);
            writeLog("Finished starting ZK: " + zk);
        }
    
        synchronized public void process(WatchedEvent event) {
            synchronized (LOCK) {
                if (!event.getPath().equals(LOCK_NODE)) {
                    LOCK.notify();
                }
            }
        }
    
        /**
         * 用于选举可用于做事情的应用
         */
        static public class Barrier extends SpeakOneByOneInCluster implements Runnable {
    
            private static final Logger LOG;
            static {
                // Keep these two lines together to keep the initialization order explicit
                LOG = LoggerFactory.getLogger(Barrier.class);
            }
            String                      nodeName;
    
            /**
             * Barrier constructor
             * 
             * @param address
             * @param root
             * @param size
             */
            Barrier(String address, String root){
                super(address);
                this.root = root;
                // Create barrier node
                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) {
                        writeLog("Keeper exception when instantiating queue: " + e.toString());
                    } catch (InterruptedException e) {
                        writeLog("Interrupted exception");
                    }
                }
                // My node name
                try {
                    this.nodeName = new StringBuilder(InetAddress.getLocalHost().getCanonicalHostName()).append((int) (Math.random() * 100)).toString();
                } catch (UnknownHostException e) {
                    writeLog(e.toString());
                }
            }
    
            /**
             * Join barrier
             * 
             * @return
             * @throws KeeperException
             * @throws InterruptedException
             */
    
            void lock() throws KeeperException, InterruptedException {
                Stat stat = zk.exists(LOCK_NODE, false);
                if (stat != null) {
                    return;
                }
                zk.create(LOCK_NODE, new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
                writeLog(SpeakOneByOneInCluster.nodeName + ":create node " + LOCK_NODE);
                getLock = Boolean.TRUE;
                List<String> childs = zk.getChildren(root, true);
                // 节点存在了就返回不处理
                if (!(childs == null || childs.size() == 0)) {
                    return;
                }
                zk.create(root + "/" + nodeName, new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
                writeLog(SpeakOneByOneInCluster.nodeName + ":create node " + root + "/" + nodeName);
                SpeakOneByOneInCluster.nodeName = this.nodeName;
            }
    
            @Override
            public void run() {
                while (true) {
                    try {
                        lock();
                    } catch (NodeExistsException e) {
                    } catch (SessionExpiredException e) {
                        try {
                            // Session过期了,进行重连
                            connect(address);
                        } catch (IOException e1) {
                            LOG.error(e1.getMessage(), e1);
                        }
                    } catch (Exception e) {
                        LOG.error(e.getMessage(), e);
                    }
                }
            }
        }
    
        /**
         * Producer-Consumer queue
         */
        static public class Speak extends SpeakOneByOneInCluster implements Runnable {
    
            private static final Logger LOG;
            static {
                // Keep these two lines together to keep the initialization order explicit
                LOG = LoggerFactory.getLogger(Speak.class);
            }
    
            /**
             * Constructor of speaker
             * 
             * @param address
             * @param root
             */
            Speak(String address, String root){
                super(address);
                this.root = root;
                // Create ZK node name
                if (zk != null) {
                    try {
                        Stat s = zk.exists(this.root, false);
                        if (s == null) {
                            zk.create(this.root, new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
                        }
                    } catch (SessionExpiredException e) {
                        try {
                            // Session过期了,进行重连
                            connect(address);
                        } catch (IOException e1) {
                            LOG.error(e1.getMessage(), e1);
                        }
                    } catch (KeeperException e) {
                        writeLog("Keeper exception when instantiating queue: " + e.toString());
                    } catch (InterruptedException e) {
                        writeLog("Interrupted exception");
                    }
                }
            }
    
            /**
             * Add element to the queue.
             * 
             * @param i
             * @return
             */
    
            boolean check() {
                try {
                    if (getLock == Boolean.FALSE) {
                        return false;
                    }
                    List<String> childs = zk.getChildren(root, true);
                    if (childs == null || childs.size() == 0) {
                        return false;
                    }
                    if (childs.size() != 1) {
                        throw new RuntimeException("childs's size is " + childs.size() + ", it must be 1.");
                    }
                    if (childs.get(0).endsWith(SpeakOneByOneInCluster.nodeName)) {
                        return true;
                    }
                    return false;
                } catch (SessionExpiredException e) {
                    try {
                        // Session过期了,进行重连
                        connect(address);
                    } catch (IOException e1) {
                        LOG.error(e1.getMessage(), e1);
                    }
                    return false;
                } catch (Exception e) {
                    LOG.error(e.getMessage(), e);
                    return false;
                }
    
            }
    
            void speak() throws InterruptedException {
                while (true) {
                    // writeLog(CopyOneFileInMultiVM.choosedNodeName + ":try speak...");
                    synchronized (LOCK) {
                        if (!check()) {
                            // writeLog(CopyOneFileInMultiVM.choosedNodeName + ":copy condition not ready,wait.");
                            LOCK.wait();
                        } else {
                            writeLog(SpeakOneByOneInCluster.nodeName + ":speak now");
                            doSpeak();
                        }
                    }
                }
            }
    
            void doSpeak() {
                try {
                    writeLog(SpeakOneByOneInCluster.nodeName + ": Now only I can speak. Let me sleep 5s.");
                    Thread.sleep(5000);
                    writeLog(SpeakOneByOneInCluster.nodeName + ": I week up now.");
                    unlock();
                } catch (SessionExpiredException e) {
                    try {
                        connect(address);
                        unlock();
                    } catch (Exception e1) {
                        LOG.error(e1.getMessage(), e1);
                    }
                } catch (Exception e) {
                    LOG.error(e.getMessage(), e);
                }
            }
    
            /**
             * delete the lock node
             * 
             * @return
             * @throws KeeperException
             * @throws InterruptedException
             */
    
            void unlock() throws KeeperException, InterruptedException {
                writeLog(SpeakOneByOneInCluster.nodeName + ":Begin to delete node " + SpeakOneByOneInCluster.nodeName + " and node " + LOCK_NODE);
                zk.delete(root + "/" + SpeakOneByOneInCluster.nodeName, 0);
                zk.delete(LOCK_NODE, 0);
                getLock = Boolean.FALSE;
                writeLog(SpeakOneByOneInCluster.nodeName + ":Resource release OK. It's you turn, bye.");
            }
    
            @Override
            public void run() {
                try {
                    speak();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    LOG.error(e.getMessage(), e);
                }
            }
        }
    
        public void writeLog(String str) {
            try {
                System.out.println(str);
                FileUtils.writeStringToFile(logFile, str + "
    ", true);
            } catch (IOException e) {
                LOG.error(e.getMessage(), e);
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            LOCK_NODE = "/zk_test/lock";
            new Thread(new Barrier("localhost", "/zk_test/chooseCopyMachine")).start();
            new Thread(new Speak("localhost", "/zk_test/chooseCopyMachine")).start();
            Runtime.getRuntime().addShutdownHook(new Thread() {
    
                public void run() {
                    try {
                        if (zk.exists(LOCK_NODE, false) != null) {
                            zk.delete(LOCK_NODE, -1);
                        }
                        zk.close();
                    } catch (Exception e) {
                        // TODO Auto-generated catch block
                        LOG.error(e.getMessage(), e);
                    }
                }
            });
        }
    }
    


       

    三、参考

    http://www.ibm.com/developerworks/library/bd-zookeeper/#N100BD

    http://www.ibm.com/developerworks/cn/opensource/os-cn-zookeeper/

    http://zookeeper.apache.org/doc/trunk/zookeeperTutorial.html

    http://zookeeper.apache.org/doc/trunk/zookeeperAdmin.html

    http://zookeeper.apache.org/doc/trunk/zookeeperAdmin.html#sc_advancedConfiguration

    再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow

  • 相关阅读:
    2018第45周日
    RabbitMQ消息的消费与持久化
    Rabbitmq的调度策略
    Rabbitmq交换器Exchange和消息队列
    RabbitMQ概念
    微服务拆分
    微服务演化
    2018第44周日
    福勒(Martin Fowler)
    微服务架构定义那点事
  • 原文地址:https://www.cnblogs.com/skiwdhwhssh/p/10341653.html
Copyright © 2011-2022 走看看