原文地址:www.xilidou.com/2018/03/13/…redis
上篇文章寫了 Redis 基礎數據結構的可變字符串、鏈表、字典。你們能夠點擊連接查看。今天咱們繼續研究 Redis 的基礎數據結構。數據庫
當一個集合只包含整數,且這個集合的元素很少的時候,Redis 就會使用整數集合 intset 。首先看 intset 的數據結構:數組
typedef struct intset {
// 編碼方式
uint32_t encoding;
// 集合包含的元素數量
uint32_t length;
// 保存元素的數組
int8_t contents[];
} intset;
複製代碼
其實 intset 的數據結構比較好理解。一個數據保存元素,length 保存元素的數量,也就是contents的大小,encoding 用於保存數據的編碼方式。微信
經過代碼咱們能夠知道,encoding 的編碼類型包括了:數據結構
#define INTSET_ENC_INT16 (sizeof(int16_t))
#define INTSET_ENC_INT32 (sizeof(int32_t))
#define INTSET_ENC_INT64 (sizeof(int64_t))
複製代碼
實際上咱們能夠看出來。 Redis encoding的類型,就是指數據的大小。做爲一個內存數據庫,採用這種設計就是爲了節約內存。對於一個數據咱們能夠畫一個圖來幫助理解:併發
既然有從小到大的三個數據結構,在插入數據的時候儘量使用小的數據結構來節約內存,若是插入的數據大於原有的數據結構,就會觸發擴容。學習
擴容有三個步驟:ui
整數集合不支持降級操做,一旦升級就不能降級了。編碼
跳躍表是鏈表的一種,是一種利用空間換時間的數據結構。跳錶平均支持 O(logN),最壞O(N)複雜度的查找。spa
跳錶是由一個zskiplist 和 多個 zskiplistNode 組成。咱們先看看他們的結構:
/* ZSETs use a specialized version of Skiplists */
/* * 跳躍表節點 */
typedef struct zskiplistNode {
// 成員對象
robj *obj;
// 分值
double score;
// 後退指針
struct zskiplistNode *backward;
// 層
struct zskiplistLevel {
// 前進指針
struct zskiplistNode *forward;
// 跨度
unsigned int span;
} level[];
} zskiplistNode;
/* * 跳躍表 */
typedef struct zskiplist {
// 表頭節點和表尾節點
struct zskiplistNode *header, *tail;
// 表中節點的數量
unsigned long length;
// 表中層數最大的節點的層數
int level;
} zskiplist;
複製代碼
因此根據這個代碼咱們能夠畫出以下的結構圖:
其實跳錶就是一個利用空間換時間的數據結構,利用 level 做爲鏈表的索引。
以前有人問過 Redis 的做者 爲何使用跳躍表,而不是 tree 來構建索引?做者的回答是:
壓縮鏈表 Redis 做者的介紹是,爲了儘量節約內存設計出來的雙向鏈表。
對於一個壓縮列表代碼裏註釋給出的數據結構以下:
zlbytes
表示的是整個壓縮列表使用的內存字節數
zltail
指定了壓縮列表的尾節點的偏移量
zllen
是壓縮列表 entry 的數量
entry
就是 ziplist 的節點
zlend
標記壓縮列表的末端
這個列表中還有單個指針:
ZIPLIST_ENTRY_HEAD
列表開始節點的頭偏移量
ZIPLIST_ENTRY_TAIL
列表結束節點的頭偏移量
ZIPLIST_ENTRY_END
列表的尾節點結束的偏移量
再看看一個 entry 的結構:
/* * 保存 ziplist 節點信息的結構 */
typedef struct zlentry {
// prevrawlen :前置節點的長度
// prevrawlensize :編碼 prevrawlen 所需的字節大小
unsigned int prevrawlensize, prevrawlen;
// len :當前節點值的長度
// lensize :編碼 len 所需的字節大小
unsigned int lensize, len;
// 當前節點 header 的大小
// 等於 prevrawlensize + lensize
unsigned int headersize;
// 當前節點值所使用的編碼類型
unsigned char encoding;
// 指向當前節點的指針
unsigned char *p;
} zlentry;
複製代碼
依次解釋一下這幾個參數。
prevrawlen
前置節點的長度,這裏多了一個 size,實際上是記錄了 prevrawlen 的尺寸。Redis 爲了節約內存並非直接使用默認的 int 的長度,而是逐漸升級的。 同理 len
記錄的是當前節點的長度,lensize
記錄的是 len 的長度。 headersize
就是前文提到的兩個 size 之和。 encoding
就是這個節點的數據類型。這裏注意一下 encoding 的類型只包括整數和字符串。 p
節點的指針,不用過多的解釋。
須要注意一點,由於每一個節點都保存了前一個節點的長度,若是發生了更新或者刪除節點,則這個節點以後的數據也須要修改,有一種最壞的狀況就是若是每一個節點都處於須要擴容的零界點,就會形成這個節點以後的節點都要修改 size 這個參數,引起連鎖反應。這個時候就是 壓縮鏈表最壞的時間複雜度 O(n^2)。不過全部節點都處於臨界值,這樣的機率能夠說比較小。
至此Redis的基本數據結構就介紹完了。咱們能夠看到 Redis 對內存的使用真是「斤斤計較」,對於內存是使用特別節約。同時 Redis 做爲一個單線程應用,不用考慮併發的問題,將不少相似 size 或者 length 的參數暴露出來,將不少 O(n) 的操做下降爲 O(1)。大大提高效率。下一講,將會介紹 Redis 是怎麼經過這些數據結構向外提供服務。 Redis 的代碼真是寫的太棒了,簡潔高效。值得你們學習。
歡迎關注個人微信公衆號: