redis 漸進式哈希

上一節說過, 擴展或收縮哈希表需要將 ht[0] 裏面的所有鍵值對 rehash 到 ht[1] 裏面, 但是, 這個 rehash 動作並不是一次性、集中式地完成的, 而是分多次、漸進式地完成的。

這樣做的原因在於, 如果 ht[0] 裏只保存着四個鍵值對, 那麼服務器可以在瞬間就將這些鍵值對全部 rehash 到 ht[1] ; 但是, 如果哈希表裏保存的鍵值對數量不是四個, 而是四百萬、四千萬甚至四億個鍵值對, 那麼要一次性將這些鍵值對全部 rehash 到 ht[1] 的話, 龐大的計算量可能會導致服務器在一段時間內停止服務。

因此, 爲了避免 rehash 對服務器性能造成影響, 服務器不是一次性將 ht[0] 裏面的所有鍵值對全部 rehash 到 ht[1] , 而是分多次、漸進式地將 ht[0] 裏面的鍵值對慢慢地 rehash 到 ht[1] 。

以下是哈希表漸進式 rehash 的詳細步驟:

  1. 爲 ht[1] 分配空間, 讓字典同時持有 ht[0] 和 ht[1] 兩個哈希表。
  2. 在字典中維持一個索引計數器變量 rehashidx , 並將它的值設置爲 0 , 表示 rehash 工作正式開始。
  3. 在 rehash 進行期間, 每次對字典執行添加、刪除、查找或者更新操作時, 程序除了執行指定的操作以外, 還會順帶將 ht[0] 哈希表在 rehashidx 索引上的所有鍵值對 rehash 到 ht[1] , 當 rehash 工作完成之後, 程序將 rehashidx 屬性的值增一。
  4. 隨着字典操作的不斷執行, 最終在某個時間點上, ht[0] 的所有鍵值對都會被 rehash 至 ht[1] , 這時程序將 rehashidx 屬性的值設爲 -1, 表示 rehash 操作已完成。

漸進式 rehash 的好處在於它採取分而治之的方式, 將 rehash 鍵值對所需的計算工作均灘到對字典的每個添加、刪除、查找和更新操作上, 從而避免了集中式 rehash 而帶來的龐大計算量。

圖 4-12 至圖 4-17 展示了一次完整的漸進式 rehash 過程, 注意觀察在整個 rehash 過程中, 字典的 rehashidx 屬性是如何變化的。

