Zookeeper(4)-使用ZooKeeper實現分佈式鎖

基於zookeeper臨時有序節點可以實現的分佈式鎖。

大致思想即爲:每個客戶端對某個方法加鎖時,在zookeeper上的與該方法對應的指定節點的目錄下,生成一個唯一的瞬時有序節點。 判斷是否獲取鎖的方式很簡單,只需要判斷有序節點中序號最小的一個。 當釋放鎖的時候,只需將這個瞬時節點刪除即可。同時,其可以避免服務宕機導致的鎖無法釋放,而產生的死鎖問題。

代碼實現

  1. 分佈式鎖鎖服務LockServer

import com.tc.zooker.config.ConnectWatcher;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.data.Stat;
import org.junit.Test;

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

public class LockServer extends ConnectWatcher {
    private String rootPath = "/lock";
    private CountDownLatch countDownLatch;
    private String currentLock;
    public LockServer() {
        this.connect();
    }

    @Test
    public void createRoot() {
        try {
            this.zooKeeper.create(rootPath, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void process(WatchedEvent watchedEvent) {
        super.process(watchedEvent);
        if(watchedEvent.getType()== Event.EventType.NodeDeleted){
            if(countDownLatch!=null){
                countDownLatch.countDown();
            }
        }
    }

    public void lock(CallBack callBack) {
        if(tryLock()){
            doAction(callBack);
        }else{
            waitLock();
            doAction(callBack);
        }

    }
    //獲取鎖的進程執行業務操作,來調用回調函數
    private void doAction(CallBack callBack) {
        callBack.lockEnd();
    }
    //元素等待獲取鎖,使用countDownLatch來阻塞當前線程的運行
    private void waitLock() {
        countDownLatch=new CountDownLatch(1);
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public boolean tryLock() {
        Long id = this.zooKeeper.getSessionId();
        String path = "lock-" + id;

        try {
            if (!isExist(path)) {
              currentLock=this.zooKeeper.create(rootPath+"/"+path+"_", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
            }
            List<String> locks=this.zooKeeper.getChildren(rootPath,false);
            Collections.sort(locks);
            //判斷當前znode是不是最小的一個
            if(locks.get(0).equals(currentLock.substring(currentLock.lastIndexOf("/")+1))){
                return true;
            }else{
                List<String> lockNames=new ArrayList<>();
                locks.forEach(lock->{
                    lockNames.add(lock.split("_")[0]);
                });
                int index=Collections.binarySearch(lockNames,path);
                //監聽比當前元素小的元素的變化,來通知等待的元素來獲取鎖
                Stat stat = this.zooKeeper.exists(rootPath + "/" + locks.get(index - 1), this);
                    if(stat!=null){
                        return false;
                    }

                return true;
            }
        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return false;
    }
    //解決連接失敗重試,造成死鎖的問題
    public boolean isExist(String path) {
        List<String> lockNames = new ArrayList<String>();
        List<String> locks = null;
        try {
            locks = this.zooKeeper.getChildren(rootPath, false);
            locks.forEach((lock) -> {
                lockNames.add(lock.split("-")[0]);
            });
            int index = Collections.binarySearch(lockNames, path);
            if (index >-1) {
                currentLock=rootPath+"/"+locks.get(index);
                return true;
            }

        } catch (KeeperException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return false;
    }

    //解鎖
    public void unLock() {
        try {
            this.zooKeeper.delete(currentLock,-1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }
}
  1. 利用接口來實現回調函數CallBack

public interface CallBack {
    void lockEnd();
}
  1. 我們需要加鎖的方法 LockClient

public class LockClient {
    public void testLock(int id){
        LockServer lockServer=new LockServer();
        lockServer.lock(new CallBack() {
            @Override
            public void lockEnd() {
                //處理自己的業務邏輯
                try {
                    Thread.sleep(800);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("編號"+id+"獲取到分佈式鎖");

                lockServer.unLock();
            }
        });
    }
  1. 併發線程測試工具類LockThread

import java.util.concurrent.CountDownLatch;

public class LockThread  implements Runnable{
    CountDownLatch countDownLatch;
    int id;
    public LockThread(CountDownLatch countDownLatch,int id){
        this.countDownLatch=countDownLatch;
        this.id=id;
    }
    public void test() {
        Thread thread=new Thread(this);
        thread.start();
    }

    @Override
    public void run() {
        try {
            countDownLatch.await();
            LockClient lockClient=new LockClient();
            lockClient.testLock(id);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
  1. 測試類

import java.util.concurrent.CountDownLatch;

public class Main  {

    public static void main(String[] args) {
        CountDownLatch countDownLatch=new CountDownLatch(20);
        for (int i=0;i<20;i++){
            new LockThread(countDownLatch,i+1).test();
            countDownLatch.countDown();
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章