Redis 對象系統
1. 介紹
redis中基於雙端鏈表、簡單動態字符串(sds)、字典、跳躍表、整數集合、壓縮列表、快速列表等等數據結構實現了一個對象系統,並且實現了5種不同的對象,每種對象都使用了至少一種前面的數據結構,優化對象在不同場合下的使用效率。
2. 對象的系統的實現
redis 3.2版本。所有註釋在github中:對象系統的註釋
2.1 對象的結構
對象結構robj功能:
- 爲5種不同的對象類型提供同一的表示形式。
- 爲不同的對象適用於不同的場景,支持同一種對象類型採用多種的數據結構方式。
- 支持引用計數,實現對象共享機制。
- 記錄對象的訪問時間,便於刪除對象。
對象結構定義在redis 3.2版本的server.h
#define LRU_BITS 24
#define LRU_CLOCK_MAX ((1<<LRU_BITS)-1) /* Max value of obj->lru */
#define LRU_CLOCK_RESOLUTION 1000 /* LRU clock resolution in ms */
typedef struct redisObject {
//對象的數據類型,佔4bits,共5種類型
unsigned type:4;
//對象的編碼類型,佔4bits,共10種類型
unsigned encoding:4;
//least recently used
//實用LRU算法計算相對server.lruclock的LRU時間
unsigned lru:LRU_BITS; /* lru time (relative to server.lruclock) */
//引用計數
int refcount;
//指向底層數據實現的指針
void *ptr;
} robj;
//type的佔5種類型:
/* Object types */
#define OBJ_STRING 0 //字符串對象
#define OBJ_LIST 1 //列表對象
#define OBJ_SET 2 //集合對象
#define OBJ_ZSET 3 //有序集合對象
#define OBJ_HASH 4 //哈希對象
/* Objects encoding. Some kind of objects like Strings and Hashes can be
* internally represented in multiple ways. The 'encoding' field of the object
* is set to one of this fields for this object. */
// encoding 的10種類型
#define OBJ_ENCODING_RAW 0 /* Raw representation */ //原始表示方式,字符串對象是簡單動態字符串
#define OBJ_ENCODING_INT 1 /* Encoded as integer */ //long類型的整數
#define OBJ_ENCODING_HT 2 /* Encoded as hash table */ //字典
#define OBJ_ENCODING_ZIPMAP 3 /* Encoded as zipmap */ //不在使用
#define OBJ_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */ //雙端鏈表,不在使用
#define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */ //壓縮列表
#define OBJ_ENCODING_INTSET 6 /* Encoded as intset */ //整數集合
#define OBJ_ENCODING_SKIPLIST 7 /* Encoded as skiplist */ //跳躍表和字典
#define OBJ_ENCODING_EMBSTR 8 /* Embedded sds string encoding */ //embstr編碼的簡單動態字符串
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */ //由壓縮列表組成的雙向列表-->快速列表
2.2 字符串對象的底層實現類型
編碼—encoding | 對象—ptr |
---|---|
OBJ_ENCODING_RAW | 簡單動態字符串實現的字符串對象 |
OBJ_ENCODING_INT | 整數值實現的字符串對象 |
OBJ_ENCODING_EMBSTR | embstr編碼的簡單動態字符串實現的字符串對象 |
2.3 列表對象的底層實現類型
編碼—encoding | 對象—ptr |
---|---|
OBJ_ENCODING_QUICKLIST | 快速列表實現的列表對象 |
OBJ_ENCODING_ZIPLIST | 壓縮列表實現的列表對象 |
2.4 集合對象的底層實現類型
編碼—encoding | 對象—ptr |
---|---|
OBJ_ENCODING_HT | 字典實現的集合對象 |
OBJ_ENCODING_INTSET | 整數集合實現的集合對象 |
2.5 哈希對象的底層實現類型
編碼—encoding | 對象—ptr |
---|---|
OBJ_ENCODING_ZIPLIST | 壓縮列表實現的哈希對象 |
OBJ_ENCODING_HT | 字典實現的哈希對象 |
2.6 有序集合對象的底層實現類型
編碼—encoding | 對象—ptr |
---|---|
OBJ_ENCODING_SKIPLIST | 跳躍表和字典實現的有序集合對象 |
OBJ_ENCODING_ZIPLIST | 壓縮列表實現的有序集合對象 |
3. 對象系統的重要操作
3.1創建一個字符串對象
- 編碼爲OBJ_ENCODING_RAW
robj *createObject(int type, void *ptr) { //創建一個對象
robj *o = zmalloc(sizeof(*o)); //分配空間
o->type = type; //設置對象類型
o->encoding = OBJ_ENCODING_RAW; //設置編碼方式爲OBJ_ENCODING_RAW
o->ptr = ptr; //設置
o->refcount = 1; //引用計數爲1
/* Set the LRU to the current lruclock (minutes resolution). */
o->lru = LRU_CLOCK(); //計算設置當前LRU時間
return o;
}
- 編碼爲OBJ_ENCODING_EMBSTR
/* Create a string object with encoding OBJ_ENCODING_EMBSTR, that is
* an object where the sds string is actually an unmodifiable string
* allocated in the same chunk as the object itself. */
//創建一個embstr編碼的字符串對象
robj *createEmbeddedStringObject(const char *ptr, size_t len) {
robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr8)+len+1); //分配空間
struct sdshdr8 *sh = (void*)(o+1); //o+1剛好就是struct sdshdr8的地址
o->type = OBJ_STRING; //類型爲字符串對象
o->encoding = OBJ_ENCODING_EMBSTR; //設置編碼類型OBJ_ENCODING_EMBSTR
o->ptr = sh+1; //指向分配的sds對象,分配的len+1的空間首地址
o->refcount = 1; //設置引用計數
o->lru = LRU_CLOCK(); //計算設置當前LRU時間
sh->len = len; //設置字符串長度
sh->alloc = len; //設置最大容量
sh->flags = SDS_TYPE_8; //設置sds的類型
if (ptr) { //如果傳了字符串參數
memcpy(sh->buf,ptr,len); //將傳進來的ptr保存到對象中
sh->buf[len] = '\0'; //結束符標誌
} else {
memset(sh->buf,0,len+1); //否則將對象的空間初始化爲0
}
return o;
}
- 兩種字符串對象編碼方式的區別
/* Create a string object with EMBSTR encoding if it is smaller than
* REIDS_ENCODING_EMBSTR_SIZE_LIMIT, otherwise the RAW encoding is
* used.
*
* The current limit of 39 is chosen so that the biggest string object
* we allocate as EMBSTR will still fit into the 64 byte arena of jemalloc. */
//sdshdr8的大小爲3個字節,加上1個結束符共4個字節
//redisObject的大小爲16個字節
//redis使用jemalloc內存分配器,且jemalloc會分配8,16,32,64等字節的內存
//一個embstr固定的大小爲16+3+1 = 20個字節,因此一個最大的embstr字符串爲64-20 = 44字節
#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
// 創建字符串對象,根據長度使用不同的編碼類型
// createRawStringObject和createEmbeddedStringObject的區別是:
// createRawStringObject是當字符串長度大於44字節時,robj結構和sdshdr結構在內存上是分開的
// createEmbeddedStringObject是當字符串長度小於等於44字節時,robj結構和sdshdr結構在內存上是連續的
robj *createStringObject(const char *ptr, size_t len) {
if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
return createEmbeddedStringObject(ptr,len);
else
return createRawStringObject(ptr,len);
}
3.2 字符串對象編碼的優化
/* Try to encode a string object in order to save space */
//嘗試優化字符串對象的編碼方式以節約空間
robj *tryObjectEncoding(robj *o) {
long value;
sds s = o->ptr;
size_t len;
/* Make sure this is a string object, the only type we encode
* in this function. Other types use encoded memory efficient
* representations but are handled by the commands implementing
* the type. */
serverAssertWithInfo(NULL,o,o->type == OBJ_STRING);
/* We try some specialized encoding only for objects that are
* RAW or EMBSTR encoded, in other words objects that are still
* in represented by an actually array of chars. */
//如果字符串對象的編碼類型爲RAW或EMBSTR時,纔對其重新編碼
if (!sdsEncodedObject(o)) return o;
/* It's not safe to encode shared objects: shared objects can be shared
* everywhere in the "object space" of Redis and may end in places where
* they are not handled. We handle them only as values in the keyspace. */
//如果refcount大於1,則說明對象的ptr指向的值是共享的,不對共享對象進行編碼
if (o->refcount > 1) return o;
/* Check if we can represent this string as a long integer.
* Note that we are sure that a string larger than 20 chars is not
* representable as a 32 nor 64 bit integer. */
len = sdslen(s); //獲得字符串s的長度
//如果len小於等於20,表示符合long long可以表示的範圍,且可以轉換爲long類型的字符串進行編碼
if (len <= 20 && string2l(s,len,&value)) {
/* This object is encodable as a long. Try to use a shared object.
* Note that we avoid using shared integers when maxmemory is used
* because every object needs to have a private LRU field for the LRU
* algorithm to work well. */
if ((server.maxmemory == 0 ||
(server.maxmemory_policy != MAXMEMORY_VOLATILE_LRU &&
server.maxmemory_policy != MAXMEMORY_ALLKEYS_LRU)) &&
value >= 0 &&
value < OBJ_SHARED_INTEGERS) //如果value處於共享整數的範圍內
{
decrRefCount(o); //原對象的引用計數減1,釋放對象
incrRefCount(shared.integers[value]); //增加共享對象的引用計數
return shared.integers[value]; //返回一個編碼爲整數的字符串對象
} else { //如果不處於共享整數的範圍
if (o->encoding == OBJ_ENCODING_RAW) sdsfree(o->ptr); //釋放編碼爲OBJ_ENCODING_RAW的對象
o->encoding = OBJ_ENCODING_INT; //轉換爲OBJ_ENCODING_INT編碼
o->ptr = (void*) value; //指針ptr指向value對象
return o;
}
}
/* If the string is small and is still RAW encoded,
* try the EMBSTR encoding which is more efficient.
* In this representation the object and the SDS string are allocated
* in the same chunk of memory to save space and cache misses. */
//如果len小於44,44是最大的編碼爲EMBSTR類型的字符串對象長度
if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT) {
robj *emb;
if (o->encoding == OBJ_ENCODING_EMBSTR) return o; //將RAW對象轉換爲OBJ_ENCODING_EMBSTR編碼類型
emb = createEmbeddedStringObject(s,sdslen(s)); //創建一個編碼類型爲OBJ_ENCODING_EMBSTR的字符串對象
decrRefCount(o); //釋放之前的對象
return emb;
}
/* We can't encode the object...
*
* Do the last try, and at least optimize the SDS string inside
* the string object to require little space, in case there
* is more than 10% of free space at the end of the SDS string.
*
* We do that only for relatively large strings as this branch
* is only entered if the length of the string is greater than
* OBJ_ENCODING_EMBSTR_SIZE_LIMIT. */
//無法進行編碼,但是如果s的未使用的空間大於使用空間的10分之1
if (o->encoding == OBJ_ENCODING_RAW &&
sdsavail(s) > len/10)
{
o->ptr = sdsRemoveFreeSpace(o->ptr); //釋放所有的未使用空間
}
/* Return the original object. */
return o;
}
3.3 引用計數管理對象
//引用計數加1
void incrRefCount(robj *o) {
o->refcount++;
}
//引用計數減1
void decrRefCount(robj *o) {
if (o->refcount <= 0) serverPanic("decrRefCount against refcount <= 0");
//當引用對象等於1時,在操作引用計數減1,直接釋放對象的ptr和對象空間
if (o->refcount == 1) {
switch(o->type) {
case OBJ_STRING: freeStringObject(o); break;
case OBJ_LIST: freeListObject(o); break;
case OBJ_SET: freeSetObject(o); break;
case OBJ_ZSET: freeZsetObject(o); break;
case OBJ_HASH: freeHashObject(o); break;
default: serverPanic("Unknown object type"); break;
}
zfree(o);
} else {
o->refcount--; //否則減1
}
}
3.4 對象的複製,創建的對象非共享
//返回 複製的o對象的副本的地址,且創建的對象非共享
robj *dupStringObject(robj *o) {
robj *d;
serverAssert(o->type == OBJ_STRING); //一定是OBJ_STRING類型
switch(o->encoding) { //根據不同的編碼類型
case OBJ_ENCODING_RAW:
return createRawStringObject(o->ptr,sdslen(o->ptr)); //創建的對象非共享
case OBJ_ENCODING_EMBSTR:
return createEmbeddedStringObject(o->ptr,sdslen(o->ptr)); //創建的對象非共享
case OBJ_ENCODING_INT: //整數編碼類型
d = createObject(OBJ_STRING, NULL); //即使是共享整數範圍內的整數,創建的對象也是非共享的
d->encoding = OBJ_ENCODING_INT;
d->ptr = o->ptr;
return d;
default:
serverPanic("Wrong encoding.");
break;
}
}
3.5 對象的解碼操作
將保存的整數值解碼成字符串對象返回回來。
/* Get a decoded version of an encoded object (returned as a new object).
* If the object is already raw-encoded just increment the ref count. */
//將對象是整型的解碼爲字符串並返回,如果是字符串編碼則直接返回輸入對象,只需增加引用計數
robj *getDecodedObject(robj *o) {
robj *dec;
if (sdsEncodedObject(o)) { //如果是OBJ_ENCODING_RAW或OBJ_ENCODING_EMBSTR類型的對象
incrRefCount(o); //增加引用計數,返回一個共享的對象
return o;
}
if (o->type == OBJ_STRING && o->encoding == OBJ_ENCODING_INT) { //如果是整數對象
char buf[32];
ll2string(buf,32,(long)o->ptr); //將整數轉換爲字符串
dec = createStringObject(buf,strlen(buf)); //創建一個字符串對象
return dec;
} else {
serverPanic("Unknown encoding type");
}
}
3.6 其他操作
所有註釋在github中:對象系統的註釋