這篇文章為大家總結分享一些Redis高頻面試題,有一定的參考價值,有需要的朋友可以參考一下,希望對大家有幫助。
從面試官的角度分析,出這題的目的是為了檢視你對快取的認知水平,以及結合快取處理業務、改善架構的能力。這題很明顯是讓你自由發揮,給了你引領面試官往自己最熟悉的知識點引導的機會,所以要盡可能的把握這次機會,給面試官一個好的印象。這題聊得好,就是能深入交流個把小時了,如果是一面基本上能輕輕鬆鬆拿下。 【相關推薦:Redis影片教學】
但是千萬不要上來就把話題聊死了,聊太淺了, 那基本上就是回去等通知了……
例如以下這種回答方式:
#很多人會說這麼回答也沒錯!沒錯是沒錯,但總有一種給你機會不中用的感覺。
此時此刻面試官內心想發大致是這樣的:
如果不想硬抗下面試官的降龍十八掌,就應該主動挑起面試官的興趣,並且把自己的格局(水平廣度和深度)率先提升起來,將自己會的東西盡可能的多講一些出來。
例如以下這種回答方式:
這個我的理解大致上是這樣的面試官! !
高效能:高效能一個很大的標準,就是反應時間快。 Redis基於記憶體存儲,CPU存取速度快,此外Redis對於資料結構的極致優化、內部線程模型和網路I/O模型的設計,決定了Redis是一個高效能儲存資料庫。唯一的缺點就是記憶體比較昂貴,通常資源比較有限,因此對於非常大的資料快取架構應該合理設計,我們通常不會在Redis中存放過大的數據,因為這樣會導致Redis效能下降。
高並發:高並發通常指標有回應時間(Response Time),吞吐量(Throughput),每秒查詢率QPS(Query Per Second)和並髮用戶數等,Redis雖然它是一個單一進程單線程模型,但是Redis確實高並發業務場景下的一把利器,目前Redis的QPS已經能達到10萬甚至是100萬級別了,這是絕對的高並發。
高可用:Redis高可用主要體現在主從複製、sentinel(哨兵模式)和Cluster(叢集模式)三者
這我的理解大致是這樣的面試官! !
Redis的單線程指的是執行命令操作使用單線程,Redis6.x發布之後使用多線程處理網路資料的讀寫和協議解析,Redis單線程這麼快的原因主要有這些點:
這個我的理解大致上是這樣的面試官! !
Redis有5種基本資料型別它們分別是String、List、Hash、Set、ZSet;此外還有三種特殊資料型別Bitmaps、Geospatial、HyperLogLog
######資料類型 | 簡單描述 | 使用場景 |
---|---|---|
String | string(字串)是Redis最簡單也是使用最廣泛的資料結構,它的內部是一個字元陣列。 String(字串)是動態字串,允許修改;它在結構上的實作類似於Java中的ArrayList(預設建構一個大小為10的初始陣列),這是冗餘分配記憶體的思想,也稱為預分配;這種想法可以減少擴容帶來的性能消耗。當string(字串)的大小達到擴容閾值時,將會對string(字串)進行擴容,string(字串)的擴容主要有三種情況:1.長度小於1MB,擴容後為原先的兩倍; length = length * 2 2.長度大於1MB,擴容後增加1MB; length = length 1MB 3. 字串的長度最大值為512MB | 快取、計數器、分散式鎖定等。 |
List | Redis的清單相當於Java語言中的LinkedList,它是雙向鍊錶資料結構(但這個結構設計比較巧妙,後面會介紹),支援前後順序遍歷。鍊錶結構插入與刪除操作快,時間複雜度O(1),查詢慢,時間複雜度O(n)。 Redis的list(列表)不是一個簡單。 LinkedList,而是quicklist ——“快速列表”,quicklist是多個ziplist(壓縮列表)組成的雙向列表; | 鍊錶、非同步隊列、微博關注人時間軸列表… |
Hash | Redis的hash(字典)相當於Java語言中的HashMap,它是根據雜湊值分佈的無序字典,內部的元素是透過鍵值對的方式儲存。 hash(字典)的實作與Java中的HashMap(JDK1.7)的結構也是一致的,它的資料結構也是由陣列鍊錶組成的二維結構,節點元素散列在陣列上,如果發生hash碰撞則使用鍊錶串聯在數組節點上。 Redis中的hash(字典)儲存的value只能是字串值,此外擴充與Java中的HashMap也不同。 Java中的HashMap在擴容的時候是一次性完成的,而Redis考慮到其核心存取是單執行緒的效能問題,為了追求高效能,因而採取了漸進式rehash策略。漸進式rehash指的是並非一次性完成,它是多次完成的,因此需要保留舊的hash結構,所以Redis中的hash(字典)會存在新舊兩個hash結構,在rehash結束後也就是舊hash的值全部搬遷到新hash之後,新的hash在功能上才會完全取代先前的hash。 | 使用者資訊、Hash 表… |
Set | #Redis的set(集合)相當於Java語言裡的HashSet,它內部的鍵值對是無序的、唯一的。它的內部實作了一個所有value為null的特殊字典。集合中的最後一個元素被移除之後,資料結構被自動刪除,記憶體被回收。 | 去重功能、讚、踩、共同好友… |
ZSet | zset(有序集合)是Redis中最常問的資料結構。它類似於Java語言中的SortedSet和HashMap的結合體,它一方面透過set來保證內部value值的唯一性,另一方面透過value的score(權重)來進行排序。這個排序的功能是透過Skip List(跳躍列表)來實現的。 zset(有序集合)的最後一個元素value被移除後,資料結構被自動刪除,記憶體被回收。 | 粉絲清單、學生成績排序、訪問量排行榜、點擊量排行榜… |
#Bitmaps | Bitmaps 稱為點陣圖,嚴格來說它不是一種資料型態。 Bitmaps底層就是字串(key-value)byte陣列。我們可以使用普通的get/set直接取得和設值位圖的內容,也可以透過Redis提供的點陣圖操作getbit/setbit等將byte陣列看成「位元組」來處理。 Bitmaps 的「位數組」每個單元格只能儲存0和1,數組的下標在Bitmaps中稱為偏移量。 Bitmaps設定時key不存在會自動產生一個新的字串,如果設定的偏移量超出了現有內容的範圍,就會自動將位數組進行零擴充 | 員工打卡… |
Geospatial | Geospatial是Redis在3.2版本以後增加的地理位置GEO模組 | 微信附近的人,線上點餐「附近的餐廳」… … |
HyperLogLog | HyperLogLog是用來做基數統計的演算法,它提供不精確的去重計數方案(這個不精確並不是非常不精確),標準誤差是0.81%,對於UV這種統計來說這樣的誤差範圍是被允許的。 HyperLogLog的優點在於,輸入元素的數量或體積非常大時,基數計算的儲存空間是固定的。在Redis中,每個HyperLogLog鍵只需要花費12KB內存,就可以計算接近2^64個不同的基數。但是HyperLogLog只能統計基數的大小(也就是資料集的大小,集合的個數),他不能儲存元素的本身,不能向set集合那樣儲存元素本身,也就是說無法傳回元素。 | 基數統計例如UV等 |
這個我的理解大致上是這樣的面試官! !
Redis的資料結構都可以透過EXPIRE key seconds 的方式設定key的過期時間(TTL)。我們也習慣的認為Redis的key過期時間到了,就會自動刪除,顯然這種想法並不正確。 Redis的設計考慮到性能/記憶體等綜合因素,設計了一套過期策略。
#主動刪除(惰性刪除)指的是當key被存取的時候,先校驗key是否過期,如果過期了則主動刪除。
被動刪除(定期策略)指的是Redis伺服器定時隨機的測試key的過期時間,如果過期了則被動刪除。被動刪除的存在必不可少,因為存在一些過期且永久不在存取的key,如果都依賴主動刪除,那麼它們將會永久佔用記憶體。
Redis為了確保提供高效能服務,被動刪除過期的key,採用了貪心策略/機率演算法,預設每隔10秒掃描一次,具體策略如下:
1、從過期字典(設定了過期時間的key的集合)中隨機選擇20個key,檢查其是否過期
2、刪除其中已經過期的key
此外開發在設計Redis快取架構時,一定要注意要盡可能的避免(禁止)將大量的key設定為同一過期時間,因為結合被動刪除可知,Redis被動刪除過期key時,會導致服務短暫的不可用;如果存在大量key同時過期,這會導致被動刪除key的三個步驟循環多次,從而導致Redis服務出現卡頓情況,這種情況在大型流量項目中是無法接收的。
因此為了避免這種情況出現,一定要將一些允許過期時間不需要非常精確的key,設定較為隨機的過期時間,這樣就可以將卡頓時間縮小。
這個我的理解大致上是這樣的面試官! !
在分散式場景中我們常見的分散式鎖定解決方案有(如果自己都會可以把其他兩種也在這帶出來,如果不會那就別把自己坑了呀! ):
基於資料庫鎖定機制實作的分散式鎖定
#基於Zookeeper實作的分散式鎖定
而關於Redis實現分散式鎖定的方案是這樣的。
如果Redis是在單機環境中, 我們可以透過,Redis提供的原子指令來實作分散式鎖定
set key value [EX seconds] [PX milliseconds ] [NX|XX]
為了防止A加的鎖,被B刪除了,可以加鎖時傳入客戶端加鎖標記,只有當客戶端傳入的標記和鎖標記相同時才允許解鎖,不過Redis並未提供這樣的功能,我們只能透過Lua腳本來處理,因為Lua腳本可以保證多個指令的原子性執行。最後我們還要考慮鎖的超時問題,如果客戶端一直不釋放鎖肯定也是不行的,因此鎖只能保證在指定的超時時間範圍內不被其他客戶端解鎖,超時之後就自動釋放了,這種情況很難我們可以這樣優化:
盡可能不要在Redis分散式鎖定中執行較長的任務,盡可能的縮小鎖定區間內執行程式碼,就像單一JVM鎖定中的synchronized優化一樣,我們可以考慮優化鎖的區間
#多做壓力測試和線上真實場景的模擬測試,估算一個合適的鎖定逾時時間
如果是在分散式環境中, 會增加一個新的問題,例如sentinel 一主多從環境中,可能存在客戶端在主節點上申請了鎖,但是同步未完成,主節點宕機了,此時新選舉的主節點上鎖是失效的。
對於這種情況的處理應該是這麼考慮的,首先Redis主從同步直接無論如何都無法解決資料會有遺失的情況。所以我們考慮把像一個Redis申請鎖,變成像多個單機Redis申請鎖,只有大部分申請成功就行。這個想法就是RedLock(紅鎖)。
RedLock透過使用多個Redis實例,各個實例之間沒有主從關係,相互獨立;加鎖的時候,客戶端向所有的節點發送加鎖指令,如果過半的節點set成功,就加鎖成功。釋放鎖定時,需要向所有的節點發送del指令來釋放鎖定。
紅鎖雖然解決了主從同步的問題,但是帶來新的複雜問題:
TIME = TTL - (TF- TL) - CLOCK_DIFF
採用Redis來實現分散式鎖,離不開伺服器宕機等不可用問題,這裡RedLock紅鎖也是一樣,即使是多台伺服器申請鎖,我們也要考慮伺服器宕機後的處理,官方建議採用AOF持久化處理。 但是AOF持久化只對正常SHUTDOWN這種指令能做到重啟恢復,但是如果是斷電的情況,可能導致最後一次持久化到斷電期間的鎖定資料遺失,當伺服器重新啟動後,可能會出現分散式鎖定語意錯誤的情況。所以為了規避這種情況,官方建議Redis服務重啟後,一個最大客戶端TTL時間內該Redis服務不可用(不提供申請鎖的服務),這確實可以解決問題,但是顯而易見這肯定影響Redis伺服器的效能,並且在多數節點都出現這種情況的時候,系統將出現全域不可用的狀態。這個我的理解大致上是這樣的面試官! !
Redis的非常快,很大一部分原因是因為Redis的資料儲存在記憶體中,既然在記憶體中,那麼當伺服器宕機或斷電的時候,資料就會全部遺失了,所以Redis提供了兩種機制來確保Redis資料不會因為故障而全部遺失,這種機制稱為Redis的持久化機制。
Redis的持久化機制有兩種:RDB(Redis DataBase) 指的是指定的時間間隔內將記憶體中的資料集快照寫入磁碟,RDB是記憶體快照(記憶體資料的二進位序列化形式)的方式持久化,每次都是從Redis產生一個快照進行資料的全量備份。
優點:
缺點:
#AOF(Append Only File) 是把所有對記憶體進行修改的指令(寫入操作)以獨立日誌檔案的方式進行記錄,重啟時透過執行AOF檔案中的Redis指令來復原資料。 AOF能夠解決資料持久化即時性問題,是現在Redis持久化機制中主流的持久化方案(後續會談到4.0以後的混合持久化)。
優點:
#缺點:
因此Redis透過呼叫Linux作業系統的glibc提供的fsync(int fid)來強制將指定檔案的內容從核心緩衝區刷回磁碟,以確保緩衝區中的資料不會遺失。不過這是一個IO操作,比起Redis的效能來說它是非常慢的,所以不能頻繁的執行。
Redis設定檔中有三種刷新緩衝區的設定:
appendfsync always# 每次Redis寫入操作,都寫入AOF日誌,這種配置理論上Linux作業系統扛不住,因為Redis的並發遠遠超過了Linux作業系統提供的最大刷新頻率,就算Redis寫操作比較少的情況,這種配置也是非常耗性能的,因為涉及IO操作,所以這個配置基本上不會用 appendfsync everysec #appendfsync nobgrewriteaof
auto-aof-rewrite-percentage 100:指的是當檔案的記憶體達到原先記憶體的兩倍
#auto-aof-rewrite-min-size 64mb:指的是檔案重寫的最小記憶體大小
此外Redis4.0後大部分的使用情境都不會單獨使用RDB或AOF來做持久化機制,而是兼顧二者的優勢混合使用。 最後來總結這兩者,到底用哪個比較好呢?這個我的理解大致上是這樣的面試官! !
Redis是基於記憶體儲存的key-value資料庫,我們知道記憶體雖然快但空間小,當實體記憶體達到上限時,系統就會跑的很慢,所以我們會設定Redis的最大內存,當Redis內存達到設定閾值的時候會觸發內存回收,Redis提供了很多內存淘汰策略:allkeys-lru是從所有的key中隨機採樣,volatile- lru是從所有設定了過期時間的key中隨機取樣),透過key物件中記錄的最近存取時間戳進行比較,淘汰掉這5個key中最舊的key;如果記憶體仍然不夠,就繼續重複這個步驟。
在Redis 3.0 maxmemory_samples設定為10的時候,Redis的近似LRU演算法已經非常的接近真實LRU演算法了,但是顯然maxmemory_samples設定為10比maxmemory_samples 設定為5要更加消耗CPU計算時間,因為每次採樣的樣本資料增大,計算時間也會增加。
Redis3.0的LRU比Redis2.8的LRU演算法更準確,是因為Redis3.0增加了一個與maxmemory_samples相同大小的淘汰池,每次淘汰key的時候,先與淘汰池中等待被淘汰的key進行比較,最後淘汰掉最老舊的key,其實就是被選中淘汰的key放到一起再比較一下,淘汰其中最舊的。
LRU有一個明顯的缺點,它無法正確的表示一個Key的熱度,如果一個key從未被訪問過,僅僅發生內存淘汰的前一會兒被用戶訪問了一下,在LRU算法中這會被認為是一個熱key。 LFU(Least Frequently Used)是Redis 4.0 引入的淘汰演算法,它透過key的存取頻率比較來淘汰key,重點突出的是Frequently Used。
LRU與LFU的差異:
在LFU模式下,Redis物件頭的24bit lru欄位被分成兩段來存儲,高16bit儲存ldt(Last Decrement Time),低8bit儲存logc(Logistic Counter)。高16bit用來記錄最近一次計數器降低的時間,由於只有8bit,儲存的是Unix分鐘時間戳取模2^16,16bit能表示的最大值為65535(65535/24/60≈ 45.5),大概45.5天會折返(折返指的是取模後的值重新從0開始)。
低8位元用來記錄存取頻次,8bit能表示的最大值為255,logc肯定無法記錄真實的Rediskey的存取次數,其實從名字可以看出儲存的是存取次數的對數值,每個新加入的key的logc初始值為5(LFU_INITI_VAL),這樣可以保證新加入的值不會被先選取淘汰;logc每次key被存取時都會更新;此外,logc會隨著時間衰減。
Logistic Counter不僅會成長,也會衰弱,成長和衰弱的規則也可以透過redis.conf進行設定。
這個我的理解大致上是這樣的面試官! !
快取擊穿:
就是說某個存取非常頻繁的熱點key ,處於集中式高並發存取的情況,當這個key 在失效的瞬間,大量的請求就擊穿了緩存,直接請求資料庫,直接穿過了Redis。
解決方式:
快取穿透:
指的是快取和資料庫中都不存在的資料被請求,這種情況通常是被駭客攻擊力,如果不做好防禦很容易導致資料庫被請求打死。例如駭客使用負數id查詢你的某個表,我們的id通常不會設定為負數。
解決方式:
快取雪崩:
快取雪崩發生在大量快取同時失效的情況,會導致資料庫瞬間崩潰(高並發場景),而且這種情況下如果快取不恢復,資料庫起來也沒用,還是會繼續被打崩。
解決方式:
差不多回答到這裡,面試官的臉色露出了久違的微笑,我們接下來只有接住這一招,這次面試就有了。
當然關於這個知識點不是幾句話能說清楚的,所以建議大家看看這篇文章,就可以輕鬆hold住了
《Redis分散式——主從複製、Sentinel、群集徹底吃透》
本文轉載自:https://juejin.cn/post/7019088999792934926
作者:李子捌
更多程式相關知識,請造訪:程式設計入門! !
以上是2023年Redis高頻面試題分享(附答案分析)的詳細內容。更多資訊請關注PHP中文網其他相關文章!