基于Redission實現(xiàn)分布式鎖

實現(xiàn)Redis的分布式鎖,除了自己基于redis client原生api來實現(xiàn)之外流部,還可以使用開源框架:Redission

Redisson是一個企業(yè)級的開源Redis Client,也提供了分布式鎖的支持舞丛。

回想一下上一篇文章《基于Redis實現(xiàn)分布式鎖》說的果漾,如果自己寫代碼來通過redis設置一個值,是通過下面這個命令設置的绒障。

SET anyLock unique_value NX PX 30000

這里設置的超時時間是30s,假如我超過30s都還沒有完成業(yè)務邏輯的情況下鸵钝,key會過期,其他線程有可能會獲取到鎖变逃。

這樣一來的話怠堪,第一個線程還沒執(zhí)行完業(yè)務邏輯,第二個線程進來了也會出現(xiàn)線程安全問題。所以我們還需要額外的去維護這個過期時間璧针,太麻煩了~

我們來看看redisson是怎么實現(xiàn)的?先感受一下使用redission的爽:


就是這么簡單申屹,我們只需要通過它的api中的lock和unlock即可完成分布式鎖隧膏,他幫我們考慮了很多細節(jié):

1:redisson所有指令都通過lua腳本執(zhí)行,redis支持lua腳本原子性執(zhí)行

2:redisson設置一個key的默認過期時間為30s,如果某個客戶端持有一個鎖超過了30s怎么辦杆煞? redisson中有一個watchdog的概念腐泻,翻譯過來就是看門狗,它會在你獲取鎖之后构诚,每隔10秒幫你把key的超時時間設為30s 這樣的話铆惑,就算一直持有鎖也不會出現(xiàn)key過期了,其他線程獲取到鎖的問題了员魏。

3:redisson的“看門狗”邏輯保證了沒有死鎖發(fā)生撕阎。 (如果機器宕機了,看門狗也就沒了闻书。此時就不會延長key的過期時間脑慧,到了30s之后就會自動過期了砰盐,其他線程可以獲取到鎖)


示例代碼:

// 加鎖以后30秒鐘自動解鎖

// 無需調用unlock方法手動解鎖

redissionLock.lock(30, TimeUnit.SECONDS);

其實上面截圖的代碼

應該改為下面這個才好岩梳,這樣子看門狗才能真正生效,不然上面的代碼會造成30秒后也物,鎖會自動解鎖的列疗。

redissionLock.lock();

但是我們除了要考慮客戶端要怎么實現(xiàn)分布式鎖之外,還需要考慮redis的部署問題抵栈。


redis有三種部署方式:

1:單機模式

如果采用單機部署模式,會存在單點問題斥赋,只要redis故障了产艾,加鎖就不行了。

2:master-slave + sentinel 哨兵模式

采用master-slave模式骚露,即便通過sentinel做了高可用(Master 宕機后立馬切換Slave作為新的Master)缚窿,但是由于節(jié)點之間是采用異步通信的方式,如果A客戶端剛剛在 Master 節(jié)點上加了鎖误续,但是數(shù)據(jù)還沒被同步到 Salve扫茅,這時 Master 節(jié)點掛了,它上面的鎖就沒了栽烂,這時進行主從切換,等新的 Master 出來后腺办,B客戶端此時就可以再獲取同樣的鎖,出現(xiàn)一把鎖被拿到了兩次的場景怀喉,從而導致系統(tǒng)出現(xiàn)臟數(shù)據(jù)书妻。


3:redis cluster 集群模式

采用redis cluster集群模式,比如3主3從躬拢,主備切換躲履。但是由于節(jié)點之間是采用異步通信的方式,如果A客戶端剛剛根據(jù)路由規(guī)則在其中一臺Master 節(jié)點上加了鎖聊闯,但是數(shù)據(jù)還沒被同步到 它的Salve工猜,這時 這臺Master 節(jié)點掛了,它上面的鎖就沒了菱蔬,這時進行主備切換篷帅,等新的 Master 出來后,B客戶端此時就可以再獲取同樣的鎖汗销,出現(xiàn)一把鎖被拿到了兩次的場景犹褒,從而導致系統(tǒng)出現(xiàn)臟數(shù)據(jù)弛针。


為了解決上面的問題,Redis 的作者提出了名為 Redlock 的算法李皇。

在 Redis 的分布式環(huán)境中削茁,我們假設有 N 個 Redis Master。這些節(jié)點完全互相獨立掉房,不存在主從復制或者其他集群協(xié)調機制茧跋。(要注意這點)

