主頁 > 知識(shí)庫 > Redis中一個(gè)String類型引發(fā)的慘案

Redis中一個(gè)String類型引發(fā)的慘案

熱門標(biāo)簽:北京400電話辦理收費(fèi)標(biāo)準(zhǔn) 十堰營銷電銷機(jī)器人哪家便宜 日本中國地圖標(biāo)注 山東外呼銷售系統(tǒng)招商 超呼電話機(jī)器人 貴州電銷卡外呼系統(tǒng) 鄭州人工智能電銷機(jī)器人系統(tǒng) 魔獸2青云地圖標(biāo)注 宿遷便宜外呼系統(tǒng)平臺(tái)

​ 曾經(jīng)看到這么一個(gè)案例,有一個(gè)團(tuán)隊(duì)需要開發(fā)一個(gè)圖片存儲(chǔ)系統(tǒng),要求這個(gè)系統(tǒng)能快速記錄圖片ID和圖片存儲(chǔ)對(duì)象ID,同時(shí)還需要能夠根據(jù)圖片的ID快速找到圖片存儲(chǔ)對(duì)象ID。我們假設(shè)用10位數(shù)來表示圖片ID和圖片存儲(chǔ)對(duì)象ID,例如圖片的ID為1101021043,它所對(duì)應(yīng)的圖片存儲(chǔ)對(duì)象的ID為2301010051,可以看到圖片ID和圖片存儲(chǔ)ID正好是一一對(duì)應(yīng)的,是典型的key-value形式,所以首先會(huì)想到直接使用String類型來保存數(shù)據(jù)。把圖片ID和圖片存儲(chǔ)ID分別作為鍵值對(duì)的key和value來保存。但是隨著存儲(chǔ)的數(shù)據(jù)量越來越大,Redis的內(nèi)存的使用量也快速上升,結(jié)果遇到了大內(nèi)存Redis實(shí)例因?yàn)樯蒖DB而響應(yīng)變慢的問題。很顯然String類型并不是一種好的選擇,

那有什么辦法可以降低內(nèi)存消耗嗎?

String類型的數(shù)據(jù)結(jié)構(gòu)

首先我們得先了解為什么String保存數(shù)據(jù)時(shí)所消耗的內(nèi)存空間較大。在剛才的案例中,由于圖片ID和圖片存儲(chǔ)對(duì)象ID都是10位數(shù),我們可以用兩個(gè)8字節(jié)的Long類型來表示這兩個(gè)ID。所以一組圖片ID及其存儲(chǔ)對(duì)象ID的記錄,實(shí)際只需要16字節(jié)就可以了。但是通過對(duì)Redis內(nèi)存分析,一組圖片ID及其存儲(chǔ)對(duì)象ID卻占用了64字節(jié),那為什么String類型會(huì)用64字節(jié)呢。其實(shí),除了要記錄實(shí)際的數(shù)據(jù),String類型還需要額外的內(nèi)存空間來記錄數(shù)據(jù)的長度、空間使用信息等,這些信息也叫做元數(shù)據(jù)。當(dāng)實(shí)際保存的數(shù)據(jù)較小時(shí),元數(shù)據(jù)的空間開銷就顯的比較大了。我們先來看一下String類型是如何保存數(shù)據(jù)的。當(dāng)你保存64位有符號(hào)的整數(shù)時(shí),String類型會(huì)把它保存為一個(gè)8字節(jié)的Long類型整數(shù),這種保存方式通常也叫作int編碼方式。但是,當(dāng)你保存的數(shù)據(jù)中包含字符時(shí),String類型就會(huì)用簡單動(dòng)態(tài)字符串結(jié)構(gòu)體(SDS)來保存。如下圖所示:

  • len:4個(gè)字節(jié),表示buf的已用長度。
  • alloc:4個(gè)字節(jié),表示buf分配的長度,一般大于len。
  • buf:字節(jié)數(shù)組,保存實(shí)際數(shù)據(jù)。為了表示數(shù)組的結(jié)尾,Redis會(huì)自動(dòng)在數(shù)組最后添加一個(gè)”\0"。

可以看到,在SDS結(jié)構(gòu)體中,除了有保存實(shí)際數(shù)據(jù)的buf,還有l(wèi)en和alloc的額外元數(shù)據(jù)的開銷。另外對(duì)于String類型來說,除了SDS的額外開銷外,還有一個(gè)叫做RedisObject結(jié)構(gòu)體的開銷。因?yàn)镽edis的數(shù)據(jù)類型有很多,不同的數(shù)據(jù)類型都有相同的元數(shù)據(jù)要記錄(例如最后一次訪問時(shí)間),所以Redis會(huì)采用一個(gè)叫做RedisObject結(jié)構(gòu)體來統(tǒng)一記錄這些元數(shù)據(jù)。一個(gè)RedisObject包含了一個(gè)8字節(jié)的元數(shù)據(jù)和一個(gè)8字節(jié)的指針,這個(gè)指針指向具體數(shù)據(jù)所在,例如String類型的SDS結(jié)構(gòu)體所在的內(nèi)存地址。如下圖所示:

