国产av日韩一区二区三区精品,成人性爱视频在线观看,国产,欧美,日韩,一区,www.成色av久久成人,2222eeee成人天堂

目錄
改進(jìn)版 " >改進(jìn)版
雙重檢查鎖 " >雙重檢查鎖
惡意攻擊 " >惡意攻擊
方案1:設(shè)置空對象" >方案1:設(shè)置空對象
" >方案2 布隆過濾器
優(yōu)勢和劣勢" >優(yōu)勢和劣勢
首頁 Java java教程 緩存擊穿!竟然不知道怎么寫代碼???

緩存擊穿!竟然不知道怎么寫代碼???

Aug 24, 2023 pm 03:59 PM
redis redis緩存管理


Redis中有三大問題:緩存雪崩、緩存擊穿、緩存穿透,今天我們來聊聊緩存擊穿。

關(guān)于緩存擊穿相關(guān)理論文章,相信大家已經(jīng)看過不少,但是具體代碼中是怎么實現(xiàn)的,怎么解決的等問題,可能就一臉懵逼了。

今天,老田就帶大家來看看,緩存擊穿解決和代碼實現(xiàn)。

場景

請看下面這段代碼:

/** 
 * @author 田維常
 * @公眾號 java后端技術(shù)全棧
 * @date 2021/6/27 15:59
 */
@Service
public class UserInfoServiceImpl implements UserInfoService {

    @Resource
    private UserMapper userMapper;
    @Resource
    private RedisTemplate<Long, String> redisTemplate;

    @Override
    public UserInfo findById(Long id) {
        //查詢緩存
        String userInfoStr = redisTemplate.opsForValue().get(id);
        //如果緩存中不存在,查詢數(shù)據(jù)庫
        //1
        if (isEmpty(userInfoStr)) {
            UserInfo userInfo = userMapper.findById(id);
            //數(shù)據(jù)庫中不存在
            if(userInfo == null){
                  return null;
            }
            userInfoStr = JSON.toJSONString(userInfo);
            //2
            //放入緩存
            redisTemplate.opsForValue().set(id, userInfoStr);
        }
        return JSON.parseObject(userInfoStr, UserInfo.class);
    }

    private boolean isEmpty(String string) {
        return !StringUtils.hasText(string);
    }
}

整個流程:

緩存擊穿!竟然不知道怎么寫代碼???

如果,在//1//2之間耗時1.5秒,那就代表著在這1.5秒時間內(nèi)所有的查詢都會走查詢數(shù)據(jù)庫。這也就是我們所說的緩存中的“緩存擊穿”。

其實,你們項目如果并發(fā)量不是很高,也不用怕,并且我見過很多項目也就差不多是這么寫的,也沒那么多事,畢竟只是第一次的時候可能會發(fā)生緩存擊穿。

但,我們也不要抱著一個僥幸的心態(tài)去寫代碼,既然是多線程導(dǎo)致的,估計很多人會想到鎖,下面我們使用鎖來解決。

改進(jìn)版

既然使用到鎖,那么我們第一時間應(yīng)該關(guān)心的是鎖的粒度。

如果我們放在方法findById上,那就是所有查詢都會有鎖的競爭,這里我相信大家都知道我們?yōu)槭裁床环旁诜椒ㄉ稀?/p>

/** 
 * @author 田維常
 * @公眾號 java后端技術(shù)全棧
 * @date 2021/6/27 15:59
 */
@Service
public class UserInfoServiceImpl implements UserInfoService {

    @Resource
    private UserMapper userMapper;
    @Resource
    private RedisTemplate<Long, String> redisTemplate;

    @Override
    public UserInfo findById(Long id) {
        //查詢緩存
        String userInfoStr = redisTemplate.opsForValue().get(id);
        if (isEmpty(userInfoStr)) {
            //只有不存的情況存在鎖
            synchronized (UserInfoServiceImpl.class){
                UserInfo userInfo = userMapper.findById(id);
                //數(shù)據(jù)庫中不存在
                if(userInfo == null){
                     return null;
                }
                userInfoStr = JSON.toJSONString(userInfo);
                //放入緩存
                redisTemplate.opsForValue().set(id, userInfoStr);
            }
        }
        return JSON.parseObject(userInfoStr, UserInfo.class);
    }

    private boolean isEmpty(String string) {
        return !StringUtils.hasText(string);
    }
}