前面已經(jīng)描述了在單點 Redis 下,怎么安全地獲取和釋放鎖卓囚,我們確保將在 N 個實例上使用此方法獲取和釋放鎖瘾杭。

在下面的示例中,我們假設有 5 個完全獨立的 Redis Master 節(jié)點哪亿,他們分別運行在 5 臺服務器中粥烁,可以保證他們不會同時宕機。

從官網(wǎng)上我們可以知道蝇棉,一個客戶端如果要獲得鎖讨阻,必須經(jīng)過下面的五個步驟:

步驟描述來源:

http://redis.cn/topics/distlock.html

1:獲取當前Unix時間,以毫秒為單位篡殷。

2:依次嘗試從N個實例钝吮,使用相同的key和隨機值獲取鎖。在步驟2,當向Redis設置鎖時,客戶端應該設置一個網(wǎng)絡連接和響應超時時間奇瘦,這個超時時間應該小于鎖的失效時間棘催。例如你的鎖自動失效時間為10秒,則超時時間應該在5-50毫秒之間链患。這樣可以避免服務器端Redis已經(jīng)掛掉的情況下巧鸭,客戶端還在死死地等待響應結果。如果服務器端沒有在規(guī)定時間內響應麻捻,客戶端應該盡快嘗試另外一個Redis實例纲仍。

3:客戶端使用當前時間減去開始獲取鎖時間(步驟1記錄的時間)就得到獲取鎖使用的時間。當且僅當從大多數(shù)(這里是3個節(jié)點)的Redis節(jié)點都取到鎖贸毕,并且使用的時間小于鎖失效時間時郑叠,鎖才算獲取成功。如果取到了鎖明棍,key的真正有效時間等于有效時間減去獲取鎖所使用的時間(步驟3計算的結果)乡革。

4:如果因為某些原因,獲取鎖失斕浮(沒有在至少N/2+1個Redis實例取到鎖或者取鎖時間已經(jīng)超過了有效時間)沸版,客戶端應該在所有的Redis實例上進行解鎖(即便某些Redis實例根本就沒有加鎖成功)。


通過上面的步驟我們可以知道兴蒸,只要大多數(shù)的節(jié)點可以正常工作视粮,就可以保證 Redlock 的正常工作。這樣就可以解決前面單點 Redis 的情況下我們討論的節(jié)點掛掉橙凳,由于異步通信蕾殴,導致鎖失效的問題。

但是岛啸,還是不能解決故障重啟后帶來的鎖的安全性的問題钓觉。你想一下下面這個場景,這個場景讓redis的創(chuàng)始人提出了“延遲重啟”的概念坚踩。

紅鎖之redis持久化失敗后重啟場景

我們一共有 A荡灾、B、C 這三個節(jié)點瞬铸。

客戶端 1 在 A批幌,B 上加鎖成功。C 上加鎖失敗赴捞。

這時節(jié)點 B 崩潰重啟了逼裆,但是由于持久化策略導致客戶端 1 在 B 上的鎖沒有持久化下來。

客戶端 2 發(fā)起申請同一把鎖的操作赦政,在 B胜宇,C 上加鎖成功耀怜。

這個時候就又出現(xiàn)同一把鎖,同時被客戶端 1 和客戶端 2 所持有了桐愉。

(接下來又得說一說Redis的持久化策略了财破,全是知識點啊,朋友們)

比如从诲,Redis 的 AOF 持久化方式默認情況下是每秒寫一次磁盤左痢,即 fsync 操作,因此最壞的情況下可能丟失 1 秒的數(shù)據(jù)系洛。

當然俊性,你也可以設置成每次修改數(shù)據(jù)都進行 fsync 操作(fsync=always),但這會嚴重降低 Redis 的性能描扯,違反了它的設計理念定页。(我也沒見過這樣用的,可能還是見的太少了吧绽诚。)

而且典徊,你以為執(zhí)行了 fsync 就不會丟失數(shù)據(jù)了?天真恩够,真實的系統(tǒng)環(huán)境是復雜的卒落,這都已經(jīng)脫離 Redis 的范疇了。上升到服務器蜂桶、系統(tǒng)問題了儡毕。

所以,根據(jù)墨菲定律屎飘,上面舉的例子:由于節(jié)點重啟引發(fā)的鎖失效問題妥曲,總是有可能出現(xiàn)的贾费。

為了解決這一問題钦购,Redis 的創(chuàng)始人又提出了延遲重啟(delayed restarts)的概念。

