自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

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

開(kāi)發(fā) 后端
對(duì)于從事后端開(kāi)發(fā)的同學(xué)來(lái)說(shuō),緩存已經(jīng)變成的項(xiàng)目中必不可少的技術(shù)之一。今天我們一起聊聊如果在項(xiàng)目中引入了緩存,可能會(huì)給我們帶來(lái)的下面這三大問(wèn)題??纯茨阒姓辛藳](méi)?

[[441985]]

前言

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

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

今天我們一起聊聊如果在項(xiàng)目中引入了緩存,可能會(huì)給我們帶來(lái)的下面這三大問(wèn)題??纯茨阒姓辛藳](méi)?

1. 緩存穿透問(wèn)題

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

1.1 我們是如何用緩存的?

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

流程圖如下:

上面的這張圖小伙們肯定再熟悉不過(guò)了,因?yàn)榇蟛糠志彺娑际沁@樣用的。

1.2 什么是緩存穿透?

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

用戶(hù)請(qǐng)求的id在緩存中不存在。

惡意用戶(hù)偽造不存在的id發(fā)起請(qǐng)求。

這樣的用戶(hù)請(qǐng)求導(dǎo)致的結(jié)果是:每次從緩存中都查不到數(shù)據(jù),而需要查詢(xún)數(shù)據(jù)庫(kù),同時(shí)數(shù)據(jù)庫(kù)中也沒(méi)有查到該數(shù)據(jù),也沒(méi)法放入緩存。也就是說(shuō),每次這個(gè)用戶(hù)請(qǐng)求過(guò)來(lái)的時(shí)候,都要查詢(xún)一次數(shù)據(jù)庫(kù)。

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

很顯然,緩存根本沒(méi)起作用,好像被穿透了一樣,每次都會(huì)去訪(fǎng)問(wèn)數(shù)據(jù)庫(kù)。

這就是我們所說(shuō)的:緩存穿透問(wèn)題。

如果此時(shí)穿透了緩存,而直接數(shù)據(jù)庫(kù)的請(qǐng)求數(shù)量非常多,數(shù)據(jù)庫(kù)可能因?yàn)榭覆蛔毫Χ鴴斓簟鑶鑶琛?/p>

那么問(wèn)題來(lái)了,如何解決這個(gè)問(wèn)題呢?

1.3 校驗(yàn)參數(shù)

我們可以對(duì)用戶(hù)id做檢驗(yàn)。

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

1.4 布隆過(guò)濾器

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

這樣能夠非??焖俚淖R(shí)別,數(shù)據(jù)在緩存中是否存在。如果存在,則讓其訪(fǎng)問(wèn)緩存。如果不存在,則直接拒絕該請(qǐng)求。

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

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

答:這就需要使用布隆過(guò)濾器了。

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

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

之后,有用戶(hù)key請(qǐng)求過(guò)來(lái)的時(shí)候,再用相同的hash算法計(jì)算位置。

  • 如果多個(gè)位置中的元素值都是1,則說(shuō)明該key在數(shù)據(jù)庫(kù)中已存在。這時(shí)允許繼續(xù)往后面操作。
  • 如果有1個(gè)以上的位置上的元素值是0,則說(shuō)明該key在數(shù)據(jù)庫(kù)中不存在。這時(shí)可以拒絕該請(qǐng)求,而直接返回。

使用布隆過(guò)濾器確實(shí)可以解決緩存穿透問(wèn)題,但同時(shí)也帶來(lái)了兩個(gè)問(wèn)題:

  1. 存在誤判的情況。
  2. 存在數(shù)據(jù)更新問(wèn)題。

先看看為什么會(huì)存在誤判呢?

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

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

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

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

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

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

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

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

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

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

這時(shí)剛好該用戶(hù)請(qǐng)求過(guò)來(lái)了,由于布隆過(guò)濾器沒(méi)有該key的數(shù)據(jù),所以直接拒絕了該請(qǐng)求。但這個(gè)是正常的用戶(hù),也被攔截了。

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

1.5 緩存空值

上面使用布隆過(guò)濾器,雖說(shuō)可以過(guò)濾掉很多不存在的用戶(hù)id請(qǐng)求。但它除了增加系統(tǒng)的復(fù)雜度之外,會(huì)帶來(lái)兩個(gè)問(wèn)題:

布隆過(guò)濾器存在誤殺的情況,可能會(huì)把少部分正常用戶(hù)的請(qǐng)求也過(guò)濾了。

如果用戶(hù)信息有變化,需要實(shí)時(shí)同步到布隆過(guò)濾器,不然會(huì)有問(wèn)題。

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

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

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

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

2. 緩存擊穿問(wèn)題

2.1 什么是緩存擊穿?

有時(shí)候,我們?cè)谠L(fǎng)問(wèn)熱點(diǎn)數(shù)據(jù)時(shí)。比如:我們?cè)谀硞€(gè)商城購(gòu)買(mǎi)某個(gè)熱門(mén)商品。

為了保證訪(fǎng)問(wèn)速度,通常情況下,商城系統(tǒng)會(huì)把商品信息放到緩存中。但如果某個(gè)時(shí)刻,該商品到了過(guò)期時(shí)間失效了。

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

