今天介紹基于ZooKeeper的分布式鎖的簡單實現(xiàn),包括阻塞鎖和非阻塞鎖。同時增加了網(wǎng)上很少介紹的基于節(jié)點的非阻塞鎖實現(xiàn),主要是為了加深對ZooKeeper的理解。

維基百科:分布式鎖,是控制分布式系統(tǒng)之間同步訪問共享資源的一種方式。在分布式系統(tǒng)中,常常需要協(xié)調(diào)他們的動作。如果不同的系統(tǒng)或是同一個系統(tǒng)的不同主機之間共享了一個或一組資源,那么訪問這些資源的時候,往往需要互斥來防止彼此干擾來保證一致性,在這種情況下,便需要使用到分布式鎖。
1 阻塞鎖和非阻塞鎖
根據(jù)業(yè)務(wù)特點,普通分布式鎖有兩種需求:阻塞鎖和非阻塞鎖。

阻塞鎖:多個系統(tǒng)同時調(diào)用同一個資源,所有請求被排隊處理。已經(jīng)得到分布式鎖的系統(tǒng),進入運行狀態(tài)完成業(yè)務(wù)操作;沒有得到分布式鎖的線程進入阻塞狀態(tài)等待,當(dāng)獲得相應(yīng)的信號并獲得分布式鎖后,進入運行狀態(tài)完成業(yè)務(wù)操作。

非阻塞鎖:多個系統(tǒng)同時調(diào)用同一個資源,當(dāng)某一個系統(tǒng)最先獲取到鎖,進入運行狀態(tài)完成業(yè)務(wù)操作;其他沒有得到分布式鎖的系統(tǒng),就直接返回,不做任何業(yè)務(wù)邏輯,可以給用戶提示進行其他操作。

2 鎖代碼簡單設(shè)計
基于ZooKeeper實現(xiàn)鎖,一般都是創(chuàng)建EPHEMERAL_SEQUENTIAL子節(jié)點并比較序號實現(xiàn)的。參照Redis的分布式鎖實現(xiàn),也可以使用EPHEMERAL節(jié)點實現(xiàn)。