意思就是說褂萧,一個節(jié)點崩潰后押桃,不要立即重啟它,而是等待一定的時間后再重啟导犹。等待的時間應該大于鎖的過期時間(TTL)唱凯。這樣做的目的是保證這個節(jié)點在重啟前所參與的鎖都過期。相當于把以前的帳勾銷之后才能參與后面的加鎖操作谎痢。

看門狗和紅鎖和延遲重啟其實都是redis的分布式鎖的概念磕昼,真正用redis去實現(xiàn)是比較困難的,所以我們一般用reddission去實現(xiàn)节猿,人家已經(jīng)幫我們做好了票从,只要學會用它的代碼就行漫雕。

這個紅鎖以及延遲重啟思路的加鎖算法在Redisson的紅鎖RedissonRedLock對象上面實現(xiàn)了,接下來我們來看是怎么用Reddsion實現(xiàn)的

該RedissonRedLock對象也可以用來將多個RLock對象關聯(lián)為一個紅鎖峰鄙,每個RLock對象實例可以來自于不同的Redisson實例浸间。

紅鎖之有看門狗手動解鎖方式:

Config config1 = new Config();

config1.useSingleServer().setAddress("redis://172.0.0.1:5378").setPassword("a123456").setDatabase(0);

RedissonClient redissonClient1 = Redisson.create(config1);

Config config2 = new Config();

config2.useSingleServer().setAddress("redis://172.0.0.1:5379").setPassword("a123456").setDatabase(0);

RedissonClient redissonClient2 = Redisson.create(config2);

Config config3 = new Config();

config3.useSingleServer().setAddress("redis://172.0.0.1:5380").setPassword("a123456").setDatabase(0);

RedissonClient redissonClient3 = Redisson.create(config3);

/**

* 獲取多個 RLock 對象

*/

RLock lock1 = redissonClient1.getLock(lockKey);

RLock lock2 = redissonClient2.getLock(lockKey);

RLock lock3 = redissonClient3.getLock(lockKey);

/**

* 根據(jù)多個 RLock 對象構建 RedissonRedLock (最核心的差別就在這里)

*/

RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);

try {

????//有看門狗,業(yè)務沒執(zhí)行完可以不停的延長鎖的過期時間

? ? boolean res = redLock.lock();

? ? if (res) {

? ? ? ? //成功獲得鎖吟榴,在這里處理業(yè)務

? ? }

} catch (Exception e) {

? ? throw new RuntimeException("aquire lock fail");

}finally{

? ? //無論如何, 最后都要解鎖

? ? redLock.unlock();

}


大家都知道魁蒜,如果負責儲存某些分布式鎖的某些Redis節(jié)點宕機以后,而且這些鎖正好處于鎖住的狀態(tài)時吩翻,這些鎖會出現(xiàn)鎖死的狀態(tài)兜看。為了避免這種情況的發(fā)生,Redisson內部提供了一個監(jiān)控鎖的看門狗狭瞎,它的作用是在Redisson實例被關閉前铣减,不斷的延長鎖的有效期。默認情況下脚作,看門狗的檢查鎖的超時時間是30秒鐘葫哗,也可以通過修改Config.lockWatchdogTimeout來另行指定。

另外Redisson還通過加鎖的方法提供了leaseTime的參數(shù)來指定加鎖的時間球涛。超過這個時間后鎖便自動解開了劣针。

//給lock1,lock2亿扁,lock3加鎖

RedissonRedLock lock=newRedissonRedLock(lock1, lock2, lock3);


紅鎖之無看門狗自動解鎖方式1:

//如果沒有手動解開的話捺典,10秒鐘后將會自動解開

lock.lock(10,TimeUnit.SECONDS);

...

lock.unlock();


紅鎖之無看門狗自動解鎖方式2:

//為加鎖等待100秒時間,并在加鎖成功10秒鐘后自動解開

booleanres=lock.tryLock(100,10,TimeUnit.SECONDS);

...?

lock.unlock();?

下面代碼采用的是紅鎖之無看門狗自動解鎖方式2

Config config1 = new Config();

config1.useSingleServer().setAddress("redis://172.0.0.1:5378").setPassword("a123456").setDatabase(0);

RedissonClient redissonClient1 = Redisson.create(config1);

Config config2 = new Config();

config2.useSingleServer().setAddress("redis://172.0.0.1:5379").setPassword("a123456").setDatabase(0);

RedissonClient redissonClient2 = Redisson.create(config2);

Config config3 = new Config();

config3.useSingleServer().setAddress("redis://172.0.0.1:5380").setPassword("a123456").setDatabase(0);

RedissonClient redissonClient3 = Redisson.create(config3);

/**

* 獲取多個 RLock 對象

*/

