redis內部儲存結構
阿新 • • 發佈:2018-12-22
redis支援的幾種資料結構
- 字串
- 列表
- set
- sort-set
- 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儲存方式
- int:如果是類似於字串"123456"的字串,redis會選擇存為整形123456,以節省儲存佔用。
- 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儲存方式
- 雙鏈表(LinkedList)
- 壓縮雙鏈表(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儲存方式
- hashtable
- ziplist(還是資料量比較小的情況下采用,儲存的方式奇位為key,偶位為value)
redis的hashtable有兩個連結串列,主要為了能夠在不中斷服務的情況下擴充套件(expand)雜湊表,使用開鏈法解決衝突,每次插入選擇頭部好處:1. 每次插入O(1);2. 資料庫系統來說,最新插入的資料往往更有可能頻繁的被獲取。
正常情況下,比如java的hashmap的擴容,會導致rehash和拷貝,redis的做法和golang相似,使用增量擴容,避免在擴容的時候出現服務阻塞。
redis增量擴容,第一連結串列拷貝至第二連結串列的時機:
1. 定時任務
2. curd操作
好處:
1. 在擴容期間,查詢收到部分影響,但是要比停止服務要好得多
2. 在擴容期間,寫操作會出現多次查詢操作,效率比較低
Set儲存方式
- hashtable
- 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儲存方式
- ziplist(和map型別)
- skiplist+hashtable(通用解決方案)