分析ZooKeeper分布式鎖的實(shí)現(xiàn)
一、分布式鎖方案比較
| 方案 | 實(shí)現(xiàn)思路 | 優(yōu)點(diǎn) | 缺點(diǎn) |
|---|---|---|---|
| 利用 MySQL 的實(shí)現(xiàn)方案 | 利用數(shù)據(jù)庫(kù)自身提供的鎖機(jī)制實(shí)現(xiàn),要求數(shù)據(jù)庫(kù)支持行級(jí)鎖 | 實(shí)現(xiàn)簡(jiǎn)單 | 性能差,無(wú)法適應(yīng)高并發(fā)場(chǎng)景;容易出現(xiàn)死鎖的情況;無(wú)法優(yōu)雅的實(shí)現(xiàn)阻塞式鎖 |
| 利用 Redis 的實(shí)現(xiàn)方案 | 使用 Setnx 和 lua 腳本機(jī)制實(shí)現(xiàn),保證對(duì)緩存操作序列的原子性 | 性能好 | 實(shí)現(xiàn)相對(duì)復(fù)雜,有可能出現(xiàn)死鎖;無(wú)法優(yōu)雅的實(shí)現(xiàn)阻塞式鎖 |
| 利用 ZooKeeper 的實(shí)現(xiàn)方案 | 基于 ZooKeeper 節(jié)點(diǎn)特性及 watch 機(jī)制實(shí)現(xiàn) | 性能好,穩(wěn)定可靠性高,能較好地實(shí)現(xiàn)阻塞式鎖 | 實(shí)現(xiàn)相對(duì)復(fù)雜 |
二、ZooKeeper實(shí)現(xiàn)分布式鎖
這里使用 ZooKeeper 來(lái)實(shí)現(xiàn)分布式鎖,以50個(gè)并發(fā)請(qǐng)求來(lái)獲取訂單編號(hào)為例,描述兩種方案,第一種為基礎(chǔ)實(shí)現(xiàn),第二種在第一種基礎(chǔ)上進(jìn)行了優(yōu)化。
2.1、方案一
流程描述:

具體代碼:
OrderNumGenerator:
/**
* @Description 生成隨機(jī)訂單號(hào)
*/
public class OrderNumGenerator {
private static long count = 0;
/**
* 使用日期加數(shù)值拼接成訂單號(hào)
*/
public String getOrderNumber() throws Exception {
String date = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(LocalDateTime.now());
String number = new DecimalFormat("000000").format(count++);
return date + number;
}
}
Lock:
/**
* @Description 自定義鎖接口
*/
public interface Lock {
/**
* 獲取鎖
*/
public void getLock();
/**
* 釋放鎖
*/
public void unLock();
}
AbstractLock:
/**
* @Description 定義一個(gè)模板,具體的方法由子類來(lái)實(shí)現(xiàn)
*/
public abstract class AbstractLock implements Lock {
/**
* 獲取鎖
*/
@Override
public void getLock() {
if (tryLock()) {
System.out.println("--------獲取到了自定義Lock鎖的資源--------");
} else {
// 沒(méi)拿到鎖則阻塞,等待拿鎖
waitLock();
getLock();
}
}
/**
* 嘗試獲取鎖,如果拿到了鎖返回true,沒(méi)有拿到則返回false
*/
public abstract boolean tryLock();
/**
* 阻塞,等待獲取鎖
*/
public abstract void waitLock();
}
ZooKeeperAbstractLock:
/**
* @Description 定義需要的服務(wù)連接
*/
public abstract class ZooKeeperAbstractLock extends AbstractLock {
private static final String SERVER_ADDR = "192.168.182.130:2181,192.168.182.131:2181,192.168.182.132:2181";
protected ZkClient zkClient = new ZkClient(SERVER_ADDR);
protected static final String PATH = "/lock";
}
ZooKeeperDistrbuteLock:
/**
* @Description 真正實(shí)現(xiàn)鎖的細(xì)節(jié)
*/
public class ZooKeeperDistrbuteLock extends ZooKeeperAbstractLock {
private CountDownLatch countDownLatch = null;
/**
* 嘗試拿鎖
*/
@Override
public boolean tryLock() {
try {
// 創(chuàng)建臨時(shí)節(jié)點(diǎn)
zkClient.createEphemeral(PATH);
return true;
} catch (Exception e) {
// 創(chuàng)建失敗報(bào)異常
return false;
}
}
/**
* 阻塞,等待獲取鎖
*/
@Override
public void waitLock() {
// 創(chuàng)建監(jiān)聽(tīng)
IZkDataListener iZkDataListener = new IZkDataListener() {
@Override
public void handleDataChange(String s, Object o) throws Exception {
}
@Override
public void handleDataDeleted(String s) throws Exception {
// 釋放鎖,刪除節(jié)點(diǎn)時(shí)喚醒等待的線程
if (countDownLatch != null) {
countDownLatch.countDown();
}
}
};
// 注冊(cè)監(jiān)聽(tīng)
zkClient.subscribeDataChanges(PATH, iZkDataListener);
// 節(jié)點(diǎn)存在時(shí),等待節(jié)點(diǎn)刪除喚醒
if (zkClient.exists(PATH)) {
countDownLatch = new CountDownLatch(1);
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 刪除監(jiān)聽(tīng)
zkClient.unsubscribeDataChanges(PATH, iZkDataListener);
}
/**
* 釋放鎖
*/
@Override
public void unLock() {
if (zkClient != null) {
System.out.println("釋放鎖資源");
zkClient.delete(PATH);
zkClient.close();
}
}
}
測(cè)試效果:使用50個(gè)線程來(lái)并發(fā)測(cè)試ZooKeeper實(shí)現(xiàn)的分布式鎖
/**
* @Description 使用50個(gè)線程來(lái)并發(fā)測(cè)試ZooKeeper實(shí)現(xiàn)的分布式鎖
*/
public class OrderService {
private static class OrderNumGeneratorService implements Runnable {
private OrderNumGenerator orderNumGenerator = new OrderNumGenerator();;
private Lock lock = new ZooKeeperDistrbuteLock();
@Override
public void run() {
lock.getLock();
try {
System.out.println(Thread.currentThread().getName() + ", 生成訂單編號(hào):" + orderNumGenerator.getOrderNumber());
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unLock();
}
}
}
public static void main(String[] args) {
System.out.println("----------生成唯一訂單號(hào)----------");
for (int i = 0; i < 50; i++) {
new Thread(new OrderNumGeneratorService()).start();
}
}
}
2.2、方案二
方案二在方案一的基礎(chǔ)上進(jìn)行優(yōu)化,避免產(chǎn)生“羊群效應(yīng)”,方案一一旦臨時(shí)節(jié)點(diǎn)刪除,釋放鎖,那么其他在監(jiān)聽(tīng)這個(gè)節(jié)點(diǎn)變化的線程,就會(huì)去競(jìng)爭(zhēng)鎖,同時(shí)訪問(wèn) ZooKeeper,那么怎么更好的避免各線程的競(jìng)爭(zhēng)現(xiàn)象呢,就是使用臨時(shí)順序節(jié)點(diǎn),臨時(shí)順序節(jié)點(diǎn)排序,每個(gè)臨時(shí)順序節(jié)點(diǎn)只監(jiān)聽(tīng)它本身的前一個(gè)節(jié)點(diǎn)變化。
流程描述:

具體代碼
具體只需要將方案一中的 ZooKeeperDistrbuteLock 改變,增加一個(gè) ZooKeeperDistrbuteLock2,測(cè)試代碼中使用 ZooKeeperDistrbuteLock2 即可測(cè)試,其他代碼都不需要改變。
/**
* @Description 真正實(shí)現(xiàn)鎖的細(xì)節(jié)
*/
public class ZooKeeperDistrbuteLock2 extends ZooKeeperAbstractLock {
private CountDownLatch countDownLatch = null;
/**
* 當(dāng)前請(qǐng)求節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)
*/
private String beforePath;
/**
* 當(dāng)前請(qǐng)求的節(jié)點(diǎn)
*/
private String currentPath;
public ZooKeeperDistrbuteLock2() {
if (!zkClient.exists(PATH)) {
// 創(chuàng)建持久節(jié)點(diǎn),保存臨時(shí)順序節(jié)點(diǎn)
zkClient.createPersistent(PATH);
}
}
@Override
public boolean tryLock() {
// 如果currentPath為空則為第一次嘗試拿鎖,第一次拿鎖賦值currentPath
if (currentPath == null || currentPath.length() == 0) {
// 在指定的持久節(jié)點(diǎn)下創(chuàng)建臨時(shí)順序節(jié)點(diǎn)
currentPath = zkClient.createEphemeralSequential(PATH + "/", "lock");
}
// 獲取所有臨時(shí)節(jié)點(diǎn)并排序,例如:000044
List<String> childrenList = zkClient.getChildren(PATH);
Collections.sort(childrenList);
if (currentPath.equals(PATH + "/" + childrenList.get(0))) {
// 如果當(dāng)前節(jié)點(diǎn)在所有節(jié)點(diǎn)中排名第一則獲取鎖成功
return true;
} else {
int wz = Collections.binarySearch(childrenList, currentPath.substring(6));
beforePath = PATH + "/" + childrenList.get(wz - 1);
}
return false;
}
@Override
public void waitLock() {
// 創(chuàng)建監(jiān)聽(tīng)
IZkDataListener iZkDataListener = new IZkDataListener() {
@Override
public void handleDataChange(String s, Object o) throws Exception {
}
@Override
public void handleDataDeleted(String s) throws Exception {
// 釋放鎖,刪除節(jié)點(diǎn)時(shí)喚醒等待的線程
if (countDownLatch != null) {
countDownLatch.countDown();
}
}
};
// 注冊(cè)監(jiān)聽(tīng),這里是給排在當(dāng)前節(jié)點(diǎn)前面的節(jié)點(diǎn)增加(刪除數(shù)據(jù)的)監(jiān)聽(tīng),本質(zhì)是啟動(dòng)另外一個(gè)線程去監(jiān)聽(tīng)前置節(jié)點(diǎn)
zkClient.subscribeDataChanges(beforePath, iZkDataListener);
// 前置節(jié)點(diǎn)存在時(shí),等待前置節(jié)點(diǎn)刪除喚醒
if (zkClient.exists(beforePath)) {
countDownLatch = new CountDownLatch(1);
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 刪除對(duì)前置節(jié)點(diǎn)的監(jiān)聽(tīng)
zkClient.unsubscribeDataChanges(beforePath, iZkDataListener);
}
/**
* 釋放鎖
*/
@Override
public void unLock() {
if (zkClient != null) {
System.out.println("釋放鎖資源");
zkClient.delete(currentPath);
zkClient.close();
}
}
}
以上就是分析ZooKeeper分布式鎖的實(shí)現(xiàn)的詳細(xì)內(nèi)容,更多關(guān)于ZooKeeper分布式鎖的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
java后臺(tái)利用Apache poi 生成excel文檔提供前臺(tái)下載示例
本篇文章主要介紹了java后臺(tái)利用Apache poi 生成excel文檔提供前臺(tái)下載示例,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-05-05
SpringBoot結(jié)果封裝和異常攔截的實(shí)現(xiàn)示例
SpringBoot 項(xiàng)目中,我們通常需要將結(jié)果數(shù)據(jù)封裝成特定的格式,以方便客戶端進(jìn)行處理,本文主要介紹了SpringBoot?優(yōu)雅的結(jié)果封裝和異常攔截,感興趣的可以了解一下2023-08-08
springBoot使用JdbcTemplate代碼實(shí)例
這篇文章主要介紹了springBoot使用JdbcTemplate代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09
Spring @Valid @Validated實(shí)現(xiàn)驗(yàn)證
這篇文章主要介紹了Spring @Valid @Validated實(shí)現(xiàn)驗(yàn)證,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01
java8中parallelStream性能測(cè)試及結(jié)果分析
本篇文章給大家用代碼實(shí)例做了segmentfaultjava8中parallelStream性能測(cè)試,并對(duì)測(cè)試結(jié)果做了說(shuō)明,需要的朋友學(xué)習(xí)下吧。2018-01-01