RLock lock1 = redissonClient1.getLock(lockKey);

RLock lock2 = redissonClient2.getLock(lockKey);

RLock lock3 = redissonClient3.getLock(lockKey);

/**

* 根據(jù)多個 RLock 對象構建 RedissonRedLock (最核心的差別就在這里)

*/

RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);

try {

? ? /**

? ? * 嘗試獲取鎖

? ? * waitTimeout 嘗試獲取鎖的最大等待時間从祝,超過這個值襟己,則認為獲取鎖失敗

? ? * leaseTime? 鎖的持有時間,超過這個時間鎖會自動失效(值應設置為大于業(yè)務處理的時間,確保在鎖有效期內業(yè)務能處理完)

? ? */

? ? boolean res = redLock.tryLock((long)waitTimeout, (long)leaseTime, TimeUnit.SECONDS);

? ? if (res) {

? ? ? ? //成功獲得鎖牍陌,在這里處理業(yè)務

? ? }

} catch (Exception e) {

? ? throw new RuntimeException("aquire lock fail");

}finally{

? ? //無論如何, 最后都要解鎖

? ? redLock.unlock();

}


Reddsion的紅鎖有一個宕機細節(jié)

如果采用紅鎖之有看門狗手動解鎖方式擎浴,按照上面的“紅鎖之redis持久化失敗后重啟場景”联逻,有3個節(jié)點ABC闽铐,其實我們已經(jīng)解決了延遲重啟問題比搭,但是這時候匣砖,只有AC節(jié)點在正常運行沾鳄,B節(jié)點還未重啟的情況:

客戶端2要獲取一樣的鎖带膀,是獲取不了的箱靴,因為AB節(jié)點獲取失敗淳地,C節(jié)點就算獲取成功捡偏,也是只有1個獲取成功唤冈,所以會獲取不了一樣的鎖,這點是正常的银伟。

客戶端2要獲取不一樣的鎖你虹,AC可以獲取得到凉当,還是有辦法的,這點是正常的售葡。

這但是如果這時候C節(jié)點也掛了看杭,剩下一個A節(jié)點有鎖,

這時候想要獲取一樣的鎖挟伙,不成功楼雹,這是正常的,等到這個鎖解鎖了尖阔,其他節(jié)點就可以重啟了贮缅!

這時候想要獲取不一樣的鎖,不成功介却,因為只剩下1個正常的節(jié)點谴供!


比如5個節(jié)點的話,ABCDE齿坷,ABC加鎖成功,DE加鎖失敗桂肌,這時候B又掛了,剩下AC有鎖而已永淌,DE沒有鎖崎场。

客戶端2要獲取一樣的鎖,是獲取不了的遂蛀,因為ABC節(jié)點獲取失敗谭跨,DC節(jié)點就算獲取成功,也是只有2個獲取成功李滴,所以會獲取不了一樣的鎖螃宙,這點是正常的。

客戶端2要獲取不一樣的鎖所坯,ACDE可以獲取得到谆扎,還是有辦法的,這點是正常的包竹。

但是如果這時候C節(jié)點也掛了燕酷,剩下一個A節(jié)點有鎖籍凝,DE沒鎖周瞎,

這時候想要獲取一樣的鎖,不成功饵蒂,這是正常的声诸,等到這個鎖解鎖了,其他節(jié)點就可以重啟了退盯!

這時候想要獲取不一樣的鎖彼乌,可能會成功泻肯,因為有3個正常的節(jié)點!

比如6個節(jié)點的話慰照,ABCDEF灶挟,ABCD加鎖成功,EF加鎖失敗,這時候B又掛了毒租,剩下ACD有鎖而已稚铣,EF沒有鎖。

客戶端2要獲取一樣的鎖墅垮,是獲取不了的惕医,因為ABCD節(jié)點獲取失敗,EF節(jié)點就算獲取成功算色,也是只有2個獲取成功抬伺,所以會獲取不了一樣的鎖,這點是正常的灾梦。

客戶端2要獲取不一樣的鎖峡钓,ACDEF可以獲取得到,還是有辦法的若河,這點是正常的椒楣。

但是如果這時候C節(jié)點也掛了,剩下一個AD節(jié)點有鎖牡肉,EF沒鎖捧灰,

這時候想要獲取一樣的鎖,不成功统锤,這是正常的毛俏,等到這個鎖解鎖了,其他節(jié)點就可以重啟了饲窿!

這時候想要獲取不一樣的鎖煌寇,可能會成功,因為有4個正常的節(jié)點逾雄!

