AVt天堂网 手机版,亚洲va久久久噜噜噜久久4399,天天综合亚洲色在线精品,亚洲一级Av无码毛片久久精品

當前位置:首頁 > 科技  > 軟件

Redis中萬金油的String,為什么不好用了?

來源: 責編: 時間:2023-10-24 09:00:07 267觀看
導讀今天,我們先了解下 String 類型的內存空間消耗問題,以及選擇節省內存開銷的數據類型的解決方案。我想和你分享一個之前我面臨的需求案例。曾經,我們面臨著一個任務,要創建一個高效的圖片存儲系統,要求這個系統能夠快速記錄

今天,我們先了解下 String 類型的內存空間消耗問題,以及選擇節省內存開銷的數據類型的解決方案。SuA28資訊網——每日最新資訊28at.com

我想和你分享一個之前我面臨的需求案例。SuA28資訊網——每日最新資訊28at.com

曾經,我們面臨著一個任務,要創建一個高效的圖片存儲系統,要求這個系統能夠快速記錄圖片 ID 和圖片在存儲系統中的唯一標識(我們稱之為圖片存儲對象 ID)。此外,還需要能夠通過圖片 ID 快速檢索到相應的圖片存儲對象 ID。SuA28資訊網——每日最新資訊28at.com

考慮到圖片數量龐大,我們決定使用 10 位數字來表示圖片 ID 和圖片存儲對象 ID。舉個例子,圖片 ID 可能是 1101000051,對應的存儲對象 ID 則是 3301000051。SuA28資訊網——每日最新資訊28at.com

photo_id: 1101000051SuA28資訊網——每日最新資訊28at.com

photo_obj_id: 3301000051SuA28資訊網——每日最新資訊28at.com

這個案例很明顯地展現了“鍵 - 單值”模式。在這種模式中,每個鍵值對中的值都是一個單一的值,而不是一個值的集合,與 String 類型的數據存儲方式完美契合。SuA28資訊網——每日最新資訊28at.com

另外,String 類型的數據可以保存二進制字節流,這使得它非常靈活,只需將數據轉換成二進制字節數組,就可以輕松地進行存儲。SuA28資訊網——每日最新資訊28at.com

因此,我們的初始解決方案是使用 String 類型來存儲數據。我們將圖片 ID 和圖片存儲對象 ID 分別用作鍵值對中的鍵和值,其中圖片存儲對象 ID 使用了 String 類型。SuA28資訊網——每日最新資訊28at.com

最初,我們成功地存儲了一億張圖片,大約使用了 6.4GB 的內存。但是,隨著圖片數據不斷增加,我們開始遇到了問題,Redis 實例的內存使用量不斷上升,導致生成 RDB 文件時出現延遲的情況。顯然,String 類型并不是一個適合大規模數據存儲的理想選擇,因此我們需要尋找更為節省內存開銷的數據類型解決方案。SuA28資訊網——每日最新資訊28at.com

在這個過程中,我深入研究了 String 類型的底層結構,找出了它內存開銷較大的原因。這讓我對這個“通用型”的 String 數據類型有了新的認識,它并不適用于所有情況,尤其在內存空間消耗方面存在明顯短板。SuA28資訊網——每日最新資訊28at.com

與此同時,我還仔細研究了集合類型的數據結構,發現它們具有非常高效的內存管理結構。但是,集合類型的數據結構通常用于保存一鍵多值的數據,不太適用于直接存儲單一鍵對應的單一值。因此,我們采用了二級編碼的方法,成功地使用集合類型來存儲單一鍵值對。這種改變顯著降低了 Redis 實例的內存開銷。SuA28資訊網——每日最新資訊28at.com

在本篇文章中,我將與你分享我在解決這一問題過程中所獲得的經驗和方法,包括 String 類型的內存開銷問題,可節省內存的數據結構選擇,以及如何使用集合類型來存儲單一鍵值對。如果你在使用 String 類型時也遇到了內存開銷較大的問題,那么今天的解決方案可能會對你有所幫助。SuA28資訊網——每日最新資訊28at.com

