一致性hash java實現
阿新 • • 發佈:2018-12-09
前言
之前講了 一致性hash演算法的原理,現在擼程式碼。
- 環的資料結構,可能首先想到的是常用的List。一致性hash需要找比當前元素大的節點,那麼list要麼排好序然後比較找到對應的節點,要麼是不排序直接遍歷算差值。顯然在這種查詢比較頻繁的場景樹結構更適合,所以我們用有序的樹結構TreeMap。
- 節點的hash衝突時我們再取一次hash值
/**一致性hash * Created by szk on 2018/9/10. */ public class ConsistentHashWithVirtualNode { /** * 待新增入Hash環的伺服器列表 */ private static String[] SERVERS = {"192.168.1.2:6379", "192.168.1.3:6379", "192.168.1.4:6379"}; /** * key表示伺服器的hash值,value表示虛擬節點的名稱 */ private static SortedMap<Integer, String> HASH_CIRCLE = new TreeMap<Integer, String>(); /** * 用於結果統計 */ private static Map<String,Integer> result = new HashMap<String,Integer>(); /** * 每個真實節點對應虛擬節點數 */ private static Integer VIRTUAL_NODES_NUM = 150; /** * 使用FNV1_32_HASH演算法計算伺服器的Hash值 */ 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; } static { for (int i = 0; i < SERVERS.length; i++) { for (Integer j = 0; j < VIRTUAL_NODES_NUM; j++) { setServer(SERVERS[i]+"vn"+j); } } } private static void setServer(String ip){ setServer(ip,null); } private static void setServer(String ip, Integer hash){ hash = hash != null ?getHash(hash.toString()) : getHash(ip); if(StringUtils.isBlank(HASH_CIRCLE.get(hash))){ HASH_CIRCLE.put(hash, ip); System.out.println("[" + ip + "]加入sortedMap中, 其Hash值為" + hash); }else { //解決hash碰撞 setServer(ip,hash); } } public static void main(String[] args) { for (int i = 0; i < 50; i++) { long nodes = RandomUtils.nextLong(); String server = getServer(nodes); String realServer = server.split("vn")[0]; System.out.println("[" + nodes + "]的hash值為"+ getHash(""+nodes) + ", 被路由到虛擬結點[" + server+ "], 真實結點[" + realServer + "]"); result.put(realServer,(result.get(realServer)==null?0:result.get(realServer))+1); } result.forEach((k,v)->{ System.out.println("結點["+k+"]上有"+v); }); } public static String getServer(Object node) { String ip = HASH_CIRCLE.get(HASH_CIRCLE.firstKey()); // 得到帶路由的結點的Hash值 int hash = getHash(node.toString()); // 得到大於該Hash值的所有Map SortedMap<Integer, String> subMap = HASH_CIRCLE.tailMap(hash); if(!subMap.isEmpty()){ // 第一個Key就是順時針過去離node最近的那個結點 Integer i = subMap.firstKey(); ip = subMap.get(i); } // 返回對應的伺服器名稱 return ip; } }
150個虛擬節點,50個元素去匹配,結果如下
[4506423623011963877]的hash值為2076651453, 被路由到虛擬結點[192.168.1.2:6379vn31], 真實結點[192.168.1.2:6379] [5570050191736927528]的hash值為852645907, 被路由到虛擬結點[192.168.1.3:6379vn3], 真實結點[192.168.1.3:6379] [8129699418679179907]的hash值為487796751, 被路由到虛擬結點[192.168.1.2:6379vn40], 真實結點[192.168.1.2:6379] [477939724802787766]的hash值為1916940123, 被路由到虛擬結點[192.168.1.2:6379vn139], 真實結點[192.168.1.2:6379] [2237405063640190973]的hash值為1033214343, 被路由到虛擬結點[192.168.1.2:6379vn65], 真實結點[192.168.1.2:6379] [3378009287555310246]的hash值為1664158122, 被路由到虛擬結點[192.168.1.3:6379vn123], 真實結點[192.168.1.3:6379] [8357430545483250147]的hash值為1498249592, 被路由到虛擬結點[192.168.1.2:6379vn66], 真實結點[192.168.1.2:6379] [6575662240925929638]的hash值為299745323, 被路由到虛擬結點[192.168.1.2:6379vn147], 真實結點[192.168.1.2:6379] [5834994408022173056]的hash值為1060475722, 被路由到虛擬結點[192.168.1.4:6379vn118], 真實結點[192.168.1.4:6379] [1553332217396956129]的hash值為401533392, 被路由到虛擬結點[192.168.1.3:6379vn122], 真實結點[192.168.1.3:6379] [7171179586727756331]的hash值為1578420313, 被路由到虛擬結點[192.168.1.2:6379vn81], 真實結點[192.168.1.2:6379] [72466124520548464]的hash值為1891754379, 被路由到虛擬結點[192.168.1.4:6379vn10], 真實結點[192.168.1.4:6379] [4099801649503227100]的hash值為618254540, 被路由到虛擬結點[192.168.1.2:6379vn61], 真實結點[192.168.1.2:6379] [7654612135449452049]的hash值為1208151474, 被路由到虛擬結點[192.168.1.2:6379vn63], 真實結點[192.168.1.2:6379] 結點[192.168.1.2:6379]上有18 結點[192.168.1.3:6379]上有15 結點[192.168.1.4:6379]上有17