成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Redis場(chǎng)景 | 緩存穿透、擊穿問(wèn)題

數(shù)據(jù)庫(kù) 其他數(shù)據(jù)庫(kù)
由于緩存重建耗時(shí)較長(zhǎng),在這時(shí)間穿插線程2,3,4進(jìn)入;那么這些線程都不能從緩存中查詢到數(shù)據(jù),同一時(shí)間去訪問(wèn)數(shù)據(jù)庫(kù),同時(shí)的去執(zhí)行數(shù)據(jù)庫(kù)操作代碼,對(duì)數(shù)據(jù)庫(kù)訪問(wèn)壓力過(guò)大。

場(chǎng)景問(wèn)題及原因

緩存穿透:

原因:客戶端請(qǐng)求的數(shù)據(jù)在緩存和數(shù)據(jù)庫(kù)中不存在,這樣緩存永遠(yuǎn)不會(huì)生效,請(qǐng)求全部打入數(shù)據(jù)庫(kù),造成數(shù)據(jù)庫(kù)連接異常。

解決思路:

  1. 緩存空對(duì)象
  2. 對(duì)于不存在的數(shù)據(jù)也在Redis建立緩存,值為空,并設(shè)置一個(gè)較短的TTL時(shí)間問(wèn)題:實(shí)現(xiàn)簡(jiǎn)單,維護(hù)方便,但短期的數(shù)據(jù)不一致問(wèn)題

緩存雪崩:

原因:在同一時(shí)段大量的緩存key同時(shí)失效或者Redis服務(wù)宕機(jī),導(dǎo)致大量請(qǐng)求到達(dá)數(shù)據(jù)庫(kù),帶來(lái)巨大壓力。

解決思路:給不同的Key的TTL添加隨機(jī)值(簡(jiǎn)單),給緩存業(yè)務(wù)添加降級(jí)限流策略(復(fù)雜),給業(yè)務(wù)添加多級(jí)緩存(復(fù)雜)

緩存擊穿(熱點(diǎn)Key):

前提條件:熱點(diǎn)Key&在某一時(shí)段被高并發(fā)訪問(wèn)&緩存重建耗時(shí)較長(zhǎng)

原因:熱點(diǎn)key突然過(guò)期,因?yàn)橹亟ê臅r(shí)長(zhǎng),在這段時(shí)間內(nèi)大量請(qǐng)求落到數(shù)據(jù)庫(kù),帶來(lái)巨大沖擊

解決思路:

  1. 互斥鎖
  2. 給緩存重建過(guò)程加鎖,確保重建過(guò)程只有一個(gè)線程執(zhí)行,其它線程等待問(wèn)題:線程阻塞,導(dǎo)致性能下降且有死鎖風(fēng)險(xiǎn)
  3. 邏輯過(guò)期
  4. 熱點(diǎn)key緩存永不過(guò)期,而是設(shè)置一個(gè)邏輯過(guò)期時(shí)間,查詢到數(shù)據(jù)時(shí)通過(guò)對(duì)邏輯過(guò)期時(shí)間判斷,來(lái)決定是否需要重建緩存;重建緩存也通過(guò)互斥鎖保證單線程執(zhí)行,但是重建緩存利用獨(dú)立線程異步執(zhí)行,其它線程無(wú)需等待,直接查詢到的舊數(shù)據(jù)即可問(wèn)題:不保證一致性,有額外內(nèi)存消耗且實(shí)現(xiàn)復(fù)雜

場(chǎng)景問(wèn)題實(shí)踐解決

完整代碼地址:https://github.com/xbhog/hm-dianping

分支:20221221-xbhog-cacheBrenkdown

分支:20230110-xbhog-Cache_Penetration_Avalance

緩存穿透:

代碼實(shí)現(xiàn):

12345678910111213141516171819202122public Shop queryWithPassThrough(Long id){
//從redis查詢商鋪信息
String shopInfo = stringRedisTemplate.opsForValue().get(SHOP_CACHE_KEY + id);
//命中緩存,返回店鋪信息
if(StrUtil.isNotBlank(shopInfo)){
return JSONUtil.toBean(shopInfo, Shop.class);
}
//redis既沒(méi)有key的緩存,但查出來(lái)信息不為null,則為空字符串
if(shopInfo != null){
return null;
}
//未命中緩存
Shop shop = getById(id);
if(Objects.isNull(shop)){
//將null添加至緩存,過(guò)期時(shí)間減少
stringRedisTemplate.opsForValue().set(SHOP_CACHE_KEY+id,"",5L, TimeUnit.MINUTES);
return null;
}
//對(duì)象轉(zhuǎn)字符串
stringRedisTemplate.opsForValue().set(SHOP_CACHE_KEY+id,JSONUtil.toJsonStr(shop),30L, TimeUnit.MINUTES);
return shop;
}

上述流程圖和代碼非常清晰,由于緩存雪崩簡(jiǎn)單實(shí)現(xiàn)(復(fù)雜實(shí)踐不會(huì))增加隨機(jī)TTL值,緩存穿透和緩存雪崩不過(guò)多解釋。

緩存擊穿:

緩存擊穿邏輯分析:

首先線程1在查詢緩存時(shí)未命中,然后進(jìn)行查詢數(shù)據(jù)庫(kù)并重建緩存。注意上述緩存擊穿發(fā)生的條件,被高并發(fā)訪問(wèn)&緩存重建耗時(shí)較長(zhǎng);

由于緩存重建耗時(shí)較長(zhǎng),在這時(shí)間穿插線程2,3,4進(jìn)入;那么這些線程都不能從緩存中查詢到數(shù)據(jù),同一時(shí)間去訪問(wèn)數(shù)據(jù)庫(kù),同時(shí)的去執(zhí)行數(shù)據(jù)庫(kù)操作代碼,對(duì)數(shù)據(jù)庫(kù)訪問(wèn)壓力過(guò)大。

互斥鎖:

解決方式:加鎖;****可以采用**tryLock方法 + double check**來(lái)解決這樣的問(wèn)題

在線程2執(zhí)行的時(shí)候,由于線程1加鎖在重建緩存,所以線程2被阻塞,休眠等待線程1執(zhí)行完成后查詢緩存。由此造成在重建緩存的時(shí)候阻塞進(jìn)程,效率下降且有死鎖的風(fēng)險(xiǎn)。

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455private Shop queryWithMutex(Long id) {
//從redis查詢商鋪信息
String shopInfo = stringRedisTemplate.opsForValue().get(SHOP_CACHE_KEY + id);
//命中緩存,返回店鋪信息
if(StrUtil.isNotBlank(shopInfo)){
return JSONUtil.toBean(shopInfo, Shop.class);
}
//redis既沒(méi)有key的緩存,但查出來(lái)信息不為null,則為空字符串
if(shopInfo != null){
return null;
}
//實(shí)現(xiàn)緩存重建
String lockKey = "lock:shop:"+id;
Shop shop = null;
try {
Boolean aBoolean = tryLock(lockKey);
if(!aBoolean){
//加鎖失敗,休眠
Thread.sleep(50);
//遞歸等待
return queryWithMutex(id);
}
//獲取鎖成功應(yīng)該再次檢測(cè)redis緩存是否還存在,做doubleCheck,如果存在則無(wú)需重建緩存。
synchronized (this){
//從redis查詢商鋪信息
String shopInfoTwo = stringRedisTemplate.opsForValue().get(SHOP_CACHE_KEY + id);
//命中緩存,返回店鋪信息
if(StrUtil.isNotBlank(shopInfoTwo)){
return JSONUtil.toBean(shopInfoTwo, Shop.class);
}
//redis既沒(méi)有key的緩存,但查出來(lái)信息不為null,則為“”
if(shopInfoTwo != null){
return null;
}
//未命中緩存
shop = getById(id);
// 5.不存在,返回錯(cuò)誤
if(Objects.isNull(shop)){
//將null添加至緩存,過(guò)期時(shí)間減少
stringRedisTemplate.opsForValue().set(SHOP_CACHE_KEY+id,"",5L, TimeUnit.MINUTES);
return null;
}
//模擬重建的延時(shí)
Thread.sleep(200);
//對(duì)象轉(zhuǎn)字符串
stringRedisTemplate.opsForValue().set(SHOP_CACHE_KEY+id,JSONUtil.toJsonStr(shop),30L, TimeUnit.MINUTES);
}

} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
unLock(lockKey);
}
return shop;
}

