Zookeeper学习笔记

Zookeeper概述
ZooKeeper允许分布式进程通过数据寄存器的共享层次结构名称空间(我们称之为寄存器znode)相互协调,就像文件系统一样。与普通文件系统不同,ZooKeeper为其客户端提供高吞吐量,低延迟,高可用性,严格有序的znode访问。ZooKeeper的性能方面允许它在大型分布式系统中使用。可靠性方面使其无法成为大系统中的单点故障。其严格的排序允许在客户端实现复杂的同步原语
ZooKeeper提供的名称空间非常类似于标准文件系统。名称是由斜杠(“/”)分隔的路径元素序列。ZooKeeper名称空间中的每个znode都由路径标识。并且每个znode都有一个父节点,其路径是znode的前缀,少一个元素; 此规则的例外是root(“/”),它没有父级。此外,与标准文件系统完全相同,如果znode有任何子节点,则无法删除它。

ZooKeeper和标准文件系统之间的主要区别在于每个znode都可以拥有与之关联的数据(每个文件也可以是一个目录,反之亦然),而znode仅限于它们可以拥有的数据量。ZooKeeper旨在存储协调数据:状态信息,配置,位置信息等。这种元信息通常以千字节(如果不是字节)来度量。ZooKeeper有一个1M的内置健全性检查,以防止它被用作大型数据存储,但通常它用于存储更小的数据。
在这里插入图片描述
服务本身通过一组包含该服务的机器进行复制。这些机器维护数据树的内存映像以及持久存储中的事务日志和快照。由于数据保存在内存中,因此ZooKeeper能够获得非常高的吞吐量和低延迟数。内存数据库的缺点是ZooKeeper可以管理的数据库大小受内存限制。此限制是保持znode中存储的数据量较小的进一步原因。

组成ZooKeeper服务的服务器必须彼此了解。只要大多数服务器可用,ZooKeeper服务就可用。客户端还必须知道服务器列表。客户端使用此服务器列表创建ZooKeeper服务的句柄。

客户端仅连接到单个ZooKeeper服务器。客户端维护TCP连接,发送请求,获取响应,获取监视事件以及发送心跳。如果与服务器的TCP连接中断,则客户端将连接到其他服务器。当客户端首次连接到ZooKeeper服务时,第一个ZooKeeper服务器将为客户端设置会话。如果客户端需要连接到另一台服务器,则将使用新服务器重新建立此会话。

ZooKeeper客户端发送的读取请求在客户端连接的ZooKeeper服务器本地处理。如果读取请求在znode上注册监视,则还会在ZooKeeper服务器本地跟踪该监视。写入请求将转发到其他ZooKeeper服务器,并在生成响应之前达成共识。同步请求也会转发到另一台服务器,但实际上并未达成共识。因此,读取请求的吞吐量随着服务器的数量而变化,并且写入请求的吞吐量随着服务器的数量而减少。

订单对ZooKeeper非常重要; 几乎接近强迫症。所有更新都是完全订购的。ZooKeeper实际上用每个更新标记一个反映此顺序的数字。我们将此数字称为zxid(ZooKeeper Transaction Id)。每次更新都有一个唯一的zxid。读取(和手表)是根据更新订购的。读取响应将标记为服务于读取的服务器处理的最后一个zxid​​

使用Zookeeper编程

static ZooKeeper zk = null;
static final Object mutex = new Object();
String root;
SyncPrimitive(String address) throws KeeperException, IOException {
    if(zk == null){
        System.out.println("Starting ZK:");
        zk = new ZooKeeper(address, 3000, this);
        System.out.println("Finished starting ZK: " + zk);
    }
}

public void process(WatcherEvent event) {
    //同步mutex
    synchronized (mutex) {
        mutex.notify();
    }
}

Barrier是一种原语,它使一组进程能够同步计算的开始和结束。这种实现的一般思想是拥有一个Barrier节点,其目的是成为各个进程节点的父节点。假设我们调用Barrier节点“/ b1”。然后每个进程“p”创建一个节点“/ b1 / p”。一旦足够的进程创建了相应的节点,连接的进程就可以开始计算
Barrier的构造函数将Zookeeper服务器的地址传递给父类的构造函数。父类创建一个ZooKeeper实例(如果不存在)。然后,Barrier的构造函数在!ZooKeeper上创建一个Barrier节点,它是所有进程节点的父节点,我们称之为root

	/**
	 Barrier constructor
	
	@param address
	@param name
	 @param size
	**/
    Barrier(String address, String name, int size)<font></font>
    throws KeeperException, InterruptedException, UnknownHostException {
        super(address);
        this.root = name;
        this.size = size;

        // Create barrier node
        if (zk != null) {
                Stat s = zk.exists(root, false);
                if (s == null) {
                    zk.create(root, new byte[0], Ids.OPEN_ACL_UNSAFE, 0);
                }
        }
        // My node name
        name = new String(InetAddress.getLocalHost().getCanonicalHostName().toString());
    }

