用過Redis哪些數(shù)據(jù)類型?Redis String 類型的底層實(shí)現(xiàn)是什么?
基本數(shù)據(jù)類型:
- String:最常用的一種數(shù)據(jù)類型,String類型的值可以是字符串、數(shù)字或者二進(jìn)制,但值最大不能超過512MB。一般用于 緩存和計(jì)數(shù)器
- Hash:Hash 是一個(gè)鍵值對(duì)集合。存儲(chǔ)商品的各個(gè)屬性
- Set:無序去重的集合。Set 提供了交集、并集等方法,對(duì)于實(shí)現(xiàn)共同好友、共同關(guān)注等功能特別方便。
- List:有序可重復(fù)的集合,底層是依賴雙向鏈表實(shí)現(xiàn)的。用于消息隊(duì)列
- SortedSet:有序Set。內(nèi)部維護(hù)了一個(gè)
score的參數(shù)來實(shí)現(xiàn)。適用于排行榜和帶權(quán)重的消息隊(duì)列等場(chǎng)景。
特殊的數(shù)據(jù)類型:
- Bitmap:位圖,可以認(rèn)為是一個(gè)以位為單位數(shù)組,數(shù)組中的每個(gè)單元只能存0或者1,數(shù)組的下標(biāo)在 Bitmap 中叫做偏移量。Bitmap的長(zhǎng)度與集合中元素個(gè)數(shù)無關(guān),而是與基數(shù)的上限有關(guān)。
- Hyperloglog。HyperLogLog 是用來做基數(shù)統(tǒng)計(jì)的算法,其優(yōu)點(diǎn)是,在輸入元素的數(shù)量或者體積非常非常大時(shí),計(jì)算基數(shù)所需的空間總是固定的、并且是很小的。典型的使用場(chǎng)景是統(tǒng)計(jì)獨(dú)立訪客。
- Geospatial :主要用于存儲(chǔ)地理位置信息,并對(duì)存儲(chǔ)的信息進(jìn)行操作,適用場(chǎng)景如定位、附近的人等。
- Stream :一種日志數(shù)據(jù)結(jié)構(gòu),適合于存儲(chǔ)時(shí)間序列數(shù)據(jù)或消息流。支持高效的消息生產(chǎn)和消費(fèi)模式,具有持久性和序列化特性。
SortedSet和List異同點(diǎn)?
相同點(diǎn):
- 都是有序的;
- 都可以獲得某個(gè)范圍內(nèi)的元素。
不同點(diǎn):
- 列表基于鏈表實(shí)現(xiàn),獲取兩端元素速度快,訪問中間元素速度慢;
- 有序集合基于散列表和跳躍表實(shí)現(xiàn),訪問中間元素時(shí)間復(fù)雜度是OlogN;
- 列表不能簡(jiǎn)單的調(diào)整某個(gè)元素的位置,有序列表可以(更改元素的分?jǐn)?shù));
- 有序集合更耗內(nèi)存。
Redis 怎么實(shí)現(xiàn)消息隊(duì)列?
BLPOP queue 0 //0表示不限制等待時(shí)間BLPOP和LPOP命令相似,唯一的區(qū)別就是當(dāng)列表沒有元素時(shí)BLPOP命令會(huì)一直阻塞連接,直到有新元素加入。
redis可以通過pub/sub主題訂閱模式實(shí)現(xiàn)一個(gè)生產(chǎn)者,多個(gè)消費(fèi)者,當(dāng)然也存在一定的缺點(diǎn),當(dāng)消費(fèi)者下線時(shí),生產(chǎn)的消息會(huì)丟失。
PUBLISH channel1 hi
SUBSCRIBE channel1
UNSUBSCRIBE channel1 //退訂通過SUBSCRIBE命令訂閱的頻道。
PSUBSCRIBE channel?*按照規(guī)則訂閱。PUNSUBSCRIBE channel?*退訂通過PSUBSCRIBE命令按照某種規(guī)則訂閱的頻道。其中訂閱規(guī)則要進(jìn)行嚴(yán)格的字符串匹配,PUNSUBSCRIBE *無法退訂channel?*規(guī)則。
如何在 Redis 中實(shí)現(xiàn)隊(duì)列和棧數(shù)據(jù)結(jié)構(gòu)?
可以通過 List 類型 來實(shí)現(xiàn) 隊(duì)列 和 棧
實(shí)現(xiàn)隊(duì)列(FIFO):隊(duì)列是一種 先進(jìn)先出(FIFO)的數(shù)據(jù)結(jié)構(gòu)。在Redis中,可以使用 PUSH 和 RPOP命令組合來實(shí)現(xiàn)隊(duì)列。LPUSH 向列表的左側(cè)推入元素,而 RPOP從列表的右側(cè)彈出元素,這樣可以保證最先進(jìn)入的元素最先被彈出
實(shí)現(xiàn)棧(LIFO):棧是一種 后進(jìn)先出(LIFO)的數(shù)據(jù)結(jié)構(gòu)。在Redis 中,可以使用 LPUSH和 LPoP命令組合來實(shí)現(xiàn)棧。LPUSH 向列表的左側(cè)推入元素,而 LPoP從列表的左側(cè)彈出元素,這樣可以保證最后進(jìn)入的元素最先被彈出。
Redis 怎么實(shí)現(xiàn)延時(shí)隊(duì)列
使用sortedset,拿時(shí)間戳作為score,消息內(nèi)容作為key,調(diào)用zadd來生產(chǎn)消息,消費(fèi)者用zrangebyscore指令獲取N秒之前的數(shù)據(jù)輪詢進(jìn)行處理。
如何使用 Redis 快速實(shí)現(xiàn)排行榜?
使用 Redis 實(shí)現(xiàn)排行榜的方式主要利用 Sorted Set(有序集合),它可以高效地存儲(chǔ)、更新、以及獲取排名數(shù)據(jù)。實(shí)現(xiàn)排行榜的主要步驟:
- 使用 Sorted Set 存儲(chǔ)分?jǐn)?shù)和成員:使用 Redis 的 ADD命令,將用戶和對(duì)應(yīng)的分?jǐn)?shù)添加到有序集合中。例如:add leaderboard 1000 user1,將用戶 user1 的分?jǐn)?shù)設(shè)置為 1000。
- 獲取排名:使用 ZRANK命令獲取某個(gè)用戶的排名。例如:zrank leaderboard user1,返回用戶user1 的排名(從0開始)。
- 獲取前 N 名:使用 ZREVRANGE 命令獲取分?jǐn)?shù)最高的前N名。例如:REVRANGE leaderboard 0 9 WITHSCORES ,獲取排行榜前 10 名用戶及其分?jǐn)?shù)。
- 更新分?jǐn)?shù):如果用戶的分?jǐn)?shù)需要更新,可以使用 ZINCRBY 命令對(duì)其分?jǐn)?shù)進(jìn)行加減操作。例如:ZINCRBY leaderboard 500 user1,將用戶 user1 的分?jǐn)?shù)增加 500。
如何使用 Redis 快速實(shí)現(xiàn)布隆過濾器?
可以通過使用 位圖(Bitmap)或使用 Redis 模塊 RedisBloom。
- 使用位圖實(shí)現(xiàn)布隆過濾器:使用 Redis 的位圖結(jié)構(gòu) SETBIT 和 GETBIT 操作來實(shí)現(xiàn)布隆過濾器。位圖本質(zhì)上是一個(gè)比特?cái)?shù)組,用于標(biāo)識(shí)元素是否存在對(duì)于給定的數(shù)據(jù),通過多個(gè) 哈希函數(shù) 計(jì)算位置索引,將位圖中的相應(yīng)位置設(shè)置為 1,表示該元素可能存在。
- 使用 RedisBloom 模塊:Redis 提供了一個(gè)官方模塊 RedisBloom,封裝了哈希函數(shù)、位圖大小等操作,可以直接用于創(chuàng)建和管理布隆過濾器。使用 BF.ADD 來向布隆過濾器添加元素,使用 BF.EXISTS 來檢查某個(gè)元素是否可能存在,
如何使用 Redis 統(tǒng)計(jì)大量用戶唯一訪問量(UV)?
Redis 中 HyperLogLog 結(jié)構(gòu),可以快速實(shí)現(xiàn)網(wǎng)頁UV、PV 等統(tǒng)計(jì)場(chǎng)景。它是一種基數(shù)估算算法的概率性數(shù)據(jù)結(jié)構(gòu),可以用極少的內(nèi)存統(tǒng)計(jì)海量用戶唯一訪問量的近似值。
Set 也可以實(shí)現(xiàn),用于精確統(tǒng)計(jì)唯一用戶訪問量,但是但當(dāng)用戶數(shù)非常大時(shí),內(nèi)存開銷較高。
Redis 中的 Geo 數(shù)據(jù)結(jié)構(gòu)是什么?
Redis中的 Geo(Geoloaton的簡(jiǎn)寫形式,代表地理坐標(biāo)) 數(shù)據(jù)結(jié)構(gòu)主要用于地理位置信息的存儲(chǔ),通過這個(gè)結(jié)構(gòu),可以方便地進(jìn)行地理位置的存儲(chǔ)、檢索、以及計(jì)算地理距離等課作,GeO 數(shù)據(jù)結(jié)內(nèi)存層使用了 Sorted set, 并結(jié)合了Geohash 編碼算法來對(duì)地理位置進(jìn)行處理。
Redis String 類型的底層實(shí)現(xiàn)是什么?(SDS)
Redis 中的 Sting 類型底層實(shí)現(xiàn)主要基于 SDS(Simple Dynamic string 簡(jiǎn)單動(dòng)態(tài)字符串)結(jié)構(gòu),并結(jié)合 int、embstr、raw 等不同的編碼方式進(jìn)行優(yōu)化存儲(chǔ)。
Redis 中的 Ziplist 和 Quicklist 數(shù)據(jù)結(jié)構(gòu)的特點(diǎn)是什么?
Ziplist:
- 簡(jiǎn)單、緊湊、連續(xù)存儲(chǔ),適用于小數(shù)據(jù)量場(chǎng)景,但對(duì)大量數(shù)據(jù)或頻繁的修改操作不太友好。
- 適合小數(shù)據(jù)量場(chǎng)景,例如短列表、小哈希表等,因?yàn)樗膬?nèi)存緊湊,可以大幅減少內(nèi)存使用
Quicklist:
- 通過將鏈表和 Ziplist 結(jié)合,既實(shí)現(xiàn)了鏈表的靈活操作,又能節(jié)省內(nèi)存,在 Redis 3.2 之后成為 List 的默認(rèn)實(shí)現(xiàn)。
- Quicklist是為了替代純而設(shè)計(jì)的,適用于需要頻繁對(duì)列表進(jìn)行插入、刪除、查找等提作的場(chǎng)景,并目數(shù)據(jù)量可能較大,它在存儲(chǔ)多個(gè)元素時(shí),既保留了鏈表的靈活性,又具備壓縮列表的內(nèi)存優(yōu)勢(shì)
Redis Zset 的實(shí)現(xiàn)原理是什么?
Redis 中的Zset(有序集合,Sorted set)是一種由 跳表 (Skip List)和哈希表 (Hash Table)組成的數(shù)據(jù)結(jié)構(gòu),Zset 結(jié)合了集合 (Set)的特性和排序功能,能夠存儲(chǔ)具有唯一性的成員,并根據(jù)成員的分?jǐn)?shù) (score) 進(jìn)行排序
ZSet 的實(shí)現(xiàn)由兩個(gè)核心數(shù)據(jù)結(jié)構(gòu)組成:
- 跳表(Skip List):用于存儲(chǔ)數(shù)據(jù)的排序和快速查找。
- 哈希表(Hash Table):用于存儲(chǔ)成員與其分?jǐn)?shù)的映射,提供快速查找
當(dāng) Zset 元素?cái)?shù)量較少時(shí),Redis 會(huì)使用壓縮列表(Zip List)來節(jié)省內(nèi)存
- 即元素個(gè)數(shù)≤ zset-max-ziplist-entries(默認(rèn) 128)
- 元素成員名和分值的長(zhǎng)度 ≤ zset-max-ziplist-value(默認(rèn) 64 字節(jié))
如果任何一個(gè)條件不滿足,Zset 將使用 跳表 +哈希表 作為底層實(shí)現(xiàn),
Redis 的有序集合底層為什么要用跳表,而不用平衡樹、紅黑樹或者 B+樹?
這道面試題很多大廠比較喜歡問,難度還是有點(diǎn)大的。
- 平衡樹 vs 跳表:平衡樹的插入、刪除和查詢的時(shí)間復(fù)雜度和跳表一樣都是 **O(log n)**。對(duì)于范圍查詢來說,平衡樹也可以通過中序遍歷的方式達(dá)到和跳表一樣的效果。但是它的每一次插入或者刪除操作都需要保證整顆樹左右節(jié)點(diǎn)的絕對(duì)平衡,只要不平衡就要通過旋轉(zhuǎn)操作來保持平衡,這個(gè)過程是比較耗時(shí)的。跳表誕生的初衷就是為了克服平衡樹的一些缺點(diǎn)。跳表使用概率平衡而不是嚴(yán)格強(qiáng)制的平衡,因此,跳表中的插入和刪除算法比平衡樹的等效算法簡(jiǎn)單得多,速度也快得多。
- 紅黑樹 vs 跳表:相比較于紅黑樹來說,跳表的實(shí)現(xiàn)也更簡(jiǎn)單一些,不需要通過旋轉(zhuǎn)和染色(紅黑變換)來保證黑平衡。并且,按照區(qū)間來查找數(shù)據(jù)這個(gè)操作,紅黑樹的效率沒有跳表高。
- B+樹 vs 跳表:B+樹更適合作為數(shù)據(jù)庫和文件系統(tǒng)中常用的索引結(jié)構(gòu)之一,它的核心思想是通過盡可能少的 IO 定位到盡可能多的索引來獲得查詢數(shù)據(jù)。對(duì)于 Redis 這種內(nèi)存數(shù)據(jù)庫來說,它對(duì)這些并不感冒,因?yàn)?Redis 作為內(nèi)存數(shù)據(jù)庫它不可能存儲(chǔ)大量的數(shù)據(jù),所以對(duì)于索引不需要通過 B+樹這種方式進(jìn)行維護(hù),只需按照概率進(jìn)行隨機(jī)維護(hù)即可,節(jié)約內(nèi)存。而且使用跳表實(shí)現(xiàn) zset 時(shí)相較前者來說更簡(jiǎn)單一些,在進(jìn)行插入時(shí)只需通過索引將數(shù)據(jù)插入到鏈表中合適的位置再隨機(jī)維護(hù)一定高度的索引即可,也不需要像 B+樹那樣插入時(shí)發(fā)現(xiàn)失衡時(shí)還需要對(duì)節(jié)點(diǎn)分裂與合并。
Redis 中跳表的實(shí)現(xiàn)原理是什么?
跳表主要是通過多層鏈表來實(shí)現(xiàn),底層鏈表保存所有元素,而每一層鏈表都是下一層的子集。
插入時(shí),首先從最高層開始查找插入位置,然后隨機(jī)決定新節(jié)點(diǎn)的層數(shù),最后在相應(yīng)的層中插入節(jié)點(diǎn)并更新指針
刪除時(shí),同樣從最高層開始查找要?jiǎng)h除的節(jié)點(diǎn),并在各層中更新指針,以保持跳表的結(jié)構(gòu)。
查找時(shí),從最高層開始,逐層向下,直到找到目標(biāo)元素或確定元素不存在。查找效率高,時(shí)間復(fù)雜度為 O(logn)

