ZooKeeper&CAP理论

 

CountDownLatch线程阻塞

lock = new ReentrantLock();
condition = lock.newCondition();

lock.lock();
condition.await();

lock.lock();
condition.signal();;收到信号后,放行阻塞进程

 

下载 https://zookeeper.apache.org/releases.html
安装 https://www.cnblogs.com/jimcsharp/p/8358271.html
tar -zxvf xx.tar.gz 解压
cd config cp zoo_sample.cfg zoo.cfg

  .\bin\zkServer.cmd  Windows下启动

 https://mp.weixin.qq.com/s/7z11XV2XWgTJcjmgbJmYBA
 https://mp.weixin.qq.com/s/-yusswLQn_tzGdrt_a3FMg
 https://blog.csdn.net/u013468915/article/details/80955110

Zookeeper的特点:

1,从一个读写请求分析,保证了可用性(不用阻塞等待全部follwer同步完成),保证不了数据的一致性,所以是ap。
2,但是从zk架构分析,zk在leader选举期间,会暂停对外提供服务(为啥会暂停,因为zk依赖leader来保证数据一致性),所以丢失了可用性,保证了一致性。
综上zk广义上来说是cp,狭义上是ap。

ZooKeeper是按照CP原则构建的,是分布式协调服务,它的职责是保证数据(注:配置数据,状态数据)在其管辖下的所有服务之间保持同步、一致,而且,作为ZooKeeper的核心实现算法Zab,就是解决了分布式系统下数据如何在多个服务之间保持同步问题的。
  1. 假设有2n+1个server,在同步流程中,leader向follower同步数据,当同步完成的follower数量大于 n+1时同步流程结束,系统可接受client的连接请求。如果client连接的并非同步完成的follower,那么得到的并非最新数据,但可以保证单调性。
  2. follower接收写请求后,转发给leader处理;leader完成两阶段提交的机制。向所有server发起提案,当提案获得超过半数(n+1)的server认同后,将对整个集群进行同步,超过半数(n+1)的server同步完成后,该写请求完成。如果client连接的并非同步完成follower,那么得到的并非最新数据,但可以保证单调性。
①强一致性(strong consistency)。任何时刻,任何用户都能读取到最近一次成功更新的数据。
②单调一致性(monotonic consistency)。任何时刻,任何用户一旦读到某个数据在某次更新后的值,那么就不会再读到比这个值更旧的值。也就是说,可  获取的数据顺序必是单调递增的。
④ 最终一致性(eventual consistency)。用户只能读到某次更新后的值,但系统保证数据将最终达到完全一致的状态,只是所需时间不能保障。
用分布式系统的CAP原则来分析Zookeeper:
(1)C: Zookeeper保证了最终一致性,在十几秒可以Sync到各个节点.
(2)A: Zookeeper保证了可用性,数据总是可用的,没有锁.并且有一大半的节点所拥有的数据是最新的,实时的. 如果想保证取得是数据一定是最新的,需要手工调用Sync()
(2)P: 有2点需要分析的.
① 节点多了会导致写数据延时非常大,因为需要多个节点同步.
② 节点多了Leader选举非常耗时, 就会放大网络的问题. 可以通过引入 observer节点缓解这个问题.


1、选主 + 恢复阶段不提供对外服务。leader与learner(follower、observer)通过Socket通讯,这个不可用的时间不可控。
2、实现是Java。过载下(eg.大量外部请求引起watch机制频繁),导致频繁的FullGc ,停顿时间过长有可能影响到与客户端的心跳会话将关联的临时节点删除 、leader与learner的Socket超时异常 等。

Zab 协议的具体实现可以分为以下两部分:
消息广播阶段
Leader 节点接受事务提交,并且将新的 Proposal 请求广播给 Follower 节点,收集各个节点的反馈,决定是否进行 Commit,在这个过程中,也会使用上一课时提到的 Quorum 选举机制。
崩溃恢复阶段
如果在同步过程中出现 Leader 节点宕机,会进入崩溃恢复阶段,重新进行 Leader 选举,崩溃恢复阶段还包含数据同步操作,同步集群中最新的数据,保持集群的数据一致性。
整个 ZooKeeper 集群的一致性保证就是在上面两个状态之前切换,当 Leader 服务正常时,就是正常的消息广播模式;当 Leader 不可用时,则进入崩溃恢复模式,崩溃恢复阶段会进行数据同步,完成以后,重新进入消息广播阶段。


