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