2022最新版 Redis大廠面試題總結(附答案)

語言: CN / TW / HK

專注於PHP、MySQL、Linux和前端開發,感興趣的感謝點個關注喲!!!文章已收錄,主要包含的技術有PHP、Redis、MySQL、JavaScript、HTML&CSS、Linux、Java、Golang、Linux和工具資源等相關理論知識、面試題和實戰內容。

文章導語

大家好,前段時間一直在忙找工作相關的事情。最近工作穩定了,於是把面試過程中遇到的Redis相關知識問題總結下來,希望能夠對大家面試、學習有所幫助。 本文重點分享資料型別,其他內容後續章節分享,也可以點選上方收錄連結。

本系列面試題會從認識Redis、Redis幾大資料型別、常見的使用場景和解決方案、Redis主從複製、Redis哨兵、Redis叢集等相關知識點進行總結。不僅僅單純的從文字方面終結,還會帶有更多的圖文方面,儘可能的讓大家深入的學習。

同時我也準備了一份彙總大綱,相對文字來說更加的清晰、明瞭。需要的小夥伴也可以回覆 Redis面試大綱 。同時該系列問題也會不斷的完善、更新。讓大家學習到更多的知識。

認識Redis

  1. REmote DIctionary Server(Redis) 是一個由 Salvatore Sanfilippo 寫的 key-value 儲存系統,是跨平臺的非關係型資料庫。
  2. Redis 是一個開源的使用 ANSI C 語言編寫、遵守 BSD 協議、支援網路、可基於記憶體、分散式、可選永續性的鍵值對(Key-Value)儲存資料庫,並提供多種語言的 API。

Redis的資料型別都有哪些

  1. 有五種基本資料型別,分別是string、hash、list、有序集合(zset)、集合(set)。在5.0之後增加了一種Stream型別。
  2. 額外的有GEO、HyperLogLog、BitMap。

Redis使用的場景有哪些

  1. 資料快取(使用者資訊、商品數量、文章閱讀數量)
  2. 訊息推送(站點的訂閱)
  3. 佇列(削峰、解耦、非同步)
  4. 排行榜(積分排行)
  5. 社交網路(共同好友、互踩、下拉重新整理)
  6. 計數器(商品庫存,站點線上人數、文章閱讀、點贊)
  7. 基數計算
  8. GEO計算

Redis功能特點都有哪些

  1. 持久化
  2. 豐富的資料型別(string、list、hash、set、zset、釋出訂閱等)
  3. 高可用方案(哨兵、叢集、主從)
  4. 事務
  5. 豐富的客戶端
  6. 提供事務
  7. 訊息釋出訂閱
  8. Geo
  9. HyperLogLog
  10. 事務
  11. 分散式事務鎖

Redis如何實現分散式鎖

  1. Redis可以使用 setnx key value + expire key expire_time 來實現分散式鎖。
  2. 正常情況下,上面的命令是沒有問題的。當Redis出現異常的情況下,很容易出現非原子性操作。
  3. 非原子性操作指的的setnx命令執行成功,但是expire沒有執行成功,此時key就成為了一個無過期時間的key,一直保留在Redis中,導致其他的請求就無法執行。
  4. 要解決該問題,可以使用lua指令碼實現。通過lua實現命令的原子性操作。

    在Redis中使用set命令,加引數也可以實現分散式鎖。 set key vale nx ex|px ttl

<!-- tabs:start -->

通過陣列定義

--- getLock key
local key = KEYS[1]
local requestId = KEYS[2]
local ttl = tonumber(KEYS[3])
local result = redis.call('setnx', key, requestId)
if result == 1 then
    --PEXPIRE:以毫秒的形式指定過期時間
    redis.call('pexpire', key, ttl)
else
    result = -1;
    -- 如果value相同,則認為是同一個執行緒的請求,則認為重入鎖
    local value = redis.call('get', key)
    if (value == requestId) then
        result = 1;
        redis.call('pexpire', key, ttl)
    end
end
--  如果獲取鎖成功,則返回 1
return result

通過陣列定義

-- releaseLock key
local key = KEYS[1]
local requestId = KEYS[2]
local value = redis.call('get', key)
if value == requestId then
    redis.call('del', key);
    return 1;
end
return -1

<!-- tabs:end -->

tips:如果對一個key第一次set添加了過期時間,第二次操作時沒有新增過期時間,此時key是沒有過期時間的(過期時間被覆蓋為永久不過期)。

Redis底層資料結構有哪些

Redis底層資料結構主要有六種,這六種構成了五種常用的資料型別。其他的資料型別,例如bitmap、hyperLogLog也是基於這五大資料型別實現。具體的資料結構圖如下:

