面試官心理分析
如果你連這個(gè)問(wèn)題都不知道,上來(lái)就懵了滋尉,回答不出來(lái)玉控,那線上你寫(xiě)代碼的時(shí)候,想當(dāng)然的認(rèn)為寫(xiě)進(jìn)redis的數(shù)據(jù)就一定會(huì)存在狮惜,后面導(dǎo)致系統(tǒng)各種 bug高诺,誰(shuí)來(lái)負(fù)責(zé)?
常見(jiàn)的問(wèn)題:
往redis寫(xiě)入的數(shù)據(jù)怎么沒(méi)了碾篡?
可能有同學(xué)會(huì)遇到虱而,在生產(chǎn)環(huán)境的redis經(jīng)常會(huì)丟掉一些數(shù)據(jù),寫(xiě)進(jìn)去了开泽,過(guò)一會(huì)兒可能就沒(méi)了牡拇。我的天,同學(xué),你問(wèn)這個(gè)問(wèn)題就說(shuō)明 redis 你就沒(méi)用對(duì)啊惠呼。redis 是緩存导俘,你給當(dāng)存儲(chǔ)了是吧?
啥叫緩存剔蹋?用內(nèi)存當(dāng)緩存旅薄。內(nèi)存是無(wú)限的嗎,內(nèi)存是很寶貴而且是有限的泣崩,磁盤(pán)是廉價(jià)而且是大量的少梁。可能一臺(tái)機(jī)器就幾十個(gè)G的內(nèi)存矫付,但是可以有幾個(gè) T 的硬盤(pán)空間猎莲。redis 主要是基于內(nèi)存來(lái)進(jìn)行高性能、高并發(fā)的讀寫(xiě)操作的技即。
那既然內(nèi)存是有限的绪杏,比如redis就只能用 10G,你要是往里面寫(xiě)了 20G 的數(shù)據(jù)酬蹋,會(huì)咋辦糖声?當(dāng)然會(huì)干掉10G 的數(shù)據(jù),然后就保留 10G 的數(shù)據(jù)了葵陵。那干掉哪些數(shù)據(jù)液荸?保留哪些數(shù)據(jù)?當(dāng)然是干掉不常用的數(shù)據(jù)脱篙,保留常用的數(shù)據(jù)了娇钱。數(shù)據(jù)明明過(guò)期了,怎么還占用著內(nèi)存绊困?這是由redis的過(guò)期策略來(lái)決定文搂。
面試題剖析
redis 過(guò)期策略?
redis過(guò)期策略是:定期刪除+惰性刪除秤朗。
所謂定期刪除煤蹭,指的是redis默認(rèn)是每隔 100ms 就隨機(jī)抽取一些設(shè)置了過(guò)期時(shí)間的 key,檢查其是否過(guò)期取视,如果過(guò)期就刪除硝皂。
假設(shè)redis里放了 10w 個(gè) key,都設(shè)置了過(guò)期時(shí)間作谭,你每隔幾百毫秒稽物,就檢查 10w 個(gè) key,那 redis 基本上就死了折欠,cpu 負(fù)載會(huì)很高的贝或,消耗在你的檢查過(guò)期 key 上了吼过。注意,這里可不是每隔 100ms 就遍歷所有的設(shè)置過(guò)期時(shí)間的 key傀缩,那樣就是一場(chǎng)性能上的災(zāi)難那先。實(shí)際上 redis 是每隔 100ms 隨機(jī)抽取一些key 來(lái)檢查和刪除的。
但是問(wèn)題是赡艰,定期刪除可能會(huì)導(dǎo)致很多過(guò)期key到了時(shí)間并沒(méi)有被刪除掉售淡,那咋整呢?所以就是惰性刪除了慷垮。這就是說(shuō)揖闸,在你獲取某個(gè) key 的時(shí)候,redis 會(huì)檢查一下 料身,這個(gè) key 如果設(shè)置了過(guò)期時(shí)間那么是否過(guò)期了汤纸?如果過(guò)期了此時(shí)就會(huì)刪除,不會(huì)給你返回任何東西芹血。
獲取key的時(shí)候贮泞,如果此時(shí) key 已經(jīng)過(guò)期,就刪除幔烛,不會(huì)返回任何東西啃擦。
但是實(shí)際上這還是有問(wèn)題的,如果定期刪除漏掉了很多過(guò)期key饿悬,然后你也沒(méi)及時(shí)去查令蛉,也就沒(méi)走惰性刪除,此時(shí)會(huì)怎么樣狡恬?如果大量過(guò)期 key 堆積在內(nèi)存里珠叔,導(dǎo)致 redis 內(nèi)存快耗盡了,咋整弟劲?
答案是:走內(nèi)存淘汰機(jī)制祷安。
內(nèi)存淘汰機(jī)制
redis內(nèi)存淘汰機(jī)制有以下幾個(gè):
· noeviction:當(dāng)內(nèi)存不足以容納新寫(xiě)入數(shù)據(jù)時(shí),新寫(xiě)入操作會(huì)報(bào)錯(cuò)函卒,這個(gè)一般沒(méi)人用吧辆憔,實(shí)在是太惡心了。
· allkeys-lru:當(dāng)內(nèi)存不足以容納新寫(xiě)入數(shù)據(jù)時(shí)报嵌,在鍵空間中,移除最近最少使用的 key(這個(gè)是最常用的)熊榛。
· allkeys-random:當(dāng)內(nèi)存不足以容納新寫(xiě)入數(shù)據(jù)時(shí)锚国,在鍵空間中,隨機(jī)移除某個(gè) key玄坦,這個(gè)一般沒(méi)人用吧血筑,為啥要隨機(jī)绘沉,肯定是把最近最少使用的key給干掉啊。
· volatile-lru:當(dāng)內(nèi)存不足以容納新寫(xiě)入數(shù)據(jù)時(shí)豺总,在設(shè)置了過(guò)期時(shí)間的鍵空間中车伞,移除最近最少使用的key(這個(gè)一般不太合適)。
· volatile-random:當(dāng)內(nèi)存不足以容納新寫(xiě)入數(shù)據(jù)時(shí)喻喳,在設(shè)置了過(guò)期時(shí)間的鍵空間中另玖,隨機(jī)移除某個(gè)key。
· volatile-ttl:當(dāng)內(nèi)存不足以容納新寫(xiě)入數(shù)據(jù)時(shí)表伦,在設(shè)置了過(guò)期時(shí)間的鍵空間中谦去,有更早過(guò)期時(shí)間的key優(yōu)先移除。
?手寫(xiě)一個(gè)LRU 算法
你可以現(xiàn)場(chǎng)手寫(xiě)最原始的LRU算法蹦哼,那個(gè)代碼量太大了鳄哭,似乎不太現(xiàn)實(shí)。
不求自己純手工從底層開(kāi)始打造出自己的LRU纲熏,但是起碼要知道如何利用已有的 JDK 數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)一個(gè)Java版的 LRU妆丘。
class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int CACHE_SIZE;
/** * 傳遞進(jìn)來(lái)最多能緩存多少數(shù)據(jù) * * @param cacheSize 緩存大小 */
public LRUCache(int cacheSize) {
// true 表示讓 linkedHashMap 按照訪問(wèn)順序來(lái)進(jìn)行排序,最近訪問(wèn)的放在頭部局劲,最老訪問(wèn)的放
在尾部勺拣。
super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);
CACHE_SIZE = cacheSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
// 當(dāng) map 中的數(shù)據(jù)量大于指定的緩存?zhèn)€數(shù)的時(shí)候,就自動(dòng)刪除最老的數(shù)據(jù)容握。
return size() > CACHE_SIZE;
}
}