《redis設計與實現》初版 閱讀筆記(未看完)

1、文檔介紹html

本文僅做爲本人讀書筆記使用,不對其中內容作解釋,記錄以本人能夠看懂爲標準redis

原書連接:https://redisbook.readthedocs.io/en/latest/index.html算法

該書以簡明的方式主要介紹了Redis內部的運行機制,從數據結構到服務器構造,值得推薦數據庫

                                                                                      第一部分  內部數據結構數組

Redis內存所使用的數據結構與算法緩存

1、SDS(Simple Dynamic String) 簡單動態字符串安全

1. SDS做用服務器

  • 實現字符串對象。Redis內字符串對象並不表明就是字符串值,字符串對象還能夠保存lang類型的值,包含字符串的字符串對象包含的纔是SDS值
  • 取代C默認的char*類型。char*類型有不少限制,好比說數據追加與長度計算。在Redis內,客戶端傳遞給服務器的aof緩存、協議內容、回覆等都是SDS類型的存儲

2. Redis中的字符串數據結構

Redis內的字符串不只包含\0結尾的字符串,還包含簡單的字節數組,還包括其餘格式的數據等內容app

Redi使用SDS類替換C語言默認字符串考慮到兩點:高效、二進制安全(程序不對字符串裏面保存的數據進行任何假設)

3. SDS實現

typedef char * sds;

struct sdshdr { len = 11; free = 0; buf = "hello world\0"; // buf 的實際長度爲 len + 1 };

sds 是 char* 的一個別名

結構體裏包含了三個屬性:len、free、buffer三個屬性

經過len屬性能夠O(1)的進行長度計算;經過buf分配額外的空間,並使用free記錄未使用空間的大小,sds可讓執行追加操做所需的內存重分配次數大大減小;在char *實現中,追加只能經過重分配內存實現

於此同時對SDS的操做必須正確處理len與free屬性

4. 如何減小內存重分配次數

建立:當調用set命令建立SDSHDR時,BUF很少申請,恰好給夠所需的,free=0

追加:當再次追加的時候,若是free的長度大於所需,就直接append進去,否則的話,會給二倍的內存,可是若是大於SDSHDR容許的MAX_Preallocation,最大預分配,不會翻倍,會再給一個MAX_Preallocation

釋放時間:當鍵值被刪除時預分配空間會被刪除;當重啓Redis時,預分配的空間也會被釋放,每一個SDS對應的SDSHDR不存在預分配空間,BUFF大小等於所需空間

SDS是Redis對應的字符串表示,SDSHDR是對應的存儲類型

2、雙端鏈表

1. 雙端鏈表做用

  • 雙端鏈表是Redis列表(List)結構的底層實現之一,另外一個是壓縮列表,由於壓縮列表佔用的額內存更少,在須要的時候纔會從壓縮列表轉換爲雙端鏈表
  • 事務模塊使用雙端鏈表依序保存輸入的命令
  • 服務器模塊使用雙端鏈表來保存多個客戶端
  • 訂閱/發送模塊使用雙端鏈表來保存訂閱模式的多個客戶端
  • 事件模塊使用雙端鏈表來保存時間事件(time event)

2. 雙端鏈表的實現

 雙端鏈表是由兩部分組成的,list與listNode

typedef struct list {

    // 表頭指針
    listNode *head;

    // 表尾指針
    listNode *tail;

    // 節點數量
    unsigned long len;          //屬性

    // 複製函數
    void *(*dup)(void *ptr);
    // 釋放函數
    void (*free)(void *ptr);
    // 比對函數
    int (*match)(void *ptr, void *key);        //方法
} list;

listNode的value值的類型是void *,方法返回值的類型也是void *,表明對值得類型不作限制

3. 迭代器

typedef struct listIter {

    // 下一節點
    listNode *next;

    // 迭代方向
    int direction;

} listIter;

迭代器內保存一個listNode,而且指明迭代的方向

3、字典

 1. 字典的做用

  • 實現數據庫鍵空間
  • 用做hash類型鍵的底層實現之一

Redis是一個鍵值對數據庫,數據庫中的鍵值對由字典保存,每一個數據庫都有一個字典,這個字典稱爲鍵空間(Key Space),當用戶添加一個鍵值對到數據庫中時,不管鍵值對是什麼類型,程序就會將該鍵值對添加到鍵空間

hash類型鍵的底層實現除了字典外就是壓縮列表

2. 字典的實現

字典的實現方式有不少種,例如鏈表與數組,優勢:簡單   缺點:只適用於元素很少的狀況

