分佈式鎖zookeeper實現詳解

  • 問題:爲什麼要使用分佈式鎖?分佈式鎖如何實現?
  • 分析
1、主流解決分佈式鎖的方式使用zookeeper分佈式協調工具;
2.....
  • 詳細介紹zookeeper實現分佈式鎖:
  • 一、爲什麼要使用分佈式鎖?
java中對於一個jvm而言,jdk提供了lock和同步。
分佈式情況下,多個進程對資源產生競爭關係,
多個進程往往在不同的主機上,jdk無法滿足。
分佈式鎖是分佈式情況的併發鎖。
  • 二、zookeeper實現分佈式鎖
  • 1、zk節點類型:
持久節點、持久順序節點、臨時節點、臨時順序節點

  • 2、實現思路:
1)創建一個持久節點,代表該分佈式鎖父節點;
(2)當進程訪問共享資源時,調用lock或trylock獲取鎖;
第一步通過臨時順序節點,建立一個臨時順序節點name+順序號。
(3)對節點下的所有節點進行排序,判斷剛建立的子節點是否是最小節點,
是的話,獲取鎖。
(4)不是的話,獲取上一個順序節點,給該節點註冊監聽事件,並阻塞,
監聽事件,並獲取到鎖的控制權。
(5)每個資源當調用完共享鎖之後,調用unlock方法,釋放該資源。
(6)監聽節點監聽到前一個節點斷開事件後,再次判斷該節點
是否是最小一個節點,防止上個節點意外(宕機、時間過長)釋放鎖。

3、java代碼實現:

package com.wqq.registry.zk;


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

import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * @ClassName: ZookeeperLock
 * @Description: 實現zk分佈式鎖
 * 思路:
 * (1)創建一個持久節點,代表該分佈式鎖父節點;
 * (2)當進程訪問共享資源時,調用lock或trylock獲取鎖;第一步通過臨時順序節點,建立一個臨時順序節點name+順序號。
 * (3)對節點下的所有節點進行排序,判斷剛建立的子節點是否是最小節點,是的話,獲取鎖。
 * (4)不是的話,獲取上一個順序節點,給該節點註冊監聽事件,並阻塞,監聽事件,並獲取到鎖的控制權。
 * (5)每個資源當調用完共享鎖之後,調用unlock方法,釋放該資源。
 * (6)監聽節點監聽到前一個節點斷開事件後,再次判斷該節點是否是最小一個節點,防止上個節點意外(宕機、時間過長)釋放鎖。
 * @Author: wangqq
 * @create: 2019/10/30
 **/

public class ZookeeperLock implements Watcher {

    //zk客戶端
    private ZooKeeper zooKeeper;
    //通一類型根節點,也可以傳入
    private String root = "/lock";
    //分割符號
    private String splitLock = "_lock_";
    //當前節點
    private String currentNode;
    //等待鎖
    private String waitNode;
    //競爭資源標誌
    private String lockName;
    //超時時間
    private int sessionTimeout = 30000;
    //未獲取鎖繼續等待,標記
    private CountDownLatch latch;
    //作用 zkclient連接
    private CountDownLatch connected = new CountDownLatch(1);

