自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

用Redis構(gòu)建一把高性能的鎖

存儲(chǔ) 存儲(chǔ)軟件 Redis
學(xué)過(guò)JAVA多線(xiàn)程的朋友都知道,為了防止多個(gè)線(xiàn)程同時(shí)執(zhí)行同一段代碼,可以用synchronized關(guān)鍵字或JAVA API中ReentrantLock類(lèi)來(lái)控制。

[[267910]]

背景:

筆者所在的公司,上周末經(jīng)歷了一場(chǎng)大促活動(dòng)后,系統(tǒng)暴露出這樣一個(gè)問(wèn)題:分布式鎖使用的zk鎖,由于當(dāng)天大促用戶(hù)量比較多,系統(tǒng)瘋狂的加鎖釋放鎖,***zk承受不住這么大的壓力宕機(jī)。由于馬上就要618,為了避免再次發(fā)生這樣的事情,公司決定把所有系統(tǒng)的zk鎖都替換為高性能的Redis鎖。

在這里簡(jiǎn)單的提一下,zk鎖性能比redis低的原因:zk中的角色分為leader,flower,每次寫(xiě)請(qǐng)求只能請(qǐng)求leader,leader會(huì)把寫(xiě)請(qǐng)求廣播到所有flower,如果flower都成功才會(huì)提交給leader,其實(shí)這里相當(dāng)于一個(gè)2PC的過(guò)程。在加鎖的時(shí)候是一個(gè)寫(xiě)請(qǐng)求,當(dāng)寫(xiě)請(qǐng)求很多時(shí),zk會(huì)有很大的壓力,***導(dǎo)致服務(wù)器響應(yīng)很慢。

正題:

什么情況下需要加鎖?

當(dāng)多個(gè)線(xiàn)程、用戶(hù)同時(shí)競(jìng)爭(zhēng)同一個(gè)資源時(shí),需要加鎖。比如,下訂單減庫(kù)存,搶票,選課,搶紅包等。如果在此處沒(méi)有鎖的控制,會(huì)導(dǎo)致很?chē)?yán)重的問(wèn)題,下訂單減庫(kù)存的時(shí)候不加鎖,會(huì)導(dǎo)致商品超賣(mài);搶票的時(shí)候不加鎖,會(huì)導(dǎo)致兩個(gè)人搶到同一個(gè)位置;選課的時(shí)候沒(méi)有鎖的控制,導(dǎo)致選課成功的人數(shù)大于教室的座位數(shù);搶紅包時(shí)沒(méi)有鎖的控制,搶到紅包的金額大于紅包的實(shí)際金額。

什么是分布式鎖?

學(xué)過(guò)JAVA多線(xiàn)程的朋友都知道,為了防止多個(gè)線(xiàn)程同時(shí)執(zhí)行同一段代碼,可以用synchronized關(guān)鍵字或JAVA API中ReentrantLock類(lèi)來(lái)控制。

但是目前幾乎任何一個(gè)系統(tǒng)都往往部署多臺(tái)機(jī)器的,單機(jī)部署的應(yīng)用很少,synchronized和ReentrantLock發(fā)揮不出任何作用,此時(shí)就需要一把全局的鎖,來(lái)代替JAVA中的synchronized和ReentrantLock。

當(dāng)Thread1線(xiàn)程獲取到鎖,執(zhí)行鎖中的代碼,其他線(xiàn)程或其他機(jī)器再次請(qǐng)求該鎖,發(fā)現(xiàn)鎖被Thread1占用,加鎖失敗。當(dāng)Thread1釋放鎖,其他線(xiàn)程則可以獲取到鎖并執(zhí)行相應(yīng)的操作。

我們可以用Jedis中是setnx命令來(lái)構(gòu)建這把鎖,首先,我列舉一些錯(cuò)誤的構(gòu)建鎖的方式:

錯(cuò)誤例子1

  1. Long lock= jedis.setnx(key,value); 
  2. if(lock>0){ 
  3. //執(zhí)行業(yè)務(wù)邏輯 

通過(guò)setnx命令創(chuàng)建一個(gè)key、value,如果key不存在,則加鎖成功。這樣做有什么問(wèn)題呢?如果執(zhí)行加鎖操作成功,在釋放鎖的時(shí)候,系統(tǒng)宕機(jī),導(dǎo)致這個(gè)key永遠(yuǎn)不會(huì)被del掉,也就是說(shuō)其他線(xiàn)程一直獲取不到鎖,

導(dǎo)致死鎖發(fā)生。為了避免這種情況,請(qǐng)看下面的代碼

錯(cuò)誤例子2

  1. Long lock= jedis.setnx(key,value); 
  2. if(lock>0){ 
  3.    jedis.expire(key,expireTime); 

和上面的例子類(lèi)似,唯一不同的是這里多了一步設(shè)置key過(guò)期時(shí)間的操作。如果在del的時(shí)候系統(tǒng)宕機(jī),等過(guò)期時(shí)間一到,Redis會(huì)刪除這個(gè)key。

其他線(xiàn)程可以再次獲取鎖。這樣就可以萬(wàn)無(wú)一失了嗎?這里有一個(gè)問(wèn)題,如果在***步setnx成功后,突然網(wǎng)絡(luò)閃斷,expire命令執(zhí)行失敗,同樣也有死鎖的風(fēng)險(xiǎn)。這兩步并不具備原子性,不保證全部成功或全部失敗。

正確的構(gòu)建方式

  1. public static boolean getDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) { 
  2.     String result = jedis.set(lockKey, requestId, "NX""EX", expireTime); 
  3.     if (LOCK_SUCCESS.equals(result)) { 
  4. return true
  5.      } 
  6. return false

參數(shù)解釋?zhuān)?/p>

key:鍵

value:值

nx:如果當(dāng)前key存在,則set失敗,否則成功

ex:設(shè)置key的過(guò)期時(shí)間

expireTime:key的過(guò)期時(shí)間,時(shí)間到了,Redis會(huì)自動(dòng)刪除key和value。

這個(gè)命令,將上面的錯(cuò)誤例子2中的兩個(gè)操作合為一個(gè)原子操作,保證了同時(shí)成功或同時(shí)失敗。

解鎖方式:

錯(cuò)誤例子1:

  1. jedis.del(key); 

執(zhí)行這個(gè)操作的線(xiàn)程,不去判斷鎖的擁有者就刪除鎖。

還記的set命令可以設(shè)置value嗎?在獲取鎖的操作時(shí),主要是判斷key是否存在,那么value有什么用呢??如果在刪除鎖的時(shí)候,不去判斷當(dāng)前鎖的擁有者,任何線(xiàn)程都可以釋放鎖。這個(gè)時(shí)候,value值就起到作用了。

錯(cuò)誤例子2:

  1. if(value==jedis.get(key)){ 
  2.     jedis.del(key); 

我們?cè)诩渔i的時(shí)候,可以將value設(shè)置成唯一標(biāo)識(shí)當(dāng)前線(xiàn)程的一個(gè)值,這個(gè)值可以是一個(gè)UUID,當(dāng)釋放鎖的時(shí)間,判斷value是否和set時(shí)的值相同,如果相同,則說(shuō)明加鎖和釋放鎖是同一個(gè)線(xiàn)程,允許釋放。否則釋放鎖失敗。

這樣就可以絕對(duì)安全了嗎??答案當(dāng)然是否定的。這步操作,同樣不具備原子性。如果ThreadA在執(zhí)行value==jedis.get(key)返回true后的瞬間,del命令還沒(méi)來(lái)的及執(zhí)行,key過(guò)期了,而此時(shí)ThreadB獲取到鎖,之后ThreadA執(zhí)行del命令,把ThreadB的鎖釋放掉了。

所以要保證兩部操作的原子性,我們不得不利用簡(jiǎn)單的Lua腳本。

正確的解鎖姿勢(shì):

  1. public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) { 
  2. String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"
  3.     Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId)); 
  4.     if (RELEASE_SUCCESS.equals(result)) { 
  5.         return true
  6.     } 
  7.     return false

Redis在2.6后內(nèi)部?jī)?nèi)嵌Lua腳本解釋器,所以我們可以通過(guò)簡(jiǎn)單的Lua腳本來(lái)保證上述操作的原子性。代碼中的Lua腳本的的意思是:我們把LockKey賦值給KEYS[1],把RequestId賦值給ARGV[1],如果key中的值等于RequestId,返回true否則返回false。這樣就保證了釋放鎖操作時(shí)原子的,并且當(dāng)前客戶(hù)端只會(huì)釋放當(dāng)前客戶(hù)端的鎖。

責(zé)任編輯:武曉燕 來(lái)源: OutOfMemoryErro
相關(guān)推薦

2019-10-30 05:51:07

物聯(lián)網(wǎng)設(shè)備物聯(lián)網(wǎng)安全物聯(lián)網(wǎng)

2018-03-23 08:26:44

Hadoop集群SQL

2023-09-22 11:48:37

2022-06-30 08:04:16

Redis分布式鎖Redisson

2020-03-02 17:04:47

戴爾

2021-06-03 08:01:12

JVM性能優(yōu)化

2012-02-07 09:33:14

2016-07-15 10:37:37

云性能云計(jì)算

2021-03-10 09:52:38

開(kāi)發(fā)技能架構(gòu)

2020-06-05 07:20:41

測(cè)試自動(dòng)化環(huán)境

2011-12-15 13:28:57

2014-07-04 10:41:19

redis數(shù)據(jù)庫(kù)緩存

2022-12-09 08:40:56

高性能內(nèi)存隊(duì)列

2011-10-21 14:20:59

高性能計(jì)算HPC虛擬化

2011-10-25 13:13:35

HPC高性能計(jì)算Platform

2021-08-26 06:00:29

密碼非對(duì)稱(chēng)加密數(shù)據(jù)安全

2025-03-04 08:00:00

機(jī)器學(xué)習(xí)Rust開(kāi)發(fā)

2009-06-03 14:24:12

ibmdwWebSphere

2009-10-29 09:11:50

Juniper高性能網(wǎng)絡(luò)

2023-10-26 08:35:53

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)