ZooKeeper典型应用场景总结

ZooKeeper典型应用场景总结

1.数据发布/订阅

  数据发布/订阅系统,即配置中心,是发布者将数据发布到ZooKeeper的一个或一系列节点上,供订阅者进行数据订阅,进而达到动态获取数据的目的,实现配置信息的集中式管理和数据的动态更新。
  Zookeeper实现数据发布/订阅系统采用这样一种设计模式:客户端向服务端注册自己需要关注的节点,当该节点发生数据变更时,服务端就会向相应的客户端发送Watcher事件通知,客户端接收到这个消息之后,需要主动到服务端获取最新的数据。

在这里插入图片描述

  知道了原理,那接下来用zookeeper实现一个简单的发布/订阅系统。

  Publish的代码如下:

public class Publish implements Watcher {
    private static CountDownLatch countDownLatch = new CountDownLatch(1);
    private static Stat stat = new Stat();
    private static ZooKeeper zk = null;

    public static void main(String[] args) {
        try {
            String path1 = "/number";
            String path2 = "/character";
            zk = new ZooKeeper("192.168.1.8:2181", 5000, new Publish());
            countDownLatch.await();
            System.out.println("zookeeper connection");

			//创建/number节点
            zk.create(path1, "0".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            //创建/character节点
            zk.create(path2, "a".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            System.out.println("init number :  " + new String(zk.getData(path1, true, stat)));
            System.out.println("init character :  " + new String(zk.getData(path2, true, stat)));
            int i = 0;
            while (true) {
                System.out.println("publish new number:" + i);
                //更新/number节点的数据
                zk.setData(path1, String.valueOf(i).getBytes(), -1);
                char c = (char) (i + 97);
                System.out.println("publish new character:" + c);
                //更新/character节点的数据
                zk.setData(path2, ("" + c).getBytes(), -1);
                Thread.sleep(5000);
                i++;
            }
        } catch (Exception e) {
        }

    }

    public void process(WatchedEvent event) {
        if (Event.KeeperState.SyncConnected == event.getState()) {
            System.out.println("receive watched event:" + event);
            System.out.println(event.getState());
            countDownLatch.countDown();
        }
    }
}

  Subscribe的代码如下:

//订阅number节点的客户端
public class Subscribe_num implements Watcher {
    private static CountDownLatch countDownLatch = new CountDownLatch(1);
    private static Stat stat = new Stat();
    private static ZooKeeper zk = null;

    public static void main(String[] args) {
        try {
            String path = "/number";
            zk = new ZooKeeper("192.168.1.8:2181", 5000, new Subscribe_num());
            countDownLatch.await();
            System.out.println("zookeeper connection");
            System.out.println("init number : number is " + new String(zk.getData(path, true, stat)));
            while (true) {
                Thread.sleep(Integer.MAX_VALUE);
            }
        } catch (Exception e) {
        }
    }

    public void process(WatchedEvent event) {
        if (Event.KeeperState.SyncConnected == event.getState()) {
            if (Event.EventType.None == event.getType() && event.getPath() == null) {
                countDownLatch.countDown();
            } else if (event.getType() == Event.EventType.NodeDataChanged) {
            	//节点数据发生变更时,去重新获取更新后的数据
                try {
                    System.out.println("path:" + event.getPath() + "\tdata has changed.\t new character :" + new String(zk.getData(event.getPath(), true, stat)));
                } catch (Exception e) {
                }
            }
        }
    }
}
//订阅/character节点的客户端
public class Subscribe_char implements Watcher {
    private static CountDownLatch countDownLatch = new CountDownLatch(1);
    private static Stat stat = new Stat();
    private static ZooKeeper zk = null;

    public static void main(String[] args) {
        try {
            String path = "/character";
            zk = new ZooKeeper("192.168.1.8:2181", 5000, new Subscribe_char());
            countDownLatch.await();
            System.out.println("zookeeper connection");
            System.out.println("init character : character is " + new String((zk.getData(path, true, stat))));
            while (true) {
                Thread.sleep(Integer.MAX_VALUE);
            }
        } catch (Exception e) {
        }
    }

    public void process(WatchedEvent event) {
        if (Event.KeeperState.SyncConnected == event.getState()) {
            if (Event.EventType.None == event.getType() && event.getPath() == null) {
                countDownLatch.countDown();
            } else if (event.getType() == Event.EventType.NodeDataChanged) {
            	//节点数据发生变化时,重新去获取更新后的数据
                try {
                    System.out.println("path:" + event.getPath() + "\tdata has changed.\t new character :" + new String(zk.getData(event.getPath(), true, stat)));
                } catch (Exception e) {
                }
            }
        }
    }
}

  Publish的运行结果如下:
在这里插入图片描述
  Subscribe_num的运行结果如下:

在这里插入图片描述
  Subscribe_character的运行结果如下:
在这里插入图片描述

2.DNS服务

  DNS是域名系统(Domain Name System)的缩写,可以将DNS系统看作是一个超大规模的分布式映射表,用于将域名和IP地址进行一一映射,方便人们通过域名来访问互联网站点。
  用ZooKeeper可以实现一种动态的DNS服务,具体如下。

(1)域名配置

  对域名的配置就相当于在zookeeper上一级一级增加节点。例如:/DDNS/app1/server.app1.company1.com。每个应用都可以创建一个属于自己的数据节点作为域名配置的根节点,例如/DDNS/app1,在这个节点上,每个应用都可以将自己的域名配置上去。
在这里插入图片描述

(2)域名解析

  传统的DNS解析中,不需要关心域名的解析过程,所有这些工作都交由操作系统的域名和IP地址映射机制。在DDNS中,域名的解析过程都是由每一个应用自己负责。
  应用首先从域名节点中获取一份IP地址和端口的配置,进行自行解析。同时,每个应用还会从域名节点上注册一个数据变更Watcher监听,以便及时收到域名变更的通知。

(3)域名变更

  当域名对应的IP地址或是端口变更,这个时候就需要进行域名变更操作。在DDNS中,只需要对指定的域名节点进行更新操作,zookeeper就会向订阅它的客户端发送这个事件通知,应用在接收到这个事件通知后,会再次进行域名配置的获取。

  接下来将上述这一DDNS系统简单的实现一下。

Server的代码如下:

public class Server implements Watcher {
    private static CountDownLatch countDownLatch = new CountDownLatch(1);
    private static ZooKeeper zooKeeper = null;

    public static void main(String[] args) {
        try {
            zooKeeper = new ZooKeeper("192.168.1.8:2181", 5000, new Server());
            countDownLatch.await();
            System.out.println("zookeeper connection");

            String path1 = "/DDNS/app1";
            String path2 = "/DDNS/app2";
            String path3 = "/DDNS/app3";
            String path4 = "/DDNS/app4";
            zooKeeper.create("/DDNS", "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            zooKeeper.create(path1, "192.168.1.1".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            zooKeeper.create(path2, "192.168.1.2".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            zooKeeper.create(path3, "192.168.1.3".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            zooKeeper.create(path4, "192.168.1.4".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);

            zooKeeper.create(path1 + "/server.app1.company1.com", "192.168.1.1:8041".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            zooKeeper.create(path4 + "/server.app4.company1.com", "192.168.1.4:8081".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            zooKeeper.create(path4 + "/server.app4.company2.com", "192.168.1.4:8182".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);

            Thread.sleep(20000);
            //对app1.company1的IP地址进行修改
            zooKeeper.setData(path1 + "/server.app1.company1.com", "192.168.1.7:8141".getBytes(), -1);
            //对app4.company2的端口号进行修改
            zooKeeper.setData(path4 + "/server.app4.company2.com", "192.168.1.4:8148".getBytes(), -1);
            Thread.sleep(20000);

        } catch (Exception e) {
        }
    }

    public void process(WatchedEvent event) {
        if (Event.KeeperState.SyncConnected == event.getState()) {
            System.out.println("receive watched event:" + event);
            System.out.println(event.getState());
            countDownLatch.countDown();
        }
    }
}

App的代码如下:

public class App1_company1 implements Watcher {
    private static CountDownLatch countDownLatch = new CountDownLatch(1);
    private static ZooKeeper zooKeeper = null;

    public static void main(String[] args) {
        try {
            zooKeeper = new ZooKeeper("192.168.1.8:2181", 5000, new App1_company1());
            countDownLatch.await();

            String path = "/DDNS/app1/server.app1.company1.com";
            System.out.println("domain name is : " + path + "\t" + "analysis to : " + new String(zooKeeper.getData(path, true, null)));
            Thread.sleep(20000);

        } catch (Exception e) {
        }
    }

    public void process(WatchedEvent event) {
        if (Event.KeeperState.SyncConnected == event.getState()) {
            if (Event.EventType.None == event.getType() && event.getPath() == null) {
                countDownLatch.countDown();
            } else if (event.getType() == Event.EventType.NodeDataChanged) {
                try {
                    System.out.println(event.getPath() + " is already changed. Please get new IP or Port!");
                    System.out.println("domain name is : " + event.getPath() + "\t" + "analysis to : " + new String(zooKeeper.getData(event.getPath(), true, null)));
                } catch (Exception e) {
                }
            }
        }
    }
}

public class App4_company1 implements Watcher {
    private static CountDownLatch countDownLatch = new CountDownLatch(1);
    private static ZooKeeper zooKeeper = null;

    public static void main(String[] args) {
        try {
            zooKeeper = new ZooKeeper("192.168.1.8:2181", 5000, new App4_company1());
            countDownLatch.await();

            String path = "/DDNS/app4/server.app4.company1.com";
            System.out.println("domain name is : " + path + "\t" + "analysis to : " + new String(zooKeeper.getData(path, true, null)));
            Thread.sleep(20000);

        } catch (Exception e) {
        }
    }

    public void process(WatchedEvent event) {
        if (Event.KeeperState.SyncConnected == event.getState()) {
            if (Event.EventType.None == event.getType() && event.getPath() == null) {
                countDownLatch.countDown();
            } else if (event.getType() == Event.EventType.NodeDataChanged) {
                try {
                    System.out.println(event.getPath() + " is already changed. Please get new IP or Port!");
                    System.out.println("domain name is : " + event.getPath() + "\t" + "analysis to : " + new String(zooKeeper.getData(event.getPath(), true, null)));
                } catch (Exception e) {
                }
            }
        }
    }
}
public class App4_company2 implements Watcher {
    private static CountDownLatch countDownLatch = new CountDownLatch(1);
    private static ZooKeeper zooKeeper = null;

    public static void main(String[] args) {
        try {
            zooKeeper = new ZooKeeper("192.168.1.8:2181", 5000, new App4_company2());
            countDownLatch.await();

            String path = "/DDNS/app4/server.app4.company2.com";
            System.out.println("domain name is : " + path + "\t" + "analysis to : " + new String(zooKeeper.getData(path, true, null)));
            Thread.sleep(20000);

        } catch (Exception e) {
        }
    }

    public void process(WatchedEvent event) {
        if (Event.KeeperState.SyncConnected == event.getState()) {
            if (Event.EventType.None == event.getType() && event.getPath() == null) {
                countDownLatch.countDown();
            } else if (event.getType() == Event.EventType.NodeDataChanged) {
                try {
                    System.out.println(event.getPath() + " is already changed. Please get new IP or Port!");
                    System.out.println("domain name is : " + event.getPath() + "\t" + "analysis to : " + new String(zooKeeper.getData(event.getPath(), true, null)));
                } catch (Exception e) {
                }
            }
        }
    }
}

  App1_company1是对应域名的IP地址发生了变化,其执行结果如下:
在这里插入图片描述
  App4_company1对应域名未发生任何变化,其执行结果如下:
在这里插入图片描述
  App4_company2对应域名的端口号发生了变化,其执行结果如下:
在这里插入图片描述

3.生成全局唯一ID

  命名服务是分布式系统最基本的公共服务之一、通过命名服务,客户端应用可以根据指定名字来获取资源信息。在分布式环境中,上层应用需要一个全局唯一的名字。那么要做的就是,给每一个节点都赋予一个全局唯一的ID,并且有所依据。
  可以使用zookeeper来实现一套分布式全局唯一ID的分配机制,利用zookeeper创建顺序节点的一个特性来实现。在zookeeper中,每个父节点都会为它的第一级子节点维护一份顺序,用于记录下每个子节点创建的先后顺序。基于这一顺序特性,在创建子节点的时候,可以设置这个标记,在创建节点过程中,zookeeper会自动为给定节点名加上一个数字后缀,作为一个新的、完整的节点名。这里要注意,zookeeper为它们加的数字后缀并不是按照1,2,3,4,…这样的顺序的,而是随机的,但是必然会保证对每个父节点下赋予的数字是之前没有在此父节点下出现过的。如下图所示。
在这里插入图片描述

使用zookeeper生成唯一ID的基本步骤:

1.所有客户端都会根据自己的任务类型,在指定类型的任务下调用create()接口来创建一个顺序节点,例如创建"job-"节点。

2.节点创建完毕后,create()接口会返回一个完整的节点名,例如“job-0000000003”。

3.客户端拿到这个返回值后,拼接上type类型,例如"type2-job-0000000003",这就可以作为一个全局唯一ID了。

  下面来简单的实现一下。

public class Client {
    private static CountDownLatch countDownLatch = new CountDownLatch(1);
    private static ZooKeeper zooKeeper = null;
    private static Watcher watcher = new Watcher() {
        @Override
        public void process(WatchedEvent event) {
            if (Event.KeeperState.SyncConnected == event.getState()) {
                System.out.println("receive watched event:" + event);
                System.out.println(event.getState());
                countDownLatch.countDown();
            }
        }
    };

    public static void main(String[] args) throws Exception {
        zooKeeper = new ZooKeeper("192.168.1.8:2181", 5000, watcher);
        String root = "/jobs";
        String father1 = "/type1";
        String father2 = "/type2";
		
		//对于/jobs节点和/type1,/type2节点,将其创建为持久节点(PERSISTENT)即可
        zooKeeper.create(root, "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        zooKeeper.create(root + father1, "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        zooKeeper.create(root + father2, "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);

		//对/type1和/type2都生成是个持久顺序节点(PERSISTENT_SEQUENTIAL)
        for (int i = 0; i < 10; i++) {
            zooKeeper.create(root + father1 + "/job-", "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);
            zooKeeper.create(root + father2 + "/job-", "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);
        }

        List<String> childrenList1 = new ArrayList<>();
        List<String> childrenList2 = new ArrayList<>();
        List<String> temp1 = zooKeeper.getChildren(root + father1, true);
        List<String> temp2 = zooKeeper.getChildren(root + father1, true);
        for (String s : temp1) {
            childrenList1.add("type1-" + s);
        }
        for (String s : temp2) {
            childrenList2.add("type2-" + s);
        }

        System.out.println("children of father1 are : " + childrenList1);
        System.out.println("children of father2 are : " + childrenList2);
        Thread.sleep(1000);
    }
}

  执行结果如下,可以看到成功生成了全局唯一ID。
在这里插入图片描述

4.分布式协调/通知

  分布式协调/通知服务是分布式系统中不可缺少的环节,它作为一个协调者来控制整个系统的运行流程。这样一个协调者,可以方便地将分布式协调的职责从应用中分离出来,从而大大减少系统之间的耦合性,显著提高系统的可扩展性。
  zookeeper中特有的Watcher注册与异步通知机制,可以很好地实现分布式环境下不同机器,甚至是不同系统之间的协调与通知,从而实现对数据变更的实时处理。
  使用zookeeper实现分布式协调与通知功能,通常的做法是不同的客户端都对zookeeper上同一个数据节点进行Watcher注册,监听数据节点的变化,如果数据节点发生变化,那么所有订阅的客户端都能够接收到相应的Watcher通知,并做出相应处理。

(1)MySQL数据复制总线
 MySQL数据复制总线是一个实时数据复制框架,用于在不同的MySQL数据库实例之间进行异步数据复制和数据变化通知。它就是zookeeper实现分布式协调的一个典型应用。
  整个系统是一个由MySQL数据库集群、消息队列系统、任务管理监控平台以及ZooKeeper集群等组件共同构成的一个包含数据生产者、复制管道和数据消费者等部分的数据总线系统。
在这里插入图片描述
  在这个系统中,ZooKeeper主要负责进行一系列的分布式协调工作,根据功能将数据复制组件划分为三个核心子模块:Core、Server和Monitor,每个模块分别为一个单独的进程,通过ZooKeeper进行数据交换。
在这里插入图片描述
任务注册
  Core进程在启动的时候,首先会向任务列表节点注册任务。例如,对于一个“复制热门商品”的任务,Task所在机器在启动的时候,会首先在任务列表节点上创建一个子节点,如“/mysql_replicator/tasks/copy_hot_item”,若是注册过程中发现该子节点已经存在,说明已经有其他Tas机器注册了该任务,因此自己不需要创建该节点了。

任务热备份
  复制组件采用“热备份”的容灾方式,即将同一个复制任务部署在不同主机上,主、备任务机器通过ZooKeeper互相检测运行健康情况。每台机器需要在/mysql_replicator/tasks/copy_hot_item/instance节点上将自己的主机名注册上去,也就是在该节点下创建临时顺序节点。每台机器都可以获取到自己创建的节点的完成节点名以及所有子节点的列表,然后通过对比判断自己是否是所有子节点中序号最小的。如果自己是序号最小的,那就将自己的运行状态设置为RUNNING,其余任务机器则将自己设置为STANDBY。这种热备份策略为“小序号优先”策略。
在这里插入图片描述

热备切换
  标记为RUNNING的客户端机器进行正常的数据复制,标记为STANDBY的客户端机器进入待命状态,一旦标记为RUNNING的机器出现故障停止了任务执行,那么就需要所有标记为STANDBY的客户端机器继续按“小序号优先”策略选出RUNNING机器来执行。

记录执行状态
  RUNNING任务机器需要将运行时的上下文状态保留给STANDBY任务机器。

控制台协调
  Server主要的工作是进行任务的控制,通过ZooKeeper来对不同的任务进行控制与协调。Server会将每个复制任务对应生产者的元数据,即库名、表名、用户名与密码等数据库信息以及消费者的相关信息以配置的形式写入任务节点中,以便该任务的所有任务机器都能够共享该复制任务的配置。

5.集群管理

  集群管理包括集群监控与集群控制两大块,前者侧重对集群运行时状态的收集,后者则是对集群进行操作与控制。
  ZooKeeper具有以下两大特性:
  1.客户端如果对ZooKeeper的一个数据节点注册Watcher监听,那么当该数据节点的内容或是其子节点列表发生变更时,ZooKeeper服务器就会向订阅的客户端发送变更通知。
  2.对在ZooKeeper上创建的临时节点,一旦客户端与服务器之间的会话失效,那么该临时节点也就被自动清除。
  利用ZooKeeper的这两大特性,就可以实现一种集群机器存活性监控的系统。例如,监控系统在/clusterServers节点上注册一个Watcher监听,那么但凡进行动态添加机器的操作,就会在/clusterServers节点下创建一个临时节点:/clusterServers/[Hostname]。这样一来,监控系统就能够实时监测到机器的变动情况。

在线云主机管理

  在线云主机管理是使用ZooKeeper实现集群管理的一个典型例子,它通常出现在那些虚拟主机提供商的应用场景中。在这类集群管理中,有很重要的一块就是集群机器的监控。这个场景通常对于集群中的机器状态,尤其是机器在线率的统计有较高的要求,同时需要能够快速地对集群中机器的变更做出响应。

机器上下线
  为实现自动化的线上运维,必须对机器的上下线情况有一个全局监控。通常在新增机器时,需要首先将指定的Agent部署到这些机器上去。Agent部署启动之后,会首先向ZooKeeper的指定节点进行注册,具体做法是在机器列表节点下创建一个临时子节点,如/XAE/machines/[Hostname]。当Agent在ZooKeeper上创建完这个临时子节点后,对/XAE/machines节点关注的监控中心就会接收到“子节点变更”事件,即上线通知。监控中心同样可以获取到机器下线的通知,这样便实现了对机器上下线的检测,同时能够很容易地获取到在线的机器列表。
在这里插入图片描述

机器监控
  对于一个在线云主机系统,不仅要对机器的在线状态进行检测,还需要对机器的运行时状态进行监控。运行过程中,Agent会定时将主机的运行状态信息写入ZooKeeper上的主机节点,监控中心通过订阅这些节点的数据变更通知来间接地获取主机的运行时信息。

6.Master选举

  Master选举是一个在分布式系统中非常常见的应用场景,在实际场景中通常需要在分布在不同机器上的独立系统单元中选出一个老大,称之为Master。Master往往用来协调集群中其他系统单元,具有对分布式系统状态变更的决定权。
  ZooKeeper创建节点有一个重要特性,利用ZooKeeper的强一致性,能够很好地保证在分布式高并发情况下节点的创建一定能够保证全局唯一性,即ZooKeeper将会保证客户端无法重复创建一个已经存在的数据节点。也就是说,如果同时有多个客户端请求创建同一个节点,那么最终一定只有一个客户端请求能够创建成功。利用这一特性,就很容易在分布式环境中进行Master选举了。
  例如,在一个系统中,首先会在ZooKeeper上创建一个日期节点,例如“2021-08-02”。客户端集群每天都会定时往ZooKeeper上创建一个临时节点,例如/master_election/2021-08-02/binding。在这个过程中,只有一个客户端能够成功创建这个节点,那么这个客户端所在的机器就成为了Master。同时,其他没有再ZooKeeper上成功创建节点的客户端,都会在节点/master_election/2021-08-02上注册一个子节点变更的Watcher,用于监控当前的Master机器是否存活。一旦发现当前的Master挂了,那么其余的客户端将重新进行Master选举。

7.分布式锁

(1)ZooKeeper实现独占锁(写锁)

定义锁
  通过ZooKeeper上的数据节点来表示一个锁,例如/exclusive_lock/lock节点就可以被定义为一个锁。

获取锁
  在需要获取独占锁时,所有客户端都会试图通过调用create()接口,在/exclusive_lock节点下创建临时子节点/exclusive_lock/lock。ZooKeeper会保证在所有的客户端中,最终只有一个客户端能够创建成功,那么就可以认为该客户端获取了锁。所有没有获取到锁的客户端就需要到/exclusive_lock节点上注册一个子节点变更的Watcher监听。

释放锁
  由于/exclusive_lock/lock是一个临时节点,因此下面两种情况都可能释放锁。
  1.当前获取锁的客户端机器发生宕机,那么ZooKeeper上的这个临时节点就会被移除。
  2.正常执行完业务逻辑后,客户端就会主动将自己创建的临时节点删除。
  无论什么情况下移除了lock节点,ZooKeeper都会通知所有在/exclusive_lock节点上注册了子节点变更Watcher监听的客户端。这些客户端在接收到通知后,再次重新发起分布式锁获取,即重复“获取锁”过程。整个独占锁的获取和释放流程如下。
在这里插入图片描述

(2)ZooKeeper实现共享锁(读锁)

定义锁
  同样通过ZooKeeper上的数据节点来表示一个锁,是一个类似于“/shared_lock/[Hostname]-请求类型-序号”的临时顺序节点,例如/shared_lock/192.168.0.1-R-0000000001,这样一个节点就代表了一个共享锁。

获取锁
  在需要获取共享锁时,所有客户端都会到/shared_lock这个节点下面创建一个临时顺序,如果当前是读请求,那么就创建例如/shared_lock/192.168.0.1-R-00000000001的节点。如果是写请求,那么就创建例如/shared_lock/192.168.0.1-W-00000000001的节点。

判断读写顺序
  根据共享锁的定义,不同的事务都可以同时对同一个数据对象进行读取操作,而更新操作必须在当前没有任何事务进行读写操作的情况下进行。ZooKeeper确定分布式读写顺序大致分为如下4个步骤:
  1.创建完节点后,获取/shared_lock节点下的所有子节点,并对该节点注册子节点变更的Watcher监听。
  2.确定自己的节点序号在所有子节点中的顺序。
  3.
  对于读请求:如果没有比自己序号小的子节点,或是所有比自己序号小的子节点都是读请求,那么表面自己已经成功获取到了共享锁,同时开始执行读取逻辑。如果比自己序号小的子节点中有写请求,那么就需要进入等待。
  对于写请求:如果自己不是序号最小的子节点,那么就需要进入等待。
  4.接收到Watcher通知后,重复步骤1。

释放锁
  和独占锁的释放锁是一样的。

8.分布式队列

  分布式队列,简单地讲分为两大类,一种是常规的先入先出队列,另一种则是要等到队列元素聚集之后才统一安排执行的Barrier模型。

(1)用ZooKeeper实现FIFO队列

  首先,所有客户端都会到/queue_fifo这个节点下面创建一个临时顺序节点,例如/queue_fifo/192.168.0.1-0000000001。
  创建完节点之后,根据下面4个步骤来确定执行顺序。
  1.通过调用getChildren()接口来获取/queue_fifo节点下的所有子节点,即获取队列中所有的元素。
  2.确定自己的节点序号在所有子节点中的顺序。
  3.如果自己不是序号最小的子节点,那么就需要进入等待,同时向比自己序号小的最后一个节点注册Watcher监听。
  4.接收到Watcher通知后,重复步骤1。

(2)用ZooKeeper实现Barrier

  Barrier在分布式系统中,特指系统之间的一个协调条件,规定了一个队列的元素必须都集聚后才能统一进行安排,否则一直等待。这往往出现在那些大规模分布式并行计算的应用场景上:最终的合并计算需要基于很多并行计算的子结果来进行。
  大致的设计思想如下:开始时,/queue_barrier节点是一个已经存在的默认节点,并且将其节点的数据内容赋值为一个数字n来代表Barrier值,例如n=10表示只有当/queue_barrier节点下的子节点个数达到10后,才会打开Barrier。之后,所有的客户端都会到/queue_barrier节点下创建一个临时节点,例如/queue_barrier/192.168.0.1。
  创建完节点后,根据下面5个步骤来确定执行顺序。
  1.通过调用getData()接口获取/queue_barrier节点的数据内容:10。
  2.通过调用getChildren()接口获取/queue_barrier节点下的所有子节点,即获取队列中的所有元素,同时注册对子节点列表变更的Watcher监听。
  3.统计子节点的个数。
  4.如果子节点个数还不足10个,那么就需要进入等待。
  接收到Watcher通知后,重复步骤2。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

loser与你

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值