網站首頁 編程語言 正文
redis應用問題解決
緩存穿透
什么是緩存穿透?
可以參考下圖,當客戶端發送讀的請求過來時,會先訪問緩存中的數據,如果不存在則直接去訪問MySQL服務器中的數據。這時候如果MySQL服務器中并不存在他請求對應的信息,請求就會反反復復一直訪問MySQL服務器,黑客利用此漏洞進行攻擊可能壓垮數據庫。
解決方案
-
方案一:緩存空值
如果MySQL服務器中不存在相對應的數據,可以將對應的key的value值設置為空,當請求再次訪問時可以直接去緩存中讀取空值
-
方案二:布隆過濾器
在訪問緩存層和存儲層之前,將存在的key用布隆過濾器提前保存起來,做第一層攔截,當收到一個對key請求時先用布隆過濾器驗證是key否存在,如果存在在進入緩存層、存儲層。可以使用bitmap做布隆過濾器。這種方法適用于數據命中不高、數據相對固定、實時性低的應用場景,代碼維護較為復雜,但是緩存空間占用少。
緩存擊穿
什么是緩存擊穿?
比如微博的熱點事件,大家都在同一時間點訪問這個請求,就可能造成服務器崩潰的情況,就好像子彈打在墻上一樣,始終往一個地方大,遲早打穿這堵墻
解決方案
-
方案一:提前設置預熱數據
在redis高峰訪問之前,把一些熱門數據提前存入到redis里面,加大這些熱門數據key的時長
-
方案二:使用分布式鎖
類似于Java中的鎖機制一樣,一個時間只有一個用戶可以訪問,當這個用戶訪問完之后,解鎖了,其他用戶才可以開始訪問,這個在后面也會詳細展開講解
緩存雪崩
什么時候緩存雪崩?
舉個栗子,在雙十一的時候,非常多的用戶訪問非常多的請求,我們雖然提前做了緩存,但在一定時間后這些緩存同時失效,那就會有一大批的請求直接訪問MySQL數據庫,給服務器帶來巨大的壓力
解決方案
-
方案一:構建多級緩存架構
-
方案二:設置過期標志更新緩存
記錄緩存過期時間,快到過期時間時觸發另外一個線程更新過期時間
-
方案三:將緩存失效時間分散開
比如A請求的失效時間為10分鐘后,B請求的失效時間可以設置為10分01秒后,依次類推,即使過期了,請求直接訪問時也可以錯開時間點
分布式鎖
業務需求
我們上述說到的解決緩存擊穿的方案,可以使用分布式鎖,讓請求一個一個訪問。隨著時代的發展,現在我們使用redis已經不是單單的一臺服務器了, 我們會建一個集群,但是鎖這個東西他是不能橫跨服務器的,這種情況redis也提供了解決方案
解決方案
-
使用setnx命令
我們都知道setnx命令如果這個key不存在的話可以對其value進行設置,但key存在時是不允許設置的,我們就可以利用這一點,將此key作為鎖。
-
設置過期時間
就好像有一個人去上廁所,外面排著長隊,結果這個人上著上著突然睡著了,但是外面的人就無法使用。所以我們要將鎖設置一定的過期時間,如果長時間沒有完成就要自動釋放鎖
-
具體命令
# set key value nx ex second : 其中nx等同于setnx,ex設置過期時間單位為秒 set k2 v2 nx ex 10
Java中使用
@GetMapping("/testLock")
public void testLock(){
//1.獲取鎖
Boolean isLock = redisTemplate.opsForValue().setIfAbsent("kkk", "vvv", 10, TimeUnit.SECONDS);
//2.操作數據
if (isLock){
//查詢key為num的數值
Object value = redisTemplate.opsForValue().get("num");
if (StringUtils.isEmpty(value)){
return;
}
//把值轉換為數字并加1
int i = Integer.parseInt(value + "");
redisTemplate.opsForValue().set("num",i+1);
//釋放鎖
redisTemplate.delete("kkk");
}else {
//3.獲取鎖失敗,每個0.1秒再獲取
try {
Thread.sleep(100);
testLock();
}catch (Exception e){
e.printStackTrace();
}
}
}
設置num的值為0,使用ab工具進行多次訪問,如果鎖有效的話,1000個請求num的值應該為1000
ab -n 1000 -c 100 http://192.168.0.10:8080/redisTest/testLock
127.0.0.1:6379> get num
"1000"
問題解決
其實上述案例中還是有那么一些些問題的,需要我們來梳理一下
問題一
如下圖所示,A請求在執行操作的過程中宕機了,但是key的過期時間已到,B請求獲取到了鎖并加上了鎖,B在執行操作的過程中,A相應過來了,手動釋放了鎖,這時其他的請求又會一起擠進來,可能會出現兩個請求操作一個數據的情況
解決方案:使用UUID作為value值,防止誤刪除
@GetMapping("/testLock")
public void testLock(){
String uuid = UUID.randomUUID().toString();
//1.獲取鎖
Boolean isLock = redisTemplate.opsForValue().setIfAbsent("kkk", "uuid", 10, TimeUnit.SECONDS);
//2.操作數據
if (isLock){
//查詢key為num的數值
Object value = redisTemplate.opsForValue().get("num");
if (StringUtils.isEmpty(value)){
return;
}
//把值轉換為數字并加1
int i = Integer.parseInt(value + "");
redisTemplate.opsForValue().set("num",i+1);
//釋放鎖
//判斷是否為自己的鎖
String keyForUuid = (String) redisTemplate.opsForValue().get("kkk");
if (uuid.equals(keyForUuid)){
redisTemplate.delete("kkk");
}
}else {
//3.獲取鎖失敗,每個0.1秒再獲取
try {
Thread.sleep(100);
testLock();
}catch (Exception e){
e.printStackTrace();
}
}
}
問題二
如下入、圖所示,A在釋放鎖的過程中,先判斷了uuid是相同的,正準備刪除時,鎖的過期時間到了,自動刪除后,B獲取到了鎖并加上了鎖,A再刪除了這個鎖
解決方案:使用LUA腳本保證刪除的原子性
@GetMapping("/testLock")
public void testLock(){
String uuid = UUID.randomUUID().toString();
//1.獲取鎖
Boolean isLock = redisTemplate.opsForValue().setIfAbsent("kkk", "uuid", 10, TimeUnit.SECONDS);
//2.操作數據
if (isLock){
//查詢key為num的數值
Object value = redisTemplate.opsForValue().get("num");
if (StringUtils.isEmpty(value)){
return;
}
//把值轉換為數字并加1
int i = Integer.parseInt(value + "");
redisTemplate.opsForValue().set("num",i+1);
// 定義lua 腳本
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
// 使用redis執行lua執行
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(script);
// 設置一下返回值類型 為Long
// 因為刪除判斷的時候,返回的0,給其封裝為數據類型。如果不封裝那么默認返回String 類型,
// 那么返回字符串與0 會有發生錯誤。
redisScript.setResultType(Long.class);
// 第一個要是script 腳本 ,第二個需要判斷的key,第三個就是key所對應的值。
redisTemplate.execute(redisScript, Arrays.asList("kkk"), uuid);
}else {
//3.獲取鎖失敗,每個0.1秒再獲取
try {
Thread.sleep(100);
testLock();
}catch (Exception e){
e.printStackTrace();
}
}
}
原文鏈接:https://blog.csdn.net/Yellow_Star___/article/details/122496108
相關推薦
- 2022-04-23 uniapp封裝本地存儲處理數據的方法和具體使用
- 2022-08-17 R包ggtreeExtra繪制進化樹_R語言
- 2023-04-04 numpy中的norm()函數求范數實例_python
- 2023-03-20 c#判斷代碼是否執行超時的幾種方式總結_C#教程
- 2022-04-04 微信登陸失敗Error: invalid code
- 2023-01-18 pip?search報錯問題及解決_python
- 2022-10-07 Go語言設計模式之實現觀察者模式解決代碼臃腫_Golang
- 2022-05-23 python中的netCDF4批量處理NC文件的操作方法_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支