1)单机
./zkServer.sh start ./zkServer.sh stop
./zkServer.sh start-foreground
执行此命令,可以看到大量详细信息的输出,以便允许查看服务器发生了什么。
./zkCli.sh -server 127.0.0.1:2181

ls / 查看包含内容
create /xmh 1
create /xmh/dir 1
ls /xmh
get /xmh
deleteall /xmh
set /xmh xxxx

2)在集群模式下,所有的zk进程可以使用相同的配置文件(是指各个zk进程部署在不同的机器上面),例如如下配置:
tickTime=2000
dataDir=/home/myname/zookeeper
clientPort=2181
initLimit=5
syncLimit=2
server.1=192.168.229.160:2888:3888
server.2=192.168.229.161:2888:3888
server.3=192.168.229.162:2888:3888
initLimit
ZooKeeper集群模式下包含多个zk进程,其中一个进程为leader,余下的进程为follower。
当follower最初与leader建立连接时,它们之间会传输相当多的数据,尤其是follower的数据落后leader很多。initLimit配置follower与leader之间建立连接后进行同步的最长时间。
syncLimit
配置follower和leader之间发送消息,请求和应答的最大时间长度。
tickTime
tickTime则是上述两个超时配置的基本单位,例如对于initLimit,其配置值为5,说明其超时时间为 2000ms * 5 = 10秒。
server.id=host:port1:port2
其中id为一个数字,表示zk进程的id,这个id也是dataDir目录下myid文件的内容。
host是该zk进程所在的IP地址,port1表示follower和leader交换消息所使用的端口,port2表示选举leader所使用的端口。
dataDir
其配置的含义跟单机模式下的含义类似,不同的是集群模式下还有一个myid文件。myid文件的内容只有一行,且内容只能为1 - 255之间的数字,这个数字亦即上面介绍server.id中的id,表示zk进程的id。
注意
如果仅为了测试部署集群模式而在同一台机器上部署zk进程,server.id=host:port1:port2配置中的port参数必须不同。但是,为了减少机器宕机的风险,强烈建议在部署集群模式时,将zk进程部署不同的物理机器上面。
5.2 启动
假如我们打算在三台不同的机器 192.168.229.160,192.168.229.161,192.168.229.162上各部署一个zk进程,以构成一个zk集群。
三个zk进程均使用相同的 zoo.cfg 配置:
tickTime=2000
dataDir=/home/myname/zookeeper
clientPort=2181
initLimit=5
syncLimit=2
server.1=192.168.229.160:2888:3888
server.2=192.168.229.161:2888:3888
server.3=192.168.229.162:2888:3888
在三台机器dataDir目录( /home/myname/zookeeper 目录)下,分别生成一个myid文件,其内容分别为1,2,3。然后分别在这三台机器上启动zk进程,这样我们便将zk集群启动了起来。
5.3 连接
可以使用以下命令来连接一个zk集群:
bin/zkCli.sh -server 192.168.229.160:2181,192.168.229.161:2181,192.168.229.162:2181

 

ZooKeeper是一种高性能,可扩展的服务,虽然读取速度比写入快,但是读取和写入操作都设计的极为快速,这样做的原因是在读取的情况下,ZooKeeper可能会提供较旧的数据
为分布式应用提供高效、高可用的分布式协调服务,提供了诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知和分布式锁等分布式基础服务
Zab协议是Zookeeper保证数据一致性的核心算法,Zab借鉴了Paxos算法,但又不像Paxos那样,是一种通用的分布式一致性算法,基于该协议,zk实现了一种主备模型(即Leader和Follower模型),保证了事务的最终一致性
数据一致性是靠Paxos算法保证的 Paxos可以说是分布式一致性算法的鼻祖 是ZooKeeper的基础
1,首先客户端所有的写请求都由一个Leader接收,而其余的都是Follower(从者)
2,Leader 负责将一个客户端事务请求,转换成一个 事务Proposal,并将该 Proposal 分发给集群中所有的 Follower(备份) ,也就是向所有 Follower 节点发送数据广播请求(或数据复制)记住这里发送给的不光是数据本身,还有其他的,相当于包装
3,分发之后Leader需要等待所有Follower的反馈(Ack请求),在Zab协议中,只要超过半数的Follower进行了正确的反馈后(也就是收到半数以上的Follower的Ack请求),那么 Leader 就会再次向所有的 Follower发送 Commit 消息,要求其将上一个 事务proposal 进行提交。

