聊聊Redis面試題
大家好,許久不見,我是Leo。
最近抽了一部分時(shí)間在BOOS上解答了一些面試題,點(diǎn)贊量和Get 數(shù)挺多的。這篇文章就聊一些Redis的一些面試題。
什么是Redis的緩存預(yù)熱?
緩存預(yù)熱主要是雙十一流量比較大的業(yè)務(wù)場(chǎng)景。為了提升用戶的訪問性能,減少一開始缺少緩存Key到數(shù)據(jù)庫查詢數(shù)據(jù)的慢性能問題。一般會(huì)采用系統(tǒng)上線前把熱點(diǎn)緩存預(yù)熱到redis中。這樣用戶就不用一開始去數(shù)據(jù)庫查了,直接Redis內(nèi)存返給你了。
單線程性能為什么優(yōu)于多線程?
一個(gè)CPU在運(yùn)行多個(gè)線程時(shí),會(huì)存在多線程調(diào)用的消耗問題,而且還有多個(gè)線程調(diào)用時(shí)數(shù)據(jù)一致性的問題。這些都要單獨(dú)處理,單獨(dú)處理又會(huì)消耗性能。于是Redis統(tǒng)籌兼顧采用了單,多線程并用的思路。
在處理數(shù)據(jù)寫入,讀取屬于鍵值對(duì)數(shù)據(jù)操作,采用單線程操作。在請(qǐng)求連接,從socket中讀取請(qǐng)求,解析客戶端發(fā)送請(qǐng)求,采用多線程操作。
Redis巧妙的把所有需要延遲等待的操作全部轉(zhuǎn)交給了多線程處理,在不需要等待的全部單線程處理。個(gè)人感覺這種設(shè)計(jì)思路很棒
多路復(fù)用機(jī)制
IO多路復(fù)用機(jī)制是指一個(gè)線程處理多個(gè)IO流,也是我們經(jīng)常聽到的select/epoll機(jī)制。那么那些連接,等待的操作Redis都是如何處理的呢?
在Redis只運(yùn)行單線程的情況下,同一時(shí)間存在多個(gè)監(jiān)聽套接字,和已連接的套接字,內(nèi)核會(huì)一直監(jiān)聽這些連接請(qǐng)求和數(shù)據(jù)請(qǐng)求。一旦客戶端發(fā)送請(qǐng)求就會(huì)以事件的方式通知Redis主線程處理。這就是Redis線程處理多個(gè)IO流的效果。
上文說到以事件方式通知Redis這里我們做一個(gè)擴(kuò)展,select/epoll提供了基于事件的回調(diào)機(jī)制,不同的事件會(huì)調(diào)用相應(yīng)的處理函數(shù)。一旦請(qǐng)求來了,立刻加到事件隊(duì)列中,Redis單線程就會(huì)源源不斷的處理該事件隊(duì)列。解決了等待與掃描的資源浪費(fèi)問題。
Redis中如何解決緩存的一致性問題
很多人一上來就是加鎖,這樣的回答顯然不是面試官想看到的,我們可以搭一個(gè)知識(shí)樹。比如可以這樣回答。
Redis緩存分為兩種?讀寫緩存?和?只讀緩存?。不同的緩存類型會(huì)引發(fā)不同的問題。
對(duì)于讀寫緩存來說,讀壓力和寫壓力都來源于一個(gè)庫,如果對(duì)數(shù)據(jù)發(fā)生了修改。我們除了要考慮數(shù)據(jù)庫的一致性以外,還要考慮緩存中的數(shù)據(jù)一致性。
這里可以展開聊一下寫壓力的寫回策略
當(dāng)采用同步直寫策略,寫緩存時(shí),可以保證緩存與數(shù)據(jù)庫的數(shù)據(jù)一致性。 當(dāng)采用異步寫回策略,寫緩存時(shí),由于是異步執(zhí)行,無法保證命令都執(zhí)行也就是無法保證數(shù)據(jù)一致性。
上述策略如何選擇主要看業(yè)務(wù)的需求了,如果核心數(shù)據(jù),一定不能錯(cuò)的數(shù)據(jù),我們可以同步直寫再搭配事務(wù)機(jī)制。如果不是特別重要的數(shù)據(jù),那我們就可以采用異步寫回策略來提升性能。
對(duì)于只讀緩存來說,會(huì)有一個(gè)子庫,專門提供讀服務(wù),但是如果有新數(shù)據(jù)進(jìn)來,子庫必須跟著主庫進(jìn)行數(shù)據(jù)同步,來保證主從庫的數(shù)據(jù)一致性。
這里可以展開聊一下 Redis,MySQL的原子性是怎么保證的
如果先刪緩存,后更新數(shù)據(jù)庫:緩存刪除成功,數(shù)據(jù)更新失敗,導(dǎo)致用戶向緩存讀數(shù)據(jù)時(shí),沒有發(fā)現(xiàn)緩存key就會(huì)打向數(shù)據(jù)庫,而數(shù)據(jù)庫數(shù)據(jù)沒有更新成功導(dǎo)致讀到舊值 如果先更新數(shù)據(jù)庫,后刪緩存:?更新數(shù)據(jù)庫成功時(shí),緩存刪除失敗,就會(huì)導(dǎo)致數(shù)據(jù)庫保留了最新的值,用戶向Redis讀數(shù)據(jù)時(shí),發(fā)現(xiàn)緩存key存在,直接返回了就拿到了上一個(gè)舊值。
會(huì)發(fā)現(xiàn)上述都不能保證,這里為什么我還要提呢?告訴面試官你是經(jīng)過很多思考的,別搞的跟背答案似的。
解決方案
可以采用重試機(jī)制,比如用Redis的List當(dāng)作消息隊(duì)列,或者采用MQ消息隊(duì)列來處理。
不管是?先刪緩存,后更新數(shù)據(jù)庫?還是?先更新數(shù)據(jù)庫后刪緩存?一旦失敗就會(huì)出現(xiàn)兩邊不一致的情況,那我們就可以采用MQ消費(fèi)機(jī)制,只有當(dāng)兩種情況全部成功,才把MQ這條數(shù)據(jù)刪掉,要不然就重復(fù)消費(fèi)。也就是重新再執(zhí)行一遍。
這里擴(kuò)展一下,我們?cè)趫?zhí)行重復(fù)消費(fèi)的那一刻,如果有數(shù)據(jù)打過來怎么辦?
所以我們?cè)诮鉀Q時(shí),一般會(huì)給他sleep一小段時(shí)間再進(jìn)行緩存操作。這樣就會(huì)避免這種那一刻的誤差。
Redis為什么要設(shè)置隨機(jī)過期時(shí)間
Redis設(shè)置的過期時(shí)間問題,如果很多key在同一時(shí)刻大面積過期,會(huì)影響Redis的性能。
Redis是使用單線程讀寫數(shù)據(jù)的,大面積過期會(huì)阻塞Redis的處理效率。所以,在應(yīng)用Redis的key時(shí)要避免兩種情況,bigkey?和?大面積過期key
對(duì)于第二種情況,我們只要設(shè)置一個(gè)隨機(jī)過期時(shí)間就不會(huì)存在大面積過期啦。
這里還可以跟面試官聊一下Redis的過期刪除策略和過期的刪除機(jī)制。因?yàn)樯衔牡膭h除key的依據(jù)是來源于過期策略和刪除機(jī)制的。
過期刪除機(jī)制
過期刪除機(jī)制是Redis用來回收內(nèi)存空間的常用機(jī)制,可以對(duì)鍵值對(duì)設(shè)置過期時(shí)間,默認(rèn)情況下,Redis每100毫秒就會(huì)刪除一些過期的key,具體算法如下:
采樣 ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 個(gè)數(shù)的 key,并將其中過期的 key 全部刪除; 如果超過 25% 的 key 過期了,則重復(fù)刪除的過程,直到過期 key 的比例降至 25% 以下。
ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 默認(rèn)為20。毫秒的概念可能不太明顯,我們換算一下也就是每秒刪除200個(gè)過期key(20個(gè)/100毫秒)=(200個(gè)/秒)
如果按照第一種的話,并不是造成太大的影響。如果命中了第二種,就會(huì)造成key大面積失效過期,過期的key超過了25%,會(huì)一直刪除直至降至25%。這段刪除期間會(huì)大量釋放內(nèi)存空間,大量插入鏈表填補(bǔ)。Redis就變慢了。
過期刪除策略
這個(gè)應(yīng)該是比較簡(jiǎn)單的
定時(shí)刪除:采用定時(shí)器方式,時(shí)間一到就刪 惰性刪除:惰性刪除并不是當(dāng)?shù)竭_(dá)過期時(shí)間去刪除,而是每次獲取鍵值時(shí),會(huì)判斷是否過期,如果過期就刪,并返回空。沒過期就返回鍵值 定期刪除:每隔一段時(shí)間,就對(duì)數(shù)據(jù)庫中的鍵進(jìn)行檢查,如果過期則刪除。至于要?jiǎng)h除多少什么時(shí)候刪除則是通過具體程序決定的
定時(shí)刪除策略
優(yōu)點(diǎn)是:對(duì)內(nèi)存友好。因?yàn)橥ㄟ^定時(shí)器,當(dāng)一個(gè)鍵到達(dá)過期時(shí)間時(shí)就會(huì)立馬被刪除,直接就釋放了內(nèi)存。
缺點(diǎn)是:對(duì) CPU 不友好。因?yàn)槿绻^期鍵比較多,那么刪除這些過期鍵會(huì)占用相當(dāng)一部分 CPU 時(shí)間,如果 CPU 時(shí)間非常緊張的話,還將 CPU 時(shí)間用在刪除和當(dāng)前任務(wù)無關(guān)的過期鍵上,會(huì)對(duì)服務(wù)器的響應(yīng)時(shí)間以及吞吐量造成影響。
因此,通過 定時(shí)刪除 策略對(duì)過期鍵的刪除不太現(xiàn)實(shí)。
惰性刪除
優(yōu)點(diǎn):對(duì) CPU 時(shí)間友好。程序只會(huì)在取出鍵時(shí)才會(huì)判斷是否刪除,并且只作用到當(dāng)前鍵上,其他過期鍵不會(huì)花費(fèi) CPU 時(shí)間去處理。
缺點(diǎn):對(duì)內(nèi)存不友好。因?yàn)橹挥墟I被使用時(shí)才會(huì)去檢查是否刪除,如果有大量的鍵一直不被使用,那么這些鍵就算過期了也不會(huì)被刪除,會(huì)一直占用著內(nèi)存。這種可以理解為是一種內(nèi)存泄漏——大量無用的數(shù)據(jù)一直占用著內(nèi)存,并且不會(huì)被刪除。
定期刪除
相比較定時(shí)刪除對(duì) CPU 的不友好,惰性刪除的對(duì)內(nèi)存不友好。定期刪除采用了一種折中的方式:
定期刪除策略每隔一段時(shí)間執(zhí)行一次刪除過期鍵操作,并通過限制刪除操作執(zhí)行的時(shí)長(zhǎng)和頻率來減少刪除操作對(duì) CPU 時(shí)間的影響。并且,通過定期刪除過期鍵,有效的減少了過期鍵帶來的內(nèi)存浪費(fèi)。但刪除的時(shí)長(zhǎng)和頻率比較難定義,
因?yàn)椋喝绻l率太高或者時(shí)長(zhǎng)太長(zhǎng),那么會(huì)占用大量的 CPU 時(shí)長(zhǎng)。如果過短又會(huì)出現(xiàn)內(nèi)存浪費(fèi)的情況。
因此。如果采用定期刪除策略的話需要通過具體的業(yè)務(wù)場(chǎng)景來定義時(shí)長(zhǎng)和頻率。
