1、文檔介紹html
本文僅做爲本人讀書筆記使用,不對其中內容作解釋,記錄以本人能夠看懂爲標準redis
原書連接:https://redisbook.readthedocs.io/en/latest/index.html算法
該書以簡明的方式主要介紹了Redis內部的運行機制,從數據結構到服務器構造,值得推薦數據庫
第一部分 內部數據結構數組
Redis內存所使用的數據結構與算法緩存
1、SDS(Simple Dynamic String) 簡單動態字符串安全
1. 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. 雙端鏈表做用
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. 字典的做用
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如何實現二進制安全的?
跳躍表與平衡樹比較?