Redis解決併發的思路
Redis中的數據存儲策略
企業中的數據存儲策略 其核心就是設計Key
這裏我們的Key的設計是
數據對象名:數據對象id:對象屬性
Key ---- Sku:108:info
Redis解決併發的簡單代碼實現
@Override
public PmsSkuInfo getSkuById1(String skuId) {
PmsSkuInfo pmsSkuInfo=new PmsSkuInfo();
//鏈接緩存
Jedis jedis = redisUtil.getJedis();
//從緩衝中獲得數據
String skuJson = jedis.get("sku"+skuId+"info");
if(StringUtils.isNotBlank(skuJson)){
pmsSkuInfo = JSON.parseObject(skuJson, PmsSkuInfo.class);
}else {
//緩存沒有 從db中取數據
pmsSkuInfo=getSkuByIdFromDb(skuId);
//將db數據寫入redis緩存
if(pmsSkuInfo!=null){
jedis.set("sku"+skuId+"info",JSON.toJSONString(pmsSkuInfo));
}
}
return pmsSkuInfo;
}
Redis緩存問題 — 緩存穿透
-
緩存穿透的概念
緩存穿透是指查詢一個一定不存在的數據 由於緩存不命中 將去查詢db 這導致這個不存在的數據每次都要去db查詢 在流量大時 db就可能掛掉 要是有人利用不存在的key頻繁攻擊我們的應用 這就是漏洞
-
解決方案
對空結果進行緩存 並且設置一個過期時間
if(pmsSkuInfo!=null){ jedis.set("sku"+skuId+"info",JSON.toJSONString(pmsSkuInfo)); }else{ //數據庫中沒有這個sku //爲了防止緩存穿透 設置一個空字符串給redis jedis.setex("sku"+skuId+"info",60*3,JSON.toJSONString("")); }
Redis緩存問題 — 緩存擊穿
-
緩存擊穿的概念
對於一些設置了過期時間的key 這些key可能會在某段時間內被高併發的訪問 是一種非常熱點的數據
這個時候 如果這個key在大量請求同時進來前剛好失效 那麼所有對於這個key的數據查詢全部都落在了
db上 我們稱爲緩存擊穿
緩存擊穿指
某一個熱點key
在高併發的情況下突然失效 導致大量的併發打到db上 -
解決方案
利用Redis數據庫的
分佈式鎖
解決Mysql的訪問壓力問題
-
-
設置分佈式鎖的代碼簡單實現
//緩存沒有 從db中取數據 //設置分佈式鎖 String OK = jedis.set("sku" + skuId + "lock", "1", "nx", "px", 10); if (StringUtils.isNotBlank(OK)&&OK.equals("OK")){ //設置分佈式鎖成功 有權利在規定的過期時間裏訪問Mysql數據庫 pmsSkuInfo=getSkuByIdFromDb(skuId); //將db數據寫入redis緩存 if(pmsSkuInfo!=null){ jedis.set("sku"+skuId+"info",JSON.toJSONString(pmsSkuInfo)); }else{ //數據庫中沒有這個sku //爲了防止緩存穿透 設置一個空字符串給redis jedis.setex("sku"+skuId+"info",60*3,JSON.toJSONString("")); } //在訪問Mysql後 將分佈式鎖釋放掉 jedis.del("sku"+skuId+"lock"); }else { //設置分佈式鎖失敗 自旋嘗試獲取鎖 try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } return getSkuById1(skuId); }
-
設置分佈式鎖的測試結果
結果與我們預想的一致 速度比較快的11先設置分佈式鎖成功後 進行Mysql的數據庫的訪問
此時 12進入商品詳情頁 設置分佈式鎖失敗 這樣一來 我們就利用了Redis的分佈式鎖解決了緩存擊穿的情況下對於Mysql數據庫的高併發的訪問問題
-
如果Redis中的鎖已經過期了 然後鎖過期的請求又執行完畢 回來刪鎖 刪除了其他線程的鎖 怎麼辦?
我們畫圖解釋一下上面這種場景
解決方式 在第一次設置分佈式鎖的時候 將value值設置爲一個隨機生成的token值
在刪除鎖的時候再去做一次判斷 驗證兩次獲取的token是否一致
這部分的完整代碼
//根據sku_id查出對應的某一個sku
@Override
public PmsSkuInfo getSkuById(String skuId) {
System.out.println(Thread.currentThread().getName() + "進入商品詳情");
PmsSkuInfo pmsSkuInfo = new PmsSkuInfo();
//連接緩存
Jedis jedis = redisUtil.getJedis();
//查詢緩存
String skuKey = "sku:" + skuId + ":info";
String skuJson = jedis.get(skuKey);
if (StringUtils.isNotBlank(skuJson)) {
pmsSkuInfo = JSON.parseObject(skuJson, PmsSkuInfo.class);
System.out.println(Thread.currentThread().getName() + "從緩存中獲取數據");
} else {
//緩存沒有 查mysql
//查詢mysql之前 設置分佈式鎖
String token = UUID.randomUUID().toString();
System.out.println(Thread.currentThread().getName()+"發現緩存中沒有 申請緩存的分佈式鎖");
String ok = jedis.set("sku:" + skuId + ":lock", token, "nx", "px", 10 * 1000);
if (StringUtils.isNotBlank(ok) && ok.equals("OK")) {
System.out.println(Thread.currentThread().getName() + "設置分佈式鎖成功 可以訪問mysql數據庫");
//設置成功 有權利在10秒過期時間訪問數據庫
pmsSkuInfo = getSkuByIdFromDb(skuId);
/*
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
//mysql查詢結果放入redis
if (pmsSkuInfo != null) {
jedis.set("sku:" + skuId + ":info", JSON.toJSONString(pmsSkuInfo));
} else {
//數據庫沒有這個sku
//爲了防止緩存穿透 設置一個null或者空字符串給redis
jedis.setex("sku:" + skuId + ":info", 60 * 3, JSON.toJSONString(""));
}
System.out.println(Thread.currentThread().getName() + "釋放緩存的分佈式鎖");
String token2 = jedis.get("sku:" + skuId + ":lock");
if (StringUtils.isNotBlank(token2) && token2.equals(token)) {
//用token確認刪除的是自己的鎖
//釋放分佈式鎖
jedis.del("sku:" + skuId + ":lock");
}
} else {
System.out.println(Thread.currentThread().getName() + "設置分佈式鎖失敗 自旋嘗試獲取鎖");
//分佈式鎖設置失敗
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return getSkuById(skuId);
}
}
jedis.close();
return pmsSkuInfo;
}
Redis緩存問題 — 緩存雪崩
-
緩存雪崩的概念
緩存雪崩是指在設置緩存時採用了相同的過期時間 導致緩存在某一時刻同時失效 請求全部打到db
緩存雪崩是很多key集體失效
-
解決方法
設置不同的緩存失效時間