Redis底層

0、redis中字符串的實現方式和redis對象

字符串的實現方式:簡單動態字符串(simple dynamic string,SDS)作爲 Redis 的默認字符串表示。

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

redis對象:

 

1、String

1、String類型的編碼形式:

  • int:8個字節的長整型
  • embstr:小於等於44個字節的字符串
  • raw:大於44個字節的字符串

 

2、list

1、list類型的編碼格式:

3.2版本之前:

  • 壓縮列表ziplist(詳細見5.2 zset的介紹)
  • 雙向鏈表linkedlist (數據結構中最基本的結構,沒啥好講的)

3.2版本之後:

  • quicklist

2、壓縮列表轉化爲雙向鏈表:

  • 試圖往列表新添加一個字符串值,且這個字符串的長度超過 server.list_max_ziplist_value (默認值爲 64 )。
  • ziplist 包含的節點超過 server.list_max_ziplist_entries (默認值爲 512 )。

3、壓縮列表和雙向鏈表的優缺點:

  • 雙向鏈表linkedlist便於在表的兩端進行push和pop操作,在插入節點上覆雜度很低,但是它的內存開銷比較大。首先,它在每個節點上除了要保存數據之外,還要額外保存兩個指針;其次,雙向鏈表的各個節點是單獨的內存塊,地址不連續,節點多了容易產生內存碎片。
  • ziplist存儲在一段連續的內存上,所以存儲效率很高。但是,它不利於修改操作,插入和刪除操作需要頻繁的申請和釋放內存。特別是當ziplist長度很長的時候,一次realloc可能會導致大批量的數據拷貝。

4、quicklist:

quicklist實際上是ziplistlinkedlist的混合體,它將linkedlist按段進行切分,每一段使用ziplist進行緊湊存儲,多個ziplist之間使用雙向指針進行串接。

 

3、hash

1、hash的編碼格式:

ziplist(壓縮列表)和hashtable

當hash對象可以同時滿足一下兩個條件時,哈希對象使用ziplist編碼。

  • 哈希對象保存的所有鍵值對的鍵和值的字符串長度都小於64字節
  • 哈希對象保存的鍵值對數量小於512個

 

4、set

1、set編碼格式:

intset和hashtable:(滿足一下使用intset)

  • 結合對象保存的所有元素都是整數值
  • 集合對象保存的元素數量不超過512個

2、intset

intset內部其實是一個數組(int8_t coentents[]數組),而且存儲數據的時候是有序的,因爲在查找數據的時候是通過二分查找來實現的。

typedef struct intset {
    
    // 編碼方式
    uint32_t encoding;

    // 集合包含的元素數量
    uint32_t length;

    // 保存元素的數組
    int8_t contents[];

} intset;

 

5、zset

1、zset編碼格式:ziplist或者skiplist

ziplist:

  • 元素數量小於128個
  • 所有member的長度都小於64字節

否則轉換爲skiplist。

 2、ziplist介紹:

  • ziplist 編碼的 Zset 使用緊挨在一起的壓縮列表節點來保存,第一個節點保存 member,第二個保存 score。
  • ziplist 內的集合元素按 score 從小到大排序,其實質是一個雙向鏈表。
ZADD price 8.5 apple 5.0 banana 6.0 cherry

 

各個部分在內存上是前後相鄰的並連續的,每一部分作用如下:

  • zlbytes: 存儲一個無符號整數,固定四個字節長度(32bit),用於存儲壓縮列表所佔用的字節(也包括<zlbytes>本身佔用的4個字節),當重新分配內存的時候使用,不需要遍歷整個列表來計算內存大小。
  • zltail: 存儲一個無符號整數,固定四個字節長度(32bit),表示ziplist表中最後一項(entry)在ziplist中的偏移字節數。<zltail>的存在,使得我們可以很方便地找到最後一項(不用遍歷整個ziplist),從而可以在ziplist尾端快速地執行push或pop操作。
  • zllen: 壓縮列表包含的節點個數,固定兩個字節長度(16bit), 表示ziplist中數據項(entry)的個數。由於zllen字段只有16bit,所以可以表達的最大值爲2^16-1。
  • 注意點:如果ziplist中數據項個數超過了16bit能表達的最大值,ziplist仍然可以表示。ziplist是如何做到的?
  • 如果<zllen>小於等於2^16-2(也就是不等於2^16-1),那麼<zllen>就表示ziplist中數據項的個數;否則,也就是<zllen>等於16bit全爲1的情況,那麼<zllen>就不表示數據項個數了,這時候要想知道ziplist中數據項總數,那麼必須對ziplist從頭到尾遍歷各個數據項,才能計數出來。
  • entry,表示真正存放數據的數據項,長度不定。一個數據項(entry)也有它自己的內部結構。
  • zlend, ziplist最後1個字節,值固定等於255,其是一個結束標記。

3、skiplist介紹:

 跳錶作爲一種數據結構,節點是通過跳躍進行查詢的,加快了查詢速度

過程:https://www.cnblogs.com/yuanfang0903/p/12165394.html

skiplist插入形成過程

skiplist上的查找路徑展示

4、skiplist與平衡樹、哈希表的比較

  • skiplist和各種平衡樹(如AVL、紅黑樹等)的元素是有序排列的,而哈希表不是有序的。因此,在哈希表上只能做單個key的查找,不適宜做範圍查找。所謂範圍查找,指的是查找那些大小在指定的兩個值之間的所有節點。
  • 在做範圍查找的時候,平衡樹比skiplist操作要複雜。在平衡樹上,我們找到指定範圍的小值之後,還需要以中序遍歷的順序繼續尋找其它不超過大值的節點。如果不對平衡樹進行一定的改造,這裏的中序遍歷並不容易實現。而在skiplist上進行範圍查找就非常簡單,只需要在找到小值之後,對第1層鏈表進行若干步的遍歷就可以實現。
  • 平衡樹的插入和刪除操作可能引發子樹的調整,邏輯複雜,而skiplist的插入和刪除只需要修改相鄰節點的指針,操作簡單又快速。
  • 從內存佔用上來說,skiplist比平衡樹更靈活一些。一般來說,平衡樹每個節點包含2個指針(分別指向左右子樹),而skiplist每個節點包含的指針數目平均爲1/(1-p),具體取決於參數p的大小。如果像Redis裏的實現一樣,取p=1/4,那麼平均每個節點包含1.33個指針,比平衡樹更有優勢。
  • 查找單個key,skiplist和平衡樹的時間複雜度都爲O(log n),大體相當;而哈希表在保持較低的哈希值衝突概率的前提下,查找時間複雜度接近O(1),性能更高一些。所以我們平常使用的各種Map或dictionary結構,大都是基於哈希表實現的。
  • 從算法實現難度上來比較,skiplist比平衡樹要簡單得多。

 

推薦:圖解redis五種數據結構底層實現(動圖哦) 

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