接下來,我們先來看看 String 類型的內存都消耗在哪里了。SuA28資訊網——每日最新資訊28at.com

為什么 String 類型內存開銷大?

在剛才的案例中,我們保存了 1 億張圖片的信息,用了約 6.4GB 的內存,一個圖片 ID 和圖片存儲對象 ID 的記錄平均用了 64 字節。SuA28資訊網——每日最新資訊28at.com

但問題是,一組圖片 ID 及其存儲對象 ID 的記錄,實際只需要 16 字節就可以了。SuA28資訊網——每日最新資訊28at.com

我們來分析一下。圖片 ID 和圖片存儲對象 ID 都是 10 位數,我們可以用兩個 8 字節的 Long 類型表示這兩個 ID。因為 8 字節的 Long 類型最大可以表示 2 的 64 次方的數值,所以肯定可以表示 10 位數。但是,為什么 String 類型卻用了 64 字節呢?SuA28資訊網——每日最新資訊28at.com

其實,除了記錄實際數據,String 類型還需要額外的內存空間記錄數據長度、空間使用等信息,這些信息也叫作元數據。當實際保存的數據較小時,元數據的空間開銷就顯得比較大了,有點“喧賓奪主”的意思。SuA28資訊網——每日最新資訊28at.com

那么,String 類型具體是怎么保存數據的呢?我來解釋一下。SuA28資訊網——每日最新資訊28at.com

當你保存 64 位有符號整數時,String 類型會把它保存為一個 8 字節的 Long 類型整數,這種保存方式通常也叫作 int 編碼方式。SuA28資訊網——每日最新資訊28at.com

但是,當你保存的數據中包含字符時,String 類型就會用簡單動態字符串(Simple Dynamic String,SDS)結構體來保存,如下圖所示:SuA28資訊網——每日最新資訊28at.com

圖片圖片SuA28資訊網——每日最新資訊28at.com

buf:字節數組,保存實際數據。為了表示字節數組的結束,Redis 會自動在數組最后加一個“/0”,這就會額外占用 1 個字節的開銷。SuA28資訊網——每日最新資訊28at.com

len:占 4 個字節,表示 buf 的已用長度。SuA28資訊網——每日最新資訊28at.com

alloc:也占個 4 字節,表示 buf 的實際分配長度,一般大于 len。SuA28資訊網——每日最新資訊28at.com

可以看到,在 SDS 中,buf 保存實際數據,而 len 和 alloc 本身其實是 SDS 結構體的額外開銷。SuA28資訊網——每日最新資訊28at.com

另外,對于 String 類型來說,除了 SDS 的額外開銷,還有一個來自于 RedisObject 結構體的開銷。SuA28資訊網——每日最新資訊28at.com

因為 Redis 的數據類型有很多,而且,不同數據類型都有些相同的元數據要記錄(比如最后一次訪問的時間、被引用的次數等),所以,Redis 會用一個 RedisObject 結構體來統一記錄這些元數據,同時指向實際數據。SuA28資訊網——每日最新資訊28at.com

一個 RedisObject 包含了 8 字節的元數據和一個 8 字節指針,這個指針再進一步指向具體數據類型的實際數據所在,例如指向 String 類型的 SDS 結構所在的內存地址,可以看一下下面的示意圖。關于 RedisObject 的具體結構細節,我會在后面的課程中詳細介紹,現在你只要了解它的基本結構和元數據開銷就行了。SuA28資訊網——每日最新資訊28at.com

圖片圖片SuA28資訊網——每日最新資訊28at.com

為了節省內存空間,Redis 還對 Long 類型整數和 SDS 的內存布局做了專門的設計。SuA28資訊網——每日最新資訊28at.com

一方面,當保存的是 Long 類型整數時,RedisObject 中的指針就直接賦值為整數數據了,這樣就不用額外的指針再指向整數了,節省了指針的空間開銷。SuA28資訊網——每日最新資訊28at.com