流程圖如下:

那么,如何解決這個(gè)問(wèn)題呢?

2.2 加鎖

數(shù)據(jù)庫(kù)壓力過(guò)大的根源是,因?yàn)橥粫r(shí)刻太多的請(qǐng)求訪(fǎng)問(wèn)了數(shù)據(jù)庫(kù)。

如果我們能夠限制,同一時(shí)刻只有一個(gè)請(qǐng)求才能訪(fǎng)問(wèn)某個(gè)productId的數(shù)據(jù)庫(kù)商品信息,不就能解決問(wèn)題了?

答:沒(méi)錯(cuò),我們可以用加鎖的方式,實(shí)現(xiàn)上面的功能。

偽代碼如下:

  1. try { 
  2.   String result = jedis.set(productId, requestId, "NX""PX", expireTime); 
  3.   if ("OK".equals(result)) { 
  4.     return queryProductFromDbById(productId); 
  5.   } 
  6. } finally{ 
  7.     unlock(productId,requestId); 
  8. }   
  9. return null

在訪(fǎng)問(wèn)數(shù)據(jù)庫(kù)時(shí)加鎖,防止多個(gè)相同productId的請(qǐng)求同時(shí)訪(fǎng)問(wèn)數(shù)據(jù)庫(kù)。

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

2.3 自動(dòng)續(xù)期

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

答:沒(méi)錯(cuò),我們可以用job給指定key自動(dòng)續(xù)期。

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

這樣就能保證,分類(lèi)緩存不會(huì)失效。

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

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

這時(shí)候,我們可以把第一次獲取到的token緩存起來(lái),請(qǐng)求對(duì)方業(yè)務(wù)接口時(shí)從緩存中獲取token。

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

2.4 緩存不失效

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

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

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

等秒殺活動(dòng)結(jié)束一段時(shí)間之后,我們?cè)偈謩?dòng)刪除這些無(wú)用的緩存即可。

3. 緩存雪崩問(wèn)題

3.1 什么是緩存雪崩?

前面已經(jīng)聊過(guò)緩存擊穿問(wèn)題了。

而緩存雪崩是緩存擊穿的升級(jí)版,緩存擊穿說(shuō)的是某一個(gè)熱門(mén)key失效了,而緩存雪崩說(shuō)的是有多個(gè)熱門(mén)key同時(shí)失效。看起來(lái),如果發(fā)生緩存雪崩,問(wèn)題更嚴(yán)重。

緩存雪崩目前有兩種:

  1. 有大量的熱門(mén)緩存,同時(shí)失效。會(huì)導(dǎo)致大量的請(qǐng)求,訪(fǎng)問(wèn)數(shù)據(jù)庫(kù)。而數(shù)據(jù)庫(kù)很有可能因?yàn)榭覆蛔毫?,而直接掛掉?/li>
  2. 緩存服務(wù)器down機(jī)了,可能是機(jī)器硬件問(wèn)題,或者機(jī)房網(wǎng)絡(luò)問(wèn)題??傊?,造成了整個(gè)緩存的不可用。

歸根結(jié)底都是有大量的請(qǐng)求,透過(guò)緩存,而直接訪(fǎng)問(wèn)數(shù)據(jù)庫(kù)了。

那么,要如何解決這個(gè)問(wèn)題呢?

3.2 過(guò)期時(shí)間加隨機(jī)數(shù)

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

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

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

  1. 實(shí)際過(guò)期時(shí)間 = 過(guò)期時(shí)間 + 1~60秒的隨機(jī)數(shù) 

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

3.3 高可用

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

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

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

3.4 服務(wù)降級(jí)

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

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

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

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

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

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

 

責(zé)任編輯:姜華 來(lái)源: 蘇三說(shuō)技術(shù)
相關(guān)推薦

2019-11-05 14:24:31

緩存雪崩框架

2023-03-10 13:33:00

緩存穿透緩存擊穿緩存雪崩

2019-10-12 14:19:05

Redis數(shù)據(jù)庫(kù)緩存

2021-06-05 09:01:01

Redis緩存雪崩緩存穿透

2020-03-16 14:57:24

Redis面試雪崩

2024-03-12 10:44:42

2022-05-27 07:57:20

緩存穿透緩存雪崩緩存擊穿

2022-03-08 00:07:51

緩存雪崩數(shù)據(jù)庫(kù)

2022-11-18 14:34:28

2023-11-10 14:58:03

2023-04-14 07:34:19

2022-07-11 07:36:36

緩存緩存雪崩緩存擊穿

2020-10-13 07:44:40

緩存雪崩 穿透

2023-12-06 13:38:00

Redis緩存穿透緩存擊穿

2020-03-05 09:09:18

緩存原因方案

2020-10-23 10:46:03

緩存雪崩擊穿

2020-12-28 12:37:36

緩存擊穿穿透

2024-04-18 11:43:28

緩存數(shù)據(jù)庫(kù)Redis

2024-04-07 00:00:02

Redis雪崩緩存

2025-03-03 07:00:00

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)