久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

基于redis的分布式鎖實(shí)現(xiàn) | Aoho''s Blog

 Gtwo 2018-08-30

關(guān)于分布式鎖

很久之前有講過(guò)并發(fā)編程中的鎖并發(fā)編程的鎖機(jī)制:synchronized和lock,。在單進(jìn)程的系統(tǒng)中,,當(dāng)存在多個(gè)線程可以同時(shí)改變某個(gè)變量時(shí),,就需要對(duì)變量或代碼塊做同步,使其在修改這種變量時(shí)能夠線性執(zhí)行消除并發(fā)修改變量,。而同步的本質(zhì)是通過(guò)鎖來(lái)實(shí)現(xiàn)的,。為了實(shí)現(xiàn)多個(gè)線程在一個(gè)時(shí)刻同一個(gè)代碼塊只能有一個(gè)線程可執(zhí)行,那么需要在某個(gè)地方做個(gè)標(biāo)記,,這個(gè)標(biāo)記必須每個(gè)線程都能看到,,當(dāng)標(biāo)記不存在時(shí)可以設(shè)置該標(biāo)記,其余后續(xù)線程發(fā)現(xiàn)已經(jīng)有標(biāo)記了則等待擁有標(biāo)記的線程結(jié)束同步代碼塊取消標(biāo)記后再去嘗試設(shè)置標(biāo)記,。

分布式環(huán)境下,,數(shù)據(jù)一致性問(wèn)題一直是一個(gè)比較重要的話題,而又不同于單進(jìn)程的情況,。分布式與單機(jī)情況下最大的不同在于其不是多線程而是多進(jìn)程,。多線程由于可以共享堆內(nèi)存,因此可以簡(jiǎn)單的采取內(nèi)存作為標(biāo)記存儲(chǔ)位置,。而進(jìn)程之間甚至可能都不在同一臺(tái)物理機(jī)上,,因此需要將標(biāo)記存儲(chǔ)在一個(gè)所有進(jìn)程都能看到的地方。

常見(jiàn)的是秒殺場(chǎng)景,,訂單服務(wù)部署了多個(gè)實(shí)例,。如秒殺商品有4個(gè),第一個(gè)用戶購(gòu)買(mǎi)3個(gè),,第二個(gè)用戶購(gòu)買(mǎi)2個(gè),,理想狀態(tài)下第一個(gè)用戶能購(gòu)買(mǎi)成功,第二個(gè)用戶提示購(gòu)買(mǎi)失敗,,反之亦可,。而實(shí)際可能出現(xiàn)的情況是,兩個(gè)用戶都得到庫(kù)存為4,,第一個(gè)用戶買(mǎi)到了3個(gè),,更新庫(kù)存之前,第二個(gè)用戶下了2個(gè)商品的訂單,,更新庫(kù)存為2,,導(dǎo)致出錯(cuò)。

在上面的場(chǎng)景中,,商品的庫(kù)存是共享變量,,面對(duì)高并發(fā)情形,需要保證對(duì)資源的訪問(wèn)互斥,。在單機(jī)環(huán)境中,,Java中其實(shí)提供了很多并發(fā)處理相關(guān)的API,但是這些API在分布式場(chǎng)景中就無(wú)能為力了,。也就是說(shuō)單純的Java Api并不能提供分布式鎖的能力,。分布式系統(tǒng)中,,由于分布式系統(tǒng)的分布性,即多線程和多進(jìn)程并且分布在不同機(jī)器中,,synchronized和lock這兩種鎖將失去原有鎖的效果,,需要我們自己實(shí)現(xiàn)分布式鎖。

常見(jiàn)的鎖方案如下:

  • 基于數(shù)據(jù)庫(kù)實(shí)現(xiàn)分布式鎖
  • 基于緩存,,實(shí)現(xiàn)分布式鎖,,如redis
  • 基于Zookeeper實(shí)現(xiàn)分布式鎖

下面我們簡(jiǎn)單介紹下這幾種鎖的實(shí)現(xiàn)。

基于數(shù)據(jù)庫(kù)

基于數(shù)據(jù)庫(kù)的鎖實(shí)現(xiàn)也有兩種方式,,一是基于數(shù)據(jù)庫(kù)表,,另一種是基于數(shù)據(jù)庫(kù)排他鎖。

基于數(shù)據(jù)庫(kù)表的增刪

基于數(shù)據(jù)庫(kù)表增刪是最簡(jiǎn)單的方式,,首先創(chuàng)建一張鎖的表主要包含下列字段:方法名,,時(shí)間戳等字段。

