中文字幕久久亚洲_色婷婷精品大在线视频_久久久久久久久久久久久夜_久久亚洲一区二区三区明星换脸


 

day03-商家查詢緩存02 今日熱聞
發(fā)布時(shí)間:2023-04-20 23:46:45 文章來(lái)源:博客園
功能02-商鋪查詢緩存02知識(shí)補(bǔ)充(1)緩存穿透https://blog.csdn.net/qq_45637260/article/details/125866738緩存穿透(cachepe
功能02-商鋪查詢緩存02知識(shí)補(bǔ)充(1)緩存穿透

https://blog.csdn.net/qq_45637260/article/details/125866738

緩存穿透(cache penetration)是指用戶訪問(wèn)的數(shù)據(jù)既不在緩存當(dāng)中,也不在數(shù)據(jù)庫(kù)中。出于容錯(cuò)的考慮,如果從底層數(shù)據(jù)庫(kù)查詢不到數(shù)據(jù),則不寫(xiě)入緩存。這就導(dǎo)致每次請(qǐng)求都會(huì)到底層數(shù)據(jù)庫(kù)進(jìn)行查詢,緩存也失去了意義。當(dāng)高并發(fā)或有人利用不存在的Key頻繁攻擊時(shí),數(shù)據(jù)庫(kù)的壓力驟增,甚至崩潰,這就是緩存穿透問(wèn)題。

簡(jiǎn)單地說(shuō),緩存穿透是指用戶請(qǐng)求的數(shù)據(jù)在緩存和數(shù)據(jù)庫(kù)中都不存在,則每次請(qǐng)求都會(huì)打到數(shù)據(jù)庫(kù)中,給數(shù)據(jù)庫(kù)帶來(lái)巨大壓力。


【資料圖】

常見(jiàn)的兩種解決方案

(1)緩存空對(duì)象:是指在持久層沒(méi)有命中的情況下,對(duì)key進(jìn)行set (key,null)。

緩存空對(duì)象會(huì)有兩個(gè)問(wèn)題:

value為null 不代表不占用內(nèi)存空間,空值做了緩存,意味著緩存層中存了更多的鍵,需要更多的內(nèi)存空間,比較有效的方法是針對(duì)這類數(shù)據(jù)設(shè)置一個(gè)較短的過(guò)期時(shí)間,讓其自動(dòng)剔除。

緩存層和存儲(chǔ)層的數(shù)據(jù)會(huì)有一段時(shí)間窗口的不一致,可能會(huì)對(duì)業(yè)務(wù)有一定影響。例如過(guò)期時(shí)間設(shè)置為5分鐘,如果此時(shí)存儲(chǔ)層添加了這個(gè)數(shù)據(jù),那此段時(shí)間就會(huì)出現(xiàn)緩存層和存儲(chǔ)層數(shù)據(jù)的不一致,此時(shí)可以利用消息系統(tǒng)或者其他方式清除掉緩存層中的空對(duì)象。

(2)布隆過(guò)濾器:

在訪問(wèn)緩存層和存儲(chǔ)層之前,將存在的key用布隆過(guò)濾器提前保存起來(lái),做第一層攔截,當(dāng)收到一個(gè)對(duì)key請(qǐng)求時(shí),先用布隆過(guò)濾器驗(yàn)證是key否存在,如果存在再進(jìn)入緩存層、存儲(chǔ)層。

可以使用bitmap做布隆過(guò)濾器。這種方法適用于數(shù)據(jù)命中不高、數(shù)據(jù)相對(duì)固定、實(shí)時(shí)性低的應(yīng)用場(chǎng)景,代碼維護(hù)較為復(fù)雜,但是緩存空間占用少。

布隆過(guò)濾器實(shí)際上是一個(gè)很長(zhǎng)的二進(jìn)制向量和一系列隨機(jī)映射函數(shù)。布隆過(guò)濾器可以用于檢索一個(gè)元素是否在一個(gè)集合中。它的優(yōu)點(diǎn)是空間效率和查詢時(shí)間都遠(yuǎn)遠(yuǎn)超過(guò)一般的算法,缺點(diǎn)是有一定的誤識(shí)別率和刪除困難。

