Redis原始碼學習簡記(七)object原理與個人理解

TuxedoLinux發表於2018-05-26
Redis原始碼學習簡記(七)object原理與個人理解

        object是redis中的封裝系統。其把string,list,set,zset與hash封裝成一個統一的物件,命名為robj。該資料結構中,儲存了型別,編碼,引用次數,資料與LRU替換演算法的一些資料。具體先看看這個資料結構的定義,在server.h中定義。

  1.  //redis屬於key-value 資料庫  
  2.  //nosql資料庫,這種對映關係使用dict用來維護  
  3.  //而dict實體資料(dictentry)中有欄位void *value  
  4. typedef struct redisObject {  
  5.     unsigned type:4;  
  6.     /* 
  7.         string 0 
  8.         list 1 
  9.         set 2 
  10.         zset 3 
  11.         hash 4 
  12.         4個bit 
  13.  
  14.     */  
  15.   
  16.     unsigned encoding:4;  
  17.     //編碼方式 4個bit  
  18.     unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or 
  19.                             * LFU data (least significant 8 bits frequency 
  20.                             * and most significant 16 bits access time). */  
  21.     //24bit,LRU替換演算法                              
  22.     int refcount;//引用計數32bit 4位元組  
  23.     void *ptr;//32位系統4位元組 64位系統8位元組  
  24.     //指向真正資料  
  25. } robj;  

來詳細講一下這裡面的東西。首先type,定義如下,其實就是4個bit用來儲存0~4的資料。

  1. /* The actual Redis Object */  
  2. #define OBJ_STRING 0    /* String object. */  
  3. #define OBJ_LIST 1      /* List object. */  
  4. #define OBJ_SET 2       /* Set object. */  
  5. #define OBJ_ZSET 3      /* Sorted set object. */  
  6. #define OBJ_HASH 4      /* Hash object. */  

然後編碼方式encoding。編碼方式的話,總的來說有11種,也是用4個bit來存,4個bit最大為16種編碼。

  1. #define OBJ_ENCODING_RAW 0     /* Raw representation */  
  2. #define OBJ_ENCODING_INT 1     /* Encoded as integer */  
  3. #define OBJ_ENCODING_HT 2      /* Encoded as hash table */  
  4. #define OBJ_ENCODING_ZIPMAP 3  /* Encoded as zipmap */  
  5. #define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */  
  6. #define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */  
  7. #define OBJ_ENCODING_INTSET 6  /* Encoded as intset */  
  8. #define OBJ_ENCODING_SKIPLIST 7  /* Encoded as skiplist */  
  9. #define OBJ_ENCODING_EMBSTR 8  /* Embedded sds string encoding */  
  10. #define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */  
  11. #define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */  

接下來的為lru,用於儲存LRU替換演算法,現在先不深究,使用了LRU_BITS (24bits)三個位元組儲存。

refcount為引用指標,作用跟智慧指標相似,用於計算物件的引用次數,當物件引用次數為0時,則釋放其使用空間。

最後的void *ptr則為最終指向資料的指標。這些資料基本就是之前分析的那些型別的資料。

資料結構不難理解,然後看看他的基本實現函式吧。基本都在object.c中實現。

首先是建立,該函式根據型別與ptr指向的資料返回一個robj指標。

  1. robj *createObject(int type, void *ptr) {  
  2.   
  3.     robj *o = zmalloc(sizeof(*o));//分配空間  
  4.     o->type = type;//設定型別  
  5.     o->encoding = OBJ_ENCODING_RAW;//原生編碼模式  
  6.     //這裡分了很多種,後面看到了再詳細討論  
  7.     o->ptr = ptr;//執行真正的資料  
  8.     o->refcount = 1;//引用計數  
  9.   
  10.     /* Set the LRU to the current lruclock (minutes resolution), or 
  11.      * alternatively the LFU counter. */  
  12.     //對於頁面置換演算法先不考慮,後面研究到了再看。  
  13.     if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {  
  14.         o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;  
  15.     } else {  
  16.         o->lru = LRU_CLOCK();  
  17.     }  
  18.     return o;  
  19. }  

