1. 程式人生 > >Redis(三)--- Redis的五大資料型別的底層實現

Redis(三)--- Redis的五大資料型別的底層實現

1、簡介

Redis的五大資料型別也稱五大資料物件;前面介紹過6大資料結構,Redis並沒有直接使用這些結構來實現鍵值對資料庫,而是使用這些結構構建了一個物件系統redisObject;這個物件系統包含了五大資料物件,字串物件(string)、列表物件(list)、雜湊物件(hash)、集合(set)物件和有序集合物件(zset);而這五大物件的底層資料編碼可以用命令OBJECT ENCODING來進行檢視。

redisObject結構

1 typedef struct redisObject {
2     // 型別
3     unsigned type:4;
4     // 編碼
5     unsigned encoding:4;
6     // 指向底層實現資料結構的指標
7     void *ptr;
8     // ...
9 } robj;

redis是以鍵值對儲存資料的,所以物件又分為鍵物件和值物件,即儲存一個key-value鍵值對會建立兩個物件,鍵物件和值物件。

鍵物件總是一個字串物件,而值物件可以是五大物件中的任意一種。

    • type屬性儲存的是物件的型別,也就是我們說的 string、list、hash、set、zset中的一種,可以使用命令 TYPE key 來檢視。
    • encoding屬性記錄了隊形所使用的編碼,即這個物件底層使用哪種資料結構實現。

表中列出了底層編碼常量及對應的OBJECT ENCODING 命令的輸出,前三項都是字串結構

我們在存入key-value鍵值對時並不會指定物件的encoding,而是Redis會根據不統的使用場景來為一個物件設定不同的編碼,可以達到節約記憶體、加快訪問速度等目的。

 

2、字串物件(string)

字串物件底層資料結構實現為簡單動態字串(SDS)和直接儲存,但其編碼方式可以是int、raw或者embstr,區別在於記憶體結構的不同。

(1)int編碼

字串儲存的是整數值,並且這個正式可以用long型別來表示,那麼其就會直接儲存在redisObject的ptr屬性裡,並將編碼設定為int,如圖:

 

(2)raw編碼

 字串儲存的大於32位元組的字串值,則使用簡單動態字串(SDS)結構,並將編碼設定為raw,此時記憶體結構與SDS結構一致,記憶體分配次數為兩次,建立redisObject物件和sdshdr結構,如圖:

(3)embstr編碼

 字串儲存的小於等於32位元組的字串值,使用的也是簡單的動態字串(SDS結構),但是記憶體結構做了優化,用於儲存頓消的字串;記憶體分配也只需要一次就可完成,分配一塊連續的空間即可,如圖:

 

 字串物件總結:

    • 在Redis中,儲存long、double型別的浮點數是先轉換為字串再進行儲存的。
    • raw與embstr編碼效果是相同的,不同在於記憶體分配與釋放,raw兩次,embstr一次。
    • embstr記憶體塊連續,能更好的利用快取在來的優勢
    • int編碼和embstr編碼如果做追加字串等操作,滿足條件下會被轉換為raw編碼;embstr編碼的物件是隻讀的,一旦修改會先轉碼到raw。

3、列表物件(list)

列表物件的編碼可以是ziplist和linkedlist之一。

(1) ziplist編碼

ziplist編碼的雜湊隨想底層實現是壓縮列表,每個壓縮裡列表節點儲存了一個列表元素。

(2)linkedlist編碼

linkedlist編碼底層採用雙端連結串列實現,每個雙端連結串列節點都儲存了一個字串物件,在每個字串物件內儲存了一個列表元素。

列表物件編碼轉換:

    • 列表物件使用ziplist編碼需要滿足兩個條件:一是所有字串長度都小於64位元組,二是元素數量小於512,不滿足任意一個都會使用linkedlist編碼。
    • 兩個條件的數字可以在Redis的配置檔案中修改,list-max-ziplist-value選項和list-max-ziplist-entries選項。
    • 圖中StringObject就是上一節講到的字串物件,字串物件是唯一個在五大物件中作為巢狀物件使用的。

 

4、雜湊物件(hash)

雜湊物件的編碼可以是ziplist和hashtable之一。

(1)ziplist編碼

