1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        go高并發(fā)之路——緩存擊穿

        共 2945字,需瀏覽 6分鐘

         ·

        2024-06-19 17:41

        緩存擊穿,Redis中的某個(gè)熱點(diǎn)key不存在或者過期,但是此時(shí)有大量的用戶訪問該key。比如xxx直播間優(yōu)惠券搶購、xxx商品活動(dòng),這時(shí)候大量用戶會(huì)在某個(gè)時(shí)間點(diǎn)一同訪問該熱點(diǎn)事件。但是可能由于某種原因,redis的這個(gè)熱點(diǎn)key沒有設(shè)置,或者過期了,那么這時(shí)候大量高并發(fā)對(duì)于該key的請(qǐng)求就得不到redis的響應(yīng),那么就會(huì)將請(qǐng)求直接打在DB服務(wù)器上,造成DB突刺,CPU和內(nèi)存瞬間被打滿,最終導(dǎo)致服務(wù)崩潰。

        本人所負(fù)責(zé)的業(yè)務(wù)就存在這樣的場(chǎng)景,以直播間邀請(qǐng)榜單為例,顧名思義就是會(huì)查詢?cè)撝辈ラg實(shí)時(shí)的邀請(qǐng)人數(shù),統(tǒng)計(jì)前30名邀請(qǐng)人數(shù)最多的用戶展示在直播間里面,通過榜單去刺激C端用戶的分享參與熱情。下面一起分析下這個(gè)場(chǎng)景遇到的問題和解決方案。

        問題1:
        統(tǒng)計(jì)邀請(qǐng)榜單需要加載實(shí)時(shí)的,即我邀請(qǐng)一個(gè)人進(jìn)來,假設(shè)在前30名,那我不得上榜嗎?那問題來了,這種數(shù)據(jù)我是不是得實(shí)時(shí)去查數(shù)據(jù)庫呢?

        解決方案:這種業(yè)務(wù),我們一般會(huì)設(shè)置一個(gè)短時(shí)間的緩存,比如30秒左右。也就是在緩存失效后,即30秒去查一次數(shù)據(jù)庫,不然數(shù)據(jù)庫肯定是頂不住的。

        問題2:
        我們常規(guī)的設(shè)置緩存的代碼邏輯可能是下面這種。(代碼片段錯(cuò)誤處理等細(xì)節(jié)請(qǐng)自行處理,這是一段精簡(jiǎn)版的代碼,主要介紹Redis的處理邏輯)

        	//step1:讀緩存,存在則返回結(jié)果
        ctx := context.Background()

        rdb := redis.NewClient(&redis.Options{
        Addr: "localhost:6379",
        Password: "123456",
        DB: 0,
        })

        redisKey := "xxx_xxx_xxx" //邀請(qǐng)榜單數(shù)據(jù)的key

        res, err := rdb.Get(ctx, redisKey).Result()
        if err == nil {
        return res
        }

        //step2:不存在緩存,讀DB
        //此處省略,查DB的數(shù)據(jù),結(jié)果為res

        //step3:設(shè)置緩存,并返回結(jié)果
        args := redis.SetArgs{
        TTL: time.Second * 30,
        Mode: "EX",
        }
        _, _ = rdb.SetArgs(ctx, redisKey, res, args).Result()

        return res

        這種代碼邏輯在并發(fā)量小的情況下是沒有任何問題的,事實(shí)上我平時(shí)寫一些業(yè)務(wù),基本上就把它當(dāng)成一個(gè)“公式”來用,用的非常多。然而,在一些高并發(fā)的場(chǎng)景下,這種邏輯就會(huì)出現(xiàn)問題。試想一下這個(gè)場(chǎng)景:假如某個(gè)大直播(用戶量巨大)是在晚上8點(diǎn)開播,那么8點(diǎn)一到,那個(gè)瞬間就會(huì)有大量的C端用戶進(jìn)入直播間,去調(diào)用后端的接口,假如此時(shí)接口的Redis緩存已經(jīng)過期或者不存在,那么這一刻就會(huì)有大量的請(qǐng)求落到DB上,可想而知這一刻DB的壓力是多么巨大(這誰頂?shù)米“。?strong>這就是一個(gè)典型的緩存擊穿的業(yè)務(wù)場(chǎng)景。
        那么我們需要怎么做,才能讓我們的服務(wù)抵抗住瞬時(shí)的請(qǐng)求洪峰呢?

        解決方案:
        解決緩存擊穿的常見方法有幾種:
        1、設(shè)置該key永不過期,那么就不會(huì)存在緩存失效、過期等問題。但這種方法很明顯不適合我這種場(chǎng)景,因?yàn)槲疑厦嫣岬竭^,我這個(gè)key值存的是邀請(qǐng)榜單的數(shù)據(jù),是動(dòng)態(tài)更新的,在直播中,這個(gè)榜單的數(shù)據(jù)是會(huì)變化的,所以只能設(shè)30秒的緩存時(shí)間。該方案行不通。

        2、人工干預(yù)該key,比如寫一個(gè)腳本去定時(shí)讀DB數(shù)據(jù),然后更新這個(gè)key,然后業(yè)務(wù)側(cè)(對(duì)接前端的接口)只能通過讀該key的緩存去獲取結(jié)果數(shù)據(jù),而不能直接讀DB。這樣也能解決問題,但是貌似維護(hù)成本有點(diǎn)高,而且業(yè)務(wù)側(cè)不能讀DB也很不靈活,你想下如果每個(gè)熱點(diǎn)key都這樣去設(shè)置維護(hù),那估計(jì)會(huì)很煩吧。該方案也行不通。

        3、使用互斥鎖,即在緩存失效的時(shí)候,只有一個(gè)請(qǐng)求可以獲取到互斥鎖,然后去查DB,最后重建緩存。這種方案就能很好地解決緩存擊穿這個(gè)問題,也是我在工作中用來應(yīng)對(duì)緩存擊穿問題的最常用的方案。下面是精簡(jiǎn)版代碼:

        	//step1:讀緩存,存在則返回結(jié)果
        ctx := context.Background()

        rdb := redis.NewClient(&redis.Options{
        Addr: "localhost:6379",
        Password: "123456",
        DB: 0,
        })

        redisKey := "xxx_xxx_xxx" //邀請(qǐng)榜單數(shù)據(jù)的key

        res, err := rdb.Get(ctx, redisKey).Result()
        if err == nil {
        return res
        }

        //step2:不存在緩存,加互斥鎖,讀緩存
        lockKey := "yyy_yyy_yyy" //互斥鎖的key

        argsLock := redis.SetArgs{
        TTL: time.Second * 3,
        Mode: "NX", //不存在時(shí)才執(zhí)行
        }

        _, err = rdb.SetArgs(ctx, lockKey, "1", argsLock).Result()
        if err != nil { //獲取互斥鎖失敗
        for i := 0; i < 3; i++ { //重復(fù)三次去讀緩存值
        res, errRetry := rdb.Get(ctx, redisKey).Result()
        if errRetry == nil { //重試讀緩存成功,則返回結(jié)果
        return res
        }
        time.Sleep(10 * time.Millisecond) //這里睡眠時(shí)間根據(jù)業(yè)務(wù)來定,取的是另一個(gè)線程從讀數(shù)據(jù)庫到設(shè)置緩存成功的大概時(shí)間區(qū)間
        }
        return nil //如果循環(huán)三次,都讀不到緩存,則返回空結(jié)果
        }

        //step3:獲取互斥鎖成功,則表明當(dāng)前的線程/協(xié)程擁有查DB的權(quán)力
        //此處省略,查DB的數(shù)據(jù),結(jié)果為res

        //step4:設(shè)置緩存,刪除互斥鎖,并返回結(jié)果
        args := redis.SetArgs{
        TTL: time.Second * 30,
        Mode: "EX",
        }
        _, _ = rdb.SetArgs(ctx, redisKey, res, args).Result()

        rdb.Del(ctx, lockKey) //刪除互斥鎖

        return res

        以上就是個(gè)人在線上的一些項(xiàng)目面對(duì)緩存擊穿問題,所做的一些處理方案了。當(dāng)然這個(gè)方案也不是完美的,例如當(dāng)獲取到互斥鎖的當(dāng)前線程/協(xié)程,出現(xiàn)異常,導(dǎo)致設(shè)置緩存失敗,那么其他線程/協(xié)程就重試3次可能都獲取不到正常結(jié)果,最后返回了一個(gè)空結(jié)果給前端。感興趣的朋友可以想想這個(gè)方案還有什么問題,然后能怎么優(yōu)化,歡迎指出。

        一個(gè)人可以被毀滅,但不可以被打敗。

        鏈接:https://www.cnblogs.com/lmz-blogs/p/18173813

        (版權(quán)歸原作者所有,侵刪)


        瀏覽 68
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            国产a级视频 | 一级一级a爱片免费兔兔软件 | 宫交疼出去太深了h | 簧片免费看 | 高潮腔乱xxxx另类hd | 亚洲一级黄色电影 | 一级片免费电影 | 麻豆一级A片久久久乱码 | 五月天丁香综合久久国产 | 国产新婚疯狂做爰视频 |