學習完《redis設計與實現》前面關於數據結構與對象的章節,以上問題都能獲得解答。你也能瞭解到redis做者如此的煞費苦心設計了這麼多豐富的數據結構,目的就是優化內存。學完這些內容,在使用redis的過程當中,也會合理的使用以適應它內部的特色。固然新版本的redis支持了更多更豐富的特性,該書基於redis3版本,尚未涉及到那些內容。ios
《redis設計與實現》這本書很是淺顯易懂,做者黃建宏老師,90後。另外仍是《redis實戰》的譯者nginx
另外一篇可參考《redis設計與實現》2-數據庫實現篇redis
struct sdshdr {
uint8_t len; /* used,使用的字節數 */
uint8_t alloc; /* excluding the header and null terminator,預分配總字節數,不包括結束符\0的長度 */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[]; /*c風格的字符,包括結束符\0*/
};
複製代碼
位於adlist.h文件算法
typedef struct listNode {
struct listNode *prev; // 前置節點
struct listNode *next; // 後置節點
void *value;//節點值
} listNode;
typedef struct list {
listNode *head; // 表頭節點
listNode *tail; // 表尾節點
void *(*dup)(void *ptr); // 節點值複製函數
void (*free)(void *ptr); // 節點值釋放函數
int (*match)(void *ptr, void *key); // 節點值對比函數
unsigned long len; // 節點數量
} list;
複製代碼
位於dict.h文件數據庫
// 哈希表
typedef struct dictht {
dictEntry **table; // 一個數組,數組中每一個元素都是指向dictEntry結構的指針
unsigned long size; // table數組的大小
unsigned long sizemask; // 值總數size-1
unsigned long used; // 哈希表目前已有節點(鍵值對)的數量
} dictht;
複製代碼
// 每一個dictEntry都保存着一個鍵值對,表示哈希表節點
typedef struct dictEntry {
void *key; // 鍵值對的鍵
// 鍵值對的值,能夠是指針,整形,浮點型
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next; // 哈希表節點指針,用於解決鍵衝突問題
} dictEntry;
複製代碼
每一個字典類型保存一簇用於操做特定類型鍵值對的函數api
typedef struct dictType {
// 計算哈希值的函數
uint64_t (*hashFunction)(const void *key);
// 複製鍵的函數
void *(*keyDup)(void *privdata, const void *key);
// 複製值的函數
void *(*valDup)(void *privdata, const void *obj);
// 對比鍵的函數
int (*keyCompare)(void *privdata, const void *key1, const void *key2);
// 銷燬鍵的函數
void (*keyDestructor)(void *privdata, void *key);
// 銷燬值的函數
void (*valDestructor)(void *privdata, void *obj);
} dictType;
複製代碼
// 字典
typedef struct dict {
dictType *type; // 不一樣鍵值對類型對應的操做函數
void *privdata; // 須要傳遞給對應函數的參數
dictht ht[2]; // ht[0]用於存放數據,ht[1]在進行rehash時使用
long rehashidx; /* rehashing not in progress if rehashidx == -1,目前rehash的進度*/
unsigned long iterators; /* number of iterators currently running */
} dict;
複製代碼
位於server.h文件中數組
// 跳躍表節點
typedef struct zskiplistNode {
sds ele; // 成員對象
double score; // 分值,從小到大排序
struct zskiplistNode *backward; // 後退指針,從表尾向表頭遍歷時使用
struct zskiplistLevel {
struct zskiplistNode *forward; // 前進指針
unsigned long span; // 跨度,記錄兩個節點之間的距離
} level[]; // 層,是一個數組
} zskiplistNode;
// 跳躍表相關信息
typedef struct zskiplist {
struct zskiplistNode *header, *tail; // 表頭和表尾
unsigned long length; // 跳躍表長度(包含節點的數量)
int level; // 跳躍表內層數最大那個節點的層數(不包括表頭節點層數)
} zskiplist;
複製代碼
位於intset.h文件緩存
typedef struct intset {
uint32_t encoding; // 編碼方式
uint32_t length; // 長度
int8_t contents[]; // 內容,數組內容類型取決於encoding屬性,並非int8_t。按照大小排序,沒有重複
} intset;
複製代碼
屬性 | 類型 | 長度 | 用途 |
---|---|---|---|
zlbytes | uint32_t | 4字節 | 整個壓縮列表佔用的內存字節數 |
zltail | uint32_t | 4字節 | 表尾節點距離壓縮列表起始地址有多少字節,無需遍歷就可獲得表尾節點 |
zllen | uint16_t | 2字節 | 節點數量,小於65535時是實際值,超過期須要遍歷才能算出 |
entryN | 列表節點 | 不定 | 包含的各個節點 |
zlend | uint8_t | 1字節 | 特殊值0xFF,末端標記 |
最高兩位取值 | 表示是數據類型 | encoding字節數 | 餘下的bit數 | 最大範圍 |
---|---|---|---|---|
00 | 字符數組 | 一個字節 | 6bit | 63位 |
01 | 字符數組 | 兩個字節 | 14bit | 2^14-1 |
10 | 字符數組 | 五個字節 | 4*8,第一個字節餘下的6bit留空 | 2^32-1位 |
11 | 整數 | 1個字節 | 000000 | int16_t類型整數 |
11 | 整數 | 1個字節 | 010000 | int32_t類型整數 |
11 | 整數 | 1個字節 | 100000 | int64_t類型整數 |
11 | 整數 | 1個字節 | 110000 | 24位有符號整數 |
11 | 整數 | 1個字節 | 111110 | 8位有符號整數 |
11 | 整數 | 1個字節 | xxxxxx | 沒有content,xxxx自己就表示了0-12的整數 |
//server.h
typedef struct redisObject {
unsigned type:4; //類型
unsigned encoding:4; // 編碼
// 對象最後一個被命令程序訪問的時間
unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
* LFU data (least significant 8 bits frequency
* and most significant 16 bits access time). */
int refcount; // 引用計數
void *ptr; // 指向底層的數據結構指針
} robj;
複製代碼
對象 | 對象type屬性 | type命令的輸出 |
---|---|---|
字符串對象 | REDIS_STRING | string |
列表對象 | REDIS_LIST | list |
哈希對象 | REDIS_HASH | hash |
集合對象 | REDIS_SET | set |
有序集合對象 | REDIS_ZSET | zset |
編碼常量 | 對應的數據結構 | OBJECT ENCODING命令輸出 |
---|---|---|
REDIS_ENCODING_INT | long類型的整數 | 「int」 |
REDIS_ENCODING_EMBSTR | embstr編碼的簡單動態字符串 | 「embstr」 |
REDIS_ENCODING_RAW | 簡單動態字符串 | 「raw」 |
REDIS_ENCODING_HT | 字典 | 「hashtable」 |
REDIS_ENCODING_LINKEDLIST | 雙端鏈表 | 「linkedlist」 |
REDIS_ENCODING_ZIPLIST | 壓縮列表 | 「ziplist」 |
REDIS_ENCODING_INTSET | 整數集合 | 「intset」 |
REDIS_ENCODING_SKIPLIST | 跳躍表和字典 | 「skiplist」 |
字符串對象內容 | 長度 | 編碼類型 |
---|---|---|
整數值 | - | int |
字符串值 | 小於32字節 | embstr |
字符串值 | 大於32字節 | raw |
embstr編碼是專門用於保存短字符串的一種優化編碼方式。這種編碼和raw編碼同樣,都使用redisObject結構和sdshdr結構來表示對象。區別在於:安全
int編碼和embstr編碼的對象知足條件時會自動轉換爲raw編碼的字符串對象bash
redis中全部鍵都是字符串對象,因此全部對於鍵的命令都是針對字符串鍵來構建的
使用ziplist編碼的兩個條件以下,不知足的都用linkedlist編碼(這兩個條件能夠在配置文件中修改):
哈希對象的編碼能夠是
集合對象的編碼能夠是:
集合使用intset須要知足兩個條件,不知足時使用hashtable(參數可經過配置文件修改)
有序集合的編碼能夠是
// 兩種數據結構經過指針共享元素成員和分值,不會浪費內存
typedef struct zset {
zskplist *zsl; //跳躍表,方便zrank,zrange
dict *dict; //字典,方便zscore
}zset;
複製代碼
當知足如下兩個條件時,使用ziplist編碼,不然使用skiplist(可經過配置文件修改)
redis的命令能夠分爲兩大類:
redis經過對象的refcount屬性記錄對象引用計數信息,適當的時候自動釋放對象進行內存回收
# 設置字符串
set msg "hello world"
rpush numbers 1 2 3 4 5
llen numbers
lrange numbers 0 5
# 獲取鍵值使用的底層數據結構
object encoding numbers
# 查看對象的引用計數值
object refcount numbers
# 對象空轉時長: value=now-object.lru
object idletime numbers
複製代碼