redis分布式鎖的問(wèn)題與解決方法
分布式鎖
在分布式環(huán)境中,為了保證業(yè)務(wù)數(shù)據(jù)的正常訪問(wèn),防止出現(xiàn)重復(fù)請(qǐng)求的問(wèn)題,會(huì)使用分布式鎖來(lái)阻攔后續(xù)請(qǐng)求。我們先寫(xiě)一段有問(wèn)題的業(yè)務(wù)代碼:
public void doSomething(String userId){ User user=getUser(userId); if(user==null){ user.setUserName("xxxxx"); user.setUserId(userId); insert(user); return; } update(user); }
上面的代碼很簡(jiǎn)單,查詢(xún)db中有沒(méi)有對(duì)應(yīng)的user數(shù)據(jù),如果有的話,執(zhí)行更新操作,如果沒(méi)有則插入。
我們知道,上面的代碼是線程不安全的,在多線程的環(huán)境中,就會(huì)出現(xiàn)問(wèn)題。為了能夠保證數(shù)據(jù)的正確性,在單機(jī)環(huán)境下,我們可以使用synchronized的方法,來(lái)保證線程安全,具體修改:
public synchronized void doSomething(String userId){ User user=getUser(userId); if(user==null){ user.setUserName("xxxxx"); user.setUserId(userId); insert(user); return; } update(user); }
在單機(jī)器的環(huán)境下,能夠解決線程安全的問(wèn)題,那在分布式環(huán)境下呢? 這個(gè)時(shí)候需要用到分布式鎖.
分布式鎖需要借助其他組件來(lái)實(shí)現(xiàn),常用的有redis和zookeeper。下面我們就用redis的實(shí)現(xiàn),來(lái)說(shuō)明下問(wèn)題,分布式鎖具體的實(shí)現(xiàn)方法如下
public void doSomething(String userId){ String lock=RedisUtils.get("xxxx"+userId); if(StringUtils.isNotEmpty(lock)){//說(shuō)明當(dāng)前userId已經(jīng)被鎖定 return; } RedisUtils.set("xxxx"+userId,userId,1000);//鎖定10s User user=getUser(userId); if(user==null){ insert(user); RedisUtils.delete("xxxx"+userId); return; } update(user); RedisUtils.delete("xxxx"+userId); }
上面的代碼解決了在分布式環(huán)境中的并發(fā)的問(wèn)題。但同樣需要考慮一個(gè)問(wèn)題,如果insert操作和update操作異常了,分布式鎖不會(huì)釋放,后續(xù)的請(qǐng)求還會(huì)被攔截。
所以我們?cè)賰?yōu)化,增加對(duì)異常的捕獲。
public void doSomething(String userId){ try { String lock=RedisUtils.get("xxxx"+userId); if(StringUtils.isNotEmpty(lock)){//說(shuō)明當(dāng)前userId已經(jīng)被鎖定 return; } RedisUtils.set("xxxx"+userId,userId,1000);//鎖定1s User user=getUser(userId); if(user==null){ insert(user); return; } update(user); } catch(Exception ex){ } finally{ RedisUtils.delete("xxxx"+userId); } }
現(xiàn)在即使是程序異常了,鎖會(huì)自動(dòng)釋放。但redis的get和set也會(huì)存在并發(fā)問(wèn)題,我們?cè)倮^續(xù)優(yōu)化,使用redis中的setnx方法
public void doSomething(String userId){ try { boolean lock=RedisUtils.setnx("xxxx"+userId,userId,1000);//鎖定1s if(!lock){//說(shuō)明當(dāng)前userId已經(jīng)被鎖定 return; } User user=getUser(userId); if(user==null){ insert(user); return; } update(user); } catch(Exception ex){ } finally{ RedisUtils.delete("xxxx"+userId); } }
上面的代碼好像沒(méi)有什么問(wèn)題了,但也存在很大的隱患。 我們分析下,假設(shè)第一個(gè)請(qǐng)求過(guò)來(lái),執(zhí)行鎖定成功,程序開(kāi)始運(yùn)行,但是insert和update操作阻塞了1s,第二個(gè)請(qǐng)求過(guò)來(lái),鎖的緩存已經(jīng)過(guò)期,第二個(gè)執(zhí)行鎖定成功,這個(gè)時(shí)候第一個(gè)請(qǐng)求完成了鎖被釋放,第二個(gè)請(qǐng)求的鎖就被第一次請(qǐng)求釋放了,第三次的請(qǐng)求就會(huì)造成線程不安全問(wèn)題。
怎么再去優(yōu)化呢?問(wèn)題主要是出現(xiàn)在第一次請(qǐng)求誤刪鎖的問(wèn)題,所以我們?cè)谝瞥i的時(shí)候要判斷能否移除。
思路:我們?cè)阪i定的時(shí)候,value使用當(dāng)前的時(shí)間戳,刪除時(shí)判斷是否過(guò)期如果不過(guò)期就不要?jiǎng)h除,具體代碼如下:
public void doSomething(String userId){ try { boolean lock=RedisUtils.setnx("xxxx"+userId,LocalDateTime.now(),1000);//鎖定10s if(!lock){//說(shuō)明當(dāng)前userId已經(jīng)被鎖定 return; } User user=getUser(userId); if(user==null){ insert(user); return; } update(user); } catch(Exception ex){ } finally{ LocalDateTime lockTIme= RedisUtils.get("xxxx"+userId); if(lockTIme.compare(LocalDateTime.now())<0){ //說(shuō)明已經(jīng)過(guò)期,可以刪除key RedisUtils.delete("xxxx"+userId); } } }
這樣即使出現(xiàn)阻塞,第二次的時(shí)間戳覆蓋了第一次的鎖定,這樣即使第一次完成了,也不會(huì)釋放鎖。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
RedisTemplate 實(shí)現(xiàn)基于Value 操作的簡(jiǎn)易鎖機(jī)制(示例代碼)
本文將介紹如何使用 RedisTemplate 的 opsForValue().setIfAbsent() 方法來(lái)實(shí)現(xiàn)一種簡(jiǎn)單的鎖機(jī)制,并提供一個(gè)示例代碼,展示如何在 Java 應(yīng)用中利用這一機(jī)制來(lái)保護(hù)共享資源的訪問(wèn),感興趣的朋友跟隨小編一起看看吧2024-05-05Redis緩存過(guò)期的實(shí)現(xiàn)示例
Redis緩存的過(guò)期策略是保證緩存可靠性和性能的關(guān)鍵之一,本文主要介紹了Redis緩存過(guò)期的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下2023-12-12Redis 事務(wù)知識(shí)點(diǎn)相關(guān)總結(jié)
這篇文章主要介紹了Redis 事務(wù)相關(guān)總結(jié),幫助大家更好的理解和學(xué)習(xí)使用Redis,感興趣的朋友可以了解下2021-03-03RedisTemplate常用操作方法總結(jié)(set、hash、list、string等)
本文主要介紹了RedisTemplate常用操作方法總結(jié),主要包括了6種常用方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05Redis隊(duì)列和阻塞隊(duì)列的實(shí)現(xiàn)
本文主要介紹了Redis隊(duì)列和阻塞隊(duì)列的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-11-11