從根兒上理解 Redis(一)

簡單動態(tài)字符串

Redis 底層使用 C 語言實現(xiàn)的则披,但是 Redis 沒有直接使用 C 語言傳統(tǒng)的字符串表示,而是構(gòu)建了一種名為簡單動態(tài)字符串(simple dynamic string铣墨,SDS)的抽象類型浦夷。
當(dāng) Redis 需要的不僅僅是一個字符串字面量邑飒,而是一個可以被修改的字符串值時,Redis 就會使用 SDS 來表示字符串值帅涂。
下面來看一下议薪,為什么 Redis 會使用 SDS 而不是 C 字符串。

SDS 的定義

struct sdshdr{
    //記錄 buf 數(shù)組中已使用字節(jié)的數(shù)量
    //等于 SDS 所保存字符串的長度
    int len;
    //記錄 buf 數(shù)組中未使用字節(jié)的數(shù)量
    int free;
    //字節(jié)數(shù)組媳友,用于保存字符串
    char buf[];
}
image.png

上圖是 SDS 結(jié)構(gòu)實例斯议,free 表示生于多少空間,len 表示占用了多少空間醇锚,末尾 ‘\0’ 不統(tǒng)計哼御。buf 用于存儲。

SDS 對比 C 字符串優(yōu)勢:
1.常數(shù)復(fù)雜度獲取字符串長度搂抒,C 字符串獲取長度需要遍歷整個字符串艇搀。
2.杜絕緩沖區(qū)溢出,SDS 完全杜絕了緩沖區(qū)溢出求晶,在新分配和拼接的時候焰雕,都會判斷空間夠不夠。
3.減少修改字符串時帶來的內(nèi)存重分配次數(shù)芳杏。應(yīng)用空間預(yù)分配和惰性kongjianshifang機制矩屁。
4.二進制安全,可以保存任意格式的二進制數(shù)據(jù)爵赵。
5.兼容部分 C 字符串函數(shù)吝秕。

鏈表

Redis 構(gòu)建了自己的鏈表實現(xiàn),當(dāng)一個列表鍵包含了數(shù)量比較多的元素空幻,又或者列表中包含的與那素都是比較長的字符串時烁峭,Redis 就會使用鏈表作為列表鍵的底層實現(xiàn)。
除了鏈表鍵之外,發(fā)布與訂閱约郁、慢查詢缩挑、監(jiān)視器等功能也用到了鏈表。
鏈表節(jié)點結(jié)構(gòu):

typedef struct listNode{
    struct listNode *prev;
    struct listNode *next;
    void *value;
}

多個 listNode 組成的雙端鏈表


image.png

Redis 應(yīng)用一個 list 結(jié)構(gòu)來持有鏈表鬓梅,如下:
typedef struct list{
//表頭
listNode *head;
//表尾
listNode * tail;
//節(jié)點個數(shù)
unsigned long len;
//節(jié)點值復(fù)制函數(shù)
void (dup)(void ptr);
//節(jié)點釋放函數(shù)
void (
free)(void ptr);
//節(jié)點值對比函數(shù)
int (
match)(void *ptr,void *key);
} list;
list 結(jié)構(gòu)為鏈表提供了表頭指針 head供置、表尾指針 tail、以及鏈表長度技術(shù)器 len绽快,dup 函數(shù)用于復(fù)制節(jié)點所保存的值芥丧,free 函數(shù)用于釋放鏈表節(jié)點所保存的值,match 函數(shù)用于對比鏈表節(jié)點所保存的值和另一個輸入值是否相等坊罢。
list 示意圖:

image.png

字典

字典续担,又稱為符號表、關(guān)聯(lián)數(shù)組或映射艘绍,是一種用于保存鍵值對的抽象數(shù)據(jù)結(jié)構(gòu)赤拒。
Redis 的字典使用哈希表作為底層實現(xiàn)秫筏,一個哈希表里面可以有多個哈希表結(jié)點诱鞠,每個哈希表結(jié)點就保存了字典中的一個鍵值對。
哈希表結(jié)構(gòu):

typedef struct dictht{
    //哈希表數(shù)組
    dictEntry **table;
    //哈希表大小
    unsigned long size;
    //哈希表大小掩碼这敬,用于計算索引值總是等于 size-1
    unsigned long sizemask;
    //該哈希表已有節(jié)點的數(shù)量
    unsigned long used;
} dictht;
image.png

table 屬性是一個數(shù)組航夺,數(shù)組中每個元素都是一個指向 dictEntry 結(jié)構(gòu)的指針,每個 dictEntry 結(jié)構(gòu)保存著一個鍵值對崔涂。size 記錄哈希表的大小阳掐,used 標(biāo)識哈希表已有節(jié)點的數(shù)量。sizemark 總是等于 size - 1冷蚂。

哈希表節(jié)點:

