1. 程式人生 > 實用技巧 >Redis之物件

Redis之物件

1、物件的型別與編碼

  Redis使用物件來表示資料庫中的鍵和值,每次我們在Redis的資料庫中新建立一個鍵值對,我們至少會建立兩個物件,一個鍵物件,另一個值物件。

  每個物件都由一個redisObject結構表示,如下:

  

1.1、物件的型別和編碼

type屬性記錄了物件的型別:

型別常量  物件的名稱
REDIS_STRING 字串物件
REIDS_LIST 列表物件
REDIS_HASH 雜湊物件
REDIS_SET 集合物件
REDIS_ZSET 有序集合物件

encoding記錄了物件使用的編碼,也就是說這個物件使用了什麼資料結構作為物件的底層實現

  

   每個型別的物件都至少使用了兩種不同的編碼:

  

   使用OBJECT ENCODING key(key就是你要查詢的鍵)命令可以檢視一個資料庫鍵的值物件的編碼。

2、字串物件

  • 字串物件的編碼可以是int、raw、embstr
編碼 條件
raw 1、字串值長度 > 32位元組
embstr  1、字串值長度 <= 32位元組
  • raw和embstr的比較 :

    • embstr編碼建立字串物件所需的記憶體分配次數從raw編碼的兩次降低為一次
    • 釋放embstr編碼的字串只需呼叫一個記憶體釋放,raw兩次
    • embstr編碼的字串所有資料儲存在一塊連續的記憶體中,更好來的利用快取帶來的優勢、
  • 可以用long、double型別表示的浮點數在Redis中也是作為字串值儲存。程式會先將浮點數轉換為字串值,然後再儲存轉換所得的字串值。在有需要的時候,程式會將儲存的字串值轉回浮點數,執行操作。

2.1、編碼的轉換

  • int ==> raw : 對int物件執行一些命令(比如APPEND),使得物件儲存的不再是整數值,變成一個字串值,那麼字串物件的編碼從int變成raw
  • embstr ==> raw : redis沒有為embstr編碼的字串物件編寫任何修改程式,當我們對embstr編碼的字串物件執行任何修改命令時,程式會先將embstr轉換為raw,再執行修改命令。

3、列表物件

  • 列表物件編碼可以是ziplist、linkedlist
編碼 條件 修改命令
ziplist 1、列表物件儲存的所有字串元素的長度都小於64位元組 list-max-ziplist-value
2、列表物件儲存的元素數量小於512個 list-max-ziplist-entries
linkedlist 上面不滿足任一條件

4、雜湊物件

  • 雜湊物件的編碼可以是ziplist、hashtable
編碼 條件 修改命令
ziplist 1、雜湊物件儲存的所有鍵值對的鍵和值的字串長度都小於64位元組 hash-max-ziplist-value
2、雜湊物件儲存的鍵值對數量小於512個 hash-max-ziplist-entries
hashtable 上面不滿足任一條件

5、集合物件

  • 集合物件的編碼可以是intset、hashtable
編碼 條件 修改命令
intset 1、集合物件儲存的所有元素都是整數值
2、集合物件儲存的元素數量不超過512個 set-max-intset-entries
hashtable 上面不滿足任一條件

6、有序集合物件

  • 有序集合的編碼可以是ziplist或者skiplist
