結構定義
在redis中,對象的數據結構定義以下:web
typedef struct redisObject { unsigned type:4; unsgined encoding:4; unsigned lru:LRU_BITS; int refcount; void *ptr; }
結構定義中的type:4
、encoding:4
這種定義方式稱爲**位段類型**。redis
使用位段類型的好處就是避免浪費內存,若是使用
unsigned int type
定義type字段,須要4個字節,而使用unsigned type:4
,只須要4個位段就足夠了。算法
參數說明
redis對象有許多特性,好比:類型檢查(經過type實現)、命令多態(encoding實現)、內存共享(經過refcount實現)等等,這些特性都是經過redisObject中的參數實現的。服務器
type
對象類型,它的取值範圍有五個,分別是redis使用的五種對象類型: 數據結構
#define OBJ_STRING 0
#define OBJ_LIST 1
#define OBJ_SET 2
#define OBJ_ZSET 3
#define OBJ_HASH 4
在執行命令前對type字段進行檢查,可判斷出對象是不是命令容許執行的對象類型。函數
encoding
對象使用的編碼類型,它的取值範圍有下面這些:編碼
#define OBJ_ENCODING_RAW 0 /* 動態字符串 */
#define OBJ_ENCODING_INT 1 /* 整數 */
#define OBJ_ENCODING_HT 2 /* 哈希表 */
#define OBJ_ENCODING_ZIPMAP 3
#define OBJ_ENCODING_LINKEDLIST 4 /* 舊的列表編碼,如今再也不使用了 */
#define OBJ_ENCODING_ZIPLIST 5 /* 壓縮表 */
#define OBJ_ENCODING_INTSET 6 /* 整數集合 */
#define OBJ_ENCODING_SKIPLIST 7 /* 跳躍表 */
#define OBJ_ENCODING_EMBSTR 8 /* 用於保存短字符串的編碼類型 */
#define OBJ_ENCODING_QUICKLIST 9 /* 壓縮鏈表和雙向鏈表組成的快速列表 */
在調用命令的時候,redis還會根據對象使用的編碼類型來選擇正確的底層對象,執行對應函數的實現代碼。spa
lru
最近最後一次被命令訪問的時間 或者 最近最少使用的數據。.net
在執行OBJECT IDLETIME命令時,經過當前時間減去lru屬性的值,獲得鍵的空轉時長。另外,若是服務器打開了maxmemory選項,且使用的內存回收算法是volatile-lur或者allkeys-lru,那麼當服務器佔用的內存超過了maxmemory的上限值,空轉時長較高的鍵會優先被服務器釋放,從而回收內存。指針
refcount
對象的引用計數。
redis的對象共享和內存回收特性就是經過refcount屬性來實現,經過將refcount + 1實現對象共享;進行內存回收檢查時,檢查refcount == 0的對象,將對象進行回收。
ptr
指向底層數據結構用於保存數據的指針。
對象使用的數據結構
redis有五種對象,不一樣對象可能用到的數據結構以下圖所示:
編碼轉換與命令多態
同一種對象使用不一樣的數據結構是經過encoding來實現,並且,同一個命令的實現方法會根據對象的編碼屬性而變化,這是命令的多態實現。 以哈希對象爲例看看編碼轉換以及命令多態等特性是怎麼實現的。
哈希對象
哈希對象使用的編碼有:ziplist、hashtable。
若是使用壓縮表做爲底層實現,每當有新的鍵值對須要加入哈希對象,會先添加鍵節點到鏈表,而後添加值節點。
使用hashtable做爲底層實現,每個新的鍵值對都會使用字典鍵值對來保存,鍵和值分別是字符串對象。
使用不一樣結構保存後的結構圖以下所示:
ziplist編碼
hashtable編碼
編碼轉換
每一種對象在使用編碼的時候都有必定的條件,使用ziplist編碼的哈希對象都應該知足兩個條件:
- 一、全部鍵值對的鍵和值字符串對象長度小於64字節
- 二、哈希對象保存的鍵值對數量小於512個
若是不能知足上述條件時,redis會進行對哈希對象底層數據結構進行從壓縮表到字典的轉換,實現步驟是遍歷壓縮表,獲取壓縮表中的鍵和值,使用獲得的鍵和值建立一個字典對象,而後添加字典裏,具體代碼以下:
hashTypeIterator *hi; dict *dict; int ret; // 建立遍歷器對象和哈希表 hi = hashTypeInitIterator(o); dict = dictCreate(&hashDictType, NULL); while (hashTypeNext(hi) != C_ERR) { sds key, value; // 用獲取ziplis中的key、value新增鍵值對到哈希表 key = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_KEY); value = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_VALUE); ret = dictAdd(dict, key, value); if (ret != DICT_OK) { serverLogHexDump(LL_WARNING,"ziplist with dup elements dump", o->ptr,ziplistBlobLen(o->ptr)); serverPanic("Ziplist corruption detected"); } } hashTypeReleaseIterator(hi); zfree(o->ptr); o->encoding = OBJ_ENCODING_HT; o->ptr = dict;
命令多態
命令多態是檢查對象的編碼,而後執行不一樣的實現方式。好比哈希對象中的hget命令。
hget命令實現代碼:
void hgetCommand(client *c) { robj *o; // key不存在,返回空 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL || checkType(c,o,OBJ_HASH)) return; addHashFieldToReply(c, o, c->argv[2]->ptr); }
hget命令的實現最終是調用addHashFieldToReply函數(代碼以下),該函數是經過判斷哈希對象的編碼來決定使用什麼函數來獲取哈希對象具體field的值,其餘命令的實現也是大同小異。
static void addHashFieldToReply(client *c, robj *o, sds field) { int ret; if (o == NULL) { addReply(c, shared.nullbulk); return; } // 根據底層不一樣編碼獲取field的值 if (o->encoding == OBJ_ENCODING_ZIPLIST) { unsigned char *vstr = NULL; unsigned int vlen = UINT_MAX; long long vll = LLONG_MAX; ret = hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll); if (ret < 0) { addReply(c, shared.nullbulk); } else { if (vstr) { addReplyBulkCBuffer(c, vstr, vlen); } else { addReplyBulkLongLong(c, vll); } } } else if (o->encoding == OBJ_ENCODING_HT) { sds value = hashTypeGetFromHashTable(o, field); if (value == NULL) addReply(c, shared.nullbulk); else addReplyBulkCBuffer(c, value, sdslen(value)); } else { serverPanic("Unknown hash encoding"); } }
總結
redis中的不少操做都是基於上面介紹的redis對象,瞭解這些對象的底層實現,能夠爲以後更多的redis特性作準備。