ZooKeeper实现生产-消费者队列
【欢迎关注公众号:程序猿讲故事 (codestory),及时接收最新文章】
生产-消费者队列,用于多节点的分布式数据结构,生产和消费数据。生产者创建一个数据对象,并放到队列中;消费者从队列中取出一个数据对象并进行处理。在ZooKeeper中,队列可以使用一个容器节点下创建多个子节点来实现;创建子节点时,CreateMode使用 PERSISTENT_SEQUENTIAL,ZooKeeper会自动在节点名称后面添加唯一序列号。EPHEMERAL_SEQUENTIAL也有同样的特点,区别在于会话结束后是否会自动删除。
敲小黑板:*_SEQUENTIAL是ZooKeeper的一个很重要的特性,分布式锁、选举制度都依靠这个特性实现的。
1 对前续代码的重构
之前的文章,我们已经用实现了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(); } } |
2 队列的生产者
生产者的关键代码
|
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。
3 测试日志
把测试结果放源码前面,免得大家被无聊的代码晃晕。
测试代码创建了两个线程,一个线程是生产者,按随机间隔往队列中添加对象;一个线程是消费者,随机间隔尝试从队列中取出第一个,如果当时队列为空,会等到直到新的数据。
两个进程都加上随机间隔,是为了模拟生产可能比消费更快的情况。以下是测试日志,为了更突出,生产和消费的日志我增加了不同的文字样式。
|
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 毫秒 |
4 队列的消费者
消费者尝试从子节点列表获取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); } } } } |
5 完整源码
5.1 ZooKeeperBase.java
|
package tech.codestory.zookeeper; import java.io.IOException; import java.util.concurrent.CountDownLatch; import org.apache.zookeeper.*; import org.apache.zookeeper.data.Stat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.profiler.Profiler; /** * 为 ZooKeeper测试代码创建一个基类,封装建立连接的过程 * * @author code story * @date 2019/8/16 */ public class ZooKeeperBase implements Watcher { /** 日志,不使用 @Slf4j ,是要使用子类的log */ Logger log = null; /** 等待连接建立成功的信号 */ private CountDownLatch connectedSemaphore = new CountDownLatch(1); /** ZooKeeper 客户端 */ private ZooKeeper zooKeeper = null; /** 避免重复根节点 */ static Integer rootNodeInitial = Integer.valueOf(1); /** 构造函数 */ public ZooKeeperBase(String address) throws IOException { log = LoggerFactory.getLogger(getClass()); Profiler profiler = new Profiler(this.getClass().getName() + " 连接到ZooKeeper"); profiler.start("开始链接"); zooKeeper = new ZooKeeper(address, 3000, this); try { profiler.start("等待连接成功的Event"); connectedSemaphore.await(); } catch (InterruptedException e) { log.error("InterruptedException", e); } profiler.stop(); profiler.setLogger(log); profiler.log(); } /** * 创建测试需要的根节点 * * @param rootNodeName * @return */ public String createRootNode(String rootNodeName) { synchronized (rootNodeInitial) { // 创建 tableSerial 的zNode try { Stat existsStat = getZooKeeper().exists(rootNodeName, false); if (existsStat == null) { rootNodeName = getZooKeeper().create(rootNodeName, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } } catch (KeeperException e) { log.error("KeeperException", e); } catch (InterruptedException e) { log.error("InterruptedException", e); } } return rootNodeName; } /** 读取ZooKeeper对象,供子类调用 */ protected ZooKeeper getZooKeeper() { return zooKeeper; } @Override 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); } } /** * 处理事件: NodeCreated * * @param event */ protected void processNodeCreated(WatchedEvent event) {} /** * 处理事件: NodeDeleted * * @param event */ protected void processNodeDeleted(WatchedEvent event) {} /** * 处理事件: NodeDataChanged * * @param event */ protected void processNodeDataChanged(WatchedEvent event) {} /** * 处理事件: NodeChildrenChanged * * @param event */ protected void processNodeChildrenChanged(WatchedEvent event) {} } |
5.2 ZooKeeperQueue.java
|
package tech.codestory.zookeeper.queue; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.ZooDefs; import org.apache.zookeeper.data.ACL; import org.apache.zookeeper.data.Stat; import lombok.extern.slf4j.Slf4j; import tech.codestory.zookeeper.ZooKeeperBase; /** * ZooKeeper实现Queue * * @author code story * @date 2019/8/16 */ @Slf4j public class ZooKeeperQueue extends ZooKeeperBase { /** 队列名称 */ private String queueName; /** 队列的同步信号 */ private static Integer queueMutex = Integer.valueOf(1); /** * 构造函数 * * @param address * @param queueName * @throws IOException */ public ZooKeeperQueue(String address, String queueName) throws IOException { super(address); this.queueName = createRootNode(queueName); } @Override protected void processNodeChildrenChanged(WatchedEvent event) { synchronized (queueMutex) { queueMutex.notify(); } } /** * 将对象添加到队列中 * * @param i * @return */ boolean produce(int i) throws KeeperException, InterruptedException { ByteBuffer b = ByteBuffer.allocate(4); byte[] value; // Add child with value i b.putInt(i); value = b.array(); String elementName = queueName + "/element"; ArrayList<ACL> ids = ZooDefs.Ids.OPEN_ACL_UNSAFE; CreateMode createMode = CreateMode.PERSISTENT_SEQUENTIAL; getZooKeeper().create(elementName, value, ids, createMode); return true; } /** * 从队列中删除第一个对象 * * @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); } } } } /** * 获取第一个子节点的名称 * * @param list * @return */ private String getFirstElementName(List<String> list) { Integer min = Integer.MAX_VALUE; String minNode = null; for (String s : list) { Integer tempValue = Integer.valueOf(s.substring(7)); if (tempValue < min) { min = tempValue; minNode = s; } } return minNode; } /** * 删除节点,并返回节点的值 * * @param minNode * @return * @throws KeeperException * @throws InterruptedException */ private int deleteNodeAndReturnValue(String minNode) throws KeeperException, InterruptedException { String fullNodeName = queueName + "/" + minNode; Stat stat = new Stat(); byte[] b = getZooKeeper().getData(fullNodeName, false, stat); getZooKeeper().delete(fullNodeName, stat.getVersion()); ByteBuffer buffer = ByteBuffer.wrap(b); return buffer.getInt(); } } |
5.3 ZooKeeperQueueTest.java
|
package tech.codestory.zookeeper.queue; import java.io.IOException; import java.security.SecureRandom; import java.util.Random; import java.util.concurrent.CountDownLatch; import org.apache.zookeeper.KeeperException; import org.testng.annotations.Test; import lombok.extern.slf4j.Slf4j; /** * ZooKeeperQueue测试 * * @author code story * @date 2019/8/16 */ @Slf4j public class ZooKeeperQueueTest { final String address = "192.168.5.128:2181"; final String queueName = "/queue"; final Random random = new SecureRandom(); // 随机生成10-20之间的个数 final int count = 10 + random.nextInt(10); /** 等待生产者和消费者线程都结束 */ private CountDownLatch connectedSemaphore = new CountDownLatch(2); @Test public void testQueue() { log.info("开始ZooKeeper队列测试,本次将测试 {} 个数据", count); new QueueProducer().start(); new QueueConsumer().start(); try { connectedSemaphore.await(); } catch (InterruptedException e) { log.error("InterruptedException", e); } } /** * 队列的生产者 */ class QueueProducer extends Thread { @Override public void run() { try { ZooKeeperQueue queue = new ZooKeeperQueue(address, queueName); for (int i = 0; i < count; i++) { int elementValue = i + 1; long waitTime = random.nextInt(50) * 100; log.info("生产对象 : {} , 然后等待 {} 毫秒", elementValue, waitTime); queue.produce(elementValue); Thread.sleep(waitTime); } } catch (IOException e) { log.error("IOException", e); } catch (InterruptedException e) { log.error("InterruptedException", e); } catch (KeeperException e) { log.error("KeeperException", e); } connectedSemaphore.countDown(); } } /** * 队列的消费者 */ class QueueConsumer extends Thread { @Override public void run() { try { ZooKeeperQueue queue = new ZooKeeperQueue(address, queueName); for (int i = 0; i < count; i++) { try { int elementValue = queue.consume(); long waitTime = random.nextInt(50) * 100; log.info("消费对象: {} , 然后等待 {} 毫秒", elementValue, waitTime); Thread.sleep(waitTime); } catch (KeeperException e) { i--; log.error("KeeperException", e); } catch (InterruptedException e) { log.error("InterruptedException", e); } } connectedSemaphore.countDown(); } catch (IOException e) { log.error("IOException", e); } } } } |
ZooKeeper实现生产-消费者队列的更多相关文章
- BlockingQueue 阻塞队列(生产/消费者队列)
1:BlockingQueue的继承关系 java.util.concurrent 包里的 BlockingQueue是一个接口, 继承Queue接口,Queue接口继承 Collection Blo ...
- 生产消费者队列(TaskCompletionSource)的应用
using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Li ...
- Python进阶----进程之间通信(互斥锁,队列(参数:timeout和block),), ***生产消费者模型
Python进阶----进程之间通信(互斥锁,队列(参数:timeout和block),), ***生产消费者模型 一丶互斥锁 含义: 每个对象都对应于一个可称为" 互斥锁&qu ...
- Kafka下的生产消费者模式与订阅发布模式
原文:https://blog.csdn.net/zwgdft/article/details/54633105 在RabbitMQ下的生产消费者模式与订阅发布模式一文中,笔者以“数据接入”和“事 ...
- Java多线程学习笔记--生产消费者模式
实际开发中,我们经常会接触到生产消费者模型,如:Android的Looper相应handler处理UI操作,Socket通信的响应过程.数据缓冲区在文件读写应用等.强大的模型框架,鉴于本人水平有限目前 ...
- RabbitMQ下的生产消费者模式与订阅发布模式
所谓模式,就是在某种场景下,一类问题及其解决方案的总结归纳.生产消费者模式与订阅发布模式是使用消息中间件时常用的两种模式,用于功能解耦和分布式系统间的消息通信,以下面两种场景为例: 数据接入 假设 ...
- Python——Queue模块以及生产消费者模型
1.了解Queue Queue是python标准库中的线程安全的队列(FIFO)实现,提供了一个适用于多线程编程的先进先出的数据结构,即队列,用来在生产者和消费者线程之间的信息传递 |queue.Qu ...
- Linux——多线程下解决生产消费者模型
我们学习了操作系统,想必对生产消费者问题都不陌生.作为同步互斥问题的一个经典案例,生产消费者模型其实是解决实际问题的基础模型,解决很多的实际问题都会依赖于它.而此模型要解决最大的问题便是同步与互斥.而 ...
- 生产消费者模式与python+redis实例运用(中级篇)
上一篇文章介绍了生产消费者模式与python+redis实例运用(基础篇),但是依旧遗留了一个问题,就是如果消费者消费的速度跟不上生产者,依旧会浪费我们大量的时间去等待,这时候我们就可以考虑使用多进程 ...
随机推荐
- kuangbin专题专题四 Heavy Transportation POJ - 1797
题目链接:https://vjudge.net/problem/POJ-1797 思路:请参考我列出的另一个题目,和这个题目要求的值相反,另一个清楚后,这个写的解释就明白了. 另一个类似题目的博客:h ...
- Mac上pycharm集成pyspark
前提: 1.已经安装好spark.我的是spark2.2.0. 2.已经有python环境,我这边使用的是python3.6. 一.安装py4j 使用pip,运行如下命令: pip install p ...
- CSDN怎么一键转载别人的博客
在参考"如何快速转载CSDN中的博客"后,由于自己不懂html以及markdown相关知识,所以花了一些时间来弄明白怎么转载博客,以下为转载CSDN博客步骤和一些知识小笔记. 参考 ...
- SSAS Tabular 表格模型建模(关系)及部署
一.表格建模(SSAS 表格) 表格模型是 Analysis Services 中的内存中数据库. 使用最先进的压缩算法和多线程查询处理器,xVelocity 内存中分析引擎 (VertiPaq) ...
- py+selenium 报错NameError: name 'NoSuchElementException' is not defined【已解决】
报错:NameError: name 'NoSuchElementException' is not defined 如图 解决方法: 头部加一句:from selenium.common.exc ...
- 个人永久性免费-Excel催化剂功能第82波-复制粘贴按源区域大小自动扩展收缩目标区域
日常工作中,复制粘贴的操作,永远是最高频的操作,没有之一,在最高频的操作上,进行优化,让过程更智能,比一天到晚鼓吹人工智能替换人的骇人听闻的新闻来得更实际.此篇带来一点点的小小的改进,让日后无数的复制 ...
- Excel催化剂开源第3波-修复ExcelCom加载项失效问题及WPS可调用Com加载项的方法
为了还原一个干净无侵扰的网络世界,本文将不进行大规模地分发,若您觉得此文有用,不妨小范围地分享到真正有需要的人手中 功能概述 修复ExcelCom加载项常见问题,如每次需重新勾选COM加载项或COM加 ...
- 【Java中级】(二)集合框架
2.1.ArraList 1.自增长 容器的容量"capacity"会随着对象的增加,自动增长 只需要不断往容器里增加英雄即可,不用担心会出现数组的边界问题. 2.常用方法 关键字 ...
- python面向对象的继承-组合-02
*面向对象(OOP)的三大特征:**# 封装.继承.多态 继承 什么是继承 继承:# 是一种关系,描述两个对象之间什么是什么的什么的关系 例如:麦兜.佩奇.猪猪侠.猪刚鬣,都是猪 为什么要使用继承 继 ...
- poj2909 欧拉素数筛选
刚刚学了一种新的素数筛选法,效率比原先的要高一些,据说当n趋近于无穷大时这个的时间复杂度趋近O(n).本人水平有限,无法证明. 这是道水题,贴代码出来重点是欧拉筛选法.我把原来普通的筛选法贴出来. / ...