基于ZooKeeper實(shí)現(xiàn)隊(duì)列源碼
實(shí)現(xiàn)原理
先進(jìn)先出隊(duì)列是最常用的隊(duì)列,使用Zookeeper實(shí)現(xiàn)先進(jìn)先出隊(duì)列就是在特定的目錄下創(chuàng)建PERSISTENT_EQUENTIAL節(jié)點(diǎn),創(chuàng)建成功時(shí)Watcher通知等待的隊(duì)列,隊(duì)列刪除序列號(hào)最小的節(jié)點(diǎn)用以消費(fèi)。此場(chǎng)景下Zookeeper的znode用于消息存儲(chǔ),znode存儲(chǔ)的數(shù)據(jù)就是消息隊(duì)列中的消息內(nèi)容,SEQUENTIAL序列號(hào)就是消息的編號(hào),按序取出即可。由于創(chuàng)建的節(jié)點(diǎn)是持久化的,所以不必?fù)?dān)心隊(duì)列消息的丟失問(wèn)題。
隊(duì)列(Queue)
分布式隊(duì)列是通用的數(shù)據(jù)結(jié)構(gòu),為了在 Zookeeper 中實(shí)現(xiàn)分布式隊(duì)列,首先需要指定一個(gè) Znode 節(jié)點(diǎn)作為隊(duì)列節(jié)點(diǎn)(queue node), 各個(gè)分布式客戶端通過(guò)調(diào)用 create() 函數(shù)向隊(duì)列中放入數(shù)據(jù),調(diào)用create()時(shí)節(jié)點(diǎn)路徑名帶"qn-"結(jié)尾,并設(shè)置順序(sequence)節(jié)點(diǎn)標(biāo)志。 由于設(shè)置了節(jié)點(diǎn)的順序標(biāo)志,新的路徑名具有以下字符串模式:"_path-to-queue-node_/qn-X",X 是唯一自增號(hào)。需要從隊(duì)列中獲取數(shù)據(jù)/移除數(shù)據(jù)的客戶端首先調(diào)用 getChildren() 函數(shù),有數(shù)據(jù)則獲?。ǐ@取數(shù)據(jù)后可以刪除也可以不刪),沒(méi)有則在隊(duì)列節(jié)點(diǎn)(queue node)上將 watch 設(shè)置為 true,等待觸發(fā)并處理最小序號(hào)的節(jié)點(diǎn)(即從序號(hào)最小的節(jié)點(diǎn)中取數(shù)據(jù))。
應(yīng)用場(chǎng)景
Zookeeper隊(duì)列不太適合要求高性能的場(chǎng)合,但可以在數(shù)據(jù)量不大的情況下考慮使用。比如已在項(xiàng)目中使用Zookeeper又需要小規(guī)模的隊(duì)列應(yīng)用,這時(shí)可以使用Zookeeper實(shí)現(xiàn)的隊(duì)列;畢竟引進(jìn)一個(gè)消息中間件會(huì)增加系統(tǒng)的復(fù)雜性和運(yùn)維的壓力。
詳細(xì)代碼
ZookeeperClient工具類
package org.massive.common;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Created by Massive on 2016/12/18.
*/
public class ZooKeeperClient {
private static String connectionString = "localhost:2181";
private static int sessionTimeout = 10000;
public static ZooKeeper getInstance() throws IOException, InterruptedException {
//--------------------------------------------------------------
// 為避免連接還未完成就執(zhí)行zookeeper的get/create/exists操作引起的(KeeperErrorCode = ConnectionLoss)
// 這里等Zookeeper的連接完成才返回實(shí)例
//--------------------------------------------------------------
final CountDownLatch connectedSignal = new CountDownLatch(1);
ZooKeeper zk = new ZooKeeper(connectionString, sessionTimeout, new Watcher() {
@Override
public void process(WatchedEvent event) {
if (event.getState() == Event.KeeperState.SyncConnected) {
connectedSignal.countDown();
} else if (event.getState() == Event.KeeperState.Expired) {
}
}
});
connectedSignal.await(sessionTimeout, TimeUnit.MILLISECONDS);
return zk;
}
public static int getSessionTimeout() {
return sessionTimeout;
}
public static void setSessionTimeout(int sessionTimeout) {
ZooKeeperClient.sessionTimeout = sessionTimeout;
}
}
ZooKeeperQueue
package org.massive.queue;
import org.apache.commons.lang3.RandomUtils;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import org.massive.common.ZooKeeperClient;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* Created by Allen on 2016/12/22.
*/
public class ZooKeeperQueue {
private ZooKeeper zk;
private int sessionTimeout;
private static byte[] ROOT_QUEUE_DATA = {0x12,0x34};
private static String QUEUE_ROOT = "/QUEUE";
private String queueName;
private String queuePath;
private Object mutex = new Object();
public ZooKeeperQueue(String queueName) throws IOException, KeeperException, InterruptedException {
this.queueName = queueName;
this.queuePath = QUEUE_ROOT + "/" + queueName;
this.zk = ZooKeeperClient.getInstance();
this.sessionTimeout = zk.getSessionTimeout();
//----------------------------------------------------
// 確保隊(duì)列根目錄/QUEUE和當(dāng)前隊(duì)列的目錄的存在
//----------------------------------------------------
ensureExists(QUEUE_ROOT);
ensureExists(queuePath);
}
public byte[] consume() throws InterruptedException, KeeperException, UnsupportedEncodingException {
List<String> nodes = null;
byte[] returnVal = null;
Stat stat = null;
do {
synchronized (mutex) {
nodes = zk.getChildren(queuePath, new ProduceWatcher());
//----------------------------------------------------
// 如果沒(méi)有消息節(jié)點(diǎn),等待生產(chǎn)者的通知
//----------------------------------------------------
if (nodes == null || nodes.size() == 0) {
mutex.wait();
} else {
SortedSet<String> sortedNode = new TreeSet<String>();
for (String node : nodes) {
sortedNode.add(queuePath + "/" + node);
}
//----------------------------------------------------
// 消費(fèi)隊(duì)列里序列號(hào)最小的消息
//----------------------------------------------------
String first = sortedNode.first();
returnVal = zk.getData(first, false, stat);
zk.delete(first, -1);
System.out.print(Thread.currentThread().getName() + " ");
System.out.print("consume a message from queue:" + first);
System.out.println(", message data is: " + new String(returnVal,"UTF-8"));
return returnVal;
}
}
} while (true);
}
class ProduceWatcher implements Watcher {
@Override
public void process(WatchedEvent event) {
//----------------------------------------------------
// 生產(chǎn)一條消息成功后通知一個(gè)等待線程
//----------------------------------------------------
synchronized (mutex) {
mutex.notify();
}
}
}
public void produce(byte[] data) throws KeeperException, InterruptedException, UnsupportedEncodingException {
//----------------------------------------------------
// 確保當(dāng)前隊(duì)列目錄存在
// example: /QUEUE/queueName
//----------------------------------------------------
ensureExists(queuePath);
String node = zk.create(queuePath + "/", data,
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT_SEQUENTIAL);
System.out.print(Thread.currentThread().getName() + " ");
System.out.print("produce a message to queue:" + node);
System.out.println(" , message data is: " + new String(data,"UTF-8"));
}
public void ensureExists(String path) {
try {
Stat stat = zk.exists(path, false);
if (stat == null) {
zk.create(path, ROOT_QUEUE_DATA, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
String queueName = "test";
final ZooKeeperQueue queue = new ZooKeeperQueue(queueName);
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
queue.consume();
System.out.println("--------------------------------------------------------");
System.out.println();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}).start();
}
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(RandomUtils.nextInt(100 * i, 200 * i));
queue.produce(("massive" + i).getBytes());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
},"Produce-thread").start();
}
}
測(cè)試
運(yùn)行main方法,本機(jī)器的某次輸出結(jié)果
Produce-thread produce a message to queue:/QUEUE/test/0000000000 , message data is: massive0 Thread-8 consume a message from queue:/QUEUE/test/0000000000, message data is: massive0 -------------------------------------------------------- Produce-thread produce a message to queue:/QUEUE/test/0000000001 , message data is: massive1 Thread-6 consume a message from queue:/QUEUE/test/0000000001, message data is: massive1 -------------------------------------------------------- Produce-thread produce a message to queue:/QUEUE/test/0000000002 , message data is: massive2 Thread-3 consume a message from queue:/QUEUE/test/0000000002, message data is: massive2 -------------------------------------------------------- Produce-thread produce a message to queue:/QUEUE/test/0000000003 , message data is: massive3 Thread-0 consume a message from queue:/QUEUE/test/0000000003, message data is: massive3 -------------------------------------------------------- Produce-thread produce a message to queue:/QUEUE/test/0000000004 , message data is: massive4 Thread-5 consume a message from queue:/QUEUE/test/0000000004, message data is: massive4 -------------------------------------------------------- Produce-thread produce a message to queue:/QUEUE/test/0000000005 , message data is: massive5 Thread-2 consume a message from queue:/QUEUE/test/0000000005, message data is: massive5 -------------------------------------------------------- Produce-thread produce a message to queue:/QUEUE/test/0000000006 , message data is: massive6 Thread-4 consume a message from queue:/QUEUE/test/0000000006, message data is: massive6 -------------------------------------------------------- Produce-thread produce a message to queue:/QUEUE/test/0000000007 , message data is: massive7 Thread-9 consume a message from queue:/QUEUE/test/0000000007, message data is: massive7 -------------------------------------------------------- Produce-thread produce a message to queue:/QUEUE/test/0000000008 , message data is: massive8 Thread-7 consume a message from queue:/QUEUE/test/0000000008, message data is: massive8 -------------------------------------------------------- Produce-thread produce a message to queue:/QUEUE/test/0000000009 , message data is: massive9 Thread-1 consume a message from queue:/QUEUE/test/0000000009, message data is: massive9
總結(jié)
以上就是本文有關(guān)于隊(duì)列和基于ZooKeeper實(shí)現(xiàn)隊(duì)列源碼介紹的全部?jī)?nèi)容,希望對(duì)大家有所幫助。
感謝朋友們對(duì)本站的支持!
相關(guān)文章
使用@PathVariable時(shí)候無(wú)法將參數(shù)映射到變量中的解決
這篇文章主要介紹了使用@PathVariable時(shí)候無(wú)法將參數(shù)映射到變量中的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
Java基于中介者模式實(shí)現(xiàn)多人聊天室功能示例
這篇文章主要介紹了Java基于中介者模式實(shí)現(xiàn)多人聊天室功能,詳細(xì)分析了中介者模式的概念、原理以及使用中介模式實(shí)現(xiàn)多人聊天的步驟、操作技巧與注意事項(xiàng),需要的朋友可以參考下2018-05-05
基于Springboot2.3訪問(wèn)本地路徑下靜態(tài)資源的方法(解決報(bào)錯(cuò):Not allowed to load local
這篇文章主要介紹了基于Springboot2.3訪問(wèn)本地路徑下靜態(tài)資源的方法(解決報(bào)錯(cuò):Not allowed to load local resource),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
Spring?Boot攔截器和監(jiān)聽(tīng)器實(shí)現(xiàn)對(duì)請(qǐng)求和響應(yīng)處理實(shí)戰(zhàn)
這篇文章主要介紹了Spring?Boot攔截器和監(jiān)聽(tīng)器實(shí)現(xiàn)對(duì)請(qǐng)求和響應(yīng)處理實(shí)戰(zhàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06
如何將復(fù)雜SQL轉(zhuǎn)換成Java對(duì)象的實(shí)例講解
轉(zhuǎn)換復(fù)雜SQL到Java代碼,我們需要確定數(shù)據(jù)庫(kù)連接方式和工具,使用JDBC的API來(lái)連接數(shù)據(jù)庫(kù)、執(zhí)行SQL語(yǔ)句,復(fù)雜SQL語(yǔ)句可以被拆分為多個(gè)步驟,每個(gè)步驟執(zhí)行一個(gè)特定的操作,通過(guò)將SQL語(yǔ)句拆分為多個(gè)步驟,我們可以更好地理解復(fù)雜SQL的邏輯,并且更容易將其轉(zhuǎn)換為Java代碼2024-05-05

