欧美free性护士vide0shd,老熟女,一区二区三区,久久久久夜夜夜精品国产,久久久久久综合网天天,欧美成人护士h版

首頁綜合 正文
目錄

柚子快報(bào)邀請(qǐng)碼778899分享:分布式鎖詳解

柚子快報(bào)邀請(qǐng)碼778899分享:分布式鎖詳解

http://yzkb.51969.com/

網(wǎng)上有很多分布式鎖相關(guān)的文章,寫了一個(gè)相對(duì)簡潔易懂的版本,針對(duì)面試和工作應(yīng)該夠用了。

# 分布式鎖介紹

對(duì)于單機(jī)多線程來說,在 Java 中,我們通常使用 ReetrantLock 類、synchronized 關(guān)鍵字這類 JDK 自帶的 本地鎖 來控制一個(gè) JVM 進(jìn)程內(nèi)的多個(gè)線程對(duì)本地共享資源的訪問。

下面是我對(duì)本地鎖畫的一張示意圖。

從圖中可以看出,這些線程訪問共享資源是互斥的,同一時(shí)刻只有一個(gè)線程可以獲取到本地鎖訪問共享資源。

分布式系統(tǒng)下,不同的服務(wù)/客戶端通常運(yùn)行在獨(dú)立的 JVM 進(jìn)程上。如果多個(gè) JVM 進(jìn)程共享同一份資源的話,使用本地鎖就沒辦法實(shí)現(xiàn)資源的互斥訪問了。于是,分布式鎖 就誕生了。

舉個(gè)例子:系統(tǒng)的訂單服務(wù)一共部署了 3 份,都對(duì)外提供服務(wù)。用戶下訂單之前需要檢查庫存,為了防止超賣,這里需要加鎖以實(shí)現(xiàn)對(duì)檢查庫存操作的同步訪問。由于訂單服務(wù)位于不同的 JVM 進(jìn)程中,本地鎖在這種情況下就沒辦法正常工作了。我們需要用到分布式鎖,這樣的話,即使多個(gè)線程不在同一個(gè) JVM 進(jìn)程中也能獲取到同一把鎖,進(jìn)而實(shí)現(xiàn)共享資源的互斥訪問。

下面是我對(duì)分布式鎖畫的一張示意圖。

從圖中可以看出,這些獨(dú)立的進(jìn)程中的線程訪問共享資源是互斥的,同一時(shí)刻只有一個(gè)線程可以獲取到分布式鎖訪問共享資源。

一個(gè)最基本的分布式鎖需要滿足:

互斥 :任意一個(gè)時(shí)刻,鎖只能被一個(gè)線程持有;高可用 :鎖服務(wù)是高可用的。并且,即使客戶端的釋放鎖的代碼邏輯出現(xiàn)問題,鎖最終一定還是會(huì)被釋放,不會(huì)影響其他線程對(duì)共享資源的訪問??芍厝耄阂粋€(gè)節(jié)點(diǎn)獲取了鎖之后,還可以再次獲取鎖。

通常情況下,我們一般會(huì)選擇基于 Redis 或者 ZooKeeper 實(shí)現(xiàn)分布式鎖,Redis 用的要更多一點(diǎn),我這里也以 Redis 為例介紹分布式鎖的實(shí)現(xiàn)。

# 基于 Redis 實(shí)現(xiàn)分布式鎖

# 如何基于 Redis 實(shí)現(xiàn)一個(gè)最簡易的分布式鎖?

不論是本地鎖還是分布式鎖,核心都在于“互斥”。

在 Redis 中, SETNX 命令是可以幫助我們實(shí)現(xiàn)互斥。SETNX 即 SET if Not eXists (對(duì)應(yīng) Java 中的 setIfAbsent 方法),如果 key 不存在的話,才會(huì)設(shè)置 key 的值。如果 key 已經(jīng)存在, SETNX 啥也不做。

> SETNX lockKey uniqueValue

(integer) 1

> SETNX lockKey uniqueValue

(integer) 0

釋放鎖的話,直接通過 DEL 命令刪除對(duì)應(yīng)的 key 即可。

> DEL lockKey

(integer) 1

為了防止誤刪到其他的鎖,這里我們建議使用 Lua 腳本通過 key 對(duì)應(yīng)的 value(唯一值)來判斷。

