【從入門到放棄-ZooKeeper】ZooKeeper實戰-分佈式隊列

前言

上文【從入門到放棄-ZooKeeper】ZooKeeper入門中,我們學習了ZooKeeper的簡單安裝和cli使用。
接下來我們開始基於java API的實戰編程。本文先來寫一個分佈式隊列的代碼實現。

設計

我們來寫一個先進先出的分佈式無界公平隊列。參考我們之前介紹的【從入門到放棄-Java】併發編程-JUC-ConcurrentLinkedQueue【從入門到放棄-Java】併發編程-JUC-LinkedBlockingQueue。我們直接繼承AbstractQueue類,並實現Queue接口。
主要重寫offer、poll、peek、size方法。
我們使用ZooKeeper的持久化順序節點來實現分佈式隊列。
offer是入隊,入隊時新創建一個持久化順序節點,節點後綴會根據ZooKeeper的特性自動累加。
poll的出隊,獲取根節點下的所有節點,根據後綴數字排序,數組最小的是最先入隊的,因此要最先出隊。
peek,獲取到最下入隊的數據,和poll的區別是,peek只獲取數據,不出隊,不刪除已經消費的節點。
size獲取隊列長度,實現方式是,獲取根節點下的節點數量即可。這個方法在併發時可能會有問題。慎用。

DistributedQueue

//繼承AbstractQueue類並實現Queue接口
public class DistributedQueue<E> extends AbstractQueue<E> implements Queue<E> {
    private static Logger logger = LoggerFactory.getLogger(DistributedQueue.class);
    
    //ZooKeeper客戶端,進行ZooKeeper操作
    private ZooKeeper zooKeeper;

    //根節點名稱
    private String dir;

    //數據節點名稱,順序節點在插入口會變爲 node{00000000xx} 格式
    private String node;

    //ZooKeeper鑑權信息
    private List<ACL> acls;

    /**
     * Constructor.
     *
     * @param zooKeeper the zoo keeper
     * @param dir       the dir
     * @param node      the node
     * @param acls      the acls
     */
    public DistributedQueue (ZooKeeper zooKeeper, String dir, String node, List<ACL> acls) {
        this.zooKeeper = zooKeeper;
        this.dir = dir;
        this.node = node;
        this.acls = acls;
        init();
    }


    private void init() {
        //需要先判斷根節點是否存在,不存在的話,創建子節點時會出錯。
        try {
            Stat stat = zooKeeper.exists(dir, false);
            if (stat == null) {
                zooKeeper.create(dir, null, acls, CreateMode.PERSISTENT);
            }
        } catch (Exception e) {
            logger.error("[DistributedQueue#init] error : " + e.toString(), e);
        }
    }
}

offer

/**
 * Offer boolean.
 *
 * @param o the o
 * @return the boolean
 */
@Override
public boolean offer(E o) {
    //構建要插入的節點名稱
    String fullPath = dir.concat("/").concat(node);
    try {
        //創建子節點成功則返回入隊成功
        zooKeeper.create(fullPath, objectToBytes(o), acls, CreateMode.PERSISTENT_SEQUENTIAL);
        return true;
    } catch (Exception e) {
        logger.error("[DistributedQueue#offer] error : " + e.toString(), e);
    }
    return false;
}

poll

/**
 * Poll e.
 *
 * @return the e
 */
@Override
public E poll() {
    try {
        //獲取根節點所有子節點信息。
        List<String> children = zooKeeper.getChildren(dir, null);
        //如果隊列是空的則返回null
        if (children == null || children.isEmpty()) {
            return null;
        }

        //將子節點名稱排序
        Collections.sort(children);
        for (String child : children) {
            //拼接子節點的具體名稱
            String fullPath = dir.concat("/").concat(child);
            try {
                //如果獲取數據成功,則類型轉換後,返回,並刪除改隊列中該節點
                byte[] bytes = zooKeeper.getData(fullPath, false, null);
                E data = (E) bytesToObject(bytes);
                zooKeeper.delete(fullPath, -1);
                return data;
            } catch (Exception e) {
                logger.warn("[DistributedQueue#poll] warn : " + e.toString(), e);
            }
        }

    } catch (Exception e) {
        logger.error("[DistributedQueue#peek] poll : " + e.toString(), e);
    }

    return null;
}

peek

/**
 * Peek e.
 *
 * @return the e
 */
@Override
public E peek() {
   
    try {
        //獲取根節點所有子節點信息。
        List<String> children = zooKeeper.getChildren(dir, null);
        //如果隊列是空的則返回null
        if (children == null || children.isEmpty()) {
            return null;
        }

        //將子節點名稱排序
        Collections.sort(children);
        
        for (String child : children) {
            //拼接子節點的具體名稱
            String fullPath = dir.concat("/").concat(child);
            try {
                //如果獲取數據成功,則類型轉換後,返回,不會刪除改隊列中該節點
                byte[] bytes = zooKeeper.getData(fullPath, false, null);
                E data = (E) bytesToObject(bytes);
                return data;
            } catch (Exception e) {
                logger.warn("[DistributedQueue#peek] warn : " + e.toString(), e);
            }
        }

    } catch (Exception e) {
        logger.error("[DistributedQueue#peek] warn : " + e.toString(), e);
    }

    return null;
}

size

/**
 * Size int.
 *
 * @return the int
 */
@Override
public int size() {
    try {
        //獲取根節點的子節點名稱
        List<String> children = zooKeeper.getChildren(dir, null);
        //返回子結點信息數量
        return children.size();
    } catch (Exception e) {
        logger.error("[DistributedQueue#offer] size : " + e.toString(), e);
    }

    return 0;
}

總結

上面我們一起學習瞭如何利用持久性順序節點,創建一個分佈式先進先出隊列。源代碼可見:aloofJr
如果有好的優化建議,歡迎一起討論。

更多文章

見我的博客:https://nc2era.com

written by AloofJr,轉載請註明出處

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章