    /**
     * 構造帶有根節點的zk client
     *
     * @param url      // zk 地址
     * @param lockName //鎖標誌
     */
    public ZookeeperLock(String url, String lockName) {
        this.lockName = lockName;
        try {
            //創建客戶端的時候需要監聽配置,本代碼直接監聽this自己
            zooKeeper = new ZooKeeper(url, sessionTimeout, this);
            //建立鏈接後,執行後面
            connected.await();
            //判斷節點是否存在,不用監聽
            Stat stat = zooKeeper.exists(root, false);
            if (null == stat) {
                //創建根節點 參數1: 要創建的節點的路徑 參數2: 節點數據  參數3: 節點權限  參數4:節點的類型(持久節點)
                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();
        }
    }

    /**
     * 監聽事件方法
     *
     * @param watchedEvent
     */
    @Override
    public void process(WatchedEvent watchedEvent) {
        //建立連接用
        if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {
            connected.countDown();
        }
        //其他線程放棄鎖的標誌,判斷該進程是否有等待的鎖
        if (this.latch != null) {
            //判斷當前節點是否是第一個節點;
            // 防止上一個節點宕機,或者意外釋放鎖的watch
            //。。。。。。。
            //取出所有子節點
            this.latch.countDown();
        }
    }

    /**
     * 獲取鎖
     */
    public void lock() {
        //嘗試獲取鎖成功,方法結束
        if (this.tryLock()) {
            System.out.println("Thread " + Thread.currentThread().getId() + " " + currentNode + " get lock true");
            return;
        } else {//未獲取到鎖,等待
            try {
                waitLock(waitNode, sessionTimeout, TimeUnit.MILLISECONDS);
            } catch (KeeperException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 等待鎖
     *
     * @param waitNode
     * @param sessionTimeout
     */
    public boolean waitLock(String waitNode, long sessionTimeout, TimeUnit unit) throws KeeperException, InterruptedException {
        synchronized (this) {
            Stat stat = zooKeeper.exists(root + "/" + waitNode, true);//同時註冊監聽。
            //判斷上一個節點是否存在,
            if (null != stat) {
                this.latch = new CountDownLatch(1);
                //等待,這裏應該一直等待其他線程釋放鎖
                this.latch.await(sessionTimeout, unit);
                this.latch = null;
            }
        }
        return true;

    }

    /**
     * 嘗試獲取鎖,指定時間
     *
     * @param time
     * @param unit
     */
    public boolean tryLock(long time, TimeUnit unit) {
        if (tryLock()) {
            return true;
        } else {
            try {
                return waitLock(waitNode, time, unit);
            } catch (KeeperException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return false;
    }


    /**
     * 嘗試獲取鎖,默認時間
     *
     * @return
     */
    public boolean tryLock() {
        if (lockName.contains(splitLock)) {
            throw new RuntimeException("lockName can not contains _lock_");
        }
        try {
            //創建臨時子節點
            currentNode = zooKeeper.create(root + "/" + lockName + splitLock, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
            System.out.println(currentNode + "is created");
            //取出所有子節點
            List<String> nodeList = zooKeeper.getChildren(root, false);
            //所有符合標準的鎖
            List<String> nodeListFilter = nodeList.stream().filter((String str) -> lockName.equals(str.split(splitLock)[0])).collect(Collectors.toList());
            //排序
            Collections.sort(nodeListFilter);
            //當前鎖節點是否是第一個節點
            if (currentNode.equals(root + "/" + nodeListFilter.get(0))) {
                //如果是獲取鎖
                return true;
            }
            //如果不是的話,獲取前一個節點 lockName_lock_/
            String lastNumStr = currentNode.substring(currentNode.lastIndexOf("/") + 1);
            //用二分法從 nodeListFilter 集合中找到lastNumStr的的位置,-1得到在集合中的下標,並獲取上個子節點
            waitNode = nodeListFilter.get(Collections.binarySearch(nodeListFilter, lastNumStr) - 1);
        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return false;
    }

    public void unlock() {
        try {
            zooKeeper.delete(currentNode, -1);
            zooKeeper.close();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }

    }
    //測試用例
    static Integer count = 1000;
    static ExecutorService executorService = Executors.newFixedThreadPool(1000);
    public static void main(String[] args) throws InterruptedException {
        IntStream.range(0, 1000).forEach(i -> executorService.execute(new Runnable() {
            @Override
            public void run() {
                ZookeeperLock zookeeperLock = new ZookeeperLock("127.0.0.1:2181", "name");
                zookeeperLock.lock();
                count--;
                System.out.println("count值爲" + count);
                zookeeperLock.unlock();
            }
        }));
        System.out.println("等待執行完成");
        //
        TimeUnit.MINUTES.sleep(1);
        System.out.println("count:" + count);

    }

 }


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章