具體使用的方法,,當(dāng)需要鎖住某個(gè)方法時(shí),,往該表中插入一條相關(guān)的記錄。這邊需要注意,,方法名是有唯一性約束的,,如果有多個(gè)請(qǐng)求同時(shí)提交到數(shù)據(jù)庫(kù)的話,數(shù)據(jù)庫(kù)會(huì)保證只有一個(gè)操作可以成功,,那么我們就可以認(rèn)為操作成功的那個(gè)線程獲得了該方法的鎖,,可以執(zhí)行方法體內(nèi)容。

執(zhí)行完畢,,需要delete該記錄,。

當(dāng)然,筆者這邊只是簡(jiǎn)單介紹一下,。對(duì)于上述方案可以進(jìn)行優(yōu)化,,如應(yīng)用主從數(shù)據(jù)庫(kù),數(shù)據(jù)之間雙向同步,。一旦掛掉快速切換到備庫(kù)上,;做一個(gè)定時(shí)任務(wù),每隔一定時(shí)間把數(shù)據(jù)庫(kù)中的超時(shí)數(shù)據(jù)清理一遍,;使用while循環(huán),,直到insert成功再返回成功,雖然并不推薦這樣做,;還可以記錄當(dāng)前獲得鎖的機(jī)器的主機(jī)信息和線程信息,,那么下次再獲取鎖的時(shí)候先查詢數(shù)據(jù)庫(kù),如果當(dāng)前機(jī)器的主機(jī)信息和線程信息在數(shù)據(jù)庫(kù)可以查到的話,直接把鎖分配給他就可以了,,實(shí)現(xiàn)可重入鎖,。

基于數(shù)據(jù)庫(kù)排他鎖

我們還可以通過(guò)數(shù)據(jù)庫(kù)的排他鎖來(lái)實(shí)現(xiàn)分布式鎖,?;贛ySql的InnoDB引擎,可以使用以下方法來(lái)實(shí)現(xiàn)加鎖操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void lock(){
connection.setAutoCommit(false)
int count = 0;
while(count < 4){
try{
select * from lock where lock_name=xxx for update;
if(結(jié)果不為空){
//代表獲取到鎖
return;
}
}catch(Exception e){
}
//為空或者拋異常的話都表示沒(méi)有獲取到鎖
sleep(1000);
count++;
}
throw new LockException();
}

在查詢語(yǔ)句后面增加for update,,數(shù)據(jù)庫(kù)會(huì)在查詢過(guò)程中給數(shù)據(jù)庫(kù)表增加排他鎖,。當(dāng)某條記錄被加上排他鎖之后,其他線程無(wú)法再在該行記錄上增加排他鎖,。其他沒(méi)有獲取到鎖的就會(huì)阻塞在上述select語(yǔ)句上,,可能的結(jié)果有2種,在超時(shí)之前獲取到了鎖,,在超時(shí)之前仍未獲取到鎖,。

獲得排它鎖的線程即可獲得分布式鎖,當(dāng)獲取到鎖之后,,可以執(zhí)行方法的業(yè)務(wù)邏輯,,執(zhí)行完方法之后,釋放鎖connection.commit(),。

存在的問(wèn)題主要是性能不高和sql超時(shí)的異常,。

基于數(shù)據(jù)庫(kù)鎖的優(yōu)缺點(diǎn)

上面兩種方式都是依賴數(shù)據(jù)庫(kù)的一張表,一種是通過(guò)表中的記錄的存在情況確定當(dāng)前是否有鎖存在,,另外一種是通過(guò)數(shù)據(jù)庫(kù)的排他鎖來(lái)實(shí)現(xiàn)分布式鎖,。

  • 優(yōu)點(diǎn)是直接借助數(shù)據(jù)庫(kù),簡(jiǎn)單容易理解,。
  • 缺點(diǎn)是操作數(shù)據(jù)庫(kù)需要一定的開(kāi)銷,,性能問(wèn)題需要考慮。

基于Zookeeper

