你肯定不來了解下 Redis 字符串的原理嗎

前言

來掘進都有兩年多了一直當個小透明,今天終於發一次文章了. 最近在看 Redis,感受收穫不少,寫篇博客記錄一下.redis

Redis 有五種基礎數據結構:string,list,set,zset,hash.其中 string是最最最簡單的也是最經常使用的.這個數據類型雖然簡單可是內部的結構設計卻非常精緻.算法

基本介紹

相比於 Java,在 Redis 中 string 是能夠修改的,是動態字符串(Simple Dynamic String 簡稱 SDS)他的內部結構更像是一個 ArrayList,維護一個字節數組並預分配冗餘空間以減小內存的頻繁分配.當字符串的長度小於 1MB時,每次擴容都是加倍現有的空間,若是字符串長度超過 1MB 時,每次擴容時只會擴展 1MB 的空間.數組

ps:字符串長度爲最大長度 512MB.bash

> set name test
OK
> get name
"test"
> mset name1 test1 name2 test2
OK
> mget name1 name2
1) "test1"
2) "test2"
> del name
(integer) 1
複製代碼

上面是字符串的基本操做 命令mset 和 mget 能夠對多個字符串讀寫 節省網絡開銷網絡

不只如此redis 的字符串還能夠用來儲存整數(更不像Java 的字符串了),而且能夠自增操做.字符串保存整數類型的的範圍在 -2^{64}2^{64}-1數據結構

若是保存的數大於這個取值範圍就會變成普通字符類型 沒法自增操做.這將由字符串編碼格式決定.優化

字符串由多個字節組成,每一個字節有 8bit.這樣的數據結構還能夠當作 bitmap 去使用.ui

> set foo 1
OK
> get foo 
"1"
> incr foo
(integer) 2
> get foo
"2"
複製代碼

內部原理

基本實現

上圖所示爲字符串的基本結構,其中 content 裏面保存的是字符串內容,和 c 同樣用 0x\0做爲結束字符.這個結束字符不會被計算len 中.代碼以下:編碼

struct SDS{
    T capacity;		//數組容量
    T len;			//實際長度
    byte flages;	//標誌位,低三位表示類型
    byte[] content;	//數組內容
}
複製代碼

能夠看到 capacity和len 都是泛型,爲何不直接使用 int 呢?由於 Redis 內部作了不少優化,爲了減小內存的使用不一樣長度的字符串會使用不一樣的數據類型去表示.而且在建立字符串的時候 len 會和 capacity 同樣大,沒有冗餘的空間,由於修改字符串的場景不多.(Redis 真的將內存優化到了極致)spa

編碼格式

Redis 字符串編碼格式有這麼幾種:int 編碼、embstr編碼和raw 編碼 下面就詳細介紹下這幾種編碼的區別.

在這以前先要說說RedisObject. Redis 的對象頭,全部的 Redis 對象都有下面這個頭部結構.

struct RedisObject{
    int4 type;		//數據類型 5 種
    int4 encoding;	//鍵值內部編碼格式 int 或 embstr 等等
    int24 lru;		// 當內存超限時採用LRU算法清除內存中的對象
    
    int32 refcount;	//改鍵值被引用的數量
    void *ptr;		//對象內容
}
複製代碼

int 編碼

當儲存的值是64 位有符號整數類型的時候將會採用 int 編碼,這時能夠使用鍵值自增操做.Redis 在啓動時會創建1w 個redisObject共享對象下文會講到,值在[0,1000)之間.若是存入整數的值在[0,1000)中Redis將不會建立新的對象,而是直接指向共享對象,鍵值不額外佔用空間.

使用 object encoding命令能夠查看編碼格式 使用 debug object命令能夠查看更多信息

> set foo 1
OK
> object encoding foo
"int"
> set foo2 1
OK
> debug object foo
Value at:0x7f44b020aca0 refcount:2147483647 encoding:int serializedlength:2 lru:14691591 lru_seconds_idle:72588
> debug object foo2
Value at:0x7f44b020aca0 refcount:2147483647 encoding:int serializedlength:2 lru:14691591 lru_seconds_idle:72594
複製代碼

能夠看到 foo 和 foo2 都在0x7f44b020aca0這裏指向的是同一個對象

embstr 編碼

當存儲的字符串長度較短時(len<=44 字節),Redis將會採用 embstr 編碼.embstr 即embedded string 嵌入式的字符串.將SDS結構體嵌入RedisObject對象中, 使用 malloc 方法一次分配內存地址是連續的.

如圖所示:

raw 編碼

當存儲的字符串長度較長時(len>44 字節),Redis 將會採用 raw 編碼,和 embstr 最大的區別就是 RedisObject 和 SDS 不在一塊兒了,內存地址再也不連續了.

如圖所示:

思考

爲何字符串會有兩種格式 embstr 和格式和 raw分界線是 44 個字節?

Redis 默認的內存分配器jemalloc分配內存大小的單位是2^n次方,爲了容納一個完整的 embstr 對象,最少會分配 32 字節的空間,再長些就是 64 字節,再以後就認爲這是一個大字符串不適合用 embstr 存儲,而改用 raw 編碼了.

那麼問題來了,64 字節的空間字符串長度是多少呢?答案就是 44 字節.

下圖中 content 的長度爲 45 字節減去結尾的 0x\0,就剩下 44 字節了.

相關文章
相關標籤/搜索