選用 Lua 腳本是為了保證解鎖操作的原子性。因?yàn)?Redis 在執(zhí)行 Lua 腳本時(shí),可以以原子性的方式執(zhí)行,從而保證了鎖釋放操作的原子性。

// 釋放鎖時(shí),先比較鎖對(duì)應(yīng)的 value 值是否相等,避免鎖的誤釋放

if redis.call("get",KEYS[1]) == ARGV[1] then

return redis.call("del",KEYS[1])

else

return 0

end

這是一種最簡易的 Redis 分布式鎖實(shí)現(xiàn),實(shí)現(xiàn)方式比較簡單,性能也很高效。不過,這種方式實(shí)現(xiàn)分布式鎖存在一些問題。就比如應(yīng)用程序遇到一些問題比如釋放鎖的邏輯突然掛掉,可能會(huì)導(dǎo)致鎖無法被釋放,進(jìn)而造成共享資源無法再被其他線程/進(jìn)程訪問。

# 為什么要給鎖設(shè)置一個(gè)過期時(shí)間?

為了避免鎖無法被釋放,我們可以想到的一個(gè)解決辦法就是: 給這個(gè) key(也就是鎖) 設(shè)置一個(gè)過期時(shí)間 。

127.0.0.1:6379> SET lockKey uniqueValue EX 3 NX

OK

lockKey :加鎖的鎖名;uniqueValue :能夠唯一標(biāo)示鎖的隨機(jī)字符串;NX :只有當(dāng) lockKey 對(duì)應(yīng)的 key 值不存在的時(shí)候才能 SET 成功;EX :過期時(shí)間設(shè)置(秒為單位)EX 3 標(biāo)示這個(gè)鎖有一個(gè) 3 秒的自動(dòng)過期時(shí)間。與 EX 對(duì)應(yīng)的是 PX(毫秒為單位),這兩個(gè)都是過期時(shí)間設(shè)置。

一定要保證設(shè)置指定 key 的值和過期時(shí)間是一個(gè)原子操作?。?! 不然的話,依然可能會(huì)出現(xiàn)鎖無法被釋放的問題。

這樣確實(shí)可以解決問題,不過,這種解決辦法同樣存在漏洞:如果操作共享資源的時(shí)間大于過期時(shí)間,就會(huì)出現(xiàn)鎖提前過期的問題,進(jìn)而導(dǎo)致分布式鎖直接失效。如果鎖的超時(shí)時(shí)間設(shè)置過長,又會(huì)影響到性能。

你或許在想: 如果操作共享資源的操作還未完成,鎖過期時(shí)間能夠自己續(xù)期就好了!

# 如何實(shí)現(xiàn)鎖的優(yōu)雅續(xù)期?

對(duì)于 Java 開發(fā)的小伙伴來說,已經(jīng)有了現(xiàn)成的解決方案:Redissonopen in new window 。其他語言的解決方案,可以在 Redis 官方文檔中找到,地址:https://redis.io/topics/distlock 。

Redisson 是一個(gè)開源的 Java 語言 Redis 客戶端,提供了很多開箱即用的功能,不僅僅包括多種分布式鎖的實(shí)現(xiàn)。并且,Redisson 還支持 Redis 單機(jī)、Redis Sentinel 、Redis Cluster 等多種部署架構(gòu)。

Redisson 中的分布式鎖自帶自動(dòng)續(xù)期機(jī)制,使用起來非常簡單,原理也比較簡單,其提供了一個(gè)專門用來監(jiān)控和續(xù)期鎖的 Watch Dog( 看門狗),如果操作共享資源的線程還未執(zhí)行完成的話,Watch Dog 會(huì)不斷地延長鎖的過期時(shí)間,進(jìn)而保證鎖不會(huì)因?yàn)槌瑫r(shí)而被釋放。

看門狗名字的由來于 getLockWatchdogTimeout() 方法,這個(gè)方法返回的是看門狗給鎖續(xù)期的過期時(shí)間,默認(rèn)為 30 秒(redisson-3.17.6open in new window)。

//默認(rèn) 30秒,支持修改

private long lockWatchdogTimeout = 30 * 1000;

public Config setLockWatchdogTimeout(long lockWatchdogTimeout) {

this.lockWatchdogTimeout = lockWatchdogTimeout;

return this;

}