為了節(jié)省內(nèi)存空間,Redis對(duì)Long類型整數(shù)和SDS的內(nèi)存布局做了專門的設(shè)計(jì)。一方面,當(dāng)保存的是 Long 類型整數(shù)時(shí),RedisObject 中的指針就直接賦值為整數(shù)數(shù)據(jù)了,這樣就不用額外的指針再指向整數(shù)了,節(jié)省了指針的空間開銷。另一方面,當(dāng)保存的是字符串?dāng)?shù)據(jù),并且字符串小于等于 44 字節(jié)時(shí),RedisObject 中的元數(shù)據(jù)、指針和 SDS 是一塊連續(xù)的內(nèi)存區(qū)域,這樣就可以避免內(nèi)存碎片。這種布局方式也被稱為 embstr 編碼方式。當(dāng)字符串大于44字節(jié)時(shí),SDS的數(shù)據(jù)量就開始變多了,Redis 就不再把SDS 和

RedisObject 布局在一起了,而是會(huì)給 SDS 分配獨(dú)立的空間,并用指針指向 SDS 結(jié)構(gòu)。這種布局方式被稱為 raw 編碼模式。如下圖所示:

現(xiàn)在我們來計(jì)算一下一對(duì)圖片ID和圖片存儲(chǔ)對(duì)象ID的內(nèi)存的使用量。由于10位數(shù)的圖片ID和圖片存儲(chǔ)對(duì)象ID是Long類型整數(shù),所以可以直接用int編碼的RedisObject保存。相對(duì)應(yīng)的RedisObject元數(shù)據(jù)部分占8字節(jié),指針部分被直接賦值為8字節(jié)的整數(shù)了。此時(shí),每個(gè)ID會(huì)使用16字節(jié),加起來一共是32字節(jié)。但是,另外的 32 字節(jié)去哪兒了呢?

由于Redis是使用全局哈希表來保存所有的鍵值對(duì),哈希表的每一項(xiàng)是一個(gè)dictEntity的結(jié)構(gòu)體來指向一個(gè)鍵值對(duì)。dictEntity由三個(gè)8字節(jié)的指針組成,分別來指向key、value以及下一個(gè)dictEntity。如下圖所示。

由于Redis使用的內(nèi)存分配庫為jemalloc,jemalloc在分配內(nèi)存時(shí),會(huì)根據(jù)申請(qǐng)的字節(jié)數(shù)N,找一個(gè)比N大的,最接近N的2的冪次數(shù)作為分配的空間。

所以申請(qǐng)一個(gè)24字節(jié)的dictEntity,實(shí)際會(huì)分配32個(gè)字節(jié)。

到目前位置,你應(yīng)該明白了為什么String類型來保存圖片ID和圖片存儲(chǔ)對(duì)象ID會(huì)占用64個(gè)字節(jié)了。一個(gè)有效信息只有16個(gè)字節(jié),在使用String類型保存時(shí),卻要占用64個(gè)字節(jié)內(nèi)存空間,有48個(gè)字節(jié)用來保存元數(shù)據(jù)信息了,這是不是極大的浪費(fèi)了內(nèi)存空間。那么有沒有更加節(jié)省內(nèi)存的方法呢?

用壓縮列表節(jié)省內(nèi)存

Redis里有一種叫做壓縮列表的結(jié)構(gòu),非常節(jié)省內(nèi)存。我們先回顧一下壓縮列表的構(gòu)成。表頭有三個(gè)字段zlbytes、zllen和zltail,分別表示列表的長度、列表尾的偏移量以及列表中entry的個(gè)數(shù)。壓縮列表表尾有一個(gè)zlend,表示列表結(jié)束。如下圖所示。

由于壓縮列表采用一系列的entry保存數(shù)據(jù),這些entry會(huì)挨個(gè)兒放置在內(nèi)存中,不需要再用額外的指針進(jìn)行連接,這樣就可以節(jié)省指針?biāo)加玫目臻g。每個(gè)entry由以下幾部分組成。

  • pre_len:表示前一個(gè)entry的長度。prev_len有兩種取值情況:1 字節(jié)或 5 字節(jié)。當(dāng)上一個(gè) entry 長度小于 254 字節(jié)時(shí),prev_len 取值為 1 字節(jié),否則,就取值為 5 字節(jié)。
  • len:表示自身的長度,占4個(gè)字節(jié)。
  • encoding:表示編碼方式,占1個(gè)字節(jié)。
  • content:保存實(shí)際數(shù)據(jù)。

