摘要
Redis是一款著名的key-value內存數(shù)據庫軟件,同時也是一款卓越的數(shù)據結構服務軟件。它支持字符串、列表、哈希表、集合、有序集合五種數(shù)據結構類型,同時每種數(shù)據結構類型針對不同的應用場景又支持不同的編碼方式。這篇文章主要介紹壓縮列表編碼,在理解壓縮列表編碼原理的基礎上介紹Redis對壓縮列表的應用,最后再對Redis壓縮列表應用進行分析。
Redis壓縮列表原理與應用
壓縮列表是一種數(shù)據結構,這種數(shù)據結構的功能是將一系列數(shù)據與其編碼信息存儲在一塊連續(xù)的內存區(qū)域,這塊內存物理上是連續(xù)的,邏輯上被分為多個組成部分,其目的是在一定可控的時間復雜讀條件下盡可能的減少不必要的內存開銷,從而達到節(jié)省內存的效果,這么介紹有點玄乎,我們先一起看看它的實現(xiàn)原理吧,Redis3.2版本中,作者對壓縮列表的實現(xiàn)在ziplist.h和ziplist.c中。
壓縮列表原理
我認為將數(shù)據按照一定規(guī)則存儲在內存中可以用“編碼”這個詞描述,因此下面會常用“編碼”這個詞。
總體編碼
上面說到壓縮列表是一塊連續(xù)的內存區(qū)域,這塊內存區(qū)域布編碼示意圖大致如下:
Redis壓縮列表內存編碼示意圖
常態(tài)的壓縮列表內存編碼如上圖所示,整個內存塊區(qū)域內分為五個部分,下面分別介紹著五個部分:
zlbytes:存儲一個無符號整數(shù),固定四個字節(jié)長度,用于存儲壓縮列表所占用的字節(jié),當重新分配內存的時候使用,不需要遍歷整個列表來計算內存大小。
zltail:存儲一個無符號整數(shù),固定四個字節(jié)長度,代表指向列表尾部的偏移量,偏移量是指壓縮列表的起始位置到指定列表節(jié)點的起始位置的距離。
zllen:壓縮列表包含的節(jié)點個數(shù),固定兩個字節(jié)長度,源碼中指出當節(jié)點個數(shù)大于2^16-2個數(shù)的時候,該值將無效,此時需要遍歷列表來計算列表節(jié)點的個數(shù)。
entryX:列表節(jié)點區(qū)域,長度不定,由列表節(jié)點緊挨著組成。
zlend:一字節(jié)長度固定值為255,用于表示列表結束。
列表元素編碼
上面介紹了壓縮列表的總體內存布局,對于初entryX區(qū)域以外的四個區(qū)域的長度都是固定的,下面再看看entryX區(qū)域的編碼情況。
每個列表節(jié)點由三部分組成:
壓縮列表節(jié)點編碼示意圖
每個壓縮列表節(jié)點區(qū)域頭部包含兩部分,一部分叫做previous length,另一部分叫encoding,最后是主體內容,叫做content,下面分別介紹他們:
previous length
用于存儲上一個節(jié)點的長度,因此壓縮列表可以從尾部向頭部遍歷,即當前節(jié)點位置減去上一個節(jié)點的長度即得到上一個節(jié)點的起始位置。previous length的長度可能是1個字節(jié)或者是5個字節(jié),如果上一個節(jié)點的長度小于254,則該節(jié)點只需要一個字節(jié)就可以表示前一個節(jié)點的長度了,如果前一個節(jié)點的長度大于等于254,則previous length的第一個字節(jié)為254,后面用四個字節(jié)表示當前節(jié)點前一個節(jié)點的長度。這么做很有效地減少了內存的浪費。
encoding
節(jié)點的encoding保存的是節(jié)點的content的內容類型以及長度,encoding類型一共有兩種,一種字節(jié)數(shù)組一種是整數(shù),encoding區(qū)域長度為1字節(jié)、2字節(jié)或者5字節(jié)長。Redis作者巧妙的利用了前兩個字節(jié)來表示content存儲的內容類型和encoding區(qū)域的長度,我們先看看字節(jié)數(shù)組類型的encoding內容:
content為字節(jié)數(shù)組的encoding內容
再看看整數(shù)編碼類型的encoding內容:
content為整數(shù)的encoding內容
content
content區(qū)域用于保存節(jié)點的內容,節(jié)點內容類型和長度由encoding決定,上面可以看出目前content的內容類型有整數(shù)類型和字節(jié)數(shù)組類型,且某些條件下content的長度可能為0。
相信到這里,我們都明白了壓縮列表的原理,壓縮列表并不是對數(shù)據利用某種算法進行壓縮,而是將數(shù)據按照一定規(guī)則編碼在一塊連續(xù)的內存區(qū)域,目的是節(jié)省內存。下面我們看看壓縮列表在Redis中的應用領域。
Redis中壓縮列表的應用
Redis中,不同的數(shù)據類型廣泛地應用了壓縮列表編碼,整理如下表:
Redis中數(shù)據結構類型與壓縮列表的應用
上表總結了壓縮列表編碼在Redis不同的數(shù)據類型中的應用,Redis一共支持五種數(shù)據結構類型,其中有三種數(shù)據結構在一定條件下會應用壓縮列表,至于什么條件后面會分析,值得一提的是Redis當前支持的GEO(地理位置)對壓縮列表也有應用,具體此處不做討論。
Redis壓縮列表應用分析
上面部分介紹了Redis壓縮列表的原理與應用,下面簡單分析一下,主要從通過試圖回答一些問題來分析:Redis為什么使用壓縮列表?使用壓縮列表的好處是什么?使用壓縮列表的好處還有什么?壓縮列表的應用對與我們使用內存有沒有什么啟發(fā)?
Redis對于每種數(shù)據結構、無論是列表、哈希表還是有序集合,在決定是否應用壓縮列表作為當前數(shù)據結構類型的底層編碼的時候都會依賴一個開關和一個閾值,開關用來決定我們是否要啟用壓縮列表編碼,閾值總的來說通常指當前結構存儲的key數(shù)量有沒有達到一個數(shù)值(條件),或者是value值長度有沒有達到一定的長度(條件)。任何策略都有其應用場景,不同場景應用不同策略。為什么當前結構存儲的數(shù)據條目達到一定數(shù)值使用壓縮列表就不好?壓縮列表的新增、刪除的操作平均時間復雜度為O(N),隨著N的增大,時間必然會增加,他不像哈希表可以以O(1)的時間復雜度找到存取位置,然而在一定N內的時間復雜度我們可以容忍。然而壓縮列表利用巧妙的編碼技術除了存儲內容盡可能的減少不必要的內存開銷,將數(shù)據存儲于連續(xù)的內存區(qū)域,這對于Redis本身來說是有意義的,因為Redis是一款內存數(shù)據庫軟件,想辦法盡可能減少內存的開銷是Redis設計者一定要考慮的事情。
另外,經過仔細琢磨,我認為使用壓縮列表的好處除了節(jié)約內存之外,還有減少內存碎片的作用,我把這種行為叫做"合并存儲",也就是將很多小的數(shù)據塊存儲在一個比較大的內存區(qū)域,試想想,如果我們將要存儲的數(shù)據都是很小的條目,我們?yōu)槊恳粋€數(shù)據條目都單獨的申請內存,結果是這些條目將有可能分散在內存的每一個角落,最終導致碎片增加,這是一件令人頭疼的事情。
總結
這篇文章在介紹Redis壓縮列表原理與應用的基礎之上對Redis壓縮列表的應用進行分析,分析部分主要摻雜著個人的理解與認知,如果有不同觀點或者補充觀點,歡迎留言討論。
另外有需要云服務器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內外云服務器15元起步,三天無理由+7*72小時售后在線,公司持有idc許可證,提供“云服務器、裸金屬服務器、高防服務器、香港服務器、美國服務器、虛擬主機、免備案服務器”等云主機租用服務以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務可用性高、性價比高”等特點與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應用場景需求。