關(guān)于Redis最常見的十道面試題總結(jié)大全
面試題一:Redis為什么執(zhí)行這么快?
Redis運(yùn)行比較快主要原因有以下幾種:
- 純內(nèi)存操作:Redis將所有數(shù)據(jù)存儲(chǔ)在內(nèi)存中,這意味著對(duì)數(shù)據(jù)的讀寫操作直接在內(nèi)存中運(yùn)行,而內(nèi)存的訪問速度遠(yuǎn)遠(yuǎn)高于磁盤。這種設(shè)計(jì)使得Redis能夠已接近硬件極限的速度處理數(shù)據(jù)讀寫
- 單線程模型:Redis使用單線程模型來(lái)處理客戶端請(qǐng)求。這可能聽起來(lái)效率不高,但是實(shí)際上,這種設(shè)計(jì)避免了多線程頻繁切換和過度競(jìng)爭(zhēng)帶來(lái)的性能開銷。Redis每個(gè)請(qǐng)求的執(zhí)行時(shí)間都是很短的,因此單線程下,也能處理大量的并發(fā)請(qǐng)求
- I/O多路復(fù)用:Redis使用了I/O多路復(fù)用技術(shù),可以在單線程的環(huán)境下同時(shí)監(jiān)聽多個(gè)客戶端連接,只有當(dāng)有網(wǎng)絡(luò)事件(如用戶發(fā)送一個(gè)請(qǐng)求)發(fā)生的時(shí)候才會(huì)進(jìn)行實(shí)際的I/O操作。這樣有效的利用了CPU資源,減少了無(wú)謂的等待
- 高效數(shù)據(jù)結(jié)構(gòu):Redis提供了多種高效的數(shù)據(jù)結(jié)構(gòu),如哈希表、有序集合等。這些數(shù)據(jù)結(jié)構(gòu)的實(shí)現(xiàn)都經(jīng)過了優(yōu)化,使得Redis在處理這些數(shù)據(jù)結(jié)構(gòu)的操作是非常高效的
面試題二:Redis是單線程執(zhí)行還是多線程執(zhí)行?它有線程安全問題嗎?為什么嗎?
Redis版本在6.0之前都是使用的單線程運(yùn)行的。所有的客戶端的請(qǐng)求處理、命令執(zhí)行以及數(shù)據(jù)讀寫操作都是在一個(gè)主線程中完成得。這種設(shè)計(jì)目的就是為了防止多線程環(huán)境下的鎖競(jìng)爭(zhēng)和上下文切換所帶來(lái)的性能開銷,這樣保證在高并發(fā)場(chǎng)景下的性能
Redis版本在6.0中,開始引入了多線程的支持,但是這僅限于網(wǎng)絡(luò)I/O層面,即在網(wǎng)絡(luò)請(qǐng)求階段使用工作線程進(jìn)行處理,對(duì)于指令的執(zhí)行過程,仍然是在主線程來(lái)處理,所以不會(huì)存在多個(gè)線程通知執(zhí)行操作指令的情況
關(guān)于線程安全問題,從Redis服務(wù)層面?zhèn)z看,Redis Server本身就是一個(gè)線程安全按的K-V數(shù)據(jù)庫(kù),也就是說在Redis Server上面執(zhí)行的指令,不需要任何同步機(jī)制,不會(huì)存在線程安全問題
面試題三:在實(shí)際工作中,使用Redis實(shí)現(xiàn)了哪些業(yè)務(wù)場(chǎng)景?
Redis在實(shí)際工作中廣泛應(yīng)用于多種業(yè)務(wù)場(chǎng)景,以下是一些常見的例子:
緩存:Redis作為Key-Value形態(tài)的內(nèi)存數(shù)據(jù)庫(kù),最先會(huì)被想到的應(yīng)用場(chǎng)景就是作為數(shù)據(jù)緩存。Redis提供了鍵過期功能,也提供了鍵淘汰策略,所以Redis用在緩存的場(chǎng)合非常多
排行榜:很多網(wǎng)站都有排行榜應(yīng)用,如京東的月度銷量榜單、商品按時(shí)間上新排行榜等。Redis提供的有序集合(zset)數(shù)據(jù)類
分布式會(huì)話:在集群模式下,一般會(huì)搭建以Redis等內(nèi)存等內(nèi)存數(shù)據(jù)庫(kù)為中心的session服務(wù),session不再由容器管理,而是由session服務(wù)及內(nèi)存數(shù)據(jù)庫(kù)管理
分布式鎖:在高并發(fā)的情景,可以利用Redis的setnx功能來(lái)編寫分布式鎖
面試題四:Redis常用數(shù)據(jù)類型有哪些?
Redis中,常見的數(shù)據(jù)類型有如下幾種:
- 字符串(String):最簡(jiǎn)單的數(shù)據(jù)類型,可以包含任意數(shù)據(jù),如文本、二進(jìn)制數(shù)據(jù)等。常見的使用場(chǎng)景是存儲(chǔ)Session信息、存儲(chǔ)緩存信息、存儲(chǔ)整數(shù)信息,可以使用incr實(shí)現(xiàn)整數(shù)+1,使用decr實(shí)現(xiàn)整數(shù)-1
- 列表(List):有序的字符串元素集合,支持雙端進(jìn)行插入和刪除操作,可以用作隊(duì)列或棧
- 哈希(Hash):用于存儲(chǔ)對(duì)象,類似于關(guān)聯(lián)數(shù)組。每個(gè)哈??梢园侄魏团c之相關(guān)聯(lián)的值。常見使用場(chǎng)景是存儲(chǔ)Session信息、存儲(chǔ)商品的購(gòu)物車,購(gòu)物車非常適用于哈希字典表示,使用人員唯一編號(hào)作為字典的key,value值可以存儲(chǔ)商品的id和數(shù)量等信息、存儲(chǔ)詳情頁(yè)等信息
- 集合(Set):一個(gè)無(wú)序并唯一的鍵值集合。它常見的使用場(chǎng)景是是仙女關(guān)注功能,比如關(guān)注我的人和我關(guān)注的人,使用集合存儲(chǔ),可以保證人員不重復(fù)
- 有序集合(Sorted Set):使用zset表示,相當(dāng)于Set集合類型多了一個(gè)排序?qū)傩詓core(分值)。。它常見的使用場(chǎng)景是可以用來(lái)存儲(chǔ)排名信息,關(guān)注列表功能,這樣就可以根據(jù)關(guān)注實(shí)現(xiàn)排序展示
面試題五:存儲(chǔ)Session信息你會(huì)使用哪種數(shù)據(jù)類型?為什么?
在實(shí)際工作中,小型的項(xiàng)目會(huì)使用Redis存儲(chǔ)Session信息,但是不同業(yè)務(wù)場(chǎng)景存儲(chǔ)Session信息的類型也是不同的,具體來(lái)說:
存儲(chǔ)數(shù)據(jù)簡(jiǎn)單(不涉及局部更新):使用String類型粗怒觸Session,這樣做的優(yōu)缺點(diǎn)如下:
優(yōu)點(diǎn):
存取操作簡(jiǎn)單直觀,只需要單個(gè)鍵執(zhí)行操作即可
多余小型Session,存儲(chǔ)開銷相對(duì)于較小
缺點(diǎn):
如果Session數(shù)據(jù)復(fù)雜或者需要頻繁更新其中的部分字段,則每次更新都需要重新序列化整個(gè)Session對(duì)象
不利于查詢Session內(nèi)特定字段值
存儲(chǔ)數(shù)據(jù)復(fù)雜(涉及局部更新):如果Session數(shù)據(jù)結(jié)構(gòu)復(fù)雜且需要頻繁更新或查詢其中個(gè)別字段,通常建議使用哈希表存儲(chǔ)Session。每個(gè)Session視為一個(gè)獨(dú)立的哈希表,Session ID作為key,Sesion內(nèi)各個(gè)字段作為field-value對(duì)存儲(chǔ)在哈希表中。示例:HSET session:123 userId 123 username user1,這樣做的優(yōu)缺點(diǎn)如下:
優(yōu)點(diǎn):
可以方便地進(jìn)行字段級(jí)別的讀寫操作,例如 HGET session:23 userd 和 HSET session:123 lastAccessTime now
更新部分字段時(shí)無(wú)需修改整個(gè)Session內(nèi)容
缺點(diǎn):
相對(duì)于簡(jiǎn)單的字符串存儲(chǔ),哈希表占用的空間可能更大,尤其時(shí)當(dāng)Session數(shù)據(jù)包含多個(gè)值的時(shí)候
小結(jié): 如果 Session 數(shù)據(jù)結(jié)構(gòu)復(fù)雜且需要頻繁更新或查詢其中的個(gè)別字段,通常建議使用哈希表來(lái)存儲(chǔ) Session;而在 Session 數(shù)據(jù)較為簡(jiǎn)單、不涉及局部更新的情況下,使用字符串存儲(chǔ)也是可行的選擇
面試題六:有序集合底層是如何實(shí)現(xiàn)的?
在Redis7之前,有序集合使用的是ziplist(壓縮列表)+skiplist(跳躍表),當(dāng)數(shù)據(jù)列表元素小于128個(gè),并且所有元素成員的長(zhǎng)度都小于64字節(jié)時(shí),使用壓縮列表存儲(chǔ),否則使用調(diào)表存儲(chǔ)
在Redis之后,有序集合使用listPack(緊湊列表)+skiplist(跳躍表)
面試題七:什么是跳表?為什么使用跳表?
skiplist是一種以空間換時(shí)間的數(shù)據(jù)結(jié)構(gòu)。由于鏈表無(wú)法進(jìn)行二分查找,因此借鑒數(shù)據(jù)庫(kù)索引的思想,提取出鏈表中的關(guān)鍵姐點(diǎn)(索引),現(xiàn)在關(guān)鍵節(jié)點(diǎn)上查找,在進(jìn)入下層鏈表查找提取多層關(guān)鍵節(jié)點(diǎn),就形成了跳表。但是由于索引要占據(jù)一定的空間,所以索引添加的越多,占用的空間越多。
對(duì)于一個(gè)單鏈表來(lái)講,即便鏈表中存儲(chǔ)的數(shù)據(jù)是有序的,如果我們要想在其中查找某個(gè)數(shù)據(jù),也只能從頭到尾遍歷鏈表。這樣查找效率就會(huì)很低,時(shí)間復(fù)雜度會(huì)很高O(N)
從這個(gè)例子里,我們看出,加來(lái)一層索引之后,查找一個(gè)結(jié)點(diǎn)需要遍歷的結(jié)點(diǎn)個(gè)數(shù)減少了,也就是說查找效率提高了。時(shí)間復(fù)雜度從原來(lái)的O(n)到O(logn),是一空間換時(shí)間的解決方法
面試題八:說一下跳表的查詢流程?
跳表的查詢流程如下:
- 起始搜索:查詢操作從跳表的頂層開始,跳表的頂層包含一個(gè)或多個(gè)節(jié)點(diǎn),從最頂層的頭節(jié)點(diǎn)開始,將當(dāng)前節(jié)點(diǎn)設(shè)置為頭節(jié)點(diǎn)
- 檢查下一個(gè)節(jié)點(diǎn):檢查當(dāng)前節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn),如果節(jié)點(diǎn)的分值小于目標(biāo)分值,則向右移動(dòng)檢查下一個(gè)節(jié)點(diǎn),重復(fù)此步驟,直到找到一個(gè)大于目標(biāo)值的節(jié)點(diǎn),或者最后一個(gè)節(jié)點(diǎn)
- 逐層探索:如果當(dāng)前下一個(gè)結(jié)點(diǎn)的值大于目標(biāo)值,或者最后一個(gè)節(jié)點(diǎn),則將當(dāng)前指針向下層進(jìn)行搜索,重復(fù)上述步驟
- 終止并返回:在查找的過程中,如果找到了和目標(biāo)分支相同的值,或者遍歷完所有層級(jí)仍然未找到對(duì)應(yīng)的節(jié)點(diǎn),則說明要查找的元素不存在于跳表中,則終止查找并返回查詢到的內(nèi)容或NULL值
面試題九:說一下跳表的添加流程?為什么要有“隨機(jī)層數(shù)”這個(gè)概念?
跳表的添加流程主要是包括以下步驟:
查找插入位置:首先,我們需要找到新元素應(yīng)該插入的位置。這個(gè)過程與跳表查找操作類似,我們從最高層所以一年開始,逐層向下查找直到找到最后一個(gè)位置,使得該位置前面的元素小于新元素,后面的元素大于新元素
生成隨機(jī)層數(shù):在確定新元素插入位置后,我們需要決定新元素在跳表中的層數(shù)。這個(gè)層數(shù)是通過一個(gè)隨機(jī)函數(shù)生成的。每個(gè)節(jié)點(diǎn)肯定都有第一層指針(每個(gè)節(jié)點(diǎn)都在第一層鏈表中)。如果一個(gè)節(jié)點(diǎn)有第i層指針(即節(jié)點(diǎn)已經(jīng)在第一層到第i層鏈表中),那么他又第(i+1)層指針的
插入新元素:根據(jù)生成的隨機(jī)層數(shù),我們?cè)谙鄳?yīng)的層中插入新元素。對(duì)于每一層,我們都需要更新相應(yīng)的前驅(qū)和后繼指針,使得它們指向新插入的元素
更新跳表的最大層數(shù):如果新插入的元素的層數(shù)大于跳表的當(dāng)前最大層數(shù),我們需要更新跳表的最大層數(shù)
關(guān)于“隨機(jī)層數(shù)”的概念,其主要目的是為了保持跳表的平衡性。如果我們固定每個(gè)元素的層數(shù),那么在某些情況下,跳表可能會(huì)退化成普通的鏈表,從而導(dǎo)致查找效率降低。通過隨機(jī)選擇每個(gè)元素的層數(shù),我們可以確保跳表的高度大致為log(n),從而保證查找、插入和刪除操作的時(shí)間復(fù)雜度為O(log n)
給定如上跳表,假設(shè)要插入節(jié)點(diǎn)2。首先需要判斷節(jié)點(diǎn)2是否已經(jīng)存在,若存在則返回false
。否則,隨機(jī)生成待插入節(jié)點(diǎn)的層數(shù)
/** * 生成隨機(jī)層數(shù)[0,maxLevel) * 生成的值越大,概率越小 * * @return */ private int randomLevel() { int level = 0; while (Math.random() < PROBABILITY && level < maxLevel - 1) { ++level; } return level; }
這里的PROBABILITY =0.5。上面算法的意思是返回1的概率是1/2,返回2的概率是1/4,返回3的概率是1/8,依次類推??闯梢粋€(gè)分布的話,第0層包含所有節(jié)點(diǎn),第1層含有1/2個(gè)節(jié)點(diǎn),第2層含有1/4 個(gè)節(jié)點(diǎn)…
注意這里有一個(gè)最大層數(shù)maxLevel ,也可以不設(shè)置最大層數(shù)。通過這種隨機(jī)生成層數(shù)的方式使得實(shí)現(xiàn)起來(lái)簡(jiǎn)單。假設(shè)我們生成的層數(shù)是3
在1和3之間插入節(jié)點(diǎn)2,層數(shù)是3,也就是節(jié)點(diǎn)2跳躍到了第3層
public boolean add(E e) { if (contains(e)) { return false; } int level = randomLevel(); if (level > curLevel) { curLevel = level; } Node newNode = new Node(e); Node current = head; //插入方向由上到下 while (level >= 0) { //找到比e小的最大節(jié)點(diǎn) current = findNext(e, current, level); //將newNode插入到current后面 //newNode的next指針指向該節(jié)點(diǎn)的后繼 newNode.forwards.add(0, current.next(level)); //該節(jié)點(diǎn)的next指向newNode current.forwards.set(level, newNode); level--;//每層都要插入 } size++; return true; }
我們通過一個(gè)例子來(lái)模擬,由于實(shí)現(xiàn)了直觀的打印算法。假設(shè)我們要插入1, 6, 9, 3, 5, 7, 4, 8
過程如下:
add: 1 Level 0: 1 add: 6 Level 0: 1 6 add: 9 Level 2: 9 Level 1: 9 Level 0: 1 6 9 add: 3 Level 2: 3 9 Level 1: 3 9 Level 0: 1 3 6 9 add: 5 Level 2: 3 9 Level 1: 3 5 9 Level 0: 1 3 5 6 9 add: 7 Level 2: 3 9 Level 1: 3 5 9 Level 0: 1 3 5 6 7 9 add: 4 Level 2: 3 9 Level 1: 3 5 9 Level 0: 1 3 4 5 6 7 9 add: 8 Level 2: 3 9 Level 1: 3 5 9 Level 0: 1 3 4 5 6 7 8 9
面試題十:使用Redis如何實(shí)現(xiàn)分布式鎖?
實(shí)現(xiàn)分布式鎖
使用Redis實(shí)現(xiàn)分布式鎖可以通過setnx(set if not exists)命令實(shí)現(xiàn),但當(dāng)我們使用setnx創(chuàng)建鍵值成功時(shí),則表中加鎖成功,否則代碼加鎖失敗,實(shí)現(xiàn)示例如下:
127.0.0.1:6379> setnx lock true (integer) 1#創(chuàng)建鎖成功 #邏輯業(yè)務(wù)處理..
當(dāng)我們重復(fù)加鎖時(shí),只有第一次會(huì)加鎖成功
127.8..1:6379> setnx lock true # 第一次加鎖 (integer) 1 127.8.8.1:6379> setnx lock true # 第二次加鎖 (integer) 0
從上述命令可以看出,我們可以看執(zhí)行結(jié)果返回是不是1,就可以看出是否加鎖成功
釋放分布式鎖
127.0.0.1:6379> de1 lock (integer) 1 #釋放鎖
然而,如果使用 setnx ock true 實(shí)現(xiàn)分布式鎖會(huì)存在死鎖問題,以為 setnx 如未設(shè)置過期時(shí)間,鎖忘記刪了或加鎖線程宕機(jī)都會(huì)導(dǎo)致死鎖,也就是分布式鎖一直被占用的情況
解決死鎖問題
死鎖問題可以通過設(shè)置超時(shí)時(shí)間來(lái)解決,如果超過了超時(shí)時(shí)間,分布鎖會(huì)自動(dòng)釋放,這樣就不會(huì)存在死鎖問題了也就是 setnx和 expire 配合使用,在 Redis 2.6.12 版本之后,新增了一個(gè)強(qiáng)大的功能,我們可以使用一個(gè)原子操作也就是一條命令來(lái)執(zhí)行 setnx 和expire 操作了,實(shí)現(xiàn)示例如下:
127.0.0.1:6379> set lock true ex 3 nx OK #創(chuàng)建鎖成功 127...1:6379> set lock true ex 3 nx (ni1) #在鎖被占用的時(shí)候,企圖獲取鎖失敗
其中ex為設(shè)置超時(shí)時(shí)間, nx 為元素非空判斷,用來(lái)判斷是否能正常使用鎖的。
因此,我們?cè)?Redis 中實(shí)現(xiàn)分布式鎖最直接的方案就是使用 set key value ex timeout nx 的方式來(lái)實(shí)現(xiàn)。
總結(jié)
到此這篇關(guān)于Redis最常見的十道面試題總結(jié)的文章就介紹到這了,更多相關(guān)Redis最常見面試題內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用Redis實(shí)現(xiàn)用戶積分排行榜的教程
這篇文章主要介紹了使用Redis實(shí)現(xiàn)用戶積分排行榜的教程,包括一個(gè)用PHP腳本進(jìn)行操作的例子,需要的朋友可以參考下2015-04-04Redis遠(yuǎn)程字典服務(wù)器?hash類型示例詳解
這篇文章主要介紹了Redis遠(yuǎn)程字典服務(wù)器?hash類型示例詳解,本文通過示例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-08-08Redis+Caffeine兩級(jí)緩存的實(shí)現(xiàn)
本文主要介紹了Redis+Caffeine兩級(jí)緩存的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06