ZooKeeper實現分佈式FIFO隊列

文章介紹瞭如何整合虛擬化和Hadoop,讓Hadoop集羣跑在VPS虛擬主機上,通過雲向用戶提供存儲和計算的服務。

現在硬件越來越便宜,一臺非品牌服務器,2顆24核CPU,配48G內存,2T的硬盤,已經降到2萬塊人民幣以下了。這種配置如果簡單地放幾個web應用,顯然是奢侈的浪費。就算是用來實現單節點的hadoop,對計算資源浪費也是非常高的。對於這麼高性能的計算機,如何有效利用計算資源,就成爲成本控制的一項重要議題了。

通過虛擬化技術,我們可以將一臺服務器,拆分成12臺VPS,每臺2核CPU,4G內存,40G硬盤,並且支持資源重新分配。多麼偉大的技術啊!現在我們有了12個節點的hadoop集羣, 讓Hadoop跑在雲端,讓世界加速。

前言

ZooKeeper是一個強大的分佈式協作系統,用ZooKeeper可以方便地實現先進先出(FIFO)隊列。給“隊列”的技術現實多一種選擇,標準化我們的程序結構。另一篇,分步式同步隊列實現,請參考:ZooKeeper實現分佈式隊列Queue

關於ZooKeeper的基本使用,請參考:ZooKeeper僞分步式集羣安裝及使用。

1. 分佈式先進先出(FIFO)隊列

在計算機科學中,消息隊列(Message queue)是一種進程間通信或同一進程的不同線程間的通信方式。消息隊列提供了異步的通信協議,消息的發送者和接收者不需要同時與消息隊列互交。消息會保存在隊列中,直到接收者取回它。

先進先出(FIFO)隊列,是消息隊列最基本的一種實現形式,先發出的先消費。

2. 設計思路

實現的思路也非常簡單,在/queue-fifo的目錄下創建 SEQUENTIAL 類型的子目錄 /x(i),這樣就能保證所有成員加入隊列時都是有編號的,出隊列時通過 getChildren( ) 方法可以返回當前所有的隊列中的元素,然後消費其中最小的一個,這樣就能保證FIFO。

應用實例

圖標解釋

1.app1,app2,app3是3個獨立的業務系統

2.zk1,zk2,zk3是ZooKeeper集羣的3個連接點

3./queue-fifo,是znode的隊列,按順序存儲數據

4./queue-fifo/x1,是znode隊列中,1號排對者,由app1提交

5./queue-fifo/x2,是znode隊列中,2號排對者,由app2提交

6.app3是消費者,通過zk3連接到znode隊列中,找到/queue-fifo中順序最少的節點消費,刪除消費後的節點(紅色線表示)

注:

1). app1可以通過zk2提交,app2也可通過zk3提交

2). app1可以提交3次請求,生成x1,x2,x3多個節點

3). app1可以作爲消費者,消費隊列數據

3. 程序實現

1). 單節點模擬實驗

模擬app1,通過zk1,生產2個節點,然後再消費3個節點。

 public static void doOne() throws Exception {
        String host1 = "192.168.1.201:2181";
        ZooKeeper zk = connection(host1);
        initQueue(zk);
        produce(zk, 1);
        produce(zk, 2);
        cosume(zk);
        cosume(zk);
        cosume(zk);
        zk.close();
    }

創建一個與服務器的連接

public static ZooKeeper connection(String host) throws IOException {
        ZooKeeper zk = new ZooKeeper(host, 60000, null);
        return zk;
    }

出始化隊列

 public static ZooKeeper connection(String host) throws IOException {
        return new ZooKeeper(host, 60000, new Watcher() {
            public void process(WatchedEvent event) {
            }
        });
    }

生產者

 public static void produce(ZooKeeper zk, int x) throws KeeperException, InterruptedException {
        System.out.println("create /queue-fifo/x" + x + " x" + x);
        zk.create("/queue-fifo/x" + x, ("x" + x).getBytes(), Ids.OPEN_ACL_UNSAFE,
 CreateMode.EPHEMERAL_SEQUENTIAL);
    }

消費者

public static void cosume(ZooKeeper zk) throws KeeperException, InterruptedException {
        List list = zk.getChildren("/queue-fifo", true);
        if (list.size() > 0) {
            long min = Long.MAX_VALUE;
            for (String num : list) {
                if (min > Long.parseLong(num.substring(1))) {
                    min = Long.parseLong(num.substring(1));
                }
            }
            System.out.println("delete /queue/x" + min);
            zk.delete("/queue-fifo/x" + min, 0);
        } else {
            System.out.println("No node to cosume");
        }
    }

啓動main函數

public static void main(String[] args) throws Exception {
            doOne();
    }

運行結果:

/queue-fifo is exist!
create /queue-fifo/x1 x1
create /queue-fifo/x2 x2
delete /queue/x10000000032
delete /queue/x20000000033
No node to cosume

完全符合我的們預期,由於produce時,我們創建的節點模式是EPHEMERAL_SEQUENTIAL,所以系統會在x(i)(n),隨機生成n=0000000032,輸出爲x10000000032。

接下來我們看分佈式環境。

2). 分佈式模擬實驗

app1通過zk1生產x1, app2通過zk2生產x2, app3通過zk3消費3個節點

public static void doAction(int client) throws Exception {
        String host1 = "192.168.1.201:2181";
        String host2 = "192.168.1.201:2182";
        String host3 = "192.168.1.201:2183";

        ZooKeeper zk = null;
        switch (client) {
        case 1:
            zk = connection(host1);
            initQueue(zk);
            produce(zk, 1);
            break;
        case 2:
            zk = connection(host2);
            initQueue(zk);
            produce(zk, 2);
            break;
        case 3:
            zk = connection(host3);
            initQueue(zk);
            cosume(zk);
            cosume(zk);
            cosume(zk);
            break;
        }
    }

啓動main函數

public static void main(String[] args) throws Exception {
        if (args.length == 0) {
            doOne();
        } else {
            doAction(Integer.parseInt(args[0]));
        }
    }

程序啓動方法,分3次啓動,命令行傳不同的參數,分別是1,2,3

run1: 執行app1–>zk1

#日誌輸出
/queue-fifo is exist!
create /queue-fifo/x1 x1

run2: 執行app2–>zk2

#日誌輸出
/queue-fifo is exist!
create /queue-fifo/x2 x2

run3: 執行app3–>zk3

#日誌輸出
/queue-fifo is exist!
delete /queue/x10000000034
delete /queue/x20000000035
No node to cosume

我們完成分佈式隊列的實驗,由於時間倉促。文字說明及代碼難免有一些問題,請發現問題的同學幫忙指正。

下面貼一下完整的代碼:

package org.conan.zookeeper.demo;

import java.io.IOException;
import java.util.List;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;

public class FIFOZooKeeper {

    public static void main(String[] args) throws Exception {
        if (args.length == 0) {
            doOne();
        } else {
            doAction(Integer.parseInt(args[0]));
        }
    }

    public static void doOne() throws Exception {
        String host1 = "192.168.1.201:2181";
        ZooKeeper zk = connection(host1);
        initQueue(zk);
        produce(zk, 1);
        produce(zk, 2);
        cosume(zk);
        cosume(zk);
        cosume(zk);
        zk.close();
    }

    public static void doAction(int client) throws Exception {
        String host1 = "192.168.1.201:2181";
        String host2 = "192.168.1.201:2182";
        String host3 = "192.168.1.201:2183";

        ZooKeeper zk = null;
        switch (client) {
        case 1:
            zk = connection(host1);
            initQueue(zk);
            produce(zk, 1);
            break;
        case 2:
            zk = connection(host2);
            initQueue(zk);
            produce(zk, 2);
            break;
        case 3:
            zk = connection(host3);
            initQueue(zk);
            cosume(zk);
            cosume(zk);
            cosume(zk);
            break;
        }
    }

    // 創建一個與服務器的連接
    public static ZooKeeper connection(String host) throws IOException {
        return new ZooKeeper(host, 60000, new Watcher() {
            public void process(WatchedEvent event) {
            }
        });
    }

    public static void initQueue(ZooKeeper zk) throws KeeperException, InterruptedException {
        if (zk.exists("/queue-fifo", false) == null) {
            System.out.println("create /queue-fifo task-queue-fifo");
            zk.create("/queue-fifo", "task-queue-fifo".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        } else {
            System.out.println("/queue-fifo is exist!");
        }
    }

    public static void produce(ZooKeeper zk, int x) throws KeeperException, InterruptedException {
        System.out.println("create /queue-fifo/x" + x + " x" + x);
        zk.create("/queue-fifo/x" + x, ("x" + x).getBytes(), Ids.OPEN_ACL_UNSAFE, 
CreateMode.EPHEMERAL_SEQUENTIAL);
    }

    public static void cosume(ZooKeeper zk) throws KeeperException, InterruptedException {
        List list = zk.getChildren("/queue-fifo", true);
        if (list.size() > 0) {
            long min = Long.MAX_VALUE;
            for (String num : list) {
                if (min > Long.parseLong(num.substring(1))) {
                    min = Long.parseLong(num.substring(1));
                }
            }
            System.out.println("delete /queue/x" + min);
            zk.delete("/queue-fifo/x" + min, 0);
        } else {
            System.out.println("No node to cosume");
        }
    }

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