redis筆記【面試】
- 前言
- 推薦
- redis筆記【面試】
- 【Redis】關系型資料庫與非關系型資料庫
- 【Redis】基本操作及特性分析
- 【Redis】基礎結構(一):String 類型指令、應用、原理
- 【Redis】基礎結構(二):Hash 類型指令、應用、原理
- 【Redis】基礎結構(三):List 類型指令、應用、原理
- 【Redis】基礎結構(四):Set 類型指令、應用、原理
- 【Redis】基礎結構(五):ZSet 類型指令、應用、原理
- 【Redis】原理分析(一):底層結構、持久化、緩存淘汰政策
- 【Redis】事務淺析
- 【Redis】高階使用(一):幾個進階指令
- 【Redis】高階使用(二):管道與 lua 腳本
- 【Redis】高階使用(三):緩存穿透,緩存擊穿,緩存雪崩及解決方案
- 【Redis】鍵值設計規範
- 最後
前言
2022/12/1 10:26
以下内容源自A minor
僅供學習交流使用
推薦
Redis
redis筆記【面試】
【Redis】關系型資料庫與非關系型資料庫
【Redis】基本操作及特性分析
【Redis】基礎結構(一):String 類型指令、應用、原理
字元串類型的内部編碼有三種:
int:存儲 8 個位元組的長整型(long,2^63-1)
embstr:代表 embstr 格式的 SDS, 存儲小于 44 個位元組的字元串
raw:代表 raw 格式的 SDS,存儲大于 44 個位元組的字元串(3.2 版本之前是 39 位元組)
問題一:為什麼 Redis 要用 SDS 實作字元串?
SDS
空間預配置設定:SDS 會預先配置設定一部分空閑空間,當字元串内容添加時不需要做空間申請的工作
空間惰性釋放:當字元串從 buf 數組中移除時,空閑出來的空間不會立馬被記憶體回收,防止新增字元串的内容寫入時空間不夠而臨時申請空間
問題二:embstr 和 raw 的差別?
embstr 的使用隻配置設定一次記憶體空間(因為 RedisObject 和 SDS 是連續的),而 raw 需要配置設定兩次記憶體空間(分别為 RedisObject 和 SDS 配置設定空間)。
是以與 raw 相比,embstr 的好處在于建立時少配置設定一次空間,删除時少釋放一次空間,以及對象的所有資料連在一起,尋找友善。 而 embstr 的壞處也很明顯,如果字元串的長度增加需要重新配置設定記憶體時,整個 RedisObject 和 SDS 都需要重新配置設定空間,是以 Redis 中的 embstr 實作為隻讀。
問題三:int 和 embstr 什麼時候轉化為 raw?
當 int 數 據 不 再 是 整 數 , 或大小超過了 long 的範圍 (2^63-1=9223372036854775807)時,自動轉化為 embstr。
問題四:明明沒有超過門檻值,為什麼變成 raw?
對于 embstr,由于其實作是隻讀的,是以在對 embstr 對象進行修改時,都會先轉化為 raw 再進行修改。 是以,隻要是修改 embstr 對象,修改後的對象一定是 raw 的,無論是否達到了 44 個位元組。
問題五:當長度小于門檻值時,會還原嗎?
關于 Redis 内部編碼的轉換,都符合以下規律:編碼轉換在 Redis 寫入資料時完成,且轉換過程不可逆,隻能從小記憶體編碼向大記憶體編碼轉換(但是不包括重新 set)
【Redis】基礎結構(二):Hash 類型指令、應用、原理
Hash 底層實作采用了 ZipList 和 HashTable 兩種實作方式。
當同時滿足如下兩個條件時底層采用了 ZipList 實作,一旦有一個條件不滿足時,就會被轉碼為 HashTable 進行存儲
Hash 中存儲的所有元素的 key 和 value 的長度都小于等于 64byte
Hash 中存儲的元素個數小于 512
ZipList(壓縮清單) 方式
ZipList 的優缺點比較
優點:記憶體位址連續,省去了每個元素的頭尾節點指針占用的記憶體
缺點:對于删除和插入操作比較可能會觸發連鎖更新反應,比如在 list 中間插入删除一個元素時,在插入或删除位置後面的元素可能都需要發生相應的移動操作
HashTable 方式
在 Redis 中,hashtable 被稱為字典(dictionary),它是一個數組+連結清單的結構。
問題:為什麼要定義兩個哈希表呢?
ht[2] redis 的 hash,預設使用的是 ht[0],ht[1]不會初始化和配置設定空間。
哈希表 dictht 是用鍊位址法來解決碰撞問題的。在這種情況下,哈希表的性能取決于它的大小(size 屬性)和它所儲存的節點的數量(used 屬性)之間的比率,比率在 1:1 時,哈希表的性能最好;。
如果節點數量比哈希表的大小要大很多的話(這個比例用 ratio 表示,ratio = used / size),那麼哈希表就會退化成多個連結清單,哈希表本身的性能優勢就不再存在,是以,在這種情況下需要擴容。
問題:為什麼要定義兩個哈希表呢?
ht[2] redis 的 hash,預設使用的是 ht[0],ht[1]不會初始化和配置設定空間。
哈希表 dictht 是用鍊位址法來解決碰撞問題的。在這種情況下,哈希表的性能取決于它的大小(size 屬性)和它所儲存的節點的數量(used 屬性)之間的比率,比率在 1:1 時,哈希表的性能最好;。
如果節點數量比哈希表的大小要大很多的話(這個比例用 ratio 表示,ratio = used / size),那麼哈希表就會退化成多個連結清單,哈希表本身的性能優勢就不再存在,是以,在這種情況下需要擴容。
dict_can_resize 為 1 并且 dict_force_resize_ratio 已使用節點數和字典大小之間的 比率超過 1:5,觸發擴容
rehash 的步驟:
1.為字元 ht[1] 哈希表配置設定空間,這個哈希表的空間大小取決于要執行的操作,以及 ht[0] 目前包含的鍵值對的數量。
2.将所有的 ht[0] 上的節點 rehash 到 ht[1]上,重新計算 hash 值和索引,然後放入指定的位置。
3.當 ht[0]全部遷移到了 ht[1]之後,釋放 ht[0] 的空間,将 ht[1] 設定為 ht[0] 表, 并建立新的 ht[1],為下次 rehash 做準備
【Redis】基礎結構(三):List 類型指令、應用、原理
在 Redis3.2 之前,List 底層采用了 ZipList 和 LinkedList 實作的。
初始化的 List 使用的 ZipList,List 滿足以下兩個條件時則一直使用 ZipList 作為底層實作,當以下兩個條件任一一個不滿足時,則會被轉換成 LinkedList
List 中存儲的每個元素的長度小于 64byte
元素個數小于 512
3.2 版本之後,List 底層采用 QuickList 來存儲。quicklist 存儲了一個雙向連結清單,每個節點都是一個 ziplist。
ZipList 方式
壓縮清單是 redis 為了節約記憶體而開發的,是由一系列的特殊編碼的連續記憶體塊組成的雙向連結清單。一個壓縮清單可以包含任意多個節點(entry),每個節點可以儲存一個位元組數組或者一個整數值,值的類型和長度由節點的encoding屬性決定。
LinkedList 方式
LinkedList 都比較熟悉了,是由一系列不連續的記憶體塊通過指針連接配接起來的雙向連結清單。
QuickList 方式
在 Redis3.2 版本之後,Redis 集合采用了 QuickList 作為 List 的底層實作,QuickList 其實就是結合了 ZipList 和 LinkedList 的優點設計出來的。
【Redis】基礎結構(四):Set 類型指令、應用、原理
Set 集合采用了整數集合和字典兩種方式來實作的,當滿足如下兩個條件的時候,采用整數集合實作;一旦有一個條件不滿足時則采用字典來實作。
Set 集合中的所有元素都為整數
Set 集合中的元素個數不大于 512(預設 512,可以通過修改 set-max-intset-entries 配置調整集合大小)
整數集合(intset)
intset 顧名思義,是由整數組成的集合。實際上,intset 是一個由整數組成的有序集合,進而便于在上面進行二分查找,用于快速地判斷一個元素是否屬于這個集合。
字典(dict)
在 Redis 中,hashtable 被稱為字典(dictionary),它是一個數組+連結清單的結構。
在之前的文章我們介紹了,Redis 的 KV 結構是通過一個 dictEntry 來實作的:
【Redis】基礎結構(五):ZSet 類型指令、應用、原理
Zset 底層同樣采用了兩種方式來實作,分别是 ZipList 和 SkipList。當同時滿足以下兩個條件時,采用 ZipList 實作;反之采用 SkipList 實作。
Zset 中儲存的元素個數小于 128。(通過修改 zset-max-ziplist-entries 配置來修改)
Zset 中儲存的所有元素長度小于 64byte。(通過修改 zset-max-ziplist-values 配置來修改)
ZipList 方式
SkipList 方式
跳表實際就是:多級有序連結清單,這樣我們就可以抽出索引節點,進而降低查詢的複雜度。
PS:為什麼不用 AVL 樹或者紅黑樹?因為 skiplist 更加簡潔(關于跳表可以參考這篇文章…)
-------
【資料結構】跳表:Skip List 特性淺析
1.跳表 = 有序連結清單+多級索引
2.時間複雜度分析
2.1 查詢:O(logn))
跳表其實是基于單連結清單實作了二分查找
2.2 插入:O(logn)
2.3 删除
3.空間複雜度分析
O(n)
4.索引動态更新
4.1 退化成連結清單的問題
4.2 随機函數更新索引
們通過一個随機函數,來決定将這個結點插入到哪幾級索引中,比如随機函數生成了值 K,那我們就将這個結點添加到第一級到第 K 級這 K 級索引中。
5.跳表應用
5.1 redis有序集合
按照區間來查找資料這個操作,紅黑樹的效率沒有跳表高。
對于按照區間查找資料這個操作,跳表可以做到 O(logn) 的時間複雜度定位區間的起點, 然後在原始連結清單中順序往後周遊就可以了。這樣做非常高效。
跳表更容易代碼實作。 雖然跳表的實作也不簡單,但比起紅黑樹來說還是好懂、好寫多了,而簡單就意味着可讀性好,不容易出錯。
跳表更加靈活,它可以通過改變索引建構政策,有效平衡執行效率和記憶體消耗。
5.2 跳表與紅黑樹
---------
【Redis】原理分析(一):底層結構、持久化、緩存淘汰政策
1.Redis 底層結構
Redis 單線程為什麼還能這麼快?
記憶體
Redis 單線程如何處理那麼多的并發用戶端連接配接?
IO多路複用
2.資料持久化
2.1 RDB快照(snapshot)(預設)
2.2 AOF(append-only file)
2.3 混合持久化
3.緩存淘汰政策
【Redis】事務淺析
1.事務的用法
2.事務可能遇到的問題
3.為什麼 Redis 事務不支援復原
4.總結
Redis 的事務有如下幾個特點:
按進入隊列的順序執行:事務中的所有指令都會序列化、按順序地執行。
不受其他用戶端請求影響:事務在執行的過程中,不會被其他用戶端發送來的指令請求所打斷。
沒有隔離級别的概念:隊列中的指令沒有送出之前都不會實際的被執行,因為事務送出前任何指令都不會被實際執行(也就不存在”事務内的查詢要看到事務裡的更新,在事務外查詢不能看到”這個讓人萬分頭痛的問題)
不保證原子性:Redis 同一個事務中如果有一條指令執行失敗,其後的指令仍然會被執行,沒有復原
在傳統的關系式資料庫中,常常用 ACID 性質來檢驗事務功能的安全性。Redis 事務保證了其中的一緻性(C)和隔離性(I),但并不保證原子性(A)和持久性(D)。
【Redis】高階使用(一):幾個進階指令
1.keys:全量周遊鍵
2.scan:漸進式周遊鍵
3.Info:檢視 redis 服務運作資訊
【Redis】高階使用(二):管道與 lua 腳本
【Redis】高階使用(三):緩存穿透,緩存擊穿,緩存雪崩及解決方案
1.緩存穿透
緩存穿透是指查詢一個根本不存在的資料, 緩存層和存儲層都不會命中, 通常出于容錯的考慮, 如果從存儲層查不到資料則不寫入緩存層。
緩存穿透将導緻不存在的資料每次請求都要到存儲層去查詢, 失去了緩存保護後端存儲的意義。 造成緩存穿透的基本原因有兩個:
自身業務代碼或者資料出現問題。
一些惡意攻擊、 爬蟲等造成大量空命中。
緩存穿透問題解決方案:
方案一:緩存空對象
方案二:布隆過濾器
2.緩存擊穿
開發人員使用“緩存+過期時間”的政策既可以加速資料讀寫, 又保證資料的定期更新, 這種模式基本能夠滿足絕大部分需求。 但是有兩個問題如果同時出現, 可能就會對應用造成緻命的危害:
目前key是一個熱點key(例如一個熱門的娛樂新聞),并發量非常大。
重建緩存不能在短時間完成, 可能是一個複雜計算, 例如複雜的SQL、 多次IO、 多個依賴等。
在緩存失效的瞬間,有大量線程來重建緩存, 造成後端負載加大, 甚至可能會讓應用崩潰。 要解決這個問題主要就是要避免大量線程同時重建緩存。 我們可以利用互斥鎖來解決,此方法隻允許一個線程重建緩存, 其他線程等待重建緩存的線程執行完, 重新從緩存擷取資料即可。
3.緩存雪崩
緩存雪崩是指,我們設定緩存時采用了相同的過期時間,是以大批量緩存在同一時間失效,導緻流量會像奔逃的野牛一樣, 打向後端存儲層。
另外,預防和解決緩存雪崩問題, 還可以從以下三個方面進行着手:
保證緩存層服務高可用性,比如使用Redis Sentinel或Redis Cluster。
依賴隔離元件為後端限流并降級。比如使用Hystrix限流降級元件。
提前演練。 在項目上線前, 演練緩存層宕掉後, 應用以及後端的負載情況以及可能出現的問題, 在此基礎上做一些預案設定。
【Redis】鍵值設計規範
1.key 設計
【建議】可讀性和可管理性。
【建議】 簡潔性。
【強制】不要包含特殊字元。
2.value 設計
【建議】選擇适合的資料類型。
【建議】控制key的生命周期,redis不是垃圾桶。
建議使用expire設定過期時間(條件允許可以打散過期時間,防止集中過期)。
【強制】拒絕 bigkey。(防止網卡流量、慢查詢)
關于 bigKey
問題一:Redis 的 bigkey 具體指什麼?
在Redis中,一個字元串大512MB,一個二級資料結構(例如hash、list、set、zset)可以存儲大約40億個(2^32-1)個元素,但實際中如果下面兩種情況,一般就會認為它是bigkey:
字元串類型:它的big展現在單個value值很大,一般認為超過10KB就是bigkey
非字元串類型:哈希、清單、集合、有序集合,它們的big展現在元素個數太多。
一般來說,string類型控制在10KB以内,hash、list、set、zset元素個數不要超過5000。 反例:一個包含200萬個元素的list。
問題二:bigkey 有什麼危害?
導緻redis阻塞(過期删除)。有個bigkey,它安分守己(隻執行簡單的指令,例如hget、lpop、zscore等),但它設定了過期時間,當它過期後,會被删除,如果沒有使用Redis 4.0的過期異步删除(lazyfree-lazyexpire yes),就會存在阻塞Redis的可能性。
網絡擁塞。bigkey也就意味着每次擷取要産生的網絡流量較大,假設一個bigkey為1MB,用戶端每秒通路量為1000,那麼每秒産生1000MB的流量,對于普通的千兆網卡(按照位元組算是128MB/s)的伺服器來說簡直是滅頂之災,而且一般伺服器會采用單機多執行個體的方式來部署,也就是說一個bigkey 可能會對其他執行個體也造成影響,其後果不堪設想。
問題三:什麼情況下會産生 bigkey?
大多數情況下,bigkey的産生都是由于程式設計不當,或者對于資料規模預料不清楚造成的,來看幾個例子:
社交類:粉絲清單,如果某些明星或者大v不精心設計下,必是bigkey。
統計類:例如按天存儲某項功能或者網站的使用者集合,除非沒幾個人用,否則必是bigkey。
緩存類:将資料從資料庫load出來序列化放到Redis裡,這個方式非常常用,但有兩個地方需要注意:
第一,是不是有必要把所有字段都緩存
第二,有沒有相關關聯的資料,有的同學為了圖友善把相關資料都存一個key下,産生bigkey
問題四:如果出現了 bigkey,那該如何優化?
第一個想法就是拆,看能不能将bigkey縮小:
big list: list1、list2、…listN
big hash:可以将資料分段存儲,比如一個大的key,假設存了1百萬的使用者資料,可以拆分成 200個key,每個key下面存放5000個使用者資料
如果bigkey不可避免,也要思考一下要不要每次把所有元素都取出來(例如有時候僅僅需要 hmget,而不是hgetall),删除也是一樣,盡量使用優雅的方式來處理。比如,非字元串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式漸進式删除。
最後,還要注意防止 bigkey 過期時間自動删除問題(例如一個200萬的zset設定1小時過期,會觸發del操作,造成阻塞)
最後
2022/12/2 17:30
這篇部落格能寫好的原因是:站在巨人的肩膀上
這篇部落格要寫好的目的是:做别人的肩膀