Redis源碼解析

來源:互聯網
上載者:User
Redis系統當中,針對字串進行的更加完善的封裝,建立了一個動態字串,並構建了大量的實用api。相關的實現代碼為sds.h及sds.c,以下為我的源碼閱讀筆記。內容較多,逐步更新

  1. typedef char *sds;    struct __attribute__ ((__packed__)) sdshdr5 {      usigned char flags;      char buf[];  };  struct __attribute__ ((__packed__)) sdshdr8 {      uint8_t len;      uint8_t alloc;      unsigned char flags;      char buf[];  };  struct __attribute__ ((__packed__)) sdshdr16 {      uint16_t len;      uint16_t alloc;      unsigned char flags;      char buf[];  };  struct __attribute__ ((__packed__)) sdshdr32 {      uint32_t len;      uint32_t alloc;      unsigned char flags;      char buf[];  };  struct __attribute__ ((__packed__)) sdshdr64 {      uint64_t len;      uint64_t alloc;      unsigned char flags;      char buf[];  };    #define SDS_TYPE_5 0  #define SDS_TYPE_8 1  #define SDS_TYPE_16 2  #define SDS_TYPE_32 3  #define SDS_TYPE_64 4  #define SDS_TYPE_MASK 7  #define SDS_TYPE_BITS 3  #define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));  #define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))  #define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)

以上是動態字串的結構體聲明及define聲明的函數。動態字串一共有5種類型,分別為不同長度的字串所實用。我在這裡稱之為動態字串的頭部。

sdshdr5:長度為小於32的字串

sdshdr8:長度為小於256的字串

sdshdr16:長度為小於2^16的字串

sdshdr32:長度為小於2^32的字串。這裡有一點注意,若是機器的LONG_MAXbu不等於LLONG_MAX,則返回sdshdr64類型。

sdshdr64:其他所有長度都實用此類。

sdshdr5這個類型不同於其他類型,它缺少len成員與alloc成員,它的判斷與處理都比較特別。但是官方在代碼有過一段注釋,如下:

/* Note: sdshdr5 is never used, we just access the flags byte directly.

* However is here to document the layout of type 5 SDS strings. */

注釋中說明,這個類型從未被使用,所以我們在這裡姑且不考慮它,而實際上,它的處理操作本質上與其他類型並沒有什麼區別。方便起見,我們以通用的類型進行研究。

結構體當中,有四個類,分別為len、alloc、flags與buf。

len:字串的長度。

alloc:字串記憶體總大小,注意,alloc不同於len。len是實際字串的長度,而alloc,則是實際分配的記憶體大小(不包含sds頭與結尾的'\0'的大小)。為了減少字串內容增加時反覆的重新申請記憶體,redis當中會申請更多記憶體以備使用。當字串大小小於1MB的時候,申請兩倍大小的記憶體使用量,當字串大小大於等於1MB的時候,多申請1MB的記憶體以備使用。詳細的設定可以看之後的sdsMakeRoomFor函數的解析。

flags:作為區分不同類型的標記使用,暫時只使用了低3位來標記,高5位暫未使用,也供以後增加新功能時使用。上面代碼中define聲明的SDS_TYPE_*類型為相應的標記內容,用於區分不同的字串類型。比如flags等於SDS_TYPE_8時,則可以從字串開始位元組在向前17位元組,或者字串頭部開始擷取8位元組資料擷取當前字串的實際長度。等於SDS_TYPE_16時,從字串開始位元組向前33位元組,或者字串頭部開始擷取16自己擷取當前字串的實際長度。flags與頭部資訊的配合使用,將在之後的函數解析裡面大量出現。

buf:實際儲存字串內容的數組,同傳統數組一樣,結尾需要'\0'字元。

在sdshdr的聲明當中,我們可以看到 __attribute__ ((__packed__)) 關鍵字,這將使這個結構體在記憶體中不再遵守字串對齊規則,而是以記憶體緊湊的方式排列。所以可以從字串位置開始向前一個位元組便可以擷取flags資訊了,比如buf[-1]。具體__attribute__ ((__packed__))與字串對齊的內容請查看另一篇部落格。

SDS_HDR_VAR函數則通過結構體類型與字串開始位元組,擷取到動態字串頭部的開始位置,並賦值給sh指標。SDS_HDR函數則通過類型與字串開始位元組,返回動態字串頭部的指標。使用方式為可在之後的代碼當中看到,具體define聲明中的雙'#'號的使用方式與意義,請看草另一篇部落格。

