Redis數據結構內部編碼

總體說明

Redis的每個鍵值都是使用一個redisObject結構體保存的:redis

typedef struct redisObject {

    // 類型
    unsigned type:4;

    // 編碼
    unsigned encoding:4;

    // 對象最後一次被訪問的時間
    unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */

    // 引用計數
    int refcount;

    // 指向實際值的指針
    void *ptr;

} robj;

type字段表示數據類型:數組

/* Object types */
#define REDIS_STRING 0
#define REDIS_LIST 1
#define REDIS_SET 2
#define REDIS_ZSET 3
#define REDIS_HASH 4

encoding表示內部編碼方式:bash

#define REDIS_ENCODING_RAW 0     /* Raw representation */
#define REDIS_ENCODING_INT 1     /* Encoded as integer */
#define REDIS_ENCODING_HT 2      /* Encoded as hash table */
#define REDIS_ENCODING_ZIPMAP 3  /* Encoded as zipmap */
#define REDIS_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */
#define REDIS_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
#define REDIS_ENCODING_INTSET 6  /* Encoded as intset */
#define REDIS_ENCODING_SKIPLIST 7  /* Encoded as skiplist */
#define REDIS_ENCODING_EMBSTR 8 /* Embedded sds string encoding */

數據結構與編碼方式對應:數據結構

字符串類型

redis使用sdshdr類型的變量來存儲字符串,redisObject的ptr字段指向sdshdr的地址,sdshdr的定義以下:性能

struct sdshdr {
  int len;   //buf已佔用的空間長度
  int free; //buf中剩餘的空間長度
  char but[];  //數據 真實存儲c字符串
}

好比執行set key "hello"時,內存結構以下:優化

當存儲字符串時,實際佔用空間就如上圖所示,字符串值存儲在sdshdr結構中。ui

若是值的內容能夠用一個64位符號整數表示,redis將會把值轉換成long,內存結構以下:編碼

能夠看出若是值是long,那麼將不會放到sdshdr結構中。spa

另外,redis會事先創建10000個key,分別存儲0到9999這些數字,若是值是0-9999,那麼能夠直接使用這10000個數字而不需在建立redisObject了。3d

當配置了maxmemory設置了redis的最大空間大小時,redis不會使用共享對象,由於對於每個鍵值都要上會用一個redisObject來記錄LRU信息。

當鍵值內容不超過39個字節時,reids會採用EMBSTR編碼,這個編碼的好處是把sdshdr結構與redisObject分配到連續的內存空間:

這麼作的好處是無論分配存儲仍是釋放內存,所須要的操做都從2次減小到1次。

當對EMBSTR編碼的字符串執行修改操做時(如APPEND操做),Redis會將其轉換爲RAW編碼。

散列類型

散列類型內部使用HT或者ZIPLIST編碼。

兩種數據結構的切換配置在:

hash-max-ziplist-entries 512
hash-max-ziplist-value 64

當散列類型字段個數少於hash-max-ziplist-entries個,或者每一個字段名和字段值的長度都小於hash-max-ziplist-value個字節,Redis會用ziplist編碼存儲數據,不然就使用ht存儲數據,轉換過程是透明的。

Redis的ZIPLIST,是一種緊湊的編碼格式,它犧牲了部分讀取性能換區極高的空間利用率,因此當entries在較少的個數而且value在較小的長度時使用。

具體實現不在此記錄了,能夠搜索相關文章。

列表類型

列表類型採用LINKEDLIST和ZIPLIST兩種編碼。

LINKEDLIST採用雙向鏈表,鏈表中的元素都是redisObject存儲的,此處與字符串優化方式相同。

兩種數據結構的切換配置在:

list-max-ziplist-entries 512
list-max-ziplist-value 64

轉換方式和Hash同樣。

其實linkedlist已經趨向於淘汰了,新版本的redis提供了QUICKLIST編碼方式:

將長列表分紅若干個以鏈表形式組織的ziplist,達到減小空間佔用的同時還能夠提高ziplist編碼性能的效果,具體實現能夠在網上搜索,此處不記錄了。

集合類型

內部實現方式是HT和INTSET。

當元素都是整型且元素的個數小於配置文件中set-max-intset-entries=512個時,Redis會使用INTSET,不然使用HT。

typedef struct intset {
    uint32_t encoding;  // 編碼類型 int16_t、int32_t、int64_t
    uint32_t length;    // 長度 最大長度:2^32
    int8_t contents[];  // 柔性數組
} intset;

其中centents存儲的就是集合中的元素,根據encoding不一樣,每一個元素佔用的大小也不一樣。默認是INT16(2字節),當新增長的整數沒法用2字節存儲時,Redis會將該集合的encoding升級到INT32(4字節),以此類推到INT64(8字節)。INTSET以有序的方式存儲元素,因此可用二分法查找,但添加或刪除元素都須要調整元素的內存位置,因此元素太多時性能很差,故定義了entries的個數限制。

INTSET轉成HT後,不會在自動轉回INTSET。

有序集合

內部實現方式是SKIPLIST和ZIPLIST。

兩種數據結構的切換配置在:

zset-max-ziplist-entries 128
zset-max-ziplist-value 64

當使用ZIPLIST時,

每一個集合元素使用兩個緊挨在一塊兒的壓縮列表節點來保存,第一個節點保存元素的成員,第二個節點保存元素的分值。而且壓縮列表內的集合元素按分值從小到大的順序進行排列,小的放置在靠近表頭的位置,大的放置在靠近表尾的位置。

當使用SKIPLIST時,有序集合對象使用 zet 結構做爲底層實現,一個 zset 結構同時包含一個字典和一個跳躍表。

關於SKIPLIST,能夠在網上搜索redis zset skiplist數據結構,在此處不作詳細記錄了。

相關文章
相關標籤/搜索