一致哈希算法(Consistent Hashing Algorithms)是一個分佈式系統中常用的算法。java
傳統的Hash算法當槽位(Slot)增減時,面臨所有數據又一次部署的問題。而一致哈希算法確能夠保證,僅僅需要移動K/n份數據(K爲數據總量, n爲槽位數量),且僅僅影響現有的當中一個槽位。node
這使得分佈式系統中面對新增或者刪除機器時。能夠更高速的處理更改請求。算法
本文將用Java實現一個簡單版本號的一致哈希算法,僅僅爲說明一致哈希算法的核心思想。app
一致哈希算法的介紹很是多。如wiki,以及很是多的博客。在此僅僅簡述其概念。具體的介紹請參考相關論文。分佈式
第一個概念是節點(Node),分佈式系統中至關於一臺機器。所有的節點邏輯上圍起來造成一個圓環。第二個概念是數據(Data)。每份數據都有一個key值。數據老是需要存儲到某一個節點上。數據和節點之間怎樣關聯的呢?經過區域的概念關聯。每一個節點都負責圓環上的一個區域,落在該區域上的就存儲在該節點上,一般區域爲左側閉合。右側開放的形式。如[2500,5000)。post
下面是一個擁有4個節點的一致哈希算法示意圖:this
總的範圍定爲10000,也限定了總槽位的數量。可以按照項目的需要,制定合適的大小。spa
一致哈希算法最大的特色在於新增或者刪除節點的處理。code
假設新增一個起始位置爲1250的節點Node5,那麼影響到的惟一一個節點就是Node1,Node1的存儲範圍由[0, 2500)變動[0, 1250)。Node5的存儲範圍爲[1250, 2500),因此需要把落於[1250, 2500)範圍的數據搬移到Node5上。其餘的不需要作出改變,這一點很的重要。至關於Node5分擔了Node1的部分工做。假設把Node3刪除,那麼需要把Node3上面的數據搬移到Node2上面,Node2的範圍擴大爲[2500,7500),Node2承擔了Node3的工做。對象
Java是面向對象的語言,首先需要抽象對象。Node。表示節點,有名字。起始位置,以及數據列表三個屬性,由於Node和數據之間的匹配。使用的是範圍,因此爲了簡單起見,Node上加了一個end的屬性。原本應該有Data以及DataKey的概念。但是爲了簡單起見,演示樣例中Data就是字符串,Key就是本身。
整個圓環有一個長度,定義爲scope,默以爲10000。
新增節點的算法是。找到最大的空擋。把新增節點放中間。固然也可以換爲找到壓力(數據量)最大的節點,把新增節點放在該節點以後。刪除節點有一點小技巧。假設刪除的是開始位置爲0的節點,那麼把下一個節點的開始位置置爲0,和普通的退格不一樣。
這能保證僅僅要有節點。就必定有一個從0開始的節點。這能簡化咱們的算法和處理邏輯。
addItem方法就是往系統裏面放數據,最後爲了展現數據的分佈效果,提供desc方法。打印出數據分佈狀況。很是有意思。
整體代碼例如如下:
public class ConsistentHash { private int scope = 10000; private List<Node> nodes; public ConsistentHash() { nodes = new ArrayList<Node>(); } public int getScope() { return scope; } public void setScope(int scope) { this.scope = scope; } public void addNode(String nodeName) { if (nodeName == null || nodeName.trim().equals("")) { throw new IllegalArgumentException("name can't be null or empty"); } if (containNodeName(nodeName)) { throw new IllegalArgumentException("duplicate name"); } Node node = new Node(nodeName); if (nodes.size() == 0) { node.setStart(0); node.setEnd(scope); nodes.add(node); } else { Node maxNode = getMaxSectionNode(); int middle = maxNode.start + (maxNode.end - maxNode.start) / 2; node.start = middle; node.end = maxNode.end; int maxPosition = nodes.indexOf(maxNode); nodes.add(maxPosition + 1, node); maxNode.setEnd(middle); // move data Iterator<String> iter = maxNode.datas.iterator(); while (iter.hasNext()) { String data = iter.next(); int value = Math.abs(data.hashCode()) % scope; if (value >= middle) { iter.remove(); node.datas.add(data); } } for (String data : maxNode.datas) { int value = Math.abs(data.hashCode()) % scope; if (value >= middle) { maxNode.datas.remove(data); node.datas.add(data); } } } } public void removeNode(String nodeName) { if (!containNodeName(nodeName)) { throw new IllegalArgumentException("unknown name"); } if (nodes.size() == 1 && nodes.get(0).datas.size() > 0) { throw new IllegalArgumentException("last node, and still have data"); } Node node = findNode(nodeName); int position = nodes.indexOf(node); if (position == 0) { if (nodes.size() > 1) { Node newFirstNode = nodes.get(1); for (String data : node.datas) { newFirstNode.datas.add(data); } newFirstNode.setStart(0); } } else { Node lastNode = nodes.get(position - 1); for (String data : node.datas) { lastNode.datas.add(data); } lastNode.setEnd(node.end); } nodes.remove(position); } public void addItem(String item) { if (item == null || item.trim().equals("")) { throw new IllegalArgumentException("item can't be null or empty"); } int value = Math.abs(item.hashCode()) % scope; Node node = findNode(value); node.datas.add(item); } public void desc() { System.out.println("Status:"); for (Node node : nodes) { System.out.println(node.name + ":(" + node.start + "," + node.end + "): " + listString(node.datas)); } } private String listString(LinkedList<String> datas) { StringBuffer buffer = new StringBuffer(); buffer.append("{"); Iterator<String> iter = datas.iterator(); if (iter.hasNext()) { buffer.append(iter.next()); } while (iter.hasNext()) { buffer.append(", " + iter.next()); } buffer.append("}"); return buffer.toString(); } private boolean containNodeName(String nodeName) { if (nodes.isEmpty()) { return false; } Iterator<Node> iter = nodes.iterator(); while (iter.hasNext()) { Node node = iter.next(); if (node.name.equals(nodeName)) { return true; } } return false; } private Node findNode(int value) { Iterator<Node> iter = nodes.iterator(); while (iter.hasNext()) { Node node = iter.next(); if (value >= node.start && value < node.end) { return node; } } return null; } private Node findNode(String nodeName) { Iterator<Node> iter = nodes.iterator(); while (iter.hasNext()) { Node node = iter.next(); if (node.name.equals(nodeName)) { return node; } } return null; } private Node getMaxSectionNode() { if (nodes.size() == 1) { return nodes.get(0); } Iterator<Node> iter = nodes.iterator(); int maxSection = 0; Node maxNode = null; while (iter.hasNext()) { Node node = iter.next(); int section = node.end - node.start; if (section > maxSection) { maxNode = node; maxSection = section; } } return maxNode; } static class Node { private String name; private int start; private int end; private LinkedList<String> datas; public Node(String name) { this.name = name; datas = new LinkedList<String>(); } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getStart() { return start; } public void setStart(int start) { this.start = start; } public int getEnd() { return end; } public void setEnd(int end) { this.end = end; } public LinkedList<String> getDatas() { return datas; } public void setDatas(LinkedList<String> datas) { this.datas = datas; } } public static void main(String[] args) { ConsistentHash hash = new ConsistentHash(); hash.addNode("Machine-1"); hash.addNode("Machine-2"); hash.addNode("Machine-3"); hash.addNode("Machine-4"); hash.addItem("Hello"); hash.addItem("hash"); hash.addItem("main"); hash.addItem("args"); hash.addItem("LinkedList"); hash.addItem("end"); hash.desc(); hash.removeNode("Machine-1"); hash.desc(); hash.addNode("Machine-5"); hash.desc(); hash.addItem("scheduling"); hash.addItem("queue"); hash.addItem("thumb"); hash.addItem("quantum"); hash.addItem("approaches"); hash.addItem("migration"); hash.addItem("null"); hash.addItem("feedback"); hash.addItem("ageing"); hash.addItem("bursts"); hash.addItem("shorter"); hash.desc(); hash.addNode("Machine-6"); hash.addNode("Machine-7"); hash.addNode("Machine-8"); hash.desc(); hash.addNode("Machine-9"); hash.addNode("Machine-10"); hash.addNode("Machine-11"); hash.desc(); hash.addNode("Machine-12"); hash.addNode("Machine-13"); hash.addNode("Machine-14"); hash.addNode("Machine-15"); hash.addNode("Machine-16"); hash.addNode("Machine-17"); hash.desc(); } }
不一樣節點之間互相備份,提升系統的可靠性。節點範圍的動態調整。有時候分佈可能不夠平衡。