https://blog.csdn.net/qq_45637260/article/details/125866738
緩存穿透(cache penetration)是指用戶訪問的數據既不在緩存當中,也不在數據庫中。出于容錯的考慮,如果從底層數據庫查詢不到數據,則不寫入緩存。這就導致每次請求都會到底層數據庫進行查詢,緩存也失去了意義。當高并發或有人利用不存在的Key頻繁攻擊時,數據庫的壓力驟增,甚至崩潰,這就是緩存穿透問題。
簡單地說,緩存穿透是指用戶請求的數據在緩存和數據庫中都不存在,則每次請求都會打到數據庫中,給數據庫帶來巨大壓力。
【資料圖】
常見的兩種解決方案
(1)緩存空對象:是指在持久層沒有命中的情況下,對key進行set (key,null)。
緩存空對象會有兩個問題:
value為null 不代表不占用內存空間,空值做了緩存,意味著緩存層中存了更多的鍵,需要更多的內存空間,比較有效的方法是針對這類數據設置一個較短的過期時間,讓其自動剔除。
緩存層和存儲層的數據會有一段時間窗口的不一致,可能會對業務有一定影響。例如過期時間設置為5分鐘,如果此時存儲層添加了這個數據,那此段時間就會出現緩存層和存儲層數據的不一致,此時可以利用消息系統或者其他方式清除掉緩存層中的空對象。
(2)布隆過濾器:
在訪問緩存層和存儲層之前,將存在的key用布隆過濾器提前保存起來,做第一層攔截,當收到一個對key請求時,先用布隆過濾器驗證是key否存在,如果存在再進入緩存層、存儲層。
可以使用bitmap做布隆過濾器。這種方法適用于數據命中不高、數據相對固定、實時性低的應用場景,代碼維護較為復雜,但是緩存空間占用少。
布隆過濾器實際上是一個很長的二進制向量和一系列隨機映射函數。布隆過濾器可以用于檢索一個元素是否在一個集合中。它的優點是空間效率和查詢時間都遠遠超過一般的算法,缺點是有一定的誤識別率和刪除困難。
布隆過濾器攔截的算法描述:
初始狀態時,BloomFilter是一個長度為m的位數組,每一位都置為0。添加元素x時,x使用k個hash函數得到k個hash值,對m取余,對應的bit位設置為1。
判斷y是否屬于這個集合,對y使用k個哈希函數得到k個哈希值,對m取余,所有對應的位置都是1,則認為y屬于該集合(哈希沖突,可能存在誤判),否則就認為y不屬于該集合??梢酝ㄟ^增加哈希函數和增加二進制位數組的長度來降低錯報率
兩種方案的比較:
| 緩存穿透的方案 | 使用場景 | 維護成本 |
|---|---|---|
| 緩存空對象 | 1.數據命中率不高 2.數據頻繁變化實時性高 | 1.代碼維護簡單 2.需要過多的緩存空間 3.數據不一致 |
| 布隆過濾器 | 1.數據命中不高 2.數據相對固定實時性低 | 1.代碼維護復雜 2.緩存空間占用少 |
緩存穿透的解決方案還有:
(2)緩存雪崩緩存雪崩
在使用緩存時,通常會對緩存設置過期時間,一方面目的是保持緩存與數據庫數據的一致性,另一方面是減少冷緩存占用過多的內存空間。但當緩存中大量熱點緩存采用了相同的實效時間,就會導致緩存在某一個時刻同時實效,請求全部轉發到數據庫,從而導致數據庫壓力驟增,甚至宕機。從而形成一系列的連鎖反應,造成系統崩潰等情況,這就是緩存雪崩(Cache Avalanche)。
簡單地說,緩存雪崩是指在同一時間段大量的熱點key同時失效,或者Redis服務宕機,導致大量請求到達數據庫,給數據庫帶來巨大壓力。
解決方案
給不同的key的TTL添加隨機值(比如隨機1-5分鐘),讓key均勻地失效利用redis集群提高服務的可用性(提高高可用性)給緩存業務添加熔斷、降級、限流策略給業務添加多級緩存(3)緩存擊穿緩存擊穿
如果有一個熱點key,在不停的扛著大并發,在這個key失效的瞬間,持續的大并發請求就會擊破緩存,直接請求到數據庫,好像蠻力擊穿一樣。這種情況就是緩存擊穿(Cache Breakdown)。
緩存擊穿問題也叫做熱點key問題,簡單來說,就是一個被高并發訪問并且緩存重建業務較復雜的key突然失效了,無數的請求訪問在瞬間給數據庫帶來巨大的沖擊。
從定義上可以看出,緩存擊穿和緩存雪崩很類似,只不過是緩存擊穿是一個熱點key失效,而緩存雪崩是大量熱點key失效。因此,可以將緩存擊穿看作是緩存雪崩的一個子集。
解決方案
方案一:使用互斥鎖(Mutex Key),只讓一個線程構建緩存,其他線程等待構建緩存執行完畢,重新從緩存中獲取數據。單機通過synchronized或lock來處理,分布式環境采用分布式鎖。
方案二:邏輯過期。熱點數據不設置過期時間,只在value中設置邏輯上的過期時間。后臺異步更新緩存,適用于不嚴格要求緩存一致性的場景。
兩種方案的對比:
3.功能02-商鋪查詢緩存3.4查詢商鋪id的緩存穿透問題3.4.3需求分析解決查詢商鋪查詢可能存在的緩存穿透問題:當訪問不存在的店鋪時,請求會直接打到數據庫上,并且redis緩存永遠不會生效。
這里使用緩存空對象的方式來解決。
3.4.4代碼實現(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未命中,根據id查詢數據庫,判斷商鋪是否存在數據庫中 Shop shop = getById(id); if (shop == null) { //2.2.1不存在,防止緩存穿透,將空值存入redis,TTL設置為2min stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES); //返回錯誤信息 return Result.fail("店鋪不存在!"); } //2.2.2存在,則將商鋪數據寫入redis中 stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES); return Result.ok(shop);}(2)測試,訪問一個緩存和數據庫都不存在的數據:
可以看到redis已經緩存了一個空值
之后再訪問該數據,只要redis的空值對沒有過期,就不會訪問到數據庫,從而起到保護數據庫的作用。
3.5查詢商鋪id的緩存擊穿問題當查詢店鋪id時,可能會出現該店鋪id對應的緩存失效,從而大量請求發送到數據庫的情況,這里使用兩種方案分別解決該問題。
3.5.1基于互斥鎖方案解決3.5.1.1需求分析修改根據id查詢商鋪的業務,基于互斥鎖方式來解決緩存擊穿問題。
如下,當出現緩存擊穿問題,首先需要判斷當前的線程是否能夠獲取鎖:
若可以,則進行緩存重建(將數據庫數據重新寫入緩存中),然后釋放鎖。如果不能,則線程等待一段時間,然后再判斷緩存是否能命中。如果未命中,則重復獲取鎖的流程,直到緩存命中,或者獲得鎖,重建緩存。根據redis的setnx命令,當setnx設置某個key之后,如果該key存在,則其他線程無法設置該key。
我們可以根據這個特性,作為一個lock的邏輯標志,當一個線程setnx某個key后,代表獲取了“鎖”。當刪除這個key時,代表釋放“鎖”,這樣其他線程就可以重新獲取“鎖”。此外,可以對該key設置一個有效期,防止刪除key失敗,產生“死鎖”。
3.5.1.2代碼實現(1)修改 ShopServiceImpl.java
package com.hmdp.service.impl;import .../** * 服務實現類 * * @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); } //緩存穿透(存儲空對象)+緩存擊穿解決(互斥鎖解決) 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); } //獲取鎖成功,開始重建緩存 //根據id查詢數據庫,判斷商鋪是否存在數據庫中 shop = getById(id); //模擬重建緩存的延遲----------- Thread.sleep(200); if (shop == null) { //不存在,防止緩存穿透,將空值存入redis,TTL設置為2min stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES); //返回錯誤信息 return null; } //存在,則將商鋪數據寫入redis中 stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES); } catch (InterruptedException e) { throw new RuntimeException(e); } finally { //釋放互斥鎖 unLock(lockKey); } //返回從緩存或數據庫中查到的數據 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未命中,根據id查詢數據庫,判斷商鋪是否存在數據庫中// Shop shop = getById(id);// if (shop == null) {// //2.2.1不存在,防止緩存穿透,將空值存入redis,TTL設置為2min// stringRedisTemplate.opsForValue().set(key, "",// CACHE_NULL_TTL, TimeUnit.MINUTES);// //返回錯誤信息// return null;// }// //2.2.2存在,則將商鋪數據寫入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.更新數據庫 updateById(shop); //2.刪除redis緩存 stringRedisTemplate.delete(CACHE_SHOP_KEY + id); return Result.ok(); }} (2)使用jemeter模擬高并發的情況:
5秒發起1000個請求線程:
模擬http請求:
全部請求成功,獲取到數據:
在服務器的控制臺中可以看到:對于數據庫的請求只觸發了一次,證明在高并發的場景下,只有一個線程對數據庫發起請求,并對redis對應的緩存重新設置。
3.5.2基于邏輯過期方案解決東方甄選(01797)一度漲超14%。截至發稿,漲10 47%,報32 7港元,成交額8 28億港元。
銀行工作人員提醒:最好在家里備上1萬元現金,關鍵時刻用處大!,現金,毛絨,交易,柜臺,備用金,數字貨幣,工作
2023年四川省沒有公布最高分的具體分數,僅公布280分及以上一共18人。不過,美考官微從相關畫室了解到
東方網記者程琦、蔡黃浩4月20日報道:繼2023年4月7日首批都樂黃金普雅榴蓮空運抵達上海,菲律賓新鮮榴蓮對
匯通財經APP訊——貨幣:歐元 美元 阻力位2:1 1076 阻力位1:1 1000 即期價格:1 0977 支
本規則于2021年12月28日發布,于2023年4月18日修訂,將于2023年5月1日生效。
為貫徹落實《江西省山茶油發展條例》和省委領導批示要求,扎實推進全省山茶油專項整治,加強山茶油生產經營
從人民銀行的調查問卷看,今年一季度貸款總體需求指數是78 4%,比上季度高了18 9個百分點。
2023北京草莓音樂節怎么走?在北京世園公園飛行營地活動交通路線:1、打車北京市區、北京南站距北京世園公
如果肝腎功能很健康,能吃得下肥肉或比較油膩的菜,可以試試為期一個月的生酮飲食。生酮飲食是以脂肪取代葡
蘇州高新區通安鎮田間地頭盡顯科技植保無人機確保夏糧豐收
1、ti:新貴妃醉酒][ar:李玉剛][by:單曲][00:00 00]新貴妃醉酒[00:10 22]李玉剛[00:16
信立泰(002294):泰嘉降價業績短暫承壓慢病創新梯隊價值逐步落地
18日,北京長峰醫院住院部東樓發生火情,21人不幸死亡。涉事醫院系一家連鎖醫院集團,2017年在新三板掛牌上
4月19日,國華投資國華(赤城)風電有限公司河北赤城風氫儲多能互補示范項目(儲能變電站)土建、電氣安裝及調試工程公開招標開標,中標人是中國電

安徽安慶市正式成立“老年助餐慈善基
記者日前從安慶市民政局獲悉,該市慈善會近日設立老年助餐慈善基金,共同守護老年人舌尖上的幸福。該基金專項用于資助城鄉社區老年食堂、社

安徽淮北積極落實2022年電網防汛度汛
近日,國網淮北供電公司工作人員來到110千伏中泰變電站開展防汛隱患排查。該公司積極落實2022年防汛度汛措施,提前細化應急預案,推進極端

安徽全椒縣完善拓展人力信息資源助企
今年以來,全椒縣不斷完善拓展人力資源信息庫、勞務對接信息庫、企業用工需求信息庫三庫信息資源,已摸排400多家次企業缺工崗位信息1 2萬個

宿州市埇橋區柔性引進博士推進鄉村振
宿州市埇橋區實施博士匯工程,柔性引進29名博士擔任副鄉鎮長或園區副主任,他們將為加快產業發展、推進鄉村振興強化智力支持。目前,博士專
安徽印發出臺全面實施零基預算改革方
為進一步提高財政資源配置效率和資金使用效益,省政府印發《安徽省全面實施零基預算改革方案》,明確從編制2023年預算起,在全省范圍內全面
5月份安徽居民消費價格同比上漲2.3%
近日,國家統計局安徽調查總隊發布了我省5月份居民消費價格統計數據。統計顯示,我省居民消費價格同比上漲2 3%,同比漲幅比上月回落0 4個百分
安徽多種方式引導群眾防范非法集資風
合肥地鐵1號線、3號線上滾動播放防范非法集資宣傳視頻,淮南市發布《致老年群眾的一封信》……6月份是一年一度防范和處置非法集資宣傳月,今
鐵路部門持續加大長三角地區運力投放
記者從中國鐵路上海局集團有限公司獲悉,隨著上海疫情防控形勢持續向好,為進一步適應旅客出行需要,助力復工復產,鐵路部門自6月10日起持續加
安徽六安持續精準施策全力促進工業發
六安市與蔚來汽車簽署合作協議,共建智能電動汽車零部件配套產業園區。該園區一期計劃2023年上半年投產,建成后將具備年產30萬噸鋁壓鑄產能,
安徽淮北全力維護外賣送餐員合法權益
為切實防范化解新業態領域重大風險隱患,強化外賣送餐員權益保障工作,淮北市市場監管局充分發揮職能作用,全力維護外賣送餐員合法權益?;幢?/p>
湖南漣源開展專項行動一對一為企業紓
位于漣源市的湖南三合美新材料科技有限公司,兩條生產線滿負荷運行,生產聚氨酯和巖棉復合板。因產品升級與產能擴充,急需新增兩條生產線,
湖南藍山縣進村入戶排查整治自建房安
老叔,這棟房屋墻體有開裂痕跡,要維修加固,安全重要!5月20日,藍山縣塔峰鎮果木村,黨員干部上門開展農村自建房安全隱患排查整治。連日來
一季度湖南萬元產值綜合能耗同比下降
近日,湖南省工業通信業節能監察中心發布一季度全省六大高耗能行業能源消耗統計監測報告。據該報告,一季度全省146家主要高耗能企業的萬元
濟南起步區一年來累計簽約優質項目11
萬里黃河第一隧濟南黃河濟濼路隧道建成通車,占地4000余畝的新能源乘用車零部件產業園加快施工……記者21日采訪獲悉,建設實施方案獲批復一
山東發布通知啟動傳統民居保護利用試
省住房城鄉建設廳、省財政廳近日聯合印發《關于做好傳統民居保護利用試點工作的通知》,在全省部署開展傳統民居保護利用試點工作。此次試點