基于zookeeper臨時(shí)有序節(jié)點(diǎn)可以實(shí)現(xiàn)的分布式鎖,。每個(gè)客戶端對(duì)某個(gè)方法加鎖時(shí),,在zookeeper上的與該方法對(duì)應(yīng)的指定節(jié)點(diǎn)的目錄下,生成一個(gè)唯一的瞬時(shí)有序節(jié)點(diǎn),。 判斷是否獲取鎖的方式很簡(jiǎn)單,,只需要判斷有序節(jié)點(diǎn)中序號(hào)最小的一個(gè)。 當(dāng)釋放鎖的時(shí)候,,只需將這個(gè)瞬時(shí)節(jié)點(diǎn)刪除即可,。同時(shí),其可以避免服務(wù)宕機(jī)導(dǎo)致的鎖無(wú)法釋放,,而產(chǎn)生的死鎖問(wèn)題,。

提供的第三方庫(kù)有curator,具體使用讀者可以自行去看一下。Curator提供的InterProcessMutex是分布式鎖的實(shí)現(xiàn),。acquire方法獲取鎖,,release方法釋放鎖。另外,,鎖釋放,、阻塞鎖、可重入鎖等問(wèn)題都可以有有效解決,。講下阻塞鎖的實(shí)現(xiàn),,客戶端可以通過(guò)在ZK中創(chuàng)建順序節(jié)點(diǎn),并且在節(jié)點(diǎn)上綁定監(jiān)聽(tīng)器,,一旦節(jié)點(diǎn)有變化,,Zookeeper會(huì)通知客戶端,客戶端可以檢查自己創(chuàng)建的節(jié)點(diǎn)是不是當(dāng)前所有節(jié)點(diǎn)中序號(hào)最小的,,如果是就獲取到鎖,,便可以執(zhí)行業(yè)務(wù)邏輯。

最后,,Zookeeper實(shí)現(xiàn)的分布式鎖其實(shí)存在一個(gè)缺點(diǎn),,那就是性能上可能并沒(méi)有緩存服務(wù)那么高。因?yàn)槊看卧趧?chuàng)建鎖和釋放鎖的過(guò)程中,,都要?jiǎng)討B(tài)創(chuàng)建,、銷毀瞬時(shí)節(jié)點(diǎn)來(lái)實(shí)現(xiàn)鎖功能。ZK中創(chuàng)建和刪除節(jié)點(diǎn)只能通過(guò)Leader服務(wù)器來(lái)執(zhí)行,,然后將數(shù)據(jù)同不到所有的Follower機(jī)器上,。并發(fā)問(wèn)題,可能存在網(wǎng)絡(luò)抖動(dòng),,客戶端和ZK集群的session連接斷了,,zk集群以為客戶端掛了,就會(huì)刪除臨時(shí)節(jié)點(diǎn),,這時(shí)候其他客戶端就可以獲取到分布式鎖了,。

基于緩存

相對(duì)于基于數(shù)據(jù)庫(kù)實(shí)現(xiàn)分布式鎖的方案來(lái)說(shuō),基于緩存來(lái)實(shí)現(xiàn)在性能方面會(huì)表現(xiàn)的更好一點(diǎn),,存取速度快很多,。而且很多緩存是可以集群部署的,可以解決單點(diǎn)問(wèn)題,?;诰彺娴逆i有好幾種,如memcached,、redis,、本文下面主要講解基于redis的分布式實(shí)現(xiàn)。

基于redis的分布式鎖實(shí)現(xiàn)

SETNX

使用redis的SETNX實(shí)現(xiàn)分布式鎖,多個(gè)進(jìn)程執(zhí)行以下Redis命令:

1
SETNX lock.id <current Unix time + lock timeout + 1>

SETNX是將 key 的值設(shè)為 value,,當(dāng)且僅當(dāng) key 不存在,。若給定的 key 已經(jīng)存在,則 SETNX 不做任何動(dòng)作,。

  • 返回1,,說(shuō)明該進(jìn)程獲得鎖,SETNX將鍵 lock.id 的值設(shè)置為鎖的超時(shí)時(shí)間,,當(dāng)前時(shí)間 +加上鎖的有效時(shí)間,。
  • 返回0,說(shuō)明其他進(jìn)程已經(jīng)獲得了鎖,,進(jìn)程不能進(jìn)入臨界區(qū)。進(jìn)程可以在一個(gè)循環(huán)中不斷地嘗試 SETNX 操作,,以獲得鎖,。

存在死鎖的問(wèn)題

SETNX實(shí)現(xiàn)分布式鎖,可能會(huì)存在死鎖的情況,。與單機(jī)模式下的鎖相比,,分布式環(huán)境下不僅需要保證進(jìn)程可見(jiàn),還需要考慮進(jìn)程與鎖之間的網(wǎng)絡(luò)問(wèn)題,。某個(gè)線程獲取了鎖之后,,斷開(kāi)了與Redis 的連接,鎖沒(méi)有及時(shí)釋放,,競(jìng)爭(zhēng)該鎖的其他線程都會(huì)hung,,產(chǎn)生死鎖的情況。

