<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>

          爛大街的緩存穿透、緩存擊穿和緩存雪崩,你真的懂了?

          共 4448字,需瀏覽 9分鐘

           ·

          2022-01-01 15:20

          前言

          對于從事后端開發(fā)的同學(xué)來說,緩存已經(jīng)變成的項目中必不可少的技術(shù)之一。

          沒錯,緩存能給我們系統(tǒng)顯著的提升性能。但如果你使用不好,或者缺乏相關(guān)經(jīng)驗,它也會帶來很多意想不到的問題。

          今天我們一起聊聊如果在項目中引入了緩存,可能會給我們帶來的下面這三大問題??纯茨阒姓辛藳]?

          ?

          1. 緩存穿透問題

          大部分情況下,加緩存的目的是:為了減輕數(shù)據(jù)庫的壓力,提升系統(tǒng)的性能。

          1.1 我們是如何用緩存的?

          一般情況下,如果有用戶請求過來,先查緩存,如果緩存中存在數(shù)據(jù),則直接返回。如果緩存中不存在,則再查數(shù)據(jù)庫,如果數(shù)據(jù)庫中存在,則將數(shù)據(jù)放入緩存,然后返回。如果數(shù)據(jù)庫中也不存在,則直接返回失敗。

          流程圖如下:

          上面的這張圖小伙們肯定再熟悉不過了,因為大部分緩存都是這樣用的。

          1.2 什么是緩存穿透?

          但如果出現(xiàn)以下這兩種特殊情況,比如:

          1. 用戶請求的id在緩存中不存在。

          2. 惡意用戶偽造不存在的id發(fā)起請求。

          這樣的用戶請求導(dǎo)致的結(jié)果是:每次從緩存中都查不到數(shù)據(jù),而需要查詢數(shù)據(jù)庫,同時數(shù)據(jù)庫中也沒有查到該數(shù)據(jù),也沒法放入緩存。也就是說,每次這個用戶請求過來的時候,都要查詢一次數(shù)據(jù)庫。

          圖中標(biāo)紅的箭頭表示每次走的路線。

          很顯然,緩存根本沒起作用,好像被穿透了一樣,每次都會去訪問數(shù)據(jù)庫。

          這就是我們所說的:緩存穿透問題

          如果此時穿透了緩存,而直接數(shù)據(jù)庫的請求數(shù)量非常多,數(shù)據(jù)庫可能因為扛不住壓力而掛掉。嗚嗚嗚。

          那么問題來了,如何解決這個問題呢?

          1.3 校驗參數(shù)

          我們可以對用戶id做檢驗。

          比如你的合法id是15xxxxxx,以15開頭的。如果用戶傳入了16開頭的id,比如:16232323,則參數(shù)校驗失敗,直接把相關(guān)請求攔截掉。這樣可以過濾掉一部分惡意偽造的用戶id。

          1.4 布隆過濾器

          如果數(shù)據(jù)比較少,我們可以把數(shù)據(jù)庫中的數(shù)據(jù),全部放到內(nèi)存的一個map中。

          這樣能夠非常快速的識別,數(shù)據(jù)在緩存中是否存在。如果存在,則讓其訪問緩存。如果不存在,則直接拒絕該請求。

          但如果數(shù)據(jù)量太多了,有數(shù)千萬或者上億的數(shù)據(jù),全都放到內(nèi)存中,很顯然會占用太多的內(nèi)存空間。

          那么,有沒有辦法減少內(nèi)存空間呢?

          答:這就需要使用布隆過濾器了。

          布隆過濾器底層使用bit數(shù)組存儲數(shù)據(jù),該數(shù)組中的元素默認(rèn)值是0。

          布隆過濾器第一次初始化的時候,會把數(shù)據(jù)庫中所有已存在的key,經(jīng)過一些列的hash算法(比如:三次hash算法)計算,每個key都會計算出多個位置,然后把這些位置上的元素值設(shè)置成1。

          之后,有用戶key請求過來的時候,再用相同的hash算法計算位置。

          • 如果多個位置中的元素值都是1,則說明該key在數(shù)據(jù)庫中已存在。這時允許繼續(xù)往后面操作。

          • 如果有1個以上的位置上的元素值是0,則說明該key在數(shù)據(jù)庫中不存在。這時可以拒絕該請求,而直接返回。

          使用布隆過濾器確實可以解決緩存穿透問題,但同時也帶來了兩個問題:

          1. 存在誤判的情況。

          2. 存在數(shù)據(jù)更新問題。

          先看看為什么會存在誤判呢?

          上面我已經(jīng)說過,初始化數(shù)據(jù)時,針對每個key都是通過多次hash算法,計算出一些位置,然后把這些位置上的元素值設(shè)置成1。

          但我們都知道hash算法是會出現(xiàn)hash沖突的,也就是說不通的key,可能會計算出相同的位置。

          上圖中的下標(biāo)為2的位置就出現(xiàn)了hash沖突,key1和key2計算出了一個相同的位置。

          如果有幾千萬或者上億的數(shù)據(jù),布隆過濾器中的hash沖突會非常明顯。

          如果某個用戶key,經(jīng)過多次hash計算出的位置,其元素值,恰好都被其他的key初始化成了1。此時,就出現(xiàn)了誤判,原本這個key在數(shù)據(jù)庫中是不存在的,但布隆過濾器確認(rèn)為存在。

          如果布隆過濾器判斷出某個key存在,可能出現(xiàn)誤判。如果判斷某個key不存在,則它在數(shù)據(jù)庫中一定不存在。

          通常情況下,布隆過濾器的誤判率還是比較少的。即使有少部分誤判的請求,直接訪問了數(shù)據(jù)庫,但如果訪問量并不大,對數(shù)據(jù)庫影響也不大。

          此外,如果想減少誤判率,可以適當(dāng)增加hash函數(shù),圖中用的3次hash,可以增加到5次。

          其實,布隆過濾器最致命的問題是:如果數(shù)據(jù)庫中的數(shù)據(jù)更新了,需要同步更新布隆過濾器。但它跟數(shù)據(jù)庫是兩個數(shù)據(jù)源,就可能存在數(shù)據(jù)不一致的情況。

          比如:數(shù)據(jù)庫中新增了一個用戶,該用戶數(shù)據(jù)需要實時同步到布隆過濾。但由于網(wǎng)絡(luò)異常,同步失敗了。

          這時剛好該用戶請求過來了,由于布隆過濾器沒有該key的數(shù)據(jù),所以直接拒絕了該請求。但這個是正常的用戶,也被攔截了。

          很顯然,如果出現(xiàn)了這種正常用戶被攔截了情況,有些業(yè)務(wù)是無法容忍的。所以,布隆過濾器要看實際業(yè)務(wù)場景再決定是否使用,它幫我們解決了緩存穿透問題,但同時了帶來了新的問題。

          1.5 緩存空值

          上面使用布隆過濾器,雖說可以過濾掉很多不存在的用戶id請求。但它除了增加系統(tǒng)的復(fù)雜度之外,會帶來兩個問題:

          1. 布隆過濾器存在誤殺的情況,可能會把少部分正常用戶的請求也過濾了。

          2. 如果用戶信息有變化,需要實時同步到布隆過濾器,不然會有問題。

          所以,通常情況下,我們很少用布隆過濾器解決緩存穿透問題。其實,還有另外一種更簡單的方案,即:緩存空值。

          當(dāng)某個用戶id在緩存中查不到,在數(shù)據(jù)庫中也查不到時,也需要將該用戶id緩存起來,只不過值是空的。這樣后面的請求,再拿相同的用戶id發(fā)起請求時,就能從緩存中獲取空數(shù)據(jù),直接返回了,而無需再去查一次數(shù)據(jù)庫。

          優(yōu)化之后的流程圖如下:

          關(guān)鍵點是不管從數(shù)據(jù)庫有沒有查到數(shù)據(jù),都將結(jié)果放入緩存中,只是如果沒有查到數(shù)據(jù),緩存中的值是空的罷了。

          ?

          2. 緩存擊穿問題

          2.1 什么是緩存擊穿?

          有時候,我們在訪問熱點數(shù)據(jù)時。比如:我們在某個商城購買某個熱門商品。

          為了保證訪問速度,通常情況下,商城系統(tǒng)會把商品信息放到緩存中。但如果某個時刻,該商品到了過期時間失效了。

          此時,如果有大量的用戶請求同一個商品,但該商品在緩存中失效了,一下子這些用戶請求都直接懟到數(shù)據(jù)庫,可能會造成瞬間數(shù)據(jù)庫壓力過大,而直接掛掉。

          流程圖如下:

          那么,如何解決這個問題呢?

          2.2 加鎖

          數(shù)據(jù)庫壓力過大的根源是,因為同一時刻太多的請求訪問了數(shù)據(jù)庫。

          如果我們能夠限制,同一時刻只有一個請求才能訪問某個productId的數(shù)據(jù)庫商品信息,不就能解決問題了?

          答:沒錯,我們可以用加鎖的方式,實現(xiàn)上面的功能。

          偽代碼如下:

          try?{
          ??String?result?=?jedis.set(productId,?requestId,?"NX",?"PX",?expireTime);
          ??if?("OK".equals(result))?{
          ????return?queryProductFromDbById(productId);
          ??}
          }?finally{
          ????unlock(productId,requestId);
          }??
          return?null;

          在訪問數(shù)據(jù)庫時加鎖,防止多個相同productId的請求同時訪問數(shù)據(jù)庫。

          然后,還需要一段代碼,把從數(shù)據(jù)庫中查詢到的結(jié)果,又重新放入緩存中。辦法挺多的,在這里我就不展開了。

          2.3 自動續(xù)期

          出現(xiàn)緩存擊穿問題是由于key過期了導(dǎo)致的。那么,我們換一種思路,在key快要過期之前,就自動給它續(xù)期,不就OK了?

          答:沒錯,我們可以用job給指定key自動續(xù)期。

          比如說,我們有個分類功能,設(shè)置的緩存過期時間是30分鐘。但有個job每隔20分鐘執(zhí)行一次,自動更新緩存,重新設(shè)置過期時間為30分鐘。

          這樣就能保證,分類緩存不會失效。

          此外,在很多請求第三方平臺接口時,我們往往需要先調(diào)用一個獲取token的接口,然后用這個token作為參數(shù),請求真正的業(yè)務(wù)接口。一般獲取到的token是有有效期的,比如24小時之后失效。

          如果我們每次請求對方的業(yè)務(wù)接口,都要先調(diào)用一次獲取token接口,顯然比較麻煩,而且性能不太好。

          這時候,我們可以把第一次獲取到的token緩存起來,請求對方業(yè)務(wù)接口時從緩存中獲取token。

          同時,有一個job每隔一段時間,比如每隔12個小時請求一次獲取token接口,不停刷新token,重新設(shè)置token的過期時間。

          2.4 緩存不失效

          此外,對于很多熱門key,其實是可以不用設(shè)置過期時間,讓其永久有效的。

          比如參與秒殺活動的熱門商品,由于這類商品id并不多,在緩存中我們可以不設(shè)置過期時間。

          在秒殺活動開始前,我們先用一個程序提前從數(shù)據(jù)庫中查詢出商品的數(shù)據(jù),然后同步到緩存中,提前做預(yù)熱。

          等秒殺活動結(jié)束一段時間之后,我們再手動刪除這些無用的緩存即可。

          ?

          3. 緩存雪崩問題

          3.1 什么是緩存雪崩?

          前面已經(jīng)聊過緩存擊穿問題了。

          而緩存雪崩是緩存擊穿的升級版,緩存擊穿說的是某一個熱門key失效了,而緩存雪崩說的是有多個熱門key同時失效??雌饋?,如果發(fā)生緩存雪崩,問題更嚴(yán)重。

          緩存雪崩目前有兩種:

          1. 有大量的熱門緩存,同時失效。會導(dǎo)致大量的請求,訪問數(shù)據(jù)庫。而數(shù)據(jù)庫很有可能因為扛不住壓力,而直接掛掉。

          2. 緩存服務(wù)器down機了,可能是機器硬件問題,或者機房網(wǎng)絡(luò)問題??傊斐闪苏麄€緩存的不可用。

          歸根結(jié)底都是有大量的請求,透過緩存,而直接訪問數(shù)據(jù)庫了。

          那么,要如何解決這個問題呢?

          3.2 過期時間加隨機數(shù)

          為了解決緩存雪崩問題,我們首先要盡量避免緩存同時失效的情況發(fā)生。

          這就要求我們不要設(shè)置相同的過期時間。

          可以在設(shè)置的過期時間基礎(chǔ)上,再加個1~60秒的隨機數(shù)。

          實際過期時間?=?過期時間?+?1~60秒的隨機數(shù)

          這樣即使在高并發(fā)的情況下,多個請求同時設(shè)置過期時間,由于有隨機數(shù)的存在,也不會出現(xiàn)太多相同的過期key。

          3.3 高可用

          針對緩存服務(wù)器down機的情況,在前期做系統(tǒng)設(shè)計時,可以做一些高可用架構(gòu)。

          比如:如果使用了redis,可以使用哨兵模式,或者集群模式,避免出現(xiàn)單節(jié)點故障導(dǎo)致整個redis服務(wù)不可用的情況。

          使用哨兵模式之后,當(dāng)某個master服務(wù)下線時,自動將該master下的某個slave服務(wù)升級為master服務(wù),替代已下線的master服務(wù)繼續(xù)處理請求。

          3.4 服務(wù)降級

          如果做了高可用架構(gòu),redis服務(wù)還是掛了,該怎么辦呢?

          這時候,就需要做服務(wù)降級了。

          我們需要配置一些默認(rèn)的兜底數(shù)據(jù)。

          程序中有個全局開關(guān),比如有10個請求在最近一分鐘內(nèi),從redis中獲取數(shù)據(jù)失敗,則全局開關(guān)打開。后面的新請求,就直接從配置中心中獲取默認(rèn)的數(shù)據(jù)。

          當(dāng)然,還需要有個job,每隔一定時間去從redis中獲取數(shù)據(jù),如果在最近一分鐘內(nèi)可以獲取到兩次數(shù)據(jù)(這個參數(shù)可以自己定),則把全局開關(guān)關(guān)閉。后面來的請求,又可以正常從redis中獲取數(shù)據(jù)了。

          需要特別說一句,該方案并非所有的場景都適用,需要根據(jù)實際業(yè)務(wù)場景決定。


          有道無術(shù),術(shù)可成;有術(shù)無道,止于術(shù)

          歡迎大家關(guān)注Java之道公眾號


          好文章,我在看??

          瀏覽 29
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  国产伦精品一区二区三区妓女下载 | 特级毛片www | 男人天堂热| 午夜福利视频一区 | 一级一级一级一级一级一级 |