Redis中的跳表是兩步兩步跳的嗎?
如果采用新增節(jié)點(diǎn)或者刪除節(jié)點(diǎn)時(shí),來調(diào)整跳表節(jié)點(diǎn)以維持比例2:1的方法的話,顯然是會(huì)帶來額外開銷的。
跳表在創(chuàng)建節(jié)點(diǎn)時(shí)候,會(huì)生成范圍為[0-1]的一個(gè)隨機(jī)數(shù),如果這個(gè)隨機(jī)數(shù)小于 0.25(相當(dāng)于概率 25%),那么層數(shù)就增加 1 層,然后繼續(xù)生成下一個(gè)隨機(jī)數(shù),直到隨機(jī)數(shù)的結(jié)果大于 0.25 結(jié)束,最終確定該節(jié)點(diǎn)的層數(shù)。因?yàn)殡S機(jī)數(shù)取值在[0,0.25)范圍內(nèi)概率不會(huì)超過25%,所以這也說明了增加一層的概率不會(huì)超過25%。這樣的話,當(dāng)插入一個(gè)新結(jié)點(diǎn)時(shí),只需修改前后結(jié)點(diǎn)的指針,而其它結(jié)點(diǎn)的層數(shù)就不需要隨之改變了,這樣就降低插入操作的復(fù)雜度。
// #define ZSKIPLIST_P 0.25
int zslRandomLevel(void) {
static const int threshold = ZSKIPLIST_P*RAND_MAX;
int level = 1; //初始化為一級(jí)索引
while (random() < threshold)
level += 1;//隨機(jī)數(shù)小于 0.25就增加一層
//如果level 沒有超過最大層數(shù)就返回,否則就返回最大層數(shù)
return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}Redis遇到哈希沖突怎么辦?
當(dāng)有兩個(gè)或以上數(shù)量的鍵被分配到了哈希表數(shù)組的同一個(gè)索引上面時(shí), 我們稱這些鍵發(fā)生了沖突(collision)。
關(guān)于解決hash沖突問題可以看這篇文章:解決哈希沖突的三種方法
而redis是先通過拉鏈法解決,再通過rehash來解決hash沖突問題的,即再hash法,只不過redis的hash使?jié)u進(jìn)式hash
rehash原理?
漸進(jìn)式 rehash 步驟如下:
- 先給
哈希表 2分配空間; - 在 rehash 進(jìn)行期間,每次哈希表元素進(jìn)行新增、刪除、查找或者更新操作時(shí),Redis 除了會(huì)執(zhí)行對(duì)應(yīng)的操作之外,還會(huì)順序?qū)?/span>
哈希表 1中索引位置上的所有 key-value 遷移到哈希表 2上; - 隨著處理客戶端發(fā)起的哈希表操作請(qǐng)求數(shù)量越多,最終在某個(gè)時(shí)間點(diǎn)會(huì)把
哈希表 1的所有 key-value 遷移到哈希表 2,從而完成 rehash 操作。
這樣就把一次性大量數(shù)據(jù)遷移工作的開銷,分?jǐn)偟搅硕啻翁幚碚?qǐng)求的過程中,避免了一次性 rehash 的耗時(shí)操作。
在進(jìn)行漸進(jìn)式 rehash 的過程中,會(huì)有兩個(gè)哈希表,所以在漸進(jìn)式 rehash 進(jìn)行期間,哈希表元素的刪除、查找、更新等操作都會(huì)在這兩個(gè)哈希表進(jìn)行。比如,在漸進(jìn)式 rehash 進(jìn)行期間,查找一個(gè) key 的值的話,先會(huì)在哈希表 1里面進(jìn)行查找,如果沒找到,就會(huì)繼續(xù)到哈希表 2 里面進(jìn)行找到。新增一個(gè) key-value 時(shí),會(huì)被保存到哈希表 2里面,而哈希表 1則不再進(jìn)行任何添加操作,這樣保證了哈希表 1的 key-value 數(shù)量只會(huì)減少,隨著 rehash 操作的完成,最終哈希表 1就會(huì)變成空表。
rehash的觸發(fā)條件?
負(fù)載因子 = 哈希表已保存節(jié)點(diǎn)數(shù)量/哈希表大小
觸發(fā) rehash 操作的條件,主要有兩個(gè):
- 當(dāng)負(fù)載因子大于等于 1 ,并且 Redis 沒有在執(zhí)行 bgsave 命令或者 bgrewiteaof 命令,也就是沒有執(zhí)行 RDB 快照或沒有進(jìn)行 AOF 重寫的時(shí)候,就會(huì)進(jìn)行 rehash 操作。
- 當(dāng)負(fù)載因子大于等于 5 時(shí),此時(shí)說明哈希沖突非常嚴(yán)重了,不管有沒有有在執(zhí)行 RDB 快照或 AOF 重寫,都會(huì)強(qiáng)制進(jìn)行 rehash 操作
一個(gè)REDIS實(shí)例最多能存放多少KEYS
redis 的每個(gè)實(shí)例最多可以存放約 2^32 - 1 個(gè)keys,即大約 42 億個(gè)keys。這是由 Redis 內(nèi)部使用的哈希表實(shí)現(xiàn)決定的,它使用 32 位有符號(hào)整數(shù)作為索引。Redis 使用的哈希函數(shù)和負(fù)載因子等因素也會(huì)影響實(shí)際可存放鍵的數(shù)量。
需要注意的是,盡管 Redis 允許存儲(chǔ)數(shù)量龐大的鍵,但在實(shí)踐中,存儲(chǔ)過多的鍵可能會(huì)導(dǎo)致性能下降和內(nèi)存消耗增加。因此,在設(shè)計(jì)應(yīng)用程序時(shí),需要根據(jù)實(shí)際需求和硬件資源來合理規(guī)劃鍵的數(shù)量,避免過度使用 Redis 實(shí)例造成負(fù)擔(dān)。如果需要存儲(chǔ)更多的鍵值對(duì),可以考慮使用 Redis 集群或分片技術(shù),以擴(kuò)展整體存儲(chǔ)容量。




