CAP理论
在理论计算机科学中,CAP定理(CAP theorem),又被称作布鲁尔定理(Brewer’s theorem),它指出对于一个分布式计算系统来说,不可能同时满足以下三点:
一致性(Consistence) (等同于所有节点访问同一份最新的数据副本)
可用性(Availability)(每次请求都能获取到非错的响应——但是不保证获取的数据为最新数据)
分区容错性(Network partitioning)(以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。)
根据定理,分布式系统只能满足三项中的两项而不可能满足全部三项。理解CAP理论的最简单方式是想象两个节点分处分区两侧。允许至少一个节点更新状态会导致数据不一致,即丧失了C性质。如果为了保证数据一致性,将分区一侧的节点设置为不可用,那么又丧失了A性质。除非两个节点可以互相通信,才能既保证C又保证A,这又会导致丧失P性质。

对于zookeeper来说,它实现了A可用性、P分区容错性、C中的写入强一致性,丧失的是C中的读取一致性。
如果客户端A将znode / a的值从0设置为1,则告诉客户端B读取/ a,则客户端B可以读取旧值0,这取决于它连接到的服务器。如果客户端A和客户端B读取相同的值很重要,则客户端B应该在执行读取之前从ZooKeeper API方法调用sync()方法。

CAP 理论告诉我们,一个分布式系统不可能同时满足一致性(C:Consistency),可用性(A: Availability)和分区容错性(P:Partition tolerance)这三个基本需求,最多只能同时满足其中的2个。
C(Consistence)一致性 指数据在多个副本之间能够保持一致的特性(严格的一致性)
A(Availability)可用性 指系统提供的服务必须一直处于可用的状态,每次请求都能获取到非错的响应——但是不保证获取的数据为最新数据
P(Network partitioning)分区容错性分布式系统在遇到任何网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务,除非整个网络环境都发生了故障

什么是分区?
在分布式系统中,不同的节点分布在不同的子网络中,由于一些特殊的原因,这些子节点之间出现了网络不通的状态,但他们的内部子网络是正常的。从而导致了整个系统的环境被切分成了若干个孤立的区域。这就是分区。
所以,最多满足两个条件:
CA,一致性,可用性 满足原子和可用,放弃分区容错。说白了,就是一个整体的应用
CP,一致性,分区容错性 满足原子和分区容错,也就是说,要放弃可用。当系统被分区,为了保证原子性,必须放弃可用性,让服务停用
AP,可用性,分区容错性 满足可用性和分区容错,当出现分区,同时为了保证可用性,必须让节点继续对外服务,这样必然导致失去原子性

BASE:全称:Basically Available(基本可用),Soft state(软状态),和 Eventually consistent(最终一致性)三个短语的缩写,来自 ebay 的架构师提出。
Base 理论是对 CAP 中一致性和可用性权衡的结果,其来源于对大型互联网分布式实践的总结,是基于 CAP 定理逐步演化而来的。其核心思想是:
既是无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。

客户端监听指定路径下数据变化,获取配置数据

通过程序操作的才被监控到

 

curator 工具包封装的API帮助我们实现分布式锁。
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>4.0.1</version>
</dependency>

