redisbook筆記——redis內部資料結構

來源:互聯網
上載者:User

標籤:

在Redis的內部,資料結構類型值由高效的資料結構和演算法進行支援,並且在Redis自身的構建當中,也大量用到了這些資料結構。

這一部分將對Redis記憶體所使用的資料結構和演算法進行介紹。

動態字串

Sds(Simple Dynamic String,簡單動態字串)

Sds在Redis中的主要作用有以下兩個:

1. 實現字串對象(StringObject);

2. 在Redis程式內部用作char* 類型的替代品;

對比C 字串,sds有以下特性:

可以高效地執行長度計算(strlen);

可以高效地執行追加操作(append);

二進位安全;

•sds會為追加操作進行最佳化:加快追加操作的速度,並降低記憶體配置的次數,代價是多佔用了一些記憶體,而且這些記憶體不會被主動釋放。

typedefchar *sds;

structsdshdr {

// buf已佔用長度

intlen;

// buf剩餘可用長度

intfree;

// 實際儲存字串資料的地方

charbuf[];

};

# 如果新字串的總長度小於SDS_MAX_PREALLOC

# 那麼為字串分配2 倍於所需長度的空間

# 否則就分配所需長度加上SDS_MAX_PREALLOC 數量的空間

雙端鏈表

大部分C 程式都會自己實現一種鏈表類型,Redis也不例外。雙端鏈表還是Redis清單類型的底層實現之一

Note: Redis列表使用兩種資料結構作為底層實現:

1. 雙端鏈表

2. 壓縮列表

因為雙端鏈表佔用的記憶體比壓縮列表要多,所以當建立新的列表鍵時,列表會優先考慮

使用壓縮列表作為底層實現,並且在有需要的時候,才從壓縮列表實現轉換到雙端鏈表實現。

除了實現清單類型以外,雙端鏈表還被很多Redis內部模組所應用:

•事務模組使用雙端鏈表來按順序儲存輸入的命令;

•伺服器模組使用雙端鏈表來儲存多個用戶端;

•訂閱/發送模組使用雙端鏈表來儲存訂閱模式的多個用戶端;

•事件模組使用雙端鏈表來儲存時間事件(time event);

 

typedefstructlist {

// 表頭指標

listNode*head;

// 表尾指標

listNode*tail;

// 節點數量

unsigned long len;

// 複製函數

void*(*dup)(void *ptr);

// 釋放函數

void(*free)(void *ptr);

// 比對函數

int(*match)(void *ptr, void *key);

} list;

Redis為雙端鏈表實現了一個迭代器,這個迭代器可以從兩個方向對雙端鏈表進行迭代:

雙端鏈表及其節點的效能特性如下:

節點帶有前驅和後繼指標,訪問前驅節點和後繼節點的複雜度為O(1) ,並且對鏈表

的迭代可以在從表頭到表尾和從表尾到表頭兩個方向進行;

鏈錶帶有指向表頭和表尾的指標,因此對錶頭和表尾進行處理的複雜度為O(1) ;

鏈錶帶有記錄節點數量的屬性,所以可以在O(1) 複雜度內返回鏈表的節點數量(長

度);

字典

字典(dictionary),又名映射(map)或關聯陣列(associative array),在Redis中的應用廣泛,使用頻率可以說和SDS 以及雙端鏈表不相上下,基本上各個功能模組都有用到字典的地方。

其中,字典的主要用途有以下兩個:

1. 實現資料庫鍵空間(key space);

2. 用作Hash 類型鍵的其中一種底層實現;

以下兩個小節分別介紹這兩種用途。

Redis的Hash 類型鍵使用以下兩種資料結構作為底層實現:

1. 字典;

2. 壓縮列表;

因為壓縮列表比字典更節省記憶體,所以程式在建立新Hash 鍵時,預設使用壓縮列表作為底層實現,當有需要時,程式才會將底層實現從壓縮列錶轉換到字典。