設定一種特殊的refcount。使得該物件時shred。增加引用於減少引用,都會檢查這種特殊的引用計數。下面是使得物件變為共享的方法,其實就是將refcout數值設為int_max.

  1. robj *makeObjectShared(robj *o) {  
  2.     serverAssert(o->refcount == 1);  
  3.     o->refcount = OBJ_SHARED_REFCOUNT;  
  4.     //使得物件為共享,設定為obj_shared_refcount 其大小為int_max  
  5.     return o;  
  6. }  

接下來是建立字串物件的兩種方法。

先來介紹原生字串的建立方法。簡單的呼叫sdsnewlen的方法,然後得到字串指標後在呼叫create進行建立操作。

  1. /* Create a string object with encoding OBJ_ENCODING_RAW, that is a plain 
  2.  * string object where o->ptr points to a proper sds string. */  
  3. robj *createRawStringObject(const char *ptr, size_t len) {  
  4.     //建立RawStringObject型別  
  5.     //其實是一個sds型別,而在預設情況下,encoding為OBJ_ENCODING_RAW  
  6.     /* 
  7.         #define OBJ_STRING 0     
  8.         #define OBJ_LIST 1      
  9.         #define OBJ_SET 2        
  10.         #define OBJ_ZSET 3       
  11.         #define OBJ_HASH 4       
  12.     */  
  13.     return createObject(OBJ_STRING, sdsnewlen(ptr,len));  
  14. }  

另外一種建立的方法這是一種字串直接是新增在物件後面,該字串直接儲存在物件的後面。規定整個物件不大於64個位元組。

  1. /* Create a string object with encoding OBJ_ENCODING_EMBSTR, that is 
  2.  * an object where the sds string is actually an unmodifiable string 
  3.  * allocated in the same chunk as the object itself. */  
  4. robj *createEmbeddedStringObject(const char *ptr, size_t len) {  
  5.     //先一步步看程式碼  
  6.     robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr8)+len+1);  
  7.     //分配了robj資料與sdshdr8的sds頭空間加上了len+1的長度,多出來的1長度儲存null  
  8.     struct sdshdr8 *sh = (void*)(o+1);  
  9.     //o+1根據robj的結構體的位元組進行增長  
  10.     //robj總共16位元組,那麼加上16剛剛好是sdshdr8的頭部  
  11.     o->type = OBJ_STRING;  
  12.     o->encoding = OBJ_ENCODING_EMBSTR;  
  13.     o->ptr = sh+1;  
  14.     //sh+1即加上sdshdr8的位元組數,而sdshdr8為3位元組即len+alloc+flags  
  15.     //那麼加1後,ptr指向的為sh->buf  
  16.     /* 
  17.     在研究字元所佔的位元組數中發現一個有趣的現象; 
  18.         struct __attribute__ ((__packed__)) sdshdr8 { 
  19.                 uint8_t len;  
  20.                 uint8_t alloc;  
  21.                 unsigned char flags;  
  22.                 char buf[]; 
  23.             }; 
  24.     由於char buf[]    會導致計算sdshdr8的大小不算上buf[]很奇怪 
  25.     而使用指標的話,則會加上8位元組用於儲存 
  26.     而[]中有值時也會增加其長度,若不寫值時則會不計算。而buf的地址為flags最後一位。 
  27.  
  28.     */  
  29.     o->refcount = 1;  
  30.     //初始化引用計數  
  31.     if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {  
  32.         o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;  
  33.     } else {  
  34.         o->lru = LRU_CLOCK();  
  35.     }  
  36.     //對於頁面演算法先不研究。  
  37.     //sds的初始化  
  38.     sh->len = len;  
  39.     sh->alloc = len;  
  40.     sh->flags = SDS_TYPE_8;  
  41.     if (ptr == SDS_NOINIT)  
  42.         //sds_noint 為一個靜態const char *  
  43.         //判斷是否要初始化  
  44.   
  45.         sh->buf[len] = '\0';  
  46.     else if (ptr) {  
  47.         //初始化的話則看ptr的值是否為NULL  
  48.         memcpy(sh->buf,ptr,len);  
  49.         sh->buf[len] = '\0';  
  50.     } else {  
  51.         memset(sh->buf,0,len+1);  
  52.     }  
  53.     return o;  
  54. }  

