咱們平時用 Redis都是處於用戶層面,咱們可能會不加思索地操做一個 key-value 對來方便地存取數據,感受方便之至。但你知道這些數據在背後是如何存儲以及編碼的嗎? 瞭解清楚了這個問題,將對咱們更加高效地使用 Redis具備指導意義。本文開始咱們將結合 Redis源碼來逐個探討Redis五大數據類型的內部編碼機制。redis
注: 本文首發於 My 公衆號 CodeSheep ,可 長按 或 掃描 下面的 當心心 來訂閱 ↓ ↓ ↓編程
對於 Redis的經常使用 5 種數據類型(String、Hash、List、Set、sorted set),每種數據類型都提供了 最少兩種 內部的編碼格式,並且每一個數據類型內部編碼方式的選擇 對用戶是徹底透明的,Redis會根據數據量自適應地選擇較優化的內部編碼格式。c#
若是想查看某個鍵的內部編碼格式,可使用 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語言結構體保存的,其代碼以下:bash
解釋以下:服務器
type
:表示鍵值的數據類型,包括 String、List、Set、ZSet、Hashencoding
:表示鍵值的內部編碼方式,從 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類型的內部編碼開始探討!數據結構
字符串是 Redis最基本的數據類型,Redis 中字符串對象的編碼能夠是 int
,raw
或者 embstr
中的某一種,分別介紹以下:框架
咱們不妨來作個實驗實際看一下:微服務
實際狀況就是 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
:字符數組瞭解了這些基本的數據結構之後,咱們就來看看上面例子中:
這三種情形下 Redis 內部究竟是怎麼存數據的!
命令示例: set foo 123
當字符串鍵值的內容能夠用一個 64位有符號整形 來表示時,Redis會將鍵值轉化爲 long型來進行存儲,此時即對應 OBJ_ENCODING_INT
編碼類型。
OBJ_ENCODING_INT
編碼類型內部的內存結構能夠形象地表示以下:
並且 Redis 啓動時會預先創建 10000 個分別存儲 0~9999 的 redisObject 變量做爲共享對象,這就意味着若是 set字符串的鍵值在 0~10000 之間的話,則能夠 直接指向共享對象 而不須要再創建新對象,此時鍵值不佔空間!
所以,當執行以下指令時:
set key1 100
set key2 100
複製代碼
其實 key1 和 key2 這兩個鍵值都直接引用了一個 Redis 預先已創建好的共享 redisObject 對象,就像下面這樣:
源碼以前,了無祕密,咱們再對照下面的源碼,來理解一下上述過程
命令示例: set foo abc
Redis 在保存長度小於 44 字節的字符串時會採用 OBJ_ENCODING_EMBSTR
編碼方式,口說無憑,咱們來瞅瞅源碼:
從上述代碼中很容易看出,對於長度小於 44的字符串,Redis 對鍵值採用OBJ_ENCODING_EMBSTR
方式,EMBSTR 顧名思義即:embedded string,表示嵌入式的String。從內存結構上來說 即字符串 sds結構體與其對應的 redisObject 對象分配在 同一塊連續的內存空間,這就彷彿字符串 sds 嵌入在 redisObject 對象之中同樣,這一切從下面的代碼便可清楚地看到:
所以,對於指令 set foo abc
所設置的鍵值,其內存結構示意圖以下:
指令示例: set foo abcdefghijklmnopqrstuvwxyzabcdeffasdffsdaadsx
正如指令示例,當字符串的鍵值爲長度大於 44 的 超長字符串 時,Redis 則會將鍵值的內部編碼方式改成 OBJ_ENCODING_RAW
格式,這與上面的 OBJ_ENCODING_EMBSTR
編碼方式的不一樣之處在於 此時動態字符串 sds 的內存與其依賴的 redisObject 的 內存再也不連續 了,以 set foo abcdefghijklmnopqrstuvwxyzabcdeffasdffsdaadsx
爲例,其鍵值的內存結構以下所示:
到此就講完了最基本的String數據類型的內部編碼狀況,怎麼樣,仍是挺好理解的吧!
後續咱們將繼續剖析 Redis 中 Hash 數據類型的內部編碼格式。
因爲能力有限,如有錯誤或者不當之處,還請你們批評指正,一塊兒學習交流!
做者更多的SpringBt實踐文章在此:
若是有興趣,也能夠抽點時間看看做者一些關於容器化、微服務化方面的文章:
可 長按 或 掃描 下面的 當心心 來訂閱 CodeSheep,獲取更多 務實、能看懂、可復現的 原創文 ↓↓↓