使用.Net Core實現FNV分散式hash一致性演算法
- 2019 年 10 月 4 日
- 筆記
# 使用.Net Core實現FNV分散式hash一致性演算法
說到FNV哈希演算法不得不提Memcached,我們先簡單介紹一下Memcached。
# Memcached
Memcached
分為客戶端與服務端,Memcached
是服務端,服務端本身不提供分散式實現,只是一個單獨的k-v快取;Memcached的分散式是在客戶端類庫中實現的,也就是說你可以根據自己的需要實現不同的分散式方案,不一定非得使用FNV哈希演算法。
Memcached通過FNV演算法實現了服務的分散式,並通過引入虛擬節點的辦法盡量是伺服器分布的更均勻。已經有很多文章在介紹Memcached的分散式實現原理了,所以我就不那麼多廢話了。
# FNV分散式hash演算法實現
如果你還不了解FNV哈希演算法,可以先看一下我之前的文章。
# FNV1演算法實現
程式碼實現上我將參考MD5演算法的實現來編寫FNV1演算法:
- 首先,我將創建一個FNV1類,該類需要實現HashAlgorithm,之所以實現HashAlgorithm,是因為該抽象類定義了hash演算法通用的介面,這樣也可以使我們的實現與.net框架集成的更好,當然如果你不喜歡也可以不實現HashAlgorithm,就當是寫了一個獨立的幫助類。
- 然後,我們重寫Create方法,這裡我們將創建一個FNV1類的實例
- 最後,我們去實現這個FNV1類
所有實現程式碼如下:
//首先我將創建FNV1類 public abstract class FNV1 : HashAlgorithm { //重寫隱藏HashAlgorithm的Create方法 public static new FNV1 Create() { return new Implementation(); } //下面FNV1的實現我們完全是套用的公式沒有什麼好講的 private sealed class Implementation : FNV1 { private const uint OFFSETBASIS = 2166136261; private const uint PRIME = 16777619; private uint _hash; public override void Initialize() { _hash = OFFSETBASIS; } protected override void HashCore(byte[] array, int ibStart, int cbSize) { int end = ibStart + cbSize; for (var index = ibStart; index < end; index++) { _hash *= PRIME; _hash ^= array[index]; } } protected override byte[] HashFinal() { return BitConverter.GetBytes(_hash); } } } ### 使用方法 var bytes=Encoding.UTF8.GetBytes("Test"); var hashBytes=FNV1.Create().ComputerHash(bytes); var hashValue=BitConverter.ToUInt32(hashBytes);
FNV其實還有FNV1a演算法,與FNV1有些許的區別,這裡我就不一一實現了,你可以參考FNV1的實現和FNV哈希演算法來實現FNV1a演算法。我有一個幫助類MicroFx.Cryptography 分別實現了FNV1和FNV1a的32bit、64bit演算法版本。
# 為什麼使用FNV演算法實現hash一致性
無論是分散式演算法還是hash一致性演算法都不只有一種或幾種實現方案,但Memached為什麼會選擇FNV演算法,而不是md5,不是sha呢?我有自己的認識。
- 我們先看幾行程式碼,分別使用MD5,sha,FNV演算法計算一個
Test
字元串的哈希值,然後對比hash結果中數組的長度 var bytes = Encoding.UTF8.GetBytes("Test"); var shabytes = SHA1.Create().ComputeHash(bytes); //shabytes長度為20,及160bit var md5bytes=MD5.Create().ComputeHash(bytes); //md5bytes長度為16,及128bit var fnvbytes = FNV1a.Create().ComputeHash(bytes); //fnvbytes長度為4,及32bit 演算法取值範圍 sha1[0,2^160-1] md5[0,2^128-1] fnv[0,2^32-1] 從上表我們可以看出,FNV的取值範圍最小,如果將區間內的每一個整數看做一個Memcached服務端節點,那麼FNV容納的數量最少,但相對於實際的環境下已經足夠多了,這樣我們每次在計算一台伺服器屬於哪個節點的時候速度上會比md5、sha1快很多。 - FNV的32bit計算結果值剛好是一個uint類型,.net core最大支援ulong也就是uint64,再大的話就需要我們自己實現,所以這也是選擇FNV的一個原因。(或許我這裡不應該拿.net舉例,但實際常用的高級語言最大也是64bit)