typedef struct dictEntry{
    //鍵
    void *key;
    //值
    union{
        void *val;
        unit64_tu64;
        int64_ts64;
    }v;
    //指向下個哈希表節(jié)點缭保,形成鏈表
    struct dictEntry *next;
}dictEntry;

next 屬性指向另一個哈希表節(jié)點指針,這個指針可以將多個哈希值相同的鍵值對連接在一起蝙茶,來解決哈希沖突艺骂。


image.png

字典結(jié)構(gòu):

typedef struct dict{
    //類型特定函數(shù)
    dictType *type;
    //私有數(shù)據(jù)
    void *privdata;
    //哈希表
    dict ht[2];
    //rehash 索引
    //當(dāng) rehash 不在進行時,值為 -1
    int rehashidx;
}dict;

type 屬性是一個指向 dictType 結(jié)構(gòu)的指針隆夯,每個 dictType 結(jié)構(gòu)保存了一簇用于特定類型鍵值對的函數(shù)钳恕。
privdata 屬性保存了需要傳給那些類型特定函數(shù)的可選參數(shù)。

image.png

跳躍表

跳躍表是一種有序數(shù)據(jù)結(jié)構(gòu)蹄衷,它通過在每個節(jié)點中維持多個指向其他節(jié)點的指針忧额,從而達到快速訪問節(jié)點的目的。
大部分情況下愧口,跳躍表可以和平衡樹相匹配睦番。
Redis 只在兩個地方用到了跳躍表,一個是實現(xiàn)有序集合鍵耍属,另一個是在集群節(jié)點中用作內(nèi)部數(shù)據(jù)結(jié)構(gòu)托嚣。
跳表示例:


image.png

header:指向跳躍表的表頭節(jié)點大咱。
tail:指向跳躍表的表尾節(jié)點。
level:記錄目前跳躍表內(nèi)注益,層數(shù)最大的那個節(jié)點的層數(shù)碴巾。
length:記錄跳躍表的長度,也即是丑搔,跳躍表目前包含節(jié)點的數(shù)量厦瓢。
跳表節(jié)點每一層都有前進指針和跨度,跨度標(biāo)識前進指針指向的節(jié)點和當(dāng)前節(jié)點的距離啤月。
每個節(jié)點都有一個后退指針煮仇,指向前一個節(jié)點。
1.0 2.0 3.0 是每個節(jié)點的分值谎仲,按順序排列浙垫。
o1,o2,o3 是節(jié)點所保存的成員對象。

跳躍表節(jié)點

typedef struct zskiplistNode{
    //層
    struct zskiplistLevel{
        //前進指針
        struct zskiplistNode *forward;
        //跨度
        unsigned int span郑诺;
    }level[];
    //后退指針
    struct zskiplistNode *backward;
    //分值
    double score;
    //成員對象
    robj *obj;
}zskiplistNode;

跳躍表

typedef struct zskiplist{
    //表頭節(jié)點和表尾節(jié)點
    struct skiplistNode *header, *tail;
    表中節(jié)點的數(shù)量
    unsigned long length夹姥;
    //表中層樹最大的節(jié)點的層數(shù)
    int level;
}zskiplist;

整數(shù)集合

整數(shù)集合是 Redis 用于保存整數(shù)值的集合抽象數(shù)據(jù)結(jié)構(gòu),可以保存的類型為 int16_t辙诞、int32_t 或者 int64_t 的整數(shù)辙售,并且保證集合不會出現(xiàn)重復(fù)元素。

typedef struct intset{
    //編碼方式
    uint32_t encoding;
    //集合包含的元素數(shù)量
    uint32_t length;
    //保存元素的數(shù)組
    int8_t contents[];
}intset;

contents 是整數(shù)集合的底層實現(xiàn)飞涂,整數(shù)集合的每個元素都是 contents 數(shù)字的一個數(shù)組項旦部。
length 屬性記錄了整數(shù)集合包含的元素數(shù)量。
encoding 決定 contents 數(shù)組的類型较店。


image.png

整數(shù)集合的升級

每當(dāng)我們要將一個新元素添加到整數(shù)集合里面士八,并且新元素的類型比整數(shù)集合現(xiàn)有元素的類型長,整數(shù)集合就要做升級梁呈。
升級分為三步:
1.根據(jù)新元素類型婚度,擴展整數(shù)集合底層數(shù)組的空間大小,并為新元素分配空間捧杉。
例如下圖陕见,原本集合中的元素是 int16_t 類型的 1,2,3。然后將 65535 添加進去味抖,因為 65535 是 int32_t 類型评甜,所以就要做升級。


image.png

2.將底層數(shù)組現(xiàn)有元素轉(zhuǎn)換成新元素相同類型仔涩,所有元素如下圖忍坷,從后往前,一次重新放到新分配的空間的 3,2,1 的位置。

image.png
image.png
image.png