在獲取鎖失敗時(shí),證明已有線程在重建緩存,使當(dāng)前線程休眠并重試(遞歸實(shí)現(xiàn))。

代碼中需要注意的是synchronized關(guān)鍵字的使用,在獲取到鎖的時(shí)候,在判斷下緩存是否存在(失效)double-check,該關(guān)鍵字鎖的是當(dāng)前對(duì)象。在其關(guān)鍵字{}中是同步處理。

推薦博客:https://blog.csdn.net/u013142781/article/details/51697672

然后進(jìn)行測(cè)試代碼,進(jìn)行壓力測(cè)試(jmeter),首先去除緩存中的值,模擬緩存失效。

設(shè)置1000個(gè)線程,多線程執(zhí)行間隔5s。

所有的請(qǐng)求都是成功的,其qps大約在200,其吞吐量還是比較可觀的。然后看下緩存是否成功(只查詢一次數(shù)據(jù)庫(kù));

邏輯過(guò)期:

思路分析:

當(dāng)用戶開始查詢r(jià)edis時(shí),判斷是否命中,如果沒(méi)有命中則直接返回空數(shù)據(jù),不查詢數(shù)據(jù)庫(kù),而一旦命中后,將value取出,判斷value中的過(guò)期時(shí)間是否滿足,如果沒(méi)有過(guò)期,則直接返回redis中的數(shù)據(jù),如果過(guò)期,則在開啟獨(dú)立線程后直接返回之前的數(shù)據(jù),獨(dú)立線程去重構(gòu)數(shù)據(jù),重構(gòu)完成后釋放互斥鎖。

封裝數(shù)據(jù):這里我們采用新建實(shí)體類來(lái)實(shí)現(xiàn)

12345678910/**
* @author xbhog
* @describe:
* @date
@Data
public class RedisData {
private LocalDateTime expireTime;
private Object data;
}

使得過(guò)期時(shí)間和數(shù)據(jù)有關(guān)聯(lián)關(guān)系,這里的數(shù)據(jù)類型是Object,方便后續(xù)不同類型的封裝。

123456789101112131415161718192021222324252627282930313233343536373839public Shop queryWithLogicalExpire( Long id ) {
String key = CACHE_SHOP_KEY + id;
// 1.從redis查詢商鋪緩存
String json = stringRedisTemplate.opsForValue().get(key);
// 2.判斷是否存在
if (StrUtil.isBlank(json)) {
// 3.存在,直接返回
return null;
}
// 4.命中,需要先把json反序列化為對(duì)象
RedisData redisData = JSONUtil.toBean(json, RedisData.class);
Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);
LocalDateTime expireTime = redisData.getExpireTime();
// 5.判斷是否過(guò)期
if(expireTime.isAfter(LocalDateTime.now())) {
// 5.1.未過(guò)期,直接返回店鋪信息
return shop;
}
// 5.2.已過(guò)期,需要緩存重建
// 6.緩存重建
// 6.1.獲取互斥鎖
String lockKey = LOCK_SHOP_KEY + id;
boolean isLock = tryLock(lockKey);
// 6.2.判斷是否獲取鎖成功
if (isLock){
exectorPool().execute(() -> {
try {
//重建緩存
this.saveShop2Redis(id, 20L);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
unLock(lockKey);
}
});
}
// 6.4.返回過(guò)期的商鋪信息
return shop;
}

當(dāng)前的執(zhí)行流程跟互斥鎖基本相同,需要注意的是,在獲取鎖成功后,我們將緩存重建放到線程池中執(zhí)行,來(lái)異步實(shí)現(xiàn)。

線程池代碼:

12345678910111213141516/**
* 線程池的創(chuàng)建
* @return
*/
private static ThreadPoolExecutor exectorPool(){
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5,
//根據(jù)自己的處理器數(shù)量+1
Runtime.getRuntime().availableProcessors()+1,
2L,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
return executor;
}

