您好,登錄后才能下訂單哦!
生產-消費者隊列,用于多節點的分布式數據結構,生產和消費數據。生產者創建一個數據對象,并放到隊列中;消費者從隊列中取出一個數據對象并進行處理。在ZooKeeper中,隊列可以使用一個容器節點下創建多個子節點來實現;創建子節點時,CreateMode使用 PERSISTENT_SEQUENTIAL,ZooKeeper會自動在節點名稱后面添加唯一序列號。EPHEMERAL_SEQUENTIAL也有同樣的特點,區別在于會話結束后是否會自動刪除。
敲小黑板:*_SEQUENTIAL是ZooKeeper的一個很重要的特性,分布式鎖、選舉制度都依靠這個特性實現的。
之前的文章,我們已經用實現了Watcher和Barrier,創建ZooKeeper連接的代碼已經復制了一遍。后續還需要類似的工作,因此先對原有代碼做一下重構,讓代碼味道干凈一點。
?
以下是 process(WatchedEvent)的代碼
final public void process(WatchedEvent event) {
if (Event.EventType.None.equals(event.getType())) {
// 連接狀態發生變化
if (Event.KeeperState.SyncConnected.equals(event.getState())) {
// 連接建立成功
connectedSemaphore.countDown();
}
} else if (Event.EventType.NodeCreated.equals(event.getType())) {
processNodeCreated(event);
} else if (Event.EventType.NodeDeleted.equals(event.getType())) {
processNodeDeleted(event);
} else if (Event.EventType.NodeDataChanged.equals(event.getType())) {
processNodeDataChanged(event);
} else if (Event.EventType.NodeChildrenChanged.equals(event.getType())) {
processNodeChildrenChanged(event);
}
}
以ZooKeeperBarrier為例,看看重構之后的構造函數和監聽Event的代碼
ZooKeeperBarrier(String address, String tableSerial, int tableCapacity, String customerName)
throws IOException {
super(address);
this.tableSerial = createRootNode(tableSerial);
this.tableCapacity = tableCapacity;
this.customerName = customerName;
}
protected void processNodeChildrenChanged(WatchedEvent event) {
log.info("{} 接收到了通知 : {}", customerName, event.getType());
// 子節點有變化
synchronized (mutex) {
mutex.notify();
}
}
生產者的關鍵代碼
String elementName = queueName + "/element";
ArrayList<ACL> ids = ZooDefs.Ids.OPEN_ACL_UNSAFE;
CreateMode createMode = CreateMode.PERSISTENT_SEQUENTIAL;
getZooKeeper().create(elementName, value, ids, createMode);
注意,重點是PERSISTENT_SEQUENTIAL,PERSISTENT是表示永久存儲直到有命令刪除,SEQUENTIAL表示自動在后面添加自增的唯一序列號。這樣,盡管elementName都一樣,但實際生成的zNode名字在 “element”后面會添加格式為%010d的10個數字,如0000000001。如一個完整的zNode名可能為/queue/element0000000021。
消費者嘗試從子節點列表獲取zNode名最小的一個子節點,如果隊列為空則等待NodeChildrenChanged事件。關鍵代碼
/** 隊列的同步信號 */
private static Integer queueMutex = Integer.valueOf(1);
@Override
protected void processNodeChildrenChanged(WatchedEvent event) {
synchronized (queueMutex) {
queueMutex.notify();
}
}
/**
* 從隊列中刪除第一個對象
*
* @return
* @throws KeeperException
* @throws InterruptedException
*/
int consume() throws KeeperException, InterruptedException {
while (true) {
synchronized (queueMutex) {
List<String> list = getZooKeeper().getChildren(queueName, true);
if (list.size() == 0) {
queueMutex.wait();
} else {
// 獲取第一個子節點的名稱
String firstNodeName = getFirstElementName(list);
// 刪除節點,并返回節點的值
return deleteNodeAndReturnValue(firstNodeName);
}
}
}
}
把測試結果放源碼前面,免得大家被無聊的代碼晃暈。
測試代碼創建了兩個線程,一個線程是生產者,按隨機間隔往隊列中添加對象;一個線程是消費者,隨機間隔嘗試從隊列中取出第一個,如果當時隊列為空,會等到直到新的數據。
兩個進程都加上隨機間隔,是為了模擬生產可能比消費更快的情況。以下是測試日志,為了更突出,生產和消費的日志我增加了不同的文字樣式。
49:47.866 [INFO] ZooKeeperQueueTest.testQueue(29) 開始ZooKeeper隊列測試,本次將測試 10 個數據
49:48.076 [DEBUG] ZooKeeperQueue.log(201)
+ Profiler [tech.codestory.zookeeper.queue.ZooKeeperQueue 連接到ZooKeeper]
|-- elapsed time [開始鏈接] 119.863 milliseconds.
|-- elapsed time [等待連接成功的Event] 40.039 milliseconds.
|-- Total [tech.codestory.zookeeper.queue.ZooKeeperQueue 連接到ZooKeeper] 159.911 milliseconds.
49:48.082 [DEBUG] ZooKeeperQueue.log(201)
+ Profiler [tech.codestory.zookeeper.queue.ZooKeeperQueue 連接到ZooKeeper]
|-- elapsed time [開始鏈接] 103.795 milliseconds.
|-- elapsed time [等待連接成功的Event] 65.899 milliseconds.
|-- Total [tech.codestory.zookeeper.queue.ZooKeeperQueue 連接到ZooKeeper] 170.263 milliseconds.
49:48.102 [INFO] ZooKeeperQueueTest.run(51) 生產對象 : 1 , 然后等待 1700 毫秒
49:48.134 [INFO] ZooKeeperQueueTest.run(80) 消費對象: 1 , 然后等待 4000 毫秒
49:49.814 [INFO] ZooKeeperQueueTest.run(51) 生產對象 : 2 , 然后等待 900 毫秒
49:50.717 [INFO] ZooKeeperQueueTest.run(51) 生產對象 : 3 , 然后等待 1300 毫秒
49:52.020 [INFO] ZooKeeperQueueTest.run(51) 生產對象 : 4 , 然后等待 3700 毫秒
49:52.139 [INFO] ZooKeeperQueueTest.run(80) 消費對象: 2 , 然后等待 2800 毫秒
49:54.947 [INFO] ZooKeeperQueueTest.run(80) 消費對象: 3 , 然后等待 4500 毫秒
49:55.724 [INFO] ZooKeeperQueueTest.run(51) 生產對象 : 5 , 然后等待 3500 毫秒
49:59.228 [INFO] ZooKeeperQueueTest.run(51) 生產對象 : 6 , 然后等待 4200 毫秒
49:59.454 [INFO] ZooKeeperQueueTest.run(80) 消費對象: 4 , 然后等待 2400 毫秒
50:01.870 [INFO] ZooKeeperQueueTest.run(80) 消費對象: 5 , 然后等待 4900 毫秒
50:03.435 [INFO] ZooKeeperQueueTest.run(51) 生產對象 : 7 , 然后等待 4500 毫秒
50:06.776 [INFO] ZooKeeperQueueTest.run(80) 消費對象: 6 , 然后等待 3600 毫秒
50:07.938 [INFO] ZooKeeperQueueTest.run(51) 生產對象 : 8 , 然后等待 1900 毫秒
50:09.846 [INFO] ZooKeeperQueueTest.run(51) 生產對象 : 9 , 然后等待 3200 毫秒
50:10.388 [INFO] ZooKeeperQueueTest.run(80) 消費對象: 7 , 然后等待 2900 毫秒
50:13.051 [INFO] ZooKeeperQueueTest.run(51) 生產對象 : 10 , 然后等待 4900 毫秒
50:13.294 [INFO] ZooKeeperQueueTest.run(80) 消費對象: 8 , 然后等待 300 毫秒
50:13.600 [INFO] ZooKeeperQueueTest.run(80) 消費對象: 9 , 然后等待 4800 毫秒
50:18.407 [INFO] ZooKeeperQueueTest.run(80) 消費對象: 10 , 然后等待 2400 毫秒
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。