Redis常見面試題解析(一)

文章目錄

一、 Redis 概念理解

1. 什麼是 Redis?

Redis 全稱爲:Remote Dictionary Server(遠程數據服務),是一個基於內存且支持持久化的高性能 key-value 數據庫。

具備一下幾個基本特徵:

  • 多數據類型
  • 持久化機制
  • 主從同步

2. Redis 的特點有哪些?

  1. Redis 本質上是一個 key-value 類型的數據庫
  2. 整個數據庫都是在內存中進行操作,可定期刷新到磁盤進行持久化存儲
  3. 由於是在內存操作,讀寫能力非常好,每秒可以處理 10 萬次讀寫操作
  4. Redis 支持多種數據結構,提供了豐富的數據類型選擇
  5. Redis 同時支持數據備份,主從配置
  6. Redis 的所有操作都是原子性的

3. Memcache 與 Redis 的區別都有哪些?

  1. 存儲方式不同:Memcache 把數據全部存在內存之中,斷電後會丟失。Redis 所有數據加載在內存,但也會持久化到磁盤,保證數據的持久性。
  2. 支持數據類型不同:Memcache 對數據類型支持相對簡單,只支持 key-value 結構。Redis 有複雜的數據類型。
  3. 底層模型不同:底層實現方式以及客戶端通信應用協議不一樣。 Redis 直接自己構建了 VM 機制。
  4. 運行環境不同:Redis 目前官方只支持 Linux 上運行。

4. Redis 相比 Memcached 有哪些優勢?

  1. Memcached 所有的值均是簡單的字符串,Redis 作爲其替代者,支持更爲豐富的數據類型
  2. Redis 的速度比 Memcached 快很多
  3. Redis 可以持久化其數據

5. 如何實現本地緩存?請描述一下你知道的方式

  1. 程序中定義內存數據結構來實現, 比如說定義一個成員變量Map 或者 List 均可以實現
  2. 使用開源的緩存框架 Ehcache,Ehcache 封裝了對於內存操作的功能
  3. Guava Cache 是 Google 開源的工具集, 提供了緩存的邊界操作工具

6. Redis 通訊協議是什麼?有什麼特點?

Redis 的通信協議是 Redis Serialization Protocol,簡稱 RESP。

有如下特性:

  • 是二進制安全的
  • 在 TCP 層
  • 基於請求—響應的模式

二、Redis 數據結構與指令

1. Redis 支持的數據類型

  1. String(字符串)
  2. list(列表):list 是字符串列表,按照插入順序排序。元素可以在列表的頭部(左邊)或者尾部(右邊)進行添加。
  3. hash(哈希):Redis hash 是一個鍵值對(key-value)集合。Redis hash 是一個 String 類型的 field 和 value 的映射表,hash 特別適合用於存儲對象。
  4. set(集合):Redis 的 set 是 String 類型的無序集合。
  5. zset(sorted set:有序集合):Redis zset 和 set 一樣也是 String 類型元素的集合,且不允許重複的成員。不同的 zset 是每個元素都會關聯一個 double 類型的分數。zset 通過這個分數來爲集合中所有元素進行從小到大的排序。zset 的成員是唯一的,但分數(score)卻可以重複。

2. Redis 常用的命令有哪些?

3. 一個字符串類型的值能存儲最大容量是多少?

512M

4. Redis 各個數據類型最大存儲量分別是多少?

  1. Strings 類型:一個 String 類型的 value 最大可以存儲 512M
  2. Lists 類型:list 的元素個數最多爲 2^32-1 個,也就是 4294967295 個。
  3. Sets 類型:元素個數最多爲 2^32-1 個,也就是 4294967295 個。
  4. Hashes 類型:鍵值對個數最多爲 2^32-1 個,也就是 4294967295 個。
  5. Sorted sets 類型:跟 Sets 類型相似。

5. 請介紹一下 Redis 的數據類型 SortedSet(zset)以及底層實現機制?

zset 有順序,不能重複。在業務場景下,適合做排行榜之類的事情。

底層實現機制:

