多級hash

我在以前的博客《sdk的windows版本點擊打開鏈接 中稍微的介紹了一些多級hash的信息,現在儘自己所知詳盡地予以介紹。

現在的服務器網絡數據收發與存儲沒有不做緩存的,不做緩存不能發揮機器的高性能。一般地,公司內部都有集中固定型號的服務器,每種都有其性能極限,一般包括但不限於CPU計算能力、網卡收發包能力、內存容量以及磁盤容量和他的讀寫速度。多級hash即與內存容量有關。假設一臺dell服務器內存是8G,如果用多級hash做緩存,那麼就可以考慮給緩存的容量是6G,即一般內存容量的3 / 4 或者 2 / 3考慮用作緩存的極大容量。某種服務器爲某種業務服務,簡單的認爲業務的值是key-value形式,一般地,key肯定有固定大小Ksize,value最大值也有極限值Vsize_max。


由上面的一段文字,如果把多級hash作爲服務器的緩存,你應該明白這個緩存有兩個特點:第一,緩存有極限容量;第二,每個key-value元素有固定大小(Ksize + Vsize_max)。


如果不考慮多級hash,只考慮上面兩個特點,數據結構中最簡單的hash表就可以勝任,不是麼?不考慮在大內存中CPU對不懂緩存單元的尋址並把數據load到CPU cache中的時間差異(局部性原理),即認爲在任何時刻CPU對緩存所有單元的訪問速度是一個常數,hash表確實是極爲完美的。


一個簡單的數組型hash表確實完美,但是hash表天生缺陷之一就是存儲位置衝突。不考慮不同的值計算出同一個key值,我們認爲不同的值有不同的key。若一個hash表的元素都存在其數組arr[hash.size]中,在簡單的hash表中必有函數計算一個元素的存儲位置時候,其簡單的計算公式如key % hash.size。此時由於hash.size大小固定,必然存在若干個元素計算出同樣的存儲位置。不同的人針對這個難題有不同的解決方法。


我的lisk中就有兩種hash結構可以用來解決這個問題,一種是可伸縮的hashtable(以下稱之爲lisk_hashtable),一種就是多級hash(以下稱之爲lisk_mul_hash)。lisk_hashtable的特點以後予以介紹,現在先提下它的缺點。在內存允許的情況下lisk_hashtable可以自動擴大或縮小它自身的大小,它的極限大小的計算是不方便的。若極限大小不能方便計算出來存在的情況下,它就不方便作爲服務器對外服務的緩存。而結構簡單的緩存結構也方便別人維護。額外話題,服務器的特點第一是穩定,第二纔是性能,一個性能很好的服務器但是她的服務時好時壞,用戶會經常罵孃的,它能服務誰呢?而lisk_mul_hash是不考慮伸縮性的,你可以認爲這是它的缺點,但是在服務器緩存極限容量存在的情況下,可伸縮性又有什麼用?


多級hash就能夠實現上面提到的服務器緩存的兩個特性。多級hash可提供插入、查找和刪除操作。


    簡單地,我們可以認爲多級hash就是一個多維數組,行數一般在10到100之間,列數則不固定,元素大小固定。
    一般網上有一些多級hash實現,你會發現它們的關鍵實現就是一個多維數組。假若其關鍵結構是arr[3][10000],若一個元素的key是99995554,則尋找插入過程如下:
1 在arr[0]的存儲位置是99995554 % 10000 = 5554;
      2 如果arr[0]的5554位置已經被佔用了,則使用arr[1]的5554位置;
3 如果arr[1]的5554位置也被佔用,則使用使用arr[2]的5554位置。
    上面每行都能存儲10000個元素,同一個元素如果計算存儲位置,在每一個行可以計算出同樣的計算位置。考慮到離散數學中同餘原理,每行最大元素數目不同且數字取質數爲宜,每行的數字可依次遞減。譬如第一行取比10000稍小的質數9973,第二行大小取值9969,第三行取值9949。
    所以若再次計算元素存儲位置,則計算過程就稍微改變。計算過程如下:
    1 在arr[0]的存儲位置是99995554 % 9973;
    2 如果arr[0]的存儲位置99995554 % 9969 被佔用,則使用arr[1]的99995554 %  9969位置;
    3 如果同樣也被佔用,則使用使用arr[2]的99995554 % 9949位置。
    考慮一個問題,上面計算key爲99995554的元素的存儲位置時, 如果三行的三個位置都被佔用了,那麼它將被存儲到那裏呢?這就是多級hash的一個特點,不保證所有元素都被存儲下來。如果多級hash設計的好,只有很少量的元素找不到存儲位置,他們被稱爲奇異值,這部分你可以另考慮別的方法來解決。譬如你就直接把他們存到磁盤上,文件分爲索引文件和數據文件,分別存儲key和value,並把索引文件通過linux的mmap接口映射到內存中來,以方便快速查找。
     即使多級hash中已經插不進新的元素,多級hash內部也肯定還有沒有存儲元素的位置。
     或者呢?我們可以接受多層hash不能飽和地存下所有元素的特點,其利用率達到85%就可以了,若達到90%就可以認爲這個多級hash就可以被緩存不可用了。你需要升級服務器了。
    
    多級hash行數越多,其空間利用率越高,然查找速度便逐漸減慢。反之,行數越少,則多級hash查找速度越快,然空間利用率低下。所以行數應該被控制在50行以內,一般在20至35行之間爲宜。


    lisk中多級hash也是一個多維數組,每行數目可以不同。如第一行實際存儲數目極限是9973,則只申請9973個位置,可以不用申請10000個位置,以減少元素浪費。

    lisk中多級hash的每行列數目是遞增的,lisk/exam/mul_hash_test.c利用隨機數測試,這個多級hash幾乎可以把所有元素存下來,即利用率幾乎可達到100%。但它沒有考慮如何存儲奇異值。


其實,關於緩存還有一些額外的話題。不管緩存的內存區是通過共享內存還是其他的os接口獲得的,其中少被訪問的數據幾乎可能將被os swap out到虛擬內存中,一旦有訪問者要訪問這個數據,os需要先將數據swap in到內存中,然後再返回給訪問者。如果平均訪問響應時間是100ms,那麼對這個數據的相應就可能在1s至2s之間。如果你認爲這是不可接受的(其實我也這麼認爲),就可以通過mprotect接口禁止緩存中的數據被swap out。

        另外,如果銀行等部門用C設計緩存時候,其對緩存中數據的可靠性是非常高的,即寧願訪問響應時間稍長一點也不會接受不可靠的數據。不考慮多線程情況,建議對共享內存數據的讀取都使用volatile關鍵字。



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