但是如果這時候CD節(jié)點都掛了阀溶,剩下一個A節(jié)點有鎖,EF沒鎖鸦泳,

這時候想要獲取一樣的鎖银锻,不成功,這是正常的做鹰,等到這個鎖解鎖了击纬,其他節(jié)點就可以重啟了!

這時候想要獲取不一樣的鎖钾麸,不會成功更振,因為只有3個正常的節(jié)點炕桨!

總結:如果大多數(shù)節(jié)點,進入了等待肯腕。就會導致系統(tǒng)的不可用献宫,因為系統(tǒng)在業(yè)務執(zhí)行完程序手動解鎖的時間內(或者TTL時間內)任何鎖(一樣的鎖和不一樣的鎖)都將無法加鎖成功,其實等待 執(zhí)行完手動解鎖或者TTL時間內实撒,其他節(jié)點自動重啟就沒事了遵蚜,所以沒有太大的問題!

Reddsion的紅鎖有一個釋放鎖細節(jié)

釋放鎖的時候是要向所有節(jié)點發(fā)起釋放鎖的操作的奈惑。這樣做的目的是為了解決有可能在加鎖階段吭净,這個節(jié)點收到加鎖請求了,也set成功了肴甸,但是由于返回給客戶端的響應包丟了寂殉,導致客戶端以為沒有加鎖成功。所有原在,釋放鎖的時候要向所有節(jié)點發(fā)起釋放鎖的操作友扰。


紅鎖算法最重要的問題來了!J痢村怪!也算要注意紅鎖算法的bug所在吧!


如果采用“看門狗失效紅鎖自動解鎖方式1和2”浮庐,

問題1:

1甚负、如果A客戶端獲取到了鎖,然后它的A線程GC暫停了审残,經(jīng)過一段時間梭域,鎖自動解鎖了

2、此時B客戶端來獲取一樣的鎖搅轿,這時候A線程GC恢復了病涨,此時A線程繼續(xù)執(zhí)行他的業(yè)務代碼,B客戶端的B線程也同時執(zhí)行它的業(yè)務代碼

3璧坟、這時候會導致同一時間內兩個客戶端有同一把鎖既穆,沒有滿足同一時間內一個鎖只能有一個客戶端持有的原則,所以會導致系統(tǒng)出現(xiàn)臟數(shù)據(jù)

從客戶端的角度來看雀鹃,就是這玩意不靠譜啊幻工,你給我一把鎖,我還沒用呢褐澎,你就過期了会钝,但是我還以為我能用,哪還知道其他人也有跟我一樣的鎖工三,這下好了這個鎖同時被兩個人用迁酸,這是不靠譜的做法!

(我覺得無法反駁)

問題2:

1俭正、客戶端 1 向 Redis 節(jié)點 A, B, C, D, E 發(fā)起鎖請求奸鬓。

2、各個 Redis 節(jié)點已經(jīng)把請求結果返回給了客戶端 1掸读,但客戶端 1 在收到請求結果之前進入了長時間的 GC 階段串远。

3、長時間的 GC儿惫,導致在所有的 Redis 節(jié)點上澡罚,鎖過期了。

4肾请、客戶端 2 在 A, B, C, D, E 上申請并獲取到了鎖留搔。

5、客戶端 1 從 GC 階段中恢復铛铁,收到了前面第 2 步來自各個 Redis 節(jié)點的請求結果隔显。客戶端 1 認為自己成功獲取到了鎖饵逐。

6括眠、客戶端 1 和客戶端 2 現(xiàn)在都認為自己持有了鎖。

我覺得可以反駁倍权,這種情況其實對于 Redlock 是沒有影響的掷豺,因為在第 5 步,客戶端 1 從 GC 階段中恢復過來以后薄声,在 Redlock 算法中萌业,如果取到了鎖,key 的真正有效時間等于有效時間減去獲取鎖所使用的時間奸柬,客戶端1通過這個檢查發(fā)現(xiàn)鎖已經(jīng)過期了生年,不會再認為自己成功獲取到鎖了)

對于問題1問題2,redis創(chuàng)始人有反駁廓奕,我只看得懂能夠反駁問題2抱婉,反駁不了問題1。

redis創(chuàng)始人的反駁:“Redlock 沒有提供類似于fencing機制那樣的單調遞增的令牌桌粉,但是也有一個隨機串蒸绩,把這個隨機串當做token,也可以達到同樣的效果啊铃肯。當需要和共享資源交互的時候患亿,我們檢查一下這個token是否發(fā)生了變化,如果沒有再執(zhí)行“獲取-修改-寫回”的操作”