整數(shù)集合升級好處就是靈活性和盡可能的節(jié)約內(nèi)存佩研。
整數(shù)集合不支持降級操作柑肴。

壓縮列表

壓縮列表是列表鍵和哈希鍵的底層實現(xiàn)之一,當(dāng)一個列表鍵只包含少量列表項旬薯,并且每個列表要么就是小整數(shù)值晰骑,要么就是長度比較短的字符串,那么 Redis 就會使用壓縮列表來做列表鍵的底層實現(xiàn)绊序。
壓縮列表的構(gòu)成:


image.png

image.png

壓縮列表節(jié)點構(gòu)成

每個壓縮列表節(jié)點可以保存一個字節(jié)數(shù)組或者一個整數(shù)值硕舆。


image.png

previous_entry_length,記錄了壓縮列表中前一個節(jié)點的長度骤公。
encoding抚官,記錄了節(jié)點的 content 屬性所保存的數(shù)據(jù)類型及長度。
content阶捆,保存節(jié)點的值凌节。

壓縮列表有連鎖更新機制,跟上面那個整數(shù)集合升級有點像洒试。
previous_entry_length 記錄了壓縮列表中前一個節(jié)點的長度倍奢,previous_entry_length 的大小是一個字節(jié),兩個字節(jié)或者五個字節(jié)儡司,根據(jù)前一個元素大小有關(guān)娱挨。但是假如在多小于 254 字節(jié)數(shù)之前添加一個大于等于 254 字節(jié)的數(shù)余指,后續(xù)的 previous_entry_length 都要挨個更新長度捕犬。

最后

至此 Redis 的底層數(shù)據(jù)結(jié)構(gòu)就介紹完了,來看下下面這個表酵镜,Redis 使用的不同類型隊形的編碼方式碉碉。是不是就很清晰了。

image.png

參考:Redis 設(shè)計與實現(xiàn)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末淮韭,一起剝皮案震驚了整個濱河市垢粮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌靠粪,老刑警劉巖蜡吧,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異占键,居然都是意外死亡昔善,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進店門畔乙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來君仆,“玉大人,你說我怎么就攤上這事》翟郏” “怎么了钥庇?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長咖摹。 經(jīng)常有香客問我评姨,道長,這世上最難降的妖魔是什么萤晴? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任参咙,我火速辦了婚禮,結(jié)果婚禮上硫眯,老公的妹妹穿的比我還像新娘蕴侧。我一直安慰自己,他們只是感情好两入,可當(dāng)我...
    茶點故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布净宵。 她就那樣靜靜地躺著,像睡著了一般裹纳。 火紅的嫁衣襯著肌膚如雪择葡。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天剃氧,我揣著相機與錄音敏储,去河邊找鬼。 笑死朋鞍,一個胖子當(dāng)著我的面吹牛已添,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播滥酥,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼更舞,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了坎吻?” 一聲冷哼從身側(cè)響起缆蝉,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎瘦真,沒想到半個月后刊头,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡诸尽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年原杂,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片弦讽。...
    茶點故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡污尉,死狀恐怖膀哲,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情被碗,我是刑警寧澤某宪,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站锐朴,受9級特大地震影響兴喂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜焚志,卻給世界環(huán)境...
    茶點故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一衣迷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧酱酬,春花似錦壶谒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至挑社,卻和暖如春陨界,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背痛阻。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工菌瘪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人阱当。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓俏扩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親斗这。 傳聞我的和親對象是個殘疾皇子动猬,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,947評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)載:可能是目前最詳細的Redis內(nèi)存模型及應(yīng)用解讀 Redis是目前最火爆的內(nèi)存數(shù)據(jù)庫之一,通過在內(nèi)存中讀寫數(shù)據(jù)...
    jwnba24閱讀 623評論 0 4
  • Redis 是一個鍵值對數(shù)據(jù)庫(key-value DB)表箭,數(shù)據(jù)庫的值可以是字符串、集合钮莲、列表等多種類型的對象免钻,而...
    吳昂_ff2d閱讀 3,199評論 0 5
  • 想像力寫作 何為想像力寫作,首先要激活想像力崔拥。如一個房子极舔,你會想到什么?一間木頭房子链瓦、一間草房子拆魏?這不是想像力盯桦,想...
    阿笨貓_6bd5閱讀 204評論 0 1
  • 雨過天晴,陽光明媚渤刃,換上喜歡的衣服拥峦,快快樂樂出門上班。 恰逢世界微笑日卖子,讓我們嘴角上翹略号,我們用微...
    嫣嫣洋閱讀 251評論 0 3
  • 昨晚放學(xué)的時候,在樓梯上遇到高一帶過的學(xué)生洋闽⌒互相打招呼,我調(diào)侃:最近瘦了诫舅,讀書用功了吧?他則告訴我一個驚人的消息:...
    紫色小路閱讀 104評論 0 1