- 問題:爲什麼要使用分佈式鎖?分佈式鎖如何實現?
- 分析
1、主流解決分佈式鎖的方式使用zookeeper分佈式協調工具;
2、.....
- 詳細介紹zookeeper實現分佈式鎖:
- 一、爲什麼要使用分佈式鎖?
java中對於一個jvm而言,jdk提供了lock和同步。
分佈式情況下,多個進程對資源產生競爭關係,
多個進程往往在不同的主機上,jdk無法滿足。
分佈式鎖是分佈式情況的併發鎖。
- 二、zookeeper實現分佈式鎖
- 1、zk節點類型:
持久節點、持久順序節點、臨時節點、臨時順序節點
- 2、實現思路:
(1)創建一個持久節點,代表該分佈式鎖父節點;
(2)當進程訪問共享資源時,調用lock或trylock獲取鎖;
第一步通過臨時順序節點,建立一個臨時順序節點name+順序號。
(3)對節點下的所有節點進行排序,判斷剛建立的子節點是否是最小節點,
是的話,獲取鎖。
(4)不是的話,獲取上一個順序節點,給該節點註冊監聽事件,並阻塞,
監聽事件,並獲取到鎖的控制權。
(5)每個資源當調用完共享鎖之後,調用unlock方法,釋放該資源。
(6)監聽節點監聽到前一個節點斷開事件後,再次判斷該節點
是否是最小一個節點,防止上個節點意外(宕機、時間過長)釋放鎖。
3、java代碼實現:
package com.wqq.registry.zk;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* @ClassName: ZookeeperLock
* @Description: 實現zk分佈式鎖
* 思路:
* (1)創建一個持久節點,代表該分佈式鎖父節點;
* (2)當進程訪問共享資源時,調用lock或trylock獲取鎖;第一步通過臨時順序節點,建立一個臨時順序節點name+順序號。
* (3)對節點下的所有節點進行排序,判斷剛建立的子節點是否是最小節點,是的話,獲取鎖。
* (4)不是的話,獲取上一個順序節點,給該節點註冊監聽事件,並阻塞,監聽事件,並獲取到鎖的控制權。
* (5)每個資源當調用完共享鎖之後,調用unlock方法,釋放該資源。
* (6)監聽節點監聽到前一個節點斷開事件後,再次判斷該節點是否是最小一個節點,防止上個節點意外(宕機、時間過長)釋放鎖。
* @Author: wangqq
* @create: 2019/10/30
**/
public class ZookeeperLock implements Watcher {
//zk客戶端
private ZooKeeper zooKeeper;
//通一類型根節點,也可以傳入
private String root = "/lock";
//分割符號
private String splitLock = "_lock_";
//當前節點
private String currentNode;
//等待鎖
private String waitNode;
//競爭資源標誌
private String lockName;
//超時時間
private int sessionTimeout = 30000;
//未獲取鎖繼續等待,標記
private CountDownLatch latch;
//作用 zkclient連接
private CountDownLatch connected = new CountDownLatch(1);
/**
* 構造帶有根節點的zk client
*
* @param url // zk 地址
* @param lockName //鎖標誌
*/
public ZookeeperLock(String url, String lockName) {
this.lockName = lockName;
try {
//創建客戶端的時候需要監聽配置,本代碼直接監聽this自己
zooKeeper = new ZooKeeper(url, sessionTimeout, this);
//建立鏈接後,執行後面
connected.await();
//判斷節點是否存在,不用監聽
Stat stat = zooKeeper.exists(root, false);
if (null == stat) {
//創建根節點 參數1: 要創建的節點的路徑 參數2: 節點數據 參數3: 節點權限 參數4:節點的類型(持久節點)
zooKeeper.create(root, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
/**
* 監聽事件方法
*
* @param watchedEvent
*/
@Override
public void process(WatchedEvent watchedEvent) {
//建立連接用
if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {
connected.countDown();
}
//其他線程放棄鎖的標誌,判斷該進程是否有等待的鎖
if (this.latch != null) {
//判斷當前節點是否是第一個節點;
// 防止上一個節點宕機,或者意外釋放鎖的watch
//。。。。。。。
//取出所有子節點
this.latch.countDown();
}
}
/**
* 獲取鎖
*/
public void lock() {
//嘗試獲取鎖成功,方法結束
if (this.tryLock()) {
System.out.println("Thread " + Thread.currentThread().getId() + " " + currentNode + " get lock true");
return;
} else {//未獲取到鎖,等待
try {
waitLock(waitNode, sessionTimeout, TimeUnit.MILLISECONDS);
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 等待鎖
*
* @param waitNode
* @param sessionTimeout
*/
public boolean waitLock(String waitNode, long sessionTimeout, TimeUnit unit) throws KeeperException, InterruptedException {
synchronized (this) {
Stat stat = zooKeeper.exists(root + "/" + waitNode, true);//同時註冊監聽。
//判斷上一個節點是否存在,
if (null != stat) {
this.latch = new CountDownLatch(1);
//等待,這裏應該一直等待其他線程釋放鎖
this.latch.await(sessionTimeout, unit);
this.latch = null;
}
}
return true;
}
/**
* 嘗試獲取鎖,指定時間
*
* @param time
* @param unit
*/
public boolean tryLock(long time, TimeUnit unit) {
if (tryLock()) {
return true;
} else {
try {
return waitLock(waitNode, time, unit);
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return false;
}
/**
* 嘗試獲取鎖,默認時間
*
* @return
*/
public boolean tryLock() {
if (lockName.contains(splitLock)) {
throw new RuntimeException("lockName can not contains _lock_");
}
try {
//創建臨時子節點
currentNode = zooKeeper.create(root + "/" + lockName + splitLock, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
System.out.println(currentNode + "is created");
//取出所有子節點
List<String> nodeList = zooKeeper.getChildren(root, false);
//所有符合標準的鎖
List<String> nodeListFilter = nodeList.stream().filter((String str) -> lockName.equals(str.split(splitLock)[0])).collect(Collectors.toList());
//排序
Collections.sort(nodeListFilter);
//當前鎖節點是否是第一個節點
if (currentNode.equals(root + "/" + nodeListFilter.get(0))) {
//如果是獲取鎖
return true;
}
//如果不是的話,獲取前一個節點 lockName_lock_/
String lastNumStr = currentNode.substring(currentNode.lastIndexOf("/") + 1);
//用二分法從 nodeListFilter 集合中找到lastNumStr的的位置,-1得到在集合中的下標,並獲取上個子節點
waitNode = nodeListFilter.get(Collections.binarySearch(nodeListFilter, lastNumStr) - 1);
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
public void unlock() {
try {
zooKeeper.delete(currentNode, -1);
zooKeeper.close();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
//測試用例
static Integer count = 1000;
static ExecutorService executorService = Executors.newFixedThreadPool(1000);
public static void main(String[] args) throws InterruptedException {
IntStream.range(0, 1000).forEach(i -> executorService.execute(new Runnable() {
@Override
public void run() {
ZookeeperLock zookeeperLock = new ZookeeperLock("127.0.0.1:2181", "name");
zookeeperLock.lock();
count--;
System.out.println("count值爲" + count);
zookeeperLock.unlock();
}
}));
System.out.println("等待執行完成");
//
TimeUnit.MINUTES.sleep(1);
System.out.println("count:" + count);
}
}