1. 程式人生 > >一致哈希算法Java實現

一致哈希算法Java實現

字符 queue 演示 shc hash sys 我們 pen oct

一致哈希算法(Consistent Hashing Algorithms)是一個分布式系統中經常使用的算法。

傳統的Hash算法當槽位(Slot)增減時,面臨全部數據又一次部署的問題。而一致哈希算法確可以保證,僅僅須要移動K/n份數據(K為數據總量, n為槽位數量),且僅僅影響現有的當中一個槽位。

這使得分布式系統中面對新增或者刪除機器時。可以更高速的處理更改請求。

本文將用Java實現一個簡單版本號的一致哈希算法,僅僅為說明一致哈希算法的核心思想。

一致哈希算法介紹

一致哈希算法的介紹非常多。如wiki,以及非常多的博客。在此僅僅簡述其概念。具體的介紹請參考相關論文。

第一個概念是節點(Node),分布式系統中相當於一臺機器。全部的節點邏輯上圍起來形成一個圓環。第二個概念是數據(Data)。每份數據都有一個key值。數據總是須要存儲到某一個節點上。數據和節點之間怎樣關聯的呢?通過區域的概念關聯。每個節點都負責圓環上的一個區域,落在該區域上的就存儲在該節點上,通常區域為左側閉合。右側開放的形式。如[2500,5000)。

下面是一個擁有4個節點的一致哈希算法示意圖:

技術分享

總的範圍定為10000,也限定了總槽位的數量。能夠按照項目的須要,制定合適的大小。

  • Node1的起始位置為0。負責存儲[0, 2500)之間的數據
  • Node2的起始位置為2500,負責存儲[2500, 5000)之間的數據
  • Node3的起始位置為5000。負責存儲[5000, 7500)之間的數據
  • Node4的起始位置為7500,負責存儲[7500, 10000)之間的數據

一致哈希算法最大的特點在於新增或者刪除節點的處理。

假設新增一個起始位置為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的詳細實現

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();
	}

}

須要進一步完好的地方

不同節點之間互相備份,提高系統的可靠性。節點範圍的動態調整。有時候分布可能不夠平衡。

一致哈希算法Java實現