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。