Redis常见面试题与答案
生活随笔
收集整理的這篇文章主要介紹了
Redis常见面试题与答案
小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
Redis的基本數(shù)據(jù)類(lèi)型
Redis有哪些常用的數(shù)據(jù)類(lèi)型?
- String:字符串(最常用的緩存)
- Hash:哈希(保存對(duì)象)
- List:有序列表(消息隊(duì)列)
- Set:無(wú)序集合(抽獎(jiǎng)、點(diǎn)贊、求交集、并集、差集)
- Zset:有序集合(熱門(mén)排行榜)
- BitMap:位圖(統(tǒng)計(jì)用戶(hù)在線狀態(tài)、活躍用戶(hù)數(shù)、簽到等)
- HyperLogLog:基數(shù)統(tǒng)計(jì)(統(tǒng)計(jì)網(wǎng)站UV)
- Geospatial:地理空間(地理位置相關(guān)查詢(xún)與計(jì)算)
String的底層結(jié)構(gòu)是什么?
- 是一種叫做sds的數(shù)據(jù)結(jié)構(gòu)(simple dynamic string 簡(jiǎn)單動(dòng)態(tài)字符串),本質(zhì)上其實(shí)還是字符串?dāng)?shù)組。
- sds又可以細(xì)分為:sdshdr5(2^5 byte)、sdshdr8(2^8 byte)、sdshdr16(2^16 byte)、sdshdr32(2^32 byte)、sdshdr64(2^64 byte),用于存儲(chǔ)不同長(zhǎng)度的字符串。
為什么Redis要用sds實(shí)現(xiàn)字符串?
- c語(yǔ)言本身沒(méi)有字符串類(lèi)型,只有字符串?dāng)?shù)組,定義字符串?dāng)?shù)組時(shí)需要先分配足夠內(nèi)存,否則會(huì)發(fā)生內(nèi)存溢出,而sds無(wú)需擔(dān)心內(nèi)存溢出問(wèn)題,它支持動(dòng)態(tài)擴(kuò)容;
- 字符串?dāng)?shù)組獲取長(zhǎng)度必須遍歷數(shù)組,時(shí)間復(fù)雜度為O(n),而sds中定義了len屬性,可以直接獲取長(zhǎng)度,時(shí)間復(fù)雜度為O(1);
- 字符串?dāng)?shù)組在進(jìn)行修改時(shí)會(huì)做內(nèi)存重新分配,而sds可以通過(guò)空間預(yù)分配和惰性空間釋放來(lái)防止多次重新分配內(nèi)存;
- 空間預(yù)分配:對(duì)字符串進(jìn)行空間擴(kuò)展的時(shí)候,擴(kuò)展的內(nèi)存比實(shí)際需要的多,這樣可以減少連續(xù)執(zhí)行字符串增長(zhǎng)操作所需的內(nèi)存重分配次數(shù)。
- 惰性空間釋放:對(duì)字符串進(jìn)行縮短操作時(shí),程序不立即使用內(nèi)存重新分配來(lái)回收縮短后多余的字節(jié),而是使用 free 屬性將這些字節(jié)的數(shù)量記錄下來(lái),等待后續(xù)使用。
- c語(yǔ)言字符串通過(guò)第一個(gè)’\0’來(lái)標(biāo)記字符串結(jié)束,因此不能保存圖片、音頻等二進(jìn)制文件,而sds是通過(guò)len屬性來(lái)確定長(zhǎng)度,因此可以保存二進(jìn)制文件。
String有幾種編碼類(lèi)型?
- int:存儲(chǔ)8個(gè)字節(jié)的長(zhǎng)整型(最大值 2^63 - 1),超出范圍會(huì)升級(jí)為embstr;
- embstr:存儲(chǔ)小于44字節(jié)的字符串,embstr中的redisObject與sds存儲(chǔ)連續(xù),創(chuàng)建時(shí)只需分配一次內(nèi)存空間,存取效率高,且embstr不可修改,修改后會(huì)升級(jí)為raw;
- raw:存儲(chǔ)大于44字節(jié)的字符串,raw中的redisObject與sds分開(kāi)存儲(chǔ),創(chuàng)建時(shí)只需分配兩次內(nèi)存空間,適合存儲(chǔ)較大或頻繁修改的字符串。
Hash的底層實(shí)現(xiàn)是什么?
- ziplist與hashtable
什么是ziplist(壓縮列表)?
- ziplist是一個(gè)經(jīng)過(guò)特殊編碼,由連續(xù)內(nèi)存塊組成的雙向鏈表。為了達(dá)到壓縮內(nèi)存,節(jié)省空間的目的,它不存儲(chǔ)上一個(gè)和下一個(gè)節(jié)點(diǎn)的指針,而是存儲(chǔ)上一個(gè)節(jié)點(diǎn)的長(zhǎng)度和當(dāng)前節(jié)點(diǎn)的長(zhǎng)度,因此可以通過(guò)計(jì)算長(zhǎng)度來(lái)實(shí)現(xiàn)元素遍歷。這樣做雖然犧牲了一定的查詢(xún)效率,但也節(jié)省了空間,(以時(shí)間換空間)因此適合元素不多的場(chǎng)景。
- 在hash類(lèi)型中,同時(shí)滿(mǎn)足以下兩個(gè)條件時(shí)才使用ziplist:
- hash對(duì)象保存的鍵值數(shù) < 512;
- 所有鍵和值的字符串長(zhǎng)度都 < 64 byte。
ziplist與數(shù)組的區(qū)別?
- ziplist和數(shù)組都是連續(xù)內(nèi)存,需要預(yù)分配,但是數(shù)組元素長(zhǎng)度相同,ziplist的元素長(zhǎng)度可以不同;
- 數(shù)組的元素保存了索引下標(biāo),而ziplist的元素只存儲(chǔ)了上一節(jié)點(diǎn)和當(dāng)前節(jié)點(diǎn)的長(zhǎng)度;
- 數(shù)組通過(guò)下標(biāo)來(lái)遍歷,ziplist通過(guò)元素記錄的長(zhǎng)度遍歷;
- 數(shù)組的查詢(xún)時(shí)間復(fù)雜度O(1),ziplist的查詢(xún)時(shí)間復(fù)雜度O(n)。
hashtable的特點(diǎn)是什么?
- hashtable與java中的hashtable類(lèi)似,也是一個(gè)數(shù)組 + 鏈表的結(jié)構(gòu),同樣采用拉鏈法解決hash沖突。
- 在redis的hashtable的底層數(shù)據(jù)結(jié)構(gòu)中有兩個(gè)ht:
- ht[0],是存放數(shù)據(jù)的table,作為非擴(kuò)容時(shí)容器;
- ht[1],只有正在進(jìn)行擴(kuò)容時(shí)才會(huì)使用,它也是存放數(shù)據(jù)的table,長(zhǎng)度為ht[0]的兩倍。
- 當(dāng)總元素個(gè)數(shù)與hash槽的比值大于dict_force_resize_ratio(1)時(shí),會(huì)觸發(fā)擴(kuò)容,即觸發(fā)ht[0] 到 ht[1] 的rehash過(guò)程。
hashtable的擴(kuò)容原理
- hashtable擴(kuò)容采用漸進(jìn)式rehash,避免數(shù)據(jù)量大時(shí),一次性rehash產(chǎn)生阻塞;
- 在rehash期間,會(huì)維護(hù)一個(gè)rehashIndex,用來(lái)表示rehash進(jìn)度,初始值為0,代表rehash進(jìn)行中;
- 每次遷移完一個(gè)槽位,rehashIndex + 1;
- 遷移過(guò)程中如果有請(qǐng)求進(jìn)來(lái),將請(qǐng)求路由到的index與rehashIndex比較,如果大于rehashIndex,代表這部分?jǐn)?shù)據(jù)還沒(méi)有遷移完成,訪問(wèn)ht[0],否則訪問(wèn)ht[1];
- 全部遷移完成后rehashIndex更新為-1。
List的底層實(shí)現(xiàn)是什么?
- List的底層實(shí)現(xiàn)是quicklist,quicklist是一個(gè)與linkedlist類(lèi)似的雙向鏈表,每個(gè)quicklist的節(jié)點(diǎn)又包含多個(gè)ziplist,這樣就結(jié)合了兩者的優(yōu)點(diǎn),既保證一定的查詢(xún)效率,又節(jié)省了一定內(nèi)存空間。
Set的底層實(shí)現(xiàn)是什么?
- set的底層實(shí)現(xiàn)為intset或hashtable,如果都是整型數(shù)字,則用intset存儲(chǔ),否則用hashtable。
Zset的底層實(shí)現(xiàn)是什么?
- 默認(rèn)使用ziplist存儲(chǔ),如果元素?cái)?shù)量大于128或任意元素長(zhǎng)度大于等于64byte,則使用skiplist + dict 存儲(chǔ)。
什么是skiplist(跳躍表)?
- 每相鄰的兩個(gè)節(jié)點(diǎn)都增加了一個(gè)指針,讓指針指向下下個(gè)節(jié)點(diǎn),這樣所有新增加的指針連成了一個(gè)新的鏈表,但包含的節(jié)點(diǎn)數(shù)只有原來(lái)的一半。這樣當(dāng)我們查找數(shù)據(jù)時(shí),先沿著新鏈表查找,類(lèi)似于二分查找的思想。
- 例如查找23的位置:
- 因?yàn)閘evel的值是隨機(jī)的,skiplist可能不只是跳一層,也可能跳兩層、三層等,如下圖:
- skiplist查找的時(shí)間復(fù)雜度為O(Log n);
- 可以在O(1)的時(shí)間復(fù)雜度下獲得skiplist的頭節(jié)點(diǎn)、尾節(jié)點(diǎn)、長(zhǎng)度和高度。
持久化
Redis有哪幾種持久化方式?
- Redis支持兩種方式的持久化,一種是RDB方式、另一種是AOF(append-only-file)方式。前者會(huì)根據(jù)指定的規(guī)則“定時(shí)”將內(nèi)存中的數(shù)據(jù)存儲(chǔ)在硬盤(pán)上,而后者在每次執(zhí)行命令后將命令本身記錄下來(lái)。兩種持久化方式可以單獨(dú)使用其中一種,也可以將這兩種方式結(jié)合使用。
AOF文件過(guò)大怎么辦?
- Redis提供了AOF文件的重寫(xiě)機(jī)制,優(yōu)化掉冗余的命令;
- 重寫(xiě)過(guò)程在AOF文件大小達(dá)到指定閾值時(shí)觸發(fā),由子進(jìn)程執(zhí)行;
- 子進(jìn)程并不是基于原有AOF文件進(jìn)行重新,而是全量遍歷當(dāng)前內(nèi)存中的數(shù)據(jù),寫(xiě)入新的AOF文件,在此期間新的寫(xiě)請(qǐng)求命令會(huì)先寫(xiě)到緩沖區(qū)aof_rewrite_buf中,最后合并到一起。
內(nèi)存回收策略
LRU和LFU的區(qū)別?
- LRU是最近最少使用頁(yè)面置換算法(Least Recently Used),也就是首先淘汰最長(zhǎng)時(shí)間未被使用的頁(yè)面(按使用時(shí)間排序);
- LFU是最近最不常用頁(yè)面置換算法(Least Frequently Used),也就是淘汰一定時(shí)期內(nèi)被訪問(wèn)次數(shù)最少的頁(yè)(按使用次數(shù)排序)。
Redis有哪些種內(nèi)存回收策略?
高可用
Redis有哪些高可用方案?
- 主從、哨兵、集群。
哨兵節(jié)點(diǎn)的最少數(shù)量是多少?
- 三個(gè),一主兩從。
哨兵Leader選舉的原理是什么?
- 哨兵的Leader選舉是基于共識(shí)算法Raft;
- Raft協(xié)議動(dòng)畫(huà)演示
- Raft
- 參與選舉的三種角色:
- Leader:負(fù)責(zé)事務(wù)請(qǐng)求的處理與數(shù)據(jù)同步;
- Follower:可以處理非事務(wù)請(qǐng)求,以及Leader選舉;
- Candidate:候選者,在Leader選舉期間,Follower會(huì)成為候選者;
- 集群總節(jié)點(diǎn)數(shù)需要滿(mǎn)足 2n + 1(n >= 1);
- Leader選舉的觸發(fā)時(shí)機(jī):
- 集群初始化;
- Leader宕機(jī);
- 成為L(zhǎng)eader的條件:
- 每個(gè)Candidate會(huì)在一個(gè)隨機(jī)時(shí)間后進(jìn)行廣播發(fā)起投票,默認(rèn)會(huì)投自己;
- 其它節(jié)點(diǎn)接收到投票請(qǐng)求后會(huì)判斷該節(jié)點(diǎn)的term(任期)是否優(yōu)于自己,如果滿(mǎn)足則會(huì)同意;
- 當(dāng)超過(guò)半數(shù)節(jié)點(diǎn)投票同意后該Candidate會(huì)成為新的Leader,同時(shí)term + 1;
- 如果平票,則在一個(gè)超時(shí)時(shí)間后重新開(kāi)啟投票流程;
- Leader的數(shù)據(jù)同步:
- Leader 基于2pc實(shí)現(xiàn)數(shù)據(jù)同步,超過(guò)半數(shù)的Follower 響應(yīng)成功后數(shù)據(jù)才算成功寫(xiě)入;
- 如何處理腦裂或網(wǎng)絡(luò)分區(qū);
- 以節(jié)點(diǎn)多的分區(qū)數(shù)據(jù)為準(zhǔn)。
哨兵模式與集群模式的區(qū)別?
- 哨兵模式是主從模式的升級(jí)版,主節(jié)點(diǎn)和從節(jié)點(diǎn)都保存了全量數(shù)據(jù),而集群模式是對(duì)整個(gè)數(shù)據(jù)集進(jìn)行了橫向的分片,每個(gè)分片節(jié)點(diǎn)只保存一部分?jǐn)?shù)據(jù)集;
- 集群模式相比哨兵模式而言部署和運(yùn)維成本更高,適合數(shù)據(jù)量更大的場(chǎng)景。
Redis集群方案不可用的情況有哪些?
- 集群master半數(shù)以上宕機(jī),無(wú)論是否有從庫(kù)存活(無(wú)法進(jìn)行選舉);
- 集群某一節(jié)點(diǎn)的主從全部宕機(jī),集群進(jìn)入fail狀態(tài)。也可以理解成進(jìn)群的slot映射[0-16383]不完整時(shí)進(jìn)入fail狀態(tài)。
緩存的經(jīng)典七大問(wèn)題
緩存失效
- 在過(guò)期時(shí)間基礎(chǔ)上加上隨機(jī)時(shí)間,避免同時(shí)失效。
緩存穿透
- 對(duì)于某些DB不存在的值,可以緩存一個(gè)特殊值,避免頻繁訪問(wèn)DB;
- 構(gòu)建BloomFilter記錄全量數(shù)據(jù),這樣可以過(guò)濾掉不存在的key。
緩存雪崩
- 對(duì)DB的訪問(wèn)增加讀寫(xiě)開(kāi)關(guān),當(dāng)發(fā)現(xiàn) DB 請(qǐng)求變慢、阻塞,慢請(qǐng)求超過(guò)閾值時(shí),就會(huì)關(guān)閉讀開(kāi)關(guān),部分或所有讀 DB 的請(qǐng)求進(jìn)行 failfast 立即返回,待 DB 恢復(fù)后再打開(kāi)讀開(kāi)關(guān)。
- 對(duì)緩存增加多個(gè)副本,緩存異?;蛘?qǐng)求 miss 后,再讀取其他緩存副本,而且多個(gè)緩存副本盡量部署在不同機(jī)架,從而確保在任何情況下,緩存系統(tǒng)都會(huì)正常對(duì)外提供服務(wù)。
- 對(duì)緩存體系進(jìn)行實(shí)時(shí)監(jiān)控,當(dāng)請(qǐng)求訪問(wèn)的慢速比超過(guò)閾值時(shí),及時(shí)報(bào)警,通過(guò)機(jī)器替換、服務(wù)替換進(jìn)行及時(shí)恢復(fù);也可以通過(guò)各種自動(dòng)故障轉(zhuǎn)移策略,自動(dòng)關(guān)閉異常接口、停止邊緣服務(wù)、停止部分非核心功能措施,確保在極端場(chǎng)景下,核心功能的正常運(yùn)行。
緩存一致性問(wèn)題
- cache 更新失敗后,可以進(jìn)行重試,如果重試失敗,則將失敗的 key 寫(xiě)入隊(duì)列機(jī)服務(wù),待緩存訪問(wèn)恢復(fù)后,將這些 key 從緩存刪除。這些 key 在再次被查詢(xún)時(shí),重新從 DB 加載,從而保證數(shù)據(jù)的一致性。
- 緩存時(shí)間適當(dāng)調(diào)短,讓緩存數(shù)據(jù)及早過(guò)期后,然后從 DB 重新加載,確保數(shù)據(jù)的最終一致性。
- 緩存的更新方案:緩存的三種讀寫(xiě)方式_學(xué)無(wú)止境-CSDN博客_只讀緩存 讀寫(xiě)緩存
數(shù)據(jù)并發(fā)競(jìng)爭(zhēng)
- 使用全局鎖。即當(dāng)緩存請(qǐng)求 miss 后,先嘗試加全局鎖,只有加全局鎖成功的線程,才可以到 DB 去加載數(shù)據(jù)。其他進(jìn)程/線程在讀取緩存數(shù)據(jù) miss 時(shí),如果發(fā)現(xiàn)這個(gè) key 有全局鎖,就進(jìn)行等待,待之前的線程將數(shù)據(jù)從 DB 回種到緩存后,再?gòu)木彺娅@取。
- 對(duì)緩存數(shù)據(jù)保持多個(gè)備份,即便其中一個(gè)備份中的數(shù)據(jù)過(guò)期或被剔除了,還可以訪問(wèn)其他備份,從而減少數(shù)據(jù)并發(fā)競(jìng)爭(zhēng)的情況。
Hot key
- 對(duì)于特殊的業(yè)務(wù)場(chǎng)景,提前分析收集熱門(mén)的key;
- 如果熱門(mén)的key的名稱(chēng)為hotkey,可以將它分散為 hotkey#1、hotkey#2、hotkey#3,……h(huán)otkey#n,這 n 個(gè) key 分散存在多個(gè)緩存節(jié)點(diǎn),然后 client 端請(qǐng)求時(shí),隨機(jī)訪問(wèn)其中某個(gè)后綴的 hotkey,這樣就可以把熱 key 的請(qǐng)求打散,避免一個(gè)緩存節(jié)點(diǎn)過(guò)載。
Big key
- 對(duì)于value超過(guò)閾值的key進(jìn)行序列化壓縮等。
分布式鎖
Redisson如何實(shí)現(xiàn)可重入鎖?
- Redisson是通過(guò)lua腳本實(shí)現(xiàn)的lock和unlock,底層使用的數(shù)據(jù)結(jié)構(gòu)是hash;
- hash的key為鎖實(shí)際的key,hash中會(huì)有一個(gè)entry,entry的key為傳入的唯一threadId,value為重入次數(shù),重入一次會(huì)+1,釋放一次會(huì)-1;
- 每次加鎖都會(huì)更新失效時(shí)間,同時(shí)還有定時(shí)任務(wù)進(jìn)行失效時(shí)間的延長(zhǎng)(看門(mén)狗);
- lock和unlock是基于Redis的Pub Sub模式實(shí)現(xiàn)鎖的等待和喚醒的,lock過(guò)程如果無(wú)法獲得鎖,當(dāng)前線程執(zhí)行subscribe(threadId),訂閱該threadId的鎖事件,當(dāng)對(duì)應(yīng)threadId的鎖完全釋放后,會(huì)執(zhí)行publish發(fā)布鎖釋放事件,這樣訂閱該事件的所有線程便可以重新競(jìng)爭(zhēng)鎖。
Redis線程模型
Redis 采用單線程為什么還這么快?
- 首先,Redis 的大部分操作都在內(nèi)存中完成,并且采用了高效的數(shù)據(jù)結(jié)構(gòu),比如哈希表和跳表;
- 其次,因?yàn)槭菃尉€程模型避免了多線程之間的競(jìng)爭(zhēng),省去了多線程切換帶來(lái)的時(shí)間和性能上的開(kāi)銷(xiāo),而且也不會(huì)導(dǎo)致死鎖問(wèn)題;
- 另外,Redis并不是完全的單線程應(yīng)用,除了核心的讀寫(xiě)操作由專(zhuān)門(mén)的線程完成之外,還有很多輔助線程在后臺(tái)執(zhí)行工作,例如:
- 主從同步;
- 持久化;
- 大key刪除:直接通過(guò)del刪除大key會(huì)導(dǎo)致Redis阻塞,例如數(shù)量較大的map、list或set等。Redis4.0引入了unlink命令,先標(biāo)記再異步刪除,避免阻塞;
- 除了unlink之外,flushall、flushdb也都是異步操作;
- 最后, Redis 采用了 I/O 多路復(fù)用機(jī)制,在Linux環(huán)境的epoll模式下,可以高效地進(jìn)行網(wǎng)絡(luò)通信,因?yàn)榛诜亲枞?I/O 模型,就意味著 I/O 的讀寫(xiě)流程不再阻塞。
總結(jié)
以上是生活随笔為你收集整理的Redis常见面试题与答案的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 程序员法律考试笔记(2)-依法治国
- 下一篇: 工业用微型计算机(17)-指令系统(12