1. 程式人生 > >基於zookeeper的分散式鎖實現

基於zookeeper的分散式鎖實現

之前已經實現過基於redis的分散式鎖
這次用zookeeper來實現.

原理:ZooKeeper有四種形式的目錄節點,四種CreateMode

  • PERSISTENT:持久化目錄節點,儲存的資料不會丟失。
  • PERSISTENT_SEQUENTIAL:順序自動編號的持久化目錄節點,儲存的資料不會丟失,並且根據當前已近存在的節點數自動加 1,然後返回給客戶端已經成功建立的目錄節點名。
  • EPHEMERAL:臨時目錄節點,一旦建立這個節點的客戶端與伺服器埠也就是session 超時,這種節點會被自動刪除。
  • EPHEMERAL_SEQUENTIAL:臨時自動編號節點,一旦建立這個節點的客戶端與伺服器埠也就是session 超時,這種節點會被自動刪除,並且根據當前已近存在的節點數自動加 1,然後返回給客戶端已經成功建立的目錄節點名。

用EPHEMERAL_SEQUENTIAL即可實現分散式鎖.當獲取鎖的時候,節點會自動增加,如果你的節點是最小的,那麼你就獲取鎖.不然就等待.(只需等待比你大的節點是否還存在,如果不存在,重複獲取)
##程式碼

package demo.zookeeper.lock;
import demo.zookeeper.AbstractZooKeeper;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;

import java.io.IOException;
import java.util.Comparator;
import java.util.List;

/**
 * 基於Zookeeper的分散式鎖
 * Created by yuyufeng on 2017/8/22.
 */
public class LockUtil {
    //鎖失效時間10秒
    private static final int TIME_OUT = 10000;
    private static final String HOST = "localhost:2181";
    private static ZooKeeper zookeeper;
    private static String ROOT_PATH = "/lockdata";

    static {
        try {
            zookeeper = new ZooKeeper(HOST, TIME_OUT, new AbstractZooKeeper());
            try {
                //建立鎖節點存放根目錄
                if (zookeeper.exists(ROOT_PATH, false) == null) {
                    zookeeper.create(ROOT_PATH, "true".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
                }
            } catch (Exception e) {}
        } catch (IOException e) {}
    }

    /**
     * 獲取一個鎖
     * @param key  同步監聽物件
     * @return
     */
    public static boolean getLock(String key) {
        Long beginTime = System.currentTimeMillis();
        try {
            if (zookeeper.exists(ROOT_PATH + "/" + key, false) == null) {
                zookeeper.create(ROOT_PATH + "/" + key, "true".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }
        } catch (KeeperException e) {} catch (InterruptedException e) {}

        String result = null;
        try {
            result = zookeeper.create(ROOT_PATH + "/" + key + "/", "true".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        } catch (KeeperException e) {} catch (InterruptedException e) {}

        result = result.substring(result.lastIndexOf("/") + 1, result.length());

        return lockHandler(key, beginTime, result);
    }

    private static boolean lockHandler(String key, Long beginTime, String result){
        List<String> list = null;
        try {
            list = zookeeper.getChildren(ROOT_PATH + "/" + key, false);
        } catch (KeeperException e) {} catch (InterruptedException e) {}
        if (list == null || list.size() == 0) {
            System.out.println(Thread.currentThread().getName()+"獲取鎖異常,失敗!");
            return false;
        }
        System.out.println("==========================================");
        list.sort(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return Integer.valueOf(o1) - Integer.valueOf(o2);
            }
        });

        System.out.println(Thread.currentThread().getName()+"獲取鎖結果: " + result + " :" + list.get(0));
        if (result.equals(list.get(0))) {
            System.out.println(Thread.currentThread().getName()+ROOT_PATH + "/" + key + " 獲取鎖成功");
            return true;
        }

        //為獲取鎖失敗,監聽當前節點之前的節點,如之前的節點已經不存在,再次獲取.
        String beforeNode = list.get(0);
        for (int i = 0; i < list.size(); i++) {
            if (result.equals(list.get(i))) {
                beforeNode = list.get(--i);
                break;
            }
        }

        //監聽上一個節點5秒,如果上一個節點不存在,則再去獲取鎖
        while ((System.currentTimeMillis() - beginTime) < 5000) {
            System.out.println(Thread.currentThread().getName()+" 正在監聽節點 " + ROOT_PATH + "/" + key + "/" + beforeNode);
            try {
                if (zookeeper.exists(ROOT_PATH + "/" + key + "/" + beforeNode, false) == null) {
                    return lockHandler(key, beginTime, result);
                }
            } catch (KeeperException e) {} catch (InterruptedException e) {}
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println(Thread.currentThread().getName()+"之前的節點" + beforeNode + "未釋放,放棄獲取鎖");
        try {
            zookeeper.delete(ROOT_PATH + "/" + key + "/" + result, -1);
        } catch (InterruptedException e) {} catch (KeeperException e) {}
        return false;
    }

    /**
     * 刪除一個監聽物件下所有的節點
     * @param key
     * @return
     */
    public static boolean unLockNode(String key){
        List<String> list = null;
        try {
            list = zookeeper.getChildren(ROOT_PATH + "/" + key, false);
        } catch (KeeperException e) {  } catch (InterruptedException e) {  }
        if (list != null) {
            for (String s : list) {
                try {
                    zookeeper.delete(ROOT_PATH + "/" + key + "/" + s, -1);
                } catch (InterruptedException e) {  } catch (KeeperException e) { }
            }
        }
        return true;
    }

    /**
     * 解鎖當前獲取鎖的節點
     *
     * @param key
     * @return
     * @throws InterruptedException
     * @throws KeeperException
     */
    public static boolean unLock(String key) {
        System.out.println("開始解鎖-------------");
        List<String> list = null;
        try {
            list = zookeeper.getChildren(ROOT_PATH + "/" + key, false);
        } catch (KeeperException e) {} catch (InterruptedException e) {}

        if (list == null || list.size() == 0) {
            System.out.println(Thread.currentThread().getName()+"鎖不存在,或已經被解鎖,成功!");
            return false;
        }
        System.out.println("==========================================");
        list.sort(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return Integer.valueOf(o1) - Integer.valueOf(o2);
            }
        });

        try {
            zookeeper.delete(ROOT_PATH + "/" + key + "/" + list.get(0), -1);
        } catch (InterruptedException e) {} catch (KeeperException e) {}
        try {
            if (zookeeper.exists(ROOT_PATH + "/" + key + "/" + list.get(0), false) != null) {
                System.out.println(Thread.currentThread().getName()+" 解鎖失敗!");
                return false;
            }
        } catch (KeeperException e) {} catch (InterruptedException e) {}

        System.out.println(Thread.currentThread().getName()+" 解鎖成功!");
        return true;
    }

