1. 程式人生 > 其它 >Redis五種資料型別的底層實現

Redis五種資料型別的底層實現

簡介

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

redisObject結構

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

redis是以鍵值對儲存資料的,所以物件又分為鍵物件和值物件,即儲存一個key-value鍵值對會建立兩個物件,鍵物件和值物件。
鍵物件總是一個字串物件,而值物件可以是五大物件中的任意一種。

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


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

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

字串物件(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。

列表物件(list)

3.2版本之前:列表物件的編碼可以是ziplist和linkedlist之一。

(1) ziplist編碼

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

(2)linkedlist編碼

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

列表物件編碼轉換:

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

3.2版本之後:列表物件的編碼quicklist。

quicklist編碼

quickList 是 zipList 和 linkedList 的混合體,它將 linkedList 按段切分,每一段使用 zipList 來緊湊儲存,多個 zipList 之間使用雙向指標串接起來。


Redis 還會對 ziplist 進行壓縮儲存,使用 LZF 演算法壓縮,可以選擇壓縮深度。
quicklist 預設的壓縮深度是 0,也就是不壓縮。壓縮的實際深度由配置引數list-compress-depth決定。為了支援快速的 push/pop 操作,quicklist 的首尾兩個 ziplist 不壓縮,此時深度就是 1。如果深度為 2,就表示 quicklist 的首尾第一個 ziplist 以及首尾第二個 ziplist 都不壓縮。
ziplist長度:
quicklist 內部預設單個 ziplist 長度為 8k 位元組,超出了這個位元組數,就會新起一個 ziplist。
ziplist 的長度由配置引數 list-max-ziplist-size 決定。

雜湊物件(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選項。

集合物件(set)

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

(1)intset編碼

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

(2)hashtable編碼

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

集合物件編碼轉換:

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

有序集合物件(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選項。

應用場景

redis一般應用場景

  • 快取會話(單點登入)
  • 分散式鎖,比如:使用setnx
  • 各種排行榜或計數器
  • 商品列表或使用者基礎資料列表等
  • 使用list作為訊息對列
  • 秒殺,庫存扣減等

五種型別的應用場景

  • String,redis對於KV的操作效率很高,可以直接用作計數器。例如,統計線上人數等等,另外string型別是二進位制儲存安全的,所以也可以使用它來儲存圖片,甚至是視訊等。
  • hash,存放鍵值對,一般可以用來存某個物件的基本屬性資訊,例如,使用者資訊,商品資訊等,另外,由於hash的大小在小於配置的大小的時候使用的是ziplist結構,比較節約記憶體,所以針對大量的資料儲存可以考慮使用hash來分段儲存來達到壓縮資料量,節約記憶體的目的,例如,對於大批量的商品對應的圖片地址名稱。比如:商品編碼固定是10位,可以選取前7位做為hash的key,後三位作為field,圖片地址作為value。這樣每個hash表都不超過999個,只要把redis.conf中的hash-max-ziplist-entries改為1024,即可。
  • list,列表型別,可以用於實現訊息佇列,也可以使用它提供的range命令,做分頁查詢功能。
  • set,集合,整數的有序列表可以直接使用set。可以用作某些去重功能,例如使用者名稱不能重複等,另外,還可以對集合進行交集,並集操作,來查詢某些元素的共同點
  • zset,有序集合,可以使用範圍查詢,排行榜功能或者topN功能。

總結

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

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

四種資料物件使用壓縮列表的優點:
(1)節約記憶體,減少記憶體開銷,Redis是記憶體型資料庫,所以一定情況下減少記憶體開銷是非常有必要的。
(2)減少記憶體碎片,壓縮列表的記憶體塊是連續的,並分配記憶體的次數一次即可。
(3)壓縮列表的新增、刪除、查詢操作的平均時間複雜度是O(N),在N再一定的範圍內,這個時間幾乎是可以忽略的,並且N的上限值是可以配置的。
(4)四種資料物件都有兩種編碼結構,靈活性增加。