package com.zzhijian.zookeeperdemo.lock;
import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
/**
* 1: 創建持久化根節點-'/lock'
* 2:多個客戶端請求時,創建臨時有序節點,將當前的節點與根節點lock的最小子節點比較,
* 如果相同的話,則獲取鎖,否則監聽比自己小的子節點,等待鎖
* 3:處理業務
* 4:釋放鎖
* @author zhijian.zheng
* @package: com.zzhijian.zookeeperdemo.lock
* @date: 2019-08-29 09:47
**/
@Slf4j
public class ZookeeperDistributorLock implements Lock{
private static String ZK_ADDRESS = "zkServer:2181,zkServer:2182,zkServer:2183";
private static ZooKeeper ZK = null;
private static String ROOT ="/locks";
private String LOCK;
private ConcurrentHashMap<Thread,String> preNodeMap= new ConcurrentHashMap<>();
private static String CHILDREN_NODE = ROOT+"/lock_";
public ZookeeperDistributorLock(){
// 初始化zk客戶端
initZookeeper(ZK_ADDRESS);
// 創建根節點持久化節點
createRootNode();
}
@Override
public void lock() {
if(tryLock()){
log.error("【{}獲取鎖成功!】",Thread.currentThread().getName());
// 正常來說要自己主動去釋放鎖,爲了演示效果,獲取鎖之後自動釋放鎖
try{
Thread.sleep(1000* 3);
unlock();
}catch (Exception e){
}
}else {
// 等待鎖
waitingLock(preNodeMap.get(Thread.currentThread()));
}
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock() {
try {
//創建臨時有序節點
String currentNode = ZK.create(CHILDREN_NODE, "0".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
log.error("[{}-開始嘗試獲取鎖-{}]",Thread.currentThread().getName(),currentNode);
// 獲取根節點子節點信息
List<String> childrenNodes = ZK.getChildren(ROOT,false);
// 排序
SortedSet<String> sortedSet = new TreeSet<>();
childrenNodes.forEach(node ->{
sortedSet.add(ROOT+"/"+node);
});
//獲取最小子節點
String firstNode = sortedSet.first();
if(firstNode.equals(currentNode)){
// 如果當前節點跟最小節點相同,則獲取鎖
log.error("-----{}---成功獲取鎖--",currentNode);
LOCK = currentNode;
return true;
}
//獲取當前節點中所有比自己更小的節點
SortedSet<String> lessThenMe = sortedSet.headSet(currentNode);
//如果當前所有節點中有比自己更小的節點
if (!CollectionUtils.isEmpty(lessThenMe)){
//獲取比自己小的節點中的最後一個節點,設置爲等待鎖
preNodeMap.put(Thread.currentThread(),lessThenMe.last());
}
return false;
}catch (Exception e){
log.error(e.getMessage());
}
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public void unlock() {
log.error("--------釋放鎖--------------");
log.error("[{}->釋放{}鎖]",Thread.currentThread().getName(),LOCK);
try{
ZK.delete(LOCK,-1);
LOCK = null;
ZK.close();
}catch (Exception e){
log.error(e.getMessage());
}
}
@Override
public Condition newCondition() {
return null;
}
/**
* TODO: 初始化zk
* * @param address
* @author zhijian.zheng
* @return org.apache.zookeeper.ZooKeeper
* @version 1.0
* @date 2019/8/15 上午9:51
*/
public static void initZookeeper(String address){
String connectString = address;
// 會話超時時間
int sessionTimeout = 3000;
log.error("zookeeper start connecting");
try {
ZK = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
@Override
public void process(WatchedEvent event) {
log.error("【事件被觸發了 --- 連接狀態:{},事件:{}】",event.getState(),event.getType());
}
});
}catch (Exception e){
log.error(e.getMessage());
}
log.error("zookeeper connection success!");
}
/**
* TODO: 創建根節點
* @author zhijian.zheng
* @return void
* @version 1.0
* @date 2019/8/15 下午1:59
*/
public static void createRootNode() {
try {
if (ZK != null) {
//判斷節點是否存在
Stat stat = ZK.exists(ROOT, false);
if(ObjectUtils.isEmpty(stat)){
// 創建根節點
log.error(ZK.create(ROOT, "0".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT));
}
}
} catch (Exception e) {
log.error("【創建根節點異常-{}】",e.getMessage());
}
}
/**
* 等待鎖
* @author zhijian.zheng
* @return void
* @version 1.0
* @date 2019/8/29 上午10:28
*/
private static void waitingLock(String prevNode){
try{
if(ZK != null){
CountDownLatch countDownLatch = new CountDownLatch(1);
Stat stat = ZK.exists(prevNode, new Watcher() {
@Override
public void process(WatchedEvent event) {
log.error("節點變更,要去釋放鎖");
countDownLatch.countDown();
}
});
if (stat!=null){
//即如果上一個節點依然存在的話
log.error(Thread.currentThread().getName()+"-->等待鎖-"+prevNode+"釋放。");
countDownLatch.await();
}
log.error("【{}-獲取鎖成功!】",Thread.currentThread().getName());
// 釋放鎖
}
}catch (Exception e){
log.error(e.getMessage());
}
}
public static void main(String[] args) throws Exception{
ZookeeperDistributorLock lock = new ZookeeperDistributorLock();
for(int i=0;i<10;i++){
new Thread(new Runnable() {
@Override
public void run() {
try {
lock.lock();
}catch (Exception e){
System.out.println(e.getMessage());
}
}
}).start();
}
Thread.sleep(1000 * 60 * 2);
}
}
先記錄下原生zookeeper 如何處理分佈式鎖,後續在關注下curator 客戶端是如何使用分佈式鎖的!
參考:https://blog.csdn.net/dongguabai/article/details/83271601