3 分布式鎖代碼
完整代碼比較多,占篇幅。在文中只保留了關(guān)鍵的代碼。完整項目代碼放到了github(
https://github.com/SeemSilly/codestory/tree/master/research-zoo-keeper ),感興趣的可以關(guān)注。
3.1 分布式鎖接口定義
ZooKeeperLock.java
public interface ZooKeeperLock {
/**
* 嘗試獲取鎖
*
* @param guidNodeName 用于加鎖的唯一節(jié)點名
* @param clientGuid 用于唯一標(biāo)識當(dāng)前客戶端的ID
* @return
*/
boolean lock(String guidNodeName, String clientGuid);
/**
* 釋放鎖
*
* @param guidNodeName 用于加鎖的唯一節(jié)點名
* @param clientGuid 用于唯一標(biāo)識當(dāng)前客戶端的ID
* @return
*/
boolean release(String guidNodeName, String clientGuid);
/**
* 鎖是否已經(jīng)存在
*
* @param guidNodeName 用于加鎖的唯一節(jié)點名
* @return
*/
boolean exists(String guidNodeName);
}
3.2 基于節(jié)點實現(xiàn)的非阻塞鎖
NodeBlocklessLock.java
public class NodeBlocklessLock extends ZooKeeperBase implements ZooKeeperLock {
/** 嘗試獲取鎖 */
public boolean lock(String guidNodeName, String clientGuid) {
boolean result = false;
if (getZooKeeper().exists(guidNodeName, false) == null) {
getZooKeeper().create(guidNodeName, clientGuid.getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
byte[] data = getZooKeeper().getData(guidNodeName, false, null);
if (data != null && clientGuid.equals(new String(data))) {
result = true;
}
}
return result;
}
/** 釋放鎖 */
public boolean release(String guidNodeName, String clientGuid) {
boolean result = false;
Stat stat = new Stat();
byte[] data = getZooKeeper().getData(guidNodeName, false, stat);
if (data != null && clientGuid.equals(new String(data))) {
getZooKeeper().delete(guidNodeName, stat.getVersion());
result = true;
}
return result;
}
/** 鎖是否已經(jīng)存在 */
public boolean exists(String guidNodeName) {
boolean result = false;
Stat stat = getZooKeeper().exists(guidNodeName, false);
result = stat != null;
return result;
}
}
3.3 基于子節(jié)點實現(xiàn)的分布式鎖基類
ChildrenNodeLock.java
public abstract class ChildrenNodeLock extends ZooKeeperBase implements ZooKeeperLock {
/** 獲取當(dāng)前節(jié)點的前一個節(jié)點,如果為空表示自己是第一個 */
protected String getPrevElementName() {
List
long curElementSerial = Long.valueOf(
elementNodeFullName.substring((this.guidNodeName + “/” + childPrefix).length()));
String prevElementName = null;
long prevElementSerial = -1;
for (String oneElementName : elementNames) {
long oneElementSerial = Long.parseLong(oneElementName.substring(childPrefix.length()));
if (oneElementSerial < curElementSerial) {
// 比當(dāng)前節(jié)點小
if (oneElementSerial > prevElementSerial) {
prevElementSerial = oneElementSerial;
prevElementName = oneElementName;
}
}
}
return prevElementName;
}
/** 嘗試獲取鎖 */
public boolean lock(String guidNodeName, String clientGuid) {
boolean result = false;
// 確保根節(jié)點存在,并且創(chuàng)建為容器節(jié)點
super.createRootNode(this.guidNodeName, CreateMode.CONTAINER);
// 創(chuàng)建子節(jié)點并返回帶序列號的節(jié)點名
elementNodeFullName = getZooKeeper().create(this.guidNodeName + “/” + childPrefix,
new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
boolean lockSuccess = isLockSuccess();
result = lockSuccess;
return result;
}
/** 釋放鎖 */
public boolean release(String guidNodeName, String clientGuid) {
// 刪除子節(jié)點
getZooKeeper().delete(elementNodeFullName, 0);
return true;
}
/** 鎖是否已經(jīng)存在,容器節(jié)點存在,并且有子節(jié)點,則說明鎖已經(jīng)存在 */
public boolean exists(String guidNodeName) {
boolean exists = false;
Stat stat = new Stat();
try {
getZooKeeper().getData(guidNodeName, false, stat);
exists = stat.getNumChildren() > 0;
} catch (KeeperException.NoNodeException e) {
exists = false;
}
return exists;
}
/** 是否加鎖成功 , 由子類實現(xiàn) */
protected abstract boolean isLockSuccess();
}
3.4 基于子節(jié)點實現(xiàn)的非阻塞鎖
ChildrenBlocklessLock.java
public class ChildrenBlocklessLock extends ChildrenNodeLock {
/** 是否加鎖成功 */
protected boolean isLockSuccess() throws KeeperException, InterruptedException {
boolean lockSuccess = false;
String prevElementName = getPrevElementName();
if (prevElementName != null) {
// 有更小的節(jié)點,說明當(dāng)前節(jié)點沒搶到鎖,刪掉自己并退出
getZooKeeper().delete(elementNodeFullName, 0);
} else {
lockSuccess = true;
}
return lockSuccess;
}
}
3.5 基于子節(jié)點實現(xiàn)的阻塞鎖
ChildrenBlockingLock.java
public class ChildrenBlockingLock extends ChildrenNodeLock {
/** 前一個節(jié)點被刪除的信號 */
static Integer mutex = Integer.valueOf(-1);
/** 監(jiān)控的節(jié)點被刪除 */
protected void processNodeDeleted(WatchedEvent event) {
synchronized (mutex) {
// 節(jié)點被刪除,通知退出線程
mutex.notify();
}
}
/** 是否加鎖成功 */
protected boolean isLockSuccess() {
boolean lockSuccess;
while (true) {
String prevElementName = getPrevElementName();
if (prevElementName == null) {
lockSuccess = true;
break;
} else {
// 有更小的節(jié)點,說明當(dāng)前節(jié)點沒搶到鎖,注冊前一個節(jié)點的監(jiān)聽
getZooKeeper().exists(this.guidNodeName + “/” + prevElementName, true);
synchronized (mutex) {
mutex.wait();
log.info(“{} 被刪除,看看是不是輪到自己了”, prevElementName);
}
}
}
return lockSuccess;
}
}
4 測試用例
4.1 測試代碼
LockClientThread.java 獲取分布式鎖和釋放鎖
public class LockClientThread extends Thread {
/** 模擬獲取分布式鎖,成功后執(zhí)行業(yè)務(wù) */
public void run() {
boolean locked = zooKeeperLock.lock(guidNodeName, clientGuid);
if (locked) {
log.info(“{} lock() success,拿到鎖了,假裝忙2秒”, clientGuid);
Thread.sleep(2000);
boolean released = zooKeeperLock.release(guidNodeName, clientGuid);
log.info(“{} release() result : {}”, clientGuid, released);
} else {
log.info(“{} lock() fail”, clientGuid);
}
}
}
模擬多個客戶端并發(fā)執(zhí)行
public void testChildrenBlocklessMultiThread() throws IOException {
String guidNodeName = “/multi-” + System.currentTimeMillis();
int threadCount = 5;
LockClientThread[] threads = new LockClientThread[threadCount];
for (int i = 0; i < threadCount; i++) {
ChildrenBlocklessLock nodeBlocklessLock = new ChildrenBlocklessLock(address);
threads[i] = new LockClientThread(nodeBlocklessLock, guidNodeName, “client-” + (i + 1));
}
for (int i = 0; i < threadCount; i++) {
threads[i].start();
}
}
4.2 非阻塞鎖的測試結(jié)果
可以看到,只有一個線程能搶到鎖并執(zhí)行業(yè)務(wù),其他線程都直接退出。
55:43.929 [INFO] LockClientThread.run(33) client-1 lock() …
55:43.942 [INFO] LockClientThread.run(33) client-3 lock() …
55:43.947 [INFO] LockClientThread.run(33) client-2 lock() …
55:43.948 [INFO] LockClientThread.run(33) client-4 lock() …
55:43.949 [INFO] LockClientThread.run(33) client-5 lock() …
55:44.052 [INFO] LockClientThread.run(36) client-1 lock() success,拿到鎖了,假裝忙2秒
55:44.072 [INFO] LockClientThread.run(47) client-5 lock() fail
55:44.085 [INFO] LockClientThread.run(47) client-4 lock() fail
55:44.091 [INFO] LockClientThread.run(47) client-2 lock() fail
55:44.096 [INFO] LockClientThread.run(47) client-3 lock() fail
55:46.053 [INFO] LockClientThread.run(42) client-1 release() …
55:46.057 [INFO] LockClientThread.run(44) client-1 release() result : true
4.3 阻塞鎖的測試結(jié)果
可以看到,搶到分布式鎖的線程執(zhí)行業(yè)務(wù),沒搶到鎖的線程會等到直到鎖被釋放重新獲取到鎖后再執(zhí)行業(yè)務(wù)。

59:32.802 [INFO] LockClientThread.run(33) client-1 lock() …
59:32.811 [INFO] LockClientThread.run(33) client-3 lock() …
59:32.812 [INFO] LockClientThread.run(33) client-4 lock() …
59:32.813 [INFO] LockClientThread.run(33) client-2 lock() …
59:32.813 [INFO] LockClientThread.run(33) client-5 lock() …
59:32.836 [INFO] LockClientThread.run(36) client-1 lock() success,拿到鎖了,假裝忙2秒
59:34.836 [INFO] LockClientThread.run(42) client-1 release() …
59:34.844 [INFO] LockClientThread.run(44) client-1 release() result : true
59:34.846 [INFO]
ChildrenBlockingLock.isLockSuccess(55) element0000000000 被刪除,看看是不是輪到自己了
59:34.848 [INFO] LockClientThread.run(36) client-5 lock() success,拿到鎖了,假裝忙2秒
59:36.848 [INFO] LockClientThread.run(42) client-5 release() …
59:36.852 [INFO]
ChildrenBlockingLock.isLockSuccess(55) element0000000001 被刪除,看看是不是輪到自己了
59:36.852 [INFO] LockClientThread.run(44) client-5 release() result : true
59:36.855 [INFO] LockClientThread.run(36) client-2 lock() success,拿到鎖了,假裝忙2秒
59:38.855 [INFO] LockClientThread.run(42) client-2 release() …
59:38.869 [INFO]
ChildrenBlockingLock.isLockSuccess(55) element0000000002 被刪除,看看是不是輪到自己了
59:38.870 [INFO] LockClientThread.run(44) client-2 release() result : true
59:38.876 [INFO] LockClientThread.run(36) client-4 lock() success,拿到鎖了,假裝忙2秒
59:40.877 [INFO] LockClientThread.run(42) client-4 release() …
59:40.881 [INFO]
ChildrenBlockingLock.isLockSuccess(55) element0000000003 被刪除,看看是不是輪到自己了
59:40.882 [INFO] LockClientThread.run(44) client-4 release() result : true
59:40.884 [INFO] LockClientThread.run(36) client-3 lock() success,拿到鎖了,假裝忙2秒
59:42.884 [INFO] LockClientThread.run(42) client-3 release() …
59:42.887 [INFO] LockClientThread.run(44) client-3 release() result : true
end:如果你覺得本文對你有幫助的話,記得關(guān)注點贊轉(zhuǎn)發(fā),你的支持就是我更新動力。
本文由網(wǎng)上采集發(fā)布,不代表我們立場,轉(zhuǎn)載聯(lián)系作者并注明出處:http://m.zltfw.cn/shbk/39361.html