說說Redis的全域性Hash表

為了實現從鍵到值的快速訪問,Redis 使用了一個雜湊表來儲存所有鍵值對。結構圖如下:

Hash表應用如此廣泛的一個重要原因,就是從理論上來說,它能以 O(1) 的複雜度快速查詢資料。Hash 表通過Hash函式的計算,就能定位資料在表中的位置,緊接著可以對資料進行操作,這就使得資料操作非常快速。

那麼我們該如何解決雜湊衝突呢?可以考慮使用以下兩種解決方案:

  1. 第一種方案,就是使用鏈式雜湊。但是鏈式雜湊容易導致Hash的鏈過長,查詢效率降低。
  2. 第二種方案,就是當鏈式雜湊的鏈長達到一定長度時,我們可以使用rehash。不過,執行rehash本身開銷比較大。

del刪除大量key有什麼問題

  1. 使用del命令可以刪除一個key或者多個key,其時間複雜度為O(N),這裡的N表示刪除的key數量。
  2. 刪除單個key時,其時間複雜度為O(1)。
  3. 當刪除單個列表、集合、有序集合或者雜湊列表型別的key時,時間複雜度為O(M),這裡的M表示key對應的內部元素個數。

說說Redis的全域性hash實現原理

說說Zset在skiplist和ziplist實現原理

Redis事務都有哪些命令

mutil: 開啟事務;exec: 提交事務;discard: 回滾事務。watch: 監聽key; unwatch: 取消監聽key。

Redis中的事務是否是原子性

嚴格來說,Redis中的事務並非滿足事務的原子性操作。當事務在命令組隊時沒有發生錯誤,則事務是原子性;當事務在命令組隊時發生錯誤,則事務是非原子性的。

Redis如何解決事務之間的衝突

  1. 使用watch監聽key變化,當key發生變化,事務中的所有操作都會被取消。
  2. 使用樂觀鎖,通過版本號實現。
  3. 使用悲觀鎖,每次開啟事務時,都新增一個鎖,事務執行結束之後釋放鎖。

悲觀鎖:悲觀鎖(Pessimistic Lock),顧名思義,就是很悲觀,每次去拿資料的時候都認為別人會修改,所以每次在拿資料的時候都會上鎖,這樣別人拿到這個資料就會block(阻塞)直到它拿到鎖。傳統的關係型資料庫裡面 就用到了很多這種鎖機制,比如行鎖、表鎖、讀鎖、寫鎖等,都是在做操作之前先上鎖。

樂觀鎖:樂觀鎖(Optimistic Lock),顧名思義,就是很樂觀,每次去那資料的時候都認為別人不會修改,所以 不會上鎖,但是在修改的時候會判斷一下在此期間別人有沒有去更新這個資料,可以使用版本號等機 制。樂觀鎖適用於多讀的應用型別,這樣可以提高吞吐量。redis就是使用這種check-and-set機制實現 事務的。

事務中的watch有什麼用

在執行multi之前,先執行watch key1 [key2 ...],可以監視一個或者多個key。若在事務的exec命令之前,這些key對應的值被其他命令所改動了,那麼事務中所有命令都將被打斷,即事務所有操作將被取消執行。

Redis事務的三大特性

  1. 事務中的所有命令都會序列化、按順序地執行,事務在執行過程中,不會被其他客戶端傳送來的命令請求所打斷。
  2. 佇列中的命令沒有提交(exec)之前,都不會實際被執行,因為事務提交前任何指令都不會被實際執行。
  3. 事務中如果有一條命令執行失敗,後續的命令仍然會被執行,沒有回滾。如果在組隊階段,有1個失敗了,後面都不會成功;如果在組隊階段成功了,在執行階段有那個命令失敗 就這條失敗,其他的命令則正常執行,不保證都成功或都失敗。

如何使用Redis實現佇列功能

  1. 可以使用list實現普通佇列,lpush新增到嘟列,lpop從佇列中讀取資料。
  2. 可以使用zset定期輪詢資料,實現延遲佇列。
  3. 可以使用釋出訂閱實現多個消費者佇列。
  4. 可以使用stream實現佇列。(推薦使用該方式實現)。

如何用Redis實現非同步佇列

  1. 一般使用list結構作為佇列,rpush生產訊息,lpop消費訊息。當lpop沒有訊息的時候,要適當sleep一會再重試。
  2. 如果對方追問可不可以不用sleep呢?list還有個指令叫blpop,在沒有訊息的時候,它會阻塞住直到訊息到來。
  3. 如果對方追問能不能生產一次消費多次呢?使用pub/sub主題訂閱者模式,可以實現1:N的訊息佇列。
  4. 如果對方追問pub/sub有什麼缺點?在消費者下線的情況下, 生產的訊息會丟失 ,可以使用Redis6增加的 stream資料型別 ,也可以使用專業的 訊息佇列如rabbitmq等
  5. 如果對方追問redis如何實現延時佇列? 使用sortedset,拿時間戳作為score ,訊息內容作為key呼叫zadd來生產訊息,消費者用zrangebyscore指令獲取N秒之前的資料輪詢進行處理。