這兩種字串的編碼,通過一個總的字串介面進行呼叫。這裡面如上面所說的,EmbeddedString總共包括下面幾個部分

robj 16位元組 sdshdr8頭部3個位元組 加上一個 '\0' 一個位元組,最後儲存內容是44個位元組

3+16+1+44=64。剛好是64節裝完所有的資料。

  1. /* Create a string object with EMBSTR encoding if it is smaller than 
  2.  * OBJ_ENCODING_EMBSTR_SIZE_LIMIT, otherwise the RAW encoding is 
  3.  * used. 
  4.  * 
  5.  * The current limit of 44 is chosen so that the biggest string object 
  6.  * we allocate as EMBSTR will still fit into the 64 byte arena of jemalloc. */  
  7. #define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44  
  8. robj *createStringObject(const char *ptr, size_t len) {  
  9.     //保證使用embeddedstringboject的大小為64否則呼叫creteRawStringObject  
  10.     //設定其長度最長為44+16+3+1=64  
  11.     //事實上sdshdr8可儲存256個字元  
  12.     if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)  
  13.         return createEmbeddedStringObject(ptr,len);  
  14.     else  
  15.         return createRawStringObject(ptr,len);  
  16. }  

看完了字串的封裝。觀察一下整型的封裝方法。總的來說當能使用8個位元組32位儲存的時候,robj中的ptr直接就是該整數。相當於彙編中的立即數。若超出該範圍,那麼就是用字串的形式儲存該資料。這時ptr就當指標使用了。

  1. robj *createStringObjectFromLongLong(long long value) {  
  2.     robj *o;  
  3.     if (value >= 0 && value < OBJ_SHARED_INTEGERS) {  
  4.         incrRefCount(shared.integers[value]);  
  5.         //大概瞭解了shared 它是一個機構體,包含了大量共享的的資料。  
  6.         //struct sharedObjectsStruct  
  7.         o = shared.integers[value];  
  8.     } else {  
  9.         if (value >= LONG_MIN && value <= LONG_MAX) {  
  10.             o = createObject(OBJ_STRING, NULL);  
  11.             o->encoding = OBJ_ENCODING_INT;  
  12.             o->ptr = (void*)((long)value);  
  13.             //若能使用8位元組儲存,直接將其儲存到ptr中相當於彙編的立即數。  
  14.         } else {  
  15.             //存不了則使用sds的模式儲存  
  16.             o = createObject(OBJ_STRING,sdsfromlonglong(value));  
  17.         }  
  18.     }  
  19.     return o;  
  20. }  

儲存浮點數的方式,都是轉化為字串的形式。

  1. /* Create a string object from a long double. If humanfriendly is non-zero 
  2.  * it does not use exponential format and trims trailing zeroes at the end, 
  3.  * however this results in loss of precision. Otherwise exp format is used 
  4.  * and the output of snprintf() is not modified. 
  5.  * 
  6.  * The 'humanfriendly' option is used for INCRBYFLOAT and HINCRBYFLOAT. */  
  7. robj *createStringObjectFromLongDouble(long double value, int humanfriendly) {  
  8.     char buf[MAX_LONG_DOUBLE_CHARS];  
  9.     //humanfriendly用於處理浮點數,可以先不理會  
  10.     int len = ld2string(buf,sizeof(buf),value,humanfriendly);  
  11.     //將long double轉換成sds,然後進行儲存  
  12.     return createStringObject(buf,len);  
  13. }  

