基於zookeeper臨時有序節點可以實現的分佈式鎖。
大致思想即爲:每個客戶端對某個方法加鎖時,在zookeeper上的與該方法對應的指定節點的目錄下,生成一個唯一的瞬時有序節點。 判斷是否獲取鎖的方式很簡單,只需要判斷有序節點中序號最小的一個。 當釋放鎖的時候,只需將這個瞬時節點刪除即可。同時,其可以避免服務宕機導致的鎖無法釋放,而產生的死鎖問題。
代碼實現
- 分佈式鎖鎖服務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();
}
}
}
- 利用接口來實現回調函數CallBack
public interface CallBack {
void lockEnd();
}
- 我們需要加鎖的方法 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();
}
});
}
- 併發線程測試工具類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();
}
}
}
- 測試類
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();
}
}
}