Zookeeper基础

  • zookeeper介绍
  1.  什么是zookeeper
     Zookeeper是一个分布式开源框架,提供了协调分布式应用的基本服务,它向外部应用暴露一组通用服务——分布式同步(Distributed Synchronization)、命名服务(Naming Service)、集群维护(Group Maintenance)等,简化分布式应用协调及其管理的难度,提供高性能的分布式服务。ZooKeeper本身可以以单机模式安装运行,不过它的长处在于通过分布式ZooKeeper集群(一个Leader,多个Follower),基于一定的策略来保证ZooKeeper集群的稳定性和可用性,从而实现分布式应用的可靠性。
    1、zookeeper是为别的分布式程序服务的
    2、Zookeeper本身就是一个分布式程序(只要有半数以上节点存活,zk就能正常服务)
    3、Zookeeper所提供的服务涵盖:主从协调、服务器节点动态上下线、统一配置管理、分布式共享锁、统> 一名称服务等
    4、虽然说可以提供各种服务,但是zookeeper在底层其实只提供了两个功能:
    管理(存储,读取)用户程序提交的数据(类似namenode中存放的metadata); 
    并为用户程序提供数据节点监听服务;
  2. Zookeeper集群机制 
    Zookeeper集群的角色: Leader 和 follower 
    只要集群中有半数以上节点存活,集群就能提供服务 
  3. zookeeper特性
    a、Zookeeper:一个leader,多个follower组成的集群
    b、全局数据一致:每个server保存一份相同的数据副本,client无论连接到哪个server,数据都是一致的
    c、分布式读写,更新请求转发,由leader实施
    d、更新请求顺序进行,来自同一个client的更新请求按其发送顺序依次执行
    e、数据更新原子性,一次数据更新要么成功,要么失败
    f、实时性,在一定时间范围内,client能读到最新数据 
     
  4. Zookeeper数据结构 
     
     a、层次化的目录结构,命名符合常规文件系统规范(类似文件系统) 
     b、每个节点在zookeeper中叫做znode,并且其有一个唯一的路径标识 
     c、节点Znode可以包含数据和子节点(但是EPHEMERAL类型的节点不能有子节点)
     
  5. Zookeeper节点的类型
    PERSISTENT  持久化节点
    PERSISTENT_SEQUENTIAL  持久化顺序节点
    EPHEMERAL  临时节点
    EPHEMERAL_SEQUENTIAL  临时顺序节点
  6. zookeeper的应用场景
    数据发布与订阅(配置中心)  
    命名服务(Naming Service)
    分布式通知/协调 
    集群管理与Master选举
    分布式锁  
     
  7. 事件监听 
    事件监听器(Watcher)是ZooKeeper非常重要的特性,我们可以在节点上注册Watcher,并且在一些特性事件触发时候,服务器将事件通知到客户端上
  • windows环境安装
  1. 下载, 地址:http://zookeeper.apache.org/ 
     
     

     
     

      
     

     
     

     直接下载到本地
  2. 解压 
     解压后如下:
  3. 设置配置文件
    因为zookeeper默认读取的是zoo.cfg配置,而且下载得没有,所有这个文件得我们自己去手动配置
    进入conf目录,直接将zoo_sample.cfg文件copy后改成zoo.cfg文件,内容不变
     
  4. 启动zookeeper 
     进入zookeeper得bin目录点击'zkServer.cmd'启动
     
     这里zookeeper安装完成
  5. zookeeper可视化工具使用
     下载ZooInspector,地址:https://issues.apache.org/jira/secure/attachment/12436620/ZooInspector.zip, 解压,
     进入ZooInspector的build目录,双击'zookeeper-dev-ZooInspector.jar',启动
      
     输入zookeeper的链接IP
      
     链接成功如下:这里我们查看到zookeeper的数据节点 
     这里可视化工具安装完成! 
  6. 使用zookeeper客户端操作
     进入zookeeper的bin目录,双击'zkCli.cmd',启动客户端。zookeeper常用命令如下:
     help 显示所有操作命令
     a、查看当前节点中所包含的内容:  ls /
      
     b、查看当前节点内容和详细信息: ls2 /
     
     c、获取节点的值:get /zookeeper
     
      d、创建节点:create /节点名称   节点value
     
     e、创建临时节点: create -e /节点名称   节点value   --使用quit退出,该节点也会消失
     
     f、修改节点: set /name xiaoming
      
     g、删除节点:delete /name
     
     h、递归删除节点:  --递归删除所有的节点,包括子节点
           rmr /name
      i、查看节点状态: 
          stat  /name
      j、监听节点值的变化:   --只要这个节点值变化,就会有且只有一次响应,即节点值改变一次之后就不会再监听了
          get  /name  watch  
     k、监听节点的子节点变化(路径变化)  --只要路径变化就会相应,就会响应一次
           ls /name watch
     l、查看所有操作命令: help
     
  • linux安装zookeeper 
     

     本次安装环境是jdk1.8,记得安装前关闭防火墙。
  1. 关闭防火墙
    service iptables stop

     

  2. 进入目录
     
    cd /usr/local
  3. 下载
     
    wget http://mirror.bit.edu.cn/apache/zookeeper/zookeeper-3.4.14/zookeeper-3.4.14.tar.gz
  4. 解压
     
    tar -zxvf zookeeper-3.4.14.tar.gz 
  5. 重命令解压目录
    mv zookeeper-3.4.14 zookeeper

     

  6. 重命名配置文件
     
    cd /usr/local/zookeeper/conf
    mv zoo_sample.cfg zoo.cfg   // 重新命令文件
  7. 编辑文件zoo.cfg,修改dataDir的指定路径
     
     
    vi zoo.cfg
    ######################修改zoo.cfg的内容如下###############################
    dataDir=/usr/local/zookeeper/data

     

     创建dataDir所指定的目录

    mkdir /usr/local/zookeeper/data  // 在zookeeper下创建data目录

     

  8. 配置环境变量 
     
    vi /etc/profile 
    ####################在文件末尾添加配置如下#########
    export ZOO_HOME=/usr/local/zookeeper
    export PATH=$ZOO_HOME/bin:$PATH
    
    
    source /etc/profile

     
  9. 启动
     
    bin/zkServer.sh start   // 启动
    jps                     // 查看是否启动成功

     
  • 集群规划

  •  准备3台服务器137、138、139,安装上述步骤先安装好zookeeper,先停止zookeeper服务。
  1. 编辑137的zoo.cfg文件
     
    cd /usr/local/zookeeper/conf
    vi zoo.cfg
    
    ##############在文件的最后追加内容########################
    server.0=192.168.5.137:2888:3888   #===对应myid文件内容为0
    server.1=192.168.5.138:2888:3888   #===对应myid文件内容为1
    server.2=192.168.5.139:2888:3888   #===对应myid文件内容为2
    
  2. 在dataDir=/usr/local/zookeeper/data目录下,创建myid文件,内容按上面的说明
     
    cd /usr/local/zookeeper/data
    vim myid
    ##############添加137:'0', 138:'1', 139;'2'内容如下#################
    0
    查看
    cat myid

    各个服务器的myid文件内容不一样,137为0,138为1,139为2,上面也有说明
  3. 按照上面的步骤138、139的也配置一遍。
  4. 启动137、138、139的zookeeper并查看状态 。这里已经就完成集群搭建了
     
    [root@localhost bin]# ./zkServer.sh start   # 启动
    [root@localhost bin]# ./zkServer.sh status  # 查看状态

     137 
     
     138
      
     139
     
     
    此时,138为主服务,137、139为从服务
  • 错误信息
     我的启动报错如下,如果一样,可以参考下
    [root@localhost bin]# ./zkServer.sh start
    ZooKeeper JMX enabled by default
    Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg
    Starting zookeeper ... STARTED
    [root@localhost bin]# ./zkServer.sh status
    ZooKeeper JMX enabled by default
    Using config: /usr/local/zookeeper/bin/../conf/zoo.cfg
    Error contacting service. It is probably not running
     
    我的是因为我配置了3台服务,但我只启动了137这台,只要138、139启起来,再去查看自然就好了。。 
     
     
  • zookeeper配置文件的讲解
  1. zoo.cfg配置文件内容如下:
    # The number of milliseconds of each tick
    tickTime=2000
    # The number of ticks that the initial 
    # synchronization phase can take
    initLimit=10
    # The number of ticks that can pass between 
    # sending a request and getting an acknowledgement
    syncLimit=5
    # the directory where the snapshot is stored.
    # do not use /tmp for storage, /tmp here is just 
    # example sakes.
    dataDir=/usr/local/zookeeper/data
    # the port at which the clients will connect
    clientPort=2181
    # the maximum number of client connections.
    # increase this if you need to handle more clients
    #maxClientCnxns=60
    #
    # 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
    #
    # The number of snapshots to retain in dataDir
    #autopurge.snapRetainCount=3
    # Purge task interval in hours
    # Set to "0" to disable auto purge feature
    #autopurge.purgeInterval=1
    server.0=192.168.5.137:2888:3888
    server.1=192.168.5.138:2888:3888
    server.2=192.168.5.139:2888:3888
    

     

  2. 配置文件说明 
     
    tickTime:心跳时间,为了确保连接存在的,以毫秒为单位,最小超时时间为两个心跳时间, 若服务挂了,自动轮询下一台
    initLimit:多少个心跳时间内,允许其他server连接并初始化数据,如果ZooKeeper管理的数据较大,则应相应增大这个值
    clientPort:服务的监听端口
    dataDir:用于存放内存数据库快照的文件夹,同时用于集群的myid文件也存在这个文件夹里(注意:一个配置文件只能包含一个dataDir字样,即使它被注释掉了。)
    dataLogDir:用于单独设置transaction log的目录,transaction log分离可以避免和普通log还有快照的竞争
    syncLimit:多少个tickTime内,允许follower同步,如果follower落后太多,则会被丢弃。
     
    server.A=B:C:D:
    A是一个数字,表示这个是第几号服务器,B是这个服务器的ip地址
    C第一个端口用来集群成员的信息交换,表示的是这个服务器与集群中的Leader服务器交换信息的端口
    D是在leader挂掉时专门用来进行选举leader所用
    

     

  • zookeeper的常用命令
    启动zookeeperbin/zkServer.sh start
    查看进程是否启动jps
    查看状态bin/zkServer.sh status
    停止zookeeperbin/zkServer.sh stop
    查看启动错误信息cat bin/zookeeper.out
    重启bin/zkServer.sh restart
    启动客户端bin/zkCli.sh
    退出客户端quit
      
     
  • 使用java语言操作zookeeper
  1. 创建maven项目,在pom.xml中引入依赖
     
    <dependency>
          <groupId>org.apache.zookeeper</groupId>
          <artifactId>zookeeper</artifactId>
          <version>3.4.6</version>
        </dependency>
  2. 创建测试类
     
    import org.apache.zookeeper.*;
    import org.apache.zookeeper.data.Stat;
    import java.io.IOException;
    import java.util.List;
    
    /**
     * Zookeeper Wathcher操作节点
     * Create by wangxb
     * 2019-06-25 6:46
     */
    public class ZkWatcher implements Watcher {
    
        /**
         * zookeeper集群连接地址
         */
        private static final String CONNECT_ADDR = "192.168.5.137:2181,192.168.5.138:2181,192.168.5.139:2181";
        /**
         * session超时时间
         */
        private static final int SESSION_OUTTIME = 2000;
    
        private ZooKeeper zooKeeper;
    
        // 创建连接
        public void createConnection(String address, int sessionTimeout){
            try {
                zooKeeper = new ZooKeeper(address, sessionTimeout, this);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        // 关闭连接
        public void close(){
            try {
                if (zooKeeper != null) zooKeeper.close();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        @Override  // 对zk节点的新增、修改、删除进行监听
        public void process(WatchedEvent event) {
            // 获取节点的创建和类型
            System.out.println("接收服务端的Watcher通知.............");
            Event.KeeperState keeperState = event.getState();  // 连接状态
            Event.EventType eventType = event.getType();       // 连接类型
            String path = event.getPath();                     // 节点路径
            // 判断是否在建立连接
            if (Event.KeeperState.SyncConnected == keeperState) {
                // 连接类型
                if (Event.EventType.None == eventType) {
                    System.out.println("zk 建立连接成功");
                }
                // 创建类型
                if (Event.EventType.NodeCreated == eventType) {
                    System.out.println("当前事件属于:创建 节点, 路径为"+path);
                }
                // 修改类型
                if (Event.EventType.NodeDataChanged == eventType) {
                    System.out.println("当前事件属于:修改 节点, 路径为"+path);
                }
                // 删除类型
                if (Event.EventType.NodeDeleted == eventType) {
                    System.out.println("当前事件属于:删除 节点, 路径为"+path);
                }
            }else{
                System.out.println("zk 连接失败: 请检查......");
            }
        }
    
        // 创建节点
        public void createNode(String path, String data) throws KeeperException, InterruptedException {
            // zooKeeper.exists(path, true); 表示开启事件通知  由于zookeeper的监控都是一次性的所以 每次必须设置监控
            zooKeeper.exists(path, true);
            // CreateMode.EPHEMERAL:临时节点,只在当前会话存在     CreateMode.PERSISTENT:持久节点,永久存在
            zooKeeper.create(path, data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            System.out.println("创建节点成功.......");
        }
    
        // 修改节点
        public void updateNode(String path, String data) throws KeeperException, InterruptedException {
            zooKeeper.exists(path, true);
            // 参数3为传入的版本号,这里默认-1
            zooKeeper.setData(path, data.getBytes(), -1);
            System.out.println("修改节点成功.......");
        }
        // 删除节点
        public void deleteNode(String path) throws KeeperException, InterruptedException {
            zooKeeper.exists(path, true);
            // Stat stat=new Stat();
            zooKeeper.delete(path, -1);  // -1表示版本号
            System.out.println("删除节点成功.......");
        }
        // 获取节点数据
        public void getData(String path, boolean watch) throws KeeperException, InterruptedException {
            String result = new String(zooKeeper.getData(path, watch, null));
            System.out.println("获取节点数据成功....."+result);
        }
        // 获取子节点
        public List<String> getChildren(String path, boolean needWatch) throws KeeperException, InterruptedException {
            return zooKeeper.getChildren(path, needWatch); // 默认true;
        }
    
        // 判断节点是否存在
        public Stat exists(String path, boolean needWatch) throws KeeperException, InterruptedException {
            return zooKeeper.exists(path, needWatch);  // 参数默认true; 返回null则不存在
        }
    
        public static void main(String[] args) throws  Exception {
            String path = "/aaa";
            ZkWatcher zk = new ZkWatcher();
            zk.createConnection(ZkWatcher.CONNECT_ADDR, ZkWatcher.SESSION_OUTTIME);
    //        zk.createNode(path, "456");
    //        zk.updateNode(path, "456");
    //        zk.getData(path, true);
    //        List<String> children = zk.getChildren(path, true);
            Stat stat = zk.exists(path, true);
            System.out.println(stat);
        }
    
    }
  3. 测试完成
  4. 相关方法讲解   
     
     
    // zooKeeper.exists(path, true); 表示开启事件通知  由于zookeeper的监控都是一次性的所以 每次必须设置监控
     
    zk.create(param1, param2, param3, param4) 
    
     param1: 节点路径(名称),创建时父节点必须存在。必须以"/"开头

     param2: 节点内容: 要求类型是字节数组(不支持序列化方式,如果需要实现序列化,可使用java相关序列化框架。)

     param3: 节点权限: 一般默认都使用Ids.OPEN_ACL_UNSAFE开放权限即可。
     
     param4: 节点类型, 有四种可选,用的最多的是EPHEMERAL、PERSISTENT

         1、PERSISTENT(持久节点)    // 数据持久存在
         2、PERSISTENT SEQUENTIAL(持久顺序节点) 
         3、EPHEMERAL(临时节点) // 数据只在当前会话有效
         4、EPHEMERAL SEQUENTAL(临时顺序节点)、

  5. 关于Watcher 
     在ZooKeeper中,接口类Watcher用于表示一个标准的事件处理器,其定义了事件通知相关的逻辑,包含KeeperState和  EventType两个枚举类,分别代表了通知状态和事件类型,同一个事件类型在不同的通知状态中代表的含义有所不同,     同时定义了事件的回调方法:process(WatchedEvent event)。 
     process方法是Watcher接口中的一个回调方法,当ZooKeeper向客户端发送一个Watcher事件通知时,客户端就会对相应的process方法进行回调,从而实现对事件的处理。process方法的定义如下:
     abstract public void process(WatchedEvent event)

     相关方法
     

    KeeperState

    EventType

    触发条件

    说明

     

    None
    (-1)

    客户端与服务端成功建立连接

     

    SyncConnected
    (0)

    NodeCreated
    (1)

    Watcher监听的对应数据节点被创建

     

     

    NodeDeleted
    (2)

    Watcher监听的对应数据节点被删除

    此时客户端和服务器处于连接状态

     

    NodeDataChanged
    (3)

    Watcher监听的对应数据节点的数据内容发生变更

     

     

    NodeChildChanged
    (4)

    Wather监听的对应数据节点的子节点列表发生变更

     

    Disconnected
    (0)

    None
    (-1)

    客户端与ZooKeeper服务器断开连接

    此时客户端和服务器处于断开连接状态

    Expired
    (-112)

    Node
    (-1)

    会话超时

    此时客户端会话失效,通常同时也会受到SessionExpiredException异常

    AuthFailed
    (4)

    None
    (-1)

    通常有两种情况,1:使用错误的schema进行权限检查 2:SASL权限检查失败

    通常同时也会收到AuthFailedException异常

  • zookeeper实现分布式锁
  1. 分布式锁的3种实现方式
     
    1、基于数据实现分布式锁
       IO操作性能较差,容易出现单点故障
       锁没有失效事件,容易死锁。
       非阻塞式,不可重入
    
    2、基于缓存实现分布式锁
       锁没有失效事件,容易死锁
       非阻塞式
       不可重入
    
     
    3、基于Zookeeper实现分布式锁
       实现相对简单
       可靠性高
       性能较好
       
                 
    
  • 分布式锁案例
      场景描述
      在线程高并发场景下,生成唯一的订单编号  ,如:2017-10-14-20-52-33-01
     
      
     实现思路: 使用zookeeper的临时节点(会话内有效),和路径不可重复。
                 在zk上创建临时节点,使临时节点作为锁且节点不可以重复,
                 如果创建失败,则继续等待。
                 如果能创建界节点成功,则生成订单号。zk临时会话结束,释放锁。其他节点继续生成订单号
     
     
  1. 创建一个maven项目,在pom.xml种引入依赖
     
    <dependency>
          <groupId>org.apache.zookeeper</groupId>
          <artifactId>zookeeper</artifactId>
          <version>3.4.6</version>
        </dependency>
        <dependency>
          <groupId>com.101tec</groupId>
          <artifactId>zkclient</artifactId>
          <version>0.10</version>
        </dependency>
  2. 定义一个接口Lock.java
     
    public interface Lock {
        // 手动上锁
        void getLock();
        // 手动释放锁
        void unLock();
    }
  3. 定义一个抽象类ZookeeperAbstractLock.java实现该接口
     
    import org.I0Itec.zkclient.ZkClient;
    
    /**
     * Create by wangxb
     * 2019-06-27 7:51
     */
    public abstract class ZookeeperAbstractLock implements  Lock{
        private static final String CONNECT_ADDR = "192.168.5.137:2181,192.168.5.138:2181,192.168.5.139:2181";
        protected static final String PATH = "/lock";
        protected ZkClient zkClient = new ZkClient(CONNECT_ADDR);
        @Override
        public void getLock() {
            // 通过创建锁资源, 如果创建成功返回true, 否则false
            if (tryLock()){
                System.out.println("获取锁资源成功.......");
            }else{
                waitLock(); // 等待
                getLock();; // 重新获取锁
            }
        }
    
        protected abstract boolean tryLock();
        protected  abstract void waitLock();
    
        @Override
        public void unLock() {
            // 关闭链接,释放锁
            if (zkClient != null){
                zkClient.close();
                System.out.println("释放锁资源成功.......");
            }
        }
    }
  4. 定义一个类ZookeeperLockImpl.java继承该抽象类
     
    import org.I0Itec.zkclient.IZkDataListener;
    import java.util.concurrent.CountDownLatch;
    
    /**
     * Create by wangxb
     * 2019-06-27 20:44
     */
    public class ZookeeperLockImpl extends ZookeeperAbstractLock {
    
        private CountDownLatch countDownLatch = null;
        @Override
        protected boolean tryLock() {
            try {
                zkClient.createEphemeral(PATH); // 通过创建临时节点, 如果成功返回true  否则false
                return true;
            } catch (Exception e){
                return false;
            }
        }
    
        @Override
        protected void waitLock() {  // 如果存在则需要等待
            IZkDataListener iZkDataListene = new IZkDataListener() {
                @Override  // 节点改变事件通知,临时节点创建成功后删除,   唤醒线程
                public void handleDataChange(String path, Object o) throws Exception {
                   
                }
    
                @Override  // 删除事件通知,临时节点创建成功后删除,   唤醒线程
                public void handleDataDeleted(String path) throws Exception {
                    if (countDownLatch != null) countDownLatch.countDown();
                }
            };
            // 注册到zkClient监听
            zkClient.subscribeDataChanges(PATH, iZkDataListene);
            if (zkClient.exists(PATH)){
               countDownLatch = new CountDownLatch(1);
               try {
                   countDownLatch.await();
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
            zkClient.unsubscribeDataChanges(PATH, iZkDataListene);
        }
    }

     
  5. 定义一个类OrderNumber.java生成订单号
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    /**
     * Create by wangxb
     * 2019-06-27 7:22
     */
    public class OrderNumber {
    
        private static int count = 0;
    
        /**
         * 生成订单号
         * @return
         */
        public String createOrdereNumber(){
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-Mm-dd-HH-mm-ss");
            String format = simpleDateFormat.format(new Date());
            return format+ "-"+ ++count;
        }
    }

     

  6. 定义一个OrderService.java测试生成订单
    /**
     * Create by wangxb
     * 2019-06-27 7:22
     */
    public class OrderService implements  Runnable {
        OrderNumber orderNumber = new OrderNumber();
        public static Object obj = new Object();
        Lock lock = new ZookeeperLockImpl() ;
        @Override
        public void run() {
            getNumber();
        }
    
        public void getNumber(){
            lock.getLock();  //  synchronized (obj)
            String number =  orderNumber.createOrdereNumber();
            System.out.println(Thread.currentThread().getName()+"生成订单号---->"+number);
            lock.unLock();
        }
    
        public static void main(String[] args) {
            for(int i=0; i<100; i++){
                new Thread(new OrderService()).start();
            }
        }
    }

     

  7. 启动OrderService.java,测试
  • Zookeeper实现服务的接口网关
     
     
  1. 未完待续...................................
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值