redis分布式鎖,你真的用對了嗎

2021-09-29 23:54:38 字數 3826 閱讀 8499

隨著業務場景越來越複雜,使用的架構也就越來越複雜,分布式、高併發已經是業務要求的常態。說到分布式,不得不提的就是分布式鎖和分布式事物。今天我們就來談談redis實現的分布式鎖的問題!

實現要求:

1.互斥性,在同一時刻,只能有乙個客戶端持有鎖

2.防止死鎖,如果持有鎖的客戶端崩潰而且沒有主動釋放鎖,怎樣保證鎖可以正常釋放,使得客戶端可以正常加鎖

3.加鎖和釋放鎖必須是同乙個客戶端。

4.容錯性,只有redis還有節點存活,就可以正常的加鎖解鎖操作。

錯誤使用方式一:

保證互斥和防止死鎖,首先想到的使用redis的setnx命令保證互斥,為了防止死鎖,需要設定乙個超時時間。

public object getandset

(string key, object value,

long timeout)

在多執行緒併發環境下,任何非原子性的操作,都可能導致問題。在這段**中,如果設定過期時間,redis例項崩潰,就無法設定過期時間。如果客戶端沒有正確釋放鎖,那麼該鎖永遠不會過期,就永遠不會被釋放。

錯誤方式二

比較容易想到的就是設定值和超時時間為原子操作不就可以了嗎。那麼使用方法就是這樣了

public

static

boolean

wronglock

(jedis jedis, string key,

int expiretime)

string value = jedis.

get(key)

;//如果當前鎖存在,且鎖已過期

if(value != null && numberutils.

tolong

(value)

< system.

currenttimemillis()

)}// 其他情況,加鎖失敗

return

true

;}

這段**,乍一眼看沒啥問題,你仔細看就會發現:

1.value 設定為過期時間,就要要求各個客戶端嚴格的時鐘同步,這需要使用到同步時鐘。即使有同步時鐘,分布式的伺服器一般也會有少許誤差,這不重要

2. 鎖過期時,使用jedis.getset雖然可以保證乙個執行緒設定成功,但不能保證加鎖和解鎖為同乙個客戶端,因為沒有標誌時那個客戶端設定的

解鎖錯誤方式一:

直接刪除key

public

static

void

wrongreleaselock

(jedis jedis, string key)

簡單粗暴,但這樣做的話,不是自己的鎖也會被刪除掉。不夠嚴謹

解鎖錯誤方式二:

判斷自己是不是鎖的持有者,只有持有者才可以釋放鎖

public

static

void

wrongreleaselock

(jedis jedis, string key, string uniqueid)

}

完美!

真的完美?

看起來很完美,但是如果你判斷的時候鎖是自己持有的,這時候超時自動釋放了,然後又被其他客戶端重新上鎖了,然後你刪除的不就是其他客戶端的鎖,一樣不就亂套了?

基於以上資訊探索,給出以下示例,僅供學習交流!

1.命令必須保證是互斥的

2. 設定的key必須要有過期時間

3. value使用唯一id,標誌每個客戶端。只有鎖的持有者才能釋放鎖。

加鎖直接使用set命令同時設定唯一id和過期時間;其中解鎖些微複雜些,加鎖後可以返回唯一id,標誌此鎖是該客戶端鎖擁有;釋放鎖時要先判斷是否是自己,只有自己才有刪除操作,**示例如下:

@component

@slf4j

public

class

redislockutil

object result = redistemplate.

opsforvalue()

.getandset

(key, value);if

(objects.

isnull

(result)

)// 設定過期時間,以防死鎖

redistemplate.

expire

(key, timeout, timeunit.milliseconds)

;// 開啟乙個守護程序,給當前鎖動態新增時間

thread thread =

newthread

(new

runnable()

}catch

(interruptedexception e)}}

}); thread.

setdaemon

(true);

// 守護程序

threadmap.

put(value, thread)

; thread.

setname

(key+

"-"+value)

; thread.

start()

;return value;}}

}catch

(exception e)

log.

info

("獲取鎖失敗");

throw

newruntimeexception

("獲取分布式鎖超時");

}public

boolean

unlock

(string key, object value)

--{}"

, key, value);if

(objects.

isnull

(key)

) defaultredisscript script =

newdefaultredisscript()

; script.

setresulttype

(list.

class);

script.

setscripttext

("if redis.call('get', keys[1]) == ar**[1] then return redis.call('del', keys[1]) else return 0 end");

object o = redistemplate.

execute

(script, collections.

singletonlist

(key)

, value);if

(objects.

nonnull

(o)&&

((arraylist)o)

.size()

!=0) log.

info

("釋放鎖{}"

, o)

;return

true;}

}

模擬呼叫**

("/hello"

)public object hello

(string hello)

", object)

;// 這裡是模擬業務處理場景

trycatch

(interruptedexception e)

}catch

(exception e)

finally

return object;

}

用redis實現分布式鎖

通常部署的服務都是在多台伺服器上,不會只有一台。那麼在分布式環境下,就會遇到共享資源的問題。比如乙個人只能有一條記錄,下次進來就只能修改,而不是再新增。如果只有一台伺服器,可以使用多執行緒下的單例模式來控制,但是分布式下,就不管用了。有三種方式,一是使用資料庫的樂觀鎖,二是redis的鎖,三是zoo...

用redis構建分布式鎖

從2.6.12版本開始,redis為set命令增加了一系列選項 如果有2個程序 可能位於不同機器 需要競爭某個資源,可以為這個資源加鎖,鎖放在redis裡面,這樣兩個程序都能訪問到,例如下面的命令 set resource name random value nx ex max lock time ...

redis分布式鎖

redis分布式鎖 直接上 我寫了四個redis分布式鎖的方法,大家可以提個意見 第一種方法 redis分布式鎖 param timeout public void lock long timeout thread.sleep 100 catch exception e override publi...