Redis選擇了高效且實現簡單的雜湊表作為字典的底層實現。

/*

* 字典

**每個字典使用兩個雜湊表,用於實現漸進式rehash

*/

typedefstructdict {

// 特定於類型的處理函數

dictType*type;

// 類型處理函數的私人資料

void*privdata;

// 雜湊表(2 個)

dicththt[2];

// 記錄rehash 進度的標誌,值為-1 表示rehash 未進行

intrehashidx;

// 當前正在運作的安全迭代器數量

intiterators;

} dict;

雜湊表實現

字典所使用的雜湊表實現由dict.h/dictht類型定義:

/*

* 雜湊表

*/

typedefstructdictht {

// 雜湊表節點指標數組(俗稱桶,bucket

dictEntry**table;

// 指標數組的大小

unsigned long size;

// 指標數組的長度掩碼,用於計算索引值

unsigned long sizemask;

// 雜湊表現有的節點數量

unsigned long used;

} dictht;

每個dictEntry都儲存著一個索引值對,以及一個指向另一個dictEntry結構的指標:

/*

* 雜湊表節點

*/

typedefstructdictEntry {

//

void*key;

//

union{

void*val;

uint64_t u64;

int64_t s64;

} v;

// 鏈往後繼節點

structdictEntry*next;

} dictEntry;

 

Redis目前使用兩種不同的雜湊演算法:

1. MurmurHash2 32 bit 演算法:這種演算法的分布率和速度都非常好,具體資訊請參考MurmurHash的首頁:http://code.google.com/p/smhasher/ 。

2. 基於djb演算法實現的一個大小寫無關散列演算法:具體資訊請參考

http://www.cse.yorku.ca/~oz/hash.html 。

字典雜湊表所使用的碰撞解決方案被稱之為鏈地址法:

字典收縮和字典擴充的一個區別是:

•字典的擴充操作是自動觸發的(不管是自動擴充還是強制擴充);

•而字典的收縮操作則是由程式手動執行。

字典由索引值對構成的抽象資料結構。

•Redis中的資料庫和雜湊鍵都基於字典來實現。

•Redis字典的底層實現為雜湊表,每個字典使用兩個雜湊表,一般情況下只使用0 號雜湊表,只有在rehash 進行時,才會同時使用0 號和1 號雜湊表。

•雜湊表使用鏈地址法來解決鍵衝突的問題。

• Rehash 可以用於擴充或收縮雜湊表。

•對雜湊表的rehash 是分多次、漸進式地進行的。

跳躍表

它的效率可以和平衡樹媲美——尋找、刪除、添加等操作都可以在對數期望時間下完成,

並且比起平衡樹來說,跳躍表的實現要簡單直觀得多。

 

•表頭(head):負責維護跳躍表的節點指標。

•跳躍表節點:儲存著元素值,以及多個層。

•層:儲存著指向其他元素的指標。高層的指標越過的元素數量大於等於低層的指標,為了提高尋找的效率,程式總是從高層先開始訪問,然後隨著元素值範圍的縮小,慢慢降低層次。

•表尾:全部由NULL 組成,表示跳躍表的末尾。

看圖想象:

1) 尋找簡單:比如要尋找5,第一層沒找到,第二層定位4—6之間,再降一層則找到5。

2) 插入演算法呢?還是不太確定怎麼實現

 

跳躍表在Redis的唯一作用,就是實現有序集資料類型。

跳躍表將指向有序集的score 值和member 域的指標作為元素,並以score 值為索引,對有序集元素進行排序。

為了適應自身的需求,Redis基於William Pugh 論文中描述的跳躍表進行了修改,包括:1. score 值可重複。

2. 對比一個元素需要同時檢查它的score 和memeber。

3. 每個節點帶有高度為1 層的後退指標,用於從表尾方向向表頭方向迭代。

redisbook筆記——redis內部資料結構

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.