zookeeper(3)-分布式配置,分布式锁

目录

1. 分布式配置

2. 分布式锁

 


zookeeper场景

两个小例子:实现获取分布式配置,实现分布式锁

1. 分布式配置

当在zk中维护一个配置时,每个服务器是怎么知道zk中配置发生了变化呢。

原理: 从zk中获取path的配置,并且监控这个配置的path,如果该path发生变化,则触发监控事件,重新从zk中获取数据。所以配置总是动态的,只有path发生修改,就会触发事件通知配置信息发生修改。


1. 分布式配置 类(用来接收配置信息的)

package com.hww.confcenter;


/**
 * 用来存储配置信息的实体类
 */
public class MyConfig {

    //配置信息,例如ip,端口等一些配置信息的string 串
    public String config;

    public String getConfig() {
        return config;
    }

    public void setConfig(String config) {
        this.config = config;
    }
}

 

2. 使用配置的类。也就是真正的服务类,在这个类中我们获取配置,使用配置。

由于没有实际的应用场景,我们这里打印一下配置信息就可以了

(1)获取zk连接

(2)将所有获取数据的工作交给CallbackWatch类,在这个类中我们只需要await,等待获取数据。而真正的获取数据,并修改配置类的事情就交给callbackWatch的类,一旦数据赋值到配置对象中,则使用配置即可。

(3)从MyConfig对象中获取配置信息,并打印。

(4)一旦配置信息发生变化,触发Watch事件,重新get数据,并赋值给MyConfig对象。然后从MyConfig对象中获取配置使用。

package com.hww.confcenter;

import org.apache.zookeeper.ZooKeeper;

import java.util.concurrent.CountDownLatch;

import static java.lang.Thread.sleep;

/**
 * 模拟场景:分布式配置
 *
 * 真正我们使用的类,工作的类。在该类中获取配置信息,做业务。
 * 
 *
 * 1.获取zk
 *
 * 2. 等一会
 *      2.1判断目录是否存在,回调获取数据,并将数据set到MyConfig对象中
 *      2.2如果目录发生变化,触发事件,再次获取数据赋值给MyConfig中。
 *
 *
 * 3.从发MyConfig对象中取出值
 *
 *
 * 
 *
 */
public class MyService {


    public static void main(String[] args) {


        ZkUtils zkUtils = new ZkUtils();
        MyConfig myconfig = new MyConfig();
        ZooKeeper zk = zkUtils.getZk();
        CallbackWatch callbackWatch = new CallbackWatch();
        callbackWatch.setZk(zk);
        callbackWatch.setMyConfig(myconfig);

        callbackWatch.await();

        while (true){
            try {
                sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            String config = myconfig.getConfig();
            System.out.println(config);
        }
    }
}

3. 真正从zk中获取配置的类

在这个类中,我们需要从zk中获取数据并赋值到配置类,

同时监控path,当数据发生变化时,触发事件

(一下用异步回调的方式实现)

package com.hww.confcenter;

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

import java.util.concurrent.CountDownLatch;

public class CallbackWatch implements Watcher, AsyncCallback.DataCallback, AsyncCallback.StatCallback {


    private ZooKeeper zk;
    private MyConfig myConfig;
    private CountDownLatch countDownLatch = new CountDownLatch(1);
    public ZooKeeper getZk() {
        return zk;
    }

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

    public MyConfig getMyConfig() {
        return myConfig;
    }

    public void setMyConfig(MyConfig myConfig) {
        this.myConfig = myConfig;
    }

    public CountDownLatch getCountDownLatch() {
        return countDownLatch;
    }

    public void setCountDownLatch(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }

    /**
     *
     * @param event
     * 触发事件
     *
     */
    @Override
    public void process(WatchedEvent event) {
        switch (event.getType()) {
            case None:
                break;
            case NodeCreated:
                break;
            case NodeDeleted:
                break;
            case NodeDataChanged:
                zk.getData("/testConfig",this,this,"123");
//                    getData();
                break;
            case NodeChildrenChanged:
                break;
            case DataWatchRemoved:
                break;
            case ChildWatchRemoved:
                break;
            case PersistentWatchRemoved:
                break;
        }
    }