    public static void main(String[] args) {
//        System.out.println(LockUtil.getLock("a1"));
//        System.out.println(LockUtil.unLock("a1"));

        new Thread() {
            @Override
            public void run() {
                System.out.println(LockUtil.getLock("a1"));
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(LockUtil.unLock("a1"));

        }}.start();

        new Thread() {
            @Override
            public void run() {
                    System.out.println(LockUtil.getLock("a1"));
                    System.out.println(LockUtil.unLock("a1"));

        }}.start();

        new Thread() {
            @Override
            public void run() {
                System.out.println(LockUtil.getLock("a1"));
            }}.start();
        LockUtil.unLockNode("a1");
    }
}

##執行

==========================================
==========================================
==========================================
Thread-2獲取鎖結果: 0000000301 :0000000300
Thread-0獲取鎖結果: 0000000300 :0000000300
Thread-1獲取鎖結果: 0000000302 :0000000300
Thread-0/lockdata/a1 獲取鎖成功
Thread-2 正在監聽節點 /lockdata/a1/0000000300
true
Thread-1 正在監聽節點 /lockdata/a1/0000000301
Thread-2 正在監聽節點 /lockdata/a1/0000000300
Thread-1 正在監聽節點 /lockdata/a1/0000000301
Thread-2 正在監聽節點 /lockdata/a1/0000000300
Thread-1 正在監聽節點 /lockdata/a1/0000000301
Thread-2 正在監聽節點 /lockdata/a1/0000000300
Thread-1 正在監聽節點 /lockdata/a1/0000000301
開始解鎖-------------
==========================================
Thread-0 解鎖成功!
true
Thread-2 正在監聽節點 /lockdata/a1/0000000300
==========================================
Thread-2獲取鎖結果: 0000000301 :0000000301
Thread-2/lockdata/a1 獲取鎖成功
true
Thread-1 正在監聽節點 /lockdata/a1/0000000301
Thread-1 正在監聽節點 /lockdata/a1/0000000301
Thread-1 正在監聽節點 /lockdata/a1/0000000301
Thread-1 正在監聽節點 /lockdata/a1/0000000301
Thread-1 正在監聽節點 /lockdata/a1/0000000301
Thread-1 正在監聽節點 /lockdata/a1/0000000301
Thread-1之前的節點0000000301未釋放,放棄獲取鎖
false

使用zookeeper相對使用redis作為分散式鎖的區別

優勢 缺點
zook可作讀寫鎖.(實現方式:使用讀寫兩種方式,獲取讀鎖時當獲取的節點前面有比自己小的寫鎖節點存在,有則獲取失敗.獲取寫鎖則必須當前節點為最小的節點) 在實現過程中可以看出,效率沒有redis的高,固應對高併發能量沒有使用redis做分散式鎖強