Redis 儲存結構設計
阿新 • • 發佈:2019-02-10
Base 2.8.7
Redis是一個包含了很多Key-Value對的大字典,這個字典支援的Value非常豐富,可以為字串、雜湊表、列表、集合和有序集,基於這些型別豐富的value,擴展出了功能強大的操作,例如hmset、lpush、sadd等
Redis的方案是“雙buffer”,正常流程使用一個buffer,當發現碰撞劇烈(判斷依據為當前槽位數和Key數的對比),分配一個更大的buffer,然後逐步將資料從老的buffer遷移到新的buffer。
Redis字典結構如下:
字串型別即前文中看到的REDIS_STRING,其物理實現(enconding)可以為 REDIS_ENCODING_INT或REDIS_ENCODING_RAW
REDIS_ENCODING_INT儲存為long型,即redis會嘗試將一個字串轉化為Long,可以轉換的話,即儲存為REDIS_ENCODING_INT
否則,Redis會將REDIS_STRING儲存為字串型別,即REDIS_ENCODING_RAW
字串型別在redis中用sds封裝,主要為了解決長度計算和追加效率的問題,其定義如下:
REDIS_ENCODING_HT即前文提到的字典的實現
REDIS_ENCODING_ZIPLIST即ZIPLIST,是一種雙端列表,且通過特殊的格式定義,壓縮記憶體適用,以時間換空間。ZIPLIST適合小資料量的讀場景,不適合大資料量的多寫/刪除場景
Hash表預設的編碼格式為REDIS_ENCODING_ZIPLIST,在收到來自使用者的插入資料的命令時:
1,呼叫hashTypeTryConversion函式檢查鍵/值的長度大於 配置的hash_max_ziplist_value(預設64)
2,呼叫hashTypeSet判斷節點數量大於 配置的hash_max_ziplist_entries (預設512)
REDIS_ENCODING_ZIPLIST同上
REDIS_ENCODING_LINKEDLIST是比較正統雙端連結表的實現:
1,元素大小大於list-max-ziplist-value(預設64)
2,元素個數大於 配置的list-max-ziplist-entries(預設512)
集合的元素型別和數量決定了encoding方式,預設採用REDIS_ENCODING_INTSET ,當滿足以下條件時,轉換為REDIS_ENCODING_HT:
1. 元素型別不是整數
2. 元素個數超過配置的“set-max-intset-entries”(預設512)
REDIS_ENCODING_INTSET是一個有序陣列,使用的資料結構如下:
字典中使用member作為key,score作為value,從而保證在O(1)時間對member的查詢
跳躍表基於score做排序,從而保證在 O(logN) 時間內完成通過score對memer的查詢
有續集預設也是採用REDIS_ENCODING_ZIPLIST的實現,當滿足以下條件時,轉換為REDIS_ENCODING_SKIPLIST
1. 資料元素個數超過配置的zset_max_ziplist_entries 的值(預設值為 128 )
2. 新新增元素的 member 的長度大於配置的 zset_max_ziplist_value 的值(預設值為 64 )
Redis是一個包含了很多Key-Value對的大字典,這個字典支援的Value非常豐富,可以為字串、雜湊表、列表、集合和有序集,基於這些型別豐富的value,擴展出了功能強大的操作,例如hmset、lpush、sadd等
字典
字典是Redis最基礎的資料結構,一個字典即一個DB,Redis支援多DBRedis字典採用Hash表實現,針對碰撞問題,其採用的方法為“鏈地址法”,即將多個雜湊值相同的節點串連在一起, 從而解決衝突問題。
“鏈地址法”的問題在於當碰撞劇烈時,效能退化嚴重,例如:當有n個數據,m個槽位,如果m=1,則整個Hash表退化為連結串列,查詢複雜度O(n)
為了避免Hash碰撞攻擊,Redis隨機化了Hash表種子
Redis的方案是“雙buffer”,正常流程使用一個buffer,當發現碰撞劇烈(判斷依據為當前槽位數和Key數的對比),分配一個更大的buffer,然後逐步將資料從老的buffer遷移到新的buffer。
Redis字典結構如下:
redisObject是真正儲存redis各種型別的結構,其內容如下:typedef struct dict { dictType *type; void *privdata; dictht ht[2]; //雙buffer int rehashidx; int iterators; } dict; typedef struct dictht { dictEntry **table; //hash連結串列 unsigned long size; unsigned long sizemask; unsigned long used; } dictht; //資料節點<K,V> typedef struct dictEntry { void *key; union { void *val; uint64_t u64; int64_t s64; } v; struct dictEntry *next; } dictEntry;
typedef struct redisObject {
unsigned type:4; //邏輯型別
unsigned notused:2; /* Not used */
unsigned encoding:4; //物理儲存型別
unsigned lru:22; /* lru time (relative to server.lruclock) */
int refcount;
void *ptr; //具體資料
} robj;
其中type即redis支援的邏輯型別,包括:enconding為物理儲存方式,一種邏輯型別可以使用不同的儲存方式,包括:#define REDIS_STRING 0 #define REDIS_LIST 1 #define REDIS_SET 2 #define REDIS_ZSET 3 #define REDIS_HASH 4
#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 */
字串
Redis的所有的key都採用字串儲存,另外,Redis也支援字串型別的value。字串型別即前文中看到的REDIS_STRING,其物理實現(enconding)可以為 REDIS_ENCODING_INT或REDIS_ENCODING_RAW
REDIS_ENCODING_INT儲存為long型,即redis會嘗試將一個字串轉化為Long,可以轉換的話,即儲存為REDIS_ENCODING_INT
否則,Redis會將REDIS_STRING儲存為字串型別,即REDIS_ENCODING_RAW
字串型別在redis中用sds封裝,主要為了解決長度計算和追加效率的問題,其定義如下:
typedef char *sds;
struct sdshdr {
int len; // buf 已佔用長度
int free; // buf 剩餘可用長度
char buf[];// 柔性陣列,實際儲存字串資料的地方
};
static inline size_t sdslen(const sds s) {
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
return sh->len;
}
static inline size_t sdsavail(const sds s) {
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
return sh->free;
}
有時間的同學可以詳細看下Sds.h和Sds.c兩個檔案,還是很有意思的。Hash表
Redis支援Value為Hash表,其邏輯型別為REDIS_HASH,REDIS_HASH可以有兩種encoding方式: REDIS_ENCODING_ZIPLIST 和 REDIS_ENCODING_HTREDIS_ENCODING_HT即前文提到的字典的實現
REDIS_ENCODING_ZIPLIST即ZIPLIST,是一種雙端列表,且通過特殊的格式定義,壓縮記憶體適用,以時間換空間。ZIPLIST適合小資料量的讀場景,不適合大資料量的多寫/刪除場景
Hash表預設的編碼格式為REDIS_ENCODING_ZIPLIST,在收到來自使用者的插入資料的命令時:
1,呼叫hashTypeTryConversion函式檢查鍵/值的長度大於 配置的hash_max_ziplist_value(預設64)
2,呼叫hashTypeSet判斷節點數量大於 配置的hash_max_ziplist_entries (預設512)
以上任意條件滿足則將Hash表的資料結構從REDIS_ENCODING_ZIPLIST轉為REDIS_ENCODING_HT
列表
Redis支援Value為一個列表,其邏輯型別為REDIS_SET,REDIS_SET有兩種encoding方式,REDIS_ENCODING_ZIPLIST和REDIS_ENCODING_LINKEDLISTREDIS_ENCODING_ZIPLIST同上
REDIS_ENCODING_LINKEDLIST是比較正統雙端連結表的實現:
typedef struct listNode {
struct listNode *prev;
struct listNode *next;
void *value;
} listNode;
列表的預設編碼格式為REDIS_ENCODING_ZIPLIST,當滿足以下條件時,編碼格式轉換為REDIS_ENCODING_LINKEDLIST1,元素大小大於list-max-ziplist-value(預設64)
2,元素個數大於 配置的list-max-ziplist-entries(預設512)
集合
Redis支援Value為集合,其邏輯型別為REDIS_SET,REDIS_SET有兩種encoding方式: REDIS_ENCODING_INTSET 和 REDIS_ENCODING_HT(同上)集合的元素型別和數量決定了encoding方式,預設採用REDIS_ENCODING_INTSET ,當滿足以下條件時,轉換為REDIS_ENCODING_HT:
1. 元素型別不是整數
2. 元素個數超過配置的“set-max-intset-entries”(預設512)
REDIS_ENCODING_INTSET是一個有序陣列,使用的資料結構如下:
typedef struct intset {
uint32_t encoding; //3種類型:INTSET_ENC_INT16、INTSET_ENC_INT32、INTSET_ENC_INT64
uint32_t length; //元素個數
int8_t contents[]; //元素實際存放的位置,按序排放
} intset;
Redis會根據整數大小選擇最適合的型別,當發生變更時,進行調整有序集
Redis支援Value為有序集合,其邏輯型別為REDIS_ZSET,REDIS_ZSET有兩種encoding方式: REDIS_ENCODING_ZIPLIST(同上)和 REDIS_ENCODING_SKIPLIST
REDIS_ENCODING_SKIPLIST使用的資料結構如下,其同事:
typedef struct zset {
dict *dict; //Hash字典(同前文)
zskiplist *zsl; //跳躍表
} zset;
由於有續集每一個元素包括:<member,score>兩個屬性,為了保證對member和score都有很好的查詢效能,REDIS_ENCODING_SKIPLIST同時採用字典和有序集兩種資料結構來儲存資料元素。字典和有序集通過指標指向同一個資料節點來避免資料冗餘。字典中使用member作為key,score作為value,從而保證在O(1)時間對member的查詢
跳躍表基於score做排序,從而保證在 O(logN) 時間內完成通過score對memer的查詢
有續集預設也是採用REDIS_ENCODING_ZIPLIST的實現,當滿足以下條件時,轉換為REDIS_ENCODING_SKIPLIST
1. 資料元素個數超過配置的zset_max_ziplist_entries 的值(預設值為 128 )
2. 新新增元素的 member 的長度大於配置的 zset_max_ziplist_value 的值(預設值為 64 )
總結
針對同一種資料型別,Redis會根據元素型別/大小/個數採用不同的編碼方式,不同的編碼方式在記憶體使用效率/查詢效率上差距巨大,在遇到記憶體問題時,可以嘗試下修改相關引數:hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-entries 512
list-max-ziplist-value 64
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64