Redis常考的知識點

cloud還是dubbo?發表於2020-12-01

title: Redis常考的知識點
categories: 資料庫
tags: Redis


一、Redis是什麼,有什麼功能?

​ Redis 是一個使用 C 語言開發的資料庫,也是一種Key-Value資料庫,資料儲存在記憶體中,常用作快取資料庫,速度較快。

功能:常用來作快取,分散式鎖,訊息佇列,排行榜等功能

二、Redis 和 Memcached 的對比
  1. Memcached 只支援String型別,Reids支援更為豐富的資料型別

  2. Redis支援資料的持久化

  3. Redis的速度更快

  4. Memcached 是多執行緒,非阻塞IO複用的網路模型,Redis使用單執行緒的IO複用

    相同點就是都是記憶體型資料庫,都有過期策略,效能都不錯,常用來做快取

三、Redis支援的資料型別以及底層資料結構
  1. ​ string,底層資料結構為簡單動態字串(simple dynamic string,SDS),SDS 可以儲存二進位制資料,並且獲取字串長度複雜度為 O(1)(C 字串為 O(N)),此外,Redis 的 SDS API 是安全的,不會造成緩衝區溢位。
  2. list,底層資料結構是連結串列,C 語言並沒有實現連結串列,所以 Redis 實現了自己的連結串列資料結構。Redis 的 list 的實現為一個 雙向連結串列,即可以支援反向查詢和遍歷,更方便操作,不過帶來了部分額外的記憶體開銷,獲取表頭表尾和連結串列長度都是O(1)複雜度
  3. set,是一種無序集合,集合中的元素沒有先後順序。需要儲存一個列表資料,又不希望出現重複時,可以選擇set,並且 set 提供了判斷某個成員是否在一個 set 集合內的重要介面
  4. hash,底層是字典結構,字典在Redis中廣泛被使用,包括資料庫和雜湊鍵,每個字典有兩個雜湊表,雜湊表使用的是鏈地址法解決雜湊衝突,擴容時採用的是漸進式雜湊
  5. Zset ,有點像是 Java 中 HashMap 和 TreeSet 的結合體,底層使用跳錶實現
四、Redis為什麼是單執行緒?

​ Redis核心就是我所有資料都在記憶體裡,單執行緒操作效率就是最高的,為什麼要多執行緒呢?多執行緒會有一個代價,就是上下文切換,對於當個CPU繫結一塊記憶體的資料,沒有上下文切換就是效率最高的;相反,如果是多次磁碟IO的話,多執行緒更優,因為在尋道和選擇的時間,執行緒在阻塞的等待磁碟,這個時間CPU可以去處理其他執行緒。

​ 總之就是CPU不是redis的瓶頸,reids的瓶頸是機器記憶體和網路頻寬,而單執行緒既不會成為瓶頸,又容易實現,那肯定單執行緒。

五、Redis是單執行緒嗎?

​ 將第五題和第四題放在一起就是為了分辨一個大部分人的誤區,大家稱Redis是單執行緒,但是Redis並不是單執行緒,比如持久化的時候就會fork子執行緒,包括網路IO也不是單執行緒,Redis的單執行緒指的是事件處理模型的單執行緒

​ Redis 基於 Reactor 模式開發了自己的網路事件處理器:這個處理器被稱為檔案事件處理器(file event handler),通過IO 多路複用程式 來監聽來自客戶端的大量連線(或者說是監聽多個 socket),它會將感興趣的事件及型別(讀、寫)註冊到核心中並監聽每個事件是否發生。

​ 檔案事件處理器(file event handler)主要是包含 4 個部分:

  • 多個 socket(客戶端連線)
  • IO 多路複用程式(支援多個客戶端連線的關鍵)
  • 檔案事件分派器(將 socket 關聯到相應的事件處理器)
  • 事件處理器(連線應答處理器、命令請求處理器、命令回覆處理器)
六、什麼是快取雪崩,什麼是快取穿透?

快取雪崩是指快取中資料大批量到過期時間,而查詢資料量巨大,引起資料庫壓力過大甚至down機。

解決方案就是:

  1. ​ 設定快取新增隨機過期時間,防止大量快取同時失效
  2. 採用Reids高可用架構比如主從或者Redis Cluster,避免Redis掛掉
  3. 及時利用本地快取和限流,防止下游資料庫崩潰
  4. 開啟持久化,重啟後快速恢復資料

快取穿透是指快取和資料庫中都沒有的資料,而使用者不斷髮起請求,如發起為id為“-1”的資料或id為特別大不存在的資料。這時的使用者很可能是攻擊者,攻擊會導致資料庫壓力過大

解決方案就是:

  1. 訪問一個不存在的引數時,將這個結果進行快取,下次直接返回null

  2. 使用布隆過濾器進行過濾

七、Redis的過期鍵的刪除策略
  1. ​ 惰性刪除 :只會在取出key的時候才對資料進行過期檢查。這樣對CPU最友好,但是可能會造成太多過期 key 沒有被刪除。

  2. ​ 定期刪除 : 每隔一段時間抽取一批 key 執行刪除過期key操作。並且,Redis 底層會通過限制刪除操作執行的時長和頻率來減少刪除操作對CPU時間的影響。

八、Redis的記憶體淘汰機制