如果有人能理解這段話能反駁問題1的話,麻煩在文章下面評論一下步藕。讓我好好理解一下惦界,謝謝!(后面我跟程序圈的why神聊過咙冗,redis的創(chuàng)始人是沒法反駁問題1的沾歪,只能反駁問題2)

問題3:時鐘發(fā)生跳躍(關于馬丁哥的疑問)

1:客戶端 1 從 Redis 節(jié)點 A, B, C 成功獲取了鎖。由于網(wǎng)絡問題雾消,無法訪問 D 和 E灾搏。

2:節(jié)點 C 上的時鐘被人修改了或者從NTP服務收到了一個大的時鐘更新事件,導致它上面維護的鎖過期了立润。

3:客戶端 2 從 Redis 節(jié)點 C, D, E 成功獲取了同一個資源的鎖狂窑。由于網(wǎng)絡問題,無法訪問 A 和 B桑腮。

4:現(xiàn)在泉哈,客戶端 1 和客戶端 2 都認為自己持有了鎖。

這樣的場景是可能出現(xiàn)的到旦,因為 Redlock 嚴重依賴系統(tǒng)時鐘旨巷,所以一旦系統(tǒng)的時間變得不準確了,那么該算法的安全性也就得不到保障了添忘。

延遲啟動方案采呐,延遲啟動還不是依賴于合理準確的時間度量,如果時鐘不準確搁骑,該算法的安全性也一樣沒有保障斧吐。

總結:

1:運維人員手動修改了系統(tǒng)時鐘。

2:從NTP服務收到了一個大的時鐘更新事件仲器。

redis創(chuàng)始人進行了回擊:

第一點這個運維人員手動修改時鐘煤率,屬于人為因素,這個我也沒辦法啊乏冀,人家就是要搞你蝶糯,怎么辦?加強管理辆沦,不要這樣做昼捍。

第二點從NTP服務收到一個大的時鐘更新,對于這個問題肢扯,需要通過運維來保證妒茬,通過正確配置NTP。需要將大的時間更新到服務器的時候蔚晨,應當采取少量多次的方式乍钻。多次修改,每次更新時間盡量小。

關于這個地方的爭論银择,就看你是信馬丁哥的時間一定會跳躍多糠,還是redis創(chuàng)始人的時間跳躍我們也是可以處理的。

redis創(chuàng)始人的想法:他是同意大的系統(tǒng)時鐘跳躍會造成 Redlock 失效的欢摄。在這一點上熬丧,他與馬丁哥的觀點的不同在于笋粟,他認為在實際系統(tǒng)中是可以通過好的運維方式避免大的時鐘跳躍的怀挠。


對于問題123,其實Redission使用“看門狗生效紅鎖方式”就能解決害捕,A線程GC的時候绿淋,看門狗一直續(xù)鎖的過期時間,使這個鎖不會過期尝盼,而且確保系統(tǒng)時鐘不要出現(xiàn)大的時間跳躍(比如人為修改系統(tǒng)時鐘或者收到大的時鐘更新時間的話吞滞,就正確配置NTP,其實這里的時間跳躍不要超過20秒就好盾沫,因為每個10秒裁赠,看門狗會去自動更新過期時間為30秒,不過這個30秒也是可以設置的赴精,可以更改為60甚至更多佩捞,改為60的話,就是60/3=20蕾哟,每隔20秒去重置過期時間)一忱,基本可以保證redission的分布式鎖的零失誤率,系統(tǒng)不出現(xiàn)臟數(shù)據(jù)L啡贰A庇!

還有就是各個redis應用不要大多數(shù)宕機逐哈,這會導致系統(tǒng)一時間沒法加鎖芬迄,但是不會導致系統(tǒng)出現(xiàn)臟數(shù)據(jù)。

但是其實上面這個方式昂秃,還有一個bug的存在禀梳,非常極端的條件。

問題4:看門狗gc暫停場景

1械蹋、如果A客戶端獲取到了鎖出皇,然后它的A線程GC暫停了,它的看門狗線程也GC暫停哗戈,經(jīng)過一段時間郊艘,鎖自動解鎖了

2、此時B客戶端來獲取一樣的鎖,這時候A線程GC恢復了纱注,它的看門狗線程GC沒有恢復(其實此時恢復與不恢復都沒關系吧畏浆,比較鎖都過期了,而且別的客戶端也一樣拿到一樣的鎖了)狞贱,此時A線程繼續(xù)執(zhí)行他的業(yè)務代碼刻获,B客戶端的B線程也同時執(zhí)行它的業(yè)務代碼