sds比起傳統的字串,有著自己優勢跟便利之處。

1、記憶體預分配:sds通過預先分配了更多的記憶體量,來避免頻繁的申請、分配記憶體,無端的浪費了效能

2、惰性釋放:sds作為一般字元串使用之時,可以通過在不同位元組打上'\0'字元,來代表字串的截斷及減少長度,而不是需要清空多餘的位元組並釋放它們,那些記憶體再之後的操作當中可以當做空閑記憶體繼續使用。

3、二進位安全:作為非字串使用儲存資料之時,通過善用頭部的len屬性,可以儲存一些包含'\0'字元的資料。當然,一定要善用len屬性,api當中,如長度更新的函數,同樣通過'\0'字元來判斷結尾!

接下來開始介紹sds相關的api函數,第一批是聲明、定義在sds.h檔案內的靜態函數,這些函數都是針對動態字串頭部的屬性的擷取與修改,簡單易懂

  1. //擷取動態字串長度  static  inline  size_t  sdslen(const sds s) {      unsigned char flags = s[-1];//擷取頭部資訊中的flags屬性,因記憶體緊密相連,可以直接通過這種方式擷取      switch(flags&SDS_TASK_MASK) {//擷取類型,SDS_TASK_MASK為7,所以flags&SDS_TASK_MASK等於flags          case SDS_TYPE_5:              return SDS_TYPE_5_LEN(flags);//SDS_TYPE_5類型的長度擷取稍微不同,它的長度被定義在flags的高5位當中,具體可查看之後的sdsnewlen函數,或者下面的sdssetlen函數          case SDS_TYPE_8:              return SDS_HDR(8,s)->len;//SDS_HDR函數通過類型與字串開始位元組擷取頭部,以此擷取字串的長度          case SDS_TYPE_16:              return SDS_HDR(16,s)->len;          case SDS_TYPE_32:              return SDS_HDR(32,s)->len;          case SDS_TYPE_64:              RETURN SDS_HDR(64,S)->len;      }      return 0;  }    //擷取動態字串的剩餘記憶體  static inline size_t sdsavail(const sds s) {      unsigned char flags = s[-1];//擷取flags      switch(flags&SDS_TYPE_MASK) {          case SDS_TYPE_5://SDS_TYPE_5直接返回0,              return 0;          case SDS_TYPE_8: {              SDS_HDR_VAR(8,s);//通過SDS_HDR_VAR函數,將頭部指標放置在sh變數              return sh->alloc - sh->len;//總記憶體大小 - 字串長度,擷取可用記憶體大小          }          case SDS_TYPE_16: {              SDS_HDR_VAR(16,s);              return sh->alloc - sh->len;          }          case SDS_TYPE_32: {              SDS_HDR_VAR(32,s);              return sh->alloc - sh->len;          }          case SDS_TYPE_64: {              SDS_HDR_VAR(64,s);              return sh->alloc - sh-len;          }      }      return 0;  }    //重設字串長度  static inline void sdssetlen(sds s, size_t newlen) {      unsigned char flags = s[-1];//擷取flags      switch(flags&SDS_TASK_MASK) {          //SDS_TYPE_5的長度設定較為特殊,長度資訊寫在flags的高5位          case SDS_TYPE_5:              {                  unsigned char *fp = ((unsigned char*)s)-1;                  *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);              }              break;          //其他類型則是統一修改len屬性的值          case SDS_TYPE_8:              SDS_HDR(8,s)->len = newlen;              break;          case SDS_TYPE_16:              SDS_HDR(16,s)->len = newlen;              break;          case SDS_TYPE_32:              SDS_HDR(32,s)->len = newlen;              break;          case SDS_TYPE_64:              SDS_HDR(64,s)->len = newlen;              break;      }  }    //按照指定數值,增加字串長度  static inline void sdsinclen(sds s, size_t inc) {      unsigned char flags = s[-1];//擷取flags      switch(flags&SDS_TYPE_MASK) {          //SDS_TYPE_5類型使用上面的函數,擷取長度、更新、設定          case SDS_TYPE_5:              {                  unsigned char *fp = ((unsigned char*)s)-1;                  unsigned char newlen = SDS_TYPE_LEN(flags)+inc;                  *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);              }              break;          //其他類型則直接通過SDS_HDR函數,更新len值          case SDS_TYPE_8:              SDS_HDR(8,s)->len += inc;              break;          case SDS_TYPE_16:              SDS_HDR(16,s)->len += inc;              break;          case SDS_TYPE_32:              SDS_HDR(32,s)->len += inc;              break;          case SDS_TYPE_64:              SDS_HDR(64,s)->len += inc;              break;      }  }    //擷取動態字串的總記憶體  static inline size_t sdsalloc(const sds s) {      unsigned char flags = s[-1];//擷取flags      switch(flags&SDS_TASK_MASK) {          //SDS_TYPE_5直接通過SDS_TYPE_5_LEN函數返回          case SDS_TYPE_5:              return SDS_TYPE_5_LEN(flags);          //其他類型則返回頭部資訊中的alloc屬性          case SDS_TYPE_8:              return SDS_HDR(8,s)->alloc;          case SDS_TYPE_16:              return SDS_HDR(16,s)->alloc;          case SDS_TYPE_32:              return SDS_HDR(32,s)->alloc;          case SDS_TYPE_64:              return SDS_HDR(64,s)->alloc;      }      return 0;  }    //重設字串記憶體大小  static inline size_t sdssetalloc(sds s, size_t newlen) {      unsigned cahr flags = s[-1];//擷取flags      switch(flags&SDS_TASK_MASK) {          case SDS_TYPE_5:              //官方注釋,SDS_TYPE_5不做任何操作              /*Nothing to do, this type has no total allocation info. */              break;          //其他類型直接修改頭部資訊中的alloc屬性          case SDS_TYPE_8:              SDS_HDR(8,s)->alloc = newlen;              break;          case SDS_TYPE_16:              SDS_HDR(16,s)->alloc = newlen;              break;          case SDS_TYPE_32:              SDS_HDR(32,s)->alloc = newlen;              break;          case SDS_TYPE_64:              SDS_HDR(64,s)->alloc = newlen;              break;      }  }