在使用 SETNX 獲得鎖時(shí),,我們將鍵 lock.id 的值設(shè)置為鎖的有效時(shí)間,,線程獲得鎖后,其他線程還會(huì)不斷的檢測(cè)鎖是否已超時(shí),,如果超時(shí),,等待的線程也將有機(jī)會(huì)獲得鎖。然而,,鎖超時(shí),,我們不能簡(jiǎn)單地使用 DEL 命令刪除鍵 lock.id 以釋放鎖。

考慮以下情況:

  1. A已經(jīng)首先獲得了鎖 lock.id,,然后線A斷線,。B,C都在等待競(jìng)爭(zhēng)該鎖;
  2. B,C讀取lock.id的值,,比較當(dāng)前時(shí)間和鍵 lock.id 的值來(lái)判斷是否超時(shí),,發(fā)現(xiàn)超時(shí);
  3. B執(zhí)行 DEL lock.id命令,并執(zhí)行 SETNX lock.id 命令,,并返回1,,B獲得鎖;
  4. C由于各剛剛檢測(cè)到鎖已超時(shí),,執(zhí)行 DEL lock.id命令,,將B剛剛設(shè)置的鍵 lock.id 刪除,執(zhí)行 SETNX lock.id命令,,并返回1,,即C獲得鎖。

上面的步驟很明顯出現(xiàn)了問(wèn)題,,導(dǎo)致B,C同時(shí)獲取了鎖,。在檢測(cè)到鎖超時(shí)后,線程不能直接簡(jiǎn)單地執(zhí)行 DEL 刪除鍵的操作以獲得鎖,。

對(duì)于上面的步驟進(jìn)行改進(jìn),,問(wèn)題是出在刪除鍵的操作上面,那么獲取鎖之后應(yīng)該怎么改進(jìn)呢,?
首先看一下redis的GETSET這個(gè)操作,,GETSET key value,將給定 key 的值設(shè)為 value ,,并返回 key 的舊值(old value),。利用這個(gè)操作指令,我們改進(jìn)一下上述的步驟,。

  1. A已經(jīng)首先獲得了鎖 lock.id,,然后線A斷線。B,C都在等待競(jìng)爭(zhēng)該鎖,;
  2. B,C讀取lock.id的值,,比較當(dāng)前時(shí)間和鍵 lock.id 的值來(lái)判斷是否超時(shí),發(fā)現(xiàn)超時(shí),;
  3. B檢測(cè)到鎖已超時(shí),,即當(dāng)前的時(shí)間大于鍵 lock.id 的值,B會(huì)執(zhí)行
    GETSET lock.id <current Unix timestamp + lock timeout + 1>設(shè)置時(shí)間戳,,通過(guò)比較鍵 lock.id 的舊值是否小于當(dāng)前時(shí)間,,判斷進(jìn)程是否已獲得鎖;
  4. B發(fā)現(xiàn)GETSET返回的值小于當(dāng)前時(shí)間,,則執(zhí)行 DEL lock.id命令,,并執(zhí)行 SETNX lock.id 命令,并返回1,,B獲得鎖,;
  5. C執(zhí)行GETSET得到的時(shí)間大于當(dāng)前時(shí)間,,則繼續(xù)等待。

在線程釋放鎖,,即執(zhí)行 DEL lock.id 操作前,,需要先判斷鎖是否已超時(shí)。如果鎖已超時(shí),,那么鎖可能已由其他線程獲得,,這時(shí)直接執(zhí)行 DEL lock.id 操作會(huì)導(dǎo)致把其他線程已獲得的鎖釋放掉。

一種實(shí)現(xiàn)方式

