針對不一樣的key使用不一樣的hash算法,如對整型、字符串以及大小寫敏感的字符串分別使用不一樣的hash算法;nginx
整型的Hash算法使用的是Thomas Wang's 32 Bit / 64 Bit Mix Function ,這是一種基於位移運算的散列方法。基於移位的散列是使用Key值進行移位操做。一般是結合左移和右移。每一個移位過程的結果進行累加,最後移位的結果做爲最終結果。這種方法的好處是避免了乘法運算,從而提升Hash函數自己的性能。c++
unsigned int dictIntHashFunction(unsigned int key) { key += ~(key << 15); key ^= (key >> 10); key += (key << 3); key ^= (key >> 6); key += ~(key << 11); key ^= (key >> 16); return key; }
字符串使用的MurmurHash算法,MurmurHash算法具備高運算性能,低碰撞率的特色,由Austin Appleby建立於2008年,現已應用到Hadoop、libstdc++、nginx、libmemcached等開源系統。2011年Appleby被Google僱傭,隨後Google推出其變種的CityHash算法。
murmur是 multiply and rotate的意思,由於算法的核心就是不斷的乘和移位(x *= m; k ^= k >> r;)git
unsigned int dictGenHashFunction(const void *key, int len) { /* 'm' and 'r' are mixing constants generated offline. They're not really 'magic', they just happen to work well. */ uint32_t seed = dict_hash_function_seed; const uint32_t m = 0x5bd1e995; const int r = 24; /* Initialize the hash to a 'random' value */ uint32_t h = seed ^ len; /* Mix 4 bytes at a time into the hash */ const unsigned char *data = (const unsigned char *)key; while(len >= 4) { uint32_t k = *(uint32_t*)data; k *= m; k ^= k >> r; k *= m; h *= m; h ^= k; data += 4; len -= 4; } /* Handle the last few bytes of the input array */ switch(len) { case 3: h ^= data[2] << 16; case 2: h ^= data[1] << 8; case 1: h ^= data[0]; h *= m; }; /* Do a few final mixes of the hash to ensure the last few * bytes are well-incorporated. */ h ^= h >> 13; h *= m; h ^= h >> 15; return (unsigned int)h; }
一個好的hash算法須要知足兩個條件:
1) 性能高,運算足夠快;
2) 相鄰的數據hash後分布廣;即便輸入的鍵是有規律的,算法仍然能給出一個很好的隨機分佈性;
好比:murmur計算"abc"是1118836419,"abd"是413429783。而使用Horner算法,"abc"是96354, "abd"就比它多1(96355);github
負載因子 = 當前結點數/桶的大小,超過1表示確定有碰撞了;碰撞的結點,經過鏈表拉鍊起來;redis
全部哈希表的初始桶的大小爲4,根據負載因子的變化進行rehash,從新分配空間(擴展或收縮)算法
當hash表的負載因子超過1後,進行擴展(小於0.01時,進行收縮);
所謂擴展,就是新建一個hash表2,將桶的數量增大(具體增大爲:第一個大於等於usedSize的2的n次冥);而後將hash表1中結點都轉移到hash表2中;app
rehash的觸發條件:
當作BGSAVE或BGREWRITEEOF時,負載因子超過5時觸發rehash,
沒有BGSAVE或BGREWRITEEOF時,負載因子超過1時觸發rehash;dom
在BGSAVE或BGREWRITEEOF時,使用到Linux的寫時複製,若是這時候作rehash,將會好用更多的內存空間(沒有變化的結點用一份,變化的結點複製一份)memcached
一個hash表中的數據可能有幾百上千萬,不可能一次rehash轉移完,須要分批逐漸轉移;
在rehash的過程當中,對redis的查詢、更新操做首先會在hash0中查找,沒有找到,而後轉到hash1中操做;
對於插入操做,直接插入到hash1中;最終目標是將hash表1變爲空表,rehash完成;函數
鍵值對的實現,value 是一個union,對整型和字符串使用不一樣的存儲對象;
// 鍵 void *key; // 值 union { void *val; uint64_t u64; int64_t s64; } v;
ref:
《Hash 函數概覽》http://www.oschina.net/translate/state-of-hash-functions
《redis設計與實現》
Posted by: 大CC | 18NOV,2015
博客:blog.me115.com [訂閱]
Github:大CC