                                          哈希表,      優勢:高效簡單

                                          平衡樹,      優勢:穩定,排序操做更高效  缺點:實現更復雜

Redis採用哈希表實現字典,哈希表的子結構是dictEntry

dictEntry的實現

/*
 * 哈希表節點
 */
typedef struct dictEntry {

    // 鍵
    void *key;

    // 值
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
    } v;          //union關鍵字 三種中只能包含其中一種

    // 鏈日後繼節點
    struct dictEntry *next;

} dictEntry;

next指針指向另外一個dircEntry節點,之因此存在鏈是由於有的鍵值可能會哈希成同一值,因而採用鏈地址法來處理哈希值碰撞問題,當不一樣的鍵擁有相同的哈希值時,哈希表就將這些鍵連接起來

dircht(dirc hash table)的實現

/*
 * 哈希表
 */
typedef struct dictht {

    // 哈希表節點指針數組(俗稱桶,bucket)
    dictEntry **table;

    // 指針數組的大小
    unsigned long size;

    // 指針數組的長度掩碼,用於計算索引值
    unsigned long sizemask;

    // 哈希表現有的節點數量
    unsigned long used;

} dictht;

**table是一個數組,俗稱桶(bucket),每個值對應一個dictEntry結構的指針

size表明數組大小,sizemask意思不清楚,used就是哈希表現有的鍵的數量

字典的定義

/*
 * 字典
 *
 * 每一個字典使用兩個哈希表,用於實現漸進式 rehash
 */
typedef struct dict {

    // 特定於類型的處理函數
    dictType *type;

    // 類型處理函數的私有數據
    void *privdata;

    // 哈希表(2 個)
    dictht ht[2];

    // 記錄 rehash 進度的標誌,值爲 -1 表示 rehash 未進行
    int rehashidx;

    // 當前正在運做的安全迭代器數量
    int iterators;

} dict;

字典的實現使用了兩個hash table,0號哈希表是字典主要使用的哈希表,1號哈希表只有當程序對0號哈希表進行rehash的時候纔會使用

3. rehash

rehash:當鍵很是多,遠大於table數組長度時,數組內的每一個值將退化成一條鏈,hash Table的優點將不復存在,因而須要進行rehash操做,對hash table進行擴容,將比率儘可能維持在1:1左右

rehash觸發的條件有兩種:1. 鍵與數組長度比率ratio>=1 && dict_can_resize爲真

                                    2. ratio>=dict_force_resize_ratio dict_force_resize_ratio是強制改變大小的比率

當數據庫執行後臺持久化任務時,爲了最大化利用系統的copy on write機制,程序會暫時將dict_can_resize置爲假,避免執行天然resize,總而言之就是爲了效率

4. 字典的收縮

與rehash相反

收縮操做是程序手動執行的,擴展操做是自動執行的,收縮程序決定填充率是多少的時候來執行收縮程序

對哈希表的擴展和收縮都是分屢次、漸進式的進行的

4、跳躍表

跳躍表是一個有層次的鏈表,增刪改查的時間複雜度都是O(logN)

跳躍表解釋:http://blog.jobbole.com/111731/ 

2019-03-07  11:13:52  有時間再看吧 得有輸出才行啊 乾點活吧

跳躍表是爲了提升鏈表的增刪改查,對於一個鏈表來說,選出一些領導者在上一層,因而在增刪改查的時候,首先在上一層進行選擇區間以後再下沉到下一層,提升了效率

1. 跳躍表的實現

跳躍表的實現

typedef struct zskiplist {

    // 頭節點,尾節點
    struct zskiplistNode *header, *tail;

    // 節點數量
    unsigned long length;

    // 目前表內節點的最大層數
    int level;

} zskiplist;

 跳躍表節點(層)的實現

typedef struct zskiplistNode {

    // member 對象
    robj *obj;

    // 分值
    double score;

    // 後退指針
    struct zskiplistNode *backward;

    // 層
    struct zskiplistLevel {

        // 前進指針
        struct zskiplistNode *forward;

        // 這個層跨越的節點數量
        unsigned int span;

    } level[];

} zskiplistNode;

 

Q&A:

爲何Redis要使用C而不是C++的STL容器?

猜測:多是基於內存或者效率的考慮吧

SDS如何實現二進制安全的?

跳躍表與平衡樹比較?

相關文章
相關標籤/搜索