public long getLockWatchdogTimeout() {

return lockWatchdogTimeout;

}

renewExpiration() 方法包含了看門狗的主要邏輯:

private void renewExpiration() {

//......

Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {

@Override

public void run(Timeout timeout) throws Exception {

//......

// 異步續(xù)期,基于 Lua 腳本

CompletionStage future = renewExpirationAsync(threadId);

future.whenComplete((res, e) -> {

if (e != null) {

// 無法續(xù)期

log.error("Can't update lock " + getRawName() + " expiration", e);

EXPIRATION_RENEWAL_MAP.remove(getEntryName());

return;

}

if (res) {

// 遞歸調(diào)用實(shí)現(xiàn)續(xù)期

renewExpiration();

} else {

// 取消續(xù)期

cancelExpirationRenewal(null);

}

});

}

// 延遲 internalLockLeaseTime/3(默認(rèn) 10s,也就是 30/3) 再調(diào)用

}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);

ee.setTimeout(task);

}

默認(rèn)情況下,每過 10 秒,看門狗就會(huì)執(zhí)行續(xù)期操作,將鎖的超時(shí)時(shí)間設(shè)置為 30 秒。看門狗續(xù)期前也會(huì)先判斷是否需要執(zhí)行續(xù)期操作,需要才會(huì)執(zhí)行續(xù)期,否則取消續(xù)期操作。

Watch Dog 通過調(diào)用 renewExpirationAsync() 方法實(shí)現(xiàn)鎖的異步續(xù)期:

protected CompletionStage renewExpirationAsync(long threadId) {

return evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,

// 判斷是否為持鎖線程,如果是就執(zhí)行續(xù)期操作,就鎖的過期時(shí)間設(shè)置為 30s(默認(rèn))

"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +

"redis.call('pexpire', KEYS[1], ARGV[1]); " +

"return 1; " +

"end; " +

"return 0;",

Collections.singletonList(getRawName()),

internalLockLeaseTime, getLockName(threadId));

}

可以看出, renewExpirationAsync 方法其實(shí)是調(diào)用 Lua 腳本實(shí)現(xiàn)的續(xù)期,這樣做主要是為了保證續(xù)期操作的原子性。

我這里以 Redisson 的分布式可重入鎖 RLock 為例來說明如何使用 Redisson 實(shí)現(xiàn)分布式鎖:

// 1.獲取指定的分布式鎖對(duì)象

RLock lock = redisson.getLock("lock");

// 2.拿鎖且不設(shè)置鎖超時(shí)時(shí)間,具備 Watch Dog 自動(dòng)續(xù)期機(jī)制

lock.lock();

// 3.執(zhí)行業(yè)務(wù)

...

// 4.釋放鎖

lock.unlock();

只有未指定鎖超時(shí)時(shí)間,才會(huì)使用到 Watch Dog 自動(dòng)續(xù)期機(jī)制。

// 手動(dòng)給鎖設(shè)置過期時(shí)間,不具備 Watch Dog 自動(dòng)續(xù)期機(jī)制

lock.lock(10, TimeUnit.SECONDS);

如果使用 Redis 來實(shí)現(xiàn)分布式鎖的話,還是比較推薦直接基于 Redisson 來做的。

# 如何實(shí)現(xiàn)可重入鎖?

所謂可重入鎖指的是在一個(gè)線程中可以多次獲取同一把鎖,比如一個(gè)線程在執(zhí)行一個(gè)帶鎖的方法,該方法中又調(diào)用了另一個(gè)需要相同鎖的方法,則該線程可以直接執(zhí)行調(diào)用的方法即可重入 ,而無需重新獲得鎖。像 Java 中的 synchronized 和 ReentrantLock 都屬于可重入鎖。

不可重入的分布式鎖基本可以滿足絕大部分業(yè)務(wù)場景了,一些特殊的場景可能會(huì)需要使用可重入的分布式鎖。

可重入分布式鎖的實(shí)現(xiàn)核心思路是線程在獲取鎖的時(shí)候判斷是否為自己的鎖,如果是的話,就不用再重新獲取了。為此,我們可以為每個(gè)鎖關(guān)聯(lián)一個(gè)可重入計(jì)數(shù)器和一個(gè)占有它的線程。當(dāng)可重入計(jì)數(shù)器大于 0 時(shí),則鎖被占有,需要判斷占有該鎖的線程和請(qǐng)求獲取鎖的線程是否為同一個(gè)。

實(shí)際項(xiàng)目中,我們不需要自己手動(dòng)實(shí)現(xiàn),推薦使用我們上面提到的 Redisson ,其內(nèi)置了多種類型的鎖比如可重入鎖(Reentrant Lock)、自旋鎖(Spin Lock)、公平鎖(Fair Lock)、多重鎖(MultiLock)、 紅鎖(RedLock)、 讀寫鎖(ReadWriteLock)。

# Redis 如何解決集群情況下分布式鎖的可靠性?

為了避免單點(diǎn)故障,生產(chǎn)環(huán)境下的 Redis 服務(wù)通常是集群化部署的。

Redis 集群下,上面介紹到的分布式鎖的實(shí)現(xiàn)會(huì)存在一些問題。由于 Redis 集群數(shù)據(jù)同步到各個(gè)節(jié)點(diǎn)時(shí)是異步的,如果在 Redis 主節(jié)點(diǎn)獲取到鎖后,在沒有同步到其他節(jié)點(diǎn)時(shí),Redis 主節(jié)點(diǎn)宕機(jī)了,此時(shí)新的 Redis 主節(jié)點(diǎn)依然可以獲取鎖,所以多個(gè)應(yīng)用服務(wù)就可以同時(shí)獲取到鎖。

針對(duì)這個(gè)問題,Redis 之父 antirez 設(shè)計(jì)了 Redlock 算法open in new window 來解決。

Redlock 算法的思想是讓客戶端向 Redis 集群中的多個(gè)獨(dú)立的 Redis 實(shí)例依次請(qǐng)求申請(qǐng)加鎖,如果客戶端能夠和半數(shù)以上的實(shí)例成功地完成加鎖操作,那么我們就認(rèn)為,客戶端成功地獲得分布式鎖,否則加鎖失敗。

即使部分 Redis 節(jié)點(diǎn)出現(xiàn)問題,只要保證 Redis 集群中有半數(shù)以上的 Redis 節(jié)點(diǎn)可用,分布式鎖服務(wù)就是正常的。

Redlock 是直接操作 Redis 節(jié)點(diǎn)的,并不是通過 Redis 集群操作的,這樣才可以避免 Redis 集群主從切換導(dǎo)致的鎖丟失問題。

Redlock 實(shí)現(xiàn)比較復(fù)雜,性能比較差,發(fā)生時(shí)鐘變遷的情況下還存在安全性隱患?!稊?shù)據(jù)密集型應(yīng)用系統(tǒng)設(shè)計(jì)》一書的作者 Martin Kleppmann 曾經(jīng)專門發(fā)文(How to do distributed locking - Martin Kleppmann - 2016open in new window)懟過 Redlock,他認(rèn)為這是一個(gè)很差的分布式鎖實(shí)現(xiàn)。感興趣的朋友可以看看Redis 鎖從面試連環(huán)炮聊到神仙打架open in new window這篇文章,有詳細(xì)介紹到 antirez 和 Martin Kleppmann 關(guān)于 Redlock 的激烈辯論。

實(shí)際項(xiàng)目中不建議使用 Redlock 算法,成本和收益不成正比。

如果不是非要實(shí)現(xiàn)絕對(duì)可靠的分布式鎖的話,其實(shí)單機(jī)版 Redis 就完全夠了,實(shí)現(xiàn)簡單,性能也非常高。如果你必須要實(shí)現(xiàn)一個(gè)絕對(duì)可靠的分布式鎖的話,可以基于 ZooKeeper 來做,只是性能會(huì)差一些。

# 基于 ZooKeeper 實(shí)現(xiàn)分布式鎖

Redis 實(shí)現(xiàn)分布式鎖性能較高,ZooKeeper 實(shí)現(xiàn)分布式鎖可靠性更高。實(shí)際項(xiàng)目中,我們應(yīng)該根據(jù)業(yè)務(wù)的具體需求來選擇。

# 如何基于 ZooKeeper 實(shí)現(xiàn)分布式鎖?

