<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 2945字,需瀏覽 6分鐘

           ·

          2024-06-19 17:41

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

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

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

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

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

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

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

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

          解決方案:
          解決緩存擊穿的常見方法有幾種:
          1、設(shè)置該key永不過期,那么就不會存在緩存失效、過期等問題。但這種方法很明顯不適合我這種場景,因為我上面提到過,我這個key值存的是邀請榜單的數(shù)據(jù),是動態(tài)更新的,在直播中,這個榜單的數(shù)據(jù)是會變化的,所以只能設(shè)30秒的緩存時間。該方案行不通。

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

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

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

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

          redisKey := "xxx_xxx_xxx" //邀請榜單數(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", //不存在時才執(zhí)行
          }

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

          //step3:獲取互斥鎖成功,則表明當前的線程/協(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

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

          一個人可以被毀滅,但不可以被打敗。

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

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


          瀏覽 68
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩蜜桃久久久 | igao在线观看免费完整版 | 欧美日韩在线免费看 | 国产人妖TS重口系列91中文 | 黑人大屌孟操日本女人 |