利用zookeeper的臨時順序節點實現分佈式鎖

利用zookeeper實現分佈式鎖的代碼有很多,本文只是其中的一種,不再過多的介紹思想,簡單說一句,就是高併發下,所有請求都去創建一個臨時順序節點,然後對所有節點進行排序,當前拿到鎖的節點執行完成後,刪除當前節點,zookeeper通知前一個節點,讓前一個節點獲得到鎖,從而達到順序執行的目的。

pom依賴:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>zk.lock</groupId>
    <artifactId>zkLock</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>zkLock</name>
    <packaging>jar</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <zkclient.version>0.10</zkclient.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.101tec</groupId>
            <artifactId>zkclient</artifactId>
            <version>${zkclient.version}</version>
        </dependency>
    </dependencies>

    <build>
        <finalName>ETSSchedule</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

分佈式鎖的實現類:

package com.zookeeper.lock;

import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import org.apache.log4j.Logger;

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;

/**
 * @author: alex
 * @Date: 2019/4/8
 * @Description: 分佈式鎖實現
 */
public class ZookeeperDistributedLock implements Lock {

    protected static Logger logger = Logger.getLogger(ZookeeperDistributedLock.class);

    /**
     * zookeeper服務地址
     */
    private String hosts = "127.0.0.1:2181";

    /**
     * zk客戶端
     */
    private ZkClient client;

    /**
     * 當前節點
     */
    private ThreadLocal<String> currentPath = new ThreadLocal<>();

    /**
     * 前一個節點
     */
    private ThreadLocal<String> beforePath = new ThreadLocal<>();

    /**
     * 構造方法
     */
    public ZookeeperDistributedLock() {
        this.client = new ZkClient(hosts);  //獲得客端
        this.client.setZkSerializer(new MyZkSerializer()); //設置序列化類
        //判斷根節點是否存在,不存在則創建
        if (!this.client.exists(Constant.LOCKPATH)) {
            try {
                this.client.createPersistent(Constant.LOCKPATH);
            } catch (Exception e) {
                logger.error("ZkClient create root node failed...");
                logger.error(e);
            }
        }
    }

    /**
     * 加鎖方法
     */
    public void lock() {
        //如果沒有獲得到鎖,那麼就等待,一直到獲得到鎖爲止
        if (!tryLock()) {
            // 沒有獲得鎖,阻塞自己
            waitForLock();
            // 再次嘗試加鎖
            lock();
        }
    }

    /**
     * 釋放鎖
     */
    public void unlock() {
        this.client.delete(this.currentPath.get());
    }

    /**
     * 嘗試獲取鎖
     * @return true拿到鎖 false沒拿到鎖
     */
    public boolean tryLock() {
        //當前節點爲空,說明還沒有線程來創建節點
        if(this.currentPath.get() == null) {
            this.currentPath.set(this.client.createEphemeralSequential(Constant.LOCKPATH + Constant.SEPARATOR,"data"));
        }
        //獲取所有子節點
        List<String> children = this.client.getChildren(Constant.LOCKPATH);
        //排序
        Collections.sort(children);
        //判斷當前節點是否是最小的節點
        if(this.currentPath.get().equals(Constant.LOCKPATH + Constant.SEPARATOR + children.get(0))) {
            return true;
        } else {
            //獲取當前節點的位置
            int curIndex = children.indexOf(this.currentPath.get().substring(Constant.LOCKPATH.length() + 1));
            //設置前一個節點
            beforePath.set(Constant.LOCKPATH + Constant.SEPARATOR + children.get(curIndex - 1));
        }
        return false;
    }

    /**
     * 等待鎖
     */
    private void waitForLock() {
        //聲明一個計數器
        CountDownLatch cdl = new CountDownLatch(1);
        IZkDataListener listener = new IZkDataListener() {
            @Override
            public void handleDataChange(String arg0, Object arg1) throws Exception {
            }
            @Override
            public void handleDataDeleted(String arg0) throws Exception {
                logger.info("Ephemeral node has been deleted....");
                //計數器減一
                cdl.countDown();
            }
        };
        //完成watcher註冊
        this.client.subscribeDataChanges(this.beforePath.get(), listener);
        //阻塞自己
        if (this.client.exists(this.beforePath.get())) {
            try {
                cdl.await();
            } catch (InterruptedException e) {
                logger.error("CountDownLatch thread has been interrupted...");
                logger.error(e);
            }
        }
        //取消註冊
        this.client.unsubscribeDataChanges(this.beforePath.get(), listener);
    }

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

    public void lockInterruptibly() throws InterruptedException {
    }

    public Condition newCondition() {
        return null;
    }

    public String getHosts() {
        return hosts;
    }

    public void setHosts(String hosts) {
        this.hosts = hosts;
    }
}

序列化類:

package com.zookeeper.lock;

import org.I0Itec.zkclient.exception.ZkMarshallingError;
import org.I0Itec.zkclient.serialize.ZkSerializer;
import org.apache.log4j.Logger;

import java.io.UnsupportedEncodingException;

/**
 * @author: alex
 * @Date: 2019/4/8
 * @Description: 序列化
 */
public class MyZkSerializer implements ZkSerializer {

    protected static Logger logger = Logger.getLogger(MyZkSerializer.class);

    /**
     * 反序列化
     * @param bytes 字節數組
     * @return 實體
     * @throws ZkMarshallingError
     */
    public Object deserialize(byte[] bytes) throws ZkMarshallingError {
        try {
            return new String(bytes, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            logger.error("MyZkSerializer deserialize happened unsupportedEncodingException...");
            logger.error(e);
            throw new ZkMarshallingError(e);
        }
    }

    /**
     * 序列化
     * @param obj 實體
     * @return 字節數組
     * @throws ZkMarshallingError
     */
    public byte[] serialize(Object obj) throws ZkMarshallingError {
        try {
            return String.valueOf(obj).getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            logger.error("MyZkSerializer serialize happened unsupportedEncodingException...");
            logger.error(e);
            throw new ZkMarshallingError(e);
        }
    }
}

常量類:

package com.zookeeper.lock;

/**
 * @author: alex
 * @Date: 2019/4/9
 * @Description: 常量類
 */
public class Constant {

    public final static String SEPARATOR = "/";

    public final static String LOCKPATH = "/zk-lock";
}

測試類:

package com.zookeeper.lock.test;

import com.zookeeper.lock.ZookeeperDistributedLock;

/**
 * @author: alex
 * @Date: 2019/4/9
 * @Description: 業務代碼
 */
public class DemoService {

    private static int count = 0; //生成計數器

    /**
     * 業務代碼
     * @param name
     */
    public void sayHello(String name) {
        ZookeeperDistributedLock lock = new ZookeeperDistributedLock();
        try {
            lock.lock();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            count++; //加一
            System.out.println(Thread.currentThread().getName() + " say hello to " + name + "_" + count);
        }finally {
            lock.unlock();
        }
    }
}

模擬高併發請求,使用的CyclicBarrier,阻塞住線程,讓線程同時執行。

package com.zookeeper.lock.test;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * @author: alex
 * @Date: 2019/4/9
 * @Description:
 */
public class DemoThread {

    static CyclicBarrier cyclicBarrier = new CyclicBarrier(10);
    static class DemoRun implements  Runnable {

        private int i;

        public DemoRun(int i) {
            this.i = i;
        }

        @Override
        public void run() {
            try {
                DemoService demoService = new DemoService();
                cyclicBarrier.await();
                demoService.sayHello("name_" + i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        for(int i = 0;i<10;i++) {
            new Thread(new DemoRun(i)).start();
        }
    }
}

 

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