zookeeper源码解析:分布式队列

由于zookeeper 的分布式一致性的特点 zk也可以用来实现分布式的队列:一般适合于数据量比较小对一致性要求比较高的,如果数据量大的推荐使用kafka,因为zk 本身不适合高并发的读写。
具体结构什么样子的呢 如下: 网上找了一个图
在这里插入图片描述
使用路径/queue下的znode节点表示队列中的元素,图上的节点都是持久化的顺序节点,这些znode 名字的后缀数字代表位置 位置越小越靠前。
那么具体怎么实现呢 其实在源码中 org.apache.zookeeper.recipes.queue.DistributedQueue
已经实现了。下面来做一个源码解析

public DistributedQueue(ZooKeeper zookeeper, String dir, List<ACL> acl) {
        this.dir = dir;

        if (acl != null) {
            this.acl = acl;
        }
        this.zookeeper = zookeeper;
    }

首先实例化对象传入 zookeeper 实例 和相应的目录(dir) 以及相应的权限
如果没有传入权限 会默认使用 OPEN_ACL_UNSAFE 这个权限比较高 基本包含所有

    private List<ACL> acl = ZooDefs.Ids.OPEN_ACL_UNSAFE;
    public boolean offer(byte[] data) throws KeeperException, InterruptedException {
        for (; ; ) {
            try {
                zookeeper.create(dir + "/" + prefix, data, acl, CreateMode.PERSISTENT_SEQUENTIAL);
                return true;
            } catch (KeeperException.NoNodeException e) {
                zookeeper.create(dir, new byte[0], acl, CreateMode.PERSISTENT);
            }
        }

    }

PERSISTENT_SEQUENTIAL 顺序自动编号持久化节点,这种节点会根据当前已存在的节点数自动加 1。 在这里我们我们通过通过zk 的能创建自动编号持久化节点,进行入队列操作。

下面看下出队列的操作 一般都有两种一个是删除数据 另外一种是不删除数据。下面我们先看一下出不删除数据那种

public byte[] peek() throws KeeperException, InterruptedException {
        try {
            return element();
        } catch (NoSuchElementException e) {
            return null;
        }
    }
public byte[] element() throws NoSuchElementException, KeeperException, InterruptedException {
        Map<Long, String> orderedChildren;

        // element, take, and remove follow the same pattern.
        // We want to return the child node with the smallest sequence number.
        // Since other clients are remove()ing and take()ing nodes concurrently,
        // the child with the smallest sequence number in orderedChildren might be gone by the time we check.
        // We don't call getChildren again until we have tried the rest of the nodes in sequence order.
        while (true) {
            try {
            // 返回一个已经排序的子列表的集合 
                orderedChildren = orderedChildren(null);
            } catch (KeeperException.NoNodeException e) {
                throw new NoSuchElementException();
            }
            if (orderedChildren.size() == 0) {
                throw new NoSuchElementException();
            }
			// 遍历返回第一个对象的值
			// 这里为什么要用循环呢 应该 可能存在在获取的同时被其他的节点删除了 
			// 所以用这样的方式 如果被删除了 则获取下一个
            for (String headNode : orderedChildren.values()) {
                if (headNode != null) {
                    try {
                        return zookeeper.getData(dir + "/" + headNode, false, null);
                    } catch (KeeperException.NoNodeException e) {
                        //Another client removed the node first, try next
                    }
                }
            }

        }
    }

下面看一下删除数据的出队方式其实和上面差不多 就是多了一步删除操作。

    public byte[] poll() throws KeeperException, InterruptedException {
        try {
            return remove();
        } catch (NoSuchElementException e) {
            return null;
        }
    }
public byte[] remove() throws NoSuchElementException, KeeperException, InterruptedException {
        Map<Long, String> orderedChildren;
        // Same as for element.  Should refactor this.
        while (true) {
            try {
                orderedChildren = orderedChildren(null);
            } catch (KeeperException.NoNodeException e) {
                throw new NoSuchElementException();
            }
            if (orderedChildren.size() == 0) {
                throw new NoSuchElementException();
            }

            for (String headNode : orderedChildren.values()) {
                String path = dir + "/" + headNode;
                try {
                    byte[] data = zookeeper.getData(path, false, null);
                    zookeeper.delete(path, -1);
                    return data;
                } catch (KeeperException.NoNodeException e) {
                    // Another client deleted the node first.
                }
            }

        }
    }

我们发现很多方法都调用了 orderedChildren 我们来看下都干了什么,首先构造一个 TreeMap 通过treemap 的性质 来保证顺序。

  private Map<Long, String> orderedChildren(Watcher watcher) throws KeeperException, InterruptedException {
        Map<Long, String> orderedChildren = new TreeMap<>();

        List<String> childNames;
        childNames = zookeeper.getChildren(dir, watcher);

        for (String childName : childNames) {
            try {
                //Check format  regionMatches() 方法用于检测两个字符串在一个区域内是否相等。
               if (!childName.regionMatches(0, prefix, 0, prefix.length())) {
                    LOG.warn("Found child node with improper name: {}", childName);
                    continue;
                }
                String suffix = childName.substring(prefix.length());
                Long childId = Long.parseLong(suffix);
                orderedChildren.put(childId, childName);
            } catch (NumberFormatException e) {
                LOG.warn("Found child node with improper format : {}", childName, e);
            }
        }

        return orderedChildren;
    }

发布了213 篇原创文章 · 获赞 35 · 访问量 85万+

猜你喜欢

转载自blog.csdn.net/u012957549/article/details/104705854