1. 程式人生 > >redis內部儲存結構

redis內部儲存結構

redis支援的幾種資料結構

  1. 字串
  2. 列表
  3. set
  4. sort-set
  5. map

redisobj 儲存結構

結構定義:

typedef struct redisObject {
	unsigned type:4; // 剛剛好32 bits,物件的型別,字串/列表/集合/雜湊表
    unsigned encoding:4; // 編碼的方式,Redis 為了節省空間,提供多種方式來儲存一個數據
    unsigned lru:22;   // 當記憶體緊張,淘汰資料的時候用到
    int refcount;  // 引用計數
    void *ptr; // 資料指標
} robj;

type的型別主要有:

#define REDIS_STRING 0
#define REDIS_LIST 1
#define REDIS_SET 2
#define REDIS_ZSET 3
#define REDIS_HASH 4

encoding型別主要有:

/* Objects encoding. Some kind of objects like Strings and Hashes can be
* internally represented in multiple ways. The 'encoding' field of the object
* is set to one of this fields for this object. */
#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 */

string儲存方式

  1. int:如果是類似於字串"123456"的字串,redis會選擇存為整形123456,以節省儲存佔用。
  2. sds(simple dynamic string):sds用於儲存位元組/字串和浮點型資料。
struct sdshdr {
   int len;
   int free;
   char buf[]; 
};

為什麼使用 char buf[]代替char *buf呢?
1. 記憶體管理方便,如果使用char *buf需要兩次記憶體申請,釋放也需要兩次,而 char buf[]只需要一次。
2. 長度為 0 的陣列即 char buf[] 不佔用記憶體

優點:
1. sds 獲取字串的長度以及剩餘空間的複雜度都是 O(1),而普通字串都需要O(N)

append 操作優化:
追加內容的長度不超過 free 屬性的值, 那麼就不需要對 buf 進行記憶體重分配,如果超過,則申請一倍記憶體,比如:
append前:
struct sdshdr {
    len = 11;
    free = 0;
    buf = "hello world\0";
}
append後:
struct sdshdr {
    len = 18;
    free = 18;
    buf = "hello world again!\0                  ";     // 空白的地方為預分配空間,共 18 + 18 + 1 個位元組
}

list儲存方式

  1. 雙鏈表(LinkedList)
  2. 壓縮雙鏈表(ziplist)
壓縮雙鏈表以連續的記憶體空間 來表示雙鏈表,壓縮雙鏈表節省前驅和後驅指標的空間(8b)

連續記憶體結構:
<zlbytes><zltail><zllen><entry>...<entry><zlend>

entry記憶體結構:
<prelen><<encoding+lensize><len>><data>

其中預定義的字串長度:
#define ZIP_STR_06B (0 << 6)
#define ZIP_STR_14B (1 << 6)
#define ZIP_STR_32B (2 << 6)

整形長度:
#define ZIP_INT_16B (0xc0 | 0<<4)
#define ZIP_INT_32B (0xc0 | 1<<4)
#define ZIP_INT_64B (0xc0 | 2<<4)
#define ZIP_INT_24B (0xc0 | 3<<4)
#define ZIP_INT_8B 0xfe

ziplist 每次新增資料都會realloc,這時可能會涉及到記憶體重新申請和拷貝的操作,所以通常用於list長度不長和元素不大的情況,同時因為ziplist不是標準的陣列結構,遍歷插入刪除基本O(N),大量資料的情況下對於linkedlist沒有效能上的優勢,如果資料小量並且緊湊, ziplist 能夠放入 CPU 快取效率也非常高,同時記憶體佔用非常小。

轉化配置:
list-max-ziplist-entries 512 # 最大接受長度為512,超過此長度則轉換為linked_list的儲存模式。
list-max-ziplist-value 64 # 每個元素的大小,最大不超過64bytes,超過則轉換為linked_list。

Map儲存方式

  1. hashtable
  2. ziplist(還是資料量比較小的情況下采用,儲存的方式奇位為key,偶位為value)

dict結構圖
redis的hashtable有兩個連結串列,主要為了能夠在不中斷服務的情況下擴充套件(expand)雜湊表,使用開鏈法解決衝突,每次插入選擇頭部好處:1. 每次插入O(1);2. 資料庫系統來說,最新插入的資料往往更有可能頻繁的被獲取。

正常情況下,比如java的hashmap的擴容,會導致rehash和拷貝,redis的做法和golang相似,使用增量擴容,避免在擴容的時候出現服務阻塞。

redis增量擴容,第一連結串列拷貝至第二連結串列的時機:
1. 定時任務
2. curd操作

好處:
1. 在擴容期間,查詢收到部分影響,但是要比停止服務要好得多
2. 在擴容期間,寫操作會出現多次查詢操作,效率比較低 

Set儲存方式

  1. hashtable
  2. intset(當新增的所有資料都是整數時,一旦出現字串型,會轉為hashtable)

intset 底層本質是一個有序的、不重複的、整型的陣列,支援不同型別整數。

typedef struct intset {
	uint32_t encoding;// 每個整數的型別
	uint32_t length;// intset 長度
	int8_t contents[];// 整數陣列
} intset;

encoding(只能升級不能降級):
#define INTSET_ENC_INT16 (sizeof(int16_t))
#define INTSET_ENC_INT32 (sizeof(int32_t))
#define INTSET_ENC_INT64 (sizeof(int64_t))

intset 搜尋:二分查詢

Sort-Set儲存方式

  1. ziplist(和map型別)
  2. skiplist+hashtable(通用解決方案)