1. 程式人生 > >【Redis】map實現原理

【Redis】map實現原理

雜湊表是一個很常用的資料結構,不同的平臺對它有不同的實現。下面是redis的實現。

首先是redis中與雜湊表有關的資料結構定義。

節點定義:


這裡有next指標的存在說明這是一個採用拉鍊法構建的雜湊表。key是鍵,值是一個union,可以是以下的任意一種型別:指標,uint64和int64型別。

下面是table的實現:


一個ht結構體就是雜湊表的一個容器陣列。table是一個指標的陣列,每一個元素都指向了一個Entry節點。size是table的大小,sizemask是獲取索引的掩碼,userd是鍵值對的數目。

最後就是整個雜湊表的實現:


雜湊表肯定需要一個table例項存放元素,但是這裡是ht[2],表明有兩個table,這與redis雜湊表的擴容有關。包括最後的trehashids屬性。還有一個type的指標,這個是與多型有關的,redis每一種資料型別都定義了一套方法,這些方法有:


因為不同的型別的這些方法的實現時不同的,比如計算雜湊值就不同,所以要為每一種型別定義樂淘方法。在java裡面,因為是由物件型別或者clas存在的,所以可以很方便地使用多型來達到這個目的,但是在c語言裡面,實現就比較困難。

在不進行rehash的正常情況下,雜湊表裡面只有ht[0]是有用的,ht[1]是空的。

所以雜湊表的這樣的:


redis使用的是murmurHash雜湊演算法。

下面是redis rehash的實現。

大致過程如下:

(1)為ht[1]分配空間,分配空間的大小是第一個大於ht[0].used*2的2的n次方,所以說table的大小是2的n次方,至於為什麼,只要是為了用掩碼算索引是保證均勻性。

(2)把ht[0]裡面的元素全部rehash到ht[1],釋放ht[0]的空間,讓ht[0]指向ht[1],再為ht[1]重新指向一個新的空的table。

其實看到這裡,redis的實現與java的區別並沒有特別大,只不過java裡面沒有設定兩個table,而只是在resize函式裡面直接新建一個區域性的陣列,rehash,再重新讓table指向了新的陣列,大致的過程一樣。

但是,redis在做rehash的具體步驟與java不同。我們知道如果雜湊表的元素非常多,那麼做一次rehash會很耗時,java的hashmap就是一次性做完全部的rehash,肯定會有效能問題,而redis不是一次性rehash的,redis的做法叫做漸進式rehash,把每一個槽位的連結串列的rehash操作平攤到每一次對雜湊表的操作上,其中dict結構中trehashidx就是指向了下一個需要rehash的槽位的索引,只要處於rehash的過程中,那麼這個屬性就是大於等於0的,如果沒有處於rehash的過程,該值是-1。每rehash完一個槽位,那麼該值會自增。在rehash的過程中,如果trehashidx所指向的槽位沒有值,那麼會去遞迴地尋找下一個trehashidx指向槽位不空的槽位。


這就是redis雜湊表實現上rehash的效能很高的原因。