3、這時候會導致同一時間內兩個客戶端有同一把鎖瞎嬉,沒有滿足同一時間內一個鎖只能有一個客戶端持有的原則蝎毡,所以會導致系統(tǒng)出現(xiàn)臟數(shù)據(jù)

所以在使用“看門狗生效紅鎖方式”的這種方式,保證時鐘不會出問題氧枣,如果出現(xiàn)“看門狗gc暫豌灞”場景,reddission的紅鎖算法也是不靠譜的便监,在極端條件下還是會出現(xiàn)bug的扎谎!

或者可以考慮,只使用redis單機模式的reddission分布式鎖就好了烧董,簡單而且效率高毁靶。用 Redlock 太重。

但是單機模式的話逊移,有一個場景:

A客戶端拿到鎖预吆,然后 此時A客戶端的線程GC暫停了,看門狗GC不GC沒有關系螟左,此時單機redis也宕機了啡浊,等到鎖過期了,我們重啟了redis胶背,此時B客戶端的拿到一樣的鎖巷嚣,此時A客戶端的線程GC恢復了,繼續(xù)執(zhí)行他的業(yè)務钳吟,此時同一個時間有兩個客戶端拿到同一把鎖廷粒!

解決方法:

redis宕機的時候,別那么快去重啟redis红且,防止有線程在GC暫停中坝茎,應該等線程超時之后再去重啟,或者你的方法里面有事務暇番,等到事務超時再去重啟redis應用嗤放!


總結:

redis暫時也沒實現(xiàn)這種令牌思維,所以采用redission分布式鎖壁酬,用哪種都可以次酌,

紅鎖:只能更好的避免出現(xiàn)臟數(shù)據(jù)的幾率恨课,而且不會出現(xiàn)單點問題。

單機redis:會出現(xiàn)單點故障岳服,但是小心點處理重啟時間剂公,也不會出現(xiàn)臟數(shù)據(jù)。

主從哨兵模式吊宋,cluster模式:宕機的話比較容易出現(xiàn)臟數(shù)據(jù)纲辽。

其實用redis做分布式鎖,在極端環(huán)境下璃搜,是無法完全保證不出現(xiàn)臟數(shù)據(jù)拖吼。

對付臟數(shù)據(jù)的方法:

其實多下了訂單的話超出了庫存,可以在業(yè)務層面去解決腺劣,比如多預留一些商品庫存绿贞,把多下的訂單商品也進行發(fā)貨因块,

庫存表如果有庫存總數(shù)量橘原,維護的時候就要把庫存的總數(shù)量進行修改,剩余庫存總數(shù)量就不用更改涡上,避免臟數(shù)據(jù)趾断。

,或者將超出庫存多下的訂單吩愧,回滾芋酌,設置為無效,修復臟數(shù)據(jù)雁佳,其中的數(shù)據(jù)修改細節(jié)脐帝,每個系統(tǒng)的都不一樣,有些系統(tǒng)還可能涉及到積分還有其他會員等級之類的變動糖权,所以修復臟數(shù)據(jù)堵腹,應該依照自己的系統(tǒng)情況來做。


redis分布式鎖在這個地方有一個缺點沒做好星澳,也是之前馬丁哥所說的例子:


也就是說 GC 恢復了疚顷,但是你其實已經(jīng)鎖已經(jīng)失效了,但是你還覺得自己是有效的禁偎,然后業(yè)務繼續(xù)執(zhí)行腿堤,殊不知道還有人也拿到這把一樣的鎖,跟你一起在執(zhí)行如暖。

要實現(xiàn)這一目標笆檀,可以采用fencing令牌思維。

1盒至、客戶端 1 獲得一個具有超時時間的鎖的同時得到了令牌號 33酗洒,但隨后陷入了一個長時間的暫停直到鎖到期浸船。

2、這時客戶端2已經(jīng)獲得了鎖和令牌號 34 寝蹈,然后發(fā)送寫請求(以及令牌號 34 )到存儲服務李命。?

3、接下來客戶端 1 恢復過來箫老,并以令牌號 33 來嘗試寫入封字,存儲服務器由于記錄了最近已經(jīng)完成了更高令牌號(34 ),因此拒絕令牌號 33 的寫請求耍鬓。

這種版本號的機制阔籽,讓我不禁想起了 Zookeeper。當使用 ZK 做鎖服務時牲蜀,可以用事務標識 zxid 或節(jié)點版本 cversion 來充當 fencing 令牌笆制,這兩個都可以滿足單調遞增的要求。

