zoukankan      html  css  js  c++  java
  • ZooKeeper 编程(一)

    Zookeeper的节点都是存放在内存中的,所以读写速度很快。更新日志被记录到了磁盘中,以便用于恢复数据。在更新内在中节点数之前,会先序列化到磁盘中。

    为避免单点失效,zookeeper的数据是在多个server上留有备份的。不管客户端连接到的是哪个server,它看到的数据都是一致的。如果client和一个server的TCP连接失效,它会尝试连接另一个server。众多server中有一个是leader。所有的server 都必须知道彼此的存在。zookeeper在读写比例为10:1时性能最佳。每个znode上data的读写都是原子操作。

    读是局部性的,即client只需要从与它相连的server上读取数据即可;而client有写请求的话,与之相连的server会通知leader,然后leader会把写操作分发给所有server。所以写要比读慢很多。

    在建立zookeeper连接时,给定的地址字符串可以是这样的:"192.168.1.1:3000,192.168.1.2:3000,192.168.1.3:3000/app/a",以后的所有操作就都是在/app/a下进行的。实际上只连接到一台ZooKeeper机器就可了,没必要指定每台zk机器的IP和端口,即用“192.168.1.2:3000/app/a”也是可以的。

    当client与一个server断连接时(可能是因为server失效了),它就收不到任何watches;当它与另一个server建立好连接后,它就会收到"session expired"通知。

    ACL不是递归的,它只针对当前节点,对子节点没有任何影响。

    默认情况下日志文件和数据文件是放在同一个目录下的,为缩短延迟提高响应性,你可以把日志文件单独放在另一个目录下。

    为避免swaping,运行java时最好把可用物理内在调得大一些,比如对于4G的内在,可以把它调到3G。java有以下两个运行参数:

    -Xms<size>
    设置虚拟机可用内存堆的初始大小,缺省单位为字节,该大小为1024的整数倍并且要大于1MB,可用k(K)或m(M)为单位来设置较大的内存数。初始堆大小为2MB。
    例如:-Xms6400K,-Xms256M
    -Xmx<size>
    设置虚拟机内存堆的最大可用大小,缺省单位为字节。该值必须为1024整数倍,并且要大于2MB。可用k(K)或m(M)为单位来设置较大的内存数。缺省堆最大值为64MB。
    例如:-Xmx81920K,-Xmx80M

    表 1 org.apache.zookeeper. ZooKeeper 方法列表

    方法名方法功能描述
    String create(String path, byte[] data, List<ACL> acl,CreateMode createMode) 创建一个给定的目录节点 path, 并给它设置数据,CreateMode 标识有四种形式的目录节点,分别是 PERSISTENT:持久化目录节点,这个目录节点存储的数据不会丢失;PERSISTENT_SEQUENTIAL:顺序自动编号的目录节点,这种目录节点会根据当前已近存在的节点数自动加 1,然后返回给客户端已经成功创建的目录节点名;EPHEMERAL:临时目录节点,一旦创建这个节点的客户端与服务器端口也就是 session 超时,这种节点会被自动删除;EPHEMERAL_SEQUENTIAL:临时自动编号节点
    Stat exists(String path, boolean watch) 判断某个 path 是否存在,并设置是否监控这个目录节点,这里的 watcher 是在创建 ZooKeeper 实例时指定的 watcher,exists方法还有一个重载方法,可以指定特定的watcher
    Stat exists(String path,Watcher watcher) 重载方法,这里给某个目录节点设置特定的 watcher,Watcher 在 ZooKeeper 是一个核心功能,Watcher 可以监控目录节点的数据变化以及子目录的变化,一旦这些状态发生变化,服务器就会通知所有设置在这个目录节点上的 Watcher,从而每个客户端都很快知道它所关注的目录节点的状态发生变化,而做出相应的反应
    void delete(String path, int version) 删除 path 对应的目录节点,version 为 -1 可以匹配任何版本,也就删除了这个目录节点所有数据
    List<String>getChildren(String path, boolean watch) 获取指定 path 下的所有子目录节点,同样 getChildren方法也有一个重载方法可以设置特定的 watcher 监控子节点的状态
    Stat setData(String path, byte[] data, int version) 给 path 设置数据,可以指定这个数据的版本号,如果 version 为 -1 怎可以匹配任何版本
    byte[] getData(String path, boolean watch, Stat stat) 获取这个 path 对应的目录节点存储的数据,数据的版本等信息可以通过 stat 来指定,同时还可以设置是否监控这个目录节点数据的状态
    voidaddAuthInfo(String scheme, byte[] auth) 客户端将自己的授权信息提交给服务器,服务器将根据这个授权信息验证客户端的访问权限。
    Stat setACL(String path,List<ACL> acl, int version) 给某个目录节点重新设置访问权限,需要注意的是 Zookeeper 中的目录节点权限不具有传递性,父目录节点的权限不能传递给子目录节点。目录节点 ACL 由两部分组成:perms 和 id。
    Perms 有 ALL、READ、WRITE、CREATE、DELETE、ADMIN 几种 
    而 id 标识了访问目录节点的身份列表,默认情况下有以下两种:
    ANYONE_ID_UNSAFE = new Id("world", "anyone") 和 AUTH_IDS = new Id("auth", "") 分别表示任何人都可以访问和创建者拥有访问权限。
    List<ACL>getACL(String path,Stat stat) 获取某个目录节点的访问权限列表
     1 package com.demo;
     2 
     3 import java.io.IOException;
     4 import java.util.List;
     5 
     6 import org.apache.zookeeper.CreateMode;
     7 import org.apache.zookeeper.KeeperException;
     8 import org.apache.zookeeper.ZooKeeper;
     9 import org.apache.zookeeper.ZooDefs.Ids;
    10 
    11 public class Demo {
    12     private static final int TIMEOUT = 3000;
    13 
    14     public static void main(String[] args) throws IOException {
    15         ZooKeeper zkp = new ZooKeeper("localhost:2181", TIMEOUT, null);
    16         try {
    17             // 创建一个EPHEMERAL类型的节点,会话关闭后它会自动被删除
    18             zkp.create("/node1", "data1".getBytes(), Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL);
    19             if (zkp.exists("/node1", false) != null) {
    20                 System.out.println("node1 exists now.");
    21             }
    22             try {
    23                 // 当节点名已存在时再去创建它会抛出KeeperException(即使本次的ACL、CreateMode和上次的不一样)
    24                 zkp.create("/node1", "data1".getBytes(), Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
    25             } catch (KeeperException e) {
    26                 System.out.println("KeeperException caught:" + e.getMessage());
    27             }
    28 
    29             // 关闭会话
    30             zkp.close();
    31             
    32             zkp = new ZooKeeper("localhost:2181", TIMEOUT, null);
    33             //重新建立会话后node1已经不存在了
    34             if (zkp.exists("/node1", false) == null) {
    35                 System.out.println("node1 dosn't exists now.");
    36             }
    37             //创建SEQUENTIAL节点
    38             zkp.create("/node-", "same data".getBytes(), Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT_SEQUENTIAL);
    39             zkp.create("/node-", "same data".getBytes(), Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT_SEQUENTIAL);
    40             zkp.create("/node-", "same data".getBytes(), Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT_SEQUENTIAL);
    41             List<String> children = zkp.getChildren("/", null);
    42             System.out.println("Children of root node:");
    43             for (String child : children) {
    44                 System.out.println(child);
    45             }
    46 
    47             zkp.close();
    48         } catch (Exception e) {
    49             System.out.println(e.getMessage());
    50         }
    51     }
    52 }

    第一次运行输出:

    node1 exists now.
    KeeperException caught:KeeperErrorCode = NodeExists for /node1
    node1 dosn't exists now.
    Children of root node:
    node-0000000003
    zookeeper
    node-0000000002
    node-0000000001

    第二次运行输出:

    node1 exists now.
    KeeperException caught:KeeperErrorCode = NodeExists for /node1
    node1 dosn't exists now.
    Children of root node:
    node-0000000003
    zookeeper
    node-0000000002
    node-0000000001
    node-0000000007
    node-0000000005
    node-0000000006

    注意两次会话中创建的PERSISTENT_SEQUENTIAL节点序号并不是连续的,比如上例中缺少了node-0000000004.

    Watcher & Version

    watcher分为两大类:data watches和child watches。

    getData()和exists()上可以设置data watches,getChildren()上可以设置child watches

    setData()会触发data watches;

    create()会触发data watches和child watches;

    delete()会触发data watches和child watches.

    如果对一个不存在的节点调用了exists(),并设置了watcher,而在连接断开的情况下create/delete了该znode,则watcher会丢失。

    在server端用一个map来存放watcher,所以相同的watcher在map中只会出现一次只要watcher被回调一次,它就会被删除----map解释了watcher的一次性。

    比如如果在getData()和exists()上设置的是同一个data watcher,调用setData()会触发data watcher,但是getData()和exists()只有一个会收到通知

     1 package com.demo;
     2 
     3 import java.io.IOException;
     4 
     5 import org.apache.zookeeper.CreateMode;
     6 import org.apache.zookeeper.KeeperException;
     7 import org.apache.zookeeper.WatchedEvent;
     8 import org.apache.zookeeper.Watcher;
     9 import org.apache.zookeeper.ZooDefs.Ids;
    10 import org.apache.zookeeper.ZooKeeper;
    11 import org.apache.zookeeper.data.Stat;
    12 
    13 public class SelfWatcher implements Watcher{
    14     
    15     ZooKeeper zk=null;
    16 
    17     @Override
    18     public void process(WatchedEvent event) {
    19         System.out.println("process:"+event.toString());
    20     }
    21     
    22     SelfWatcher(String address){
    23         try{
    24             zk=new ZooKeeper(address,3000,this);     //在创建ZooKeeper时第三个参数负责设置该类的默认构造函数
    25             zk.create("/root", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
    26         }catch(IOException e){
    27             e.printStackTrace();
    28             zk=null;
    29         }catch (KeeperException e) {
    30             e.printStackTrace();
    31         } catch (InterruptedException e) {
    32             e.printStackTrace();
    33         }
    34     }
    35     
    36     void setWatcher(){
    37         try {
    38             Stat s=zk.exists("/root", true);
    39             if(s!=null){
    40                 zk.getData("/root", false, s);
    41             }
    42         } catch (KeeperException e) {
    43             e.printStackTrace();
    44         } catch (InterruptedException e) {
    45             e.printStackTrace();
    46         }
    47     }
    48     
    49     void trigeWatcher(){
    50         try {
    51             Stat s=zk.exists("/root", false);        //此处不设置watcher
    52             zk.setData("/root", "a".getBytes(), s.getVersion());//修改数据时需要提供version,version设为-1表示强制修改
    53         }catch(Exception e){
    54             e.printStackTrace();
    55         }
    56     }
    57     
    58     void disconnect(){
    59         if(zk!=null)
    60             try {
    61                 zk.close();
    62             } catch (InterruptedException e) {
    63                 e.printStackTrace();
    64             }
    65     }
    66     
    67     public static void main(String[] args){
    68         SelfWatcher inst=new SelfWatcher("127.0.0.1:2181");
    69         inst.setWatcher();
    70         inst.trigeWatcher();
    71         inst.disconnect();
    72     }
    73 
    74 }

    可以在创建Zookeeper时指定默认的watcher回调函数,这样在getData()、exists()和getChildren()收到通知时都会调用这个函数--只要它们在参数中设置了true。所以如果把代码22行的this改为null,则不会有任何watcher被注册。

    上面的代码输出:

    WatchedEvent state:SyncConnected type:None path:null
    WatchedEvent state:SyncConnected type:NodeDataChanged path:/root

    之所会输出第1 行是因为本身在建立ZooKeeper连接时就会触发watcher。输出每二行是因为在代码的第36行设置了true。

    WatchEvent有三种类型:NodeDataChanged、NodeDeleted和NodeChildrenChanged。

    调用setData()时会触发NodeDataChanged;

    调用create()时会触发NodeDataChanged和NodeChildrenChanged;

    调用delete()时上述三种event都会触发。

    如果把代码的第38--41行改为:

    Stat s=zk.exists("/root", false);
    if(s!=null){
        zk.getData("/root", true, s);
    }

    Stat s=zk.exists("/root", true);
    if(s!=null){
        zk.getData("/root", true, s);
    }

    跟上面的输出是一样的。这也证明了watcher是一次性的。

    设置watcher的另外一种方式是不使用默认的watcher,而是在getData()、exists()和getChildren()中指定各自的watcher。示例代码如下:

     1 public class SelfWatcher{
     2     
     3     ZooKeeper zk=null;
     4 
     5     private Watcher getWatcher(final String msg){
     6         return new Watcher(){
     7             @Override
     8             public void process(WatchedEvent event) {
     9                 System.out.println(msg+"	"+event.toString());
    10             }
    11         };
    12     }
    13     
    14     SelfWatcher(String address){
    15         try{
    16             zk=new ZooKeeper(address,3000,null);     //在创建ZooKeeper时第三个参数负责设置该类的默认构造函数
    17             zk.create("/root", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
    18         }catch(IOException e){
    19             e.printStackTrace();
    20             zk=null;
    21         }catch (KeeperException e) {
    22             e.printStackTrace();
    23         } catch (InterruptedException e) {
    24             e.printStackTrace();
    25         }
    26     }
    27     
    28     void setWatcher(){
    29         try {
    30             Stat s=zk.exists("/root", getWatcher("EXISTS"));
    31             if(s!=null){
    32                 zk.getData("/root", getWatcher("GETDATA"), s);
    33             }
    34         } catch (KeeperException e) {
    35             e.printStackTrace();
    36         } catch (InterruptedException e) {
    37             e.printStackTrace();
    38         }
    39     }
    40     
    41     void trigeWatcher(){
    42         try {
    43             Stat s=zk.exists("/root", false);        //此处不设置watcher
    44             zk.setData("/root", "a".getBytes(), s.getVersion());
    45         }catch(Exception e){
    46             e.printStackTrace();
    47         }
    48     }
    49     
    50     void disconnect(){
    51         if(zk!=null)
    52             try {
    53                 zk.close();
    54             } catch (InterruptedException e) {
    55                 e.printStackTrace();
    56             }
    57     }
    58     
    59     public static void main(String[] args){
    60         SelfWatcher inst=new SelfWatcher("127.0.0.1:2181");
    61         inst.setWatcher();
    62         inst.trigeWatcher();
    63         inst.disconnect();
    64     }
    65 
    66 }

    输出:

    GETDATA WatchedEvent state:SyncConnected type:NodeDataChanged path:/root
    EXISTS WatchedEvent state:SyncConnected type:NodeDataChanged path:/root

    上例中由于exists和getData分别设置了两个不同的Watcher实例,所以虽然watcher都是由同了一个NodeDataChanged触发的,但exists()和getData()都会收到通知。由于16行创建Zookeeper时没有设置watcher(参数为null),所以建立连接时没有收到通知。

    关于Version:为了方便进行cache validations 和coordinated updates,每个znode都有一个stat结构体,其中包含:version的更改记录、ACL的更改记录、时间戳。znode的数据每更改一次,version就会加1。客户端每次检索data的时候都会把data的version一并读出出来。修改数据时需要提供version。

    zk.delete("/root", -1);        //触发data watches和children watches。version设为-1时表示要强制删除
    zk.getChildren("/root", getWatcher("LISTCHILDREN"));    //getChildren()上可以设置children watches

    输出:

    LISTCHILDREN WatchedEvent state:SyncConnected type:NodeDeleted path:/root

    zk.delete("/root", -1);        //触发data watches和children watches
    Stat s=zk.exists("/root", getWatcher("EXISTS"));    //exists()上可以设置data watches
    if(s!=null){
        zk.getChildren("/root", getWatcher("LISTCHILDREN"));
    }

    输出:

    EXISTS WatchedEvent state:SyncConnected type:NodeDeleted path:/root
    LISTCHILDREN WatchedEvent state:SyncConnected type:NodeDeleted path:/root

    zk.delete("/root", -1);        //触发data watches和children watches
    Stat s=zk.exists("/root", getWatcher("EXISTS"));
    if(s!=null){
        zk.getData("/root", getWatcher("GETDATA"), s);
        zk.getChildren("/root", getWatcher("LISTCHILDREN"));
    }

    输出:

    GETDATA WatchedEvent state:SyncConnected type:NodeDeleted path:/root
    LISTCHILDREN WatchedEvent state:SyncConnected type:NodeDeleted path:/root
    EXISTS WatchedEvent state:SyncConnected type:NodeDeleted path:/root

    复制代码
    tat s=zk.exists("/root", false);        
    zk.setData("/root", "a".getBytes(), s.getVersion());        
    zk.delete("/root", -1);        
    
    Stat s=zk.exists("/root", getWatcher("EXISTS"));
    if(s!=null){
        zk.getData("/root", getWatcher("GETDATA"), s);
        zk.getChildren("/root", getWatcher("LISTCHILDREN"));
    }
    复制代码

    输出:

    GETDATA WatchedEvent state:SyncConnected type:NodeDataChanged path:/root
    EXISTS WatchedEvent state:SyncConnected type:NodeDataChanged path:/root
    LISTCHILDREN WatchedEvent state:SyncConnected type:NodeDeleted path:/root

    按说data watches触发了两次,但是exists()和getData()只会收到一次通知。

    Barriers and Queues

    Barrier是指:

    1)所有的线程都到达barrier后才能进行后续的计算

    或者

    2)所有的线程都完成自己的计算后才能离开barrier

    Double Barrier是指同时具有上述两点。

    Queue就不说了,一个产生--消费模型,先生产的先被消费。

    Double Barrier的实现:

    复制代码
    enter barrier:
    1.建一个根节点"/root"
    2.想进入barrier的线程在"/root"下建立一个子节点"/root/c_i"
    3.循环监听"/root"孩子节点数目的变化,当其达到size时就说明有size个线程都已经barrier点了
    
    leave barrier:
    1.想离开barrier的线程删除其在"/root"下建立的子节点
    2.循环监听"/root"孩子节点数目的变化,当size减到0时它就可以离开barrier了
    复制代码

    Queue的实现:

    1.建立一个根节点"/root"
    2.生产线程在"/root"下建立一个SEQUENTIAL子节点
    3.消费线程检查"/root"有没有子节点,如果没有就循环监听"/root"子节点的变化,直到它有子节点。删除序号最小的子节点。

    原代码:

    Locks

    复制代码
    获得锁:
    1.创建根节点"/root"
    2.在根节点下新建子节点"/root/c-xxxxxx",SEQUENTIAL模式
    3.对根节点调用getChildren(),如果第2步创建的节点是所有子节点中序号最小的,则获得锁;否则进入第4步
    4.在序号最小的子节点上调用exists(),当序号最小的子节点被删除后返回第3步
    
    释放锁:
    删除自己创建的子节点即可
    复制代码

    原代码:

    读锁(共享锁)和写锁(排斥锁)并存的情况跟单独只有排斥锁的情况有几点不同:

    1.当一个线程想施加读锁时就新建一个节点"/root/read-xxxxxx",施加写锁时就新建一个节点"/root/write-xxxxxx";

    2.欲施加读锁的线程查看"/root"下有没有“write"开头的节点,如果没有则直接获得读锁;如果有,但是"write"节点的序号比自己刚才创建的"read"节点的序号要大说明是先施加的读锁后施加的写锁,所以依然获得读锁;else,在序号最小的"write"节点上调用exists,等待它被删除。

     
     
     
  • 相关阅读:
    朴素贝叶斯分类算法原理
    DevExpress控件学习总结
    Nginx + Tomcat 配置负载均衡集群
    DotNet Core全新认识
    为何梯度反方向是函数值下降最快的方向
    理解矩阵
    C#版-Redis缓存服务器在Windows下的使用
    文本情感分类:分词 OR 不分词(3)
    文本情感分类:深度学习模型(2)
    文本情感分类:传统模型(1)
  • 原文地址:https://www.cnblogs.com/siv8/p/5712720.html
Copyright © 2011-2022 走看看