在SpringBoot中如何利用Redis實現(xiàn)互斥鎖
在SpringBoot中利用Redis實現(xiàn)互斥鎖
基本知識
前提條件,有一個能夠在Springboot中使用Redis的項目,或者能夠直接開也行
為什么要實現(xiàn)互斥鎖:當(dāng)我們利用Redis存儲熱點數(shù)據(jù)時,突然就過期失效或者被刪除了,導(dǎo)致大量請求同時訪問數(shù)據(jù)庫,增加了數(shù)據(jù)庫的負(fù)載。為減輕數(shù)據(jù)庫的負(fù)載我們利用互斥鎖。
業(yè)務(wù)的一個邏輯圖流程:

核心思路:相較于原來從緩存中查詢不到數(shù)據(jù)后直接查詢數(shù)據(jù)庫而言,現(xiàn)在的方案是 進(jìn)行查詢之后,如果從緩存沒有查詢到數(shù)據(jù),則進(jìn)行互斥鎖的獲取,獲取互斥鎖后,判斷是否獲得到了鎖,如果沒有獲得到,則休眠,過一會再進(jìn)行嘗試,直到獲取到鎖為止(這個嘗試,要重新從Redis再次嘗試獲取數(shù)據(jù),可能別的鎖已經(jīng)獲取到了),才能進(jìn)行查詢
如果獲取到了鎖的線程,再去進(jìn)行查詢,查詢后將數(shù)據(jù)寫入redis,再釋放鎖,返回數(shù)據(jù),利用互斥鎖就能保證只有一個線程去執(zhí)行操作數(shù)據(jù)庫的邏輯,防止緩存擊穿
操作鎖的核心思路就是利用redis的setnx方法來表示獲取鎖,該方法含義是redis中如果沒有這個key,則插入成功,返回1
具體實現(xiàn)
設(shè)置鎖,刪除鎖
/**
* 根據(jù)name對特定的數(shù)據(jù)進(jìn)行鎖
* @param name
* @return
*/
public boolean setLock(String name) {
return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(name, true, 10, TimeUnit.SECONDS));
}
public boolean releaseLock(String name) {
return Boolean.TRUE.equals(redisTemplate.delete(name));
}具體流程實現(xiàn)
@GetMapping("/getOneByLock/{sequence}")
public BaseResponse<Sentences> getOneByLock(@PathVariable long sequence) {
// 從redis中查信息
String name = "test:redis:sentences:"+ sequence;
Sentences sentence = (Sentences) redisTemplate.opsForValue().get(name);
// 命中返回數(shù)據(jù)
if(sentence != null ){
redisTemplate.expire(name,2,TimeUnit.MINUTES);
return ResultUtils.success(sentence);
}
// 未命中獲取鎖
String LOCK_NAME = "test:redis:lock:" + sequence;
boolean lock = redisTemplate.opsForValue().get(LOCK_NAME) != null && (boolean) redisTemplate.opsForValue().get(LOCK_NAME);
//如果lock等于false 那么就可以獲取到鎖并且,鎖住不許其他人操作
if(!lock){
return ResultUtils.success(setLockReleaseLockAboutSentence(LOCK_NAME,name,sequence));
}
// 沒有獲取到鎖 休眠一段時間,并且反復(fù)檢測redis中的數(shù)據(jù)是否存在,或者鎖是否釋放
while(true){
try {
Thread.sleep(1000);
log.error("等待中");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 檢查是否存在值
sentence = (Sentences) redisTemplate.opsForValue().get(name);
if(sentence != null){
return ResultUtils.success(sentence);
}
boolean checkAgain = (boolean) redisTemplate.opsForValue().get(LOCK_NAME);
if(!checkAgain){
sentence = setLockReleaseLockAboutSentence(LOCK_NAME,name,sequence);
}
return ResultUtils.success(sentence);
}
}
public Sentences setLockReleaseLockAboutSentence(String LOCK_NAME,String redisName, long sequence){
// 設(shè)置 鎖值 為true
setLock(LOCK_NAME);
// 并且從數(shù)據(jù)中查取數(shù)據(jù)
Sentences sentence = sentencesService.getById(sequence);
// 這里為了明顯不能搶鎖設(shè)置一個睡眠時間
try {
log.error("休眠中");
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 把數(shù)據(jù)寫入Redis
redisTemplate.opsForValue().set(redisName,sentence,2, TimeUnit.MINUTES);
// 釋放鎖
releaseLock(LOCK_NAME);
// 返回數(shù)據(jù)
return sentence;
}代碼說明,在這個代碼中為了演示明顯,獲取鎖中延遲3s,競爭鎖會延遲1s,下面的演示,初始時Redis中沒有數(shù)據(jù),只能去數(shù)據(jù)庫中取數(shù)據(jù),但是設(shè)置了互斥鎖,所以只能夠一個線程進(jìn)入數(shù)據(jù)庫取數(shù)據(jù),其他只能等待數(shù)據(jù)得到結(jié)果。
結(jié)果示意 redis中無數(shù)據(jù)

結(jié)果

最終效果是好的。redis中已存入數(shù)據(jù)

到此這篇關(guān)于在SpringBoot中利用Redis實現(xiàn)互斥鎖的文章就介紹到這了,更多相關(guān)SpringBoot互斥鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用Java編寫控制JDBC連接、執(zhí)行及關(guān)閉的工具類
這篇文章主要介紹了如何使用Java來編寫控制JDBC連接、執(zhí)行及關(guān)閉的程序,包括一個針對各種數(shù)據(jù)庫通用的釋放資源的工具類的寫法,需要的朋友可以參考下2016-03-03
springboot集成nacos報錯:get data from Nacos
這篇文章給大家介紹了springboot集成nacos報錯:get data from Nacos error,dataId:null.yaml的原因及解決方法,如果又遇到相同問題的朋友可以參考閱讀本文2023-10-10
windows下java -jar 后臺運行以及殺死后臺進(jìn)程的操作
這篇文章主要介紹了windows下java -jar 后臺運行以及殺死后臺進(jìn)程的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12
Java中System.currentTimeMillis()計算方式與時間單位轉(zhuǎn)換講解
本文詳細(xì)講解了Java中System.currentTimeMillis()計算方式與時間單位轉(zhuǎn)換,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-12-12
JDK21中虛擬線程到底是什么以及用法總結(jié)(看完便知)
這篇文章主要給大家介紹了關(guān)于JDK21中虛擬線程到底是什么以及用法的相關(guān)資料,虛擬線程是一種輕量化的線程封裝,由jvm直接調(diào)度和管理,反之普通的線程其實是調(diào)用的操作系統(tǒng)的能力,對應(yīng)的是操作系統(tǒng)級的線程,需要的朋友可以參考下2023-12-12