編碼 條件
修改命令
ziplist  1、有序集合儲存的元素數量小於128個 zset-max-ziplist-entries
2、有序集合儲存的所有元素成員的長度都小於64位元組 zset-max-ziplist-value
skiplist 上面不滿足任一條件
  • ziplist編碼的壓縮列表物件使用壓縮列表作為底層實現,每個集合元素使用兩個緊挨在一起的壓縮列表節點來儲存,第一個節點儲存元素的成員(member),而第二個元素則儲存元素的分值(score)。壓縮列表內的集合元素按分值從小到大進行排序,分值較小的元素被放置在靠近表頭的方向,而分值較大的元素則被放置在靠近表尾的方向。
  • skiplist編碼的有序集合物件使用zset結構作為底層實現,一個zset結構同時包含一個字典和一個跳躍表:

  

    • zset結構中的zsl跳躍表按分值從小到大儲存了所有集合元素,每個跳躍表節點都儲存了一個集合元素。通過這個跳躍表,程式可以對有序集合進行範圍型操作,比如ZRANK、ZRANGE等命令就是基於跳躍表API來實現的
    • zset結構中的dict字典為有序集合建立了一個從成員到分值的對映,字典中的每個鍵值對都儲存了一個集合元素:字典的鍵儲存了元素的成員,而字典的值則儲存了元素的分值。通過這個字典,程式可以用O(1)複雜度查詢給定成員的分值,ZSCORE命令就是根據這一特性實現的
  • 為什麼有序集合需要同時使用跳躍表和字典來實現?
    • 在理論上,有序集合可以單獨使用字典或者跳躍表的其中一種資料結構來實現,但無論單獨使用字典還是跳躍表,在效能上對比起同時使用字典和跳躍表都會有所降低。舉個例子,如果我們只使用字典來實現有序集合,那麼雖然以O(1)複雜度查詢成員的分值這一特性會被保留,但是,因為字典以無序的方式來儲存集合元素,所以每次在執行範圍型操作——比如ZRANK、ZRANGE等命令時,程式都需要對字典儲存的所有元素進行排序,完成這種排序需要至少O(NlogN)時間複雜度,以及額外的O(N)記憶體空間(因為要建立一個陣列來儲存排序後的元素)。另一方面,如果我們只使用跳躍表來實現有序集合,那麼跳躍表執行範圍型操作的所有優點都會被保留,但因為沒有了字典,所以根據成員查詢分值這一操作的複雜度將從O(1)上升為O(logN)。因為以上原因,為了讓有序集合的查詢和範圍型操作都儘可能快地執行,Redis選擇了同時使用字典和跳躍表兩種資料結構來實現有序集合。

7、記憶體回收

  Redis在自己的物件系統中構建了一個引用計數(reference counting)技術實現的記憶體回收機制,通過這一機制,程式可以通過跟蹤物件的引用計數資訊,在適當的時候自動釋放物件並進行記憶體回收。

  每個物件的引用計數資訊由redisObject結構的refcount屬性記錄:

  

  物件的引用計數資訊會隨著物件的使用狀態而不斷變化:

  1. 在建立一個新物件時,引用計數的值會被初始化為1
  2. 當物件被一個新程式使用時,它的引用計數值會被增一
  3. 當物件不再被一個程式使用時,它的引用計數值會被減一
  4. 當物件的引用計數值變為0時,物件所佔用的記憶體會被釋放

8、物件共享

  物件的引用計數屬性還帶有物件共享的作用,範圍是0~9999,共享物件不單單隻有字串鍵可以使用,那些在資料結構中嵌套了字串物件的物件(linkedlist編碼的列表物件、hashtable編碼的雜湊物件、hashtable編碼的集合物件,以及zset編碼的有序集合物件)都可以使用這些共享物件

  讓多個鍵共享同一個值物件需要執行以下兩個步驟:

  1、將資料庫鍵的值指標指向一個現有物件

  2、將被共享的值物件的引用計數增加一

  為什麼Redis不共享包含字串的物件?

  當伺服器考慮將一個共享物件設定為鍵的值物件時,程式需要先檢查給定的共享物件和鍵想建立的目標物件是否完全相同,只有在共享物件和目標物件完全相同的情況下,程式才會將共享物件用作鍵的值物件,而一個共享物件儲存的值越複雜,驗證共享物件和目標物件是否相同所需的複雜度就會越高,消耗的CPU時間也會越多:□ 如果共享物件是儲存整數值的字串物件,那麼驗證操作的複雜度為O(1);□ 如果共享物件是儲存字串值的字串物件,那麼驗證操作的複雜度為O(N);□ 如果共享物件是包含了多個值(或者物件的)物件,比如列表物件或者雜湊物件,那麼驗證操作的複雜度將會是O(N 2)。因此,儘管共享更復雜的物件可以節約更多的記憶體,但受到CPU時間的限制,Redis只對包含整數值的字串物件進行共享

9、物件的空轉時長

  除了前面介紹過的type、encoding、ptr和refcount四個屬性之外,redisObject結構包含的最後一個屬性為lru屬性,該屬性記錄了物件最後一次被命令程式訪問的時間。

   OBJECT IDLETIME命令可以打印出給定鍵的空轉時長,這一空轉時長就是通過將當前時間減去鍵的值物件的lru時間計算得出的。

  如果伺服器開啟了maxmemory選項,並且伺服器用於回收記憶體的演演算法為volatile-lru或者allkeys-lru,那麼當伺服器佔用的記憶體數超過了maxmemory選項所設定的上限值時,空轉時長較高的那部分鍵會優先被伺服器釋放,從而回收記憶體。