排他锁(Exclusive Locks)又被称为写锁或独占锁实现方式:
利用 zookeeper 的同级节点的唯一性特性,在需要获取排他锁时,所有的客户端试图通过调用 create() 接口,在 /exclusive_lock 节点下创建临时子节点 /exclusive_lock/lock,
最终只有一个客户端能创建成功,那么此客户端就获得了分布式锁。同时,所有没有获取到锁的客户端可以在 /exclusive_lock 节点上注册一个子节点变更的 watcher 监听事件,
以便重新争取获得锁。 public class InterprocessLock { public static void main(String[] args) { CuratorFramework zkClient = getZkClient(); String lockPath = "/lock"; InterProcessMutex lock = new InterProcessMutex(zkClient, lockPath); //模拟50个线程抢锁 for (int i = 0; i < 50; i++) { new Thread(new TestThread(i, lock)).start(); } } static class TestThread implements Runnable { private Integer threadFlag; private InterProcessMutex lock; public TestThread(Integer threadFlag, InterProcessMutex lock) { this.threadFlag = threadFlag; this.lock = lock; } @Override public void run() { try { lock.acquire(); System.out.println("第"+threadFlag+"线程获取到了锁"); //等到1秒后释放锁 Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); }finally { try { lock.release(); } catch (Exception e) { e.printStackTrace(); } } } } private static CuratorFramework getZkClient() { String zkServerAddress = "192.168.3.39:2181"; ExponentialBackoffRetry retryPolicy = new ExponentialBackoffRetry(1000, 3, 5000); CuratorFramework zkClient = CuratorFrameworkFactory.builder() .connectString(zkServerAddress) .sessionTimeoutMs(5000) .connectionTimeoutMs(5000) .retryPolicy(retryPolicy) .build(); zkClient.start(); return zkClient; } }

  

 

      <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.7.0</version>
        </dependency>
        <dependency>
            <groupId>com.github.sgroschupf</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.1</version>
        </dependency>


1,场景一-监听配置数据变化
public class ZkConfigClient implements Runnable {
    private String nodePath = "/cc";
    @Override
    public void run() {
        ZkClient zkClient = new ZkClient(new ZkConnection("192.168.89.134:2181", 5000));
        System.out.println("zkClient:"+zkClient);
        while (!zkClient.exists(nodePath)) {
            System.out.println("配置节点不存在!");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        zkClient.subscribeDataChanges("/cc", new IZkDataListener() {
            @Override
            public void handleDataDeleted(String path) throws Exception {
                System.out.println("删除的节点为:" + path);
            }

            @Override
            public void handleDataChange(String path, Object data) throws Exception {
                System.out.println("变更的节点为:" + path + ", 变更内容为:" + data);
            }
        });
        zkClient.subscribeChildChanges("/cc", new IZkChildListener() {
            @Override
            public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
                System.out.println("parentPath: " + parentPath);
                System.out.println("currentChilds: " + currentChilds);
            }
        });

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        zkClient.writeData("/cc", "456", -1);
    }
}  

main方法
        SpringApplication.run(SampleController.class, args);
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        // 模拟多个客户端获取配置
        executorService.submit(new ZkConfigClient());



    public void syncConfigToZookeeper() {
        if(zkClient == null) {
            zkClient = new ZkClient("192.168.89.134:2181");
        }
        if(!zkClient.exists(nodePath)) {
            zkClient.createPersistent(nodePath);
        }
        zkClient.writeData(nodePath, commonConfig);
    }


2,场景二-分布式锁
首先zookeeper中我们可以创建一个/distributed_lock持久化节点
然后再在/distributed_lock节点下创建自己的临时顺序节点,比如:/distributed_lock/task_00000000008
获取所有的/distributed_lock下的所有子节点,并排序
判读自己创建的节点是否最小值(第一位)
如果是,则获取得到锁,执行自己的业务逻辑,最后删除这个临时节点。
如果不是最小值,则需要监听自己创建节点前一位节点的数据变化,并阻塞。
当前一位节点被删除时,我们需要通过递归来判断自己创建的节点是否在是最小的,如果是则执行5);如果不是则执行6)(就是递归循环的判断)

public class DistributedLock {
    // 常亮
    static class Constant {
        private static final int SESSION_TIMEOUT = 10000;
        private static final String CONNECTION_STRING = "127.0.0.1:2181";
        private static final String LOCK_NODE = "/distributed_lock";
        private static final String CHILDREN_NODE = "/task_";
    }

