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