    /**
     *
     * @param rc
     * @param path
     * @param ctx
     * @param data
     * @param stat
     *
     * 获取数据回调
     */
    @Override
    public void processResult(int rc, String path, Object ctx, byte[] data, Stat stat) {
        String s = new String(data);
        System.out.println("回调返回"+s);
        myConfig.setConfig(s);
        countDownLatch.countDown();
    }

    /**
     *
     * @param rc
     * @param path
     * @param ctx
     * @param stat
     *
     * exits异步回调
     *
     */
    @Override
    public void processResult(int rc, String path, Object ctx, Stat stat) {
        if(stat!=null){
            zk.getData("/testConfig",this,this,"123");
        }
    }


    public void await(){

        zk.exists("/testConfig",this,this,"123");
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

4. 用到的工具类

获取zk连接的工具类

package com.hww.confcenter;

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 javax.swing.*;
import java.io.IOException;

public class ZkUtils {

    ZooKeeper zk;
    String connect = "127.0.0.1:2181";


    public ZooKeeper getZk(){

        try {
            zk = new ZooKeeper(connect,3000,new DefaultWatcher());
        } catch (IOException e) {
            e.printStackTrace();
        }
        return zk;
    }
}

defaultWatch 用来监控Session级别的事件。用于创建zk连接时的watcher。当创建zk连接时,会有一个defaultWatcher存储到WatcherManage中,作为全局的默认watcher。

package com.hww.confcenter;

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

public class DefaultWatcher implements Watcher {
    @Override
    public void process(WatchedEvent event) {


        switch (event.getState()) {
            case Unknown:
                break;
            case Disconnected:
                break;
            case NoSyncConnected:
                break;
            case SyncConnected:
                break;
            case AuthFailed:
                break;
            case ConnectedReadOnly:
                break;
            case SaslAuthenticated:
                break;
            case Expired:
                break;
            case Closed:
                break;
        }

        switch (event.getType()) {
            case None:
                break;
            case NodeCreated:
                break;
            case NodeDeleted:
                break;
            case NodeDataChanged:
                break;
            case NodeChildrenChanged:
                break;
            case DataWatchRemoved:
                break;
            case ChildWatchRemoved:
                break;
            case PersistentWatchRemoved:
                break;
        }

    }
}

 

 

 

 

2. 分布式锁

 

分布式锁,主要是为了解决并发问题。

例如,比如我们在修改数据的时候,如果当前值i= 0,我们期望每次拿到数据的时候进行加1操作,A线程拿到i加1变成1,B线程拿到i(此时i=1), 然后加1,变为2。

但是如果并发的情况,A线程和B线程都拿到i=0,此时A和B都加1,i=1。

所以我们更期望的是线程一个一个的通过。比如100个人过独木桥,希望是一个人过去,下一个人在过。

主要原理:利用的zk中临时序列节点,每一个线程(或者客户端)在zk中创建一个临时序列节点,然后阻塞,只能让这些线程对应的节点序列最小的那个节点通过(也就是说只能让最小的节点的线程获取到锁),其他线程都等着,

但是这些阻塞的线程对应临时序列节点需要做件事,就是监控序列中上一个序列值对应的节点(由于临时节点是有顺序的,所以监控上一个节点 4监控3,3监控2,2监控1.....)。

直到上个线程工作完成后,释放锁(实际操作就是删除上个线程对应的那个临时节点),当删除这个节点的时候,监控这节点的线程(或者客户端)就能收到监控事件,在一次去找当前最小序列的那个节点,让其通过。以此类推,实现分布式锁。

详细说明

1.多个线程(或者客户端)同时创建带序列的临时节点,然后都await(等待)。
eg:3个客户端(a,b,c)同时获取锁,都创建有序列的临时节点 a->/lock1  b->/lock2 c->/lock3,并且等待

2.异步创建成功回调时,获取children节点(获取当前目录的所有临时节点)/

3.异步获取children节点成功,进行回调时,识别出最小的序列节点,进行countDown,让最小的序列节点对应的线程继续往下执行
其他线程需要进行watcher,序列大的节点对应的线程(客户端) 去监控(watcher)前一个序列节点。
eg:此时lock1最小,所以lock1进行countDown,lock1对应的线程a跳过await继续执行。而b线程监控(watcher) a线程创建的临时节点,c线程
监控b线程创建的临时节点

4.当第一个线程执行完后,进行unlock解锁,也就是删除临时节点,那么监控这个被删除的临时节点的线程就会收到事件通知,在通知里再一次
去回调,识别当前最小的序列节点对应的线程进行countDown,跳出等待,往下执行,以此类推。

eg:当a线程执行完成,此时删除a线程的临时节点/lock1,删除/lock1会触发事件,b线程就知道/lock1被删除了,重新进行识别最小序列,以此类推。

总结:
1. 要注意这里只是模拟高并发,这里的线程相当于客户端的请求,可能同一时间有多个客户同时访问造成并发。
2. 这里要注意watcher是客户端去监控zk中的节点,一定要分清楚watch是谁与谁的关系,是存在客户端和zk节点之间的关系。
3. 临时节点和session会话相关,如果客户端关闭,临时节点也会消失。

 

1. 业务逻辑类

package com.hww.lock;

import org.apache.zookeeper.ZKUtil;
import org.apache.zookeeper.ZooKeeper;

import static java.lang.Thread.sleep;


/**
 *
 * 实现分布式锁
 *
 * 模拟多个客户端的并发情况
 *
 *
 *
 *
 */
public class LockService {

    public static void main(String[] args) {

        ZkUtils zkUtils = new ZkUtils();
        final ZooKeeper zk = zkUtils.getZk();

        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {

                    //真正工作的代码
                    Thread thread1 = Thread.currentThread();
                    String thread1Name = thread1.getName();
                    LockUtils lockUtils = new LockUtils();
                    lockUtils.setZk(zk);
                    lockUtils.setThreaName(thread1Name);
                    //上锁
                    lockUtils.tryLock();
                    try {
                        sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //获取到锁,开始工作
                    System.out.println(thread1Name+":工作。。。。。。。");
                    //解锁
                    lockUtils.unLock();

                }
            });
            thread.start();
        }

        while(true){

        }



    }

}

2. 实现锁的工具类

package com.hww.lock;

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

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

public class LockUtils implements AsyncCallback.StringCallback , AsyncCallback.ChildrenCallback , Watcher, AsyncCallback.StatCallback {