    private ZkClient zkClient;

    public DistributedLock() {
        // 连接到Zookeeper
        zkClient = new ZkClient(new ZkConnection(Constant.CONNECTION_STRING));
        if(!zkClient.exists(Constant.LOCK_NODE)) {
            zkClient.create(Constant.LOCK_NODE, "分布式锁节点", CreateMode.PERSISTENT);
        }
    }

    public String getLock() {
        try {
            // 1。在Zookeeper指定节点下创建临时顺序节点
            String lockName = zkClient.createEphemeralSequential(Constant.LOCK_NODE + Constant.CHILDREN_NODE, "");
            // 尝试获取锁
            acquireLock(lockName);
            return lockName;
        } catch(Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * 获取锁
     * @throws InterruptedException
     */
    public Boolean acquireLock(String lockName) throws InterruptedException {
        // 2.获取lock节点下的所有子节点
        List<String> childrenList = zkClient.getChildren(Constant.LOCK_NODE);
        // 3.对子节点进行排序,获取最小值
        Collections.sort(childrenList, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return Integer.parseInt(o1.split("_")[1]) - Integer.parseInt(o2.split("_")[1]);
            }

        });
        // 4.判断当前创建的节点是否在第一位
        int lockPostion = childrenList.indexOf(lockName.split("/")[lockName.split("/").length - 1]);
        if(lockPostion < 0) {
            // 不存在该节点
            throw new ZkNodeExistsException("不存在的节点:" + lockName);
        } else if (lockPostion == 0) {
            // 获取到锁
            System.out.println("获取到锁:" + lockName);
            return true;
        } else if (lockPostion > 0) {
            // 未获取到锁,阻塞
            System.out.println("...... 未获取到锁,阻塞等待 。。。。。。");
            // 5.如果未获取得到锁,监听当前创建的节点前一位的节点
            final CountDownLatch latch = new CountDownLatch(1);
            IZkDataListener listener = new IZkDataListener() {

                @Override
                public void handleDataDeleted(String dataPath) throws Exception {
                    // 6.前一个节点被删除,当不保证轮到自己
                    System.out.println("。。。。。。前一个节点被删除  。。。。。。");
                    acquireLock(lockName);
                    latch.countDown();
                }

                @Override
                public void handleDataChange(String dataPath, Object data) throws Exception {
                    // 不用理会
                }
            };
            try {
                zkClient.subscribeDataChanges(Constant.LOCK_NODE + "/" + childrenList.get(lockPostion - 1), listener);
                latch.await();
            } finally {
                zkClient.unsubscribeDataChanges(Constant.LOCK_NODE + "/" + childrenList.get(lockPostion - 1), listener);
            }
        }
        return false;
    }

    /**
     * 释放锁(删除节点)
     *
     * @param lockName
     */
    public void releaseLock(String lockName) {
        zkClient.delete(lockName);
    }

    public void closeZkClient() {
        zkClient.close();
    }
}

@SpringBootApplication
public class ZookeeperDemoApplication {

    public static void main(String[] args) throws InterruptedException {
        SpringApplication.run(ZookeeperDemoApplication.class, args);

        DistributedLock lock = new DistributedLock();
        String lockName = lock.getLock();
        /**
         * 执行我们的业务逻辑  != null 则获取到锁
         */
        if(lockName != null) {
            lock.releaseLock(lockName);
        }

        lock.closeZkClient();
    }
}
分布式队列
首先利用Zookeeper中临时顺序节点的特点
当生产者创建节点生产时,需要判断父节点下临时顺序子节点的个数,如果达到了上限,则阻塞等待;如果没有达到,就创建节点。
当消费者获取节点时,如果父节点中不存在临时顺序子节点,则阻塞等待;如果有子节点,则获取执行自己的业务,执行完毕后删除该节点即可。
获取时获取最小值,保证FIFO特性。