緩存重建代碼:

1234567891011121314/**
* 重建緩存
* @param id 重建ID
* @param l 過(guò)期時(shí)間
*/
public void saveShop2Redis(Long id, long l){
//查詢店鋪信息
Shop shop = getById(id);
//封裝邏輯過(guò)期時(shí)間
RedisData redisData = new RedisData();
redisData.setData(shop);
redisData.setExpireTime(LocalDateTime.now().plusSeconds(l));
stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,JSONUtil.toJsonStr(redisData));
}

測(cè)試條件:100線程,1s線程間隔時(shí)間,緩存失效時(shí)間10s。

測(cè)試環(huán)境:緩存中存在對(duì)應(yīng)的數(shù)據(jù),并且在緩存快失效之前修改數(shù)據(jù)庫(kù)中的數(shù)據(jù),造成緩存與數(shù)據(jù)庫(kù)不一致,通過(guò)執(zhí)行壓測(cè),來(lái)查看相關(guān)線程返回的數(shù)據(jù)情況。

從上述兩張圖中可以看到,在前幾個(gè)線程執(zhí)行過(guò)程中店鋪name為102,當(dāng)執(zhí)行時(shí)間從19-20的時(shí)候店鋪name發(fā)生變化為105,滿足邏輯過(guò)期異步執(zhí)行緩存重建的需求.?

責(zé)任編輯:武曉燕 來(lái)源: 今日頭條
相關(guān)推薦

2020-03-16 14:57:24

Redis面試雪崩

2023-03-10 13:33:00

緩存穿透緩存擊穿緩存雪崩

2019-10-12 14:19:05

Redis數(shù)據(jù)庫(kù)緩存

2021-06-05 09:01:01

Redis緩存雪崩緩存穿透

2022-03-08 00:07:51

緩存雪崩數(shù)據(jù)庫(kù)

2019-11-05 14:24:31

緩存雪崩框架

2023-04-14 07:34:19

2024-04-18 11:43:28

緩存數(shù)據(jù)庫(kù)Redis

2024-04-07 00:00:02

Redis雪崩緩存

2022-11-18 14:34:28

2023-12-06 13:38:00

Redis緩存穿透緩存擊穿

2022-05-27 07:57:20

緩存穿透緩存雪崩緩存擊穿

2023-11-10 14:58:03

2020-10-23 10:46:03

緩存雪崩擊穿

2024-03-12 10:44:42

2023-05-15 10:03:00

Redis緩存穿透

2021-12-25 22:28:27

緩存穿透緩存擊穿緩存雪崩

2020-10-13 07:44:40

緩存雪崩 穿透

2022-07-11 07:36:36

緩存緩存雪崩緩存擊穿

2025-05-28 02:25:00

高并發(fā)緩存穿透雪崩
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 操人网站| 精品一区二区三区四区 | 91中文字幕在线观看 | 九九热在线视频 | 怡红院成人在线视频 | 毛片大全 | 男女久久久 | 久久久久亚洲国产| 成人免费看黄网站在线观看 | 成人午夜黄色 | 男女网站免费观看 | 精品亚洲一区二区 | 国产区在线 | 成年人在线观看视频 | 亚洲精品成人免费 | 国产精品毛片无码 | 日韩中文电影 | 羞羞免费网站 | 亚欧洲精品在线视频免费观看 | 国产精品久久久久久久久久了 | 精品中文字幕在线 | 日韩免费福利视频 | 色在线看| 久久精品欧美一区二区三区不卡 | 欧美美女一区二区 | 国产特级毛片 | 在线一区观看 | 亚洲毛片在线 | caoporn视频| 中文字幕一页二页 | 国产高清在线观看 | 精品视频一区二区三区 | 国产免费一区二区三区 | 成人在线精品视频 | 正在播放一区二区 | 久久99精品久久久久久狂牛 | 国产在线网址 | 国产视频一区在线观看 | 国产精品久久久亚洲 | 一区二区高清 | 日本手机看片 |