Redis 底層數據結構原理

注:本篇按照Redis設計與實現這本書來寫。基於Redis3.0版本

redis使用了 SDS、鏈表、字典(哈希表)、跳躍表、整數集合、壓縮列表 幾種數據類型,我們操作的api是對這幾個數據結構的封裝

SDS 簡單動態字符串

Redis是用c語言寫的,自然而言遵循c語言的特性。c語言字符串就有一堆坑,比如緩衝區溢出,獲取字符串長度o(n)複雜度。

對於c語言來說,字符串存儲在字符數組裏面,每次字符串的變更導致數組的變更,從而進行內存重新分配。因爲內存重分配涉及複雜的算法, 並且可能需要執行系統調用, 所以它通常是一個比較耗時的操作。
爲了避免 C 字符串的這種缺陷, SDS 通過未使用空間解除了字符串長度和底層數組長度之間的關聯: 在 SDS 中, buf 數組的長度不一定就是字符數量加一, 數組裏面可以包含未使用的字節, 而這些字節的數量就由 SDS 的 free 屬性記錄。


通過未使用空間, SDS 實現了空間預分配和惰性空間釋放兩種優化策略。

空間預分配
空間預分配用於優化 SDS 的字符串增長操作: 當 SDS 的 API 對一個 SDS 進行修改, 並且需要對 SDS 進行空間擴展的時候, 程序不僅會爲 SDS 分配修改所必須要的空間, 還會爲 SDS 分配額外的未使用空間。
如果對 SDS 進行修改之後, SDS 的長度(也即是 len 屬性的值)將小於 1 MB , 那麼程序分配和 len 屬性同樣大小的未使用空間, 這時 SDS len 屬性的值將和 free 屬性的值相同。    通過這種預分配策略, SDS 將連續增長 N 次字符串所需的內存重分配次數從必定 N 次降低爲最多 N 次。

惰性空間釋放
惰性空間釋放用於優化 SDS 的字符串縮短操作: 當 SDS 的 API 需要縮短 SDS 保存的字符串時, 程序並不立即使用內存重分配來回收縮短後多出來的字節, 而是使用 free 屬性將這些字節的數量記錄起來, 並等待將來使用。
比如一個  free 爲5  buf[]= redis 的sds對象,變成re 這樣就是 free=3   buf[]=re 此時不會立刻釋放掉free的內存,而是提供api等你啥時候缺了再釋放,所以不用擔心惰性空間釋放策略會造成內存浪費。比如內存灌滿了等等、

二進制安全
C語言字符串還有一個缺點是 \0表示結尾,那麼字符中有空格肯定會有問題。這些限制使得C 字符串只能保存文本數據, 而不能保存像圖片、音頻、視頻、壓縮文件這樣的二進制數據。

所有 SDS API 都會以處理二進制的方式來處理 SDS 存放在 buf 數組裏的數據, 程序不會對其中的數據做任何限制、過濾、或者假設 —— 數據在寫入時是什麼樣的, 它被讀取時就是什麼樣。因爲 SDS 使用 len 屬性的值而不是空字符來判斷字符串是否結束

C 字符串與SDS  區別

  1. 獲取字符串長度的複雜度爲 O(N) 。                                   獲取字符串長度的複雜度爲 O(1) 。
  2. API 是不安全的,可能會造成緩衝區溢出。                        API 是安全的,不會造成緩衝區溢出。
  3. 修改字符串長度 N 次必然需要執行 N 次內存重分配。       修改字符串長度 N 次最多需要執行 N 次內存重分配。
  4. 只能保存文本數據。                                                            可以保存文本或者二進制數據。
  5. 可以使用所有 <string.h> 庫中的函數。                               可以使用一部分 <string.h> 庫中的函數。

 

鏈表

鏈表提供了高效的節點排重能力,以及順序的節點訪問方式。

鏈表,存儲了next和prev指針。有頭結點和尾節點的指針,訪問頭尾是o(1) 其他位置是o(n).存儲了鏈表的長度o(1)

使用adlist.h/list對鏈表進行封裝如下。鏈表的查找、刪除、修改是o(n),其餘操作是o(1)的

字典

字典又稱關聯數組、map。 Redis中的字典實際上就是哈希表數組。   Redis中所有的key是唯一的,實際上就是一個字典。

Redis的數據庫就是用字典作爲底層實現的,對數據庫的crud也是構建在字典上的,所以很快的。set a "hello world"

對key進行hash,存儲的是hash之後的值,如果發生衝突就鏈地址法,衝突的位置連成一個鏈表。

如下爲dictEntry的具體內部結構。next指針就是連成鏈表用的。

字典的數據結構如下:

rehash 當hash進行擴展時需要進行rehash操作,擴展2^n
在進行拓展或者壓縮的時候,可以直接將所有的鍵值對rehash 到ht[1]中,這是因爲數據量比較小。在實際開發過程中,這個rehash 操作並不是一次性、集中式完成的,而是分多次、漸進式地完成的。
漸進式rehash 的詳細步驟:

  1. 爲ht[1] 分配空間,讓字典同時持有ht[0]和ht[1]兩個哈希表
  2. 在幾點鐘維持一個索引計數器變量rehashidx,並將它的值設置爲0,表示rehash 開始
  3. 在rehash 進行期間,每次對字典執行CRUD操作時,程序除了執行指定的操作以外,還會將ht[0]中的數據rehash 到ht[1]表中,並且將rehashidx加一
  4. 當ht[0]中所有數據轉移到ht[1]中時,將rehashidx 設置成-1,釋放ht[0]。然後將ht[1]改成ht[0],重新分配ht[1]
     

字典除了釋放所有鍵值對是o(n),其餘操作都是o(1)

跳躍表

skiplist是一種有序數據結構,通過在每個節點中維持多個指向其他節點的指針,從而達到快速訪問節點的目的。

如果一個有序集合包含的元素數量比較多,又或者有序集合中元素的成員是比較長的字符串時,Redis就會使用跳躍表作爲有序結合鍵的底層實現。

和字典、鏈表不同的是,跳躍表只是在兩個地方用到了,有序集合鍵&&集羣節點中作爲內部數據結構。

上圖是一個節點的數據結構。跳躍表肯定是多個這種節點組成然後又包了一下的。

每新增一個上圖數據結構的節點就會根據冪次定律生成一個介於1-32之間的值作爲level數組的大小。這個大小就是層的高度。整體的排序根據score的大小來進行排序,可以理解爲高端的平衡樹。其中層裏面的span是記錄兩個節點之間的距離。

跳躍表就是多個跳躍節點組成的,持有最高層、長度等的一個數據結構、

其中level是存儲的節點最高的。如上圖就是o3所在的L5  

length就是遍歷到尾部指針需要的遍歷層度,如上就是o1---o2---o3 所在的層,一共三層。

可以看出不是每層都一樣多,是1-32隨機的,而多少個節點值就是多少的length,只不過訪問的時候由於層指針的原因會遍歷的相對鏈表來說更少。

其中level[] 就是上圖中的 L(n)數組,對應節點所在的L(n)相連,存儲到*forward前進指針,其中span存儲的是與其餘相連節點的距離

所以跳躍表是有平均複雜度o(logn)和最壞複雜度的o(n)

 

整數集合

整數集合是集合鍵(set)的底層實現之一,當一個集合只包括整數值元素。並且這個集合的元素不多時,就採用整數集合進行存儲。

升級:當加入一個新元素,int32比int8要數據類型長時,需要將之前的元素全都升級爲int32

降級:整數集合不支持降級的操作。

壓縮列表

壓縮列表是列表鍵和哈希鍵的底層實現之一。當一個列表鍵只包含少量列表項,並且每個列表項要麼就是小整數值,要麼就是長度比較短的字符串,那麼Redis就會使用壓縮列表來做列表鍵的底層實現。

壓縮列表是Redis爲了節約內存而開發的,是由一系列特殊編碼的連續內存塊組成的順序型數據結構。

每個壓縮列表節點可以保存一個字節數組或者一個整數值。

壓縮列表的組成previous_entry_length  encoding  content   

  1. previous_entry_length前一個節點的長度。
  2. encoding記錄了節點的content數據所保存的數據類型和長度。
  3. content屬性負責保存節點的值,節點值可以是一個字節數組或者整數。值得類型和長度由節點encoding決定。

壓縮列表的時間複雜度查、刪除、增加、修改都是o(n)相當於節約內存犧牲時間。

對象

就是我們直接用的string  list  set  zset  hash  

encoding存儲的是對象使用了什麼數據結構作爲對象的底層實現。下面是數據結構與數據類型的對應關係。

其中還包括 lru 屬性,記錄對象最後一次被命令程序訪問的時間。

內存回收

redis在自己的對象系統中構建了一個引用計數信息,在適當的時候自動釋放對象並進行內存回收。

Java是把引用計數回收這個方式給否了的。因爲循環引用,但是在Redis並不存在循環引用。

對象共享

如果一個對象例如 “100”   在多個地方使用,那麼就引用+1,然後指針指向這個redisObject對象的內存地址。

object  refcount  key 看引用計數數量。

Redis會在初始化服務器是創建一萬個字符串對象  0-9999

 

object idletime 展示 當前時間 - lru時間

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章