1. SDS:簡單動態字符串(simple dynamic string)算法
Redis沒有直接使用C語言的字符串,而是本身構建了一種名爲簡單動態字符串類型,並將SDS用做Redis的默認字符串。數組
SDS的定義安全
struct sdshdr { // buf 中已佔用空間的長度 int len; // buf 中剩餘可用空間的長度 int free; // 字節數組 char buf[]; };
SDS與C字符串的區別服務器
** 2. 鏈表 **函數
Redis的List(列表)和發佈訂閱,慢查詢,監視器等功能都用到了鏈表性能
鏈表節點實現 adlist.h/listNode結構表示優化
// listNode 雙端鏈表節點 typedef struct listNode { // 前置節點 struct listNode *prev; // 後置節點 struct listNode *next; // 節點的值 void *value; } listNode;
該鏈表爲雙向鏈表,由多個listNode結點組成的鏈表結構圖以下:ui
鏈表實現 adlist.h/list結構表示
// list 雙端鏈表 typedef struct list { // 在c語言中,用結構體的方式來模擬對象是一種常見的手法 // 表頭節點 listNode *head; // 表尾節點 listNode *tail; // 節點值複製函數 void *(*dup)(void *ptr); // 節點值釋放函數 void(*free)(void *ptr); // 節點值對比函數 int(*match)(void *ptr, void *key); // 鏈表所包含的節點數量 unsigned long len; } list;
例:由一個list結構和三個listNode結構組成的鏈表 ![](https://img2020.cnblogs.com/blog/1186190/202102/1186190-20210227163531303-2083424534.jpg) 鏈表提供表頭指針head,表尾指針tail,以及鏈表長度計數器len,和封裝了3個內置函數 1.dup函數:複製鏈表結點所保存的值 2.free函數:釋放鏈表結點所保存的值 3.match函數:對比鏈表結點所保存的值和另外一個輸入值是否相等 這三個函數是用於實現多態鏈表所需的類型特定函數。
Redis鏈表實現特徵總結
1.雙端:獲取某個結點的前驅和後繼結點都是O(1)
2.無環:表頭的prev指針和表尾的next指針都指向NULL,對鏈表的訪問都是以NULL爲終點
3.帶表頭指針和表尾指針:獲取表頭和表尾的複雜度都是O(1)
4.帶鏈表長度計數器:len屬性記錄,獲取鏈表長度O(1)
5.多態:鏈表結點使用void*指針來保存結點的值,而且能夠經過鏈表結構的三個函數爲結點值設置類型特定函數,因此鏈表能夠保存各類不一樣類型的值指針
字典code
哈希節點使用dictEntry結構表示,每一個dictEntry結構都保存着一個鍵值對。
// dictEntry 哈希表節點 typedef struct dictEntry { // 鍵 void *key; // 值 union {//值v的類型能夠是如下三種類型 void *val; uint64_t u64; int64_t s64; } v; // 指向下個哈希表節點,造成鏈表 struct dictEntry *next; } dictEntry;
Redis字典使用哈希表有dictht.h/dictht結構定義
typedef struct dictht { // 哈希表數組, 每一個元素都是一條鏈表 dictEntry **table; // 哈希表大小 unsigned long size; // 哈希表大小掩碼,用於計算索引值 // 老是等於 size - 1 unsigned long sizemask; // 該哈希表已有節點的數量 unsigned long used; } dictht;
![](https://img2020.cnblogs.com/blog/1186190/202102/1186190-20210227171229827-765330912.png)
// dict 字典 typedef struct dict { // 類型特定函數 dictType *type; // type裏面主要記錄了一系列的函數,能夠說是規定了一系列的接口 // 私有數據 void *privdata; // privdata保存了須要傳遞給那些類型特定函數的可選參數 //兩張哈希表 dictht ht[2];//便於漸進式rehash //rehash 索引,並無rehash時,值爲 -1 int rehashidx; //目前正在運行的安全迭代器的數量 int iterators; } dict;
* type 屬性是一個指向dictType結構的指針,每一個dictType結構保存了一族用於操做特定類型鍵值對的函數,Redis爲用途不一樣的字典設置不一樣的類型特定函數。 * privdata 屬性則保存了須要傳遞給那些類型特定函數的可選參數。 * ht是一個包含兩個項的數組,數組每一個項都是一個dictht哈希表,通常狀況下只使用ht[0]哈希表,ht[1]只會對ht[0]哈希表進行rehash時使用。 * rehashidx它記錄了rehash目前的進度,若是目前沒有進行rehash,那麼他的值爲-1.
// dictType 用於操做字典類型函數 typedef struct dictType { // 計算哈希值的函數 unsigned int(*hashFunction)(const void *key); // 複製鍵的函數 void *(*keyDup)(void *privdata, const void *key); // 複製值的函數 void *(*valDup)(void *privdata, const void *obj); // 對比鍵的函數 int(*keyCompare)(void *privdata, const void *key1, const void *key2); // 銷燬鍵的函數 void(*keyDestructor)(void *privdata, void *key); // 銷燬值的函數 void(*valDestructor)(void *privdata, void *obj); } dictType;
![](https://img2020.cnblogs.com/blog/1186190/202102/1186190-20210227171242872-913327145.png)
哈希算法
使用字典類型設置的哈希函數擊視鍵key的哈希值
int hash = dict->type->hashFunction(key)
使用哈希表的sizemask的屬性和哈希值計算出索引值
index = hash & dict->ht[0].sizemask;
使用哈希表節點next指針構成單向鏈表解決哈希衝突。
擴展和收縮哈希表的恭祝經過執行rehash操做來完成步驟以下
若是執行的是擴展操做,那麼擴展ht[1]的大小爲第一個大於等於ht[0].used*2的2的n此冪
若是執行的是收縮操做,那麼收縮ht[1]的大小爲第一個大於等於ht[0].used的2的n此冪
將保存在ht[0]中的全部鍵值對rehash到ht[1]上面:rehash指的是從新計算鍵的哈希值和索引值,而後將鍵值對放置到ht[1]哈希表的指點位置。
當ht[0]包含的全部鍵值對都遷移到ht[1]以後,釋放ht[0],將ht[1]設置爲ht[0],並在ht[1]從新建立一個空哈希表,爲下一次rehash作準備。
當如下條件中的任意一個被知足時,程序會自動開始對哈希表進行擴展操做
1)服務器目前沒有執行BGSAVE命令或者BGREWRITEOF命令,而且哈希表的負載因子大於等於1.
2)服務器目前正在執行BGSAVE命令或者BGREWRITEOF命令,而且哈希表的負載因子大於等於5.
3) 哈希表的負載因子能夠經過公式:load_factor = ht[0].used / ht[0].size;
4) 哈希表的負載因子小於0.1時,自動執行哈希表收縮操做;
若是哈希表中有成千上萬個鍵值對,那麼要一次性rehash到ht[1]的話,可能會致使服務器一段時間內中止服務。爲了不rehash對服務器性能影響,服務器二十分屢次,漸進性的將ht[0]裏面的鍵值對漸進性的rehash。詳細步驟: 1)爲ht[1]分配空間,讓字典同時持有ht[0]和ht[1]兩個哈希表。 2)在字典中維持一個索引計數器變量rehashidx,並將它的值設置爲0,表示rehash工做正式開始。 3) 在rehash進行期間,每次對字典執行添加,刪除,查找或者更新操做時,程序除了執行指定的操做外,還會順帶將ht[0]哈希表在rehashidx索引上的全部鍵值對rehash到ht[1],完成順帶操做後,程序將rehashidx屬性的值加一. 4) 隨着字典操做的不斷執行,最終在某個時間點,ht[0]的全部鍵值對會被rehash至ht[1]。這是將rehashidx設置爲-1.表示rehash操做已執行完。