標籤:dma 簡單 code 技術分享 highlight 而且 little long 順時針
把不同號段的資料儲存在不同的機器上,以用來分散壓力。假如我們有一百萬個QQ號,十台機器,,如何劃分呢?
最簡單粗暴的方法是用QQ號直接對10求餘,結果為0-9 分別對應上面的十台機器。比如QQ號為 23900 的使用者在編號為0的機器 23901的使用者在編號為1的機器,以此類推。那麼問題來了,現在QQ使用者急劇上升 由一百萬漲到了五百萬,顯然10台機器已經無能為力
了,於是我們擴充到25台。這個時候我們發現以前的資料全亂了。完蛋!只能跑路了……
Hash 演算法的一個衡量指標是單調性( Monotonicity ),定義如下:
單調性是指如果已經有一些內容通過雜湊指派到了相應的緩衝中,又有新的緩衝加入到系統中。雜湊的結果應能夠保證原有已指派的內容可以被映射到新的緩衝中去,而不會被映射到舊的緩衝集合中的其他緩衝區。
容易看到,上面的簡單 hash 演算法 hash(object)%N 難以滿足單調性要求。
所以在保證合理分散的情況下,我們還是是可拓展的。這就是一致性hash,一致性hash 演算法都是將 value 映射到一個 32 位的 key 值,也即是 0~2^32-1 次方的數值空間;我們可以將這個空間想象成一個首( 0 )尾( 2^32-1 )相接的圓環,當有資料過來按順時針找到離他最近的一個點,這個點,就是我要的節點機器。如:
hash("192.168.128.670") ---->A //根據伺服器IP hash出去產生節點
hash("192.168.148.670") ---->C //根據伺服器IP hash出去產生節點
hash("81288812") ----> k1 //根據QQ號 hash出去產生值 ----->順時針找到機器
hash("8121243812") ----> k4 //根據QQ號 hash出去產生值 ----->順時針找到機器
這樣當有新的機器加進來,舊的機器去掉,影響的也是一小部分的資料。這樣看似比較完美了,,但假如其中一個節點B資料激增,掛了,所有資料會倒到C--->C也扛不住了---->所有資料會倒到D ……以此類推,最終全掛了!整個世界清靜了!!!
顯然,這種方式因為資料的不平均導致服務掛了。所以我們的一致性hash還需要具有平衡性。
平衡性是指雜湊的結果能夠儘可能分布到所有的緩衝中去,這樣可以使得所有的緩衝空間都得到利用。
為解決平衡性,一致性hash引入了虛擬節點”的概念。“虛擬節點”( virtual node )是實際節點在 hash 空間的複製品( replica ),一實際個節點對應了若干個“虛擬節點”,這個對應個數也成為“複製個數”,“虛擬節點”在 hash 空間中以 hash 值排列。這樣我們如果有25台伺服器,每台虛擬成10個,就有250個虛擬節點。這樣就保證了每個節點的負載不會太大,壓力均攤,有事大家一起扛!!!
hash("192.168.128.670#36kr01") ---->A //根據伺服器IP hash出去產生節點
hash("192.168.128.670#36kr02") ---->B //根據伺服器IP hash出去產生節點
hash("192.168.128.670#36kr03") ---->B //根據伺服器IP hash出去產生節點
……
final 虛擬節點+murmurhash成了我們的解決方案:
class Shard<S> { // S類封裝了機器節點的資訊 ,如name、password、ip、port等 private TreeMap<Long, S> nodes; // 虛擬節點 private List<S> shards; // 真實機器節點 private final int NODE_NUM = 100; // 每個機器節點關聯的虛擬節點個數 public Shard(List<S> shards) { super(); this.shards = shards; init(); } private void init() { // 初始化一致性hash環 nodes = new TreeMap<Long, S>(); for (int i = 0; i != shards.size(); ++i) { // 每個真實機器節點都需要關聯虛擬節點 final S shardInfo = shards.get(i); for (int n = 0; n < NODE_NUM; n++) // 一個真實機器節點關聯NODE_NUM個虛擬節點 nodes.put(hash("SHARD-" + i + "-NODE-" + n), shardInfo); } } public S getShardInfo(String key) { SortedMap<Long, S> tail = nodes.tailMap(hash(key)); // 沿環的順時針找到一個虛擬節點 if (tail.size() == 0) { return nodes.get(nodes.firstKey()); } return tail.get(tail.firstKey()); // 返回該虛擬節點對應的真實機器節點的資訊 } /** * MurMurHash演算法,是非加密HASH演算法,效能很高, * 比傳統的CRC32,MD5,SHA-1(這兩個演算法都是加密HASH演算法,複雜度本身就很高,帶來的效能上的損害也不可避免) * 等HASH演算法要快很多,而且據說這個演算法的碰撞率很低. * http://murmurhash.googlepages.com/ */ private Long hash(String key) { ByteBuffer buf = ByteBuffer.wrap(key.getBytes()); int seed = 0x1234ABCD; ByteOrder byteOrder = buf.order(); buf.order(ByteOrder.LITTLE_ENDIAN); long m = 0xc6a4a7935bd1e995L; int r = 47; long h = seed ^ (buf.remaining() * m); long k; while (buf.remaining() >= 8) { k = buf.getLong(); k *= m; k ^= k >>> r; k *= m; h ^= k; h *= m; } if (buf.remaining() > 0) { ByteBuffer finish = ByteBuffer.allocate(8).order( ByteOrder.LITTLE_ENDIAN); // for big-endian version, do this first: // finish.position(8-buf.remaining()); finish.put(buf).rewind(); h ^= finish.getLong(); h *= m; } h ^= h >>> r; h *= m; h ^= h >>> r; buf.order(byteOrder); return h; }}
一致性hash-java實現treemap版