文章目錄
- 1、緩存擊穿
- 2、緩存穿透
- 3、緩存雪崩
- 4、解決緩存擊穿的分佈式鎖有兩種
- 1. redis自帶一個分佈式鎖,set px nx
- 2. redisson框架,一個redis的帶有juc的lock功能的客戶端的實現(既有jedis的功能,又有juc的鎖功能)
- 3.使用redis分佈式鎖的實現
- 3.1、使用本方法處理高併發下的人會遇到的問題
- 1、 高併發下,若是拿鎖的線程因某些原因,導致自己的鎖未經過自己釋放而過期,而某一時刻該線程醒來時,會來釋放鎖,而此時redis上的鎖已經是別的線程上的鎖了,怎麼辦?
- 2、若是剛好在比對鎖的key對於的唯一Value的時候,該線程的鎖過期,由別的線程進入並拿到鎖,此時釋放鎖,而此時redis上的鎖已經是別的線程上的鎖了,該怎麼辦?
- 3.2、redis分佈式鎖代碼體現,以下代碼對上述問題的解決方案都有體現。
- 4、使用redission的簡單實現
1、緩存擊穿
- 是某一個熱點key在高併發訪問的情況下,突然失效,導致大量的併發打進mysql數據庫的情況
- 解決:在正常的訪問情況下,如果緩存失效,需保護mysql,在重啓緩存的過程,
使用redis數據庫的分佈式鎖,解決mysql的訪問壓力問題。
2、緩存穿透
- 是利用redis和mysql的機制(redis緩存一旦不存在,就訪問mysql),直接繞過緩存訪問mysql,而製造的db請求壓力
一般在代碼中防止該現象的發生 - 解決: 爲了防止緩存穿透將,null或者空字符串值設置給redis
3、緩存雪崩
- 緩存時採用了相同的過期時間,導致緩存在某一時刻同時失效,導致的db崩潰
- 解決:設置不同的緩存失效時間
4、解決緩存擊穿的分佈式鎖有兩種
1. redis自帶一個分佈式鎖,set px nx
- 這種分佈式鎖作用在redis上
- redis上的命令:set key:lock 1 px time(ms) nx
2. redisson框架,一個redis的帶有juc的lock功能的客戶端的實現(既有jedis的功能,又有juc的鎖功能)
- 這種分佈式鎖作用在多個Jedis上
3.使用redis分佈式鎖的實現
3.1、使用本方法處理高併發下的人會遇到的問題
1、 高併發下,若是拿鎖的線程因某些原因,導致自己的鎖未經過自己釋放而過期,而某一時刻該線程醒來時,會來釋放鎖,而此時redis上的鎖已經是別的線程上的鎖了,怎麼辦?
解決此問題有兩種方案:
-
- 不主動釋放鎖,讓其自動釋放。(但要求高響應的系統都不會這樣實現)
-
- 仍然主動釋放鎖,但是設置鎖的key對於的唯一Value ,需解鎖時,先判斷是否是自己的鎖,再釋放鎖,是則釋放,否則直接跳過。
2、若是剛好在比對鎖的key對於的唯一Value的時候,該線程的鎖過期,由別的線程進入並拿到鎖,此時釋放鎖,而此時redis上的鎖已經是別的線程上的鎖了,該怎麼辦?
- 可以用lua腳本,在查詢到key的同時刪除該key,防止高併發下的該意外的發生。
(lua:別讓我看到你,看到我就把你幹掉)
3.2、redis分佈式鎖代碼體現,以下代碼對上述問題的解決方案都有體現。
本代碼是一個業務邏輯中的服務層的一個方法。
public PmsSkuInfo getSkuBySkuId(String skuId,String ip) {
System.out.println("ip爲"+ip+"的同學:"+Thread.currentThread().getName()+"進入的商品詳情的請求");
//獲取jedis對象
Jedis jedis = redisUtil.getJedis();
//獲取skuinfo
PmsSkuInfo pmsSkuInfo = new PmsSkuInfo();
//構造key
String skukey = "sku:"+skuId+":Info";
//查緩存
String skuJson = jedis.get(skukey);
//判斷緩存是否存在
if (StringUtils.isNotBlank(skuJson)){
//緩存存在 提取出數據封裝在對象中
pmsSkuInfo = JSON.parseObject(skuJson,PmsSkuInfo.class);
}else{
//緩存中不存在改數據 去mysql中查找
//設置分佈式鎖 防止擊穿
//設置redis上分佈式鎖的唯一值
String lockValue = UUID.randomUUID().toString();
//上鎖成功放回 OK
String Ok = jedis.set("sku:"+skuId+":lock",lockValue,"nx","px",10*1000);
if (StringUtils.isNotBlank(Ok) && Ok.equals("OK")){
//設置鎖成功 有權在10s內操作mysql
pmsSkuInfo = getSkuBySkuIdFromDb(skuId);
if (pmsSkuInfo != null){
//先睡眠5s再讓其操作mysql 再解鎖
// 睡眠時爲了看到其他線程自旋效果 生產環境不會睡眠
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//db存在數據 將db數據存入redis
jedis.set("sku:"+skuId+":Info", JSON.toJSONString(pmsSkuInfo));
}else{
//db不存在數據 將null存入redis 防止穿透
jedis.set("sku:"+skuId+":Info",JSON.toJSONString(""));
}
//操作完mysql,將redis的分佈式鎖釋放
System.out.println("ip爲"+ip+"的同學:"+Thread.currentThread().getName()+"使用完畢,將鎖歸還:"+"sku:" + skuId + ":lock");
String currentLockValue = jedis.get("sku:"+skuId+":lock");
if (StringUtils.isNotBlank(currentLockValue) && currentLockValue.equals(lockValue)){
//jedis.eval("lua");可與用lua腳本,在查詢到key的同時刪除該key,防止高併發下的意外的發生
jedis.del("sku:" + skuId + ":lock");// 用token確認刪除的是自己的sku的鎖
}
}else {
//設置失敗 說明已經有別的用戶線程設置了鎖
System.out.println("ip爲"+ip+"的同學:"+Thread.currentThread().getName()+"沒有拿到鎖,開始自旋");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//自旋 讓其睡眠幾秒後重寫調用本方法 睡眠是爲了看效果 具體生產環境不會設置睡眠
//注意 不能不帶return ,若是不帶return 直接調用方法,會另外啓動一個線程
//被新啓動的線程爲孤兒線程,別的線程無法訪問
return getSkuBySkuId(skuId,ip);
}
}
//關閉jedis
jedis.close();
return pmsSkuInfo;
}
3.1.1、模擬併發,兩個頁面同時訪問,看後臺輸出,第一個先進入的線程會拿到鎖,鎖是唯一,別的線程拿不到鎖會不斷自旋,直到拿鎖線程釋放鎖爲止。
ip爲127.0.0.1的同學:DubboServerHandler-192.168.154.1:53630-thread-2進入的商品詳情的請求
ip爲127.0.0.1的同學:DubboServerHandler-192.168.154.1:53630-thread-2拿到分佈式鎖
ip爲127.0.0.1的同學:DubboServerHandler-192.168.154.1:53630-thread-3進入的商品詳情的請求
ip爲127.0.0.1的同學:DubboServerHandler-192.168.154.1:53630-thread-3沒有拿到鎖,開始自旋
ip爲127.0.0.1的同學:DubboServerHandler-192.168.154.1:53630-thread-3進入的商品詳情的請求
ip爲127.0.0.1的同學:DubboServerHandler-192.168.154.1:53630-thread-3沒有拿到鎖,開始自旋
ip爲127.0.0.1的同學:DubboServerHandler-192.168.154.1:53630-thread-2使用完畢,將鎖歸還:sku:115:lock
ip爲127.0.0.1的同學:DubboServerHandler-192.168.154.1:53630-thread-3進入的商品詳情的請求
4、使用redission的簡單實現
redisson封裝許多juc的併發工具類,可以調用內部工具類實現併發控制。
public String testRedisson(){
Jedis jedis = redisUtil.getJedis();
RLock lock = redissonClient.getLock("lock");// 聲明鎖
lock.lock();//上鎖
try {
String v = jedis.get("k");
if (StringUtils.isBlank(v)) {
v = "1";
}
System.out.println("->" + v);
jedis.set("k", (Integer.parseInt(v) + 1) + "");
}finally {
jedis.close();
lock.unlock();// 解鎖
}
return "success";
}
有興趣的小夥伴可以 一起交流哦!!!