digraph {    label = "\n 圖 4-12    準備開始 rehash";    rankdir = LR;    node [shape = record];    // 字典    dict [label = " <head> dict | type | privdata | <ht> ht | rehashidx \n -1 "];    // 哈希表    dictht0 [label = " <head> dictht | <table> table | <size> size \n 4 | <sizemask> sizemask \n 3 | <used> used \n 4"];    dictht1 [label = " <head> dictht | <table> table | <size> size \n 8 | <sizemask> sizemask \n 7 | <used> used \n 0"];    table0 [label = " <head> dictEntry*[4] | <0> 0 | <1> 1 | <2> 2 | <3> 3 "];    table1 [label = " <head> dictEntry*[8] | <0> 0 | <1> 1 | <2> 2 | ... | <7> 7 "];    // 哈希表節點    kv0 [label = " <head> dictEntry | { k0 | v0 } "];    kv1 [label = " <head> dictEntry | { k1 | v1 } "];    kv2 [label = " <head> dictEntry | { k2 | v2 } "];    kv3 [label = " <head> dictEntry | { k3 | v3 } "];    //    node [shape = plaintext, label = "NULL"];    //    dict:ht -> dictht0:head [label = "ht[0]"];    dict:ht -> dictht1:head [label = "ht[1]"];    dictht0:table -> table0:head;    dictht1:table -> table1:head;    table0:0 -> kv2:head -> null0;    table0:1 -> kv0:head -> null1;    table0:2 -> kv3:head -> null2;    table0:3 -> kv1:head -> null3;    table1:0 -> null10;    table1:1 -> null11;    table1:2 -> null12;    table1:7 -> null17;}

digraph {    label = "\n 圖 4-13    rehash 索引 0 上的鍵值對";    rankdir = LR;    node [shape = record];    // 字典    dict [label = " <head> dict | type | privdata | <ht> ht | rehashidx \n 0 "];    // 哈希表    dictht0 [label = " <head> dictht | <table> table | <size> size \n 4 | <sizemask> sizemask \n 3 | <used> used \n 3"];    dictht1 [label = " <head> dictht | <table> table | <size> size \n 8 | <sizemask> sizemask \n 7 | <used> used \n 1"];    table0 [label = " <head> dictEntry*[4] | <0> 0 | <1> 1 | <2> 2 | <3> 3 "];    table1 [label = " <head> dictEntry*[8] | ... | <4> 4 | ... "];    // 哈希表節點    kv0 [label = " <head> dictEntry | { k0 | v0 } "];    kv1 [label = " <head> dictEntry | { k1 | v1 } "];    kv2 [label = " <head> dictEntry | { k2 | v2 } "];    kv3 [label = " <head> dictEntry | { k3 | v3 } "];    //    node [shape = plaintext, label = "NULL"];    //    dict:ht -> dictht0:head [label = "ht[0]"];    dict:ht -> dictht1:head [label = "ht[1]"];    dictht0:table -> table0:head;    dictht1:table -> table1:head;    table0:0 -> null0;    table0:1 -> kv0:head -> null1;    table0:2 -> kv3:head -> null2;    table0:3 -> kv1:head -> null3;    table1:4 -> kv2:head -> null14}

digraph {    label = "\n 圖 4-14    rehash 索引 1 上的鍵值對";    rankdir = LR;    node [shape = record];    // 字典    dict [label = " <head> dict | type | privdata | <ht> ht | rehashidx \n 1 "];    // 哈希表    dictht0 [label = " <head> dictht | <table> table | <size> size \n 4 | <sizemask> sizemask \n 3 | <used> used \n 2"];    dictht1 [label = " <head> dictht | <table> table | <size> size \n 8 | <sizemask> sizemask \n 7 | <used> used \n 2"];    table0 [label = " <head> dictEntry*[4] | <0> 0 | <1> 1 | <2> 2 | <3> 3 "];    table1 [label = " <head> dictEntry*[8] | ... | <4> 4 | <5> 5 | ... "];    // 哈希表節點    kv0 [label = " <head> dictEntry | { k0 | v0 } "];    kv1 [label = " <head> dictEntry | { k1 | v1 } "];    kv2 [label = " <head> dictEntry | { k2 | v2 } "];    kv3 [label = " <head> dictEntry | { k3 | v3 } "];    //    node [shape = plaintext, label = "NULL"];    //    dict:ht -> dictht0:head [label = "ht[0]"];    dict:ht -> dictht1:head [label = "ht[1]"];    dictht0:table -> table0:head;    dictht1:table -> table1:head;    table0:0 -> null0;    table0:1 -> null1;    table0:2 -> kv3:head -> null2;    table0:3 -> kv1:head -> null3;    table1:4 -> kv2:head -> null14    table1:5 -> kv0:head -> null15;}

digraph {    label = "\n 圖 4-15    rehash 索引 2 上的鍵值對";    rankdir = LR;    node [shape = record];    // 字典    dict [label = " <head> dict | type | privdata | <ht> ht | rehashidx \n 2 "];    // 哈希表    dictht0 [label = " <head> dictht | <table> table | <size> size \n 4 | <sizemask> sizemask \n 3 | <used> used \n 1"];    dictht1 [label = " <head> dictht | <table> table | <size> size \n 8 | <sizemask> sizemask \n 7 | <used> used \n 3"];    table0 [label = " <head> dictEntry*[4] | <0> 0 | <1> 1 | <2> 2 | <3> 3 "];    table1 [label = " <head> dictEntry*[8] | ... | <1> 1 | ... | <4> 4 | <5> 5 | ... "];    // 哈希表節點    kv0 [label = " <head> dictEntry | { k0 | v0 } "];    kv1 [label = " <head> dictEntry | { k1 | v1 } "];    kv2 [label = " <head> dictEntry | { k2 | v2 } "];    kv3 [label = " <head> dictEntry | { k3 | v3 } "];    //    node [shape = plaintext, label = "NULL"];    //    dict:ht -> dictht0:head [label = "ht[0]"];    dict:ht -> dictht1:head [label = "ht[1]"];    dictht0:table -> table0:head;    dictht1:table -> table1:head;    table0:0 -> null0;    table0:1 -> null1;    table0:2 -> null2;    table0:3 -> kv1:head -> null3;    table1:1 -> kv3:head -> null11;    table1:4 -> kv2:head -> null14    table1:5 -> kv0:head -> null15;}

digraph {    label = "\n 圖 4-16    rehash 索引 3 上的鍵值對";    rankdir = LR;    node [shape = record];    // 字典    dict [label = " <head> dict | type | privdata | <ht> ht | rehashidx \n 3 "];    // 哈希表    dictht0 [label = " <head> dictht | <table> table | <size> size \n 4 | <sizemask> sizemask \n 3 | <used> used \n 0"];    dictht1 [label = " <head> dictht | <table> table | <size> size \n 8 | <sizemask> sizemask \n 7 | <used> used \n 4"];    table0 [label = " <head> dictEntry*[4] | <0> 0 | <1> 1 | <2> 2 | <3> 3 "];    table1 [label = " <head> dictEntry*[8] | ... | <1> 1 | ... | <4> 4 | <5> 5 | ... | <7> 7 "];    // 哈希表節點    kv0 [label = " <head> dictEntry | { k0 | v0 } "];    kv1 [label = " <head> dictEntry | { k1 | v1 } "];    kv2 [label = " <head> dictEntry | { k2 | v2 } "];    kv3 [label = " <head> dictEntry | { k3 | v3 } "];    //    node [shape = plaintext, label = "NULL"];    //    dict:ht -> dictht0:head [label = "ht[0]"];    dict:ht -> dictht1:head [label = "ht[1]"];    dictht0:table -> table0:head;    dictht1:table -> table1:head;    table0:0 -> null0;    table0:1 -> null1;    table0:2 -> null2;    table0:3 -> null3;    table1:1 -> kv3:head -> null11;    table1:4 -> kv2:head -> null14    table1:5 -> kv0:head -> null15;    table1:7 -> kv1:head -> null17;}

digraph {    label = "\n 圖 4-17    rehash 執行完畢";    rankdir = LR;    node [shape = record];    // 字典    dict [label = " <head> dict | type | privdata | <ht> ht | rehashidx \n -1 "];    // 哈希表    dictht0 [label = " <head> dictht | <table> table | <size> size \n 8 | <sizemask> sizemask \n 7 | <used> used \n 4"];    dictht1 [label = " <head> dictht | <table> table | <size> size \n 0 | <sizemask> sizemask \n 0 | <used> used \n 0"];    table0 [label = " <head> dictEntry*[8] | ... | <1> 1 | ... | <4> 4 | <5> 5 | ... | <7> 7 "];    table1 [label = "NULL", shape = plaintext];    // 哈希表節點    kv0 [label = " <head> dictEntry | { k0 | v0 } "];    kv1 [label = " <head> dictEntry | { k1 | v1 } "];    kv2 [label = " <head> dictEntry | { k2 | v2 } "];    kv3 [label = " <head> dictEntry | { k3 | v3 } "];    //    node [shape = plaintext, label = "NULL"];    //    dict:ht -> dictht0:head [label = "ht[0]"];    dict:ht -> dictht1:head [label = "ht[1]"];    dictht0:table -> table0:head;    dictht1:table -> table1;    table0:1 -> kv3:head -> null11;    table0:4 -> kv2:head -> null14;    table0:5 -> kv0:head -> null15;    table0:7 -> kv1:head -> null17;}

漸進式 rehash 執行期間的哈希表操作

因爲在進行漸進式 rehash 的過程中, 字典會同時使用 ht[0] 和 ht[1] 兩個哈希表, 所以在漸進式 rehash 進行期間, 字典的刪除(delete)、查找(find)、更新(update)等操作會在兩個哈希表上進行: 比如說, 要在字典裏面查找一個鍵的話, 程序會先在 ht[0] 裏面進行查找, 如果沒找到的話, 就會繼續到 ht[1] 裏面進行查找, 諸如此類。

另外, 在漸進式 rehash 執行期間, 新添加到字典的鍵值對一律會被保存到 ht[1] 裏面, 而 ht[0] 則不再進行任何添加操作: 這一措施保證了 ht[0] 包含的鍵值對數量會只減不增, 並隨着 rehash 操作的執行而最終變成空表。



原文:http://redisbook.com/preview/dict/incremental_rehashing.html

https://www.jianshu.com/p/9c84856cd5c0

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章