物件間字串的複製,僅限於字串儲存的資料(這裡包括了使用字串儲存的整型),或者使用立即數儲存的整型。

  1. /* Duplicate a string object, with the guarantee that the returned object 
  2.  * has the same encoding as the original one. 
  3.  * 
  4.  * This function also guarantees that duplicating a small integere object 
  5.  * (or a string object that contains a representation of a small integer) 
  6.  * will always result in a fresh object that is unshared (refcount == 1). 
  7.  * 
  8.  * The resulting object always has refcount set to 1. */  
  9. robj *dupStringObject(const robj *o) {  
  10.     robj *d;  
  11.   
  12.     serverAssert(o->type == OBJ_STRING);  
  13.   
  14.     switch(o->encoding) {  
  15.     case OBJ_ENCODING_RAW:  
  16.         return createRawStringObject(o->ptr,sdslen(o->ptr));  
  17.     case OBJ_ENCODING_EMBSTR:  
  18.         return createEmbeddedStringObject(o->ptr,sdslen(o->ptr));  
  19.     case OBJ_ENCODING_INT:  
  20.         d = createObject(OBJ_STRING, NULL);  
  21.         d->encoding = OBJ_ENCODING_INT;  
  22.         d->ptr = o->ptr;  
  23.         return d;  
  24.     default:  
  25.         serverPanic("Wrong encoding.");  
  26.         break;  
  27.     }  
  28. }  

