日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪(fǎng)問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > 数据库 >内容正文

数据库

Redis进阶-string底层数据结构精讲

發(fā)布時(shí)間:2025/3/21 数据库 48 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Redis进阶-string底层数据结构精讲 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

文章目錄

  • Pre
  • string 字符串
  • 字符串的實(shí)現(xiàn)
  • 字符串 內(nèi)部結(jié)構(gòu)
    • embstr vs raw


Pre

Redis進(jìn)階-核心數(shù)據(jù)結(jié)構(gòu)進(jìn)階實(shí)戰(zhàn)

Redis 有 5 種基礎(chǔ)數(shù)據(jù)結(jié)構(gòu),分別為:string (字符串)、list (列表)、set (集合)、hash (哈希) 和 zset (有序集合) 。

Redis 所有的數(shù)據(jù)結(jié)構(gòu)都是以唯一的key 字符串作為名稱(chēng),然后通過(guò)這個(gè)唯一 key 值來(lái)獲取相應(yīng)的 value 數(shù)據(jù)。不同類(lèi)型的數(shù)據(jù)結(jié)構(gòu)的差異就在于 value 的結(jié)構(gòu)不一樣。


string 字符串

字符串 string 是 Redis 最簡(jiǎn)單的數(shù)據(jù)結(jié)構(gòu) .

舉個(gè)簡(jiǎn)單的例子:緩存用戶(hù)信息。我們將用戶(hù)信息結(jié)構(gòu)體使用 JSON 序列化成字符串,然后將序列化后的字符串塞進(jìn) Redis 來(lái)緩存。

同樣,取用戶(hù)信息會(huì)經(jīng)過(guò)一次反序列化的過(guò)程。

當(dāng)然了,不限于使用string存儲(chǔ),看使用場(chǎng)景。


字符串的實(shí)現(xiàn)

Redis 的字符串是動(dòng)態(tài)字符串,是可以修改的字符串,內(nèi)部結(jié)構(gòu)實(shí)現(xiàn)上類(lèi)似于 Java 的ArrayList,采用預(yù)分配冗余空間的方式來(lái)減少內(nèi)存的頻繁分配

如上圖

  • 內(nèi)部為當(dāng)前字符串實(shí)際分配的空間 capacity 一般要高于實(shí)際字符串長(zhǎng)度 len.

  • 當(dāng)字符串長(zhǎng)度小于 1M 時(shí),擴(kuò)容都是加倍現(xiàn)有的空間

  • 超過(guò) 1M,擴(kuò)容時(shí)一次只會(huì)多擴(kuò) 1M 的空間

  • 字符串最大長(zhǎng)度為 512M

  • 字符串是由多個(gè)字節(jié)組成,每個(gè)字節(jié)又是由 8 個(gè) bit 組成,如此便可以將一個(gè)字符串看成很多 bit 的組合,這便是 bitmap「位圖」數(shù)據(jù)結(jié)構(gòu)


字符串 內(nèi)部結(jié)構(gòu)

Redis 中的字符串是可以修改的字符串,在內(nèi)存中它是以字節(jié)數(shù)組的形式存在的。

C 語(yǔ)言里面的字符串標(biāo)準(zhǔn)形式是以 NULL 作為結(jié)束符,但是在 Redis 里面字符串不
是這么表示的。因?yàn)橐@取 NULL 結(jié)尾的字符串的長(zhǎng)度使用的是 strlen 標(biāo)準(zhǔn)庫(kù)函數(shù),這個(gè)函數(shù)的算法復(fù)雜度是 O(n),它需要對(duì)字節(jié)數(shù)組進(jìn)行遍歷掃描,作為單線(xiàn)程的 Redis 表示承受不起。

Redis 的字符串叫著「SDS」,也就是 Simple Dynamic String。它的結(jié)構(gòu)是一個(gè)帶長(zhǎng)度信息的字節(jié)數(shù)組。

struct SDS<T> {T capacity; // 數(shù)組容量 使用泛型表示的T len; // 數(shù)組長(zhǎng)度 使用泛型表示的 byte flags; // 特殊標(biāo)識(shí)位,不理睬它byte[] content; // 數(shù)組內(nèi)容 字節(jié)數(shù)組 }

  • content 里面存儲(chǔ)了真正的字符串內(nèi)容
  • capacity 表示所分配數(shù)組的長(zhǎng)度
  • len 表示字符串的實(shí)際長(zhǎng)度

前面我們提到字符串是可以修改的字符串,它要支持 append 操作。如果數(shù)組沒(méi)有冗余空間,那么追加操作必然涉及到分配新數(shù)組,然后將舊內(nèi)容復(fù)制過(guò)來(lái),再 append 新內(nèi)容。如果字符串的長(zhǎng)度非常長(zhǎng),這樣的內(nèi)存分配和復(fù)制開(kāi)銷(xiāo)就會(huì)非常大。

/* Append the specified binary-safe string pointed by 't' of 'len' bytes to the * end of the specified sds string 's'. * * After the call, the passed sds string is no longer valid and all the * references must be substituted with the new pointer returned by the call. */ sds sdscatlen(sds s, const void *t, size_t len) {size_t curlen = sdslen(s); // 原字符串長(zhǎng)度// 按需調(diào)整空間,如果 capacity 不夠容納追加的內(nèi)容,就會(huì)重新分配字節(jié)數(shù)組并復(fù)制原字符串的內(nèi)容到新數(shù)組中s = sdsMakeRoomFor(s,len);if (s == NULL) return NULL; // 內(nèi)存不足memcpy(s+curlen, t, len); // 追加目標(biāo)字符串的內(nèi)容到字節(jié)數(shù)組中sdssetlen(s, curlen+len); // 設(shè)置追加后的長(zhǎng)度值s[curlen+len] = '\0'; // 讓字符串以\0 結(jié)尾,便于調(diào)試打印,還可以直接使用 glibc 的字符串函數(shù)進(jìn)行操作return s; }

上面的 SDS 結(jié)構(gòu)使用了范型 T,為什么不直接用 int 呢 ?

這是因?yàn)楫?dāng)字符串比較短時(shí),len 和 capacity 可以使用 byte 和 short 來(lái)表示,Redis 為了對(duì)內(nèi)存做極致的優(yōu)化,不同長(zhǎng)度的字符串使用不同的結(jié)構(gòu)體來(lái)表示。

