Zookeeper-实战

Master选举

Master选举架构

这里写图片描述

  • 服务注册——各个服务器在启动过程中,首先会到Zookeeper节点下创建一个临时节点,并把自己的基本信息写入到这个临时节点。
  • 服务发现——系统中的其他服务可以通过获取servers节点的子节点列表,来了解当前系统哪些服务器可用。
  • 争抢Master——尝试创建master节点,谁能创建成功,谁就是master,其他的服务器则为Slave。
  • 重新选举——所有服务器必须监听master服务器的删除时间(因为Zookeeper的临时节点hui会随着会话失效而被删除),一旦master节点宕机,其他的节点就可立刻发现,并重新选举。

Master争抢流程

master选举

ZkClient实现服务器Master争抢

就Master争抢做一个简易Demo
- 服务器信息

public class ServerData implements Serializable{

    private int serverId;

    private String serverName;

    public int getServerId() {
        return serverId;
    }

    public void setServerId(int serverId) {
        this.serverId = serverId;
    }

    public String getServerName() {
        return serverName;
    }

    public void setServerName(String serverName) {
        this.serverName = serverName;
    }
}
  • Master选举
public class MasterVote {
    //zkclient客户端
    private ZkClient zkClient;

    //争抢的master节点
    private static final String MASTER_NODE = "/master";

    //连接地址
    private static final String CONNECTION_STRING = "xxx.xxx.xxx.xxx:2181";

    //超时时间
    private static final int SESSION_TIMEOUT=5000;

    //争抢master的服务器节点信息
    private ServerData serverData;

    //争抢到master节点的服务器信息
    private ServerData masterData;

    //服务器开启状态
    private volatile boolean running = false;

    //master节点监听事件
    IZkDataListener dataListener;

    private ScheduledExecutorService scheService = Executors.newScheduledThreadPool(1);

    public MasterVote(ZkClient zkclient,ServerData serverData){
        this.zkClient = zkclient;
        this.serverData = serverData;
        dataListener = new IZkDataListener() {
            @Override
            public void handleDataChange(String s, Object o) throws Exception {

            }

            @Override
            public void handleDataDeleted(String s) throws Exception {
                takeMaster();
            }
        };
    }

    /**
     * 开始争抢master
     */
    public void start(){
        if(running){
            throw new RuntimeException("服务器在启动状态!");
        }
        running = true;
        zkClient.subscribeDataChanges(MASTER_NODE,dataListener);
        takeMaster();
    }

    /**
     * 停止争抢master
     */
    public void stop(){
        if(!running){
            throw new RuntimeException("服务器在停止状态");
        }
        running = false;
        zkClient.subscribeDataChanges(MASTER_NODE,dataListener);
        releaseMaster();
    }

    /**
     * 争抢master节点的具体实现
     */
    private void takeMaster(){
        if(!running){
            throw new RuntimeException("服务器没有启动");
        }
        System.out.println(serverData.getServerName()+"开始抢master节点");
        try {
            //创建master节点成功,该服务器为master,记录master节点信息
            zkClient.createEphemeral(MASTER_NODE,serverData);
            masterData = serverData;
            System.out.println(masterData.getServerName()+"成功抢到master!");
            //每五秒释放一次
            scheService.schedule(new Runnable() {
                @Override
                public void run() {
                    releaseMaster();
                }
            },5,TimeUnit.SECONDS);
        }
        //ZkNode节点已经存在,说明master节点已经存在
        catch (ZkNodeExistsException e) {
            //读取当前master节点的信息
            ServerData currentMasterData = zkClient.readData(MASTER_NODE);
            //读取的过程中master节点已经被释放
            if(currentMasterData==null){
                takeMaster();
            }
//            else {
//                masterData = currentMasterData;
//            }
        }
    }

    /**
     * 释放master节点
     */
    private void releaseMaster(){
        if(checkMaster()){
            zkClient.delete(MASTER_NODE);
            System.out.println("释放===================================");
        }
    }

    /**
     * 校验当前的服务器是否是master
     */
    private boolean checkMaster(){
        try {
            //读取当前master节点数据
            ServerData currentMasterData = zkClient.readData(MASTER_NODE);
            masterData = currentMasterData;
            //master的服务名等于当前争抢的服务器名
            if (masterData.getServerName().equals(masterData.getServerName())) {
                return true;
            }
            else {
                return false;
            }
        }
        catch (ZkNoNodeException e) {
            return false;
        }
        //因网络打断出现的异常
        catch (ZkInterruptedException e) {
            return checkMaster();
        }
        catch (Exception e) {
            return false;
        }
    }
  • 测试
    使用信号量模拟多个服务同时开始争抢
public static void main(String[] args){
        ExecutorService service = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(10);
        for(int i = 0 ; i < 10 ;i++){
            final int idx= i;
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    try{
                        semaphore.acquire();
                        //初始化
                        ZkClient zk = new ZkClient(CONNECTION_STRING,SESSION_TIMEOUT,
                                SESSION_TIMEOUT,new SerializableSerializer());
                        //定义争抢的服务器信息
                        ServerData serverData = new ServerData();
                        serverData.setServerId(idx);
                        serverData.setServerName("#server-"+idx);

                        MasterVote mv = new MasterVote(zk,serverData);
                        //开始争抢
                        mv.start();
                        semaphore.release();
                    }catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            };
            service.execute(runnable);
        }
        service.shutdown();
    }
  • 结果
#server-3开始抢master节点
#server-1开始抢master节点
#server-6开始抢master节点
#server-8开始抢master节点
#server-4开始抢master节点
#server-9开始抢master节点
#server-2开始抢master节点
#server-0开始抢master节点
#server-7开始抢master节点
#server-5开始抢master节点
#server-4成功抢到master!
释放===================================
#server-0开始抢master节点
#server-3开始抢master节点
#server-8开始抢master节点
#server-9开始抢master节点
#server-2开始抢master节点
#server-5开始抢master节点
#server-6开始抢master节点
#server-7开始抢master节点
#server-1开始抢master节点
#server-4开始抢master节点
#server-8成功抢到master!

分布式锁

分布式锁包含:共享锁,排他锁。
- 排他锁(Exclusive Locks)——又称为写锁或独占锁,需要等待其它所有的锁全部释放才能争抢,且仅当持有该锁时才能进行写操作。
- 共享锁(Shared Locks) ——又称为读锁,所有操作都可持有读锁,但这些锁是有序的,根据顺序依次进行读操作。

原生API实现共享锁

获取子节点列表,如果自己创建的节点值最小,则持有锁,并进行操作,待操作完成释放锁;否则等待并监听比自己小的节点。
共享锁
下面使用原生API模拟一次共享锁的竞争过程。
- DistributeSharedLock

public class DistributeSharedLock implements Watcher{
    //原生API
    ZooKeeper zooKeeper = null;
    //定义根节点
    private String root = "/locks";
    //当前获取节点
    private String currentZnode;
    //当前等待节点
    private String waitZnode;
    //发令枪
    private CountDownLatch countDownLatch;
    //连接地址
    private static final String CONNECTION_STRING = "xxx.xxx.xxx.xxx:2181,xxx.xxx.xxx.xxx:2182,xxx.xxx.xxx.xxx:2183";
    //超时时间
    private static final int SESSION_TIMEOUT=5000;

    /**
     * 构造函数
     * @param config
     */
    public DistributeSharedLock(String config){
        try {
            zooKeeper = new ZooKeeper(config,SESSION_TIMEOUT,this);
            //查看root节点是否存在
            Stat stat = zooKeeper.exists(root,false);
            //不存在则创建根节点
            if(stat == null){
                zooKeeper.create(root,new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取锁
     */
    public void lock(){
        if (tryLock()) {
            System.out.println("Thread " + Thread.currentThread().getName()+" hold lock!");
            return;
        }

        try {
            //等待并获取锁
            waitLock(waitZnode,SESSION_TIMEOUT);
        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 释放锁
     */
    public void unLock(){
        System.out.println("Thread " + Thread.currentThread().getName()+" un lock!");
        try {
            zooKeeper.delete(currentZnode,-1);
            currentZnode=null;
            zooKeeper.close();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }

    private boolean tryLock(){
        //   /locks/lock_0000000001
        String spliteStr = "lock_";
        try {
            currentZnode = zooKeeper.create(root+"/"+spliteStr,new byte[0],ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL);
            System.out.println("Thread " + Thread.currentThread().getName()+" create success: " + currentZnode);
            //获取根节点下所有子节点
            List<String> childNodes = zooKeeper.getChildren(root,false);
            //排序
            Collections.sort(childNodes);
            //如果当前节点的值是最小值,则获得锁
            if(currentZnode.equals(root+"/"+childNodes.get(0))){
                return true;
            }

            //否则,监听比自己小的节点
            String subNode = currentZnode.substring(currentZnode.lastIndexOf("/")+1);//lock_0000000003
            waitZnode = childNodes.get(Collections.binarySearch(childNodes,subNode)-1);//lock_0000000002
        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return false;
    }

    private boolean waitLock(String lower,long waitTime) throws KeeperException, InterruptedException {
        Stat stat = zooKeeper.exists(root + "/" + lower, true);
        //存在比自己小的节点,开始等待
        if (stat != null) {
            System.out.println("Thread " + Thread.currentThread().getName() + " waiting for: " + root + "/" + lower);
            this.countDownLatch = new CountDownLatch(1); //实例化计数器,让当前的线程等待
            this.countDownLatch.await(waitTime, TimeUnit.MILLISECONDS);
            this.countDownLatch = null;
        }

        return true;
    }

    /**
     * 比当前节点小的临时节点删除时触发,计数器-1
     * @param watchedEvent
     */
    @Override
    public void process(WatchedEvent watchedEvent) {
        if(this.countDownLatch!=null){ //如果计数器不为空话话,释放计数器锁
            this.countDownLatch.countDown();
        }
    }
}
  • 测试
public static void main(String[] args) {
        ExecutorService executorService= Executors.newCachedThreadPool();
        final Semaphore semaphore=new Semaphore(10);
        for(int i=0;i<10;i++){
            Runnable runnable=new Runnable() {
                @Override
                public void run() {
                    try {
                        semaphore.acquire();
                        DistributeSharedLock sharedLock = new DistributeSharedLock(CONNECTION_STRING);
                        sharedLock.lock();
                        //业务代码
                        Thread.sleep(3000);
                        sharedLock.unLock();
                        semaphore.release();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            executorService.execute(runnable);
        }
        executorService.shutdown();
    }
  • 结果
Thread pool-1-thread-4 create success: /locks/lock_0000000123
Thread pool-1-thread-10 create success: /locks/lock_0000000122
Thread pool-1-thread-7 create success: /locks/lock_0000000121
Thread pool-1-thread-6 create success: /locks/lock_0000000128
Thread pool-1-thread-2 create success: /locks/lock_0000000129
Thread pool-1-thread-5 create success: /locks/lock_0000000130
Thread pool-1-thread-8 create success: /locks/lock_0000000125
Thread pool-1-thread-9 create success: /locks/lock_0000000127
Thread pool-1-thread-3 create success: /locks/lock_0000000126
Thread pool-1-thread-1 create success: /locks/lock_0000000124
Thread pool-1-thread-7 hold lock!
Thread pool-1-thread-4 waiting for: /locks/lock_0000000122
Thread pool-1-thread-6 waiting for: /locks/lock_0000000127
Thread pool-1-thread-5 waiting for: /locks/lock_0000000129
Thread pool-1-thread-8 waiting for: /locks/lock_0000000124
Thread pool-1-thread-2 waiting for: /locks/lock_0000000128
Thread pool-1-thread-9 waiting for: /locks/lock_0000000126
Thread pool-1-thread-10 waiting for: /locks/lock_0000000121
Thread pool-1-thread-1 waiting for: /locks/lock_0000000123
Thread pool-1-thread-3 waiting for: /locks/lock_0000000125
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值