長發(fā)哥在書中也說到了:在服務端檢查令牌可能看起來有點復雜涣达,但是這其實是推薦的正確的做法:系統(tǒng)服務不能假定所有的客戶端都表現(xiàn)的符合預期在辆。從安全角度講,服務端必須防范這種來自客戶端的濫用度苔。

對于redis的分布式鎖而言匆篓,優(yōu)缺點:

1、它獲取鎖的方式簡單粗暴寇窑,獲取不到鎖直接不斷嘗試獲取鎖鸦概,比較消耗性能。

2甩骏、另外來說的話窗市,redis的設計定位決定了它的數(shù)據(jù)并不是強一致性的,在某些極端情況下饮笛,可能會出現(xiàn)問題咨察。鎖的模型不夠健壯

3、即便使用redlock算法來實現(xiàn)缎浇,在某些復雜場景下扎拣,也無法保證其實現(xiàn)100%沒有問題,關于redlock的討論可以看How to do distributed locking

4素跺、但是另一方面使用redis實現(xiàn)分布式鎖在很多企業(yè)中非常常見二蓝,而且大部分情況下都不會遇到所謂的“極端復雜場景”

所以使用redis作為分布式鎖也不失為一種好的方案,最重要的一點是redis的性能很高指厌,可以支撐高并發(fā)的獲取刊愚、釋放鎖操作。


另外還有一種分布式鎖是zookeeper分布式鎖

對于zookeeper的分布式鎖而言踩验,優(yōu)缺點:

1鸥诽、zookeeper天生設計定位就是分布式協(xié)調商玫,強一致性。鎖的模型健壯牡借、簡單易用拳昌、適合做分布式鎖。

2钠龙、如果獲取不到鎖炬藤,只需要添加一個監(jiān)聽器就可以了,不用一直輪詢碴里,性能消耗較小沈矿。

3、如果有較多的客戶端頻繁的申請加鎖咬腋、釋放鎖羹膳,對于zk集群的壓力會比較大。


建議:

如果注重數(shù)據(jù)的準確性根竿,數(shù)據(jù)不允許有一點錯誤:可以用zookeeper分布式鎖解決陵像,可以保證絕對不出問題,但是性能比redis差犀填。

如果能容忍redis宕機問題導致reddsion分布式鎖出現(xiàn)的鎖失效問題蠢壹,從而可能會導致數(shù)據(jù)出現(xiàn)問題,則可以還是使用redis九巡。

如果使用“看門狗生效紅鎖方式”,則只需要確保系統(tǒng)時鐘不要出現(xiàn)大的跳躍就可以保證零失誤蹂季,極端條件下出現(xiàn)臟數(shù)據(jù)的話冕广,就按照自己的系統(tǒng)的情況去處理臟數(shù)據(jù)吧!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末偿洁,一起剝皮案震驚了整個濱河市撒汉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌涕滋,老刑警劉巖睬辐,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異宾肺,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門殿较,熙熙樓的掌柜王于貴愁眉苦臉地迎上來泽裳,“玉大人,你說我怎么就攤上這事增拥∽那桑” “怎么了寻歧?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長秩仆。 經(jīng)常有香客問我码泛,道長,這世上最難降的妖魔是什么澄耍? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任弟晚,我火速辦了婚禮,結果婚禮上逾苫,老公的妹妹穿的比我還像新娘卿城。我一直安慰自己,他們只是感情好铅搓,可當我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布瑟押。 她就那樣靜靜地躺著,像睡著了一般星掰。 火紅的嫁衣襯著肌膚如雪多望。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天氢烘,我揣著相機與錄音怀偷,去河邊找鬼。 笑死播玖,一個胖子當著我的面吹牛椎工,可吹牛的內容都是我干的。 我是一名探鬼主播蜀踏,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼维蒙,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了果覆?” 一聲冷哼從身側響起颅痊,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎局待,沒想到半個月后斑响,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡钳榨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年舰罚,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片重绷。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡沸停,死狀恐怖,靈堂內的尸體忽然破棺而出昭卓,到底是詐尸還是另有隱情愤钾,我是刑警寧澤瘟滨,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站能颁,受9級特大地震影響杂瘸,放射性物質發(fā)生泄漏。R本人自食惡果不足惜伙菊,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一败玉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧镜硕,春花似錦运翼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至财剖,卻和暖如春悠夯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背躺坟。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工沦补, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人咪橙。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓夕膀,卻偏偏與公主長得像,于是被迫代替她去往敵國和親匣摘。 傳聞我的和親對象是個殘疾皇子店诗,可洞房花燭夜當晚...
    茶點故事閱讀 45,077評論 2 355