接下來就是一些別的型別的建立,大多數都是使用該型別的create函式進行建立,後又釋放空間的函式,都是比較簡單。還有引用計數減少的函式,都比較直接簡單。直接看看就好了。

  1. robj *createQuicklistObject(void) {  
  2.     quicklist *l = quicklistCreate();  
  3.     robj *o = createObject(OBJ_LIST,l);  
  4.     o->encoding = OBJ_ENCODING_QUICKLIST;  
  5.     return o;  
  6. }  
  7.   
  8. robj *createZiplistObject(void) {  
  9.     unsigned char *zl = ziplistNew();  
  10.     robj *o = createObject(OBJ_LIST,zl);  
  11.     o->encoding = OBJ_ENCODING_ZIPLIST;  
  12.     return o;  
  13. }  
  14.   
  15. robj *createSetObject(void) {  
  16.     dict *d = dictCreate(&setDictType,NULL);  
  17.     robj *o = createObject(OBJ_SET,d);  
  18.     o->encoding = OBJ_ENCODING_HT;  
  19.     return o;  
  20. }  
  21.   
  22. robj *createIntsetObject(void) {  
  23.     intset *is = intsetNew();  
  24.     robj *o = createObject(OBJ_SET,is);  
  25.     o->encoding = OBJ_ENCODING_INTSET;  
  26.     return o;  
  27. }  
  28.   
  29. robj *createHashObject(void) {  
  30.     unsigned char *zl = ziplistNew();  
  31.     robj *o = createObject(OBJ_HASH, zl);  
  32.     o->encoding = OBJ_ENCODING_ZIPLIST;  
  33.     return o;  
  34. }  
  35.   
  36. robj *createZsetObject(void) {  
  37.     zset *zs = zmalloc(sizeof(*zs));  
  38.     robj *o;  
  39.   
  40.     zs->dict = dictCreate(&zsetDictType,NULL);  
  41.     zs->zsl = zslCreate();  
  42.     o = createObject(OBJ_ZSET,zs);  
  43.     o->encoding = OBJ_ENCODING_SKIPLIST;  
  44.     return o;  
  45. }  
  46.   
  47. robj *createZsetZiplistObject(void) {  
  48.     unsigned char *zl = ziplistNew();  
  49.     robj *o = createObject(OBJ_ZSET,zl);  
  50.     o->encoding = OBJ_ENCODING_ZIPLIST;  
  51.     return o;  
  52. }  
  53.   
  54. robj *createStreamObject(void) {  
  55.     stream *s = streamNew();  
  56.     robj *o = createObject(OBJ_STREAM,s);  
  57.     o->encoding = OBJ_ENCODING_STREAM;  
  58.     return o;  
  59. }  
  60.   
  61. robj *createModuleObject(moduleType *mt, void *value) {  
  62.     moduleValue *mv = zmalloc(sizeof(*mv));  
  63.     mv->type = mt;  
  64.     mv->value = value;  
  65.     return createObject(OBJ_MODULE,mv);  
  66. }  
  67.   
  68. void freeStringObject(robj *o) {  
  69.     if (o->encoding == OBJ_ENCODING_RAW) {  
  70.         sdsfree(o->ptr);  
  71.     }  
  72. }  
  73.   
  74. void freeListObject(robj *o) {  
  75.     if (o->encoding == OBJ_ENCODING_QUICKLIST) {  
  76.         quicklistRelease(o->ptr);  
  77.     } else {  
  78.         serverPanic("Unknown list encoding type");  
  79.     }  
  80. }  
  81.   
  82. void freeSetObject(robj *o) {  
  83.     switch (o->encoding) {  
  84.     case OBJ_ENCODING_HT:  
  85.         dictRelease((dict*) o->ptr);  
  86.         break;  
  87.     case OBJ_ENCODING_INTSET:  
  88.         zfree(o->ptr);  
  89.         break;  
  90.     default:  
  91.         serverPanic("Unknown set encoding type");  
  92.     }  
  93. }  
  94.   
  95. void freeZsetObject(robj *o) {  
  96.     zset *zs;  
  97.     switch (o->encoding) {  
  98.     case OBJ_ENCODING_SKIPLIST:  
  99.         zs = o->ptr;  
  100.         dictRelease(zs->dict);  
  101.         zslFree(zs->zsl);  
  102.         zfree(zs);  
  103.         break;  
  104.     case OBJ_ENCODING_ZIPLIST:  
  105.         zfree(o->ptr);  
  106.         break;  
  107.     default:  
  108.         serverPanic("Unknown sorted set encoding");  
  109.     }  
  110. }  
  111.   
  112. void freeHashObject(robj *o) {  
  113.     switch (o->encoding) {  
  114.     case OBJ_ENCODING_HT:  
  115.         dictRelease((dict*) o->ptr);  
  116.         break;  
  117.     case OBJ_ENCODING_ZIPLIST:  
  118.         zfree(o->ptr);  
  119.         break;  
  120.     default:  
  121.         serverPanic("Unknown hash encoding type");  
  122.         break;  
  123.     }  
  124. }  
  125.   
  126. void freeModuleObject(robj *o) {  
  127.     moduleValue *mv = o->ptr;  
  128.     mv->type->free(mv->value);  
  129.     zfree(mv);  
  130. }  
  131.   
  132. void freeStreamObject(robj *o) {  
  133.     freeStream(o->ptr);  
  134. }  
  135.   
  136. void incrRefCount(robj *o) {  
  137.     if (o->refcount != OBJ_SHARED_REFCOUNT) o->refcount++;  
  138. }  
  139.   
  140. void decrRefCount(robj *o) {  
  141.     if (o->refcount == 1) {  
  142.         switch(o->type) {  
  143.         case OBJ_STRING: freeStringObject(o); break;  
  144.         case OBJ_LIST: freeListObject(o); break;  
  145.         case OBJ_SET: freeSetObject(o); break;  
  146.         case OBJ_ZSET: freeZsetObject(o); break;  
  147.         case OBJ_HASH: freeHashObject(o); break;  
  148.         case OBJ_MODULE: freeModuleObject(o); break;  
  149.         case OBJ_STREAM: freeStreamObject(o); break;  
  150.         default: serverPanic("Unknown object type"); break;  
  151.         }  
  152.         zfree(o);  
  153.     } else {  
  154.         if (o->refcount <= 0) serverPanic("decrRefCount against refcount <= 0");  
  155.         if (o->refcount != OBJ_SHARED_REFCOUNT) o->refcount--;  
  156.     }  
  157. }  
  158.   
  159. /* This variant of decrRefCount() gets its argument as void, and is useful 
  160.  * as free method in data structures that expect a 'void free_object(void*)' 
  161.  * prototype for the free method. */  
  162. void decrRefCountVoid(void *o) {  
  163.     decrRefCount(o);  

相關文章