Redis源码剖析(九)对象系统概述
在Redis的源碼中,到處可見robj類型的變量,在介紹其他模塊時,只是將它看成Redis的數(shù)據(jù)類型,并沒有深入探究。而事實上,它是對象系統(tǒng),提供了對多種類型的封裝,Redis可以根據(jù)數(shù)據(jù)的具體形式,采用不同的類型進行存儲,一方面提高了靈活性,一方面也為節(jié)省內(nèi)存提供了便利,因為Redis所有的數(shù)據(jù)都是直接存在內(nèi)存中的,所以需要想方設(shè)法節(jié)省內(nèi)存
對象結(jié)構(gòu)
redisObject結(jié)構(gòu)中包含了對象系統(tǒng)的定義,記錄了數(shù)據(jù)類型,數(shù)據(jù)編碼格式,最后一次訪問的時間,引用計數(shù),值
//server.h /* 對象系統(tǒng)的定義 */ typedef struct redisObject {unsigned type:4; //類型,可以是string, hash, list, set和zset(宏定義給出)unsigned encoding:4; //編碼,表示ptr底層數(shù)據(jù)以何種方式存儲unsigned lru:LRU_BITS; //最后一次訪問的時間int refcount; //引用計數(shù)void *ptr; //實際存放的值 } robj;:n是位域,顯式指出該變量占用的位數(shù),上述定義中,type占4位,encoding占4位,二者共占8位,即1個字節(jié)
類型
類型就是命令指出的數(shù)據(jù)格式
| SET | 鍵為字符串對象,值為字符串對象 | SET db redis |
| SADD | 鍵為字符串對象,值為集合對象 | SADD db redis mongodb mysql |
| RPUSH | 鍵為字符串對象,值為列表對象 | RPUSH db redis mongodb mysql |
| HMSET | 鍵為字符串對象,值為哈希對象 | HMSET profile name Tom age 25 sex male |
| ZADD | 鍵為字符串對象,值為有序集合對象 | ZADD price 8.5 apple 5.0 banana 6.0 cherry |
這5種類型由宏定義給出
//server.h #define OBJ_STRING 0 #define OBJ_LIST 1 #define OBJ_SET 2 #define OBJ_ZSET 3 #define OBJ_HASH 4Redis提供了TYPE命令用于返回不同數(shù)據(jù)的類型
127.0.0.1:6379> SET db redis OK 127.0.0.1:6379> TYPE db //SET,字符串類型值 string 127.0.0.1:6379> SADD db_sadd redis mongodb mysql (integer) 3 127.0.0.1:6379> TYPE db_sadd //SADD,集合類型值 set 127.0.0.1:6379> ZADD price 8.5 apple 5.0 banana 6.0 cherry (integer) 3 127.0.0.1:6379> TYPE price //ZADD,有序集合類型值 zset 127.0.0.1:6379> RPUSH db_rpush redis mongodb mysql (integer) 3 127.0.0.1:6379> TYPE db_rpush //RPUSH,列表類型值 list 127.0.0.1:6379> HMSET profile name Tom age 25 sex male OK 127.0.0.1:6379> TYPE profile //HMSET,哈希表類型值 hash編碼
編碼代表數(shù)據(jù)實際的存儲格式,實際保存的類型和提供的類型不一定相同,舉個例子,如果使用SET version 10添加一個字符串類型的鍵值對
#define OBJ_ENCODING_RAW 0 /* Raw格式,常規(guī)字符串類型 */ #define OBJ_ENCODING_INT 1 /* 整數(shù)形式 */ #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 /* 整數(shù)集合 */ #define OBJ_ENCODING_SKIPLIST 7 /* 跳表 */ #define OBJ_ENCODING_EMBSTR 8 /* EMBSTR格式,適用于存儲較短的字符串類型,比Raw少申請一次內(nèi)存 */ #define OBJ_ENCODING_QUICKLIST 9 /* 快速列表 */Redis提供OBJECT ENCODING命令獲取鍵對應(yīng)的值在底層的編碼格式
| 整數(shù) | OBJ_ENCODING_INT | “int” |
| embstr編碼字符串 | OBJ_ENCODING_EMBSTR | “embstr” |
| raw編碼字符串 | OBJ_ENCODING_RAW | “raw” |
| 字典 | OBJ_ENCODING_HT | “hashtable” |
| 雙端鏈表 | OBJ_ENCODING_LINKEDLIST | “l(fā)inkedlist” |
| 壓縮列表 | OBJ_ENCODING_ZIPLIST | “ziplist” |
| 整數(shù)集和 | OBJ_ENCODING_INTSET | “intset” |
| 跳表 | OBJ_ENCODING_SKIPLIST | “skiplist” |
可以看到,Redis會自適應(yīng)改變數(shù)據(jù)底層的編碼格式,而不是固定和一種類型綁定,這大大提高了靈活性
最后一次訪問時間
用來記錄最后一次訪問該數(shù)據(jù)的時間,可以獲得該數(shù)據(jù)的空轉(zhuǎn)時長,使用頻率等
引用計數(shù)
模仿C++的智能指針,使多個對象共享同一個底層數(shù)據(jù),以便于節(jié)省內(nèi)存占用,當(dāng)引用計數(shù)為0時,Redis會釋放該對象的內(nèi)存。
對象創(chuàng)建
對象操作主要涉及根據(jù)不同類型創(chuàng)建不同對象等操作
最基本的創(chuàng)建對象操作由createObject函數(shù)完成,函數(shù)根據(jù)給定類型和值創(chuàng)建編碼格式為raw的對象,其它創(chuàng)建對象的函數(shù)大多數(shù)都是直接或間接調(diào)用該函數(shù)
//object.c /* 根據(jù)type和ptr創(chuàng)建編碼為raw字符串的對象 */ robj *createObject(int type, void *ptr) {/* 申請對象內(nèi)存空間 */robj *o = zmalloc(sizeof(*o));/* 設(shè)置類型,編碼,值,引用計數(shù)初始化為1 */o->type = type;o->encoding = OBJ_ENCODING_RAW;o->ptr = ptr;o->refcount = 1;/* Set the LRU to the current lruclock (minutes resolution). *//* 計算當(dāng)前時間,賦值給lru作為最后一次訪問時間 */o->lru = LRU_CLOCK();/* 返回對象指針 */return o; }創(chuàng)建字符串類型對象
字符串有raw和embstr兩種類型和編碼格式,raw適用于長字符串,需要執(zhí)行兩次動態(tài)內(nèi)存的申請,而embstr適用于短字符串,僅僅需要一次內(nèi)存申請,在創(chuàng)建字符串類型的對象時,Redis會判斷數(shù)據(jù)的長度以決定采用哪一個
//object.c #define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44 /* 創(chuàng)建字符串類型對象,根據(jù)數(shù)據(jù)長度不同選擇不同的類型格式 */ robj *createStringObject(const char *ptr, size_t len) {/* 根據(jù)長度不同選擇不同的編碼方式 */if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)return createEmbeddedStringObject(ptr,len);elsereturn createRawStringObject(ptr,len); }可以看到,長度小于44的字符串默認(rèn)都采用embstr,而大于44的采用raw
raw類型的字符串對象創(chuàng)建直接調(diào)用createObject函數(shù)即可,因為raw類型的字符串底層編碼也是raw
//object.c /* 創(chuàng)建raw字符串類型變量 */ robj *createRawStringObject(const char *ptr, size_t len) {/* sdsnewlen()創(chuàng)建一個長度為len的sds字符串 */return createObject(OBJ_STRING,sdsnewlen(ptr,len)); }sdsnewlen函數(shù)是創(chuàng)建一個長度為len,值為ptr的sds變量
embstr類型的字符串創(chuàng)建不可以調(diào)用createObject函數(shù),由于采用embstr編碼格式,數(shù)據(jù)分布是不同的,需要重新實現(xiàn)創(chuàng)建函數(shù)
//object.c /* 創(chuàng)建類型為embstr,編碼為embstr的字符串對象 */ robj *createEmbeddedStringObject(const char *ptr, size_t len) {/* 和sds對象的創(chuàng)建有關(guān) */robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr8)+len+1);struct sdshdr8 *sh = (void*)(o+1);/* 設(shè)置類型,編碼,數(shù)據(jù),引用計數(shù),最后一次訪問時間 */o->type = OBJ_STRING;o->encoding = OBJ_ENCODING_EMBSTR;o->ptr = sh+1;o->refcount = 1;o->lru = LRU_CLOCK();/* 將數(shù)據(jù)復(fù)制給sds對象,和sds有關(guān) */sh->len = len;sh->alloc = len;sh->flags = SDS_TYPE_8;if (ptr) {memcpy(sh->buf,ptr,len);sh->buf[len] = '\0';} else {memset(sh->buf,0,len+1);}return o; }此外,Redis還提供根據(jù)長整型,長浮點型創(chuàng)建一個字符串類型對象,本質(zhì)都一樣,這里不再一一贅述
創(chuàng)建其它類型對象
除了字符串類型對象之外,其它類型對象的創(chuàng)建都顯得比較簡單,僅僅是創(chuàng)建一個相應(yīng)類型的變量,然后調(diào)用createObject函數(shù),返回后將編碼格式改成對應(yīng)類型的編碼格式
//object.c /* 創(chuàng)建快速列表對象 */ robj *createQuicklistObject(void) {quicklist *l = quicklistCreate();robj *o = createObject(OBJ_LIST,l);o->encoding = OBJ_ENCODING_QUICKLIST;return o; }/* 創(chuàng)建壓縮列表對象 */ robj *createZiplistObject(void) {unsigned char *zl = ziplistNew();robj *o = createObject(OBJ_LIST,zl);o->encoding = OBJ_ENCODING_ZIPLIST;return o; }/* 創(chuàng)建集合對象 */ robj *createSetObject(void) {dict *d = dictCreate(&setDictType,NULL);robj *o = createObject(OBJ_SET,d);o->encoding = OBJ_ENCODING_HT;return o; }/* 創(chuàng)建整數(shù)集合對象 */ robj *createIntsetObject(void) {intset *is = intsetNew();robj *o = createObject(OBJ_SET,is);o->encoding = OBJ_ENCODING_INTSET;return o; }/* 創(chuàng)建哈希對象 */ robj *createHashObject(void) {unsigned char *zl = ziplistNew();robj *o = createObject(OBJ_HASH, zl);o->encoding = OBJ_ENCODING_ZIPLIST;return o; }/* 創(chuàng)建有序集合對象 */ robj *createZsetObject(void) {zset *zs = zmalloc(sizeof(*zs));robj *o;zs->dict = dictCreate(&zsetDictType,NULL);zs->zsl = zslCreate();o = createObject(OBJ_ZSET,zs);o->encoding = OBJ_ENCODING_SKIPLIST;return o; }/* 創(chuàng)建集合壓縮列表對象 */ robj *createZsetZiplistObject(void) {unsigned char *zl = ziplistNew();robj *o = createObject(OBJ_ZSET,zl);o->encoding = OBJ_ENCODING_ZIPLIST;return o; }小結(jié)
本篇主要是對Redis對象系統(tǒng)的一個概述,核心目的就是弄清楚Redis底層的類型和編碼都有哪些,接下來會對每個數(shù)據(jù)結(jié)構(gòu)進行具體的分析,到時候還會引用本篇的部分代碼。分析數(shù)據(jù)結(jié)構(gòu)是最無聊的事情,也正因為如此才沒有在最開始分析,不過為了后面的持久化功能,對象系統(tǒng)是不得不啃的骨頭,希望自己能夠堅持!
與50位技術(shù)專家面對面20年技術(shù)見證,附贈技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的Redis源码剖析(九)对象系统概述的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Redis源码剖析(八)链表
- 下一篇: 【算法】差分约束系统