Redis字符串類型內部編碼剖析

Profile

概述

我們平時用 Redis都是處於用戶層面,我們可能會不加思索地操作一個 key-value 對來方便地存取數據,感覺方便之至。但你知道這些數據在背後是如何存儲以及編碼的嗎? 瞭解清楚了這個問題,將對我們更加高效地使用 Redis具有指導意義。本文開始我們將結合 Redis源碼來逐個探討Redis五大數據類型的內部編碼機制。

  • 實驗環境:Redis 4.0.10

注: 本文原載於 My Personal Blog:CodeSheep · 程序羊



Redis數據類型內部編碼概況

對於 Redis的常用 5 種數據類型(String、Hash、List、Set、sorted set),每種數據類型都提供了 最少兩種 內部的編碼格式,而且每個數據類型內部編碼方式的選擇 對用戶是完全透明的,Redis會根據數據量自適應地選擇較優化的內部編碼格式。

如果想查看某個鍵的內部編碼格式,可以使用 OBJECT ENCODING keyname 指令來進行,比如:

127.0.0.1:6379> 
127.0.0.1:6379> set foo bar
OK
127.0.0.1:6379> 
127.0.0.1:6379> object encoding foo  // 查看某個Redis鍵值的編碼
"embstr"
127.0.0.1:6379> 
127.0.0.1:6379> 

Redis 的每個鍵值內部都是使用一個名字叫做 redisObject 這個 C語言結構體保存的,其代碼如下:

redisObject 結構體

解釋如下:

  • type:表示鍵值的數據類型,包括 String、List、Set、ZSet、Hash
  • encoding:表示鍵值的內部編碼方式,從 Redis源碼看目前取值有如下幾種:
#define OBJ_ENCODING_RAW 0        /* Raw representation */
#define OBJ_ENCODING_INT 1        /* Encoded as integer */
#define OBJ_ENCODING_HT 2         /* Encoded as hash table */
#define OBJ_ENCODING_ZIPMAP 3     /* Encoded as zipmap */
#define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */
#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 */
#define OBJ_ENCODING_QUICKLIST 9  /* Encoded as linked list of ziplists */
  • refcount:表示該鍵值被引用的數量,即一個鍵值可被多個鍵引用

本文我們就從 Redis最基本的 String類型的內部編碼開始探討!



String類型的內部編碼情況

字符串是 Redis最基本的數據類型,Redis 中字符串對象的編碼可以是 intraw 或者 embstr 中的某一種,分別介紹如下:

  • int 編碼:保存long 型的64位有符號整數
  • embstr 編碼:保存長度小於44字節的字符串
  • raw 編碼:保存長度大於44字節的字符串

我們不妨來做個實驗實際看一下:

String的各種內部編碼格式

實際情況就是 Redis 內部會根據用戶給的不同鍵值而使用不同的編碼格式,而這一切對用戶完全透明!

Redis 是使用 SDS(“簡單動態字符串”)這個結構體來存儲字符串,代碼裏定義了 5種 SDS結構體:

struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* used */
    uint8_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

可以看出,除了結構體字段數據類型的不同,其字段含義相差無幾,其中:

  • len:字符串的長度(實際使用的長度)
  • alloc:分配內存的大小
  • flags:標誌位,低三位表示類型,其餘五位未使用
  • buf:字符數組

瞭解了這些基本的數據結構以後,我們就來看看上面例子中:

  • set foo 123
  • set foo abc
  • set foo abcdefghijklmnopqrstuvwxyzabcdeffasdffsdaadsx

這三種情形下 Redis 內部到底是怎麼存數據的!



INT 編碼格式

命令示例: set foo 123

當字符串鍵值的內容可以用一個 64位有符號整形 來表示時,Redis會將鍵值轉化爲 long型來進行存儲,此時即對應 OBJ_ENCODING_INT 編碼類型。

OBJ_ENCODING_INT 編碼類型內部的內存結構可以形象地表示如下:

set foo 123 時鍵值的內存結構

而且 Redis 啓動時會預先建立 10000 個分別存儲 0~9999 的 redisObject 變量作爲共享對象,這就意味着如果 set字符串的鍵值在 0~10000 之間的話,則可以 直接指向共享對象 而不需要再建立新對象,此時鍵值不佔空間!

因此,當執行如下指令時:

set key1 100
set key2 100

其實 key1key2 這兩個鍵值都直接引用了一個 Redis 預先已建立好的共享 redisObject 對象,就像下面這樣:

共享對象

源碼之前,了無祕密,我們再對照下面的源碼,來理解一下上述過程

INT編碼的源碼


EMBSTR編碼格式

命令示例: set foo abc

Redis 在保存長度小於 44 字節的字符串時會採用 OBJ_ENCODING_EMBSTR 編碼方式,口說無憑,我們來瞅瞅源碼:

EMBSTR編碼的判斷條件

從上述代碼中很容易看出,對於長度小於 44的字符串,Redis 對鍵值採用OBJ_ENCODING_EMBSTR 方式,EMBSTR 顧名思義即:embedded string,表示嵌入式的String。從內存結構上來講 即字符串 sds結構體與其對應的 redisObject 對象分配在 同一塊連續的內存空間,這就彷彿字符串 sds 嵌入在 redisObject 對象之中一樣,這一切從下面的代碼即可清楚地看到:

embedded string

因此,對於指令 set foo abc 所設置的鍵值,其內存結構示意圖如下:

set foo abc時的鍵值內存結構


RAW 編碼格式

指令示例: set foo abcdefghijklmnopqrstuvwxyzabcdeffasdffsdaadsx

正如指令示例,當字符串的鍵值爲長度大於 44超長字符串 時,Redis 則會將鍵值的內部編碼方式改爲 OBJ_ENCODING_RAW 格式,這與上面的 OBJ_ENCODING_EMBSTR 編碼方式的不同之處在於 此時動態字符串 sds 的內存與其依賴的 redisObject 的 內存不再連續 了,以 set foo abcdefghijklmnopqrstuvwxyzabcdeffasdffsdaadsx 爲例,其鍵值的內存結構如下所示:

set foo abcdefghijklmnopqrstuvwxyzabcdeffasdffsdaadsx時鍵值的內存結構

到此就講完了最基本的String數據類型的內部編碼情況,怎麼樣,還是挺好理解的吧!

後續我們將繼續剖析 Redis 中 Hash 數據類型的內部編碼格式。



後 記

由於能力有限,若有錯誤或者不當之處,還請大家批評指正,一起學習交流!



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章