1. 程式人生 > >Redis壓縮列表原理與應用分析

Redis壓縮列表原理與應用分析

 

摘要

Redis是一款著名的key-value記憶體資料庫軟體,同時也是一款卓越的資料結構服務軟體。它支援字串、列表、雜湊表、集合、有序集合五種資料結構型別,同時每種資料結構型別針對不同的應用場景又支援不同的編碼方式。這篇文章主要介紹壓縮列表編碼,在理解壓縮列表編碼原理的基礎上介紹Redis對壓縮列表的應用,最後再對Redis壓縮列表應用進行分析。

Redis壓縮列表原理與應用

壓縮列表是一種資料結構,這種資料結構的功能是將一系列資料與其編碼資訊儲存在一塊連續的記憶體區域,這塊記憶體物理上是連續的,邏輯上被分為多個組成部分,其目的是在一定可控的時間複雜讀條件下儘可能的減少不必要的記憶體開銷,從而達到節省記憶體的效果,這麼介紹有點玄乎,我們先一起看看它的實現原理吧,Redis3.2版本中,作者對壓縮列表的實現在ziplist.h和ziplist.c中。

壓縮列表原理

我認為將資料按照一定規則儲存在記憶體中可以用“編碼”這個詞描述,因此下面會常用“編碼”這個詞。

總體編碼

上面說到壓縮列表是一塊連續的記憶體區域,這塊記憶體區域布編碼示意圖大致如下:

Redis壓縮列表記憶體編碼示意圖

常態的壓縮列表記憶體編碼如上圖所示,整個記憶體塊區域內分為五個部分,下面分別介紹著五個部分:

zlbytes:儲存一個無符號整數,固定四個位元組長度,用於儲存壓縮列表所佔用的位元組,當重新分配記憶體的時候使用,不需要遍歷整個列表來計算記憶體大小。

zltail:儲存一個無符號整數,固定四個位元組長度,代表指向列表尾部的偏移量,偏移量是指壓縮列表的起始位置到指定列表節點的起始位置的距離。

zllen:壓縮列表包含的節點個數,固定兩個位元組長度,原始碼中指出當節點個數大於2^16-2個數的時候,該值將無效,此時需要遍歷列表來計算列表節點的個數。

entryX:列表節點區域,長度不定,由列表節點緊挨著組成。

zlend:一位元組長度固定值為255,用於表示列表結束。

列表元素編碼

上面介紹了壓縮列表的總體記憶體佈局,對於初entryX區域以外的四個區域的長度都是固定的,下面再看看entryX區域的編碼情況。

每個列表節點由三部分組成:

壓縮列表節點編碼示意圖

每個壓縮列表節點區域頭部包含兩部分,一部分叫做previous length,另一部分叫encoding,最後是主體內容,叫做content,下面分別介紹他們:

previous length

用於儲存上一個節點的長度,因此壓縮列表可以從尾部向頭部遍歷,即當前節點位置減去上一個節點的長度即得到上一個節點的起始位置。previous length的長度可能是1個位元組或者是5個位元組,如果上一個節點的長度小於254,則該節點只需要一個位元組就可以表示前一個節點的長度了,如果前一個節點的長度大於等於254,則previous length的第一個位元組為254,後面用四個位元組表示當前節點前一個節點的長度。這麼做很有效地減少了記憶體的浪費。

encoding

節點的encoding儲存的是節點的content的內容型別以及長度,encoding型別一共有兩種,一種位元組陣列一種是整數,encoding區域長度為1位元組、2位元組或者5位元組長。Redis作者巧妙的利用了前兩個位元組來表示content儲存的內容型別和encoding區域的長度,我們先看看位元組陣列型別的encoding內容:

content為位元組陣列的encoding內容

再看看整數編碼型別的encoding內容:

content為整數的encoding內容

content

content區域用於儲存節點的內容,節點內容型別和長度由encoding決定,上面可以看出目前content的內容型別有整數型別和位元組陣列型別,且某些條件下content的長度可能為0。

相信到這裡,我們都明白了壓縮列表的原理,壓縮列表並不是對資料利用某種演算法進行壓縮,而是將資料按照一定規則編碼在一塊連續的記憶體區域,目的是節省記憶體。下面我們看看壓縮列表在Redis中的應用領域。

Redis中壓縮列表的應用

Redis中,不同的資料型別廣泛地應用了壓縮列表編碼,整理如下表:

Redis中資料結構型別與壓縮列表的應用

上表總結了壓縮列表編碼在Redis不同的資料型別中的應用,Redis一共支援五種資料結構型別,其中有三種資料結構在一定條件下會應用壓縮列表,至於什麼條件後面會分析,值得一提的是Redis當前支援的GEO(地理位置)對壓縮列表也有應用,具體此處不做討論。

Redis壓縮列表應用分析

上面部分介紹了Redis壓縮列表的原理與應用,下面簡單分析一下,主要從通過試圖回答一些問題來分析:Redis為什麼使用壓縮列表?使用壓縮列表的好處是什麼?使用壓縮列表的好處還有什麼?壓縮列表的應用對與我們使用記憶體有沒有什麼啟發?

Redis對於每種資料結構、無論是列表、雜湊表還是有序集合,在決定是否應用壓縮列表作為當前資料結構型別的底層編碼的時候都會依賴一個開關和一個閾值,開關用來決定我們是否要啟用壓縮列表編碼,閾值總的來說通常指當前結構儲存的key數量有沒有達到一個數值(條件),或者是value值長度有沒有達到一定的長度(條件)。任何策略都有其應用場景,不同場景應用不同策略。為什麼當前結構儲存的資料條目達到一定數值使用壓縮列表就不好?壓縮列表的新增、刪除的操作平均時間複雜度為O(N),隨著N的增大,時間必然會增加,他不像雜湊表可以以O(1)的時間複雜度找到存取位置,然而在一定N內的時間複雜度我們可以容忍。然而壓縮列表利用巧妙的編碼技術除了儲存內容儘可能的減少不必要的記憶體開銷,將資料儲存於連續的記憶體區域,這對於Redis本身來說是有意義的,因為Redis是一款記憶體資料庫軟體,想辦法儘可能減少記憶體的開銷是Redis設計者一定要考慮的事情。

另外,經過仔細琢磨,我認為使用壓縮列表的好處除了節約記憶體之外,還有減少記憶體碎片的作用,我把這種行為叫做"合併儲存",也就是將很多小的資料塊儲存在一個比較大的記憶體區域,試想想,如果我們將要儲存的資料都是很小的條目,我們為每一個數據條目都單獨的申請記憶體,結果是這些條目將有可能分散在記憶體的每一個角落,最終導致碎片增加,這是一件令人頭疼的事情。

總結

這篇文章在介紹Redis壓縮列表原理與應用的基礎之上對Redis壓縮列表的應用進行分析,分析部分主要摻雜著個人的理解與認知,如果有不同觀點或者補充觀點,