2023-01-12
節(jié)點(diǎn)
4.4. 子節(jié)點(diǎn)實(shí)現(xiàn)的鎖
在生產(chǎn)環(huán)境中,使用子節(jié)點(diǎn)做分布式鎖的實(shí)現(xiàn)的場景是最常見的,包括阻塞型的鎖和非阻塞型的鎖。因此,設(shè)計(jì)子節(jié)點(diǎn)實(shí)現(xiàn)的鎖的父類,提供公共的處理邏輯。將阻塞型和非阻塞型的實(shí)現(xiàn)不同的邏輯分別在子類中實(shí)現(xiàn)即可。
package com.qianfeng.lock.childNodeLock;
import com.qianfeng.lock.ZkLocker;
import com.qianfeng.lock.ZkLockerBase;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import java.util.Collections;
import java.util.List;
/**
* @author 千鋒大數(shù)據(jù)教研院 - 章魚哥
* @company 北京千鋒互聯(lián)科技有限公司
*/
public abstract class ZkChildNodeLockerBase extends ZkLockerBase implements ZkLocker {
/**
* 創(chuàng)建的子節(jié)點(diǎn)鎖的全路徑
*/
protected String nodeFullPath;
public ZkChildNodeLockerBase(String connectString, String lockName) {
super(connectString, lockName);
createLockNode();
}
public ZkChildNodeLockerBase(String lockName) {
super();
this.lockName = lockName;
createLockNode();
}
// 創(chuàng)建鎖節(jié)點(diǎn)
public void createLockNode() {
// 1. 判斷節(jié)點(diǎn)是否存在
if (!exists(getLockName())) {
// 3. 創(chuàng)建鎖節(jié)點(diǎn)
createNode(getLockName(), CreateMode.CONTAINER);
}
}
@Override
public boolean lock() {
// 1. 在鎖節(jié)點(diǎn)下創(chuàng)建子節(jié)點(diǎn)
this.nodeFullPath = createNode(getLockName() + "/child-", CreateMode.EPHEMERAL_SEQUENTIAL);
// 2. 判斷是否可以成功上鎖
return canLock();
}
@Override
public boolean unlock() {
return deleteNode(this.nodeFullPath);
}
@Override
public boolean exists() {
// 獲取當(dāng)前鎖節(jié)點(diǎn)下的子節(jié)點(diǎn)數(shù)量,如果大于0,說明有鎖存在
try {
Listchildren = getZkCli().getChildren(getLockName(), false);
return children.size() > 0;
} catch (KeeperException | InterruptedException ignored) {
}
return false;
}
protected abstract boolean canLock();
/**
* 獲取當(dāng)前創(chuàng)建的子節(jié)點(diǎn)之前的節(jié)點(diǎn)(根據(jù)序號)
* @return 之前的節(jié)點(diǎn)
*/
protected String getPreviousNode() {
// 定義變量,記錄上一個(gè)節(jié)點(diǎn)名稱
String previousNodeName = null;
try {
// 1. 獲取鎖節(jié)點(diǎn)下所有的子節(jié)點(diǎn)
Listchildren = getZkCli().getChildren(getLockName(), false);
// 2. 對所有的節(jié)點(diǎn)進(jìn)行名字排序
Collections.sort(children);
// 3. 獲取當(dāng)前創(chuàng)建的子節(jié)點(diǎn)名稱
String childNodeName = this.nodeFullPath.substring(getLockName().length() + 1);
// 4. 遍歷所有的節(jié)點(diǎn),進(jìn)行名稱的比對
for (String child : children) {
if (child.equals(childNodeName)) {
break;
}
previousNodeName = child;
}
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
return previousNodeName;
}
}
4.5. 子節(jié)點(diǎn)實(shí)現(xiàn)的非阻塞型鎖
package com.qianfeng.lock.childNodeLock;
/**
* @author 千鋒大數(shù)據(jù)教研院 - 章魚哥
* @company 北京千鋒互聯(lián)科技有限公司
*/
public class ZkChildNodeNoneBlockingLocker extends ZkChildNodeLockerBase {
public ZkChildNodeNoneBlockingLocker(String connectString, String lockName) {
super(connectString, lockName);
}
public ZkChildNodeNoneBlockingLocker(String lockName) {
super(lockName);
}
@Override
protected boolean canLock() {
// 1. 判斷是否有之前的節(jié)點(diǎn)
String previousNodeName = getPreviousNode();
// 2. 如果不存在之前的節(jié)點(diǎn),說明上鎖成功
if (previousNodeName == null) {
return true;
}
// 3. 如果存在之前的節(jié)點(diǎn),說明上鎖失敗,刪除自己創(chuàng)建的子節(jié)點(diǎn)即可
deleteNode(this.nodeFullPath);
return false;
}
}
4.6. 子節(jié)點(diǎn)實(shí)現(xiàn)的阻塞型鎖
A程序在進(jìn)行上鎖的時(shí)候,發(fā)現(xiàn)已經(jīng)被其他的程序獲取到鎖了,自己需要等待其他的程序釋放鎖。基本的實(shí)現(xiàn)邏輯就是自己監(jiān)控已經(jīng)創(chuàng)建好的節(jié)點(diǎn),發(fā)現(xiàn)這個(gè)節(jié)點(diǎn)消失的時(shí)候,自己去創(chuàng)建節(jié)點(diǎn)。但是為什么沒有用節(jié)點(diǎn)作為鎖來實(shí)現(xiàn)阻塞型的鎖呢?是因?yàn)檫@里有“羊群效應(yīng)”。
假如有100個(gè)程序來獲取鎖,結(jié)果發(fā)現(xiàn)鎖已經(jīng)被其他的程序注冊了,于是這100個(gè)程序都去監(jiān)聽這個(gè)節(jié)點(diǎn)。一旦這個(gè)節(jié)點(diǎn)被刪除,這100個(gè)程序都可以獲取到狀態(tài)的變更,會同時(shí)請求ZooKeeper創(chuàng)建節(jié)點(diǎn)。這樣會帶來瞬時(shí)的資源占用劇增,嚴(yán)重的甚至可能導(dǎo)致服務(wù)器宕機(jī)。因此,我們在實(shí)現(xiàn)阻塞型鎖的時(shí)候,使用的是有序的子節(jié)點(diǎn)來實(shí)現(xiàn)的,每一個(gè)程序監(jiān)控比自己編號小的節(jié)點(diǎn)即可。詳情可參考3.2.章節(jié)的內(nèi)容。
package com.qianfeng.lock.childNodeLock;
import org.apache.zookeeper.AddWatchMode;
import org.apache.zookeeper.KeeperException;
import java.util.concurrent.CountDownLatch;
/**
* @author 千鋒大數(shù)據(jù)教研院 - 章魚哥
* @company 北京千鋒互聯(lián)科技有限公司
*/
public class ZkChildNodeBlockingLocker extends ZkChildNodeLockerBase {
// 等待釋放信號
private CountDownLatch latch = new CountDownLatch(1);
public ZkChildNodeBlockingLocker(String connectString, String lockName) {
super(connectString, lockName);
}
public ZkChildNodeBlockingLocker(String lockName) {
super(lockName);
}
@Override
protected boolean canLock() {
// 獲取自己創(chuàng)建的子節(jié)點(diǎn)之前序號的節(jié)點(diǎn)
String previousNode = getPreviousNode();
// 如果之前節(jié)點(diǎn)不存在,說明已經(jīng)上鎖成功
if (previousNode == null) {
return true;
}
// 如果有比當(dāng)前創(chuàng)建的節(jié)點(diǎn)序號更小的節(jié)點(diǎn),說明上鎖失敗,自己阻塞,監(jiān)聽之前的節(jié)點(diǎn)即可
try {
getZkCli().addWatch(getLockName() + "/" + previousNode, event -> {
if (event.getType().equals(Event.EventType.NodeDeleted)) {
// 說明之前節(jié)點(diǎn)被刪除掉了
latch.countDown();
}
}, AddWatchMode.PERSISTENT);
// 等待信號
latch.await();
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
// 自己成為了最小的節(jié)點(diǎn),上鎖成功
return true;
}
}
開班時(shí)間:2021-04-12(深圳)
開班盛況開班時(shí)間:2021-05-17(北京)
開班盛況開班時(shí)間:2021-03-22(杭州)
開班盛況開班時(shí)間:2021-04-26(北京)
開班盛況開班時(shí)間:2021-05-10(北京)
開班盛況開班時(shí)間:2021-02-22(北京)
開班盛況開班時(shí)間:2021-07-12(北京)
預(yù)約報(bào)名開班時(shí)間:2020-09-21(上海)
開班盛況開班時(shí)間:2021-07-12(北京)
預(yù)約報(bào)名開班時(shí)間:2019-07-22(北京)
開班盛況Copyright 2011-2023 北京千鋒互聯(lián)科技有限公司 .All Right 京ICP備12003911號-5 京公網(wǎng)安備 11010802035720號