2021年最新Redis面試題匯總(4)
1、Redis 實(shí)現(xiàn)分布式鎖
1)加鎖
加鎖通常使用 set 命令來(lái)實(shí)現(xiàn),偽代碼如下:
set key value PX milliseconds NX
幾個(gè)參數(shù)的意義如下:
key、value
:鍵值對(duì)
PX milliseconds
:設(shè)置鍵的過(guò)期時(shí)間為 milliseconds 毫秒。
NX
:只在鍵不存在時(shí),才對(duì)鍵進(jìn)行設(shè)置操作。SET key value NX 效果等同于 SETNX key value。
PX
、expireTime
參數(shù)則是用于解決沒有解鎖導(dǎo)致的死鎖問題。因?yàn)槿绻麤]有過(guò)期時(shí)間,萬(wàn)一程序員寫的代碼有 bug 導(dǎo)致沒有解鎖操作,則就出現(xiàn)了死鎖,因此該參數(shù)起到了一個(gè)“兜底”的作用。
NX
參數(shù)用于保證在多個(gè)線程并發(fā) set 下,只會(huì)有1個(gè)線程成功,起到了鎖的“唯一”性。
2)解鎖
解鎖需要兩步操作:
1)查詢當(dāng)前“鎖”是否還是我們持有,因?yàn)榇嬖谶^(guò)期時(shí)間,所以可能等你想解鎖的時(shí)候,“鎖”已經(jīng)到期,然后被其他線程獲取了,所以我們?cè)诮怄i前需要先判斷自己是否還持有“鎖”
2)如果“鎖”還是我們持有,則執(zhí)行解鎖操作,也就是刪除該鍵值對(duì),并返回成功;否則,直接返回失敗。
由于當(dāng)前 Redis 還沒有原子命令直接支持這兩步操作,所以當(dāng)前通常是使用 Lua 腳本來(lái)執(zhí)行解鎖操作,Redis 會(huì)保證腳本里的內(nèi)容執(zhí)行是一個(gè)原子操作。
腳本代碼如下,邏輯比較簡(jiǎn)單:
if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end
兩個(gè)參數(shù)的意義如下:
KEYS[1]:我們要解鎖的 key
ARGV[1]:我們加鎖時(shí)的 value,用于判斷當(dāng)“鎖”是否還是我們持有,如果被其他線程持有了,value 就會(huì)發(fā)生變化。
上述方法是 Redis 當(dāng)前實(shí)現(xiàn)分布式鎖的主流方法,可能會(huì)有一些小優(yōu)區(qū)別,但是核心都是這個(gè)思路??粗孟駴]啥毛病,但是真的是這個(gè)樣子嗎?讓我們繼續(xù)往下看。
2、Redis 分布式鎖過(guò)期了,還沒處理完怎么辦
為了防止死鎖,我們會(huì)給分布式鎖加一個(gè)過(guò)期時(shí)間,但是萬(wàn)一這個(gè)時(shí)間到了,我們業(yè)務(wù)邏輯還沒處理完,怎么辦?
首先,我們?cè)谠O(shè)置過(guò)期時(shí)間時(shí)要結(jié)合業(yè)務(wù)場(chǎng)景去考慮,盡量設(shè)置一個(gè)比較合理的值,就是理論上正常處理的話,在這個(gè)過(guò)期時(shí)間內(nèi)是一定能處理完畢的。
之后,我們?cè)賮?lái)考慮對(duì)這個(gè)問題進(jìn)行兜底設(shè)計(jì)。
關(guān)于這個(gè)問題,目前常見的解決方法有兩種:
- 守護(hù)線程“續(xù)命”:額外起一個(gè)線程,定期檢查線程是否還持有鎖,如果有則延長(zhǎng)過(guò)期時(shí)間。Redisson 里面就實(shí)現(xiàn)了這個(gè)方案,使用“看門狗”定期檢查(每1/3的鎖時(shí)間檢查1次),如果線程還持有鎖,則刷新過(guò)期時(shí)間。
- 超時(shí)回滾:當(dāng)我們解鎖時(shí)發(fā)現(xiàn)鎖已經(jīng)被其他線程獲取了,說(shuō)明此時(shí)我們執(zhí)行的操作已經(jīng)是“不安全”的了,此時(shí)需要進(jìn)行回滾,并返回失敗。
同時(shí),需要進(jìn)行告警,人為介入驗(yàn)證數(shù)據(jù)的正確性,然后找出超時(shí)原因,是否需要對(duì)超時(shí)時(shí)間進(jìn)行優(yōu)化等等。
3、守護(hù)線程續(xù)命的方案有什么問題嗎
Redisson 使用看門狗(守護(hù)線程)“續(xù)命”的方案在大多數(shù)場(chǎng)景下是挺不錯(cuò)的,也被廣泛應(yīng)用于生產(chǎn)環(huán)境,但是在極端情況下還是會(huì)存在問題。
問題例子如下:
- 線程1首先獲取鎖成功,將鍵值對(duì)寫入 redis 的 master 節(jié)點(diǎn)
- 在 redis 將該鍵值對(duì)同步到 slave 節(jié)點(diǎn)之前,master 發(fā)生了故障
- redis 觸發(fā)故障轉(zhuǎn)移,其中一個(gè) slave 升級(jí)為新的 master此時(shí)新的 master
- 并不包含線程1寫入的鍵值對(duì),因此線程2嘗試獲取鎖也可以成功拿到鎖
- 此時(shí)相當(dāng)于有兩個(gè)線程獲取到了鎖,可能會(huì)導(dǎo)致各種預(yù)期之外的情況發(fā)生,例如最常見的臟數(shù)據(jù)
解決方法:上述問題的根本原因主要是由于 redis 異步復(fù)制帶來(lái)的數(shù)據(jù)不一致問題導(dǎo)致的,因此解決的方向就是保證數(shù)據(jù)的一致。
當(dāng)前比較主流的解法和思路有兩種:
1)Redis 作者提出的 RedLock;
2)Zookeeper 實(shí)現(xiàn)的分布式鎖。
4、RedLock
首先,該方案也是基于文章開頭的那個(gè)方案(set加鎖、lua腳本解鎖)進(jìn)行改良的,所以 antirez 只描述了差異的地方,大致方案如下。
假設(shè)我們有 N 個(gè) Redis 主節(jié)點(diǎn),例如 N = 5,這些節(jié)點(diǎn)是完全獨(dú)立的,我們不使用復(fù)制或任何其他隱式協(xié)調(diào)系統(tǒng),為了取到鎖,客戶端應(yīng)該執(zhí)行以下操作:
- 獲取當(dāng)前時(shí)間,以毫秒為單位。
- 依次嘗試從5個(gè)實(shí)例,使用相同的 key 和隨機(jī)值(例如UUID)獲取鎖。當(dāng)向Redis 請(qǐng)求獲取鎖時(shí),客戶端應(yīng)該設(shè)置一個(gè)超時(shí)時(shí)間,這個(gè)超時(shí)時(shí)間應(yīng)該小于鎖的失效時(shí)間。例如你的鎖自動(dòng)失效時(shí)間為10秒,則超時(shí)時(shí)間應(yīng)該在 5-50 毫秒之間。這樣可以防止客戶端在試圖與一個(gè)宕機(jī)的 Redis 節(jié)點(diǎn)對(duì)話時(shí)長(zhǎng)時(shí)間處于阻塞狀態(tài)。如果一個(gè)實(shí)例不可用,客戶端應(yīng)該盡快嘗試去另外一個(gè)Redis實(shí)例請(qǐng)求獲取鎖。
- 客戶端通過(guò)當(dāng)前時(shí)間減去步驟1記錄的時(shí)間來(lái)計(jì)算獲取鎖使用的時(shí)間。當(dāng)且僅當(dāng)從大多數(shù)(N/2+1,這里是3個(gè)節(jié)點(diǎn))的Redis節(jié)點(diǎn)都取到鎖,并且獲取鎖使用的時(shí)間小于鎖失效時(shí)間時(shí),鎖才算獲取成功。
- 如果取到了鎖,其真正有效時(shí)間等于初始有效時(shí)間減去獲取鎖所使用的時(shí)間(步驟3計(jì)算的結(jié)果)。
- 如果由于某些原因未能獲得鎖(無(wú)法在至少N/2+1個(gè)Redis實(shí)例獲取鎖、或獲取鎖的時(shí)間超過(guò)了有效時(shí)間),客戶端應(yīng)該在所有的Redis實(shí)例上進(jìn)行解鎖(即便某些Redis實(shí)例根本就沒有加鎖成功,防止某些節(jié)點(diǎn)獲取到鎖但是客戶端沒有得到響應(yīng)而導(dǎo)致接下來(lái)的一段時(shí)間不能被重新獲取鎖)。
可以看出,該方案為了解決數(shù)據(jù)不一致的問題,直接舍棄了異步復(fù)制,只使用 master 節(jié)點(diǎn),同時(shí)由于舍棄了 slave,為了保證可用性,引入了 N 個(gè)節(jié)點(diǎn),官方建議是 5。
該方案看著挺美好的,但是實(shí)際上我所了解到的在實(shí)際生產(chǎn)上應(yīng)用的不多,主要有兩個(gè)原因:1)該方案的成本似乎有點(diǎn)高,需要使用5個(gè)實(shí)例;2)該方案一樣存在問題。
該方案主要存以下問題:
- 嚴(yán)重依賴系統(tǒng)時(shí)鐘。如果線程1從3個(gè)實(shí)例獲取到了鎖,但是這3個(gè)實(shí)例中的某個(gè)實(shí)例的系統(tǒng)時(shí)間走的稍微快一點(diǎn),則它持有的鎖會(huì)提前過(guò)期被釋放,當(dāng)他釋放后,此時(shí)又有3個(gè)實(shí)例是空閑的,則線程2也可以獲取到鎖,則可能出現(xiàn)兩個(gè)線程同時(shí)持有鎖了。
- 如果線程1從3個(gè)實(shí)例獲取到了鎖,但是萬(wàn)一其中有1臺(tái)重啟了,則此時(shí)又有3個(gè)實(shí)例是空閑的,則線程2也可以獲取到鎖,此時(shí)又出現(xiàn)兩個(gè)線程同時(shí)持有鎖了。
針對(duì)以上問題其實(shí)后續(xù)也有人給出一些相應(yīng)的解法,但是整體上來(lái)看還是不夠完美,所以目前實(shí)際應(yīng)用得不是那么多。
5、使用緩存時(shí),先操作數(shù)據(jù)庫(kù) or 先操作緩存
1)先操作數(shù)據(jù)庫(kù)
案例如下,有兩個(gè)并發(fā)的請(qǐng)求,一個(gè)寫請(qǐng)求,一個(gè)讀請(qǐng)求,流程如下:
可能存在的臟數(shù)據(jù)時(shí)間范圍:更新數(shù)據(jù)庫(kù)后,失效緩存前。這個(gè)時(shí)間范圍很小,通常不會(huì)超過(guò)幾毫秒。
2)先操作緩存
案例如下,有兩個(gè)并發(fā)的請(qǐng)求,一個(gè)寫請(qǐng)求,一個(gè)讀請(qǐng)求,流程如下:
可能存在的臟數(shù)據(jù)時(shí)間范圍:更新數(shù)據(jù)庫(kù)后,下一次對(duì)該數(shù)據(jù)的更新前。這個(gè)時(shí)間范圍不確定性很大,情況如下:
- 如果下一次對(duì)該數(shù)據(jù)的更新馬上就到來(lái),那么會(huì)失效緩存,臟數(shù)據(jù)的時(shí)間就很短。
- 如果下一次對(duì)該數(shù)據(jù)的更新要很久才到來(lái),那這期間緩存保存的一直是臟數(shù)據(jù),時(shí)間范圍很長(zhǎng)。
結(jié)論:通過(guò)上述案例可以看出,先操作數(shù)據(jù)庫(kù)和先操作緩存都會(huì)存在臟數(shù)據(jù)的情況。但是相比之下,先操作數(shù)據(jù)庫(kù),再操作緩存是更優(yōu)的方式,即使在并發(fā)極端情況下,也只會(huì)出現(xiàn)很小量的臟數(shù)據(jù)。
6、為什么是讓緩存失效,而不是更新緩存
1)更新緩存
案例如下,有兩個(gè)并發(fā)的寫請(qǐng)求,流程如下:
分析:數(shù)據(jù)庫(kù)中的數(shù)據(jù)是請(qǐng)求B的,緩存中的數(shù)據(jù)是請(qǐng)求A的,數(shù)據(jù)庫(kù)和緩存存在數(shù)據(jù)不一致。
2)失效(刪除)緩存
案例如下,有兩個(gè)并發(fā)的寫請(qǐng)求,流程如下:
分析:由于是刪除緩存,所以不存在數(shù)據(jù)不一致的情況。
結(jié)論:通過(guò)上述案例,可以很明顯的看出,失效緩存是更優(yōu)的方式。
7、如何保證數(shù)據(jù)庫(kù)和緩存的數(shù)據(jù)一致性
在上文的案例中,無(wú)論是先操作數(shù)據(jù)庫(kù),還是先操作緩存,都會(huì)存在臟數(shù)據(jù)的情況,有辦法避免嗎?
答案是有的,由于數(shù)據(jù)庫(kù)和緩存是兩個(gè)不同的數(shù)據(jù)源,要保證其數(shù)據(jù)一致性,其實(shí)就是典型的分布式事務(wù)場(chǎng)景,可以引入分布式事務(wù)來(lái)解決,常見的有:2PC、TCC、MQ事務(wù)消息等。
但是引入分布式事務(wù)必然會(huì)帶來(lái)性能上的影響,這與我們當(dāng)初引入緩存來(lái)提升性能的目的是相違背的。
所以在實(shí)際使用中,通常不會(huì)去保證緩存和數(shù)據(jù)庫(kù)的強(qiáng)一致性,而是做出一定的犧牲,保證兩者數(shù)據(jù)的最終一致性。
如果是實(shí)在無(wú)法接受臟數(shù)據(jù)的場(chǎng)景,則比較合理的方式是放棄使用緩存,直接走數(shù)據(jù)庫(kù)。
保證數(shù)據(jù)庫(kù)和緩存數(shù)據(jù)最終一致性的常用方案如下:
1)更新數(shù)據(jù)庫(kù),數(shù)據(jù)庫(kù)產(chǎn)生 binlog。
2)監(jiān)聽和消費(fèi) binlog,執(zhí)行失效緩存操作。
3)如果步驟2失效緩存失敗,則引入重試機(jī)制,將失敗的數(shù)據(jù)通過(guò)MQ方式進(jìn)行重試,同時(shí)考慮是否需要引入冪等機(jī)制。
兜底:當(dāng)出現(xiàn)未知的問題時(shí),及時(shí)告警通知,人為介入處理。
人為介入是終極大法,那些外表看著光鮮艷麗的應(yīng)用,其背后大多有一群苦逼的程序員,在不斷的修復(fù)各種臟數(shù)據(jù)和bug。
8、緩存穿透
描述:訪問一個(gè)緩存和數(shù)據(jù)庫(kù)都不存在的 key,此時(shí)會(huì)直接打到數(shù)據(jù)庫(kù)上,并且查不到數(shù)據(jù),沒法寫緩存,所以下一次同樣會(huì)打到數(shù)據(jù)庫(kù)上。
此時(shí),緩存起不到作用,請(qǐng)求每次都會(huì)走到數(shù)據(jù)庫(kù),流量大時(shí)數(shù)據(jù)庫(kù)可能會(huì)被打掛。此時(shí)緩存就好像被“穿透”了一樣,起不到任何作用。
解決方案:
1)接口校驗(yàn)。在正常業(yè)務(wù)流程中可能會(huì)存在少量訪問不存在 key 的情況,但是一般不會(huì)出現(xiàn)大量的情況,所以這種場(chǎng)景最大的可能性是遭受了非法攻擊??梢栽谧钔鈱酉茸鲆粚有r?yàn):用戶鑒權(quán)、數(shù)據(jù)合法性校驗(yàn)等,例如商品查詢中,商品的ID是正整數(shù),則可以直接對(duì)非正整數(shù)直接過(guò)濾等等。
2)緩存空值。當(dāng)訪問緩存和DB都沒有查詢到值時(shí),可以將空值寫進(jìn)緩存,但是設(shè)置較短的過(guò)期時(shí)間,該時(shí)間需要根據(jù)產(chǎn)品業(yè)務(wù)特性來(lái)設(shè)置。
3)布隆過(guò)濾器。使用布隆過(guò)濾器存儲(chǔ)所有可能訪問的 key,不存在的 key 直接被過(guò)濾,存在的 key 則再進(jìn)一步查詢緩存和數(shù)據(jù)庫(kù)。
9、布隆過(guò)濾器
布隆過(guò)濾器的特點(diǎn)是判斷不存在的,則一定不存在;判斷存在的,大概率存在,但也有小概率不存在。并且這個(gè)概率是可控的,我們可以讓這個(gè)概率變小或者變高,取決于用戶本身的需求。
布隆過(guò)濾器由一個(gè) bitSet 和 一組 Hash 函數(shù)(算法)組成,是一種空間效率極高的概率型算法和數(shù)據(jù)結(jié)構(gòu),主要用來(lái)判斷一個(gè)元素是否在集合中存在。
在初始化時(shí),bitSet 的每一位被初始化為0,同時(shí)會(huì)定義 Hash 函數(shù),例如有3組 Hash 函數(shù):hash1、hash2、hash3。
寫入流程
當(dāng)我們要寫入一個(gè)值時(shí),過(guò)程如下,以“jionghui”為例:
1)首先將“jionghui”跟3組 Hash 函數(shù)分別計(jì)算,得到 bitSet 的下標(biāo)為:1、7、10。
2)將 bitSet 的這3個(gè)下標(biāo)標(biāo)記為1。
假設(shè)我們還有另外兩個(gè)值:java 和 diaosi,按上面的流程跟 3組 Hash 函數(shù)分別計(jì)算,結(jié)果如下:
java:Hash 函數(shù)計(jì)算 bitSet 下標(biāo)為:1、7、11
diaosi:Hash 函數(shù)計(jì)算 bitSet 下標(biāo)為:4、10、11
查詢流程
當(dāng)我們要查詢一個(gè)值時(shí),過(guò)程如下,同樣以“jionghui”為例::
1)首先將“jionghui”跟3組 Hash 函數(shù)分別計(jì)算,得到 bitSet 的下標(biāo)為:1、7、10。
2)查看 bitSet 的這3個(gè)下標(biāo)是否都為1,如果這3個(gè)下標(biāo)不都為1,則說(shuō)明該值必然不存在,如果這3個(gè)下標(biāo)都為1,則只能說(shuō)明可能存在,并不能說(shuō)明一定存在。
其實(shí)上圖的例子已經(jīng)說(shuō)明了這個(gè)問題了,當(dāng)我們只有值“jionghui”和“diaosi”時(shí),bitSet 下標(biāo)為1的有:1、4、7、10、11。
當(dāng)我們又加入值“java”時(shí),bitSet 下標(biāo)為1的還是這5個(gè),所以當(dāng) bitSet 下標(biāo)為1的為:1、4、7、10、11 時(shí),我們無(wú)法判斷值“java”存不存在。
其根本原因是,不同的值在跟 Hash 函數(shù)計(jì)算后,可能會(huì)得到相同的下標(biāo),所以某個(gè)值的標(biāo)記位,可能會(huì)被其他值給標(biāo)上了。
這也是為啥布隆過(guò)濾器只能判斷某個(gè)值可能存在,無(wú)法判斷必然存在的原因。但是反過(guò)來(lái),如果該值根據(jù) Hash 函數(shù)計(jì)算的標(biāo)記位沒有全部都為1,那么則說(shuō)明必然不存在,這個(gè)是肯定的。
降低這種誤判率的思路也比較簡(jiǎn)單:
- 一個(gè)是加大 bitSet 的長(zhǎng)度,這樣不同的值出現(xiàn)“沖突”的概率就降低了,從而誤判率也降低。
- 提升 Hash 函數(shù)的個(gè)數(shù),Hash 函數(shù)越多,每個(gè)值對(duì)應(yīng)的 bit 越多,從而誤判率也降低。
布隆過(guò)濾器的誤判率還有專門的推導(dǎo)公式,有興趣的可以去搜相關(guān)的文章和論文查看。
10、緩存擊穿
描述:某一個(gè)熱點(diǎn) key,在緩存過(guò)期的一瞬間,同時(shí)有大量的請(qǐng)求打進(jìn)來(lái),由于此時(shí)緩存過(guò)期了,所以請(qǐng)求最終都會(huì)走到數(shù)據(jù)庫(kù),造成瞬時(shí)數(shù)據(jù)庫(kù)請(qǐng)求量大、壓力驟增,甚至可能打垮數(shù)據(jù)庫(kù)。
解決方案:
1)加互斥鎖。在并發(fā)的多個(gè)請(qǐng)求中,只有第一個(gè)請(qǐng)求線程能拿到鎖并執(zhí)行數(shù)據(jù)庫(kù)查詢操作,其他的線程拿不到鎖就阻塞等著,等到第一個(gè)線程將數(shù)據(jù)寫入緩存后,直接走緩存。
關(guān)于互斥鎖的選擇,網(wǎng)上看到的大部分文章都是選擇 Redis 分布式鎖(可以參考我之前的文章:面試必問的分布式鎖,你懂了嗎?),因?yàn)檫@個(gè)可以保證只有一個(gè)請(qǐng)求會(huì)走到數(shù)據(jù)庫(kù),這是一種思路。
但是其實(shí)仔細(xì)想想的話,這邊其實(shí)沒有必要保證只有一個(gè)請(qǐng)求走到數(shù)據(jù)庫(kù),只要保證走到數(shù)據(jù)庫(kù)的請(qǐng)求能大大降低即可,所以還有另一個(gè)思路是 JVM 鎖。
JVM 鎖保證了在單臺(tái)服務(wù)器上只有一個(gè)請(qǐng)求走到數(shù)據(jù)庫(kù),通常來(lái)說(shuō)已經(jīng)足夠保證數(shù)據(jù)庫(kù)的壓力大大降低,同時(shí)在性能上比分布式鎖更好。
需要注意的是,無(wú)論是使用“分布式鎖”,還是“JVM 鎖”,加鎖時(shí)要按 key 維度去加鎖。
我看網(wǎng)上很多文章都是使用一個(gè)“固定的 key”加鎖,這樣會(huì)導(dǎo)致不同的 key 之間也會(huì)互相阻塞,造成性能嚴(yán)重?fù)p耗。
使用 redis 分布式鎖的偽代碼,僅供參考:
public Object getData(String key) throws InterruptedException { Object value = redis.get(key); // 緩存值過(guò)期 if (value == null) { // lockRedis:專門用于加鎖的redis; // "empty":加鎖的值隨便設(shè)置都可以 if (lockRedis.set(key, "empty", "PX", lockExpire, "NX")) { try { // 查詢數(shù)據(jù)庫(kù),并寫到緩存,讓其他線程可以直接走緩存 value = getDataFromDb(key); redis.set(key, value, "PX", expire); } catch (Exception e) { // 異常處理 } finally { // 釋放鎖 lockRedis.delete(key); } } else { // sleep50ms后,進(jìn)行重試 Thread.sleep(50); return getData(key); } } return value; }
2)熱點(diǎn)數(shù)據(jù)不過(guò)期。直接將緩存設(shè)置為不過(guò)期,然后由定時(shí)任務(wù)去異步加載數(shù)據(jù),更新緩存。
這種方式適用于比較極端的場(chǎng)景,例如流量特別特別大的場(chǎng)景,使用時(shí)需要考慮業(yè)務(wù)能接受數(shù)據(jù)不一致的時(shí)間,還有就是異常情況的處理,不要到時(shí)候緩存刷新不上,一直是臟數(shù)據(jù),那就涼了。
11、緩存雪崩
描述:大量的熱點(diǎn) key 設(shè)置了相同的過(guò)期時(shí)間,導(dǎo)在緩存在同一時(shí)刻全部失效,造成瞬時(shí)數(shù)據(jù)庫(kù)請(qǐng)求量大、壓力驟增,引起雪崩,甚至導(dǎo)致數(shù)據(jù)庫(kù)被打掛。
緩存雪崩其實(shí)有點(diǎn)像“升級(jí)版的緩存擊穿”,緩存擊穿是一個(gè)熱點(diǎn) key,緩存雪崩是一組熱點(diǎn) key。
解決方案:
1)過(guò)期時(shí)間打散。既然是大量緩存集中失效,那最容易想到就是讓他們不集中生效??梢越o緩存的過(guò)期時(shí)間時(shí)加上一個(gè)隨機(jī)值時(shí)間,使得每個(gè) key 的過(guò)期時(shí)間分布開來(lái),不會(huì)集中在同一時(shí)刻失效。
2)熱點(diǎn)數(shù)據(jù)不過(guò)期。該方式和緩存擊穿一樣,也是要著重考慮刷新的時(shí)間間隔和數(shù)據(jù)異常如何處理的情況。
3)加互斥鎖。該方式和緩存擊穿一樣,按 key 維度加鎖,對(duì)于同一個(gè) key,只允許一個(gè)線程去計(jì)算,其他線程原地阻塞等待第一個(gè)線程的計(jì)算結(jié)果,然后直接走緩存即可。
總結(jié)
本篇文章就到這里了,希望能給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
SWT(JFace) 體驗(yàn)之FontRegistry
測(cè)試代碼如下:2009-06-06Spring集成Mybatis過(guò)程詳細(xì)講解
mybatis-plus是一個(gè)Mybatis的增強(qiáng)工具,在Mybatis的基礎(chǔ)上只做增強(qiáng)不做改變,為簡(jiǎn)化開發(fā)、提高效率而生,下面這篇文章主要給大家介紹了關(guān)于SpringBoot整合Mybatis-plus案例及用法實(shí)例的相關(guān)資料,需要的朋友可以參考下2023-03-03Java?數(shù)據(jù)結(jié)構(gòu)與算法系列精講之?dāng)?shù)組
數(shù)組是有序的元素序列,若將有限個(gè)類型相同的變量的集合命名,那么這個(gè)名稱為數(shù)組名。組成數(shù)組的各個(gè)變量稱為數(shù)組的分量,也稱為數(shù)組的元素,有時(shí)也稱為下標(biāo)變量。數(shù)組是在程序設(shè)計(jì)中,為了處理方便, 把具有相同類型的若干元素按有序的形式組織起來(lái)的一種形式2022-02-02java初學(xué)者如何讓編程學(xué)習(xí)起來(lái)更簡(jiǎn)單
我們給大家?guī)?lái)一篇關(guān)于java初學(xué)者如何讓編程學(xué)習(xí)起來(lái)更簡(jiǎn)單的基礎(chǔ)性文章,有需要的朋友們可以學(xué)習(xí)下。2020-11-11JAVA技術(shù)實(shí)現(xiàn)上傳下載文件到FTP服務(wù)器(完整)
這篇文章主要介紹了JAVA技術(shù)實(shí)現(xiàn)上傳下載文件到FTP服務(wù)器(完整),本文使用 Apache Jakarta Commons Net(commons-net-3.3.jar) 基于FileZilla Server服務(wù)器實(shí)現(xiàn)FTP服務(wù)器上文件的上傳/下載/刪除等操作,需要的朋友可以參考下2015-07-07Java switch case數(shù)據(jù)類型原理解析
這篇文章主要介紹了Java switch case數(shù)據(jù)類型原理解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-01-01