面了6家大廠,把問爛了的Redis常見面試題(附答案解析)總結一下分享給大家。有一定的參考價值,有需要的朋友可以參考一下,希望對大家有幫助。
【相關推薦:Redis影片教學】
快取知識點
#有哪些類型在快取?
快取是高並發場景下提高熱點資料存取效能的有效手段,在開發專案時會經常使用到。
快取的類型分為:本機快取、分散式快取和多層快取。
本地快取:
本機快取就是在進程的記憶體中進行緩存,例如我們的 JVM 堆中,可以用LRUMap 來實現,也可以使用Ehcache 這樣的工具來實現。
本地快取是記憶體訪問,沒有遠端互動開銷,效能最好,但是受限於單機容量,一般快取較小且無法擴展。
分散式快取:
分散式快取可以很好地解決這個問題。
分散式快取一般都具有良好的水平擴展能力,對較大資料量的場景也能應付自如。缺點就是需要遠端請求,效能不如本地快取。
多層快取:
為了平衡這種情況,實際業務中一般採用多層快取,本地快取只保存存取頻率最高的部分熱點數據,其他的熱點數據放在分散式快取中。
在目前的一線大廠中,這也是最常用的快取方案,單考單一的快取方案往往難以撐住很多高並發的場景。
淘汰策略
不管是本地快取還是分散式緩存,為了保證較高效能,都是使用記憶體來保存數據,由於成本和記憶體限制,當儲存的資料超過快取容量時,需要對快取的資料進行剔除。
一般的剔除策略有FIFO 淘汰最早資料、LRU 剔除最近最少使用、和LFU 剔除最近使用頻率最低的資料幾種策略。
noeviction:返回錯誤當記憶體限制達到並且客戶端嘗試執行會讓更多記憶體被使用的命令(大部分的寫入指令,但DEL和幾個例外)
allkeys-lru: 嘗試回收最少使用的鍵(LRU),使得新新增的資料有空間存放。
volatile-lru: 嘗試回收最少使用的鍵(LRU),但僅限於在過期集合的鍵,使得新新增的資料有空間存放。
allkeys-random: 回收隨機的按鍵使得新加入的資料有空間存放。
volatile-random: 回收隨機的鍵使得新加入的資料有空間存放,但僅限於在過期集合的鍵。
volatile-ttl: 回收在過期集合的鍵,並且優先回收存活時間(TTL)較短的鍵,使得新添加的資料有空間存放。
如果沒有鍵滿足回收的前提條件的話,策略volatile-lru, volatile-random以及volatile-ttl就和noeviction 差不多了。
其實在大家熟悉的LinkedHashMap中也實作了Lru演算法的,實作如下:
當容量超過100時,開始執行LRU策略:將最近最少未使用的TimeoutInfoHolder 物件evict 掉。
真實面試中會讓你寫LUR演算法,你可別搞原始的那個,那真TM多,寫不完的,你要么懟上面這個,要么懟下面這個,找一個數據結構實現下Java版的LRU還是比較容易的,知道啥原理就好了。
Memcache
注意後面會把Memcache 簡稱為MC。
先來看看 MC 的特色:
另外,使用MC 有些限制,這些限制在現在的網路場景下很致命,成為大家選擇Redis、MongoDB的重要原因:
Redis
先簡單說一下 Redis 的特點,方便和 MC 比較。
詳解 Redis
#Redis 的知識點結構如下圖所示。
功能
來Redis 提供的功能有哪些吧!
String:
String 類型是 Redis 中最常使用的類型,內部的實作是透過SDS(Simple Dynamic String )來儲存的。 SDS 類似於 Java 中的 ArrayList,可以透過預先分配冗餘空間的方式來減少記憶體的頻繁分配。
這是最簡單的類型,就是普通的 set 和 get,做簡單的 KV 快取。
但是真實的開發環境中,很多仔可能會把很多比較複雜的結構也統一轉成String去儲存使用,例如有的仔他就喜歡把物件或List轉換成JSONString進行存儲,拿出來再反序列話啥的。
我在這裡就不討論這樣做的對錯了,但是我還是希望大家能在最合適的場景使用最合適的資料結構,物件找不到最合適的但是類型可以選最合適的嘛,之後別人接手你的程式碼一看這麼規範,誒這小伙子有點東西呀,看到你啥都是用的String,垃圾!
好了這些都是題外話了,道理還是希望大家記在心裡,習慣成自然嘛,小習慣成就你。
String的實際應用場景比較廣泛的有:
#快取功能:String字串是最常用的資料類型,不只是Redis,各個語言都是最基本類型,因此,利用Redis作為緩存,配合其它資料庫作為儲存層,利用Redis支援高並發的特點,可以大幅加快系統的讀寫速度、以及降低後端資料庫的壓力。
計數器:許多系統都會使用Redis作為系統的即時計數器,可以快速實現計數和查詢的功能。而且最終的資料結果可以按照特定的時間落地到資料庫或其它儲存媒體當中進行永久保存。
共享使用者Session:使用者重新刷新一次介面,可能需要存取資料重新登錄,或存取頁面快取Cookie,但是可以利用Redis將使用者的Session集中管理,在這種模式只需要保證Redis的高可用,每次使用者Session的更新和獲取都可以快速完成。大大提高效率。
Hash:
這個是類似Map 的結構,這個一般就是可以將結構化的數據,例如一個物件(前提是這個物件沒嵌套其他的物件)給緩存在Redis 裡,然後每次讀寫快取的時候,可以就操作Hash 裡的某個欄位。
但這個的場景其實還是多少單一了一些,因為現在很多物件都是比較複雜的,像是你的商品物件可能裡面就包含了很多屬性,其中也有物件。我自己使用的場景用得不是那麼多。
List:
List 是有序列表,這個還是可以玩兒出很多花樣的。
例如可以透過 List 儲存一些清單型的資料結構,類似粉絲清單、文章的評論清單之類的東西。
例如可以透過lrange 指令,讀取某個閉區間內的元素,可以基於List 實作分頁查詢,這個是很棒的功能,基於Redis 實現簡單的高效能分頁,可以做類似微博那種下拉不斷分頁的東西,效能高,就一頁一頁走。
例如可以搞個簡單的訊息佇列,從 List 頭懟進去,從 List 屁股那裡弄出來。
List本身就是我們在開發過程中比較常用的資料結構了,熱點資料更不用說了。
訊息佇列:Redis的鍊錶結構,可以輕鬆實作阻塞佇列,可以使用左進右出的指令組成來完成佇列的設計。例如:數據的生產者可以透過Lpush指令從左邊插入數據,多個數據消費者,可以使用BRpop指令阻塞的「搶」列表尾部的數據。
文章清單或資料分頁展示的應用。
例如,我們常用的部落格網站的文章列表,當用戶量越來越多時,而且每個用戶都有自己的文章列表,而且當文章多時,都需要分頁展示,這時可以考慮使用Redis的列表,列表不但有序同時也支援依照範圍內取得元素,可以完美解決分頁查詢功能。大大提高查詢效率。
Set:
#Set 是一個無序集合,會自動去重的那種。
直接基於Set 將系統裡需要去重的資料丟進去,自動就給去重了,如果你需要對一些資料進行快速的全域去重,你當然也可以基於JVM 記憶體裡的HashSet 進行去重,但是如果你的某個系統部署在多台機器上呢?得基於Redis進行全域的 Set 去重。
可以基於Set 玩兒交集、並集、差集的操作,例如交集吧,我們可以把兩個人的好友列表整一個交集,看看倆人的共同好友是誰?對吧。
反正這些場景比較多,因為比較很快,操作也簡單,兩個查詢一個Set搞定。
Sorted Set:
#Sorted set 是排序的Set,去重但可以排序,寫進去的時候給一個分數,自動依照分數排序。
有序集合的使用場景與集合類似,但是set集合不是自動有序的,而Sorted set可以利用分數進行成員間的排序,而且是插入時就排序好。所以當你需要一個有序且不重複的集合清單時,就可以選擇Sorted set資料結構作為選擇方案。
排行榜:有序集合經典使用場景。例如影片網站需要對使用者上傳的影片做排行榜,榜單維護可能是多方面:依照時間、依照播放量、依照取得的讚數等。
用Sorted Sets來做帶權重的佇列,例如普通訊息的score為1,重要訊息的score為2,然後工作執行緒可以選擇按score的倒序來取得工作任務。讓重要的任務優先執行。
微博熱搜榜,就是有個後面的熱度值,前面就是名稱
進階用法:
Bitmap :
位圖是支援按bit 位元來儲存訊息,可以用來實作布隆過濾器(BloomFilter );
HyperLogLog:
#供不精確的去重計數功能,比較適合用來做大規模資料的去重新統計,例如統計UV;
Geospatial:
#可以用來保存地理位置,並作位置距離計算或根據半徑計算位置等。有沒有想過用Redis來實現附近的人?或計算最優地圖路徑?
這三個其實也可以算是一種資料結構,不知道還有多少朋友記得,我在夢開始的地方,Redis基礎中提到過,你如果只知道五種基礎類型那只能拿60分,如果你能講出高階用法,那就覺得你有點東西。
pub/sub:
功能是訂閱發布功能,可以用作簡單的訊息佇列。
Pipeline:
可以批次執行一組指令,一次傳回全部結果,可以減少頻繁的請求回應。
Lua:
Redis 支援提交 Lua 腳本來執行一系列的功能。
我在前電商老東家的時候,秒殺場景經常使用這個東西,講道理有點香,利用他的原子性。
話說你們想看秒殺的設計麼?我記得我面試好像每次都問啊,想看的直接按讚後評論秒殺吧。
事務:
最後一個功能是事務,但Redis 提供的不是嚴格的事務, Redis 只保證序列執行指令,並且能保證全部執行,但是執行指令失敗時並不會回滾,而是會繼續執行下去。
持久化
Redis 提供了RDB 和AOF 兩種持久化方式,RDB 是把記憶體中的資料集以快照形式寫入磁碟,實際操作是透過fork 子程序執行,採用二進位壓縮儲存;AOF 是以文字日誌的形式記錄Redis 處理的每一個寫入或刪除操作。
RDB 把整個Redis 的資料保存在單一檔案中,比較適合用來做災備,但缺點是快照在快照保存完成之前如果宕機,這段時間的資料將會遺失,另外儲存快照時可能導致服務短時間不可用。
AOF 日誌檔案的寫入作業所使用的追加模式,有靈活的同步策略,支援每秒同步、每次修改同步和不同步,缺點就是相同規模的數據集,AOF 大於RDB,AOF 在運作效率上往往會慢於RDB。
細節的點大家去高可用這章看,特別是兩者的優缺點,以及怎麼抉擇。
《吊打麵試官》系列-Redis哨兵、持久化、主從、手撕LRU
高可用
來看Redis 的高可用。 Redis 支援主從同步,提供 Cluster 叢集部署模式,透過 Sentine l哨兵來監控 Redis 主伺服器的狀態。當主掛掉時,在從節點中根據一定策略選出新主,並調整其他從 slaveof 到新主。
選主的策略簡單來說有三個:
在 Redis 叢集中,sentinel 也會進行多實例部署,sentinel 之間透過 Raft 協定來保證自身的高可用。
Redis Cluster 使用分片機制,在內部分為 16384 個 slot 插槽,分佈在所有 master 節點上,每個 master 節點負責一部分 slot。資料運算時按 key 做 CRC16 來計算在哪個 slot,由哪個 master 進行處理。資料的冗餘是透過 slave 節點來保障。
哨兵
哨兵必須用三個實例去保證自己的健全性的,哨兵主從並不能保證資料不遺失,但是可以保證群集的高可用。
為啥必須要三個實例呢?我們先看看兩支哨兵會咋樣。
master宕機了s1和s2兩個哨兵只要有一個認為你宕機了就切換了,並且會選舉出一個哨兵去執行故障,但這個時候也需要大多數哨兵都是運作的。
那這樣有啥問題呢? M1宕機了,S1沒掛那其實是OK的,但整台機器都掛了呢?哨兵只剩下S2個裸屌了,沒有哨兵去允許故障轉移了,雖然另外一個機器上還有R1,但是故障轉移就是不執行。
經典的哨兵群集是這樣的:
#M1所在的機器掛了,哨兵還有兩個,兩個人一看他不是掛了嘛,那我們就選一個出來執行故障轉移不就好了。
暖男我,小的總結下哨兵組件的主要功能:
主從
提到這個,就跟我前面提到的資料持久化的RDB和AOF有著比密切的關係了。
我先說下為啥要用主從這樣的架構模式,前面提到了單機QPS是有上限的,而且Redis的特性就是必須支撐讀高並發的,那你一台機器又讀又寫,這誰頂得住啊,不當人啊!但是你讓這個master機器去寫,資料同步給別的slave機器,他們都拿去讀,分發掉大量的請求那是不是好很多,而且擴容的時候還可以輕鬆實現水平擴容。
你啟動一台slave 的時候,他會發送一個psync指令給master ,如果是這個slave第一次連接到master,他會觸發一個全量複製。 master就會啟動一個線程,產生RDB快照,也會把新的寫入請求都快取在記憶體中,RDB檔案產生後,master會這個RDB發送給slave的,slave拿到之後做的第一件事情就是寫進本地的磁碟,然後加載進內存,然後master會把內存裡面緩存的那些新命名都發給slave。
我發出來之後來自CSDN的網友:Jian_Shen_Zer 問了個問題:
主從同步的時候,新的slaver進來的時候用RDB ,那之後的數據呢?有新的資料進入master怎麼同步到slaver啊
敖丙答:笨,AOF嘛,增量的就像MySQL的Binlog一樣,把日誌增量同步給從服務就好了
key 失效機制
Redis 的key 可以設定過期時間,過期後Redis 採用主動和被動結合的失效機制,一個是和MC 一樣在訪問時觸發被動刪除,另一種是定期的主動刪除。
定期惰性記憶體淘汰
快取常見問題
#快取更新方式
這是決定在使用快取時就該考慮的問題。
快取的資料在資料來源發生變更時需要對快取進行更新,資料來源可能是 DB,也可能是遠端服務。更新的方式可以是主動更新。資料來源是 DB 時,可以在更新完 DB 後就直接更新快取。
當資料來源不是 DB 而是其他遠端服務,可能無法及時主動感知資料變更,這種情況下一般會選擇對快取資料設定失效期,也就是資料不一致的最大容忍時間。
這種場景下,可以選擇失效更新,key 不存在或失效時先請求資料來源取得最新數據,然後再次緩存,並更新失效期。
但這樣做有個問題,如果依賴的遠端服務在更新時出現異常,則會導致資料不可用。改進的辦法是非同步更新,就是當失效時先不清除數據,繼續使用舊的數據,然後由非同步執行緒去執行更新任務。這樣就避免了失效瞬間的空窗期。另外還有一種純非同步更新方式,定時對資料進行分批更新。實際使用時可根據業務場景選擇更新方式。
資料不一致
第二個問題是資料不一致的問題,可以說只要使用緩存,就要考慮如何面對這個問題。快取不一致產生的原因一般是主動更新失敗,例如更新 DB 後,更新 Redis 因為網路原因請求逾時;或是非同步更新失敗導致。
解決的辦法是,如果服務對耗時不是特別敏感可以增加重試;如果服務對耗時敏感可以透過非同步補償任務來處理失敗的更新,或者短期的資料不一致不會影響業務,那麼只要下次更新時可以成功,能保證最終一致性就可以。
快取穿透
快取穿透。產生這個問題的原因可能是外部的惡意攻擊,例如,對用戶資訊進行了緩存,但惡意攻擊者使用不存在的用戶id頻繁請求接口,導致查詢緩存不命中,然後穿透 DB 查詢依然不命中。這時會有大量請求穿透快取存取到 DB。
解決的辦法如下。
對不存在的用戶,在快取中保存一個空物件進行標記,防止相同 ID 再次存取 DB。不過有時這個方法並不能很好解決問題,可能導致快取中儲存大量無用資料。
使用BloomFilter 過濾器,BloomFilter 的特徵是存在性檢測,如果BloomFilter 中不存在,那麼數據一定不存在;如果BloomFilter 中存在,實際數據也有可能會不存在。非常適合解決這類的問題。
快取擊穿
#快取擊穿,就是某個熱點資料失效時,大量針對這個資料的請求會穿透到資料來源。
解決這個問題有以下辦法。
可以使用互斥鎖定更新,確保同一個進程中針對同一個資料不會並發請求到 DB,減少 DB 壓力。
使用隨機退避方式,失效時隨機 sleep 一個很短的時間,再次查詢,如果失敗再執行更新。
針對多個熱點 key 同時失效的問題,可以在快取時使用固定時間加上一個小的隨機數,避免大量熱點 key 同一時刻失效。
快取雪崩
#快取雪崩,產生的原因是快取掛掉,這時所有的請求都會穿透到DB。
解決方法:
使用快速失敗的熔斷策略,減少DB 瞬間壓力;
使用主從模式和叢集模式來盡量保證快取服務的高可用。
在實際場景中,這兩種方法會結合使用。
老朋友都知道為啥我沒有大篇幅介紹這個幾個點了吧,我在之前的文章實在是寫得太詳細了,忍不住點讚那種,我這裡就不做重複拷貝了。
考點與加分項
# #拿筆記一下!
考點
面試的時候問你緩存,主要是考察快取特性的理解,對MC、Redis
的特性和使用方式的掌握。- 對DB 熱點資料進行快取減少DB 壓力;對依賴的服務進行緩存,提高並發效能;
- 單純K-V 快取的場景可以使用MC
,而需要快取list、set 等特殊資料格式,可以使用- 需要快取一個使用者最近播放影片的清單可以使用
Redis 的zset 結構來儲存。 要了解 MC 和 Redis
的常用命令,例如原子增減、對不同資料結構進行操作的命令等。Redis
的資料失效方式和剔除策略,例如主動觸發的定期剔除和被動觸發延期剔除要理解Redis 的持久化、主從同步與Cluster
部署的原理,例如RDB
和要知道快取穿透、擊穿、雪崩分別的異同點以及解決方案。
######不管你有沒有電商經驗我覺得你都應該知道秒殺的具體實現,以及細節點。 ############……..###################加分項############如果想要在面試中獲得更好的表現,也應了解以下這些加分項。 ############是要結合實際應用場景來介紹快取的使用。例如呼叫後端服務介面取得資訊時,可以使用本地 遠端的多層快取;對於動態排行榜類別的場景可以考慮透過 ###Redis### 的 ###Sorted set### 來實現等等。 ###最好你有過分散式快取設計和使用經驗,例如專案中在什麼場景使用過Redis,使用了什麼資料結構,解決哪一類的問題;使用MC 時根據預估價大小調整McSlab 分配參數等等。
最好可以了解快取使用時可能產生的問題。例如Redis 是單執行緒處理請求,應盡量避免耗時較高的單一請求任務,防止相互影響;Redis 服務應避免和其他CPU 密集的行程部署在同一機器;或停用Swap 記憶體交換,防止Redis 的快取資料交換到硬碟上,影響效能。再例如前面提到的 MC 鈣化問題等等。
要了解Redis 的典型應用場景,例如,使用Redis 來實作分散式鎖定;使用Bitmap來實作BloomFilter,使用HyperLogLog 來進行UV 統計量等等。
知道 Redis4.0、5.0 中的新特性,例如支援多播的可持久化訊息佇列 Stream;透過 Module 系統來進行自訂功能擴充等等。
……..
更多程式相關知識,請造訪:程式設計影片! !
以上是Redis那些常見的面試問題總結(附答案解析)的詳細內容。更多資訊請關注PHP中文網其他相關文章!