要进入Barrier,进程会调用enter()。该进程在根目录下创建一个节点来表示它,使用其主机名来形成节点名称。然后等到有足够的进程进入Barrier。一个进程通过检查根节点具有“getChildren()”的子节点数并在没有足够的情况下等待通知来完成它。要在根节点发生更改时收到通知,进程必须设置监视,并通过调用“getChildren()”来完成。在代码中,我们知道“getChildren()”有两个参数。第一个表示要读取的节点,第二个是布尔标记,使进程能够设置监视。在代码中,标记为true

		 /** 
		Join barrier
		@return
		@throws KeeperException
		@throws InterruptedException
		** /

        boolean enter() throws KeeperException, InterruptedException{
        zk.create(root + "/" + name, new byte[0], Ids.OPEN_ACL_UNSAFE,
                CreateFlags.EPHEMERAL);
         while (true) {
            synchronized (mutex) {
                ArrayList<String> list = zk.getChildren(root, true);

                if (list.size() < size) {
                    mutex.wait();
                } else {
                    return true;
                }
            }
        }
    }

enter()会抛出两个!KeeperException和!InterruptedException,因此应用程序可以捕获并处理此类异常。

计算完成后,进程调用leave()离开Barrier。首先,它删除其对应的节点,然后获取根节点的子节点。如果至少有一个子节点,则它等待通知(obs:注意调用getChildren()的第二个参数为true,这意味着!ZooKeeper必须在根节点上设置监视)。收到通知后,它再次检查根节点是否有任何子节点

	/**
	Wait until all reach barrier
	
	@return
	@throws KeeperException
	@throws InterruptedException
	** /

    boolean leave() throws KeeperException, InterruptedException{
        zk.delete(root + "/" + name, 0);
        while (true) {
            synchronized (mutex) {
                ArrayList<String> list = zk.getChildren(root, true);
                    if (list.size() > 0) {
                        mutex.wait();
                    } else {
                        return true;
                    }
                }
            }
    }

生产者 - 消费者队列
生产者 - 消费者队列是一组分布式数据结构,该组进程用于生成和使用项目。生产者进程创建新元素并将其添加到队列中。使用者进程从列表中删除元素并处理它们。在此实现中,元素是简单的整数。队列由根节点表示,并且为了向队列添加元素,生成器进程创建新节点,即根节点的子节点

与Barrier对象一样,它首先调用父类的构造函数!SyncPrimitive,如果不存在,则创建一个!ZooKeeper对象。然后,它验证队列的根节点是否存在,如果不存在则创建

	/**
	Constructor of producer-consumer queue
	 
	@param address
	@param name
	** /
    Queue(String address, String name) throws KeeperException, InterruptedException {
        super(address);
        this.root = name;
        // Create ZK node name
        if (zk != null) {
                Stat s = zk.exists(root, false);
                if (s == null) {
                    zk.create(root, new byte[0], Ids.OPEN_ACL_UNSAFE, 0);
                }
        }
    }

生产者进程调用“produce()”将元素添加到队列,并传递整数作为参数。要向队列添加元素,该方法使用“create()”创建一个新节点,并使用SEQUENCE标志指示!ZooKeeper附加与根节点关联的sequencer计数器的值。通过这种方式,我们对队列的元素施加了一个总顺序,从而保证队列中最旧的元素是下一个消耗的元素

    /**
	Add element to the queue.
	 
	@param i
	@return
	** /

    boolean produce(int i) throws KeeperException, InterruptedException{
        ByteBuffer b = ByteBuffer.allocate(4);
        byte[] value;

        // Add child with value i
        b.putInt(i);
        value = b.array();
        zk.create(root + "/element", value, Ids.OPEN_ACL_UNSAFE,
                    CreateFlags.SEQUENCE);

        return true;
    }

要使用元素,使用者进程将获取根节点的子节点,读取具有最小计数器值的节点,并返回该元素。请注意,如果存在冲突,则两个竞争进程中的一个将无法删除该节点,并且删除操作将引发异常
对getChildren()的调用以字典顺序返回子项列表。由于词典顺序不必遵循计数器值的数字顺序,我们需要确定哪个元素是最小的。为了确定哪一个具有最小的计数器值,我们遍历列表,并从每个列表中删除前缀“元素”

    /**
	Remove first element from the queue.
	
	@return
	@throws KeeperException
	@throws InterruptedException
	** /
    int consume() throws KeeperException, InterruptedException{
        int retvalue = -1;
        Stat stat = null;

        // Get the first element available
        while (true) {
            synchronized (mutex) {
                ArrayList<String> list = zk.getChildren(root, true);
                if (list.isEmpty()) {
                    System.out.println("Going to wait");
                    mutex.wait();
                } else {
                    Integer min = new Integer(list.get(0).substring(7));
                    for(String s : list){
                        Integer tempValue = new Integer(s.substring(7));
                        if(tempValue < min) min = tempValue;
                    }
                    System.out.println("Temporary value: " + root + "/element" + min);
                    byte[] b = zk.getData(root + "/element" + min, false, stat);
                    zk.delete(root + "/element" + min, 0);
                    ByteBuffer buffer = ByteBuffer.wrap(b);
                    retvalue = buffer.getInt();

                    return retvalue;
                }
            }
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值