《redis設(shè)計與實現(xiàn)》——數(shù)據(jù)結(jié)構(gòu)

一爽醋、SDS

Redis自己構(gòu)建了一種名為簡單動態(tài)字符串(simple dynamic string, SDS)的抽象類型,將SDS用作Redis的默認(rèn)字符串表示雷逆。而沒有直接使用C語言傳統(tǒng)的字符串表示富拗。

每個sds.h/shshdr結(jié)構(gòu)表示一個SDS值:

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

要點:

  • Redis 只會使用C字符串作為字面量喇肋,在大多數(shù)情況下,Redis使用SDS作為字符串表示档桃。
  • 比起C字符串枪孩,SDS具有以下優(yōu)點:
    • 常數(shù)復(fù)雜度獲取字符串長度。
    • 杜絕緩沖區(qū)溢出藻肄。
    • 減少修改字符串長度時所需的內(nèi)存重分配次數(shù)蔑舞。
    • 二進制安全:不以空字符作為字符串結(jié)尾。
    • 兼容部分C字符串函數(shù)嘹屯。

二攻询、鏈表

鏈表節(jié)點:adlist.h/listNode

typedef struct listNode {
    // 前置節(jié)點
    struct listNode *prev;
    
    // 后置節(jié)點
    struct listNode *next;
    
    // 節(jié)點的值
    void *value;
} listNode;

鏈表:adlist.h/list