public interface AppConstant {
	static String ZK_CONNECT_STR = "127.0.0.1:2181";
	static String NODE_PATH = "/mailbox";
	static String CHILD_NODE_PATH = "/mail_";
	static int MAILBOX_SIZE = 10;
}
 
public class MailConsumer implements Runnable, AppConstant{
 
	private ZkClient zkClient;
	private Lock lock;
	private Condition condition;
	
	public MailConsumer() {
		lock = new ReentrantLock();
		condition = lock.newCondition();
		zkClient = new ZkClient(new ZkConnection(ZK_CONNECT_STR));
		System.out.println("sucess connected to zookeeper server!");
		// 不存在就创建mailbox节点
		if(!zkClient.exists(NODE_PATH)) {
			zkClient.create(NODE_PATH, "this is mailbox", CreateMode.PERSISTENT);
		}
	}
 
	@Override
	public void run() {
		IZkChildListener listener = new IZkChildListener() {		
			@Override
			public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
				System.out.println("Znode["+parentPath + "] size:" + currentChilds.size());
				// 还是要判断邮箱是否为空
				if(currentChilds.size() > 0) {
					// 唤醒等待的线程
					try {
						lock.lock();
						condition.signal();
					} catch (Exception e) {
						e.printStackTrace();
					} finally {
						lock.unlock();
					}
				}
			}
		};
		// 监视子节点的改变,不用放用while循环中,监听一次就行了,不需要重复绑定
		zkClient.subscribeChildChanges(NODE_PATH, listener);
		try {
			//循环随机发送邮件模拟真是情况
			while(true) {
				// 判断是否可以发送邮件
				checkMailReceive();
				// 接受邮件
				List<String> mailList = zkClient.getChildren(NODE_PATH);
				// 如果mailsize==0,也没有关系;可以直接循环获取就行了
				if(mailList.size() > 0) {
					Collections.sort(mailList, new Comparator<String>() {
						@Override
						public int compare(String o1, String o2) {
							return Integer.parseInt(o1.split("_")[1]) - Integer.parseInt(o2.split("_")[1]);
						}
					});
					// 模拟邮件处理(0-1S)
					TimeUnit.MILLISECONDS.sleep(new Random().nextInt(1000));
					zkClient.delete(NODE_PATH + "/" + mailList.get(0));
					System.out.println("mail has been received:" + NODE_PATH + "/" + mailList.get(0));
				}
			} 
		}catch (Exception e) {
			e.printStackTrace();
		} finally {
			zkClient.unsubscribeChildChanges(NODE_PATH, listener);
		}
	}
 
	private void checkMailReceive() {
		try {
			lock.lock();
			// 判断邮箱是为空
			List<String> mailList = zkClient.getChildren(NODE_PATH);
			System.out.println("mailbox size: " + mailList.size());
			if(mailList.size() == 0) {
				// 邮箱为空,阻塞消费者,直到邮箱有邮件
				System.out.println("mailbox is empty, please wait 。。。");
				condition.await();
				// checkMailReceive();
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
}
 
public class MailProducer implements Runnable, AppConstant{
	
	private ZkClient zkClient;
	private Lock lock;
	private Condition condition;
	
	/**
	 * 初始化状态
	 */
	public MailProducer() {
		lock = new ReentrantLock();
		condition = lock.newCondition();
		zkClient = new ZkClient(new ZkConnection(ZK_CONNECT_STR));
		System.out.println("sucess connected to zookeeper server!");
		// 不存在就创建mailbox节点
		if(!zkClient.exists(NODE_PATH)) {
			zkClient.create(NODE_PATH, "this is mailbox", CreateMode.PERSISTENT);
		}
	}
 
	@Override
	public void run() {
		IZkChildListener listener = new IZkChildListener() {		
			@Override
			public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
				System.out.println("Znode["+parentPath + "] size:" + currentChilds.size());
				// 还是要判断邮箱是否已满
				if(currentChilds.size() < MAILBOX_SIZE) {
					// 唤醒等待的线程
					try {
						lock.lock();
						condition.signal();
					} catch (Exception e) {
						e.printStackTrace();
					} finally {
						lock.unlock();
					}
				}
			}
		};
		// 监视子节点的改变,不用放用while循环中,监听一次就行了,不需要重复绑定
		zkClient.subscribeChildChanges(NODE_PATH, listener);
		try {
			//循环随机发送邮件模拟真是情况
			while(true) {
				// 判断是否可以发送邮件
				checkMailSend();
				// 发送邮件
				String cretePath = zkClient.createEphemeralSequential(NODE_PATH + CHILD_NODE_PATH, "your mail");
				System.out.println("your mail has been send:" + cretePath);
				// 模拟随机间隔的发送邮件(0-10S)
				TimeUnit.MILLISECONDS.sleep(new Random().nextInt(1000));
			} 
		}catch (Exception e) {
			e.printStackTrace();
		} finally {
			zkClient.unsubscribeChildChanges(NODE_PATH, listener);
		}
	}
	
	private void checkMailSend() {
		try {
			lock.lock();
			// 判断邮箱是否已满
			List<String> mailList = zkClient.getChildren(NODE_PATH);
			System.out.println("mailbox size: " + mailList.size());
			if(mailList.size() >= MAILBOX_SIZE) {
				// 邮箱已满,阻塞生产者,直到邮箱有空间
				System.out.println("mailbox is full, please wait 。。。");
				condition.await();
				checkMailSend();
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
}
服务发现,负载均衡

public class ServiceProvider {
	// 静态常量
	static String ZK_CONNECT_STR = "127.0.0.1:2181";
	static String NODE_PATH = "/service";
	static String SERIVCE_NAME = "/myService";
	
	private ZkClient zkClient;
	
	public ServiceProvider() {
		zkClient = new ZkClient(new ZkConnection(ZK_CONNECT_STR));
		System.out.println("sucess connected to zookeeper server!");
		// 不存在就创建NODE_PATH节点
		if(!zkClient.exists(NODE_PATH)) {
			zkClient.create(NODE_PATH, "this is mailbox", CreateMode.PERSISTENT);
		}
	}
	
	public void registryService(String localIp, Object obj) {
		if(!zkClient.exists(NODE_PATH + SERIVCE_NAME)) {
			zkClient.create(NODE_PATH + SERIVCE_NAME, "provider services list", CreateMode.PERSISTENT);
		}
		// 对自己的服务进行注册
		zkClient.createEphemeral(NODE_PATH + SERIVCE_NAME + "/" + localIp, obj);
		System.out.println("注册成功![" + localIp + "]");
	}	
}
 
/**
 * 消费者,通过某种均衡负载算法选择某一个提供者
 * 
 * @author Administrator
 *
 */
public class ServiceConsumer {
	// 静态常量
	static String ZK_CONNECT_STR = "127.0.0.1:2181";
	static String NODE_PATH = "/service";
	static String SERIVCE_NAME = "/myService";
	
	private List<String> serviceList = new ArrayList<String>();
	
	private ZkClient zkClient;
	
	public ServiceConsumer() {
		zkClient = new ZkClient(new ZkConnection(ZK_CONNECT_STR));
		System.out.println("sucess connected to zookeeper server!");
		// 不存在就创建NODE_PATH节点
		if(!zkClient.exists(NODE_PATH)) {
			zkClient.create(NODE_PATH, "this is mailbox", CreateMode.PERSISTENT);
		}
	}
	
	/**
	 * 订阅服务
	 */
	public void subscribeSerivce() {
		serviceList = zkClient.getChildren(NODE_PATH + SERIVCE_NAME);
		zkClient.subscribeChildChanges(NODE_PATH + SERIVCE_NAME, new IZkChildListener() {
			@Override
			public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
				serviceList = currentChilds;
			}
		});
	}
	
	/**
	 * 模拟调用服务
	 */
	public void consume() {
		//负载均衡算法获取某台机器调用服务
		int index = new Random().nextInt(serviceList.size());
		System.out.println("调用[" + NODE_PATH + SERIVCE_NAME + "]服务:" + serviceList.get(index));
	}
}

  

  

  

 

posted @ 2021-09-04 18:20  XUMT111  阅读(582)  评论(0)    收藏  举报