Redis原始碼研究 雜湊表

2021-07-03 01:51:07 字數 3172 閱讀 4531

前面提到

redis是個key/value儲存系統,學過資料結構的人都知道,key/value最簡單的資料結果就是雜湊表(當然,還有其他方式,如b-樹,二叉平衡樹等),hash表的效能取決於兩個因素:hash表的大小和解決衝突的方法。這兩個是矛盾的:hash表大,則衝突少,但是用記憶體過大;而hash錶小,則記憶體使用少,但衝突多,效能低。乙個好的hash表會權衡這兩個因素,使記憶體使用量和效能均盡可能低。在redis中,雜湊表是所有其他資料結構的基礎,對於其他所有資料結構,如:string,set,sortedset,均是儲存到hash表中的value中的,這個可以很容易的通過設定value的型別為void*做到。本文詳細介紹了redis中hash表的設計思想和實現方法。

【注】 本文的源**分析是基於redis-2.4.3版本的。

下圖是從**《redis記憶體儲存結構分析》

中摘得的,主要描述redis中hash表的組織方式。

在redis中,hash表被稱為字典(dictionary),採用了典型的鏈式解決衝突方法,即:當有多個key/value的key的對映值(每對key/value儲存之前,會先通過類似hash(key) mod n的方法計算乙個值,以便確定其對應的hash table的位置)相同時,會將這些value以單鏈表的形式儲存;同時為了控制雜湊表所佔記憶體大小,redis採用了雙雜湊表(ht[2])結構,並逐步擴大雜湊表容量(桶的大小)的策略,即:剛開始,雜湊表ht[0]的桶大小為4,雜湊表ht[1]的桶大小為0,待衝突嚴重(redis有一定的判斷條件)後,ht[1]中桶的大小增為ht[0]的兩倍,並逐步(注意這個詞:」逐步」)將雜湊表ht[0]中元素遷移(稱為「再次hash」)到ht[1],待ht[0]中所有元素全部遷移到ht[1]後,再將ht[1]交給ht[0](這裡僅僅是c語言位址交換),之後重複上面的過程。

3.1  基本資料結構

redis雜湊表的實現位於檔案dict.h和dict.c中,主要資料結構如下:

//hash表結構

typedef struct dictht dictht;

//hash表結構,含有兩個hash表,以實現增量再hash演算法。

typedef struct dict dict;

//hash表中每一項key/value,若key的對映值,以單鏈表的形式儲存

typedef struct dictentry dictentry;

//每種hash table的型別,裡面既有成員函式,又有成員變數,完全是模擬的c++類,注意,每個函式帶有的privdata均為預留引數

typedef struct dicttype dicttype;

3.2  基本操作

redis中hash table主要有以下幾個對外提供的介面:dictcreate、dictadd、dictreplace、dictdelete、dictfind、dictempty等,而這些介面呼叫了一些基礎操作,包括:_dictrehashstep,_dictkeyindex等。下面分析一下_dictrehashstep函式:

該函式主要完成rehash操作。hash table在一定情況下會觸發rehash操作,即:將第乙個hash table中的資料逐步轉移到第二個hash table中。

觸發條件

_dictexpandifneeded()

if (d->ht[0].used >= d->ht[0].size &&

(dict_can_resize ||

d->ht[0].used/d->ht[0].size > dict_force_resize_ratio))

當第乙個表的元素數目大於桶數目且元素數目與桶數目比值大於5時,hash 表就會擴張,擴大後新錶的大小為舊表的2倍。

轉移策略

為了避免一次性轉移帶來的開銷,redis採用了平攤開銷的策略,即:將轉移代價平攤到每個基本操作中,如:dictadd、dictreplace、dictfind中,每執行一次這些基本操作會觸發乙個桶中元素的遷移操作。在此,有讀者可能會問,如果這樣的話,如果舊hash table非常大,什麼時候才能遷移完。為了提高前移速度,redis有乙個週期性任務servercron,每隔一段時間會遷移100個桶。

int dictrehashmilliseconds(dict *d, int ms) 

return rehashes;

}

下面分析一下dictadd函式:

首先,檢查hash table是否正在rehash操作,如果是,則分攤乙個rehash開銷:

if (dictisrehashing(d)) _dictrehashstep(d);
然後,檢查該key/value的key是否已經存在,如果存在,則直接返回:

if ((index = _dictkeyindex(d, key)) == -1)

return dict_err;

需要注意的是,決定是否需要進行rehash是在查詢操作(_dictkeyindex)中順便做的:

//_dictkeyindex()

if (_dictexpandifneeded(d) == dict_err)

return -1;

接著,會通過hash演算法定位該key的位置,並建立乙個dictentry節點,插入到對應單鏈表中:

entry = zmalloc(sizeof(*entry));

entry->next = ht->table[index];

ht->table[index] = entry;

ht->used++;

最後將key/value對填充到該entry中:

dictsethashkey(d, entry, key);

dictsethashval(d, entry, val);

這就是整個dictadd函式的流程。其他操作類似,均是剛開始分攤rehash開銷(如果需要),然後通過hash方法定位位置,並進行相應的邏輯操作。

,作者介紹:

本部落格的文章集合:

Redis原始碼研究 Redis的RESP協議

redis客戶端和服務端互動使用的是redis作者制定的乙個協議,叫resp redis serialization protocol 具體分如下幾個層次 客戶端發給服務端的命令都會序列化為array,而服務端返回給客戶端的可以為如上任意一種型別,各簡單舉例如下 具體介紹參考 請求響應模式有兩種特殊...

Redis原始碼分析(三) dict雜湊結構

昨天分析完adlist的redis 今天馬上馬不停蹄的繼續學習redis 中的雜湊部分的結構學習,不過在這裡他不叫什麼hashmap,而是叫dict,而且是一種全新設計的一種雜湊結構,他只是通過幾個簡單的結構體,再搭配上一些比較常見的雜湊演算法,就實現了類似高階語言中hashmap的作用了。也讓我見...

redis原始碼解析 跳躍表

定義 跳躍表是一種有序資料結構,它通過在每個節點中維護多個指向其他節點的指標,從而達到快速訪問節點的目的。跳躍表支援平均o logn 最壞o n 複雜度的節點查詢,大部分情況下,跳躍表的效率可以和平衡樹相媲美,並且因為跳躍表的實現比平衡樹要來得簡單,所以有不少程式都使用跳躍表來替代平衡樹。從圖中可以...