zookeeper-3.案例:分布式配置注册发现、分布式锁、ractive模式编程

zookeeper

ZooKeeper是做分布式协调的,那它协调啥?
配置写在哪里?难道运维人员要登录到每一台机器一台一台地改吗?
可以将配置文件放在一个共享的位置中,比如redis,比如数据库,比如zk,任何一个地方。
zk具有回调机制,就不需要轮询了。
分布式锁的实现,这个zk也可以做,面试常问,虽然可能用不到,但是这道题可以带出很多知识点。

使用ZooKeeper实现分布式配置中心

思路:我们将所有的配置数据用data配置到zk中去,在客户端我们既可以get它,也可以watch它。
一旦外界对这个数据进行了修改,这个修改就会引发watch的回调。

1. 四台机器配好 Zookeeper 集群

2. 项目目录结构

在这里插入图片描述

3. 代码

TestConfig.java,主程序测试入口

package com.msb.zookeeper.config;

import org.apache.zookeeper.ZooKeeper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class TestConfig {
    ZooKeeper zk;

    @Before
    public void conn() {
        zk = ZKUtils.getZK();
    }

    @After
    public void close() {
        try {
            zk.close();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


    @Test
    public void getConf() {
        WatchCallBack watchCallBack = new WatchCallBack();
        watchCallBack.setZk(zk);
        MyConf myConf = new MyConf();
        watchCallBack.setMyConf(myConf);

        watchCallBack.awaitExists();
        //1,节点不存在
        //2,节点存在
        while (true) {
            if (myConf.getConf().equals("")) {
                System.out.println("conf diu le ......");
                watchCallBack.awaitExists();
            } else {
                System.out.println("In getConf Test, myConf is:" + myConf.getConf());
            }

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Myconf.java,用来存放配置。
真正的开发中,配置中的内容可能是一个复杂的json,xml等等。
此处用一个String conf字符串代替。

package com.msb.zookeeper.config;

//这个class是你未来用来存放配置数据的。真正的开发中,配置的内容可能是一个复杂的json,xml等等
public class MyConf {
    private String conf;

    public String getConf() {
        return conf;
    }

    public void setConf(String conf) {
        System.out.println("MyConf.conf is set to: " + conf);
        this.conf = conf;
    }
}

ZKUtils.java,用来配置Zookeeper的Server端服务器地址,以及创建并返回Zookeeper实例

package com.msb.zookeeper.config;

import org.apache.zookeeper.ZooKeeper;

import java.util.concurrent.CountDownLatch;

public class ZKUtils {

    private static ZooKeeper zk;

    // 需要在zk中设置    create /testLock    create /testLock/AppConf     set /testLock/AppConf "hello,conf"
    // ip:port 后面的 /testLock 是指定的根目录,后续所有的操作都在以 /testLock 为根目录的基础上进行
    private static String address = "10.0.0.131:2181,10.0.0.132:2181,10.0.0.133:2181,10.0.0.134:2181/testLock";

    private static DefaultWatch watch = new DefaultWatch();

    private static CountDownLatch init = new CountDownLatch(1);

    public static ZooKeeper getZK() {
        try {
            zk = new ZooKeeper(address, 1000, watch);//new ZooKeeper 对象的时候需要使用到 DefaultWatch
            watch.setLatch(init);
            init.await();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return zk;
    }
}

DefaultWatch.java,在 new ZooKeeper() 对象的时候需要用到,作为参数传入

package com.msb.zookeeper.config;

import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;

import java.util.concurrent.CountDownLatch;

public class DefaultWatch implements Watcher {

    CountDownLatch latch;

    public void setLatch(CountDownLatch latch) {
        this.latch = latch;
    }

    @Override
    public void process(WatchedEvent event) {

        System.out.println(event.toString());

        switch (event.getState()) {
            case Unknown:
                break;
            case Disconnected:
                break;
            case NoSyncConnected:
                break;
            case SyncConnected:
                System.out.println("In DefaultWatch, SyncConnected,连接成功.");
                latch.countDown();
                break;
            case AuthFailed:
                break;
            case ConnectedReadOnly:
                break;
            case SaslAuthenticated:
                break;
            case Expired:
                break;
        }
    }
}

WatchCallBack.java,实现多个接口,既是Watcher,又是Callback。重写了各个回调函数,是整个Reactor模型的核心

package com.msb.zookeeper.config;

import org.apache.zookeeper.AsyncCallback;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;

import java.util.concurrent.CountDownLatch;

public class WatchCallBack implements Watcher, AsyncCallback.StatCallback, AsyncCallback.DataCallback {
    ZooKeeper zk;
    MyConf myConf;
    CountDownLatch latch = new CountDownLatch(1);

    public MyConf getMyConf() {
        return myConf;
    }

    public ZooKeeper getZk() {
        return zk;
    }

    public void setMyConf(MyConf myConf) {
        this.myConf = myConf;
    }

    public void setZk(ZooKeeper zk) {
        this.zk = zk;
    }


    public void awaitExists() {
        System.out.println("awaitExists...");
        zk.exists("/AppConf", this, this, "ABC");
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("awaitExists finish...");
    }

    @Override
    /**
     * DataCallback接口实现
     */
    public void processResult(int rc, String path, Object ctx, byte[] data, Stat stat) {
        if (data != null) {
            String str = new String(data);
            System.out.println("In WatchCallBack, data is: " + str);
            myConf.setConf(str);
            latch.countDown();
        }
    }

    @Override
    /**
     * StatCallback 接口实现
     */
    public void processResult(int rc, String path, Object ctx, Stat stat) {
        if (stat != null) {
            zk.getData("/AppConf", this, this, "sdfs");
        }
    }

    @Override
    /**
     * Watcher 接口实现
     */
    public void process(WatchedEvent event) {
        switch (event.getType()) {
            case None:
                break;
            case NodeCreated:
                //节点被创建事件
                zk.getData("/AppConf", this, this, "sdfs");
                break;
            case NodeDeleted:
                //节点被删除事件,这要根据业务的容忍性,写处理方式
                System.out.println("In WatchCallBack, 节点被删除了");
                myConf.setConf("");
                latch = new CountDownLatch(1);//阻塞
                break;
            case NodeDataChanged:
                //数据被更改
                zk.getData("/AppConf", this, this, "sdfs");
                break;
            case NodeChildrenChanged:
                break;
        }
    }
}

4. 运行 & 测试

运行 Test
在这里插入图片描述
控制台输出,可以看到由于zk.exists("/AppConf", this, this, "ABC");后面加了latch.await();正在阻塞地等待Zookeeper中对应节点的创建。
在这里插入图片描述
用客户端连接Zookeeper
在这里插入图片描述
建指定节点create /testLock/AppConf "foo"]
在这里插入图片描述
检测到配置文件节点的产生

在这里插入图片描述
修改配置文件的节点数据set /testLock/AppConf "bar"
在这里插入图片描述
检测到节点数据的修改
在这里插入图片描述
删除配置文件节点delete /testLock/AppConf
在这里插入图片描述
输出“conf 丢了”,重新进入阻塞等待状态…等待节点的创建
在这里插入图片描述

附:Zookeeper客户端常用命令:

ls /列出根目录所有节点名称
create /testLock/AppConf "foo"创建节点并设置节点内容
set /testLock/AppConf "bar"更新节点内容
delete /testLock/AppConf删除节点

使用Zookeeper实现分布式锁

在这里插入图片描述

1. 和前面一样,配好4台Zookeeper集群

2. 目录结构

在这里插入图片描述

3、代码

用到了前面的两个:

  • ZKUtils.java,获取zk实例,配置server ip
  • DefaultWatch.java,默认的Watcher,在 new ZooKeeper 对象的时候会用到

另外,增加了:

** TestLock.java**,测试分布式锁的程序入口

package com.msb.zookeeper.lock;

import com.msb.zookeeper.config.ZKUtils;
import org.apache.zookeeper.ZooKeeper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class TestLock {
    ZooKeeper zk;

    @Before
    public void conn() {
        zk = ZKUtils.getZK();
    }

    @After
    public void close() {
        try {
            zk.close();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void lock() {
        for (int i = 0; i < 10; i++) {//模拟10个线程分布在10台机器上
            new Thread() {
                @Override
                public void run() {
                    WatchCallBack watchCallBack = new WatchCallBack();
                    watchCallBack.setZk(zk);
                    String threadName = Thread.currentThread().getName();
                    watchCallBack.setThreadName(threadName);
                    //每一个线程:
                    //抢锁
                    watchCallBack.tryLock();
                    //干活
                    System.out.println(threadName + " working...");
                    try {
                        Thread.sleep(100);//模拟干活需要的时间,如果这里不sleep,会在第一个已经释放了,后面的还没开始watch,看不到第一个释放锁的消息,就断层了
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //释放锁
                    watchCallBack.unLock();
                }
            }.start();
        }
        while (true) {
        }
    }
}

WatchCallBack.java,分布式锁的Watcher,里面维护了一个CountDownLatch。同时,继承了接口实现各种回调方法,所以既是Watcher,也是Callback,在传参的时候直接传this即可

package com.msb.zookeeper.lock;

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;

public class WatchCallBack implements Watcher, AsyncCallback.StringCallback, AsyncCallback.Children2Callback, AsyncCallback.StatCallback {
    ZooKeeper zk;
    String threadName;
    CountDownLatch cc = new CountDownLatch(1);
    String pathName;

    public String getPathName() {
        return pathName;
    }

    public void setPathName(String pathName) {
        this.pathName = pathName;
    }

    public String getThreadName() {
        return threadName;
    }

    public void setThreadName(String threadName) {
        this.threadName = threadName;
    }

    public ZooKeeper getZk() {
        return zk;
    }

    public void setZk(ZooKeeper zk) {
        this.zk = zk;
    }

    /**
     * 通过zk创建顺序节点的方式,获取锁
     */
    public void tryLock() {
        try {
            System.out.println(threadName + " create....");
            zk.create("/lock", threadName.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL, this, "str");
            cc.await();//等待自己是第一个的时候,才能返回
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 释放锁
     */
    public void unLock() {
        try {
            zk.delete(pathName, -1);
            System.out.println(threadName + ": finish work....");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }


    @Override
    //process in interface Watcher
    public void process(WatchedEvent event) {
        //如果第一台机器拿到的锁释放了,只有第二台机器收到了回调事件
        //如果,不是第一台机器,而是其他的某一台机器挂了,也能造成他后边的那台机器收到这个通知,从而让他后边那台机器跟去watch挂掉这个哥们前边的机器
        switch (event.getType()) {
            case None:
                break;
            case NodeCreated:
                break;
            case NodeDeleted:
                zk.getChildren("/", false, this, "str");//去调用getChildren的回调方法
                break;
            case NodeDataChanged:
                break;
            case NodeChildrenChanged:
                break;
        }

    }

    @Override
    //StringCallback
    public void processResult(int rc, String path, Object ctx, String name) {
        if (name != null) {//接收zk帮你创建的顺序节点的名称
            System.out.println(threadName + " create node : " + name);
            pathName = name;
            zk.getChildren("/", false, this, "str");
        }

    }


    @Override
    //zk.getChildren 的 callback
    public void processResult(int rc, String path, Object ctx, List<String> children, Stat stat) {
        //能看到自己前边的所有节点 List<String> children,对前面所有的节点名称排序
        Collections.sort(children);
        int index = children.indexOf(pathName.substring(1));//看自己的位置

        //判断自己是否为第一个
        if (index == 0) {
            //如果自己是第一个,执行countdown
            System.out.println(threadName + ": I am first....");
            try {
                zk.setData("/", threadName.getBytes(), -1);//把自己的锁信息写进根目录的node里面去,你可以用get看到,这个步骤为了拖慢一下速度
                cc.countDown();
            } catch (KeeperException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } else {
            //自己不是第一个,则watch前一个
            zk.exists("/" + children.get(index - 1), this, this, "sdf");
        }

    }

    @Override
    // StatCallback
    public void processResult(int rc, String path, Object ctx, Stat stat) {
        //偷懒,这里省略没写
    }
}

4、运行 & 测试

输出结果如下,可以看到10个线程在创建之后,都拿到了自己的自增目录名称,然后按照自己的目录名称顺序依次进行:拿到锁,干活,释放锁。

WatchedEvent state:SyncConnected type:None path:null
In DefaultWatch, SyncConnected,连接成功.
Thread-3 create....
Thread-9 create....
Thread-1 create....
Thread-7 create....
Thread-0 create....
Thread-5 create....
Thread-8 create....
Thread-4 create....
Thread-2 create....
Thread-6 create....
Thread-3 create node : /lock0000000071
Thread-9 create node : /lock0000000072
Thread-6 create node : /lock0000000073
Thread-0 create node : /lock0000000074
Thread-5 create node : /lock0000000075
Thread-7 create node : /lock0000000076
Thread-1 create node : /lock0000000077
Thread-4 create node : /lock0000000078
Thread-8 create node : /lock0000000079
Thread-2 create node : /lock0000000080
Thread-3: I am first....
Thread-3 working...
Thread-3: finish work....
Thread-9: I am first....
Thread-9 working...
Thread-9: finish work....
Thread-6: I am first....
Thread-6 working...
Thread-6: finish work....
Thread-0: I am first....
Thread-0 working...
Thread-0: finish work....
Thread-5: I am first....
Thread-5 working...
Thread-5: finish work....
Thread-7: I am first....
Thread-7 working...
Thread-7: finish work....
Thread-1: I am first....
Thread-1 working...
Thread-1: finish work....
Thread-4: I am first....
Thread-4 working...
Thread-4: finish work....
Thread-8: I am first....
Thread-8 working...
Thread-8: finish work....
Thread-2: I am first....
Thread-2 working...
Thread-2: finish work....

在程序执行的同时,从Zookeeper的Client端不断获取目录内容,看到整个过程大致是这样的:
当一个线程中的任务执行完之后,显示调用了zk.delete(pathName, -1);将节点删除。这样就能触发下一个正在watch它的节点所在的线程,去判断自己是不是在排名的第一个。

  • 如果是第一个,开始干活
  • 如果不是第一个,watch它的前一个(这种情况出现在前一个节点突然挂了的情况)

在这里插入图片描述

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值