ziplist編碼的雜湊物件底層實現是壓縮列表,在ziplist編碼的雜湊物件中,key-value鍵值對是以緊密相連的方式放入壓縮連結串列的,先把key放入表尾,再放入value;鍵值對總是向表尾新增。

(2)hashtable編碼

hashtable編碼的雜湊物件底層實現是字典,雜湊物件中的每個key-value對都使用一個字典鍵值對來儲存。

字典鍵值對即是,字典的鍵和值都是字串物件,字典的鍵儲存key-value的key,字典的值儲存key-value的value。

雜湊物件編碼轉換:

    • 雜湊物件使用ziplist編碼需要滿足兩個條件:一是所有鍵值對的鍵和值的字串長度都小於64位元組;二是鍵值對數量小於512個;不滿足任意一個都使用hashtable編碼。
    • 以上兩個條件可以在Reids配置檔案中修改hash-max-ziplist-value選項和hash-max-ziplist-entries選項。

 

5、集合物件(set)

集合物件的編碼可以是intset和hashtable之一。

(1)intset編碼

intset編碼的集合物件底層實現是整數集合,所有元素都儲存在整數集合中。

(2)hashtable編碼

hashtable編碼的集合物件底層實現是字典,字典的每個鍵都是一個字串物件,儲存一個集合元素,不同的是字典的值都是NULL;可以參考java中的hashset結構。

集合物件編碼轉換:

    • 集合物件使用intset編碼需要滿足兩個條件:一是所有元素都是整數值;二是元素個數小於等於512個;不滿足任意一條都將使用hashtable編碼。
    • 以上第二個條件可以在Redis配置檔案中修改et-max-intset-entries選項。

 

 6、有序集合物件(zset)

有序集合的編碼可以是ziplist和skiplist之一。

(1)ziplist編碼 

ziplist編碼的有序集合物件底層實現是壓縮列表,其結構與雜湊物件類似,不同的是兩個緊密相連的壓縮列表節點,第一個儲存元素的成員,第二個儲存元素的分值,而且分值小的靠近表頭,大的靠近表尾。

(2)skiplist編碼

skiplist編碼的有序集合物件底層實現是跳躍表和字典兩種;

每個跳躍表節點都儲存一個集合元素,並按分值從小到大排列;節點的object屬性儲存了元素的成員,score屬性儲存分值;

字典的每個鍵值對儲存一個集合元素,字典的鍵儲存元素的成員,字典的值儲存分值。

為何skiplist編碼要同時使用跳躍表和字典實現?

    • 跳躍表優點是有序,但是查詢分值複雜度為O(logn);字典查詢分值複雜度為O(1) ,但是無序,所以結合連個結構的有點進行實現。
    • 雖然採用兩個結構但是集合的元素成員和分值是共享的,兩種結構通過指標指向同一地址,不會浪費記憶體。

有序集合編碼轉換:

  • 有序集合物件使用ziplist編碼需要滿足兩個條件:一是所有元素長度小於64位元組;二是元素個數小於128個;不滿足任意一條件將使用skiplist編碼。
  • 以上兩個條件可以在Redis配置檔案中修改zset-max-ziplist-entries選項和zset-max-ziplist-value選項。

 

7、總結

在Redis的五大資料物件中,string物件是唯一個可以被其他四種資料物件作為內嵌物件的;

列表(list)、雜湊(hash)、集合(set)、有序集合(zset)底層實現都用到了壓縮列表結構,並且使用壓縮列表結構的條件都是在元素個數比較少、位元組長度較短的情況下;

四種資料物件使用壓縮列表的優點:

(1)節約記憶體,減少記憶體開銷,Redis是記憶體型資料庫,所以一定情況下減少記憶體開銷是非常有必要的。

(2)減少記憶體碎片,壓縮列表的記憶體塊是連續的,並分配記憶體的次數一次即可。

(3)壓縮列表的新增、刪除、查詢操作的平均時間複雜度是O(N),在N再一定的範圍內,這個時間幾乎是可以忽略的,並且N的上限值是可以配置的。

(4)四種資料物件都有兩種編碼結構,靈活性增加。

 

參考:

《Redis設計與實現》黃健巨集著,網上對Redis的詳解等

 

此部落格為筆者使用redis很久之後,參考網路上各類文章總結性書寫,原創手打,如有錯誤歡迎指