    private ZooKeeper zk;
    private CountDownLatch countDownLatch=new CountDownLatch(1);
    private String pathName;
    private String threaName;
    public ZooKeeper getZk() {
        return zk;
    }

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

    public CountDownLatch getCountDownLatch() {
        return countDownLatch;
    }

    public void setCountDownLatch(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }

    public String getThreaName() {
        return threaName;
    }

    public void setThreaName(String threaName) {
        this.threaName = threaName;
    }

    /**
     * 上锁
     *
     * 1.多个线程(或者客户端)同时创建带序列的临时节点,然后都await(等待)。
     * eg:3个客户端(a,b,c)同时获取锁,都创建有序列的临时节点 a->/lock1  b->/lock2 c->/lock3,并且等待
     *
     * 2.异步创建成功回调时,获取children节点(获取当前目录的所有临时节点)/
     *
     * 3.异步获取children节点成功,进行回调时,识别出最小的序列节点,进行countDown,让最小的序列节点对应的线程继续往下执行
     * 其他线程需要进行watcher,序列大的节点对应的线程(客户端) 去监控(watcher)前一个序列节点。
     * eg:此时lock1最小,所以lock1进行countDown,lock1对应的线程a跳过await继续执行。而b线程监控(watcher) a线程创建的临时节点,c线程
     * 监控b线程创建的临时节点
     *
     * 4.当第一个线程执行完后,进行unlock解锁,也就是删除临时节点,那么监控这个被删除的临时节点的线程就会收到事件通知,在通知里再一次
     * 去回调,识别当前最小的序列节点对应的线程进行countDown,跳出等待,往下执行,以此类推。
     *
     * eg:当a线程执行完成,此时删除a线程的临时节点/lock1,删除/lock1会触发事件,b线程就知道/lock1被删除了,重新进行识别最小序列,以此类推。
     *
     * 总结:
     * 1. 要注意这里只是模拟高并发,这里的线程相当于客户端的请求,可能同一时间有多个客户同时访问造成并发。
     * 2. 这里要注意watcher是客户端去监控zk中的节点,一定要分清楚watch是谁与谁的关系,是存在客户端和zk节点之间的关系。
     * 3. 临时节点和session会话相关,如果客户端关闭,临时节点也会消失。
     *
     */

