基於Zookeeper一步步完善的分佈式鎖

本文以一個訂單服務作爲例子

首先需要一個訂單接口類

訂單服務接口

然後需要一個實現類

訂單接口實現類
工具類爲
訂單號生成工具類

測試類

不加所測試代碼

測試結果

在這裏插入圖片描述

在普通情況下是沒有問題的,在併發的情況下,會造成訂單編號的不唯一

加可重入鎖進行修正

可重入鎖

測試類

重入鎖測試
在這裏插入圖片描述

測試結果

在這裏插入圖片描述

可重入鎖-只能保證在一個JVM中使得訂單唯一,分佈式系統下訂單並不唯一

使用Zookeeper分佈式鎖進行修正

在這裏插入圖片描述

<!---所需要的依賴項-->
<dependency>
       <groupId>com.101tec</groupId>
       <artifactId>zkclient</artifactId>
       <version>0.10</version>
</dependency>

實現Lock接口類

package com.milla.navicat.spring.study.distribute.lock;

import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.serialize.SerializableSerializer;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * @Package: com.milla.navicat.spring.study.distribute.lock
 * @Description: <zookeeper分佈式鎖>
 * @Author: MILLA
 * @CreateDate: 2020/3/19 15:01
 * @UpdateUser: MILLA
 * @UpdateDate: 2020/3/19 15:01
 * @UpdateRemark: <>
 * @Version: 1.0
 */
public class ZKDistributeLock implements Lock {
    private String lockPath;

    private ZkClient client;

    public ZKDistributeLock(String lockPath) {
        super();
        this.lockPath = lockPath;
        this.client = new ZkClient("hadoop-master:2181,hadoop-slave01:2181,hadoop-slave02:2181");
        this.client.setZkSerializer(new SerializableSerializer());
        int i = this.lockPath.substring(1).indexOf("/");
        String rootPath = this.lockPath.substring(0, i + 1);
        if (!this.client.exists(rootPath)) {
            this.client.createPersistent(rootPath);
        }
    }

    @Override
    public void lock() {
        if (!tryLock()) {
            waitForLock();
            lock();
        }

    }

