一、Zookeeper核心概念

  1、什么是znode

         

  ZooKeeper操作和维护的为一个个数据节点,称为 znode,如上图所示,znode采用类似文件系统的层级树状结构进行管理。如果 znode 节点包含数据则存储为字节数组(byte array)。

  创建 znode 时需要指定节点类型,znode 共有 4 种类型,分别为:持久(无序)、临时(无序)、持久有序和临时有序。节点类型如下:

  • PERSISTENT     持久类型,如果不手动删除会一直存在的;
  • PERSISTENT_SEQUENTIAL  持久类型,有序自增
  • EPHEMERAL      临时,客户端session失效就会随着删除节点,没有子节点
  • EPHEMERAL_SEQUENTIAL      临时,有序,自增

  当我们打开zookeeper安装目录/bin目录下的zkCli.sh,可以看到shell脚本:

"$JAVA" "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" "-Dzookeeper.log.file=${ZOO_LOG_FILE}" \
         -cp "$CLASSPATH" $CLIENT_JVMFLAGS $JVMFLAGS \
         org.apache.zookeeper.ZooKeeperMain "$@"

  我们去Jar包下找到这个类,可以看到org.apache.zookeeper.ZooKeeperMain类下有这么一些方法以及定义的枚举CreateModel。代码如下:

public static void main(String[] args) throws KeeperException, IOException, InterruptedException {
    ZooKeeperMain main = new ZooKeeperMain(args);
    main.run();
}

if (cmd.equals("create") && args.length >= 3) {
    first = 0;
    CreateMode flags = CreateMode.PERSISTENT;
    if (args[1].equals("-e") && args[2].equals("-s") || args[1].equals("-s") && args[2].equals("-e")) {
        first += 2;
        flags = CreateMode.EPHEMERAL_SEQUENTIAL;
    } else if (args[1].equals("-e")) {
        ++first;
        flags = CreateMode.EPHEMERAL;
    } else if (args[1].equals("-s")) {
        ++first;
        flags = CreateMode.PERSISTENT_SEQUENTIAL;
    }

    if (args.length == first + 4) {
        acl = parseACLs(args[first + 3]);
    }

    path = args[first + 1];
    String newPath = this.zk.create(path, args[first + 2].getBytes(), (List)acl, flags);
    System.err.println("Created " + newPath);
}

public enum CreateMode {
    PERSISTENT(0, false, false),
    PERSISTENT_SEQUENTIAL(2, false, true),
    EPHEMERAL(1, true, false),
    EPHEMERAL_SEQUENTIAL(3, true, true);
}

  由上可知,create命令创建的默认是持久无序节点。

  2、znode节点使用示例

  PERSISTENT:持久无序节点,如图所示,我们打开两个客户端,断开创建节点的客户端,节点依旧存在(无法创建相同节点):

        

  PERSISTENT_SEQUENTIAL :持久有序节点,同样的创建命令会创建命名有序的同样开头的节点。

         

  EPHEMERAL :临时节点,创建节点的客户端断开连接后节点消失。

        

  EPHEMERAL_SEQUENTIAL:临时有序节点,客户端断开连接后消失的有序节点

        

  3、Stat数据结构

  Stat中记录了这个 ZNode 的三个数据版本,分别是version(当前ZNode的版本)、cversion(当前ZNode子节点的版本)和 cversion(当前ZNode的ACL版本)。Stat:状态信息、版本、权限相关如下:

  • czxid:节点创建时的zxid
  • mzxid:节点最新一次更新发生时的zxid
  • ctime:节点创建时的时间戳.
  • mtime:节点最新一次更新发生时的时间戳.
  • dataVersion: 节点数据的更新次数.
  • cversion:其子节点的更新次数
  • aclVersion:节点ACL(授权信息)的更新次数.
  • ephemeralOwner:如果该节点为ephemeral节点, ephemeralOwner值表示与该节点绑定的session id. 如果该节点不是ephemeral节点, ephemeralOwner值为0. 至于什么是ephemeral节点
  • dataLength:节点数据的字节数.
  • numChildren:子节点个数.

  如代码所示,当我们调用get的时候,会返回如下信息:

private static void printStat(Stat stat) {
    System.err.println("cZxid = 0x" + Long.toHexString(stat.getCzxid()));
    System.err.println("ctime = " + (new Date(stat.getCtime())).toString());
    System.err.println("mZxid = 0x" + Long.toHexString(stat.getMzxid()));
    System.err.println("mtime = " + (new Date(stat.getMtime())).toString());
    System.err.println("pZxid = 0x" + Long.toHexString(stat.getPzxid()));
    System.err.println("cversion = " + stat.getCversion());
    System.err.println("dataVersion = " + stat.getVersion());
    System.err.println("aclVersion = " + stat.getAversion());
    System.err.println("ephemeralOwner = 0x" + Long.toHexString(stat.getEphemeralOwner()));
    System.err.println("dataLength = " + stat.getDataLength());
    System.err.println("numChildren = " + stat.getNumChildren());
}

  实际返回结果如下:

[zk: localhost:2181(CONNECTED) 33] get -s /test
2020
cZxid = 0x2c
ctime = Thu Apr 16 15:20:39 CST 2020
mZxid = 0x2c
mtime = Thu Apr 16 15:20:39 CST 2020
pZxid = 0x2c
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 4
numChildren = 0
[zk: localhost:2181(CONNECTED) 34] set /test 2019
[zk: localhost:2181(CONNECTED) 35] get -s /test
2019
cZxid = 0x2c
ctime = Thu Apr 16 15:20:39 CST 2020
mZxid = 0x2d
mtime = Thu Apr 16 15:21:56 CST 2020
pZxid = 0x2c
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 4
numChildren = 0

  4、Session会话

  客户端来创建一个和zk服务端连接的句柄。连接状态:CONNECTING、CONNECTED、CLOSED。

  5、Watcher

  Watcher(事件监听器),是Zookeeper中的一个很重要的特性。Zookeeper允许用户在指定节点上注册一些Watcher,并且在一些特定事件触发的时候,ZooKeeper服务端会将事件通知到感兴趣的客户端上去,该机制是Zookeeper实现分布式协调服务的重要特性。

            

   代码如下所示 org.apache.zookeeper.Watcher :

public static enum EventType {        // 事件类型:(znode节点相关的)
    None(-1),
    NodeCreated(1),        //节点创建
    NodeDeleted(2),        //节点删除
    NodeDataChanged(3),        //节点的数据变更
    NodeChildrenChanged(4);        //子节点下的数据变更
}

public static enum KeeperState {        //  状态类型:(是跟客户端实例相关的)
    /** @deprecated */
    @Deprecated
    Unknown(-1),
    Disconnected(0),        //连接失败
    /** @deprecated */
    @Deprecated
    NoSyncConnected(1),
    SyncConnected(3),        //连接成功
    AuthFailed(4),            //认证失败
    ConnectedReadOnly(5),
    SaslAuthenticated(6),
    Expired(-112);            //会话过期
}

  测试结果如下所示:

        

  我们使用ZooKeeper原生客户端,实现Watcher接口,访问测试如下:

public class ZookeeperWatcher  implements Watcher  {
   private final String connectString= "localhost:2181";
   private ZooKeeper zookeeper;

   public ZookeeperWatcher() {
      try {
         this.zookeeper = new ZooKeeper(connectString,5000,this);
      } catch (Exception e) {
         e.printStackTrace();
      }
   }