    public void tryLock()  {

        //创建临时序列节点
        //多线程过来,所有线程都创建
        zk.create("/lock","aaaa".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL,this,"124");
        //所以线程都等着
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


    }

    /**
     * 解锁
     */
    public void unLock(){

        try {
            zk.delete(pathName,-1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }


    /**
     *
     * @param rc
     * @param path
     * @param ctx
     * @param name
     *
     * 创建的Callback
     *
     *
     */
    @Override
    public void processResult(int rc, String path, Object ctx, String name) {
        if(name!=null){
            //获取临时节点目录下的同级的其他的节点,不需要watch
            zk.getChildren("/",false,this,"123");
            pathName=name;
            System.out.println(threaName+"创建"+pathName);

        }
    }

    /**
     *
     * @param rc
     * @param path
     * @param ctx
     * @param children
     *
     * children的callback
     *
     */
    @Override
    public void processResult(int rc, String path, Object ctx, List<String> children) {

        //获取临时节点,进行排序
        Collections.sort(children);
        //如果是最小的那个节点就shutdown
        //如果这个节点名称在list中的位置为0,就认为是最小的那个
        int i = children.indexOf(pathName.substring(1));
        if(i==0){
            countDownLatch.countDown();
        }else{
            //当前线程去watch前一个节点
            zk.exists("/"+children.get(i-1),this,this,"123");
        }


    }

    /**
     *
     * @param rc
     * @param path
     * @param ctx
     * @param stat
     *
     * exists callback
     *
     */
    @Override
    public void processResult(int rc, String path, Object ctx, Stat stat) {

    }

    /**
     *
     * @param event
     * watcher
     */
    @Override
    public void process(WatchedEvent event) {
        switch (event.getType()) {
            case None:
                break;
            case NodeCreated:
                break;
            case NodeDeleted:
                //当节点删除,则在再次判断children中那个是最下的。
                zk.getChildren("/",false,this,"123");
                break;
            case NodeDataChanged:
                break;
            case NodeChildrenChanged:
                break;
            case DataWatchRemoved:
                break;
            case ChildWatchRemoved:
                break;
            case PersistentWatchRemoved:
                break;
        }
    }
}



3. 工具类,获取zk等

package com.hww.lock;

import com.hww.confcenter.DefaultWatcher;
import org.apache.zookeeper.ZooKeeper;

import java.io.IOException;

public class ZkUtils {

    ZooKeeper zk;
    String connect = "127.0.0.1:2181";


    public ZooKeeper getZk(){

        try {
            zk = new ZooKeeper(connect,3000,new DefaultWatcher());
        } catch (IOException e) {
            e.printStackTrace();
        }
        return zk;
    }
}

 

 

 

 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值