SortedSet 的實現方式可能有兩種:ziplist 或者 skiplist

當有序集合對象同時滿足以下兩個條件時,對象使用 ziplist 編碼:

  1. 保存的元素數量小於 128;

  2. 保存的所有元素長度都小於 64 字節。    不能滿足上面兩個條件的使用 skiplist 編碼。

  3. ziplist 編碼的有序集合對象使用壓縮列表作爲底層實現,每個集合元素使用兩個緊挨在一起的壓縮列表節點來保存,第一個節點保存元素的成員,第二個節點保存元素的分值。並且壓縮列表內的集合元素按分值從小到大的順序進行排列,小的放置在靠近表頭的位置,大的放置在靠近表尾的位置。

  4. skiplist 編碼的有序集合對象使用 zset 結構作爲底層實現,一個 zset 結構同時包含一個字典和一個跳躍表。字典的鍵保存元素的值,字典的值則保存元素的分值;跳躍表節點的 object 屬性保存元素的成員,跳躍表節點的 score 屬性保存元素的分值。

6. Redis 事務相關命令有哪些?

  1. discard 命令:取消事務,丟棄事務中所有命令。
  2. exec 命令:執行所有事務內的命令。
  3. multi 命令:標記一個事務開始。
  4. unwatch 命令:取消 watch 命令對所有 key 的監視。
  5. watch 命令:監視一個(或多個)key,如果在執行事務之前這個(這些)key 被其他命令所改動,事務將被打斷。

7. 什麼是 Redis 事務?原理是什麼?

  • Redis 中的事務是一組命令的集合,是 Redis 的最小執行單位,一個事務要麼都執行,要麼都不執行。

  • Reids 事務保證一個事務內的命令依次執行,而不會被其他命令插入。

  • Redis 事務的原理是先將屬於一個事務的命令發送給 Redis,然後依次執行這些命令。

8. Redis 事務的注意點有哪些?

  1. 不支持回滾,如果事務中有錯誤的操作,無法回滾到處理前的狀態,需要開發者處理。
  2. 在執行完當前事務內所有指令前,不會同時執行其他客戶端的請求。

9. Redis 爲什麼不支持回滾?

Redis 事務不支持回滾,如果遇到問題,會繼續執行餘下的命令。 這一點和關係型數據庫不太一致。這樣處理的原因有:

  1. 只有語法錯誤,Redis纔會執行失敗,例如錯誤類型的賦值, 這就是說從程序層面完全可以捕獲以及解決這些問題
  2. 支持回滾需要增加很多工作,不支持的情況下,Redis 可以保持簡單、速度快的特性

10. 請介紹一下 Redis 的 Pipeline(管道),以及使用場景

Redis 客戶端與服務端通信模型使用的 TCP 協議進行連接, 那麼在單個指令的執行過程中,都會存在“交互往返”的時間。

Redis 提供了批量操作命令,例如 mget、mset 等,能夠一定程度上節省這類時間,但大部分命令還是不支持批量操作。

因此 Pipeline 功能,能夠改善這一類問題。 Pipeline 將一組 Redis 命令進行組裝,一次性傳輸給 Redis,再將這些命令執行結果,按照順序返回給客戶端。

適用場景:有批量的數據寫入 Redis,並且這些數據允許一定比例的寫入失敗,那麼這種場景就可以適用 Pipeline。失敗的數據後期進行補償即可。

11. 請說明一下 Redis 的批量命令與 Pipeline 有什麼不同?

  1. 批量命令保證原子性的,Pipeline 非原子性
  2. 批量命令是一個命令對應多個 key,Pipeline 支持多個命令
  3. 批量命令是 Redis 服務端實現,而 Pipeline 是需要服務端和客戶端共同實現

12. 請介紹一下 Redis 的發佈訂閱功能

Redis 提供了基於“發佈/訂閱”模式的消息機制,消息發佈者和訂閱者不能直接通信,客戶端發佈消息的時候指定發送的頻道,然後訂閱了該頻道的用戶可以接收到該消息。具體指令如下:

  1. 發佈消息:publish channel message
  2. 訂閱消息:subscribe channel [……]
  3. 退訂消息:punsubscribe