Stream與list、zset和釋出訂閱區別

  1. list可以使用lpush向佇列中新增資料,lpop可以向佇列中讀取資料。list作為訊息佇列無法實現一個訊息多個消費者。如果出現訊息處理失敗,需要手動回滾訊息。
  2. zset在新增資料時,需要新增一個分值,可以根據該分值對資料進行排序,實現延遲訊息佇列的功能。訊息是否消費需要額外的處理。
  3. 釋出訂閱可以實現多個消費者功能,但是釋出訂閱無法實現資料持久化,容易導致資料丟失。並且開啟一個訂閱者無法獲取到之前的資料。
  4. stream借鑑了常用的MQ服務,新增一個訊息就會產生一個訊息ID,每一個訊息ID下可以對應多個消費組,每一個消費組下可以對應多個消費者。可以實現多個消費者功能,同時支援ack機制,減少資料的丟失情況。也是支援資料值持久化和主從複製功能。

如何設計一個網站每日、每月和每天的PV和UV

實現這樣的功能,如果只是統計一個彙總資料,推薦使用HyperLogLog資料型別。Redis HyperLogLog 是用來做基數統計的演算法,HyperLogLog 的優點是,在輸入元素的數量或者體積非常非常大時,計算基數所需的空間總是固定 的、並且是很小的。在 Redis 裡面,每個 HyperLogLog 鍵只需要花費 12 KB 記憶體,就可以計算接近 2^64 個不同元素的基 數。這和計算基數時,元素越多耗費記憶體就越多的集合形成鮮明對比。

Redis如何實現距離檢索功能

實現距離檢索,可以使用Redis中的GEO資料型別。GEO 主要用於儲存地理位置資訊,並對儲存的資訊進行操作,該功能在 Redis 3.2 版本新增。但是GEO適合精度不是很高的場景。由於GEO是在記憶體中進行計算,具備計算速度快的特點。

list和釋出訂閱實現佇列有什麼問題

  1. list可以使用lpush向佇列中新增資料,lpop可以向佇列中讀取資料。list作為訊息佇列無法實現一個訊息多個消費者。如果出現訊息處理失敗,需要手動回滾訊息。
  2. 釋出訂閱可以實現多個消費者功能,但是釋出訂閱無法實現資料持久化,容易導致資料丟失。並且開啟一個訂閱者無法獲取到之前的資料。

Redis如何實現秒殺功能

  1. 在秒殺場景下,超賣是一個非常嚴重的問題。常規的邏輯是先查詢庫存在減少庫存。但在秒殺場景中,無法保證減少庫存的過程中有其他的請求讀取了未減少的庫存資料。
  2. 由於Redis是單執行緒的執行,同一時刻只有一個執行緒進行操作。因此可以使用Redis來實現秒殺減少庫存。
  3. 在Redis的資料型別中,可以使用lpush,decr命令實現秒殺減少庫存。該命令屬於原子操作。

Redis如何實現使用者簽到功能

  1. 使用Redis實現使用者簽到可以使用bitmap實現。bitmap底層資料儲存的是1否者0,佔用記憶體小。
  2. Redis提供的資料型別BitMap(點陣圖),每個bit位對應0和1兩個狀態。雖然內部還是採用String型別儲存,但Redis提供了一些指令用於直接操作BitMap,可以把它看作一個bit陣列,陣列的下標就是偏移量。
  3. 它的優點是記憶體開銷小,效率高且操作簡單,很適合用於簽到這類場景。
  4. 缺點在於位計算和位表示數值的侷限。如果要用位來做業務資料記錄,就不要在意value的值。

Redis如何實現延遲佇列

  1. 使用Redis實現延遲佇列,可以使用zset資料型別。
  2. zset在新增資料時,需要新增一個分值,將時間作為分值,根據該分值對資料進行排序。
  3. 單獨開啟執行緒,根據分值大小定期實行資料。

Redis實現一個積分排行功能

  1. 使用Redis實現積分排行,可以使用zset資料型別。
  2. zset在新增資料時,需要新增一個分值,將積分作為分值,值作為使用者ID,根據該分值對資料進行排序。

字串型別儲存最大容量是多少

一個字串最大可儲存512M。