    //等待獲取鎖
    private void waitForLock() {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        IZkDataListener listener = new IZkDataListener() {
            @Override
            public void handleDataChange(String dataPath, Object data) throws Exception {

            }

            @Override
            public void handleDataDeleted(String dataPath) throws Exception {
                System.out.println("刪除了節點....." + dataPath);
                countDownLatch.countDown();
            }
        };
        //註冊訂閱
        client.subscribeDataChanges(lockPath, listener);
        //如果要創建的節點已經存在了就等待
        if (this.client.exists(lockPath)) {
            try {
                //阻塞
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //取消註冊
        client.unsubscribeDataChanges(lockPath, listener);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
    }

    @Override
    public boolean tryLock() {
        try {
            //創建臨時節點
            this.client.createEphemeral(lockPath);
        } catch (RuntimeException e) {
//            e.printStackTrace();
            return false;
        }
        return true;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    @Override
    public void unlock() {
        this.client.delete(lockPath);

    }

    @Override
    public Condition newCondition() {
        return null;
    }
}

測試類

在這裏插入圖片描述

測試結果

在這裏插入圖片描述

Zookeeper實現分佈式鎖的理論依據:
多個Jvm同時在Zookeeper上創建同一個相同的節點( /Lock)
zk節點唯一的! 不能重複!
節點類型爲臨時節點, jvm1創建成功時候,jvm2和jvm3創建節點時候會報錯,該節點已經存在。
這時候 jvm2和jvm3進行等待。
jvm1的程序現在執行完畢,執行釋放鎖。關閉當前會話。臨時節點不復存在了並且事件通知Watcher,jvm2和jvm3繼續創建。
**由測試結果可以看到,一個線程釋放鎖之後,所有的線程都要去搶這把鎖,在高併發情況下浪費了巨大的資源[驚羣效應]**

解決驚羣效應

在這裏插入圖片描述

實現Lock接口改善類

package com.milla.navicat.spring.study.distribute.lock;

import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.serialize.SerializableSerializer;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @Package: com.milla.navicat.spring.study.distribute.lock
 * @Description: <zookeeper分佈式鎖[解決驚羣效應]>
 * @Author: MILLA
 * @CreateDate: 2020/3/19 15:01
 * @UpdateUser: MILLA
 * @UpdateDate: 2020/3/19 15:01
 * @UpdateRemark: <>
 * @Version: 1.0
 */
public class ZKDistributeImproveLock implements Lock {

    private String lockPath;

    private String beforePath;

    private String currentPath;

    private ZkClient client;

    public ZKDistributeImproveLock(String lockPath) {
        super();
        this.lockPath = lockPath;
        this.client = new ZkClient("hadoop-master:2181,hadoop-slave01:2181,hadoop-slave02:2181");
        this.client.setZkSerializer(new SerializableSerializer());
    }

    @Override
    public void lock() {
        if (!tryLock()) {
            waitForLock();
            lock();
        }
    }

    //等待獲取鎖
    private void waitForLock() {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        IZkDataListener listener = new IZkDataListener() {
            @Override
            public void handleDataChange(String dataPath, Object data) throws Exception {

            }

            @Override
            public void handleDataDeleted(String dataPath) throws Exception {
                System.out.println(Thread.currentThread().getName() + " ;//刪除了節點....." + dataPath);
                countDownLatch.countDown();
            }

        };
        //註冊訂閱
        client.subscribeDataChanges(beforePath, listener);
        //如果要創建的節點已經存在了就等待
        if (this.client.exists(beforePath)) {
            try {
                System.out.println("....waiting...............");
                countDownLatch.await();
                System.out.println("....overing...............");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //取消註冊
        client.unsubscribeDataChanges(beforePath, listener);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
    }

    @Override
    public boolean tryLock() {

        //如果當前節點是空創建臨時連續節點
        if (this.currentPath == null) {
            System.out.println(Thread.currentThread().getName() + " 執行過幾次");
            this.currentPath = this.client.createEphemeralSequential(lockPath + "/", "aaa");
        }
        //獲取所有的子節點

        List<String> children = client.getChildren(lockPath);
        //排序
        Collections.sort(children);
        //判斷當前節點是否是最小的
        if (currentPath.equals(lockPath + "/" + children.get(0))) {
            return true;
        } else {
            int index = children.indexOf(currentPath.substring(lockPath.length() + 1));
            beforePath = lockPath + "/" + children.get(index - 1);
        }
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    @Override
    public void unlock() {
        this.client.delete(currentPath);

    }

    @Override
    public Condition newCondition() {
        return null;
    }
}

測試類

在這裏插入圖片描述
在這裏插入圖片描述

測試結果

以上改進,當一個進程中一個訂單服務實例是能解決驚羣效應,訂單也唯一,但是共享實例時,訂單不唯一

使用ThreadLocal進行修正

在這裏插入圖片描述

修正Lock接口的實現類

package com.milla.navicat.spring.study.distribute.lock;

import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.serialize.SerializableSerializer;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * @Package: com.milla.navicat.spring.study.distribute.lock
 * @Description: <zookeeper分佈式鎖[解決驚羣效應]>
 * @Author: MILLA
 * @CreateDate: 2020/3/19 15:01
 * @UpdateUser: MILLA
 * @UpdateDate: 2020/3/19 15:01
 * @UpdateRemark: <>
 * @Version: 1.0
 */
public class ZKDistributeThreadLocalLock implements Lock {

    private String lockPath;


    private ThreadLocal<String> beforePath = new ThreadLocal<>();

    private ThreadLocal<String> currentPath = new ThreadLocal<>();

    private ZkClient client;

    public ZKDistributeThreadLocalLock(String lockPath) {
        super();
        this.lockPath = lockPath;
        this.client = new ZkClient("hadoop-master:2181,hadoop-slave01:2181,hadoop-slave02:2181");
        this.client.setZkSerializer(new SerializableSerializer());
    }

    @Override
    public void lock() {
        if (!tryLock()) {
            waitForLock();
            lock();
        }

    }

    //等待獲取鎖
    private void waitForLock() {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        IZkDataListener listener = new IZkDataListener() {
            @Override
            public void handleDataChange(String dataPath, Object data) throws Exception {

            }

            @Override
            public void handleDataDeleted(String dataPath) throws Exception {
                System.out.println(Thread.currentThread().getName() + " ;//刪除了節點....." + dataPath);
                countDownLatch.countDown();
            }

        };
        //註冊訂閱
        client.subscribeDataChanges(beforePath.get(), listener);
        //如果要創建的節點已經存在了就等待
        if (this.client.exists(beforePath.get())) {
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //取消註冊
        client.unsubscribeDataChanges(beforePath.get(), listener);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
    }

    @Override
    public boolean tryLock() {

        //如果當前節點是空創建臨時連續節點
        if (this.currentPath.get() == null) {
            this.currentPath.set(this.client.createEphemeralSequential(lockPath + "/", "aaa"));
        }
        //獲取所有的子節點

        List<String> children = client.getChildren(lockPath);
        //排序
        Collections.sort(children);
        String currentNode = this.currentPath.get();
        //判斷當前節點是否是最小的
        if (currentNode.equals(lockPath + "/" + children.get(0))) {
            return true;
        } else {
            int index = children.indexOf(currentNode.substring(lockPath.length() + 1));
            beforePath.set(lockPath + "/" + children.get(index - 1));
        }
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    @Override
    public void unlock() {
        this.client.delete(currentPath.get());
        currentPath.remove();

    }

    @Override
    public Condition newCondition() {
        return null;
    }
}

測試類

在這裏插入圖片描述

測試結果

在這裏插入圖片描述

測試結果是交替的,如Lock的實現類中定義的一樣,每次只訂閱自己之前的那個節點,從而避免了資源的浪費

記錄下,分佈式鎖的漸變過程…如果問題還請佐證

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