13. Redis 的鏈表數據結構的特徵有哪些?

  • 雙端引用:鏈表的最前和最後節點都有引用,獲取前後節點的複雜度爲 o(1)
  • 無環鏈表:對於鏈表的訪問都是以 null 結束
  • 長度計數器:通過 len 屬性來記錄鏈表長度

14. 請介紹一下 Redis 的 String 類型底層實現?

Redis 底層實現了簡單動態字符串的類型(SSD),來表示 String 類型。 沒有直接使用 C 語言定義的字符串類型。

struct sdshdr{
    //記錄 buf 數組中已使用字節的數量
    //等於 SDS 保存字符串的長度
    int len;
    //記錄 buf 數組中未使用字節的數量
    int free;
    //字節數組,用於保存字符串
    char buf[];
}

15. Redis 的 String 類型使用 SSD 方式實現的好處?

  1. 避免緩衝區溢出:進行字符修改時候,可以根據 len 屬性來檢查空間是否滿足要求
  2. 減少內存分配次數:len 和 free 兩個屬性,可以協助進行空間預分配以及惰性空間釋放
  3. 二進制安全:SSD 不是以空字符串來判斷是否結束,而是以 len 屬性來判斷字符串是否結束
  4. 常數級別獲取字符串長度:獲取字符串的長度只需要讀取 len 屬性就可以獲取
  5. 兼容 C 字符串函數:可以重用 C 語言庫的 的一部分函數

16. 設置鍵的生存時間和過期時間有哪些命令?

  • EXPIRE 以秒爲單位,設置鍵的生存時間
  • PEXPIRE 以毫秒爲單位,設置鍵的生存時間
  • EXPIREAT 以秒爲單位,設置鍵的過期 UNIX 時間戳
  • PEXPIREAT 以毫秒爲單位,設置鍵的過期 UNIX 時間戳

三 、Redis 高併發處理策略

1. 爲什麼 Redis 需要把所有數據放到內存中?

追求更快的讀寫速度,並異步方式持久化存儲到磁盤。 如果不將數據放到內存中,磁盤的 I/O 速度會嚴重影響 Redis 的性能。

2. Redis 是單線程的嗎?

是。這裏的單線程指的是 Redis 網絡請求模塊使用了一個線程(所以不需考慮併發安全性),即一個線程處理所有網絡請求,其他模塊仍用了多個線程。

3. Redis 爲什麼設計成單線程的?

  1. 絕大部分請求是純粹的內存操作(非常快速)
  2. 採用單線程,避免了不必要的上下文切換和競爭條件
  3. 非阻塞 IO,內部採用 epoll,epoll 中的讀、寫、關閉、連接都轉化成了事件,然後利用 epoll 的多路複用特性,避免 IO 代價。

4. 什麼是緩存穿透?怎麼解決?

緩存穿透是指緩存中查詢一個不存在的數據,需要去數據庫中獲取。

如果數據庫也查不到結果,將不會同步到緩存,導致這個不存在數據每次請求都要到數據庫查詢,失去了緩存的意義。

解決方法有兩個:

  1. 布隆過濾(Bloom filter)

    將所有查詢的參數都存儲到一個 bitmap 中,在查詢緩存之前,先再找個 bitmap 裏面進行驗證。

    如果 bitmap 中存在,則進行底層緩存的數據查詢; 如果 bitmap 中不存在查詢參數,則進行攔截,不再進行緩存的數據查詢。

    適用範圍:可以用來實現數據字典,進行數據的判重,或者集合求交集

  2. 緩存空對象

    如果查詢返回的數據爲空,仍然把這個空結果進行緩存。那麼再次用相同 key 獲取數據的時候,即使不存在的數據,緩存也可以直接返回空值,避免重複訪問 DB。

    緩存空對象有兩個不足之處:

    1. 緩存層將存儲更多的鍵值對,如果是惡意的隨機訪問,將造成很多內存空間的浪費。這個不足之處可以通過將這類數據設置很短的過期時間來控制。
    2. DB 與緩存數據不一致。這種可以考慮通過異步消息來進行數據更新的通知,在一定程度上減少這類不一致的時間。