看似解決問題了,其實,問題還是沒得到解決,還是會緩存擊穿,因為排隊獲取到鎖后,還是會執(zhí)行同步塊代碼,也就是還會查詢數(shù)據(jù)庫,完全沒有解決緩存擊穿。

雙重檢查鎖

由此,我們引入雙重檢查鎖,我們在上的版本中進(jìn)行稍微改變,在同步模塊中再次校驗緩存中是否存在。

/** 
 * @author 田維常
 * @公眾號 java后端技術(shù)全棧
 * @date 2021/6/27 15:59
 */
@Service
public class UserInfoServiceImpl implements UserInfoService {

    @Resource
    private UserMapper userMapper;
    @Resource
    private RedisTemplate<Long, String> redisTemplate;

    @Override
    public UserInfo findById(Long id) {
        //查緩存
        String userInfoStr = redisTemplate.opsForValue().get(id);
        //第一次校驗緩存是否存在
        if (isEmpty(userInfoStr)) {
            //上鎖
            synchronized (UserInfoServiceImpl.class){ 
                //再次查詢緩存,目的是判斷是否前面的線程已經(jīng)set過了
                userInfoStr = redisTemplate.opsForValue().get(id);
                //第二次校驗緩存是否存在
                if (isEmpty(userInfoStr)) {
                    UserInfo userInfo = userMapper.findById(id);
                    //數(shù)據(jù)庫中不存在
                    if(userInfo == null){
                        return null;
                    }
                    userInfoStr = JSON.toJSONString(userInfo);
                    //放入緩存
                    redisTemplate.opsForValue().set(id, userInfoStr);
                }
            }
        }
        return JSON.parseObject(userInfoStr, UserInfo.class);
    }

    private boolean isEmpty(String string) {
        return !StringUtils.hasText(string);
    }
}

這樣,看起來我們就解決了緩存擊穿問題,大家覺得解決了嗎?

惡意攻擊

回顧上面的案例,在正常的情況下是沒問題,但是一旦有人惡意攻擊呢?

比如說:入?yún)d=10000000,在數(shù)據(jù)庫里并沒有這個id,怎么辦呢?

第一步、緩存中不存在

第二步、查詢數(shù)據(jù)庫

第三、由于數(shù)據(jù)庫中不存在,直接返回了,并沒有操作緩存

第四步、再次執(zhí)行第一步.....死循環(huán)了吧

方案1:設(shè)置空對象

就是當(dāng)緩存中和數(shù)據(jù)庫中都不存在的情況下,以id為key,空對象為value。

set(id,空對象);

回到上面的四步,就變成了。

比如說:入?yún)?code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">id=10000000,在數(shù)據(jù)庫里并沒有這個id,怎么辦呢?

第一步、緩存中不存在

第二步、查詢數(shù)據(jù)庫

第三、由于數(shù)據(jù)庫中不存在,以id為key,空對象為value放入緩存中

第四步、執(zhí)行第一步,此時,緩存就存在了,只是這時候只是一個空對象。

代碼實現(xiàn)部分:

/** 
 * @author 田維常
 * @公眾號 java后端技術(shù)全棧
 * @date 2021/6/27 15:59
 */
@Service
public class UserInfoServiceImpl implements UserInfoService {

    @Resource
    private UserMapper userMapper;
    @Resource
    private RedisTemplate<Long, String> redisTemplate; 

    @Override
    public UserInfo findById(Long id) {
        String userInfoStr = redisTemplate.opsForValue().get(id);
        //判斷緩存是否存在,是否為空對象
        if (isEmpty(userInfoStr)) {
            synchronized (UserInfoServiceImpl.class){
                userInfoStr = redisTemplate.opsForValue().get(id);
                if (isEmpty(userInfoStr)) {
                    UserInfo userInfo = userMapper.findById(id);
                    if(userInfo == null){
                        //構(gòu)建一個空對象
                        userInfo= new UserInfo();
                    }
                    userInfoStr = JSON.toJSONString(userInfo);
                    redisTemplate.opsForValue().set(id, userInfoStr);
                }
            }
        }
        UserInfo userInfo = JSON.parseObject(userInfoStr, UserInfo.class);
        //空對象處理
        if(userInfo.getId() == null){
            return null;
        }
        return JSON.parseObject(userInfoStr, UserInfo.class);
    }

    private boolean isEmpty(String string) {
        return !StringUtils.hasText(string);
    }
}

方案2 布隆過濾器