布隆過(guò)濾器攔截的算法描述:

初始狀態(tài)時(shí),BloomFilter是一個(gè)長(zhǎng)度為m的位數(shù)組,每一位都置為0。添加元素x時(shí),x使用k個(gè)hash函數(shù)得到k個(gè)hash值,對(duì)m取余,對(duì)應(yīng)的bit位設(shè)置為1。

判斷y是否屬于這個(gè)集合,對(duì)y使用k個(gè)哈希函數(shù)得到k個(gè)哈希值,對(duì)m取余,所有對(duì)應(yīng)的位置都是1,則認(rèn)為y屬于該集合(哈希沖突,可能存在誤判),否則就認(rèn)為y不屬于該集合??梢酝ㄟ^(guò)增加哈希函數(shù)和增加二進(jìn)制位數(shù)組的長(zhǎng)度來(lái)降低錯(cuò)報(bào)率

兩種方案的比較:

緩存穿透的方案使用場(chǎng)景維護(hù)成本
緩存空對(duì)象1.數(shù)據(jù)命中率不高 2.數(shù)據(jù)頻繁變化實(shí)時(shí)性高1.代碼維護(hù)簡(jiǎn)單 2.需要過(guò)多的緩存空間 3.數(shù)據(jù)不一致
布隆過(guò)濾器1.數(shù)據(jù)命中不高 2.數(shù)據(jù)相對(duì)固定實(shí)時(shí)性低1.代碼維護(hù)復(fù)雜 2.緩存空間占用少

緩存穿透的解決方案還有:

(2)緩存雪崩

緩存雪崩

在使用緩存時(shí),通常會(huì)對(duì)緩存設(shè)置過(guò)期時(shí)間,一方面目的是保持緩存與數(shù)據(jù)庫(kù)數(shù)據(jù)的一致性,另一方面是減少冷緩存占用過(guò)多的內(nèi)存空間。但當(dāng)緩存中大量熱點(diǎn)緩存采用了相同的實(shí)效時(shí)間,就會(huì)導(dǎo)致緩存在某一個(gè)時(shí)刻同時(shí)實(shí)效,請(qǐng)求全部轉(zhuǎn)發(fā)到數(shù)據(jù)庫(kù),從而導(dǎo)致數(shù)據(jù)庫(kù)壓力驟增,甚至宕機(jī)。從而形成一系列的連鎖反應(yīng),造成系統(tǒng)崩潰等情況,這就是緩存雪崩(Cache Avalanche)。

簡(jiǎn)單地說(shuō),緩存雪崩是指在同一時(shí)間段大量的熱點(diǎn)key同時(shí)失效,或者Redis服務(wù)宕機(jī),導(dǎo)致大量請(qǐng)求到達(dá)數(shù)據(jù)庫(kù),給數(shù)據(jù)庫(kù)帶來(lái)巨大壓力。

解決方案

給不同的key的TTL添加隨機(jī)值(比如隨機(jī)1-5分鐘),讓key均勻地失效利用redis集群提高服務(wù)的可用性(提高高可用性)給緩存業(yè)務(wù)添加熔斷、降級(jí)、限流策略給業(yè)務(wù)添加多級(jí)緩存(3)緩存擊穿

緩存擊穿

如果有一個(gè)熱點(diǎn)key,在不停的扛著大并發(fā),在這個(gè)key失效的瞬間,持續(xù)的大并發(fā)請(qǐng)求就會(huì)擊破緩存,直接請(qǐng)求到數(shù)據(jù)庫(kù),好像蠻力擊穿一樣。這種情況就是緩存擊穿(Cache Breakdown)。