5. 什麼是緩存雪崩? 怎麼解決?

在集中的一段時間內,有大量的緩存失效,導致大量的訪問沒有命中緩存,從而將所有查詢進行數據庫訪問,導致數據庫的壓力增大,從而造成了緩存雪崩。

比如,如果要做一個促銷活動,我們將商品信息都刷新到緩存, 過期時間統一爲 30 分鐘。那麼在 30 分鐘之後,這批商品將全部過期。這時候這批商品的訪問查詢,都落到了數據庫,對於數據庫而言,這一刻的壓力會非常大。從而造成系統整體性風險。

解決方案:

  1. 分散失效時間

    分析緩存數據的特點,儘量將熱點緩存的失效時間均勻分佈。 比如說將相同類型的緩存的失效時間設置成一個在一定區間內的隨機值。從而有效的分散失效時間。

  2. DB 訪問限制

    對數據的訪問進行限流性質的操作。比如說對數據庫訪問進行加鎖的處理或者限流相關的處理。

  3. 多級緩存設計

    一級緩存爲基礎緩存,緩存失效時間設置一個較長時間, 二級緩存爲應用緩存,失效時間正常設置,一般會比較短。 當二級緩存失效的時候,再從一級緩存裏面獲取。

6. 緩存的更新策略有幾種?分別有什麼注意事項?

緩存的更新策略包含:

  1. 先更新數據庫,再更新緩存
  2. 先刪除緩存,再更新數據庫
  3. 先更新數據庫,再刪除緩存

策略一:先更新數據庫,再更新緩存

  1. 這種策略會導致線程安全問題

    例如:線程 1 更新了數據庫,線程 2 也更新數據庫, 這時候由於某種原因,線程 2 首先更新了緩存,線程 1 後續更新。 這樣就導致了髒數據的問題。 因爲目前數據庫中存儲的線程 2 更新後的數據,而緩存存儲的是線程1更新的老數據。

  2. 更新緩存的複雜度相對較高

    數據寫入數據庫之後,一般存入緩存的數據都要經過一系列的加工計算,然後寫入緩存。 這時候更新緩存相比較於直接刪除緩存要比較複雜。

策略二:先刪除緩存,再更新數據庫

這種策略可能導致數據不一致的問題。線程 1 寫數據刪除緩存;這時候有線程 2 查詢該緩存,發現不存在,則去訪問數據庫,得到舊值放入緩存;線程 1 更新數據庫。這時候就出現了數據不一致的問題。 如果緩存沒有過期時間,這個髒數據一直存在。

解決方案:在寫數據庫成功之後, 再次淘汰緩存一次。

策略三:先更新數據庫,再刪除緩存

可能會造成比較短暫的數據不一致。在更新完成數據庫, 還沒有刪除緩存的時刻,如果有緩存數據訪問, 就會造成數據不一致的情形。 但這種如果數據同步機制比較科學,一般都會比較快, 不一致的影響比較小。

7. 請介紹幾個可能導致 Redis 阻塞的原因

內部原因:

  1. Redis 的 API 或者指令數據結構使用不合理
  2. Redis 主機 CPU 負載過高,導致系統崩潰
  3. 持久化工作資源佔用過多

外部原因:

  1. CPU 競爭
  2. 內存交換
  3. 網絡問題

8. 怎麼去發現 Redis 阻塞異常情況?

  1. 通過應用服務監控發現

    當 Redis 阻塞的時候,線上應用服務應該會感知發現。比如說發現 Redis 鏈接超時等。這種就需要應用服務增加對於異常的統計,並針對 Redis 相關的異常,進行報警。

  2. 通過 Redis 自身監控系統

    藉助 Redis 監控系統發現阻塞問題,當監控系統發現各個監控指標存在異常的時候,發送報警。 指標有:CPU/內存/磁盤等, 慢查詢,命令耗時增加等。

發佈了139 篇原創文章 · 獲贊 42 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章