Redis 規(guī)定字符串的長(zhǎng)度不得超過(guò) 512M 字節(jié)。創(chuàng)建字符串時(shí) len 和 capacity 一樣長(zhǎng),不會(huì)多分配冗余空間,這是因?yàn)榻^大多數(shù)場(chǎng)景下我們不會(huì)使用 append 操作來(lái)修改字符串。


embstr vs raw

Redis 的字符串有兩種存儲(chǔ)方式,在長(zhǎng)度特別短時(shí),使用 emb 形式存儲(chǔ)(embeded),當(dāng)長(zhǎng)度超過(guò) 44 時(shí),使用 raw 形式存儲(chǔ) 。

上面 debug object 輸出中有個(gè) encoding 字段,一個(gè)字符的差別,存儲(chǔ)形式就發(fā)生
了變化.

為啥呢? 我們首先來(lái)了解一下 Redis 對(duì)象頭結(jié)構(gòu)體,所有的 Redis 對(duì)象都有
下面的這個(gè)結(jié)構(gòu)頭:

struct RedisObject {int4 type; // 4bitsint4 encoding; // 4bitsint24 lru; // 24bitsint32 refcount; // 4bytesvoid *ptr; // 8bytes,64-bit system } robj;
  • 不同的對(duì)象具有不同的類(lèi)型 type(4bit),

  • 同一個(gè)類(lèi)型的 type 會(huì)有不同的存儲(chǔ)形式encoding(4bit),

  • 為了記錄對(duì)象的 LRU 信息,使用了 24 個(gè) bit 來(lái)記錄 LRU 信息。

  • 每個(gè)對(duì)象都有個(gè)引用計(jì)數(shù),當(dāng)引用計(jì)數(shù)為零時(shí),對(duì)象就會(huì)被銷(xiāo)毀,內(nèi)存被回收。

  • ptr 指針將指向?qū)ο髢?nèi)容 (body) 的具體存儲(chǔ)位置。

這樣一個(gè) RedisObject 對(duì)象頭需要占據(jù) 16 字節(jié)( 4bit + 4bit + 24bit + 4bytes + 8bytes )的存儲(chǔ)空間。

接著我們?cè)倏?SDS 結(jié)構(gòu)體的大小,在字符串比較小時(shí),SDS 對(duì)象頭的大小是capacity+3,至少是 3。意味著分配一個(gè)字符串的最小空間占用為 19 字節(jié) (16+3)。

struct SDS {int8 capacity; // 1byteint8 len; // 1byteint8 flags; // 1bytebyte[] content; // 內(nèi)聯(lián)數(shù)組,長(zhǎng)度為 capacity }

如圖所示,embstr 存儲(chǔ)形式是這樣一種存儲(chǔ)形式,它將 RedisObject 對(duì)象頭和 SDS 對(duì)象連續(xù)存在一起,使用 malloc 方法一次分配。

而 raw 存儲(chǔ)形式不一樣,它需要兩次malloc,兩個(gè)對(duì)象頭在內(nèi)存地址上一般是不連續(xù)的。

而內(nèi)存分配器 jemalloc/tcmalloc 等分配內(nèi)存大小的單位都是 2、4、8、16、32、64 等等,為了能容納一個(gè)完整的 embstr 對(duì)象,jemalloc 最少會(huì)分配 32 字節(jié)的空間,如果字符串再稍微長(zhǎng)一點(diǎn),那就是 64 字節(jié)的空間。

如果總體超出了 64 字節(jié),Redis 認(rèn)為它是一個(gè)大字符串,不再使用 emdstr 形式存儲(chǔ),而該用 raw 形式。

當(dāng)內(nèi)存分配器分配了 64 空間時(shí),那這個(gè)字符串的長(zhǎng)度最大可以是多少呢?這個(gè)長(zhǎng)度就是 44。那為什么是 44 呢?

SDS 結(jié)構(gòu)體中的 content 中的字符串是以字節(jié)\0 結(jié)尾的字符串,之所以多出這樣一個(gè)字節(jié),是為了便于直接使用 glibc 的字符串處理函數(shù),以及為了便于字符串的調(diào)試打印輸出。

看上面這張圖可以算出,留給 content 的長(zhǎng)度最多只有 45(64-19) 字節(jié)了。字符串又是以\0 結(jié)尾,所以 embstr 最大能容納的字符串長(zhǎng)度就是 44。

總結(jié)

以上是生活随笔為你收集整理的Redis进阶-string底层数据结构精讲的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。