布隆過濾器(Bloom Filter):是一種空間效率極高的概率型算法和數(shù)據(jù)結(jié)構(gòu),用于判斷一個元素是否在集合中(類似Hashset)。它的核心一個很長的二進(jìn)制向量和一系列hash函數(shù),數(shù)組長度以及hash函數(shù)的個數(shù)都是動態(tài)確定的。

Hash函數(shù):SHA1,SHA256,MD5..

布隆過濾器的用處就是,能夠迅速判斷一個元素是否在一個集合中。因此他有如下三個使用場景:

  • 網(wǎng)頁爬蟲對URL的去重,避免爬取相同的URL地址
  • URL的去重,避免爬取相同的URL地址
  • 反垃圾郵件,從數(shù)十億個垃圾郵件列表中判斷某郵箱是否垃圾郵箱(垃圾短信)
  • 緩存擊穿,將已存在的緩存放到布隆過濾器中,當(dāng)黑客訪問不存在的緩存時迅速返回避免緩存及DB掛掉。
  • 其內(nèi)部維護(hù)一個全為0的bit數(shù)組,需要說明的是,布隆過濾器有一個誤判率的概念,誤判率越低,則數(shù)組越長,所占空間越大。誤判率越高則數(shù)組越小,所占的空間越小。布隆過濾器的相關(guān)理論和算法這里就不聊了,感興趣的可以自行研究。

    優(yōu)勢和劣勢

    優(yōu)勢

    • 全量存儲但是不存儲元素本身,在某些對保密要求非常嚴(yán)格的場合有優(yōu)勢;
    • 空間高效率
    • 插入/查詢時間都是常數(shù)O(k),遠(yuǎn)遠(yuǎn)超過一般的算法

    劣勢

    反垃圾郵件,從數(shù)十億個垃圾郵件列表中判斷某郵箱是否垃圾郵箱(垃圾短信)????緩存擊穿,將已存在的緩存放到布隆過濾器中,當(dāng)黑客訪問不存在的緩存時迅速返回避免緩存及DB掛掉。

    其內(nèi)部維護(hù)一個全為0的bit數(shù)組,需要說明的是,布隆過濾器有一個誤判率的概念,誤判率越低,則數(shù)組越長,所占空間越大。誤判率越高則數(shù)組越小,所占的空間越小。布隆過濾器的相關(guān)理論和算法這里就不聊了,感興趣的可以自行研究。??

    優(yōu)勢和劣勢

    優(yōu)勢????????全量存儲但是不存儲元素本身,在某些對保密要求非常嚴(yán)格的場合有優(yōu)勢;????空間高效率????插入/查詢時間都是常數(shù)O(k),遠(yuǎn)遠(yuǎn)超過一般的算法

    劣勢??

    • 存在誤算率(False Positive),默認(rèn)0.03,隨著存入的元素數(shù)量增加,誤算率隨之增加;
    • 一般情況下不能從布隆過濾器中刪除元素;
    • 數(shù)組長度以及hash函數(shù)個數(shù)確定過程復(fù)雜;

    代碼實現(xiàn):

    /** 
     * @author 田維常
     * @公眾號 java后端技術(shù)全棧
     * @date 2021/6/27 15:59
     */
    @Service
    public class UserInfoServiceImpl implements UserInfoService {
    
        @Resource
        private UserMapper userMapper;
        @Resource
        private RedisTemplate<Long, String> redisTemplate;
        private static Long size = 1000000000L;
    
        private static BloomFilter<Long> bloomFilter = BloomFilter.create(Funnels.longFunnel(), size);
    
        @Override
        public UserInfo findById(Long id) {
            String userInfoStr = redisTemplate.opsForValue().get(id);
            if (isEmpty(userInfoStr)) {
                //校驗是否在布隆過濾器中
                if(bloomFilter.mightContain(id)){
                    return null;
                }
                synchronized (UserInfoServiceImpl.class){
                    userInfoStr = redisTemplate.opsForValue().get(id);
                    if (isEmpty(userInfoStr) ) {
                        if(bloomFilter.mightContain(id)){
                            return null;
                        }
                        UserInfo userInfo = userMapper.findById(id);
                        if(userInfo == null){
                            //放入布隆過濾器中
                            bloomFilter.put(id);
                            return null;
                        }
                        userInfoStr = JSON.toJSONString(userInfo);
                        redisTemplate.opsForValue().set(id, userInfoStr);
                    }
                }
            }
            return JSON.parseObject(userInfoStr, UserInfo.class);
        } 
        private boolean isEmpty(String string) {
            return !StringUtils.hasText(string);
        }
    }

    方案3 互斥鎖

    使用Redis實現(xiàn)分布式的時候,有用到setnx,這里大家可以想象,我們是否可以使用這個分布式鎖來解決緩存擊穿的問題?

    這個方案留給大家去實現(xiàn),只要掌握了Redis的分布式鎖,那這個實現(xiàn)起來就非常簡單了。

    總結(jié)

    搞定緩存擊穿、使用雙重檢查鎖的方式來解決,看到雙重檢查鎖,大家肯定第一印象就會想到單例模式,這里也算是給大家復(fù)習(xí)一把雙重檢查鎖的使用。

    由于惡意攻擊導(dǎo)致的緩存擊穿,解決方案我們也實現(xiàn)了兩種,至少在工作和面試中,肯定是能應(yīng)對了。

    另外,使用鎖的時候注意鎖的力度,這里建議換成分布式鎖(Redis或者Zookeeper實現(xiàn)),因為我們既然引入緩存,大部分情況下都會是部署多個節(jié)點的,同時,引入分布式鎖了,我們就可以使用方法入?yún)d用起來,這樣是不是更爽!

    希望大家能領(lǐng)悟到的是文中的一些思路,并不是死記硬背技術(shù)。

    以上是緩存擊穿!竟然不知道怎么寫代碼???的詳細(xì)內(nèi)容。更多信息請關(guān)注PHP中文網(wǎng)其他相關(guān)文章!

    本站聲明
    本文內(nèi)容由網(wǎng)友自發(fā)貢獻(xiàn),版權(quán)歸原作者所有,本站不承擔(dān)相應(yīng)法律責(zé)任。如您發(fā)現(xiàn)有涉嫌抄襲侵權(quán)的內(nèi)容,請聯(lián)系admin@php.cn

    熱AI工具

    Undress AI Tool

    Undress AI Tool

    免費(fèi)脫衣服圖片

    Undresser.AI Undress

    Undresser.AI Undress

    人工智能驅(qū)動的應(yīng)用程序,用于創(chuàng)建逼真的裸體照片

    AI Clothes Remover

    AI Clothes Remover

    用于從照片中去除衣服的在線人工智能工具。

    Clothoff.io

    Clothoff.io

    AI脫衣機(jī)

    Video Face Swap

    Video Face Swap

    使用我們完全免費(fèi)的人工智能換臉工具輕松在任何視頻中換臉!

    熱工具

    記事本++7.3.1

    記事本++7.3.1

    好用且免費(fèi)的代碼編輯器

    SublimeText3漢化版

    SublimeText3漢化版

    中文版,非常好用

    禪工作室 13.0.1

    禪工作室 13.0.1

    功能強(qiáng)大的PHP集成開發(fā)環(huán)境

    Dreamweaver CS6

    Dreamweaver CS6

    視覺化網(wǎng)頁開發(fā)工具

    SublimeText3 Mac版

    SublimeText3 Mac版

    神級代碼編輯軟件(SublimeText3)

    熱門話題

    Laravel 教程
    1601
    29
    PHP教程
    1502
    276
    Laravel 最佳擴(kuò)展包推薦:2024 年必備工具 Laravel 最佳擴(kuò)展包推薦:2024 年必備工具 Apr 30, 2025 pm 02:18 PM

    2024年必備的Laravel擴(kuò)展包包括:1.LaravelDebugbar,用于監(jiān)控和調(diào)試代碼;2.LaravelTelescope,提供詳細(xì)的應(yīng)用監(jiān)控;3.LaravelHorizon,管理Redis隊列任務(wù)。這些擴(kuò)展包能提升開發(fā)效率和應(yīng)用性能。

    Laravel 環(huán)境搭建與基礎(chǔ)配置(Windows/Mac/Linux) Laravel 環(huán)境搭建與基礎(chǔ)配置(Windows/Mac/Linux) Apr 30, 2025 pm 02:27 PM

    在不同操作系統(tǒng)上搭建Laravel環(huán)境的步驟如下:1.Windows:使用XAMPP安裝PHP和Composer,配置環(huán)境變量,安裝Laravel。2.Mac:使用Homebrew安裝PHP和Composer,安裝Laravel。3.Linux:使用Ubuntu更新系統(tǒng),安裝PHP和Composer,安裝Laravel。每個系統(tǒng)的具體命令和路徑有所不同,但核心步驟一致,確保順利搭建Laravel開發(fā)環(huán)境。

    REDIS:與傳統(tǒng)數(shù)據(jù)庫服務(wù)器的比較 REDIS:與傳統(tǒng)數(shù)據(jù)庫服務(wù)器的比較 May 07, 2025 am 12:09 AM

    Redis在高并發(fā)和低延遲場景下優(yōu)于傳統(tǒng)數(shù)據(jù)庫,但不適合復(fù)雜查詢和事務(wù)處理。1.Redis使用內(nèi)存存儲,讀寫速度快,適合高并發(fā)和低延遲需求。2.傳統(tǒng)數(shù)據(jù)庫基于磁盤,支持復(fù)雜查詢和事務(wù)處理,數(shù)據(jù)一致性和持久性強(qiáng)。3.Redis適用于作為傳統(tǒng)數(shù)據(jù)庫的補(bǔ)充或替代,但需根據(jù)具體業(yè)務(wù)需求選擇。

    linux如何限制用戶資源?ulimit怎么配置? linux如何限制用戶資源?ulimit怎么配置? May 29, 2025 pm 11:09 PM

    Linux系統(tǒng)通過ulimit命令限制用戶資源,防止資源過度占用。1.ulimit是shell內(nèi)置命令,可限制文件描述符數(shù)(-n)、內(nèi)存大?。?v)、線程數(shù)(-u)等,分為軟限制(當(dāng)前生效值)和硬限制(最高上限)。2.臨時修改直接使用ulimit命令,如ulimit-n2048,但僅對當(dāng)前會話有效。3.永久生效需修改/etc/security/limits.conf及PAM配置文件,并添加sessionrequiredpam_limits.so。4.systemd服務(wù)需在unit文件中設(shè)置Lim

    Redis主要是數(shù)據(jù)庫嗎? Redis主要是數(shù)據(jù)庫嗎? May 05, 2025 am 12:07 AM

    Redis主要是一個數(shù)據(jù)庫,但它不僅僅是數(shù)據(jù)庫。1.作為數(shù)據(jù)庫,Redis支持持久化,適合高性能需求。2.作為緩存,Redis提升應(yīng)用響應(yīng)速度。3.作為消息代理,Redis支持發(fā)布-訂閱模式,適用于實時通信。

    REDIS:超越SQL- NOSQL的觀點 REDIS:超越SQL- NOSQL的觀點 May 08, 2025 am 12:25 AM

    Redis超越SQL數(shù)據(jù)庫的原因在于其高性能和靈活性。1)Redis通過內(nèi)存存儲實現(xiàn)極快的讀寫速度。2)它支持多種數(shù)據(jù)結(jié)構(gòu),如列表和集合,適用于復(fù)雜數(shù)據(jù)處理。3)單線程模型簡化開發(fā),但高并發(fā)時可能成瓶頸。

    用PhpStudy搭建動態(tài)PHP網(wǎng)站的步驟與示例 用PhpStudy搭建動態(tài)PHP網(wǎng)站的步驟與示例 May 16, 2025 pm 07:54 PM

    使用PhpStudy搭建動態(tài)PHP網(wǎng)站的步驟包括:1.安裝PhpStudy并啟動服務(wù);2.配置網(wǎng)站根目錄和數(shù)據(jù)庫連接;3.編寫PHP腳本生成動態(tài)內(nèi)容;4.調(diào)試和優(yōu)化網(wǎng)站性能。通過這些步驟,你可以從零開始搭建一個功能完整的動態(tài)PHP網(wǎng)站。

    REDIS:揭示其目的和關(guān)鍵應(yīng)用程序 REDIS:揭示其目的和關(guān)鍵應(yīng)用程序 May 03, 2025 am 12:11 AM

    Redisisanopen-Source,內(nèi)存內(nèi)部的庫雷斯塔氏菌,卡赫和梅斯吉級,excellingInsPeedAndVersatory.itiswidelysusedforcaching,Real-Timeanalytics,Session Management,Session Managements,and sessighterboarderboarderboardobboardotoitsssupportfortfortfortfortfortfortfortfortorvortfortfortfortfortfortforvortfortforvortforvortforvortfortforvortforvortforvortforvortdatastherctuct anddatataCcessandcessanddataaCces

    See all articles