SpringBootRedisTemplate分布式鎖的項(xiàng)目實(shí)戰(zhàn)_第1頁
SpringBootRedisTemplate分布式鎖的項(xiàng)目實(shí)戰(zhàn)_第2頁
SpringBootRedisTemplate分布式鎖的項(xiàng)目實(shí)戰(zhàn)_第3頁
SpringBootRedisTemplate分布式鎖的項(xiàng)目實(shí)戰(zhàn)_第4頁
SpringBootRedisTemplate分布式鎖的項(xiàng)目實(shí)戰(zhàn)_第5頁
已閱讀5頁,還剩4頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)

文檔簡介

第SpringBootRedisTemplate分布式鎖的項(xiàng)目實(shí)戰(zhàn)目錄1.使用場景2.加鎖解決3.分布式鎖4.增加失效時間5.增加線程唯一值6.Lua腳本7.Lua是如何實(shí)現(xiàn)原子性的8.代碼演示9.總結(jié)

1.使用場景

想直接獲取加鎖和解鎖代碼,請直接到代碼處

在下單場景減庫存時我們一般會將庫存查詢出來,進(jìn)行庫存的扣除

@GetMapping(value="order")

publicRorder(){

intstock=RedisUtil.getObject("stock",Integer.class);

if(stock0){

RedisUtil.set("stock",--stock);

returnR.ok(stock);

上述的操作看起來很正常,但是其實(shí)是有問題的,試想一下當(dāng)我們有兩個線程同時訪問這個接口會發(fā)生什么

Thread-1查詢庫存結(jié)果為100

Thread-2也來查詢庫存,此時Thread-1還沒有執(zhí)行減少庫存操作,Thread-2查詢庫存的結(jié)果也是100

Thread-1Set庫存為99

Thread-2Set庫存為99

這樣就出問題了,明天扣了兩次庫存,但是庫存僅僅減了1次

使用Idea時,我們可以使在斷點(diǎn)處右鍵將Suspend調(diào)整為Thread,僅阻斷線程,并使用多個客戶端同時請求接口,即可復(fù)現(xiàn)上述過程

2.加鎖解決

synchronized我們可以用Java提供的synchronized關(guān)鍵字將方法分布式鎖,分布式鎖的實(shí)現(xiàn)方案有很多種,zookeeper,redis,db,這邊我們使用redis來實(shí)現(xiàn)以下分布式鎖

3.分布式鎖

上述兩個線程同時進(jìn)行的時候沒有正確扣除庫存正是因?yàn)椤静樵儙齑妗亢汀究鄢龓齑妗坎皇且粋€原子操作,我們增加一個鎖的機(jī)制,當(dāng)線程持有鎖的時候才允許進(jìn)行【查詢庫存】和【扣除庫存】,redis有一個sexNx命令允許當(dāng)指定的key不存在時才進(jìn)行set操作,在java中為RedisTemplate的setIfAbsent方法,這個方法保證了同時只能有一個線程set成功,set成功時就表明我們拿到了鎖,可以進(jìn)行原子操作了,當(dāng)我們執(zhí)行完原子操作時我們也需要將鎖釋放掉,在redis實(shí)現(xiàn)中也就是將key刪除,允許下一個線程set值,加鎖和釋放鎖的代碼如下

/**

*加鎖

*@paramkeyredis主鍵

*@paramvalue值

publicstaticbooleanlock(Stringkey,Stringvalue){

finalbooleanresult=Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(CacheConstant.LOCK_KEY+key,value));

if(result){

("[redisTemplateredis]設(shè)置鎖緩存緩存url:{}",key);

returnresult;

*解鎖

*@paramkeyredis主鍵

publicstaticbooleanunlock(Stringkey){

finalbooleanresult=Boolean.TRUE.equals(redisTemplate.delete(CacheConstant.LOCK_KEY+key));

if(result){

("[redisTemplateredis]釋放鎖緩存url:{}",key);

returnresult;

那么我們將代碼稍微修改一下,來利用鎖來完成接口的改進(jìn)

@GetMapping(value="order")

publicRorder(){

booleanlock;

intstock;

try{

lock=RedisUtil.lock("stock","");

if(!lock){

returnR.failed("服務(wù)繁忙,稍后再試");

stock=RedisUtil.getObject("stock",Integer.class);

if(stock0){

RedisUtil.set("stock",--stock);

}finally{

RedisUtil.unlock("stock");

returnR.ok(stock);

此時,我們再將斷點(diǎn)放在獲取庫存之后,并先用一個終端請求接口

然后,我們再從終端2發(fā)起請求,可以看到我們終端1沒有結(jié)束自己的原子操作時,終端2是無法進(jìn)行庫存的扣除的

4.增加失效時間

在上一步中,我們仿佛已經(jīng)完成了需求,同時進(jìn)行扣除庫存的只有一個線程,但是試想一下,當(dāng)線程獲取到鎖之后,服務(wù)突然宕機(jī)了,這時候就算及時重啟機(jī)器,那么鎖也一直得不到釋放,那么扣除庫存接口始終無法獲取到鎖,這肯定不是我們想要的效果,那么我們改進(jìn)一下我們加鎖的方法,增加一下失效時間,即使服務(wù)宕機(jī)了,我們重啟機(jī)器之后,鎖也能正常釋放掉不會影響一下個線程獲取到鎖

/**

*加鎖

*@paramkeyredis主鍵

*@paramvalue值

*@paramtime過期時間

publicstaticbooleanlock(Stringkey,Stringvalue,longtime){

finalbooleanresult=Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(CacheConstant.LOCK_KEY+key,value,time,TimeUnit.SECONDS));

if(result){

("[redisTemplateredis]設(shè)置鎖緩存緩存url:{}========緩存時間為{}秒",key,time);

returnresult;

5.增加線程唯一值

還有一種情況會導(dǎo)致我們可能誤刪除別人的鎖,比如當(dāng)線程1執(zhí)行完流程之后準(zhǔn)備釋放鎖之時,這時候鎖正好失效了,線程2此時獲取到鎖,線程1釋放鎖時并不知道鎖失效了,那么線程1執(zhí)行釋放操作就會將線程2擁有的鎖釋放掉,這肯定是不對的,那么我們再對unlock方法改進(jìn)一下

/**

*解鎖

*@paramkeyredis主鍵

publicstaticbooleanunlock(Stringkey,Stringvalue){

if(Objects.equals(value,redisTemplate.opsForValue().get(CacheConstant.LOCK_KEY))){

finalbooleanresult=Boolean.TRUE.equals(redisTemplate.delete(CacheConstant.LOCK_KEY+key));

if(result){

("[redisTemplateredis]釋放鎖緩存url:{}",key);

returnresult;

returnfalse;

@GetMapping(value="order")

publicRorder(){

booleanlock;

intstock;

Stringuuid=IdUtil.fastUUID();

try{

lock=RedisUtil.lock("stock",uuid,60L);

if(!lock){

returnR.failed("服務(wù)繁忙,稍后再試");

stock=RedisUtil.getObject("stock",Integer.class);

if(stock0){

RedisUtil.set("stock",--stock);

}finally{

//在此釋放鎖時,判斷鎖是為自己持有才進(jìn)行釋放

RedisUtil.unlock("stock",uuid);

returnR.ok(stock);

6.Lua腳本

上面我們說了為了防止誤刪別人的鎖,我們需要在刪除鎖時判斷一下鎖是否為自己持有,那么問題來了,我們這個查詢鎖值和刪除鎖的操作也并不是一個原子操作,也就是說可能你在獲取鎖值時鎖還為自己持有,但是執(zhí)行刪除時鎖已經(jīng)不為自己持有了,還是會可能誤刪別人的鎖,想要保證釋放鎖的原子性,我們可以通過redis原生支持的lua腳本來實(shí)現(xiàn)

/**

*解鎖

*@paramkeyredis主鍵

*@paramvalue值

publicstaticbooleanunlock(Stringkey,Stringvalue){

Stringscript="ifredis.call('get',KEYS[1])==ARGV[1]thenreturnredis.call('del',KEYS[1])elsereturn0end";

RedisScriptLongredisScript=newDefaultRedisScript(script,Long.class);

Longresult=redisTemplate.execute(redisScript,Collections.singletonList(CacheConstant.LOCK_KEY+key),value);

if(Objects.equals(1L,result)){

("[redisTemplateredis]釋放鎖緩存url:{}",key);

returntrue;

returnfalse;

7.Lua是如何實(shí)現(xiàn)原子性的

可以看到Lua腳本的大致意思也是跟我們自己寫的代碼差不多,判斷是否為自己持有如果是才進(jìn)行刪除,那為什么Lua腳本可以保證原子性呢

Redis使用同一個Lua解釋器來執(zhí)行所有命令,同時,Redis保證以一種原子性的方式來執(zhí)行腳本:當(dāng)lua腳本在執(zhí)行的時候,不會有其他腳本和命令同時執(zhí)行,這種語義類似于MULTI/EXEC。從別的客戶端的視角來看,一個lua腳本要么不可見,要么已經(jīng)執(zhí)行完。

然而這也意味著,執(zhí)行一個較慢的lua腳本是不建議的,由于腳本的開銷非常低,構(gòu)造一個快速執(zhí)行的腳本并非難事。但是你要注意到,當(dāng)你正在執(zhí)行一個比較慢的腳本時,所以其他的客戶端都無法執(zhí)行命令。

8.代碼演示

代碼演示

/**

*加鎖

*@paramkeyredis主鍵

*@paramvalue值

*@paramtime過期時間

publicstaticbooleanlock(Stringkey,Stringvalue,longtime){

finalbooleanresult=Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(CacheConstant.LOCK_KEY+key,value,time,TimeUnit.SECONDS));

if(result){

("[redisTemplateredis]設(shè)置鎖緩存緩存url:{}========緩存時間為{}秒",key,time);

returnresult;

*解鎖

*@paramkeyredis主鍵

*@paramvalue值

publicstaticbooleanunlock(Stringkey,Stringvalue){

Stringscript="ifredis.call('get',KEYS[1])==ARGV[1]thenreturnredis.call('del',KEYS[1])elsereturn0end";

RedisScriptLongredisScript=newDefaultRedisScript(script,Long.class);

Longresult=redisTemplate.execute(redisScript,Collections.singletonList(CacheConstant.LOCK_KEY+key),value);

if(Objects.equals(1L,result)){

("[redisTemplateredis]釋放鎖緩存url:{}",key);

returntrue;

returnfa

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

最新文檔

評論

0/150

提交評論