上述的幾個函數,sdslen,sdsavail,sdssetlen,sdsinclen,sdsalloc,sdssetalloc函數,都是基本的頭部屬性操作函數。代碼的難度也不大,可以直觀的閱讀、理解。

接下來的sds的相關api,數量有點多,之後的dict、zskiplist也是有大量api,挑部分代碼較多,需要逐行理解的函數來記錄、分析。

sdsnewlen:

  1. //建立一個新的sds對象  sds sdsnewlen(const void *init, size_t initlen) {      void *sh;      sds s;      char type = sdsReqType(initlen);//根據初始化長度擷取對應結構體類型      if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;//若長度為0的則初始化為SDS_TYPE_8類型      int hdrlen = sdsHdrSize(type);      unsigned char *fp;            sh = s_malloc(hdrlen+initlen+1);//申請足夠的記憶體,頭部大小+初始化大小+結尾符      if (!init)          memset(sh, 0, hdrlen+initlen+1);      if (sh == NULL) return NULL;      s = (char*)sh+hdrlen;//擷取字串起始位元組      fp = ((unsigned char*)s)-1;//擷取flags位元組      switch(type) {          //SDS_TYPE_5又是特立獨行了,有自己的初始化方案,我都懶得說明了。。。          case SDS_TYPE_5: {              *fp = type | (initlen << SDS_TYPE_BITS);              break;          }          //其他類型通過SDS_HDR_VAR函數擷取頭部資訊,並逐步初始化          case SDS_TYPE_8: {              SDS_HDR_VAR(8,s);              sh->len = initlen;              sh->alloc = initlen;              *fp = type;              break;          }          case SDS_TYPE_16: {              SDS_HDR_VAR(16,s);              sh->len = initlen;              sh->alloc = initlen;              *fp = type;              break;          }          case SDS_TYPE_32: {              SDS_HDR_VAR(32,s);              sh->len = initlen;              sh->alloc = initlen;              *fp = type;              break;          }          case SDS_TYPE_64: {              SDS_HDR_VAR(64,s);              sh->len = initlen;              sh->alloc = initlen;              *fp = type;              break;          }      }      //根據init與initlen,將內容複寫給字串      if (initlen && init)          memcpy(s, init, initlen);      //打上結尾符      s[initlen] = '\0';      return s;  }

sdsnewlen根據參數給予的init字串與initlen初始長度,產生並返回一個動態字串。代碼都打上了注釋,閱讀已經沒什麼難度了。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.