typedef struct list {
    // 表頭節(jié)點
    listNode *head;
    
    // 表尾節(jié)點
    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;

要點

  • 鏈表被廣泛用于實現(xiàn)Redis的各種功能,比如列表鍵州弟、發(fā)布與訂閱钧栖、慢查詢低零、監(jiān)視器等。
  • 每一個鏈表節(jié)點有一個listNode結(jié)構(gòu)表示拯杠,每一個節(jié)點都有一個指向前置節(jié)點和后直接點的指針掏婶,所以Redis的鏈表實現(xiàn)時雙端鏈表。
  • 每一個鏈表使用一個list結(jié)構(gòu)來表示潭陪,這個結(jié)構(gòu)帶有表頭節(jié)點指針雄妥、表尾節(jié)點指針,以及鏈表長度等信息依溯。
  • 因為鏈表表頭節(jié)點和前置節(jié)點和表尾節(jié)點的后置節(jié)點都指向NULL茎芭,所以Redis的鏈表實現(xiàn)是無環(huán)鏈表。
  • 通過為鏈表設(shè)置不同的類型特定函數(shù)誓沸,Redis的鏈表可以用于保存各種不同類型的值梅桩。

三、字典

哈希表:dict.h/dictht

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

哈希表節(jié)點:

typedef struct dictEntry {
    // 鍵
    void *key;
    
    // 值
    union {
        void *val;
        uint64_t u64;
        int64_t 64;
    } v;
    
    // 指向下個哈希表節(jié)點宿百,形成鏈表, 以此解決沖突
    struct dictEntry *next;
    
} dictEntry;

字典:dict.h/dict

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

dictType:

typedef struct dictType {
    // 計算哈希值的函數(shù)
    unsigned int (*hashFunction)(const void *key);
    
    // 復(fù)制鍵的函數(shù)
    void *(*keyDup)(void *privdata, const void *key);
    
    // 復(fù)制值的函數(shù)
    void *(*valDup)(void *privdata, const void *obj);
    
    // 對比鍵的函數(shù)
    int (*keyCompare)(void *privdata, const void *key1, const void *key2);
    
    // 銷毀鍵的函數(shù)
    void (*keyDestructor)(void *privdata, void *key);
    
    // 銷毀值的函數(shù)
    void (*valDestructor)(void *privdata, void *obj);
}dictType;

要點

  • 字典被廣泛用于實現(xiàn)redis的各項功能洪添,其中包括數(shù)據(jù)庫和哈希鍵垦页。
  • Redis中的字典使用哈希表作為底層實現(xiàn),每個字典帶有兩個哈希表干奢,一個平時使用痊焊,另一個僅在進行rehash時使用。
  • 當(dāng)字典被用作數(shù)據(jù)庫的底層實現(xiàn)忿峻,或者哈希鍵的底層實現(xiàn)時薄啥,Redis使用MurmurHash2算法來計算哈希鍵的哈希值(優(yōu)點:即使輸入的鍵是有規(guī)律的,算法仍能給出一個很好的隨機分布性逛尚,并且算法的計算速度也非陈⒕澹快)。
  • 哈希表使用鏈地址法來解決鍵沖突绰寞,被分配到同一個索引上的多個鍵值會連成一個單項鏈表(速度考慮到逊,采用頭插法)。
  • 在對哈希表進行擴展或者收縮操作時滤钱,程序需要將現(xiàn)有哈希表包含的所有鍵值對rehash到新哈希表里面觉壶,并且這個rehash過程并不是一次性地完成,而是漸進式的完成的(每次更新更新新哈希表件缸,刪除舊哈希表節(jié)點铜靶,插入只插入新哈希表,保證ht[0]包含的鍵值對數(shù)量只減不增停团,最終為空表)旷坦。

四掏熬、跳躍表

跳躍表節(jié)點:redis.h/zskiplistNode

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

zskiplist 結(jié)構(gòu)定義:

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

要點

  • 跳躍表是有序集合的底層實現(xiàn)之一。
  • Redis的跳躍表實現(xiàn)由zskiplist和zskiplistNode兩個結(jié)構(gòu)組成秒梅,其中zskiplist用于保存跳躍表信息(比如表頭節(jié)點旗芬、表尾節(jié)點、長度)捆蜀,而zskiplistNde則用于表示跳躍表節(jié)點疮丛。
  • 每個跳躍表節(jié)點的層高都是1至32之間的隨機數(shù)。
  • 在同一個跳躍表中辆它,多個節(jié)點可以包含相同的分值誊薄,但每個節(jié)點的成員對象必須是唯一的。
  • 跳躍表中的節(jié)點按照分值大小進行排序锰茉,當(dāng)分值相同時呢蔫,節(jié)點按照成員對象的大小進行排序。

六飒筑、整數(shù)集合

整數(shù)集合:intset.h/intset

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

要點

  • 整數(shù)集合是集合鍵的底層實現(xiàn)之一片吊。
  • 整數(shù)集合的底層實現(xiàn)為數(shù)組,這個數(shù)組以有序协屡、無重復(fù)的方式保存集合元素俏脊,在有需要時,程序會根據(jù)新添加元素的類型肤晓,改變這個數(shù)組的類型爷贫。
  • 升級操作為整數(shù)集合帶來了操作上的靈活性,并且盡可能地節(jié)約了內(nèi)存补憾。
  • 整數(shù)集合只支持升級操作漫萄,不支持降級操作。

七余蟹、壓縮列表

zlbytes zltail zllen entry1 entry2 ... entryN zlend

要點

  • 壓縮列表是一種為節(jié)約內(nèi)存而開發(fā)的順序型數(shù)據(jù)結(jié)構(gòu)卷胯。
  • 壓縮列表被用作列表鍵和哈希鍵的底層實現(xiàn)之一。
  • 壓縮列表可以包含多個節(jié)點威酒,每一個節(jié)點可以保存一個字節(jié)數(shù)組或者整數(shù)值。
  • 添加新節(jié)點到壓縮列表挺峡,或者從壓縮列表刪除節(jié)點葵孤,可能會引發(fā)連鎖更新操作,但這種操作出現(xiàn)的機率并不高橱赠。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末尤仍,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子狭姨,更是在濱河造成了極大的恐慌宰啦,老刑警劉巖苏遥,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異赡模,居然都是意外死亡田炭,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門漓柑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來教硫,“玉大人,你說我怎么就攤上這事辆布∷簿兀” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵锋玲,是天一觀的道長景用。 經(jīng)常有香客問我,道長惭蹂,這世上最難降的妖魔是什么丛肢? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮剿干,結(jié)果婚禮上蜂怎,老公的妹妹穿的比我還像新娘。我一直安慰自己置尔,他們只是感情好杠步,可當(dāng)我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著榜轿,像睡著了一般幽歼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上谬盐,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天甸私,我揣著相機與錄音,去河邊找鬼飞傀。 笑死皇型,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的砸烦。 我是一名探鬼主播弃鸦,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼幢痘!你這毒婦竟也來了唬格?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎购岗,沒想到半個月后汰聋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡喊积,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年烹困,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片注服。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡韭邓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出溶弟,到底是詐尸還是另有隱情女淑,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布辜御,位于F島的核電站鸭你,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏擒权。R本人自食惡果不足惜袱巨,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望碳抄。 院中可真熱鬧愉老,春花似錦、人聲如沸剖效。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽璧尸。三九已至咒林,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間爷光,已是汗流浹背垫竞。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蛀序,地道東北人欢瞪。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像哼拔,于是被迫代替她去往敵國和親引有。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,781評論 2 354