獲取鎖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public boolean lock(long acquireTimeout, TimeUnit timeUnit) throws InterruptedException {
acquireTimeout = timeUnit.toMillis(acquireTimeout);
long acquireTime = acquireTimeout + System.currentTimeMillis();
//使用J.U.C的ReentrantLock
threadLock.tryLock(acquireTimeout, timeUnit);
try {
//循環(huán)嘗試
while (true) {
//調(diào)用tryLock
boolean hasLock = tryLock();
if (hasLock) {
//獲取鎖成功
return true;
} else if (acquireTime < System.currentTimeMillis()) {
break;
}
Thread.sleep(sleepTime);
}
} finally {
if (threadLock.isHeldByCurrentThread()) {
threadLock.unlock();
}
}
return false;
}
public boolean tryLock() {
long currentTime = System.currentTimeMillis();
String expires = String.valueOf(timeout + currentTime);
//設(shè)置互斥量
if (redisHelper.setNx(mutex, expires) > 0) {
//獲取鎖,,設(shè)置超時(shí)時(shí)間
setLockStatus(expires);
return true;
} else {
String currentLockTime = redisUtil.get(mutex);
//檢查鎖是否超時(shí)
if (Objects.nonNull(currentLockTime) && Long.parseLong(currentLockTime) < currentTime) {
//獲取舊的鎖時(shí)間并設(shè)置互斥量
String oldLockTime = redisHelper.getSet(mutex, expires);
//舊值與當(dāng)前時(shí)間比較
if (Objects.nonNull(oldLockTime) && Objects.equals(oldLockTime, currentLockTime)) {
//獲取鎖,,設(shè)置超時(shí)時(shí)間
setLockStatus(expires);
return true;
}
}
return false;
}
}

lock調(diào)用tryLock方法,參數(shù)為獲取的超時(shí)時(shí)間與單位,,線程在超時(shí)時(shí)間內(nèi),,獲取鎖操作將自旋在那里,直到該自旋鎖的保持者釋放了鎖,。

tryLock方法中,,主要邏輯如下:

  • setnx(lockkey, 當(dāng)前時(shí)間+過(guò)期超時(shí)時(shí)間) ,如果返回1,,則獲取鎖成功;如果返回0則沒(méi)有獲取到鎖
  • get(lockkey)獲取值oldExpireTime ,,并將這個(gè)value值與當(dāng)前的系統(tǒng)時(shí)間進(jìn)行比較,,如果小于當(dāng)前系統(tǒng)時(shí)間,則認(rèn)為這個(gè)鎖已經(jīng)超時(shí),,可以允許別的請(qǐng)求重新獲取
  • 計(jì)算newExpireTime=當(dāng)前時(shí)間+過(guò)期超時(shí)時(shí)間,,然后getset(lockkey, newExpireTime) 會(huì)返回當(dāng)前l(fā)ockkey的值currentExpireTime
  • 判斷currentExpireTime與oldExpireTime 是否相等,如果相等,,說(shuō)明當(dāng)前getset設(shè)置成功,,獲取到了鎖。如果不相等,,說(shuō)明這個(gè)鎖又被別的請(qǐng)求獲取走了,,那么當(dāng)前請(qǐng)求可以直接返回失敗,或者繼續(xù)重試

釋放鎖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public boolean unlock() {
//只有鎖的持有線程才能解鎖
if (lockHolder == Thread.currentThread()) {
//判斷鎖是否超時(shí),,沒(méi)有超時(shí)才將互斥量刪除
if (lockExpiresTime > System.currentTimeMillis()) {
redisHelper.del(mutex);
logger.info("刪除互斥量[{}]", mutex);
}
lockHolder = null;
logger.info("釋放[{}]鎖成功", mutex);
return true;
} else {
throw new IllegalMonitorStateException("沒(méi)有獲取到鎖的線程無(wú)法執(zhí)行解鎖操作");
}
}

在上面獲取鎖的實(shí)現(xiàn)下,,其實(shí)此處的釋放鎖函數(shù)可以不需要了,有興趣的讀者可以結(jié)合上面的代碼看下為什么,?有想法可以留言哦,!

總結(jié)

本文主要講解了基于redis分布式鎖的實(shí)現(xiàn),在分布式環(huán)境下,,數(shù)據(jù)一致性問(wèn)題一直是一個(gè)比較重要的話題,,而synchronized和lock鎖在分布式環(huán)境已經(jīng)失去了作用,。常見(jiàn)的鎖的方案有基于數(shù)據(jù)庫(kù)實(shí)現(xiàn)分布式鎖、基于緩存實(shí)現(xiàn)分布式鎖,、基于Zookeeper實(shí)現(xiàn)分布式鎖,,簡(jiǎn)單介紹了每種鎖的實(shí)現(xiàn)特點(diǎn);然后,,文中探索了一下redis鎖的實(shí)現(xiàn)方案,;最后,本文給出了基于Java實(shí)現(xiàn)的redis分布式鎖,,讀者可以自行驗(yàn)證一下,。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn),。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào),。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多