ZooKeeper之(七)应用实例

6.3 分布式队列

很多单机上很平常的事情,放在集群环境中都会发生质的变化。
以一个常见的生产者-消费者模型举例:有一个容量有限的邮筒,寄信者(即生产者)不断地将信件塞入邮筒,邮递员(即消费者)不断地从邮筒取出信件发往目的地。运行期间需要保证:
(1)邮筒已达上限时,寄信者停止活动,等带邮筒恢复到非满状态
(2)邮筒已空时,邮递员停止活动,等带邮筒恢复到非空状态
该邮筒用有序队列实现,保证FIFO(先进先出)特性。
在一台机器上,可以用有序队列来实现邮筒,保证FIFO(先进先出)特性,开启两个线程,一个充当寄信者,一个充当邮递员,通过wait()/notify()很容易实现上述功能。
但是,如果在跨进程或者分布式环境下呢?比如,一台机器运行生产者程序,另一台机器运行消费者程序,代表邮筒的有序队列无法跨机器共享,但是两者需要随时了解邮筒的状态(是否已满、是否已空)以及保证信件的有序(先到达的先发送)。
这种情况下,可以借助ZooKeeper实现一个分布式队列。新建一个“/mailBox”节点代表邮筒。一旦有信件到达,就在该节点下创建PERSISTENT_SEQUENTIAL类型的子节点,当子节点总数达到上限时,阻塞生产者,然后使用getChildren(String path, Watcher watcher)方法监控子节点的变化,子节点总数减少后再回复生产;而消费者每次选取序号最小的子节点进行处理,然后删除该节点,当子节点总数为0时,阻塞消费者,同样设置监控,子节点总数增加后再回复消费。

import java.io.IOException;  
import java.util.Collections;  
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;  
import org.apache.zookeeper.data.Stat;  

public class ZkDistributedQueue {  

    // 邮箱上限为10封信  
    private static final int MAILBOX_MAX_SIZE = 10;  

    // 邮箱路径  
    private static final String MAILBOX_ROOT_PATH = "/mailBox";  

    // 信件节点  
    private static final String LETTER_NODE_NAME = "letter_";  

    // 生产者线程,负责接受信件  
    static class Producer extends Thread {  

        ZooKeeper zkClient;  

        @Override  
        public void run() {  
            while (true) {  
                try {  
                    if (getLetterNum() == MAILBOX_MAX_SIZE) { // 信箱已满  
                        System.out.println("mailBox has been full");  
                        // 创建Watcher,监控子节点的变化  
                        Watcher watcher = new Watcher() {  
                            @Override  
                            public void process(WatchedEvent event) {  
                                // 生产者已停止,只有消费者在活动,所以只可能出现发送信件的动作  
                                System.out.println("mailBox has been not full");  
                                synchronized (this) {  
                                    notify(); // 唤醒生产者  
                                }  
                            }  
                        };  
                        zkClient.getChildren(MAILBOX_ROOT_PATH, watcher);  

                        synchronized (watcher) {  
                            watcher.wait(); // 阻塞生产者  
                        }  
                    } else {  
                        // 线程随机休眠数毫秒,模拟现实中的费时操作  
                        int sleepMillis = (int) (Math.random() * 1000);  
                        Thread.sleep(sleepMillis);  

                        // 接收信件,创建新的子节点  
                        String newLetterPath = zkClient.create(  
                                MAILBOX_ROOT_PATH + "/" + LETTER_NODE_NAME,  
                                "letter".getBytes(),   
                                Ids.OPEN_ACL_UNSAFE,   
                                CreateMode.PERSISTENT_SEQUENTIAL);  
                        System.out.println("a new letter has been received: "   
                                + newLetterPath.substring(MAILBOX_ROOT_PATH.length()+1)   
                                + ", letter num: " + getLetterNum());  
                    }  
                } catch (Exception e) {  
                    System.out.println("producer equit task becouse of exception !");  
                    e.printStackTrace();  
                    break;  
                }  
            }  
        }  

        private int getLetterNum() throws KeeperException, InterruptedException {  
            Stat stat = zkClient.exists(MAILBOX_ROOT_PATH, null);  
            int letterNum = stat.getNumChildren();  
            return letterNum;  
        }  

        public void setZkClient(ZooKeeper zkClient) {  
            this.zkClient = zkClient;  
        }  
    }  

    // 消费者线程,负责发送信件  
    static class Consumer extends Thread {  

        ZooKeeper zkClient;  

        @Override  
        public void run() {  
            while (true) {  
                try {  
                    if (getLetterNum() == 0) { // 信箱已空  
                        System.out.println("mailBox has been empty");  
                        // 创建Watcher,监控子节点的变化  
                        Watcher watcher = new Watcher() {  
                            @Override  
                            public void process(WatchedEvent event) {  
                                // 消费者已停止,只有生产者在活动,所以只可能出现收取信件的动作  
                                System.out.println("mailBox has been not empty");  
                                synchronized (this) {  
                                    notify(); // 唤醒消费者  
                                }  
                            }  
                        };  
                        zkClient.getChildren(MAILBOX_ROOT_PATH, watcher);  

                        synchronized (watcher) {  
                            watcher.wait(); // 阻塞消费者  
                        }  
                    } else {  
                        // 线程随机休眠数毫秒,模拟现实中的费时操作  
                        int sleepMillis = (int) (Math.random() * 1000);  
                        Thread.sleep(sleepMillis);  

                        // 发送信件,删除序号最小的子节点  
                        String firstLetter = getFirstLetter();  
                        zkClient.delete(MAILBOX_ROOT_PATH + "/" +firstLetter, -1);  
                        System.out.println("a letter has been delivered: " + firstLetter   
                                + ", letter num: " + getLetterNum());  
                    }  
                } catch (Exception e) {  
                    System.out.println("consumer equit task becouse of exception !");  
                    e.printStackTrace();  
                    break;  
                }  
            }  
        }  

        private int getLetterNum() throws KeeperException, InterruptedException {  
            Stat stat = zkClient.exists(MAILBOX_ROOT_PATH, false);  
            int letterNum = stat.getNumChildren();  
            return letterNum;  
        }  

        private String getFirstLetter() throws KeeperException, InterruptedException {  
            List<String> letterPaths = zkClient.getChildren(MAILBOX_ROOT_PATH, false);  
            Collections.sort(letterPaths);  
            return letterPaths.get(0);  
        }  

        public void setZkClient(ZooKeeper zkClient) {  
            this.zkClient = zkClient;  
        }  
    }  

    public static void main(String[] args) throws IOException {  
        // 开启生产者线程  
        Producer producer = new Producer();  
        ZooKeeper zkClientA = new ZooKeeper("127.0.0.1:2181", 3000, null);  
        producer.setZkClient(zkClientA);  
        producer.start();  

        // 开启消费者线程  
        Consumer consumer = new Consumer();  
        ZooKeeper zkClientB = new ZooKeeper("127.0.0.1:2181", 3000, null);  
        consumer.setZkClient(zkClientB);  
        consumer.start();  
    }  
}  

打印结果如下:
这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值