在工做中常遇到分库分表或者分布式结点分布的问题,保证均衡,可扩展的分配策略很重要,分配策略通常采用hash算法,通常都是余数等策略,这种hash算法在结点固定的时候会有比较均衡的分布,可是碰到须要扩展结点就比较难处理,涉及迁移的数据特别多,因此引入一致性哈希算法java
一致性算法能解决什么问题?node
解决了普通余数Hash算法伸缩性差的问题,能够保证在服务器上线或下线变动时尽可能有多的请求命中原来路由到的服务器程序员
算法的具体原理:算法
先构造一个长度为232的整数环(这个环被称为一致性Hash环),根据节点名称的Hash值(其分布为[0, 232-1])将服务器节点放置在这个Hash环上,而后根据数据的Key值计算获得其Hash值(其分布也为[0, 232-1]),接着在Hash环上顺时针查找距离这个Key值的Hash值最近的服务器节点,完成Key到服务器的映射查找。服务器
实现:数据结构
一、数据结构选取负载均衡
根据原理咱们须要找到一个合适的数据结构存放232的数据,由于这里的查询特别多,因此尽量的选用稳定、查找时间复杂度低的数据结构,这里采用红黑树来存储(各数据结构的对比参考其余文档),咱们主要是采用他的TreeMap方法,由于他自己提供了tailMap(K fromKey)方法,支持从红黑树中查找比fromKey大的值的集合,但并不须要遍历整个数据结构,这样在集合中取第一个就能实现顺时针查找最近结点的需求分布式
二、Hash算法学习
string的hashcode方法在一致性hash算法中实用价值较低,由于遇到连续的key时hash结果比较集中,作不到均衡,须要从新找个算法计算Hash值,这里算法比较多好比CRC32_HASH、FNV1_32_HASH、KETAMA_HASH等,这里实用FNV1_32_HASH算法(其余hash算法对比参考其余文档)ui
三、增长虚拟结点
在原有的负载均衡分布中增长新的结点,势必会致使平衡破坏,致使新增结点和该结点后一个结点的请求少于其余结点,这种能够增长虚拟结点来解决。原理能够理解为对于新增的结点,把他拆分为多个虚拟结点,而后把这些虚拟结点尽可能分表到hash环上,这样能够作到最大程度的解决结点变动致使的负责不均衡问题,可是一个物理结点应该分为多少个虚拟结点(这个问题待研究)
四、具体代码实现
1 package com.lexin.hash; 2 3 import java.util.SortedMap; 4 import java.util.TreeMap; 5 6 /** 7 * 8 * 带虚拟节点的一致性Hash算法 9 * 〈功能详细描述〉 10 * @author handyliu 11 * @version 2018-2-27 12 * @see ConsistentHashArithmetic 13 * @since 14 */ 15 public class ConsistentHashArithmetic { 16 /** 17 * 物理的服务器列表 18 * 这些列表就是要加入到Hash环的服务器列表 19 */ 20 private static String[] servers = {"192.168.0.0:3306", "192.168.0.1:3306", "192.168.0.2:3306", 21 "192.168.0.3:3306", "192.168.0.4:3306", "192.168.0.5:3306"}; 22 23 24 /** 25 * 虚拟节点使用TreeMap,key表示虚拟节点的hash值,value表示虚拟节点的名称 26 */ 27 private static SortedMap<Integer, String> virtualNodes = new TreeMap<Integer, String>(); 28 29 /** 30 * 每一个物理结点对应的虚拟节点的数目 31 */ 32 private static final int VIRTUAL_NODES = 5; 33 34 /** 35 * ConsistentHashArithmetic的静态初始化,把结点放到结点列表中 36 */ 37 static { 38 // 先把原始的服务器添加到真实结点列表中 39 for (int i = 0; i < servers.length; i++){ 40 for (int j = 0; j < VIRTUAL_NODES; j++){ 41 String virtualNodeName = servers[i] + "&Node" + String.valueOf(i); 42 int hashValue = getHashValue(virtualNodeName); 43 virtualNodes.put(hashValue, virtualNodeName); 44 System.out.println("虚拟节点[" + virtualNodeName + "]已添加到hash环, hash值为:" + hashValue); 45 } 46 } 47 } 48 49 /** 50 * 使用FNV1_32_HASH算法计算Hash值 51 */ 52 private static int getHashValue(String str){ 53 final int p = 16777619; 54 int hash = (int)2166136261L; 55 for (int i = 0; i < str.length(); i++) 56 hash = (hash ^ str.charAt(i)) * p; 57 hash += hash << 13; 58 hash ^= hash >> 7; 59 hash += hash << 3; 60 hash ^= hash >> 17; 61 hash += hash << 5; 62 63 if (hash < 0) 64 hash = Math.abs(hash); 65 return hash; 66 } 67 68 /** 69 * 获得应当路由到的结点 70 */ 71 private static String getServerNode(String splitKey){ 72 //须要分配的key值 73 int hash = getHashValue(splitKey); 74 // 获取大于该Hash值的全部Map,直接使用tailMap方法 75 SortedMap<Integer, String> subMap = virtualNodes.tailMap(hash); 76 String virtualNode; 77 if(subMap.isEmpty()){ 78 //若是没有比该key的hash值大的,则从第一个node开始 79 Integer i = virtualNodes.firstKey(); 80 //返回对应的服务器 81 virtualNode = virtualNodes.get(i); 82 }else{ 83 //第一个Key就是顺时针过去离node最近的那个结点 84 Integer i = subMap.firstKey(); 85 //返回对应的服务器 86 virtualNode = subMap.get(i); 87 } 88 //virtualNode虚拟节点名称要截取一下 89 if(!"".equals(virtualNode)){ 90 return virtualNode.substring(0, virtualNode.indexOf("&")); 91 } 92 return null; 93 } 94 95 public static void main(String[] args){ 96 String[] uids = {"100","199", "10000", "19999", "1000000", "1999999", "100000000", "199999999"}; 97 for (int i = 0; i < uids.length; i++){ 98 System.out.println("[" + uids[i] + "]的hash值为" + getHashValue(uids[i]) + ", 被路由到结点[" + getServerNode(uids[i]) + "]"); 99 } 100 } 101 }
===================================================
我不能保证写的每一个地方都是对的,可是至少能保证不复制、不黏贴,保证每一句话、每一行代码都通过了认真的推敲、仔细的斟酌。每一篇文章的背后,但愿都能看到本身对于技术、对于生活的态度。
学习是一种信仰。面对压力,挑灯夜战、不眠不休;面对困难,迎难而上、永不退缩。
我是一个纯粹的程序员。
===================================================