​ Redis 提供 6 種資料淘汰策略:

  1. volatile-lru(least recently used):從已設定過期時間的資料集(server.db[i].expires)中挑選最近最少使用的資料淘汰
  2. volatile-ttl:從已設定過期時間的資料集(server.db[i].expires)中挑選將要過期的資料淘汰
  3. volatile-random:從已設定過期時間的資料集(server.db[i].expires)中任意選擇資料淘汰
  4. allkeys-lru(least recently used):當記憶體不足以容納新寫入資料時,在鍵空間中,移除最近最少使用的 key(這個是最常用的)
  5. allkeys-random:從資料集(server.db[i].dict)中任意選擇資料淘汰
  6. no-eviction:禁止驅逐資料,也就是說當記憶體不足以容納新寫入資料時,新寫入操作會報錯。這個應該沒人使用吧!

4.0 版本後增加以下兩種:

  1. volatile-lfu(least frequently used):從已設定過期時間的資料集(server.db[i].expires)中挑選最不經常使用的資料淘汰
  2. allkeys-lfu(least frequently used):當記憶體不足以容納新寫入資料時,在鍵空間中,移除最不經常使用的 key
九、Reids和資料庫的雙寫一致性

​ a. 先更新資料再更新快取的話是不行的,更新結束,更新快取失敗豈不是gg

​ b. 先刪快取再更新資料庫看起來可以,實際上也有問題:

​ i. A刪快取,B拿舊資料,放到快取裡,A更新資料庫,就出問題了

​ ii. 解決方案:延時雙刪(但是第二次刪除還是會出現不一致問題),(要設定過期時間,保證最終一致性)

​ c. 先更新資料庫再刪快取

​ i. 問題:快取剛好失效,然後A拿到舊資料,然後B更新快取刪快取,A把舊資料放到資料庫,但是碰上快取剛好失效的概率比較低

十、Redis的持久化方式

快照(snapshotting)持久化(RDB):Redis 可以通過建立快照來獲得儲存在記憶體裡面的資料在某個時間點上的副本。Redis 建立快照之後,可以對快照進行備份,可以將快照複製到其他伺服器從而建立具有相同資料的伺服器副本(Redis 主從結構,主要用來提高 Redis 效能),還可以將快照留在原地以便重啟伺服器的時候使用。快照持久化是 Redis 預設採用的持久化方式

AOF(append-only file)持久化:與快照持久化相比,AOF 持久化 的實時性更好,因此已成為主流的持久化方案。預設情況下 Redis 沒有開啟 AOF(append only file)方式的持久化,可以通過 appendonly 引數開啟

十一、Redis的漸進式擴容

​ 每個字典有兩個雜湊表,一個ht[0],一個ht[1],擴充套件或收縮雜湊表需要將 ht[0] 裡面的所有鍵值對 rehash 到 ht[1] 裡面。如果雜湊表裡儲存的鍵值對數量非常大, 那麼要一次性將這些鍵值對全部 rehash 到 ht[1] 的話, 龐大的計算量可能會導致伺服器在一段時間內停止服務。為了避免此情況,所以採用漸進式雜湊。

​ 雜湊表漸進式 rehash 的詳細步驟:

  1. ht[1] 分配空間, 讓字典同時持有 ht[0]ht[1] 兩個雜湊表。
  2. 在字典中維持一個索引計數器變數 rehashidx , 並將它的值設定為 0 , 表示 rehash 工作正式開始。
  3. 在 rehash 進行期間, 每次對字典執行新增、刪除、查詢或者更新操作時, 程式除了執行指定的操作以外, 還會順帶將 ht[0] 雜湊表在 rehashidx 索引上的所有鍵值對 rehash 到 ht[1] , 當 rehash 工作完成之後, 程式將 rehashidx 屬性的值增一。
  4. 隨著字典操作的不斷執行, 最終在某個時間點上, ht[0] 的所有鍵值對都會被 rehash 至 ht[1] , 這時程式將 rehashidx 屬性的值設為 -1, 表示 rehash 操作已完成。

在漸進式 rehash 進行期間, 字典的刪除(delete)、查詢(find)、更新(update)等操作會在兩個雜湊表上進行: 比如說, 要在字典裡面查詢一個鍵的話, 程式會先在 ht[0] 裡面進行查詢, 如果沒找到的話, 就會繼續到 ht[1] 裡面進行查詢, 諸如此類。

另外, 在漸進式 rehash 執行期間, 新新增到字典的鍵值對一律會被儲存到 ht[1] 裡面, 而 ht[0] 則不再進行任何新增操作

十二、Redis分散式鎖(後續會有單獨文章)

​ 方法一:SETNX key value

​ 將 key 的值設為 value,當且僅當 key 不存在。
​ 若給定的 key 已經存在,則 SETNX 不做任何動作。
​ SETNX 是SET if Not eXists的簡寫

​ 方法二(Redlock演算法):

​ 起 5 個 master 節點,分佈在不同的機房儘量保證可用性。為了獲得鎖,client 會進行如下操作:

  1. 得到當前的時間,微秒單位
  2. 嘗試順序地在 5 個例項上申請鎖,當然需要使用相同的 key 和 random value,這裡一個 client 需要合理設定與 master 節點溝通的 timeout 大小,避免長時間和一個 fail 了的節點浪費時間
  3. 當 client 在大於等於 3 個 master 上成功申請到鎖的時候,且它會計算申請鎖消耗了多少時間,這部分消耗的時間採用獲得鎖的當下時間減去第一步獲得的時間戳得到,如果鎖的持續時長(lock validity time)比流逝的時間多的話,那麼鎖就真正獲取到了。
  4. 如果鎖申請到了,那麼鎖真正的 lock validity time 應該是 origin(lock validity time) - 申請鎖期間流逝的時間
  5. 如果 client 申請鎖失敗了,那麼它就會在少部分申請成功鎖的 master 節點上執行釋放鎖的操作,重置狀態

後續將會推送Reids叢集的知識,敬請期待!

相關文章