1. 程式人生 > >java實現分散式環境Hash一致性

java實現分散式環境Hash一致性

package hash;
import java.util.LinkedList;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
/**
 * hash一致性,解決hash求模擴充套件性差以及無虛擬節點方案增刪物理節點導致負載不均衡的問題
 * 實現思路:將物理節點衍生出的虛擬節點通過FNV1_32_HASH演算法相對均勻的放到hash環裡,
 * 當請求過來將請求的通過FNV1_32_HASH演算法計算出hash值,根據這個hash值找到hash
 * 環裡第一個大於這個hash值的虛擬節點,再根據虛擬節點計算出真實的物理節點。從而實現負載
 * 均衡
 * @author ocean
 *
 */
public class ConsistentHash {
	// 待加入hash環的原始服務列表
	private static String[] servers = { "192.168.30.1:1001", "192.168.30.1:1002", "192.168.30.1:1003",
			"192.168.30.1:1004", "192.168.30.1:1005" };
	// 真實的節點列表
	private static List<String> realNodes = new LinkedList<>();
	// 虛擬節點
	private static TreeMap<Integer, String> virtualNodes = new TreeMap<>();
	// 每一個真實節點掛載固定數量的虛擬節點
	private static final int VIRTUAL_NODE = 5;
	static {
		for (int index = 0; index < servers.length; index++) {
			realNodes.add(index, servers[index]);
		}
		for (String node : realNodes) {
			//引入虛擬節點,解決增加或者刪除物理節點導致負載不均衡的問題
			for (int i = 0; i < VIRTUAL_NODE; i++) {
				String virtualName = node + "&&VN" + i;
				int hashValue = getHash(virtualName);
				System.out.println("虛擬節點[" + virtualName + "]被新增,hash值為:"+hashValue+"," + virtualNodes.put(hashValue, virtualName));
			}
		}
	}

	/**
	 * 使用FNV1_32_HASH演算法計算伺服器的Hash值,這裡不使用重寫hashCode的方法,最終效果沒區別
	 */
	private static int getHash(String str) {
		final int p = 16777619;
		int hash = (int) 2166136261L;
		for (int i = 0; i < str.length(); i++)
			hash = (hash ^ str.charAt(i)) * p;
		hash += hash << 13;
		hash ^= hash >> 7;
		hash += hash << 3;
		hash ^= hash >> 17;
		hash += hash << 5;

		// 如果算出來的值為負數則取其絕對值
		if (hash < 0)
			hash = Math.abs(hash);
		return hash;
	}
	
	/**
	 * 路由到指定的伺服器
	 * @param node
	 * @return
	 */
	public static String getServer(String node) {
		//得到帶路由的結點的Hash值
		int hashCode = getHash(node);
		//獲取大於該Hash值的所有Map
		SortedMap<Integer,String> map = virtualNodes.tailMap(hashCode);
		//獲取第一個key
		int firstKey = map.firstKey();
		//獲取第一個虛擬節點
		String virtualName = map.get(firstKey);
		return virtualName.substring(0, virtualName.indexOf("&&"));
	}
	
	//測試
	public static void main(String[] args) {
		String[] clientAdds = {"127.0.0.1:22","127.0.0.1:23","127.0.0.1:25"};
		for(String clientAdd : clientAdds) {
			System.out.println(getServer(clientAdd));
		}
	}
}