假設(shè)我們使用entry來保存圖片存儲(chǔ)對(duì)象ID(占8個(gè)字節(jié)),此時(shí),每個(gè)entry的prev_len占用1個(gè)字節(jié)就行,因?yàn)槊恳粋€(gè)entry的前一個(gè)entry的長度小于264字節(jié)。這樣一來,一個(gè)圖片對(duì)象ID所占用的內(nèi)存大小是14(1+4+1+8)個(gè)字節(jié),實(shí)際上會(huì)分配16個(gè)字節(jié)。

Redis里基于壓縮列表實(shí)現(xiàn)了List、Hash和Sorted Set集合類型,這樣做的最大好處就是節(jié)省了dictEntity的內(nèi)存開銷。對(duì)于String類型來說,一個(gè)鍵值對(duì)就有一個(gè)dictEntity,占用32個(gè)字節(jié)。對(duì)于集合類型來說,一個(gè)key對(duì)應(yīng)了很多數(shù)據(jù),卻只是占用了一個(gè)dictEntity,這樣就節(jié)省了內(nèi)存空間。

如何用集合類型存儲(chǔ)單值的鍵值對(duì)的數(shù)據(jù)

在保存單值鍵值對(duì)的數(shù)據(jù)時(shí),我們可以使用基于Hash類型的二級(jí)編碼方式。這里所說的二級(jí)編碼,是指把單值的數(shù)據(jù)拆成兩部分,前一部分作為Hash的key,后一部分作為Hash的value。 以圖片的ID為1101021043,它所對(duì)應(yīng)的圖片存儲(chǔ)對(duì)象的ID為2301010051為例,我們將圖片的ID的前7位(1101021)作為Hash類型的鍵,后3位(043)和圖片存儲(chǔ)對(duì)象ID為2301010051作為Hash類型的key和value。我們按照這種設(shè)計(jì),在Redis中插入一條記錄,只占用了16字節(jié),所以和使用String類型占用64字節(jié)對(duì)比,節(jié)省了很多空間。 最后,我們?cè)偎伎家粋€(gè)問題,為什么要把圖片ID的前7位作為Hash類型的鍵,后3位作為Hash類型的key呢。我們?cè)赗edis存儲(chǔ)結(jié)構(gòu)里介紹過Redis的Hash類型的兩種底層實(shí)現(xiàn)結(jié)構(gòu),分別是壓縮列表和哈希表。Hash 類型設(shè)置了用壓縮列表保存數(shù)據(jù)時(shí)的兩個(gè)閾值,一旦超過了閾值,Hash 類型就會(huì)用哈希表來保存數(shù)據(jù)了。這兩個(gè)閾值分別對(duì)應(yīng)以下兩個(gè)配置項(xiàng):

  • hash-max-ziplist-entries:表示用壓縮列表保存時(shí)哈希集合中的最大元素個(gè)數(shù)。
  • hash-max-ziplist-value:表示用壓縮列表保存時(shí)哈希集合中單個(gè)元素的最大長度。

在內(nèi)存節(jié)省空間方面,哈希表就沒有壓縮列表那么高效。我們只用后3位作為Hash類型的key,也就保證哈希集合中元素的個(gè)數(shù)不會(huì)超過1000,同時(shí)我們通過設(shè)置hash-max-ziplist-entries=1000,來確保Hash類型底層使用的是壓縮列表這種數(shù)據(jù)結(jié)構(gòu)。

到此這篇關(guān)于Redis中一個(gè)String類型引發(fā)的慘案的文章就介紹到這了,更多相關(guān)Redis String類型內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

您可能感興趣的文章:
  • Redis中一個(gè)String類型引發(fā)的慘案
  • Java三種獲取redis的連接及redis_String類型演示(適合新手)
  • Redis中的String類型及使用Redis解決訂單秒殺超賣問題
  • Redis02 使用Redis數(shù)據(jù)庫(String類型)全面解析
  • Redis String 類型和 Hash 類型學(xué)習(xí)筆記與總結(jié)
  • Redis教程(二):String數(shù)據(jù)類型

標(biāo)簽:朝陽 楊凌 北京 大慶 臺(tái)州 江蘇 吉安 果洛

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《Redis中一個(gè)String類型引發(fā)的慘案》,本文關(guān)鍵詞  Redis,中,一個(gè),String,類型,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無關(guān)。
  • 相關(guān)文章
  • 下面列出與本文章《Redis中一個(gè)String類型引發(fā)的慘案》相關(guān)的同類信息!
  • 本頁收集關(guān)于Redis中一個(gè)String類型引發(fā)的慘案的相關(guān)信息資訊供網(wǎng)民參考!
  • 推薦文章