ZooKeeper 分布式鎖是基于 臨時(shí)順序節(jié)點(diǎn) 和 Watcher(事件監(jiān)聽器) 實(shí)現(xiàn)的。

獲取鎖:

首先我們要有一個(gè)持久節(jié)點(diǎn)/locks,客戶端獲取鎖就是在locks下創(chuàng)建臨時(shí)順序節(jié)點(diǎn)。假設(shè)客戶端 1 創(chuàng)建了/locks/lock1節(jié)點(diǎn),創(chuàng)建成功之后,會(huì)判斷 lock1是否是 /locks 下最小的子節(jié)點(diǎn)。如果 lock1是最小的子節(jié)點(diǎn),則獲取鎖成功。否則,獲取鎖失敗。如果獲取鎖失敗,則說明有其他的客戶端已經(jīng)成功獲取鎖。客戶端 1 并不會(huì)不停地循環(huán)去嘗試加鎖,而是在前一個(gè)節(jié)點(diǎn)比如/locks/lock0上注冊(cè)一個(gè)事件監(jiān)聽器。這個(gè)監(jiān)聽器的作用是當(dāng)前一個(gè)節(jié)點(diǎn)釋放鎖之后通知客戶端 1(避免無效自旋),這樣客戶端 1 就加鎖成功了。

釋放鎖:

成功獲取鎖的客戶端在執(zhí)行完業(yè)務(wù)流程之后,會(huì)將對(duì)應(yīng)的子節(jié)點(diǎn)刪除。成功獲取鎖的客戶端在出現(xiàn)故障之后,對(duì)應(yīng)的子節(jié)點(diǎn)由于是臨時(shí)順序節(jié)點(diǎn),也會(huì)被自動(dòng)刪除,避免了鎖無法被釋放。我們前面說的事件監(jiān)聽器其實(shí)監(jiān)聽的就是這個(gè)子節(jié)點(diǎn)刪除事件,子節(jié)點(diǎn)刪除就意味著鎖被釋放。

實(shí)際項(xiàng)目中,推薦使用 Curator 來實(shí)現(xiàn) ZooKeeper 分布式鎖。Curator 是 Netflix 公司開源的一套 ZooKeeper Java 客戶端框架,相比于 ZooKeeper 自帶的客戶端 zookeeper 來說,Curator 的封裝更加完善,各種 API 都可以比較方便地使用。

Curator主要實(shí)現(xiàn)了下面四種鎖:

InterProcessMutex:分布式可重入排它鎖InterProcessSemaphoreMutex:分布式不可重入排它鎖InterProcessReadWriteLock:分布式讀寫鎖InterProcessMultiLock:將多個(gè)鎖作為單個(gè)實(shí)體管理的容器,獲取鎖的時(shí)候獲取所有鎖,釋放鎖也會(huì)釋放所有鎖資源(忽略釋放失敗的鎖)。

CuratorFramework client = ZKUtils.getClient();

client.start();

// 分布式可重入排它鎖

InterProcessLock lock1 = new InterProcessMutex(client, lockPath1);

// 分布式不可重入排它鎖

InterProcessLock lock2 = new InterProcessSemaphoreMutex(client, lockPath2);

// 將多個(gè)鎖作為一個(gè)整體

InterProcessMultiLock lock = new InterProcessMultiLock(Arrays.asList(lock1, lock2));

if (!lock.acquire(10, TimeUnit.SECONDS)) {

throw new IllegalStateException("不能獲取多鎖");

}

System.out.println("已獲取多鎖");

System.out.println("是否有第一個(gè)鎖: " + lock1.isAcquiredInThisProcess());

System.out.println("是否有第二個(gè)鎖: " + lock2.isAcquiredInThisProcess());

try {

// 資源操作

resource.use();

} finally {

System.out.println("釋放多個(gè)鎖");

lock.release();

}

System.out.println("是否有第一個(gè)鎖: " + lock1.isAcquiredInThisProcess());

System.out.println("是否有第二個(gè)鎖: " + lock2.isAcquiredInThisProcess());

client.close();

# 為什么要用臨時(shí)順序節(jié)點(diǎn)?

每個(gè)數(shù)據(jù)節(jié)點(diǎn)在 ZooKeeper 中被稱為 znode,它是 ZooKeeper 中數(shù)據(jù)的最小單元。

我們通常是將 znode 分為 4 大類:

持久(PERSISTENT)節(jié)點(diǎn) :一旦創(chuàng)建就一直存在即使 ZooKeeper 集群宕機(jī),直到將其刪除。臨時(shí)(EPHEMERAL)節(jié)點(diǎn) :臨時(shí)節(jié)點(diǎn)的生命周期是與 客戶端會(huì)話(session) 綁定的,會(huì)話消失則節(jié)點(diǎn)消失 。并且,臨時(shí)節(jié)點(diǎn)只能做葉子節(jié)點(diǎn) ,不能創(chuàng)建子節(jié)點(diǎn)。持久順序(PERSISTENT_SEQUENTIAL)節(jié)點(diǎn) :除了具有持久(PERSISTENT)節(jié)點(diǎn)的特性之外, 子節(jié)點(diǎn)的名稱還具有順序性。比如 /node1/app0000000001 、/node1/app0000000002 。臨時(shí)順序(EPHEMERAL_SEQUENTIAL)節(jié)點(diǎn) :除了具備臨時(shí)(EPHEMERAL)節(jié)點(diǎn)的特性之外,子節(jié)點(diǎn)的名稱還具有順序性。

可以看出,臨時(shí)節(jié)點(diǎn)相比持久節(jié)點(diǎn),最主要的是對(duì)會(huì)話失效的情況處理不一樣,臨時(shí)節(jié)點(diǎn)會(huì)話消失則對(duì)應(yīng)的節(jié)點(diǎn)消失。這樣的話,如果客戶端發(fā)生異常導(dǎo)致沒來得及釋放鎖也沒關(guān)系,會(huì)話失效節(jié)點(diǎn)自動(dòng)被刪除,不會(huì)發(fā)生死鎖的問題。

使用 Redis 實(shí)現(xiàn)分布式鎖的時(shí)候,我們是通過過期時(shí)間來避免鎖無法被釋放導(dǎo)致死鎖問題的,而 ZooKeeper 直接利用臨時(shí)節(jié)點(diǎn)的特性即可。

假設(shè)不適用順序節(jié)點(diǎn)的話,所有嘗試獲取鎖的客戶端都會(huì)對(duì)持有鎖的子節(jié)點(diǎn)加監(jiān)聽器。當(dāng)該鎖被釋放之后,勢(shì)必會(huì)造成所有嘗試獲取鎖的客戶端來爭奪鎖,這樣對(duì)性能不友好。使用順序節(jié)點(diǎn)之后,只需要監(jiān)聽前一個(gè)節(jié)點(diǎn)就好了,對(duì)性能更友好。

# 為什么要設(shè)置對(duì)前一個(gè)節(jié)點(diǎn)的監(jiān)聽?

Watcher(事件監(jiān)聽器),是 ZooKeeper 中的一個(gè)很重要的特性。ZooKeeper 允許用戶在指定節(jié)點(diǎn)上注冊(cè)一些 Watcher,并且在一些特定事件觸發(fā)的時(shí)候,ZooKeeper 服務(wù)端會(huì)將事件通知到感興趣的客戶端上去,該機(jī)制是 ZooKeeper 實(shí)現(xiàn)分布式協(xié)調(diào)服務(wù)的重要特性。

同一時(shí)間段內(nèi),可能會(huì)有很多客戶端同時(shí)獲取鎖,但只有一個(gè)可以獲取成功。如果獲取鎖失敗,則說明有其他的客戶端已經(jīng)成功獲取鎖。獲取鎖失敗的客戶端并不會(huì)不停地循環(huán)去嘗試加鎖,而是在前一個(gè)節(jié)點(diǎn)注冊(cè)一個(gè)事件監(jiān)聽器。

這個(gè)事件監(jiān)聽器的作用是: 當(dāng)前一個(gè)節(jié)點(diǎn)對(duì)應(yīng)的客戶端釋放鎖之后(也就是前一個(gè)節(jié)點(diǎn)被刪除之后,監(jiān)聽的是刪除事件),通知獲取鎖失敗的客戶端(喚醒等待的線程,Java 中的 wait/notifyAll ),讓它嘗試去獲取鎖,然后就成功獲取鎖了。

# 如何實(shí)現(xiàn)可重入鎖?

這里以 Curator 的 InterProcessMutex 對(duì)可重入鎖的實(shí)現(xiàn)來介紹(源碼地址:InterProcessMutex.javaopen in new window)。

當(dāng)我們調(diào)用 InterProcessMutex#acquire方法獲取鎖的時(shí)候,會(huì)調(diào)用InterProcessMutex#internalLock方法。

// 獲取可重入互斥鎖,直到獲取成功為止

@Override

public void acquire() throws Exception {

if (!internalLock(-1, null)) {

throw new IOException("Lost connection while trying to acquire lock: " + basePath);

}

}

internalLock 方法會(huì)先獲取當(dāng)前請(qǐng)求鎖的線程,然后從 threadData( ConcurrentMap 類型)中獲取當(dāng)前線程對(duì)應(yīng)的 lockData 。 lockData 包含鎖的信息和加鎖的次數(shù),是實(shí)現(xiàn)可重入鎖的關(guān)鍵。

第一次獲取鎖的時(shí)候,lockData為 null。獲取鎖成功之后,會(huì)將當(dāng)前線程和對(duì)應(yīng)的 lockData 放到 threadData 中

private boolean internalLock(long time, TimeUnit unit) throws Exception {

// 獲取當(dāng)前請(qǐng)求鎖的線程

Thread currentThread = Thread.currentThread();

// 拿對(duì)應(yīng)的 lockData

LockData lockData = threadData.get(currentThread);

// 第一次獲取鎖的話,lockData 為 null

if (lockData != null) {

// 當(dāng)前線程獲取過一次鎖之后

// 因?yàn)楫?dāng)前線程的鎖存在, lockCount 自增后返回,實(shí)現(xiàn)鎖重入.

lockData.lockCount.incrementAndGet();

return true;

}

// 嘗試獲取鎖

String lockPath = internals.attemptLock(time, unit, getLockNodeBytes());

if (lockPath != null) {

LockData newLockData = new LockData(currentThread, lockPath);

// 獲取鎖成功之后,將當(dāng)前線程和對(duì)應(yīng)的 lockData 放到 threadData 中

threadData.put(currentThread, newLockData);

return true;

}

return false;

}

LockData是 InterProcessMutex中的一個(gè)靜態(tài)內(nèi)部類。

private final ConcurrentMap threadData = Maps.newConcurrentMap();

private static class LockData

{

// 當(dāng)前持有鎖的線程

final Thread owningThread;

// 鎖對(duì)應(yīng)的子節(jié)點(diǎn)

final String lockPath;

// 加鎖的次數(shù)

final AtomicInteger lockCount = new AtomicInteger(1);

private LockData(Thread owningThread, String lockPath)

{

this.owningThread = owningThread;

this.lockPath = lockPath;

}

}

如果已經(jīng)獲取過一次鎖,后面再來獲取鎖的話,直接就會(huì)在 if (lockData != null) 這里被攔下了,然后就會(huì)執(zhí)行l(wèi)ockData.lockCount.incrementAndGet(); 將加鎖次數(shù)加 1。

整個(gè)可重入鎖的實(shí)現(xiàn)邏輯非常簡單,直接在客戶端判斷當(dāng)前線程有沒有獲取鎖,有的話直接將加鎖次數(shù)加 1 就可以了。

# 總結(jié)

這篇文章我們介紹了分布式鎖的基本概念以及實(shí)現(xiàn)分布式鎖的兩種常見方式。至于具體選擇 Redis 還是 ZooKeeper 來實(shí)現(xiàn)分布式鎖,還是要看業(yè)務(wù)的具體需求。如果對(duì)性能要求比較高的話,建議使用 Redis 實(shí)現(xiàn)分布式鎖。如果對(duì)可靠性要求比較高的話,建議使用 ZooKeeper 實(shí)現(xiàn)分布式鎖。

柚子快報(bào)邀請(qǐng)碼778899分享:分布式鎖詳解

http://yzkb.51969.com/

精彩內(nèi)容

評(píng)論可見,查看隱藏內(nèi)容

本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場。

轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。

本文鏈接:http://gantiao.com.cn/post/19148669.html

發(fā)布評(píng)論

您暫未設(shè)置收款碼

請(qǐng)?jiān)谥黝}配置——文章設(shè)置里上傳

掃描二維碼手機(jī)訪問

文章目錄