淺析Redis中String數(shù)據(jù)類型及其底層編碼
從 RedisObject 說起
在 Redis 中,任意數(shù)據(jù)類型的鍵和值都會被封裝為一個 RedisObject ,也叫做Redis對象,源碼如下
/*server.h*/ typedef struct redisObject { unsigned type:4; unsigned encoding:4; unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or * LFU data (least significant 8 bits frequency * and most significant 16 bits access time). */ int refcount; void *ptr; } robj;
我們來看一下這個結(jié)構(gòu)體中的成員變量分別代表什么:
unsigned type:4
:對象類型,分別是string hash list set zset
,占 4 個 bit 位,如下所示
#define OBJ_STRING 0 /* String object. */ #define OBJ_LIST 1 /* List object. */ #define OBJ_SET 2 /* Set object. */ #define OBJ_ZSET 3 /* Sorted set object. */ #define OBJ_HASH 4 /* Hash object. */
unsigned encoding:4
: 底層編碼方式,共有 11 種,4 個 bit 位unsigned lru:LRU_BITS
:該對象最后一次被訪問的時間,占 24 個 bit ,在 Redis 內(nèi)存回收中起到關(guān)鍵作用int refcount
:對象引用計數(shù)器,計數(shù)器為 0 則說明對象無人引用,可以被回收void *ptr
:指針,指向存放實際數(shù)據(jù)的空間
我們注意到,在 Redis 中有 5 中數(shù)據(jù)結(jié)構(gòu)(用戶使用的),但在底層卻有 11 種編碼方式,Redis 會根據(jù)存儲的數(shù)據(jù)類型、存儲數(shù)據(jù)的大小,選擇不同的編碼方式,以獲得最優(yōu)的性能。一種數(shù)據(jù)結(jié)構(gòu)會對應(yīng)多種數(shù)據(jù)結(jié)構(gòu),如下表所示。
數(shù)據(jù)類型 | 編碼方式 |
---|---|
OBJ_STRING | int、embstr、raw |
OBJ_LIST | LinkedList和ZipList(3.2以前)、QuickList(3.2以后) |
OBJ_SET | intset、HT |
OBJ_ZSET | ZipList、HT、SkipList |
OBJ_HASH | ZipList、HT |
下面,我們現(xiàn)在介紹以下 String 數(shù)據(jù)類型,及其底層的編碼方式。
Redis 數(shù)據(jù)結(jié)構(gòu) -- String
String 類型的基本介紹和命令
String 類型,也就是字符串類型,是Redis中最簡單的存儲類型。它可以存儲字符串、整數(shù)或浮點數(shù)。下面是一些 String 類型常用的命令
SET key value
:設(shè)置指定 key 的值為指定的字符串或數(shù)字。GET key
:獲取指定 key 的值。
本地虛擬機redis:0>set key01 value01
"OK"
本地虛擬機redis:0>get key01
"value01"
INCR key
:將指定 key 的值加 1,如果該 key 不存在,則先將其設(shè)置為 0,再進行加 1 操作。DECR key
:將指定 key 的值減 1,如果該 key 不存在,則先將其設(shè)置為 0,再進行減 1 操作。INCRBY key increment
:將指定 key 的值增加指定的增量。DECRBY key decrement
:將指定 key 的值減少指定的減量。APPEND key value
:將指定的值追加到指定 key 的值的末尾。STRLEN key
:返回指定 key 的值的長度。GETRANGE key start end
:返回指定 key 的值的子字符串,根據(jù)起始位置和結(jié)束位置指定。SETRANGE key offset value
:將指定 key 的值從指定偏移位置開始,替換為指定的字符串。MSET key1 value1 [key2 value2 ...]
:同時設(shè)置多個 key 的值。(”[ ]” 中括號內(nèi)表示可選)MGET key1 [key2 ...]
:獲取多個 key 的值。
?? 這里僅給出 SET、GET 命令,其他的請自行測試。這些命令只是 Redis String 類型命令的一小部分,Redis 還提供了其他更多的命令來處理 String 類型的數(shù)據(jù)。你可以參考 Redis 官方文檔以獲取完整的命令列表和詳細的命令說明。
String 類型的底層實現(xiàn)
在 Redis 中,String 類型的數(shù)據(jù)結(jié)構(gòu)并不是采用 C 語言中自帶的字符串類型,C 語言中的數(shù)據(jù)結(jié)構(gòu)存在很多問題,比如:
- 獲取字符串長度的需要通過運算
- 非二進制安全
- 不可修改
因此,String 在 Redis 中有其他三種編碼方式: int、embstr、raw
。其中, raw 和 embstr 類型,都是基于動態(tài)字符串(SDS)實現(xiàn)的,下面我們先來看看動態(tài)字符串的結(jié)構(gòu)是怎樣的。
動態(tài)字符串(SDS)
動態(tài)字符串的結(jié)構(gòu)體如下
struct __attribute__ ((__packed__)) hisdshdr8 { 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[]; };
這里解釋一下結(jié)構(gòu)體中各個成員變量的作用:
len
:已經(jīng)保存的字符串字節(jié)數(shù),不包含結(jié)束標示alloc
:申請的總的字節(jié)數(shù),不包含結(jié)束標示flags
:不同的 SDS 的頭類型,用來控制 SDS 的頭大小buf[]
:真正存儲數(shù)據(jù)
我們先來聊一下 flags
這個成員變量。在 redis 中其實定義了 5 個 SDS結(jié)構(gòu)體(其中 hisdshdr5 已經(jīng)棄用)如圖所示。他們之間的主要區(qū)別在于 len 和 alloc 的長度不同。
在 redis 中,為了盡可能地節(jié)省內(nèi)存空間,當字符串長度在不同的區(qū)間時,會選擇不同的結(jié)構(gòu)體,例如:
- 當字符串長度在 0~255 個字節(jié)之間時,會選擇 hisdshdr8 ,這樣一來,用于表示字符串字節(jié)數(shù)和申請的總字節(jié)數(shù)的空間就會被大大節(jié)省,以此類推。
例如,一個包含字符串“name”的 sds 結(jié)構(gòu)如下:
SDS之所以叫做動態(tài)字符串,是因為它具備動態(tài)擴容的能力,例如一個內(nèi)容為 “hello” 的 SDS,假如我們要給這個 SDS 追加一段字符串 ”world” ,這里首先會申請新內(nèi)存空間:
- 如果新字符串小于1M,則新空間為擴展后字符串長度的兩倍+1
- 如果新字符串大于1M,則新空間為擴展后字符串長度+1M+1。
這種機制稱為內(nèi)存預(yù)分配。內(nèi)存預(yù)分配可以減少進行內(nèi)存重新分配的開銷,減少內(nèi)存碎片,使得 redis 的性能得到提高,空間利用率也得到提高。
String 的三種編碼方式
RAW
- raw 是 string 的基本編碼方式,基于簡單動態(tài)字符串(SDS)實現(xiàn),存儲上限為512mb。當一個字符串采用 raw 的編碼方式的時候,它的結(jié)構(gòu)如圖所示。
EMBSTR
- 如果存儲在 SDS 中的數(shù)據(jù)小于等于 44 字節(jié),則會采用 EMBSTR 編碼,此時 **RedisObject 與 SDS 是一段連續(xù)空間。而不是像 RAW 的編碼方式一樣,由 ptr 指向另外一片空間,**申請內(nèi)存時只需要調(diào)用一次內(nèi)存分配函數(shù),效率更高。結(jié)構(gòu)如下,
?? 為什么是 44 字節(jié)?Redis 默認的內(nèi)存分配器 jemalloc 分配內(nèi)存大小的單位是 $2^n$ ,因此,如果分配的空間大小為 2、4 、8 … 字節(jié)等 $2^n$ 字節(jié),就不會產(chǎn)生內(nèi)存碎片。
而 redisObject
和 hisdshdr8
中 len alloc flags
三個成員變量加起來剛剛好是 16 + 4 = 20 字節(jié),如果 char[]
(數(shù)據(jù)大?。┑拇笮?44 字節(jié)時,加起來剛剛好是 64 字節(jié),也即 262^626 不會產(chǎn)生內(nèi)存碎片。
- RAW 和 EMBSTR 的編碼演示
/* 44個v,采用embstr編碼*/ 本地虛擬機redis:0>set key vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv "OK" 本地虛擬機redis:0>object encoding key "embstr" /* 45個v,采用embstr編碼*/ 本地虛擬機redis:0>set key vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv "OK" 本地虛擬機redis:0>object encoding key "raw"
INT
- 如果存儲的字符串是整數(shù)值,并且大小在 LONG MAX 范圍內(nèi),則會采用 INT 編碼
- 直接將數(shù)據(jù)保存在 RedisObject 的 ptr 指針位置(剛好8字節(jié)),不再需要SDS了。
- INT 編碼演示
本地虛擬機redis:0>set key 12 "OK" 本地虛擬機redis:0>object encoding key "int"
寫在最后:在使用 string 類型時,盡可能讓其長度小于 44 字節(jié),或者使用整數(shù)表示,使其使用 EMBSTR 和 INT 編碼
以上就是淺析Redis中String數(shù)據(jù)類型及其底層編碼的詳細內(nèi)容,更多關(guān)于Redis Redis數(shù)據(jù)類型及編碼的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Redis數(shù)據(jù)導(dǎo)入導(dǎo)出以及數(shù)據(jù)遷移的4種方法詳解
這篇文章主要介紹了Redis數(shù)據(jù)導(dǎo)入導(dǎo)出以及數(shù)據(jù)遷移的4種方法詳解,需要的朋友可以參考下2020-02-02使用Spring?Boot實現(xiàn)Redis鍵過期回調(diào)功能示例詳解
這篇文章主要介紹了使用Spring?Boot實現(xiàn)Redis鍵過期回調(diào)功能,就是一個實現(xiàn)Redis鍵過期回調(diào)功能的Spring?Boot應(yīng)用的示例,代碼簡單易懂,對大家的學(xué)習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-07-07Redis數(shù)據(jù)庫的使用場景介紹(避免誤用Redis)
這篇文章主要介紹了Redis數(shù)據(jù)庫的使用場景介紹(避免誤用Redis),本文用簡要的語言總結(jié)了Redis數(shù)據(jù)庫的適應(yīng)場合,人而避免錯誤的使用它而產(chǎn)生昂貴的維護代價,需要的朋友可以參考下2015-03-03Redis報錯:Could not create server TCP 
這篇文章主要介紹了Redis報錯:Could not create server TCP listening socket 127.0.0.1:6379: bind:解決方法,是安裝與啟動Redis過程中比較常見的問題,需要的朋友可以參考下2023-06-06Redis?的內(nèi)存淘汰策略和過期刪除策略的區(qū)別
這篇文章主要介紹了Redis?的內(nèi)存淘汰策略和過期刪除策略的區(qū)別,Redis?是可以對?key?設(shè)置過期時間的,因此需要有相應(yīng)的機制將已過期的鍵值對刪除,而做這個工作的就是過期鍵值刪除策略2022-07-07在Centos?8.0中安裝Redis服務(wù)器的教程詳解
由于考慮到linux服務(wù)器的性能,所以經(jīng)常需要把一些中間件安裝在linux服務(wù)上,今天通過本文給大家介紹下在Centos?8.0中安裝Redis服務(wù)器的詳細過程,感興趣的朋友一起看看吧2022-03-03