利用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();
}
}
}