1. 程式人生 > 其它 >Redis學習之Redis資料結構詳解(RedisObject、SDS)

Redis學習之Redis資料結構詳解(RedisObject、SDS)

  redis是一個key-value儲存系統。和Memcached類似,它支援儲存的value型別相對更多,包括string(字串)、list(連結串列)、set(集合)、zset(sorted set --有序集合)和hash(雜湊型別)

  redis字串:在redis-Client中執行以下命令:

SET USER_NAME zhangsan

  會建立一個key為USER_NAME,value為zhangsan 的鍵值對。那麼,那麼這個字串的值 "zhangsan" 在資料庫中是以哪種資料結構儲存的呢?

RedisObject物件

  redis並沒有直接只用string list set等來直接實現鍵值對資料庫,而是根據這些資料結構建立了一個物件系統,這個系統包含了redis中五種資料結構。並且redis物件中記錄最後一次訪問時間,伺服器會根據這個時間刪除物件,從而釋放記憶體(如果啟用了maxmemory功能的情況下).

  redisObject資料結構分析:

typedef struct redisObject {
    unsigned type:4;
    unsigned encoding:4;
    unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
    int refcount;
    void *ptr;
} robj;

  type:佔用4個bit位,記錄了物件的型別REDIS_STRING 0,REDIS_LIST 1,REDIS_SET 2,REDIS_ZSET 3,REDIS_HASH 4

  ptr:該指標執行物件底層實現資料結構,這些資料結構物件由encoding屬性決定。

  encoding屬性:記錄了物件所使用的編碼,也就是說物件使用了什麼資料結構作為物件底層實現。

  可以這樣理解:type記錄的資料結構是針對redis使用者來說的。encoding記錄的資料結構是redis底層的具體實現。並且每種型別的物件redis至少兩種不同的編碼,也就是兩種不同的實現方式。使用 OBJECT ENCODING命令來檢視資料庫鍵的值物件編碼。

          

  可以看到“zhangsan”的編碼是embstr.

字串物件

  字串物件的編碼可以是int ,raw 或者embstr.如果一個字串物件儲存的是整數值,並且這個整數值可以用long型別標識,那麼字串物件將以整數值儲存在ptr屬性裡面,並將字串物件的編碼設定為INT。

          

  如果對"zhang_san"字串進行修改,型別會變成raw。

          

  為什麼對字串進行更改時,會變成raw型別呢,emptr型別與raw型別有什麼區別呢,接下類針對這兩個具體型別進行詳細描述。

  embstr編碼是為專門儲存短字串的一種編碼方式,這種編碼和raw編碼一樣,都使用redisObject結構和sdshdr結構來標識字串物件,但raw編碼會呼叫兩次記憶體分配函式來建立redisObject結構和sdshdr結構而embstr編碼則通過呼叫一次記憶體分配函式來分配一塊連續的空間,空間中一次包含redisObject和sdshdr兩個結構

              

SDS資料結構

  在c語言中,傳統的字串以空字元結尾的字元陣列來表示,redis並沒有直接使用這種傳統的字串標識,而是自己構建了一種名為(simple dynamid string ,SDS)的抽象型別,並將SDS用作Redis預設字串表示。在redis中c字串只會用在一些無需對字串值修改的地方。

typedef char *sds;
struct sdshdr {
    int len;
    int free;
    char buf[];
};

  其中len表示buf中已佔用空間的長度,free中表示buf中可剩餘空間的長度,buf是資料空間,如下圖所示。

            

  free屬性值為0,表示這個SDS沒有分配任何未使用空間,len為5表示這個SDS儲存了一個五個位元組的字串,buf為一個char型別的陣列,陣列的前五個位元組分別儲存了'H','E','L','L','O'五個字元,最後一個位元組則儲存了空字串\0.

SDS字串與C字串的區別

  • C語言使用長度為N+1的字元陣列來表示長度為N的字串,並且字元陣列的最後一個元素總是空字元'\0'.(java也是用的char[])
  • 獲取字串的長度,對c語言字串來說,c字串並不記錄自身的長度,獲取一個c字串的長度必須遍歷整個字串,這個操作的複雜度為O(n)的。而redis的STRLEN命令的複雜度僅為O(1)。
  • 減少記憶體分配的次數,c字串並不記錄自身長度,因此每次增長或者縮短一個字串時,都需要對記憶體進行一次記憶體重新分配的操作。對於redis中sds來說會進行空間預分配:(可以參考JAVA 中 ArrayList這種資料結構的擴容,他們是類似的。ArrayList沒記錯的話是每次擴1.5倍)。
sds sdsMakeRoomFor(sds s, size_t addlen) {

    struct sdshdr *sh, *newsh;

    // 獲取sds目前空閒的長度
    size_t free = sdsavail(s);

    size_t len, newlen;

    // 空間足夠,則直接返回,不重新分配
    if (free >= addlen) return s;

    // 獲取 s 目前已佔用空間的長度
    len = sdslen(s);
    sh = (void*) (s-(sizeof(struct sdshdr)));

    // s 最少需要的長度
    newlen = (len+addlen);

    // 根據新長度,為 s 分配新空間所需的大小
    if (newlen < SDS_MAX_PREALLOC)
        // 如果新長度小於 SDS_MAX_PREALLOC 
        // 那麼為它分配兩倍於所需長度的空間
        newlen *= 2;
    else
        // 否則,分配長度為目前長度加上 SDS_MAX_PREALLOC
        newlen += SDS_MAX_PREALLOC;
    // T = O(N)
    newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);

    // 記憶體不足,分配失敗,返回
    if (newsh == NULL) return NULL;

    // 更新 sds 的空餘長度
    newsh->free = newlen - len;

    // 返回 sds
    return newsh->buf;
}
  • 同時sds是惰性空間釋放,當sds字串縮短操作時不會立即free()空間,而是使用free屬性將這些位元組的數量記錄起來,並等待將來使用。
  • C字串中的字元必須符合某種編碼,比如(ASCII),並且出了字串末尾之外,字串裡不能包含空字元,否則會認為已經到結尾,因此c字串只能儲存文字資料,而不能儲存圖片,音訊,視訊等二進位制資料。
  • 同時c字串提供的api是不安全的,比如在修改內容時忘記分配空間。

  總結:

C字串 SDS
獲取字串長度複雜度為O(n) 獲取字串長度複雜度為O(1)
API是不安全的,可能造成緩衝區溢位 API是安全的,可能造成緩衝區溢位
修改N次字串必然進行N次記憶體分配 修改N次字串最多進行N次記憶體分配
只能儲存文字資料 可以儲存文字資料和二進位制資料

  對SET key HELLO 命令執行後,內部儲存結構示意圖。