此篇文章是主要介紹Redis在數據存儲方面的其中一種方式,壓縮列表。本文會介紹1. 壓縮列表(ziplist)的使用場景 2.如何達到節約記憶體的效果?3.壓縮列表的存儲格式 4. 連鎖更新的問題 5. conf文件配置。在實踐上的操作主要是對conf配置文件進行配置,具體上沒有確切的一個值,更多是經 ...
此篇文章是主要介紹Redis在數據存儲方面的其中一種方式,壓縮列表。本文會介紹1. 壓縮列表(ziplist)的使用場景 2.如何達到節約記憶體的效果?3.壓縮列表的存儲格式 4. 連鎖更新的問題 5. conf文件配置。在實踐上的操作主要是對conf配置文件進行配置,具體上沒有確切的一個值,更多是經驗值。也有的項目會直接使用原本的預設值。此篇對於更好地理解一個資料庫底層的存儲邏輯會有一點幫助。修學儲能,既要博,也要淵。希望這篇文章對同樣也是在學習Redis的各位同伴有點用。
一、壓縮列表(ziplist)的使用場景:
Redis為了優化數據存儲,節約記憶體,在列表、字典(哈希鍵)和有序集合的底層實現了使用壓縮列表這一優化方案。
例如,假如一個哈希鍵裡面存儲的字元串比較短,那麼Redis就會將它用壓縮列表的格式去存儲,即轉換為位元組數組存儲。而一個哈希鍵內部存儲的整數值比較小,同樣也會把它存儲為壓縮列表的一個節點。同理,列表鍵的對小數據的存儲跟哈希鍵的操作類似。
如此說來,壓縮列表並不是開發者可以直接調用的Redis中的一種存儲數據結構,而是Redis中為優化數據存儲而在底層做的一項努力。理解好這點還是比較重要的。
二、如何達到節約記憶體的效果?
壓縮列表是一種序列化的數據結構,這種數據結構的功能是將一系列數據與其編碼信息存儲在一塊連續的記憶體區域,這塊記憶體物理上是連續的。但邏輯上被分為多個組成部分,即節點。目的是為了在一定可控的時間複雜度條件下儘可能的減少不必要的記憶體開銷,從而達到節省記憶體的效果。需要理解是怎麼達到節約記憶體作用的,還需要去瞭解壓縮列表的存儲格式。
三、壓縮列表的存儲格式:
壓縮列表(ziplist)是Redis列表鍵、哈希鍵和有序集合鍵的底層實現之一,其實質是一種序列化的數據存儲結構。有別於普通情況下,Redis用雙端鏈表表示列表,使用散列表表示哈希鍵,用散列表+跳躍表表示有序集合。當一個列表或者哈希字典/有序集合只包含很少內容,並且每一個列表項或者哈希項/有序集合項如果是小整數,或者比較短的字元串。那麼Redis就會用壓縮列表來做底層的實現。
壓縮列表由一系列經Redis特殊編碼的連續記憶體塊組成,每一個記憶體塊稱為一個節點(entry),而一個壓縮列表可以包含很多個節點。每個節點存儲的數據格式可以是位元組數組(中文字元串等都會轉換為位元組數組)或者整數值。
位元組數組的長度可以是以下的其中一種:
1. 長度小於等於63位元組(2的6次方)
2. 長度小於等於16383位元組(2的14次方)
3. 長度小於等於4294967295位元組(2的32次方)
整數值可能是以下六種中的其中一種:
1. 4位長,介於0-12之間的無符號整數
2. 1位元組長的有符號整數
3. 3位元組長的有符號整數
4. int16_t類型整數
5. int32_類型整數
6. int64_t類型整數
普通存儲格式下和壓縮列表存儲格式下的不同點:
列表存儲結構典型的為雙端鏈表,每一個值都是用一個節點來表示,每個節點都會有指向前一個節點和後一個節點的指針,以及指向節點包含的字元串值的指針。而字元串值又分為3個部分存儲,第一部分存儲字元串長度,第二部分存儲字元串值中剩餘可用的位元組量,第三部分存儲的則是字元串數據本身。所以一個節點往往都需要存儲3個指針、2個記錄字元串信息的整數、字元串本省和一個額外的位元組。總體上額外的開銷是很大的(21位元組)。
壓縮列表節點的格式:
每一個節點都有previous_entry_length,encoding,content三個部分組成,在遍歷壓縮列表的時候是從後往前遍歷的。
1. previous_entry_length記錄了前一個節點的長度,只要用當前指針減去這個值就可以達到前一個節點的起始地址。
2. encoding記錄了節點content屬性所保存數據的類型和長度
3. content記錄了一個節點的值
顯然壓縮列表這種方式節約了不少存儲空間。但同時也會引發下麵的問題。
四、連鎖更新的問題:
一般而言如果前一個節點的整體長度小於254位元組,previous_entry_length屬性只需要1個位元組的空間來保存這個長度值。而當前一個節點大於254位元組的時候,previous_entry_length屬性要用5個位元組長的空間來記錄長度值。
當長度為254位元組左右的節點前插入一個新的節點的時候,需要增加previous_entry_length來記錄這個節點到新節點的偏移量。這個時候,這個節點的長度肯定就大於254位元組了。所以這個節點的後一個節點就不能只用一個位元組的previous_entry_length來記錄這個節點的信息了,而是需要5個位元組來記錄。如果連續多個節點的長度都為254位元組左右,在其中的某一個節點前/後發生節點的插入和刪除(刪除的推理與插入相反,原本用5位元組記錄前一節點的可能變為1位元組),都可能引發連鎖的更新,顯然,這樣對系統地運行效率是很不利的。不過,在實際應用中這種情況還是比較少發生的。
而雙端鏈表在節點的更新、增加和刪除上顯得就會“輕鬆”很多了。 因為每一個節點存儲的信息都是相對獨立的。
實踐意義:
要預估一個節點大概占據多少位元組的存儲空間,適當地調整欄位的存儲格式而不要使存儲的欄位值占據存儲空間落在254位元組(除去encoding屬性和previous_entry_length屬性)左右。
Redis中查看字元串和哈希鍵值的長度相關命令:
1. 查詢字元串鍵對應的值長度
命令:
Strlen
例如:
127.0.0.1:6379> strlen m_name
(integer) 8
2. 查詢哈希鍵某一個域長度
命令:
Hstrlen
例如:
127.0.0.1:6379> hstrlen good_list good_list1
(integer) 226
五、Conf文件配置:
通過修改配置文件,可以控制是否使用壓縮列表存儲相關鍵的最大元素個數和最大元素的大小
Conf文件中的配置:
1.
[] -max-ziplist-entries : 表示對於鍵的最大元素個數,即一個鍵中在該指定值下的數量的節點個數都會用壓縮列表來儲存
[] -max-ziplist-value :表示壓縮列表中每個節點的最大體積是多少位元組
實際使用中,一個列表鍵/哈希鍵的某一個元素往往存儲著比較大的信息量,會大於64位元組,所以配置時很有可能會比64大,同時考慮到實際存儲數據的容量大小以及上面談到的previous_entry_length的大小問題,對[] -max-ziplist-value進行合理的配置。
配置文件內容:
############## ADVANCED CONFIG ########################## 哈希鍵 # Hashes are encoded using a memory efficient data structure when they have a # small number of entries, and the biggest entry does not exceed a given # threshold. These thresholds can be configured using the following directives. hash-max-ziplist-entries 512 hash-max-ziplist-value 64 有序集合鍵 # Similarly to hashes and lists, sorted sets are also specially encoded in # order to save a lot of space. This encoding is only used when the length and # elements of a sorted set are below the following limits: zset-max-ziplist-entries 128 zset-max-ziplist-value 64 列表鍵,比較特殊,直接使用制定大小kb位元組數表示(有些conf文件的列表鍵與hash鍵的表達式沒太大區別) # Lists are also encoded in a special way to save a lot of space. # The number of entries allowed per internal list node can be specified # as a fixed maximum size or a maximum number of elements. # For a fixed maximum size, use -5 through -1, meaning: # -5: max size: 64 Kb <-- not recommended for normal workloads # -4: max size: 32 Kb <-- not recommended # -3: max size: 16 Kb <-- probably not recommended # -2: max size: 8 Kb <-- good # -1: max size: 4 Kb <-- good # Positive numbers mean store up to _exactly_ that number of elements # per list node. # The highest performing option is usually -2 (8 Kb size) or -1 (4 Kb size), # but if your use case is unique, adjust the settings as necessary. list-max-ziplist-size -2
案例:
修改配置前使用預設配置:
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
127.0.0.1:6379> hstrlen good_list good_list1
(integer) 226
127.0.0.1:6379> object encoding good_list
"hashtable"
修改配置:
hash-max-ziplist-entries 512
hash-max-ziplist-value 254
註意:修改配置後需要重啟伺服器
127.0.0.1:6379> hstrlen good_list good_list1
(integer) 226
127.0.0.1:6379> object encoding good_list
"ziplist"
可以看到存儲方式已將變為ziplist
較官方的壓力測試和指導建議:
當一個壓縮列表的元素數量上升到幾千(實際使用可能遠小於這個值)的時候,壓縮列表的性能可能會下降,因為Redis在操作這種結構的時候,編解碼會出現一定的壓力。
壓縮列表的長度限制在500-2000之內,每個元素體積限制在128位元組或以下,壓縮列表的的性能都會處於合理範圍之內。
參考資料:
《redis設計與實現》