另一方面,當保存的是字符串數據,并且字符串小于等于 44 字節時,RedisObject 中的元數據、指針和 SDS 是一塊連續的內存區域,這樣就可以避免內存碎片。這種布局方式也被稱為 embstr 編碼方式。SuA28資訊網——每日最新資訊28at.com

當然,當字符串大于 44 字節時,SDS 的數據量就開始變多了,Redis 就不再把 SDS 和 RedisObject 布局在一起了,而是會給 SDS 分配獨立的空間,并用指針指向 SDS 結構。這種布局方式被稱為 raw 編碼模式。SuA28資訊網——每日最新資訊28at.com

為了幫助你理解 int、embstr 和 raw 這三種編碼模式,我畫了一張示意圖,如下所示:SuA28資訊網——每日最新資訊28at.com

圖片圖片SuA28資訊網——每日最新資訊28at.com

好了,知道了 RedisObject 所包含的額外元數據開銷,現在,我們就可以計算 String 類型的內存使用量了。SuA28資訊網——每日最新資訊28at.com

因為 10 位數的圖片 ID 和圖片存儲對象 ID 是 Long 類型整數,所以可以直接用 int 編碼的 RedisObject 保存。每個 int 編碼的 RedisObject 元數據部分占 8 字節,指針部分被直接賦值為 8 字節的整數了。此時,每個 ID 會使用 16 字節,加起來一共是 32 字節。但是,另外的 32 字節去哪兒了呢?SuA28資訊網——每日最新資訊28at.com

Redis 會使用一個全局哈希表保存所有鍵值對,哈希表的每一項是一個 dictEntry 的結構體,用來指向一個鍵值對。dictEntry 結構中有三個 8 字節的指針,分別指向 key、value 以及下一個 dictEntry,三個指針共 24 字節,如下圖所示:SuA28資訊網——每日最新資訊28at.com

圖片圖片SuA28資訊網——每日最新資訊28at.com

但是,這三個指針只有 24 字節,為什么會占用了 32 字節呢?這就要提到 Redis 使用的內存分配庫 jemalloc 了。SuA28資訊網——每日最新資訊28at.com

jemalloc 在分配內存時,會根據我們申請的字節數 N,找一個比 N 大,但是最接近 N 的 2 的冪次數作為分配的空間,這樣可以減少頻繁分配的次數。SuA28資訊網——每日最新資訊28at.com

舉個例子。如果你申請 6 字節空間,jemalloc 實際會分配 8 字節空間;如果你申請 24 字節空間,jemalloc 則會分配 32 字節。所以,在我們剛剛說的場景里,dictEntry 結構就占用了 32 字節。SuA28資訊網——每日最新資訊28at.com

好了,到這兒,你應該就能理解,為什么用 String 類型保存圖片 ID 和圖片存儲對象 ID 時需要用 64 個字節了。SuA28資訊網——每日最新資訊28at.com

你看,明明有效信息只有 16 字節,使用 String 類型保存時,卻需要 64 字節的內存空間,有 48 字節都沒有用于保存實際的數據。我們來換算下,如果要保存的圖片有 1 億張,那么 1 億條的圖片 ID 記錄就需要 6.4GB 內存空間,其中有 4.8GB 的內存空間都用來保存元數據了,額外的內存空間開銷很大。那么,有沒有更加節省內存的方法呢?SuA28資訊網——每日最新資訊28at.com

用什么數據結構可以節省內存?

Redis 有一種底層數據結構,叫壓縮列表(ziplist),這是一種非常節省內存的結構。SuA28資訊網——每日最新資訊28at.com

我們先回顧下壓縮列表的構成。表頭有三個字段 zlbytes、zltail 和 zllen,分別表示列表長度、列表尾的偏移量,以及列表中的 entry 個數。壓縮列表尾還有一個 zlend,表示列表結束。SuA28資訊網——每日最新資訊28at.com

圖片圖片SuA28資訊網——每日最新資訊28at.com

壓縮列表之所以能節省內存,就在于它是用一系列連續的 entry 保存數據。每個 entry 的元數據包括下面幾部分。SuA28資訊網——每日最新資訊28at.com

prev_len,表示前一個 entry 的長度。prev_len 有兩種取值情況:1 字節或 5 字節。取值 1 字節時,表示上一個 entry 的長度小于 254 字節。雖然 1 字節的值能表示的數值范圍是 0 到 255,但是壓縮列表中 zlend 的取值默認是 255,因此,就默認用 255 表示整個壓縮列表的結束,其他表示長度的地方就不能再用 255 這個值了。所以,當上一個 entry 長度小于 254 字節時,prev_len 取值為 1 字節,否則,就取值為 5 字節。SuA28資訊網——每日最新資訊28at.com

len:表示自身長度,4 字節;SuA28資訊網——每日最新資訊28at.com

encoding:表示編碼方式,1 字節;SuA28資訊網——每日最新資訊28at.com

content:保存實際數據。SuA28資訊網——每日最新資訊28at.com

這些 entry 會挨個兒放置在內存中,不需要再用額外的指針進行連接,這樣就可以節省指針所占用的空間。SuA28資訊網——每日最新資訊28at.com

我們以保存圖片存儲對象 ID 為例,來分析一下壓縮列表是如何節省內存空間的。SuA28資訊網——每日最新資訊28at.com

每個 entry 保存一個圖片存儲對象 ID(8 字節),此時,每個 entry 的 prev_len 只需要 1 個字節就行,因為每個 entry 的前一個 entry 長度都只有 8 字節,小于 254 字節。這樣一來,一個圖片的存儲對象 ID 所占用的內存大小是 14 字節(1+4+1+8=14),實際分配 16 字節。SuA28資訊網——每日最新資訊28at.com

Redis 基于壓縮列表實現了 List、Hash 和 Sorted Set 這樣的集合類型,這樣做的最大好處就是節省了 dictEntry 的開銷。當你用 String 類型時,一個鍵值對就有一個 dictEntry,要用 32 字節空間。但采用集合類型時,一個 key 就對應一個集合的數據,能保存的數據多了很多,但也只用了一個 dictEntry,這樣就節省了內存。SuA28資訊網——每日最新資訊28at.com

這個方案聽起來很好,但還存在一個問題:在用集合類型保存鍵值對時,一個鍵對應了一個集合的數據,但是在我們的場景中,一個圖片 ID 只對應一個圖片的存儲對象 ID,我們該怎么用集合類型呢?換句話說,在一個鍵對應一個值(也就是單值鍵值對)的情況下,我們該怎么用集合類型來保存這種單值鍵值對呢?SuA28資訊網——每日最新資訊28at.com

如何用集合類型保存單值的鍵值對?

在保存單值的鍵值對時,可以采用基于 Hash 類型的二級編碼方法。這里說的二級編碼,就是把一個單值的數據拆分成兩部分,前一部分作為 Hash 集合的 key,后一部分作為 Hash 集合的 value,這樣一來,我們就可以把單值數據保存到 Hash 集合中了。SuA28資訊網——每日最新資訊28at.com

以圖片 ID 1101000060 和圖片存儲對象 ID 3302000080 為例,我們可以把圖片 ID 的前 7 位(1101000)作為 Hash 類型的鍵,把圖片 ID 的最后 3 位(060)和圖片存儲對象 ID 分別作為 Hash 類型值中的 key 和 value。SuA28資訊網——每日最新資訊28at.com

按照這種設計方法,我在 Redis 中插入了一組圖片 ID 及其存儲對象 ID 的記錄,并且用 info 命令查看了內存開銷,我發現,增加一條記錄后,內存占用只增加了 16 字節,如下所示:SuA28資訊網——每日最新資訊28at.com

127.0.0.1:6379> info memory# Memoryused_memory:1039120127.0.0.1:6379> hset 1101000 060 3302000080(integer) 1127.0.0.1:6379> info memory# Memoryused_memory:1039136

在使用 String 類型時,每個記錄需要消耗 64 字節,這種方式卻只用了 16 字節,所使用的內存空間是原來的 1/4,滿足了我們節省內存空間的需求。SuA28資訊網——每日最新資訊28at.com

不過,你可能也會有疑惑:“二級編碼一定要把圖片 ID 的前 7 位作為 Hash 類型的鍵,把最后 3 位作為 Hash 類型值中的 key 嗎?”其實,二級編碼方法中采用的 ID 長度是有講究的SuA28資訊網——每日最新資訊28at.com

Redis Hash 類型的兩種底層實現結構,分別是壓縮列表和哈希表。SuA28資訊網——每日最新資訊28at.com

那么,Hash 類型底層結構什么時候使用壓縮列表,什么時候使用哈希表呢?其實,Hash 類型設置了用壓縮列表保存數據時的兩個閾值,一旦超過了閾值,Hash 類型就會用哈希表來保存數據了。SuA28資訊網——每日最新資訊28at.com

這兩個閾值分別對應以下兩個配置項:SuA28資訊網——每日最新資訊28at.com

hash-max-ziplist-entries:表示用壓縮列表保存時哈希集合中的最大元素個數。SuA28資訊網——每日最新資訊28at.com

hash-max-ziplist-value:表示用壓縮列表保存時哈希集合中單個元素的最大長度。SuA28資訊網——每日最新資訊28at.com

如果我們往 Hash 集合中寫入的元素個數超過了 hash-max-ziplist-entries,或者寫入的單個元素大小超過了 hash-max-ziplist-value,Redis 就會自動把 Hash 類型的實現結構由壓縮列表轉為哈希表。SuA28資訊網——每日最新資訊28at.com

一旦從壓縮列表轉為了哈希表,Hash 類型就會一直用哈希表進行保存,而不會再轉回壓縮列表了。在節省內存空間方面,哈希表就沒有壓縮列表那么高效了。SuA28資訊網——每日最新資訊28at.com

為了能充分使用壓縮列表的精簡內存布局,我們一般要控制保存在 Hash 集合中的元素個數。所以,在剛才的二級編碼中,我們只用圖片 ID 最后 3 位作為 Hash 集合的 key,也就保證了 Hash 集合的元素個數不超過 1000,同時,我們把 hash-max-ziplist-entries 設置為 1000,這樣一來,Hash 集合就可以一直使用壓縮列表來節省內存空間了。SuA28資訊網——每日最新資訊28at.com

小結

在這篇文章中,我們將顛覆以往對 String 數據類型的傳統認知。以前,String 被視為一種“萬金油”,在各種場合都被廣泛使用。然而,當存儲的鍵值對數據本身占用的內存空間較小時,String 類型的元數據開銷占據了主導地位。這些開銷包括 RedisObject 結構、SDS 結構以及dictEntry 結構的內存消耗。SuA28資訊網——每日最新資訊28at.com

為了應對這種情況,我們可以采用壓縮列表(ziplist)來存儲數據。當然,當使用 Hash 這種集合類型來保存單一鍵值對數據時,我們需要將單一值數據分割成兩部分,分別作為 Hash 集合的鍵和值。就像之前案例中使用了二級編碼來表示圖片 ID那樣,我們鼓勵你將這一方法應用到你的具體場景中。這不僅可以減少內存開銷,還能提高 Redis 的性能。希望這個解決方案對你的應用有所幫助。SuA28資訊網——每日最新資訊28at.com

本文鏈接:http://www.tebozhan.com/showinfo-26-14711-0.htmlRedis中萬金油的String,為什么不好用了?

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: 阿里二面:消息隊列的事務消息可以用 TCC 模式實現嗎?

下一篇: 如何從單體架構遷移到微服務架構:挑戰和最佳實踐

標簽:
  • 熱門焦點
Top