緩存擊穿問(wèn)題也叫做熱點(diǎn)key問(wèn)題,簡(jiǎn)單來(lái)說(shuō),就是一個(gè)被高并發(fā)訪問(wèn)并且緩存重建業(yè)務(wù)較復(fù)雜的key突然失效了,無(wú)數(shù)的請(qǐng)求訪問(wèn)在瞬間給數(shù)據(jù)庫(kù)帶來(lái)巨大的沖擊。

從定義上可以看出,緩存擊穿和緩存雪崩很類似,只不過(guò)是緩存擊穿是一個(gè)熱點(diǎn)key失效,而緩存雪崩是大量熱點(diǎn)key失效。因此,可以將緩存擊穿看作是緩存雪崩的一個(gè)子集。

解決方案

方案一:使用互斥鎖(Mutex Key),只讓一個(gè)線程構(gòu)建緩存,其他線程等待構(gòu)建緩存執(zhí)行完畢,重新從緩存中獲取數(shù)據(jù)。單機(jī)通過(guò)synchronized或lock來(lái)處理,分布式環(huán)境采用分布式鎖。

方案二:邏輯過(guò)期。熱點(diǎn)數(shù)據(jù)不設(shè)置過(guò)期時(shí)間,只在value中設(shè)置邏輯上的過(guò)期時(shí)間。后臺(tái)異步更新緩存,適用于不嚴(yán)格要求緩存一致性的場(chǎng)景。

兩種方案的對(duì)比:

3.功能02-商鋪查詢緩存3.4查詢商鋪id的緩存穿透問(wèn)題3.4.3需求分析

解決查詢商鋪查詢可能存在的緩存穿透問(wèn)題:當(dāng)訪問(wèn)不存在的店鋪時(shí),請(qǐng)求會(huì)直接打到數(shù)據(jù)庫(kù)上,并且redis緩存永遠(yuǎn)不會(huì)生效。

這里使用緩存空對(duì)象的方式來(lái)解決。

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

(1)修改ShopServiceImpl.java的queryById方法

