设计:
使用路径为/queue的znode下的节点表示队列中的元素。/queue下的节点都是顺序持久化的znode。这些znode名字的后缀数字表示了对应队列元素在队列中的位置。Znode名字后缀数字越小,对应队列元素在队列中的位置越靠前。
offer方法:
在/queue下面创建一个顺序znode。因为znode的后缀数字是/queue下面现有znode最大后缀数字加1,所有znode对应的队列元素处于队尾
/**
* 向队列提交数据
*
* @param element 提交的数据
* @return
* @throws Exception
*/
public boolean offer(T element) throws Exception {
String nodeFullPath = root.concat("/").concat(Node_NAME);
try {
// 创建持久的顺序节点
zkClient.createPersistentSequential(nodeFullPath, element);
} catch (ZkNoNodeException e) {
zkClient.createPersistent(root);
offer(element);
} catch (Exception e) {
throw ExceptionUtil.convertToRuntimeException(e);
}
return true;
}
poll方法:
/**
* 从队列获取数据
*
* @return
* @throws Exception
*/
public T poll() throws Exception {
try {
List<String> list = zkClient.getChildren(root);
if (list.size() == 0) {
return null;
}
// 排序队列 根据名称由小到大
Collections.sort(list, new Comparator<String>() {
public int compare(String lhs, String rhs) {
return getNodeNumber(lhs, Node_NAME).compareTo(getNodeNumber(rhs, Node_NAME));
}
});
for (String nodeName : list) {
String nodeFullPath = root.concat("/").concat(nodeName);
try {
T node = (T) zkClient.readData(nodeFullPath);
zkClient.delete(nodeFullPath);
return node;
} catch (ZkNoNodeException e) {
// 其他客户端消费了 继续循环
}
}
return null;
} catch (Exception e) {
throw ExceptionUtil.convertToRuntimeException(e);
}
}
注意:T node = (T) zkClient.readData(nodeFullPath);的成功,并不代表删除的成功,原因是该队列元素可能会被其他的用户出队。
总体的代码:
package com.queue.model;
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = 7726840211952830151L;
String name;
String id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", id='" + id + '\'' +
'}';
}
}
package com.queue;
import org.I0Itec.zkclient.ExceptionUtil;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.exception.ZkNoNodeException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* 简单的分布式队列
*
* @author jerome_s@qq.com
* @date 2016/8/30 20:19
*/
public class DistributedSimpleQueue<T> {
protected final ZkClient zkClient;
/**
* 根节点路径
*/
protected final String root;
/**
* 顺序节点的前缀
*/
protected static final String Node_NAME = "qn_";
public DistributedSimpleQueue(ZkClient zkClient, String root) {
this.zkClient = zkClient;
this.root = root;
}
/**
* 获取队列的大小
*
* @return
*/
public int getQueueSize() {
return zkClient.getChildren(root).size();
}
/**
* 向队列提交数据
*
* @param element 提交的数据
* @return
* @throws Exception
*/
public boolean offer(T element) throws Exception {
String nodeFullPath = root.concat("/").concat(Node_NAME);
try {
// 创建持久的顺序节点
zkClient.createPersistentSequential(nodeFullPath, element);
} catch (ZkNoNodeException e) {
zkClient.createPersistent(root);
offer(element);
} catch (Exception e) {
throw ExceptionUtil.convertToRuntimeException(e);
}
return true;
}
public List<T> element() {
List<String> list = zkClient.getChildren(root);
// 排序队列 根据名称由小到大
Collections.sort(list, new Comparator<String>() {
public int compare(String lhs, String rhs) {
return getNodeNumber(lhs, Node_NAME).compareTo(getNodeNumber(rhs, Node_NAME));
}
});
List<T> res = new ArrayList<>();
try {
for (String nodeName : list) {
String nodeFullPath = root.concat("/").concat(nodeName);
res.add((T)zkClient.readData(nodeFullPath));
}
} catch (ZkNoNodeException e) {
// 其他客户端消费了 继续循环
}
return res;
}
/**
* 从队列获取数据
*
* @return
* @throws Exception
*/
public T poll() throws Exception {
try {
List<String> list = zkClient.getChildren(root);
if (list.size() == 0) {
return null;
}
// 排序队列 根据名称由小到大
Collections.sort(list, new Comparator<String>() {
public int compare(String lhs, String rhs) {
return getNodeNumber(lhs, Node_NAME).compareTo(getNodeNumber(rhs, Node_NAME));
}
});
for (String nodeName : list) {
String nodeFullPath = root.concat("/").concat(nodeName);
try {
T node = (T) zkClient.readData(nodeFullPath);
zkClient.delete(nodeFullPath);
return node;
} catch (ZkNoNodeException e) {
// 其他客户端消费了 继续循环
}
}
return null;
} catch (Exception e) {
throw ExceptionUtil.convertToRuntimeException(e);
}
}
private String getNodeNumber(String str, String nodeName) {
int index = str.lastIndexOf(nodeName);
if (index >= 0) {
index += Node_NAME.length();
return index <= str.length() ? str.substring(index) : "";
}
return str;
}
}
测试用例:
package com.queue;
import com.queue.model.User;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.serialize.SerializableSerializer;
import java.util.List;
/**
* 测试简单的分布式队列
*
* @author jerome_s@qq.com
* @date 2016/8/30 19:48
*/
public class TestDistributedSimpleQueue {
public static void main(String[] args) {
ZkClient zkClient = new ZkClient("192.168.40.138:2181", 10000, 500000, new SerializableSerializer());
DistributedSimpleQueue<User> queue = new DistributedSimpleQueue<>(zkClient, "/queue");
User user1 = new User();
user1.setId("1");
user1.setName("jerome1");
User user2 = new User();
user2.setId("2");
user2.setName("jerome2");
try {
queue.offer(user1);
queue.offer(user2);
System.out.println("queue.offer end!");
List<User> element = queue.element();
System.out.println(element.toString());
User u1 = queue.poll();
User u2 = queue.poll();
System.out.println("queue.poll() u1 = " + u1.toString());
System.out.println("queue.poll() u2 = " + u2.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
/* console:
queue.offer end!
queue.poll() u1 = User{name='jerome1', id='1'}
queue.poll() u2 = User{name='jerome2', id='2'}
*/
}
运行结果: