Zk实现分布式锁

来源:互联网 发布:python 海森堡模型 编辑:程序博客网 时间:2024/06/05 15:21

Zk实现分布式锁

原理:

在分布式环境中,如果多个server一起访问,可能会造成垃圾数据或重复执行,这时需要对资源(如数据库)限制单server访问,我们可以考虑用到分布式锁。

zookeeper可以轻松实现。基本思想:

    1. 创建虚拟节点

    2. 虚拟节点排序 ,获取最小的节点名称

    3. 比较当前jvm创建的节点路径和最小的节点名称,如果想当则持有锁,否则等待或退出竞争。


清单 4. 同步锁的关键代码

               

 void getLock() throws KeeperException, InterruptedException{ 

        List<String> list = zk.getChildren(root, false); 

        String[] nodes = list.toArray(new String[list.size()]); 

        Arrays.sort(nodes); 

        if(myZnode.equals(root+"/"+nodes[0])){ 

            doAction(); 

        } 

        else{ 

            waitForLock(nodes[0]); 

        } 

    } 

    void waitForLock(String lower) throws InterruptedException, KeeperException {

        Stat stat = zk.exists(root + "/" + lower,true); 

        if(stat != null){ 

            mutex.wait(); 

        } 

        else{ 

            getLock(); 

        } 

    } 


实现:

package com.mylearn.zookeeper.test;

import com.jd.common.util.ArrayUtils;

import org.apache.zookeeper.CreateMode;

import org.apache.zookeeper.KeeperException;

import org.apache.zookeeper.ZooDefs;

import org.apache.zookeeper.ZooKeeper;

import org.apache.zookeeper.data.ACL;

import org.apache.zookeeper.data.Stat;

import java.util.*;

import java.util.concurrent.BrokenBarrierException;

import java.util.concurrent.CountDownLatch;

import java.util.concurrent.CyclicBarrier;

/**

 * Created by IntelliJ IDEA.

 * User: yingkuohao

 * Date: 13-11-26

 * Time: 上午10:54

 * CopyRight:360buy

 * Descrption:

 * 分布式锁  :

 * 1. 创建虚拟节点

      * 2. 虚拟节点排序 ,获取最小的节点名称

      * 3. 比较当前jvm创建的节点路径和最小的节点名称,如果想当则持有锁,否则等待或退出竞争。

 * To change this template use File | Settings | File Templates.

 */

public class ZkLock {

    private static final int SESSION_TIMEOUT = 10000;

    static CountDownLatch countDownLatch = new CountDownLatch(1);

    CyclicBarrier cyclicBarrier = new CyclicBarrier(3, new Runnable() {

        public void run() {

            System.out.println("虚拟节点准备完毕,开始抢锁");  //后续线程执行

        }

    });

    private static String parentLockPth = "/lockRoot";

    volatile static boolean flag = false;

    public static void main(String args[]) {

        final Map<String, String> map = new HashMap<String, String>();

        map.put("node0", "192.168.192.68:2181");

        map.put("node1", "192.168.229.79:2181");

        map.put("node2", "192.168.229.80:2181");

        final ZkLock zkLock = new ZkLock();

        zkLock.creatRootNode(map.get("node0")); //创建lock父节点

        checkParentIsOk(map);   //校验父节点是否ok

        //三个节点开始抢锁

        for (int i = 0; i < 3; i++) {

            final int finalI = i;

            Thread thread = new Thread(new Runnable() {

                public void run() {

                    try {

                        countDownLatch.await();  //三个节点同时执行

                        String key = "node" + finalI;

                        zkLock.createEphemeralNode(map.get(key), finalI);

                    } catch (InterruptedException e) {

                        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.

                    }

                }

            });

            thread.start();

        }

        countDownLatch.countDown();//子线程一起开始

    }

    /**

     * 换一个节点去校验是否父节点已经同步

     *

     * @param map

     */

    private static void checkParentIsOk(Map<String, String> map) {

        ZkApi zkApi = new ZkApi(map.get("node1"));

        ZooKeeper zooKeeper = zkApi.getZk();

        try {

            String parentData = new String(zooKeeper.getData(parentLockPth, false, null));

            System.out.println(Thread.currentThread().getName() + "parentData=" + parentData);

        } catch (KeeperException e) {

            e.printStackTrace();

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

    }

    /**

     * 创建锁的根节点:lockRoot

     *

     * @param serverPath

     */

    public void creatRootNode(String serverPath) {

        ZkApi zkApi = new ZkApi(serverPath);

        ZooKeeper zooKeeper = zkApi.getZk();

        String parentLockPth = "/lockRoot";

        zkApi.createNod(parentLockPth, "lockRootData", ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); //创建一个虚节点

        try {

            String parentData = new String(zooKeeper.getData(parentLockPth, false, null));

            System.out.println(Thread.currentThread().getName() + "parentData=" + parentData);

        } catch (KeeperException e) {

            e.printStackTrace();

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

    }

    /**

     * 创建虚拟节点,然后抢锁,生成的结构如:

     * /lockRoot/lock0

     * /lockRoot/lock1

     * /lockRoot/lock2

     *

     * @param serverPath

     * @param i

     */

    public void createEphemeralNode(String serverPath, int i) {

        ZkApi zkApi = new ZkApi(serverPath);

        String currentPath = "/lock" + i;

        String wholePath = parentLockPth + currentPath;        //节点路径 ,,形如/lockRoot/lock1

        String testData = "lockdata";       //  节点初始化数据

        List<ACL> aclList = ZooDefs.Ids.OPEN_ACL_UNSAFE; //节点的权限

//      zkApi.createNod(path, testData, aclList, CreateMode.EPHEMERAL_SEQUENTIAL); //创建一个带顺序的虚节点成功后会带有一个序号,如lock1000000018, lock1000000019

        zkApi.createNod(wholePath, testData, aclList, CreateMode.EPHEMERAL); //创建一个虚节点

        try {

            cyclicBarrier.await();  //等待三个节点都创建成功后开始抢锁

        } catch (InterruptedException e) {

            e.printStackTrace();

        } catch (BrokenBarrierException e) {

            e.printStackTrace();

        }

        getLock(zkApi.getZk(), parentLockPth, currentPath); //抢锁

    }

    /**

     * 抢锁的核心逻辑

     * 1. 创建虚拟节点

     * 2. 虚拟节点排序 ,获取最小的节点名称

     * 3. 比较当前jvm创建的节点路径和最小的节点名称,如果想当则持有锁,否则等待或退出竞争。

     *

     * @param zooKeeper

     * @param parentLockPth

     * @param currentPath

     */

    private void getLock(ZooKeeper zooKeeper, String parentLockPth, String currentPath) {

        try {

            List<String> childNode = zooKeeper.getChildren(parentLockPth, false);    //获取孩子列表

            Collections.sort(childNode); //把孩子列表排序 ,如:lock0,lock1,lock2

            System.out.println(Thread.currentThread().getName() + "孩子节点一共:" + ArrayUtils.join(childNode.toArray(), ","));

            String firtNode = childNode.get(0); //获取最小的节点,默认排序是升序,这里获取lock0

            System.out.println(Thread.currentThread().getName() + "firNode=" + firtNode + "cureentNode=" + currentPath);

            firtNode = "/" + firtNode;

            if (currentPath.equals(firtNode)) {

                //如果当前节点和第一个孩子节点的顺序相等,则可以获取锁

                System.out.println(Thread.currentThread().getName() + "[获取锁ok]" + zooKeeper.getSaslClient().getConfigStatus());

                doAction();

                zooKeeper.delete(parentLockPth + firtNode, -1);//删除此节点,唤醒其他node

                Stat stat = zooKeeper.exists(parentLockPth + firtNode, false);

                if (stat == null) {

                    System.out.println(Thread.currentThread().getName() + "删除虚节点成功");

                }

            } else {

                //如果当前节点的序号不是最小的,则等待获取锁

                waitLock(zooKeeper, parentLockPth, currentPath);

            }

        } catch (KeeperException e) {

            e.printStackTrace();

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

    }

    /**

     * 等待锁,其实真正的来讲,分布式环境下获取不到锁的server就直接退出了,

     * 就由获取锁的server去执行任务了。

     * @param zooKeeper

     * @param parentLockPth

     * @param currentPath

     */

    private void waitLock(ZooKeeper zooKeeper, String parentLockPth, String currentPath) {

        try {

            Stat stat = zooKeeper.exists(parentLockPth + currentPath, false);

            while (flag = false) {

                this.wait();  //flag默认为false,该节点线程一直等待,直至拥有锁的线程修改flag状态,唤醒,

            }

            if (stat != null) {

                //如果获取锁的节点还存在,说明上个节点还在用锁,当前节点只能等待。

                System.out.println(Thread.currentThread().getName() + "其他线程正在占用,请等待");

                flag = false;

                Thread.sleep(2000);

            }

            System.out.println(Thread.currentThread().getName() + "watiLock.重试,node[0]已不存在");

            //如果获取锁的节点不存在了,说明上个节点获取的锁已经释放,它对应的虚节点已经被删除,这时可以继续尝试获取了

            getLock(zooKeeper, parentLockPth, currentPath);

        } catch (InterruptedException e) {

            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.

        } catch (KeeperException e) {

            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.

        }

    }

    private void getLock() {

        doAction();

        flag = true;

    }

    private void doAction() {

        System.out.println(Thread.currentThread().getName() + "获取锁成功,执行内容");

        try {

            Thread.currentThread().sleep(2000);

            System.out.println(Thread.currentThread().getName() + "睡眠完成");

        } catch (InterruptedException e) {

            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.

        }

    }

}

结果:

触发了None事件!

节点/lockRoot已存在!

mainparentData=lockRootData

path=192.168.229.79:2181

触发了None事件!

mainparentData=lockRootData

path=192.168.192.68:2181

path=192.168.229.79:2181

path=192.168.229.80:2181

触发了None事件!

触发了None事件!

触发了NodeCreated事件!

创建新节点成功:/lockRoot/lock1

触发了NodeCreated事件!

创建新节点成功:/lockRoot/lock2

触发了None事件!

触发了NodeCreated事件!

创建新节点成功:/lockRoot/lock0

Thread-0孩子节点一共:lock0,lock1,lock2

Thread-0firNode=lock0cureentNode=/lock0

Thread-0[获取锁ok]Will not attempt to authenticate using SASL (无法定位登录配置)

Thread-0获取锁成功,执行内容

Thread-1孩子节点一共:lock0,lock1,lock2

Thread-1firNode=lock0cureentNode=/lock1

Thread-2孩子节点一共:lock0,lock1,lock2

Thread-2firNode=lock0cureentNode=/lock2

Thread-1其他线程正在占用,请等待

Thread-2其他线程正在占用,请等待

Thread-0睡眠完成

Thread-1watiLock.重试,node[0]已不存在

Thread-2watiLock.重试,node[0]已不存在

Thread-1孩子节点一共:lock0,lock1,lock2

Thread-1firNode=lock0cureentNode=/lock1

Thread-2孩子节点一共:lock0,lock1,lock2

Thread-2firNode=lock0cureentNode=/lock2

Thread-1其他线程正在占用,请等待

Thread-2其他线程正在占用,请等待

Thread-0删除虚节点成功

Thread-1watiLock.重试,node[0]已不存在

Thread-2watiLock.重试,node[0]已不存在

Thread-1孩子节点一共:lock1,lock2

Thread-1firNode=lock1cureentNode=/lock1

Thread-1[获取锁ok]Will not attempt to authenticate using SASL (无法定位登录配置)

Thread-1获取锁成功,执行内容

Thread-2孩子节点一共:lock1,lock2

Thread-2firNode=lock1cureentNode=/lock2

Thread-2其他线程正在占用,请等待

Thread-1睡眠完成

Thread-2watiLock.重试,node[0]已不存在

Thread-1删除虚节点成功

Thread-2孩子节点一共:lock2

Thread-2firNode=lock2cureentNode=/lock2

Thread-2[获取锁ok]Will not attempt to authenticate using SASL (无法定位登录配置)

Thread-2获取锁成功,执行内容

Thread-2睡眠完成

Thread-2删除虚节点成功

可见,程序最开始三个线程模拟三个节点,先各自创建自己的虚拟节点:lock0lock1lock2.然后通过CyclicBarrier来控制同时抢锁,很显然,lock0会成功,lock1lock2都会阻塞;之后lock0执行完任务后会释放锁,lock1lock2被唤醒再次去竞争锁,这时候lock1获胜,lock2继续阻塞;lock1执行完毕后释放,剩下lock2.此时lock2也可以获取锁了。

参考:

https://www.ibm.com/developerworks/cn/opensource/os-cn-zookeeper/

原创粉丝点击