@Overridepublic Result queryById(Long id) {    String key = CACHE_SHOP_KEY + id;    //1.從redis中查詢商鋪緩存    String shopJson = stringRedisTemplate.opsForValue().get(key);    //2.判斷緩存是否命中    if (StrUtil.isNotBlank(shopJson)) {        //2.1若命中,直接返回商鋪信息        Shop shop = JSONUtil.toBean(shopJson, Shop.class);        return Result.ok(shop);    }    //判斷命中的是否是redis的空值    if (shopJson != null) {        return Result.fail("店鋪不存在!");    }    //2.2未命中,根據(jù)id查詢數(shù)據(jù)庫(kù),判斷商鋪是否存在數(shù)據(jù)庫(kù)中    Shop shop = getById(id);    if (shop == null) {        //2.2.1不存在,防止緩存穿透,將空值存入redis,TTL設(shè)置為2min        stringRedisTemplate.opsForValue().set(key, "",                CACHE_NULL_TTL, TimeUnit.MINUTES);        //返回錯(cuò)誤信息        return Result.fail("店鋪不存在!");    }    //2.2.2存在,則將商鋪數(shù)據(jù)寫(xiě)入redis中    stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop),            CACHE_SHOP_TTL, TimeUnit.MINUTES);    return Result.ok(shop);}

(2)測(cè)試,訪問(wèn)一個(gè)緩存和數(shù)據(jù)庫(kù)都不存在的數(shù)據(jù):

可以看到redis已經(jīng)緩存了一個(gè)空值

之后再訪問(wèn)該數(shù)據(jù),只要redis的空值對(duì)沒(méi)有過(guò)期,就不會(huì)訪問(wèn)到數(shù)據(jù)庫(kù),從而起到保護(hù)數(shù)據(jù)庫(kù)的作用。

3.5查詢商鋪id的緩存擊穿問(wèn)題

當(dāng)查詢店鋪id時(shí),可能會(huì)出現(xiàn)該店鋪id對(duì)應(yīng)的緩存失效,從而大量請(qǐng)求發(fā)送到數(shù)據(jù)庫(kù)的情況,這里使用兩種方案分別解決該問(wèn)題。

3.5.1基于互斥鎖方案解決3.5.1.1需求分析

修改根據(jù)id查詢商鋪的業(yè)務(wù),基于互斥鎖方式來(lái)解決緩存擊穿問(wèn)題。

如下,當(dāng)出現(xiàn)緩存擊穿問(wèn)題,首先需要判斷當(dāng)前的線程是否能夠獲取鎖:

若可以,則進(jìn)行緩存重建(將數(shù)據(jù)庫(kù)數(shù)據(jù)重新寫(xiě)入緩存中),然后釋放鎖。如果不能,則線程等待一段時(shí)間,然后再判斷緩存是否能命中。如果未命中,則重復(fù)獲取鎖的流程,直到緩存命中,或者獲得鎖,重建緩存。

根據(jù)redis的setnx命令,當(dāng)setnx設(shè)置某個(gè)key之后,如果該key存在,則其他線程無(wú)法設(shè)置該key。

我們可以根據(jù)這個(gè)特性,作為一個(gè)lock的邏輯標(biāo)志,當(dāng)一個(gè)線程setnx某個(gè)key后,代表獲取了“鎖”。當(dāng)刪除這個(gè)key時(shí),代表釋放“鎖”,這樣其他線程就可以重新獲取“鎖”。此外,可以對(duì)該key設(shè)置一個(gè)有效期,防止刪除key失敗,產(chǎn)生“死鎖”。

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

(1)修改 ShopServiceImpl.java

package com.hmdp.service.impl;import .../** * 服務(wù)實(shí)現(xiàn)類 * * @author 李 * @version 1.0 */@Servicepublic class ShopServiceImpl extends ServiceImpl        implements IShopService {    @Resource    StringRedisTemplate stringRedisTemplate;    @Override    public Result queryById(Long id) {        Shop shop = queryWithMutex(id);        if (shop == null) {            return Result.fail("店鋪不存在!");        }        return Result.ok(shop);    }    //緩存穿透(存儲(chǔ)空對(duì)象)+緩存擊穿解決(互斥鎖解決)    public Shop queryWithMutex(Long id) {        String key = CACHE_SHOP_KEY + id;        //從redis中查詢商鋪緩存        String shopJson = stringRedisTemplate.opsForValue().get(key);        //判斷緩存是否命中        if (StrUtil.isNotBlank(shopJson)) {            //命中,直接返回商鋪信息            return JSONUtil.toBean(shopJson, Shop.class);        }        //判斷命中的是否是redis的空值(緩存擊穿解決)        if (shopJson != null) {            return null;        }        //未命中,嘗試獲取互斥鎖        String lockKey = "lock:shop:" + id;        boolean isLock = false;        Shop shop = null;        try {            //獲取互斥鎖            isLock = tryLock(lockKey);            //判斷是否獲取成功            if (!isLock) {//失敗                //等待并重試                Thread.sleep(50);                //直到緩存命中,或者獲取到鎖                return queryWithMutex(id);            }            //獲取鎖成功,開(kāi)始重建緩存            //根據(jù)id查詢數(shù)據(jù)庫(kù),判斷商鋪是否存在數(shù)據(jù)庫(kù)中            shop = getById(id);            //模擬重建緩存的延遲-----------            Thread.sleep(200);            if (shop == null) {                //不存在,防止緩存穿透,將空值存入redis,TTL設(shè)置為2min                stringRedisTemplate.opsForValue().set(key, "",                        CACHE_NULL_TTL, TimeUnit.MINUTES);                //返回錯(cuò)誤信息                return null;            }            //存在,則將商鋪數(shù)據(jù)寫(xiě)入redis中            stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop),                    CACHE_SHOP_TTL, TimeUnit.MINUTES);        } catch (InterruptedException e) {            throw new RuntimeException(e);        } finally {            //釋放互斥鎖            unLock(lockKey);        }        //返回從緩存或數(shù)據(jù)庫(kù)中查到的數(shù)據(jù)        return shop;    }    //緩存穿透方案//    public Shop queryWithPassThrough(Long id) {//        String key = CACHE_SHOP_KEY + id;//        //1.從redis中查詢商鋪緩存//        String shopJson = stringRedisTemplate.opsForValue().get(key);//        //2.判斷緩存是否命中//        if (StrUtil.isNotBlank(shopJson)) {//            //2.1若命中,直接返回商鋪信息//            return JSONUtil.toBean(shopJson, Shop.class);//        }//        //判斷命中的是否是redis的空值//        if (shopJson != null) {//            return null;//        }//        //2.2未命中,根據(jù)id查詢數(shù)據(jù)庫(kù),判斷商鋪是否存在數(shù)據(jù)庫(kù)中//        Shop shop = getById(id);//        if (shop == null) {//            //2.2.1不存在,防止緩存穿透,將空值存入redis,TTL設(shè)置為2min//            stringRedisTemplate.opsForValue().set(key, "",//                    CACHE_NULL_TTL, TimeUnit.MINUTES);//            //返回錯(cuò)誤信息//            return null;//        }//        //2.2.2存在,則將商鋪數(shù)據(jù)寫(xiě)入redis中//        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop),//                CACHE_SHOP_TTL, TimeUnit.MINUTES);//        return shop;//    }    private boolean tryLock(String key) {        Boolean flag = stringRedisTemplate.opsForValue()                .setIfAbsent(key, "1", 10, TimeUnit.SECONDS);        return BooleanUtil.isTrue(flag);    }    private void unLock(String key) {        stringRedisTemplate.delete(key);    }    @Override    @Transactional    public Result update(Shop shop) {        Long id = shop.getId();        if (id == null) {            return Result.fail("店鋪id不能為空");        }        //1.更新數(shù)據(jù)庫(kù)        updateById(shop);        //2.刪除redis緩存        stringRedisTemplate.delete(CACHE_SHOP_KEY + id);        return Result.ok();    }}

(2)使用jemeter模擬高并發(fā)的情況:

5秒發(fā)起1000個(gè)請(qǐng)求線程:

模擬http請(qǐng)求:

全部請(qǐng)求成功,獲取到數(shù)據(jù):

在服務(wù)器的控制臺(tái)中可以看到:對(duì)于數(shù)據(jù)庫(kù)的請(qǐng)求只觸發(fā)了一次,證明在高并發(fā)的場(chǎng)景下,只有一個(gè)線程對(duì)數(shù)據(jù)庫(kù)發(fā)起請(qǐng)求,并對(duì)redis對(duì)應(yīng)的緩存重新設(shè)置。

3.5.2基于邏輯過(guò)期方案解決
關(guān)鍵詞:

樂(lè)活HOT

娛樂(lè)LOVE

主站蜘蛛池模板: 久久99久久久久久久噜噜| 国产在线精品一区| 久久久久欧美| 91精品国产自产在线| 日韩精品一区二区三区四| 久久中文精品视频| 色婷婷成人综合| 午夜免费电影一区在线观看| 91精品视频网站| 日韩在线视频网站| 亚洲欧美日韩不卡| 日本一区二区三区www| 欧美一级免费看| 欧美成人免费在线观看| 国产成人一二三区| 久久国产成人精品国产成人亚洲| 久久久www成人免费精品| 人妻久久久一区二区三区| 91精品国产高清| 亚洲欧洲日韩精品| 久久人妻精品白浆国产| 中文字幕免费在线不卡| 不卡av在线播放| 国产欧美一区二区三区在线看| 免费在线国产精品| 日本成人精品在线| 国产日本欧美在线观看| 国产一区视频在线| 国内自拍中文字幕| 日韩中文字幕网| 国产精品高清免费在线观看| av免费观看国产| 久久久综合免费视频| 日韩欧美视频第二区| 欧美亚洲另类制服自拍| 国产成a人亚洲精v品在线观看| 青青成人在线| 久久久综合av| 国产精品入口福利| 久久艹在线视频| 久久精品国产成人|