   /***
    * 创建持久节点
    * @param path
    * @param data
    * @return
    */
   public String createPersistent(String path,String data){
      try {
         return  zookeeper.create(path, data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
      } catch (KeeperException e) {
         e.printStackTrace();
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      return  null;
   }

   /***
    * 创建临时节点
    * @param path
    * @param data
    * @return
    */
   public String createEphemeral(String path,String data){
      try {
         return  zookeeper.create(path, data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
      } catch (KeeperException e) {
         e.printStackTrace();
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      return  null;
   }

   /***
    * 更新信息
    * @param path
    * @return
    * @throws KeeperException
    * @throws InterruptedException
    */
   public String getData(String path,boolean watcher) throws KeeperException, InterruptedException {
      byte data[] = zookeeper.getData(path,watcher,null);
      data = (data == null)? "null".getBytes() : data;
      return new String(data);
   }

   /***
    * 更新信息
    * @param path
    * @param data
    * @return
    * @throws KeeperException
    * @throws InterruptedException
    */
   public Stat setData(String path, String data) throws KeeperException, InterruptedException {
      return zookeeper.setData(path, data.getBytes(), -1);
   }

   /***
    * 是否存在
    * @param path
    * @return
    * @throws KeeperException
    * @throws InterruptedException
    */
   public Stat exists(String path,boolean watcher) throws KeeperException, InterruptedException {
      return zookeeper.exists(path,watcher);

   }

   /***
    * 删除
    * @param path
    * @return
    * @throws KeeperException
    * @throws InterruptedException
    */
   public void delete(String path) throws KeeperException, InterruptedException {
      zookeeper.delete(path,-1);
   }
/*** * 删除 * @param path * @return * @throws KeeperException * @throws InterruptedException */ public void deleteRecursive(String path) throws KeeperException, InterruptedException { ZKUtil.deleteRecursive(zookeeper, path); } public void close() throws InterruptedException { zookeeper.close(); } @Override public void process(WatchedEvent event) { // 连接状态 Event.KeeperState keeperState = event.getState(); // 事件类型 Event.EventType eventType = event.getType(); // 受影响的path String path = event.getPath(); //step 1: // System.out.println("连接状态:"+keeperState+",事件类型:"+eventType+",受影响的path:"+path); //step:2 try { if(null!=this.exists("/path",true)) { System.out.println("内容:"+ this.getData("/path", true)); } System.out.println("连接状态:"+keeperState+",事件类型:"+eventType+",受影响的path:"+path); } catch (Exception e) { e.printStackTrace(); } System.out.println("--------------------"); } }
public class WatcherTest {

   public static void main(String[] args)  {
      ZookeeperWatcher zookeeperWatcher=new ZookeeperWatcher();
      try {
         zookeeperWatcher.delete("/path");
      } catch (Exception e) {

      }
      zookeeperWatcher.createPersistent("/path","abc");
      try {
         Thread.sleep(200000);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }

   }
}

  运行代码,然后我们手动修改值,可以看到,控制台接收到了节点的修改,测试截图如下所示:

    

  6、ACL

  ACL(Access Control List)就是对zookeeper设置ACL属性,用于控制访问权限。ACL的格式由<schema>:<id>:<acl>三段组成。

  • schema:可以取下列值:world, auth, digest, host/ip
  • id: 标识身份,值依赖于schema做解析。
  • acl:就是权限:cdwra分别表示create, delete,write,read, admin
  注意:zookeeper对权限的控制是znode级别的,不具有继承性,即子节点不继承父节点的权限。

  6.1、schema:world

  这是默认方式,表示没有认证。当创建一个新的节点(znode),而又没有设置任何权限时,就是这个值,相当于全世界都能访问。如:

[zk: localhost:2181(CONNECTED) 17] create /acltest 2020
Created /acltest
[zk: localhost:2181(CONNECTED) 18] getAcl /acltest
'world,'anyone
: cdrwa

  如果要手工设置这个属性,那么此时的id域只允许一个值,即anyone,格式如下:

setAcl /newznode world:anyone:crdwa

  6.2、schema:auth

  这种授权不针对任何特点ID,而是对所有已经添加认证的用户,换句话说,就是对所有已经通过认证的用户授权(cli中可以通过addauth digest user:pwd 来添加当前上下文中的授权用户),如下所示:

[zk: localhost:2181(CONNECTED) 19] addauth digest tom1:tom1
[zk: localhost:2181(CONNECTED) 20] addauth digest tom2:tom2
[zk: localhost:2181(CONNECTED) 21] create /authtest 2020
Created /authtest
[zk: localhost:2181(CONNECTED) 22] setAcl /authtest auth:tom2:crdwa
[zk: localhost:2181(CONNECTED) 23] getAcl /authtest
'digest,'tom2:2iJM00A7+qkeKdEXt8Bhgq+IACw=
: cdrwa
'digest,'tom1:ben+k/3JomjGj4mfd4fYsfM6p0A=
: cdrwa

  zkCli的命令addauth digest user:pwd是用来添加当前上下文中的认证用户的,关于addauth有以下几点注意:

  • auth的id值是无效的,表示给所有认证用户设置acl权限。
  • 当使用addauth命令添加多个认证用户后,再用auth setAcl来设置acl时,那么所有之前addauth的用户都被会加入到acl中。
  • 如果在当前会话中还没有认证过的用户就使用auth setAcl来设置acl权限时会失败。
    [zk: localhost:2181(CONNECTED) 1] setAcl /test auth::crdwa
    Acl is not valid : /test
  • 在auth setAcl之后再使用addauth添加的认证用户是没有acl权限的,必须重新执行auth setAcl来设置权限。
  • 使用addauth命令(addauth digest <username>:<password>)添加的认证用户只在当前会话(session)有效,如果此时在另外一个会话中,不添加对应的认证用户,那么就没有相应访问权限的(如下图所示),而且如果再使用auth setAcl来设置acl权限,则会覆盖之前的acl权限信息,而且只会针对当前会话中的认证用户来设置acl权限。

   

  所以这种授权方式更倾向于用作测试开发环境,而不是产品环境中。

  6.3、schema:digest

  这就是最普通的用户名:密码的验证方式,在一般业务系统中最常用。格式如下:

setAcl <path> digest:<user>:<password(密文)>:<acl>

  和schema auth相比,有两点不同:

  • 第一不需要预先添加认证用户(但是在zkCli访问的时候,肯定还是要添加认证用户的)。
  • 第二密码是经过sha1及base64处理的密文。

  密码常用2种方式生成,一种为Shell,另一种为,如Shell方式:

echo -n <user>:<password> | openssl dgst -binary -sha1 | openssl base64

  测试如下:

192:bin houjing$ echo -n root:root123 | openssl dgst -binary -sha1 | openssl base64
L+fIVcOMuQZbEws2hLQZUfW3awI=

  另一种是使用zookeeper的库文件生成,如下所示:

$ java -cp /Users/houjing/.m2/repository/org/apache/zookeeper/zookeeper/3.4.9/zookeeper-3.4.9.jar:/Volumes/work/apache-zookeeper-3.6.0/lib/slf4j-api-1.7.25.jar   org.apache.zookeeper.server.auth.DigestAuthenticationProvider   root:root123
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
root:root123->root:L+fIVcOMuQZbEws2hLQZUfW3awI=

  可以看到两种方式得到的密文是一样的。

setAcl /digesttest digest:root:L+fIVcOMuQZbEws2hLQZUfW3awI=:rwdca

  注意,只有通过zkCli.sh设置digest的ACL时id才需要密文,而通过zookeeper的客户端设置digest的ACL时对应的auth数据是明文。和auth比较,digest有如下特性:

  • setAcl不需要事先添加认证用户。
  • 授权是针对单个特定用户。
  • setAcl使用的密码不是明文,是sha1摘要值,无法反推出用户密码内容。
  如在代码访问中则使用:
public class ZookeeperAcl {
   private final String connectString= "localhost:2181";
   private ZooKeeper zookeeper;
   /** 认证类型 */
   final static String scheme = "digest";
   final static String auth="root:root123";

   /****
    * 区分下auth和非auth
    */
   public ZookeeperAcl() {
      try {
         this.zookeeper = new ZooKeeper(connectString,5000,null);
         zookeeper.addAuthInfo(scheme,auth.getBytes());
      } catch (Exception e) {
         e.printStackTrace();
      }
   }

   public ZookeeperAcl(boolean flag) {
      try {
         this.zookeeper =new ZooKeeper(connectString,5000,null);
      } catch (Exception e) {
         e.printStackTrace();
      }
   }

   public Stat setData(String path, String data) throws KeeperException, InterruptedException {
      return zookeeper.setData(path, data.getBytes(), -1);
   }public void close() throws InterruptedException {
      zookeeper.close();
   }
}

public class AclTest {

   public static void main(String[] args) throws KeeperException, InterruptedException {
      //授权的  zk客户端不能操作
       ZookeeperAcl zookeeperAcl=new ZookeeperAcl();
       zookeeperAcl.setData("/digesttest","acl");
   }
}

  6.4、schema:host/ip

  就是客户机地址,或者是主机名、或者是IP地址。主机名可以是单个主机名,也可以是域名。IP可以是单个IP地址,也可以是IP地址段,比如ip:192.168.1.0/16。

  6.5、super超级用户

  设置一个超级用户,这个超级用户的设置必须在zookeeper内部,zookeeper启动之前设置好。在这种scheme情况下,超级用户具有超级权限,可以做任何事情(cdrwa),不需要授权。那么如何设置超级用户呢?
  首先,设置zookeeper环境变量SERVER_JVMFLAGS:
export SERVER_JVMFLAGS="-Dzookeeper.DigestAuthenticationProvider.superDigest=root:qiTlqPLK7XM2ht3HMn02qRpkKIE="

  重启zookeeper并创建/test节点,我们测试时设置acl为jerry用户。

[zk: localhost:2181(CONNECTED) 0] create /test 'test'
Created /test
[zk: localhost:2181(CONNECTED) 1] setAcl /test digest:jerry:dJJW56m9FIOfUDDHVC5wVWNsFEo=:rwdca
[zk: localhost:2181(CONNECTED) 2] getAcl /test
'digest,'jerry:dJJW56m9FIOfUDDHVC5wVWNsFEo=
: cdrwa

  添加认证用户tom,这时失败,因为tom用户没有权限。

[zk: localhost:2181(CONNECTED) 3] addauth digest tom:tom 
[zk: localhost:2181(CONNECTED) 4] get /test
Authentication is not valid : /test

  然后添加认证用户root,并再次访问节点/test:

[zk: localhost:2181(CONNECTED) 6] addauth digest root:root
[zk: localhost:2181(CONNECTED) 6] get /test
test
...

  由此可见,虽然root也没有在/test的acl列表里面(只有jerry),但是也能访问,因为root在zookeeper集群里面被配置成了超级用户。

  关于ACL支持的权限cdrwa

  • CREATE: 能创建子节点
  • DELETE: 能删除子节点
  • READ:能获取节点数据和列出其子节点
  • WRITE: 能设置节点数据
  • ADMIN: 能设置权限

二、zkClient客户端 

  ZkClient是由Datameer的工程师开发的开源客户端,对Zookeeper的原生API进行了包装,实现了超时重连、Watcher反复注册等功能。

  github源代码地址:https://github.com/sgroschupf/zkclient

  1、maven依赖

<dependency>
     <groupId>org.apache.zookeeper</groupId>
     <artifactId>zookeeper</artifactId>
     <version>3.4.9</version>
 </dependency>
<dependency>
    <groupId>com.101tec</groupId>
    <artifactId>zkclient</artifactId>
    <version>0.10</version>
</dependency>

  2、zkclient初始化

public ZkClient(String serverstring)
public ZkClient(String zkServers, int connectionTimeout)
public ZkClient(String zkServers, int sessionTimeout, int connectionTimeout)
public ZkClient(String zkServers, int sessionTimeout, int connectionTimeout, ZkSerializer zkSerializer)
public ZkClient(final String zkServers, final int sessionTimeout, final int connectionTimeout, final ZkSerializer zkSerializer, final long operationRetryTimeout)
public ZkClient(IZkConnection connection)
public ZkClient(IZkConnection connection, int connectionTimeout)
public ZkClient(IZkConnection zkConnection, int connectionTimeout, ZkSerializer zkSerializer)
public ZkClient(final IZkConnection zkConnection, final int connectionTimeout, final ZkSerializer zkSerializer, final long operationRetryTimeout)

  3、CRUD操作

//    创建节点
public void createPersistent(String path)
public void createPersistent(String path, boolean createParents)
public void createPersistent(String path, boolean createParents, List<ACL> acl)
public void createPersistent(String path, Object data)
public void createPersistent(String path, Object data, List<ACL> acl)
public String createPersistentSequential(String path, Object data)
public String createPersistentSequential(String path, Object data, List<ACL> acl) 
public void createEphemeral(final String path)
public void createEphemeral(final String path, final List<ACL> acl)
public String create(final String path, Object data, final CreateMode mode)
public String create(final String path, Object data, final List<ACL> acl, final CreateMode mode) 
public void createEphemeral(final String path, final Object data)
public void createEphemeral(final String path, final Object data, final List<ACL> acl)
public String createEphemeralSequential(final String path, final Object data)
public String createEphemeralSequential(final String path, final Object data, final List<ACL> acl)

//    删除节点:
public boolean delete(final String path)
public boolean delete(final String path, final int version)
public boolean deleteRecursive(String path)

//    读取列表:
public List<String> getChildren(String path)

//    获取节点内容:
public <T extends Object> T readData(String path)
public <T extends Object> T readData(String path, boolean returnNullIfPathNotExists)
public <T extends Object> T readData(String path, Stat stat)

//    更新内容:
public void writeData(String path, Object object)
public void writeData(final String path, Object datat, final int expectedVersion)
public Stat writeDataReturnStat(final String path, Object datat, final int expectedVersion)

//    监测节点是否存在
protected boolean exists(final String path, final boolean watch)

  4、注册监听

     

   其中ZkClient还提供了一个unsubscribeAll方法,来解除所有监听。 

  5、测试代码

public class ZkClientWatcher<T> {
   ZkClient zkClient;
   private final String connectString= "localhost:2181";
   public ZkClientWatcher() {
      this.zkClient = new ZkClient(connectString,5000,5000,new MyZkSerializer());
   }

   public  T readData(String path){
      return zkClient.readData(path);

   }

   public List<String> getChildren(String path){
      return zkClient.getChildren(path);

   }

   public  void writeData(String path,Object object){
      zkClient.writeData(path,object);

   }

   public  void deleteRecursive(String path){
      zkClient.deleteRecursive(path);

   }

   public void createPersistent(String path,Object data){
      zkClient.createPersistent(path,data);
   }

   public void lister(String path){
      //对父节点添加监听子节点变化。
      zkClient.subscribeDataChanges(path, new IZkDataListener() {
         @Override
         public void handleDataChange(String dataPath, Object data) throws Exception {
            System.out.printf("变更的节点为:%s,%s", dataPath,data );
         }
         @Override
         public void handleDataDeleted(String dataPath) throws Exception {
            System.out.printf("删除的节点为:%s", dataPath );
         }
      });
      //对父节点添加监听子节点变化。
      zkClient.subscribeChildChanges(path, new IZkChildListener() {
         @Override
         public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
            System.out.println("parentPath: " + parentPath+",currentChilds:"+currentChilds);
         }
      });
      //对父节点添加监听子节点变化。
      zkClient.subscribeStateChanges(new IZkStateListener() {
         @Override
         public void handleStateChanged(Watcher.Event.KeeperState state) throws Exception {
            if(state== Watcher.Event.KeeperState.SyncConnected){
               //当我重新启动后start,监听触发
               System.out.println("连接成功");
            }else if(state== Watcher.Event.KeeperState.Disconnected){
               System.out.println("连接断开");//当我在服务端将zk服务stop时,监听触发
            }else
               System.out.println("其他状态"+state);
         }
         @Override
         public void handleNewSession() throws Exception {
            System.out.println("重建session");
         }
         @Override
         public void handleSessionEstablishmentError(Throwable error) throws Exception {
         }
      });

   }
}

public class MyZkSerializer implements ZkSerializer
{
    public Object deserialize(byte[] bytes) throws ZkMarshallingError
    {
        try {
            return new String(bytes, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return  null;
    }

    public byte[] serialize(Object obj) throws ZkMarshallingError
    {
        try {
            return String.valueOf(obj).getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return  null;
    }
}

public class User implements Serializable {

    private Integer userid;
    private String userName;

    public Integer getUserid() {
        return userid;
    }

    public void setUserid(Integer userid) {
        this.userid = userid;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    @Override
    public String toString() {
        return "User{" +
                "userid=" + userid +
                ", userName='" + userName + '\'' +
                '}';
    }
}

public class WatcherTest {

   public static void main(String[] args) throws InterruptedException {
      ZkClientWatcher zkClientWatcher=new ZkClientWatcher();
      String path="/monkey";
      zkClientWatcher.deleteRecursive(path);
      zkClientWatcher.lister(path);
      User user = new User();
      user.setUserid(1);
      user.setUserName("源码");
      zkClientWatcher.createPersistent(path,user);
      Thread.sleep(2000);
      zkClientWatcher.writeData(path,"abc");
      Thread.sleep(Integer.MAX_VALUE);
   }
}

四、Curator客户端

  Curator 是 Netflix 公司开源的一个 Zookeeper 客户端,与 Zookeeper 提供的原生客户端相比,Curator 的抽象层次更高,简化了 Zookeeper 客户端的开发量。现在已是 apache 的顶级开源框架,Fluent 编程风格的实现。

  官网:http://curator.apache.org

  1、Maven 依赖

<dependency>
  <groupId>org.apache.curator</groupId>    <!--基础框架-->
  <artifactId>curator-framework</artifactId>
  <version></version>
</dependency>

<dependency>
  <groupId>org.apache.curator</groupId> 
  <artifactId>curator-recipes</artifactId> <!--功能 jar 分布式锁、队列等-->
  <version></version> </dependency> <dependency>   <!--客户端重试策略-->   <groupId>org.apache.curator</groupId>   <artifactId>curator-client</artifactId>   <version></version> </dependency>

  2、客户端初始化

  Curator 框架提供了一种流式接口,通过 builder 串起来,传递参数都是调方法。

  Curator 框架通过 CuratorFrameworkFactory 以工厂模式和 builder 模式创建 CuratorFramework 实例。 CuratorFramework 实例都是线程安全的,你应该在你的应用中共享同一个。

   工厂方法 newClient()提供了一个简单方式创建实例。 而 Builder 提供了更多的参数控制。一旦你创建了一个 CuratorFramework 实例,你必须调用它的 start()启动,在应用退出时调

用 close()方法关闭.

   创建 Curator 连接实例:

String address = "localhost:2181";
CuratorFramework client = CuratorFrameworkFactory.newClient(address, new ExponentialBackoffRetry(1000, 3));  //重试机制
client.start();

  注意:一个 Zookeeper 集群只需要构造一个 CuratorFramework 实例对象即可。CuratorFramework 使用之前必须先调用:

client.start();

  3、CuratorFramework 提供的方法

         

  4、事件类型以及事件的方法

         

   4、监听器

  Curator 提供了三种 Watcher(Cache)来监听结点的变化:

  • Path Cache:监视一个路径下子结点的创建、删除,以及结点数据的更新。产生的事件会传递给注册的 PathChildrenCacheListener。
  • Node Cache:监视一个结点的创建、更新、删除,并将结点的数据缓存在本地。
  • Tree Cache:Path Cache 和 Node Cache 的“合体”,监视路径下的创建、更新、删除事件,并缓存路径下所有子结点的数据。

  5、重试机制

  Curator 内部实现的几种重试策略:

  • ExponentialBackoffRetry:重试指定的次数, 且每一次重试之间停顿的时间逐渐增加.
  • RetryNTimes:指定最大重试次数的重试策略
  • RetryOneTime:仅重试一次
  • RetryUntilElapsed:一直重试直到达到规定的时间

          

  6、分布式锁

  Curator客户端还提供了多种分布式锁,用分布式锁或者原子操作、队列等功能需引入。需要引入包:

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>${version}</version>
</dependency>

        

  7、代码示例

public class CuratorLockTest implements  Runnable{

    final  static CuratorFramework client= CuratorFrameworkFactory.builder().connectString("localhost:2181")
                              .retryPolicy(new ExponentialBackoffRetry(100,1)).build(); static int i=0; final InterProcessMutex lock=new InterProcessMutex(client,"/lock"); public static void main(String[] args) throws InterruptedException { client.start(); CuratorLockTest lockTest2=new CuratorLockTest(); Thread t1= new Thread(lockTest2); Thread t2= new Thread(lockTest2); t1.start();t2.start(); t1.join();t2.join(); System.out.println(i); } @Override public void run() { try { lock.acquire(); for(int j=0;j<10000;j++){ i++; } lock.release(); } catch (Exception e) { e.printStackTrace(); } } }
public class CuratorCrud {
   private final String connectString = "localhost:2181";
   CuratorFramework cf ;
   public CuratorCrud() {
      //1 重试策略:初试时间为1s 重试10次
      RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 10);
      //2 通过工厂创建连接
      cf = CuratorFrameworkFactory.builder()
              .connectString(connectString)
              .sessionTimeoutMs(5000)
              .retryPolicy(retryPolicy)
              .build();
      //3 开启连接
      cf.start();
   }

   public String createPersistent(String path,String  data){
      try {
         cf.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath(path,data.getBytes());
      } catch (Exception e) {
         e.printStackTrace();
      }
      return  null;
   }

   public String getData(String path){
      try {
         return new String(cf.getData().forPath(path));
      } catch (Exception e) {
         e.printStackTrace();
      }
      return  null;
   }


   public void delete(String path){
      try {
         cf.delete().guaranteed().deletingChildrenIfNeeded().forPath(path);
      } catch (Exception e) {
         e.printStackTrace();
      }
   }

   public void setData(String path,String  data){
      try {
         cf.setData().forPath(path,data.getBytes());
      } catch (Exception e) {
         e.printStackTrace();
      }
   }

   public static void main(String[] args) {
      CuratorCrud curatorCrud=new CuratorCrud();
      curatorCrud.createPersistent("/ccc","abc");
   }
}

五、注册中心模拟

  如下所示代码,模拟了一个服务注册和订阅:

  先看结构图如下:

        

  注册中心:

/***
 * 注册中心 对外提供注册服务的
 */
public class ZookeeperServerResiter {
    private ZooKeeper zk;

    public static final String root = "/bat";
    private static final String host = "localhost:2181";

    public ZooKeeper getConnection(Watcher watch) throws IOException {
        zk = new ZooKeeper(host, 500, watch);
        return zk;
    }
}

public class StatDto implements Serializable {

    private String ip;
    private String name;
    private String port;
    private Integer num;//连接数
    private String status;
    private String node;
    private String client;

    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPort() {
        return port;
    }

    public void setPort(String port) {
        this.port = port;
    }

    public Integer getNum() {
        return num;
    }

    public void setNum(Integer num) {
        this.num = num;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public String getNode() {
        return node;
    }

    public void setNode(String node) {
        this.node = node;
    }

    public String getClient() {
        return client;
    }

    public void setClient(String client) {
        this.client = client;
    }
}

public class ServerStatus {
    //wait:无消费者,run:运行中,stop:禁用中

    public static final String run="run";
    public static final String wait="wait";
    public static final String stop="stop";
}

  管理后台:

@Controller
@RequestMapping("/admin")
public class AdminController {

    ZookeeperServerResiter bitZook = new ZookeeperServerResiter();
    private ZooKeeper zook;

    @RequestMapping("/list")
    public String getList(HttpServletRequest request) throws Exception {
        zook = bitZook.getConnection(new Watcher() {
            @Override
            public void process(WatchedEvent event) {
            }
        });
        List<String> data = zook.getChildren(ZookeeperServerResiter.root, true);
        List<StatDto> serverList = new ArrayList<StatDto>();
        for (String server : data) {
            byte[] bytes = zook.getData(ZookeeperServerResiter.root + "/" + server,
                    false, null);
            String datas = new String(bytes);
            StatDto dto = JSON.parseObject(datas, StatDto.class);
            serverList.add(dto);
        }
        request.setAttribute("serverList", serverList);
        return "admin";

    }

    @RequestMapping("/stop")
    public String stop(HttpServletRequest request) throws Exception {
        zook = bitZook.getConnection(new Watcher() {
            @Override
            public void process(WatchedEvent event) {
            }
        });
        String server = request.getParameter("server");
        byte[] bytes = zook.getData(ZookeeperServerResiter.root + "/" + server,
                false, null);
        String datas = new String(bytes);
        StatDto dto = JSON.parseObject(datas, StatDto.class);
        dto.setStatus(ServerStatus.stop);
        zook.setData(ZookeeperServerResiter.root + "/" + server,
                JSON.toJSONString(dto).getBytes(), -1);
        
        return "redirect:/admin/list";
    }
    
    @RequestMapping("/run")
    public String run(HttpServletRequest request) throws Exception {
        zook = bitZook.getConnection(new Watcher() {
            @Override
            public void process(WatchedEvent event) {
            }
        });
        String server = request.getParameter("server");
        byte[] bytes = zook.getData(ZookeeperServerResiter.root + "/" + server,
                false, null);
        String datas = new String(bytes);
        StatDto dto = JSON.parseObject(datas, StatDto.class);
        dto.setStatus(ServerStatus.run);
        zook.setData(ZookeeperServerResiter.root + "/" + server,
                JSON.toJSONString(dto).getBytes(), -1);
        return "redirect:/admin/list";
    }

    @RequestMapping("/delete")
    public String delete(HttpServletRequest request) throws Exception {
        zook = bitZook.getConnection(new Watcher() {
            @Override
            public void process(WatchedEvent event) {
            }
        });
        String server = request.getParameter("server");
        byte[] bytes = zook.getData(ZookeeperServerResiter.root + "/" + server,
                false, null);
        String datas = new String(bytes);
        StatDto dto = JSON.parseObject(datas, StatDto.class);
        dto.setStatus(ServerStatus.stop);
        zook.delete(ZookeeperServerResiter.root + "/" + server, -1);
        return "redirect:/admin/list";
    }
}

  consumer:

/***
 * dubbo的 consumer
 */
public class ConsumerServers implements Watcher {
    Logger logger = LoggerFactory.getLogger(ConsumerServers.class);
    ZookeeperServerResiter bitZook = new ZookeeperServerResiter();
    private ZooKeeper zook;
    private String clientName;

    public StatDto useServer(List<String> data, int dom, int i)
            throws Exception {
        if (data.size() == i) {
            throw new Exception("没有可用的服务");
        }
        String node = data.get(dom);
        byte[] bytes = zook.getData(ZookeeperServerResiter.root + "/" + node,
                true, null);
        String datas = new String(bytes);
        StatDto dto = JSON.parseObject(datas, StatDto.class);
        if (ServerStatus.stop.equals(dto.getStatus())) {
            i++;
            useServer(data, dom, i);
        }
        dto.setNode(node);
        return dto;
    }

    /**
     * 客户端订阅
     * @param clientName
     * @throws Exception
     */
    public void subscribe(String clientName) throws Exception {
        this.clientName = clientName;
        zook = bitZook.getConnection(this);
        List<String> data = zook.getChildren(ZookeeperServerResiter.root, true);
        if (data.isEmpty()) {
            throw new Exception("没有可用的服务");
        }
        //随机
        int dom = new SecureRandom().nextInt(data.size());
        StatDto dto = useServer(data, dom, 0);
        dto.setNum(dto.getNum() + 1);
        dto.setName(dto.getName());
        dto.setClient(dto.getClient() == null ? clientName : dto.getClient() + "," + clientName);
        dto.setStatus(ServerStatus.run);
        zook.setData(ZookeeperServerResiter.root + "/" + dto.getNode(), JSON
                .toJSONString(dto).getBytes(), -1);
    }

    public void call() throws InterruptedException {
        System.out.println("客户端开启,建立netty连接");
    }

    @Override
    public void process(WatchedEvent event) {
        try {
            if (event.getType() == EventType.NodeChildrenChanged) {
                System.out.println("服务器发生改变,重新订阅");

            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws Exception {
        ConsumerServers client = new ConsumerServers();
        client.subscribe(args[0]);
        try {
            client.call();
            Thread.sleep(Long.MAX_VALUE);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

  provider:

public class ProviderServers implements Watcher {

    ZookeeperServerResiter bitZookeeperServer=new ZookeeperServerResiter();

    /***
     * 注册
     * @param serverName
     * @throws IOException
     * @throws KeeperException
     * @throws InterruptedException
     */
    void register(String serverName) throws IOException, KeeperException, InterruptedException {
        ZooKeeper zook= bitZookeeperServer.getConnection(this);
        String node=  zook.create(ZookeeperServerResiter.root+"/server",serverName.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        System.out.println("创建成功过"+serverName+",节点:"+node);
    }

        @Override
        public void process(WatchedEvent watchedEvent) {
    }

    public static void main(String[] args) throws InterruptedException, IOException, KeeperException {
        ProviderServers server=new ProviderServers();
        StatDto stat=new StatDto();
        int i=0;
        stat.setIp(args[i++]);
        stat.setPort(args[i++]);
        stat.setName(args[i++]);
        stat.setNum(0);
        stat.setStatus(ServerStatus.wait);
        server.register(JSON.toJSONString(stat));
        Thread.sleep(Long.MAX_VALUE);
    }
}

  测试结果如下:

    

posted on 2020-04-16 20:19  kosamino  阅读(722)  评论(0编辑  收藏  举报