一致性 hash 算法
题目:
用你熟悉的编程语言实现一致性 hash 算法。
编写测试用例测试这个算法,测试 100 万 KV 数据,10 个服务器节点的情况下,计算这些 KV 数据在服务器上分布数量的标准差,以评估算法的存储负载不均衡性。
主要思想:
首先,理解题意。
一致性算法是什么?
简单来说,一致性哈希将整个哈希值空间组织成一个虚拟的圆环,如假设某哈希函数H的值空间为0-2^32-1(即哈希值是一个32位无符号整形),整个哈希空间环如下,整个空间按顺时针方向组织。0和232-1在零点中方向重合。下一步将各个服务器使用Hash进行一个哈希,具体可以选择服务器的ip或主机名作为关键字进行哈希,这样每台机器就能确定其在哈希环上的位置。
标准差是什么?
标准差(Standard Deviation) ,是离均差平方的算术平均数的平方根,用σ表示。在概率统计中最常使用作为统计分布程度上的测量。标准差是方差的算术平方根。标准差能反映一个数据集的离散程度。平均数相同的两组数据,标准差未必相同。
其次,要达到以上目的,要选择合适的数据结构,计算hash值,从而具有:均衡性(在整个环中分布尽量均匀),不变性(每次更新等操作原来的值不变)。
具体实现如下:
1、选取计算hash的算法和计算标准差的工具类 ConsistentUtil
public class ConsistentUtil { /** * 计算Hash值, 使用FNV1_32_HASH算法,memcached官方使用了基于md5的KETAMA算法 * @param str * @return */ public 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; } /** * 标准差(Standard Deviation) ,是离均差平方的算术平均数的平方根,用σ表示。 * @param statMap * @return */ public static double computeStd(Map<String,Integer> statMap) { Integer[] hitData = new Integer[statMap.size()]; statMap.values().toArray(hitData); double avg = Arrays.stream(hitData).mapToInt(Integer::intValue).average().orElse(0d); double avgStd = Arrays.stream(hitData).map(count -> Math.pow(count - avg, 2)).mapToDouble(Double::doubleValue).average().orElse(0d); double std = Math.sqrt(avgStd); return std; } }
2、对一致性hash算法的接口IConsistHash
public interface IConsistHash { /** * 构建环 */ SortedMap<Integer,String> SERVER_SORTED_MAP = new TreeMap<>(); /** * 虚拟节点 */ int VIRTUAL_NODE_NUM = 200; /** * 统计命中服务的次数 */ Map<String,Integer> SERVER_HIT_RET = Maps.newHashMap(); /** * 加入统计 * @param node */ default void addStat(String node){ if (!SERVER_HIT_RET.containsKey(node)){ SERVER_HIT_RET.put(node,1); }else { SERVER_HIT_RET.put(node, SERVER_HIT_RET.get(node)+1); } } SortedMap<Integer,String> putNodetoHashring(List<Server> serverList); /** * 根据访问key,得到所在服务node * @param key * @return */ String getServer(String key); public void setVnode(int vnode);}
3、实现IConsistHash接口,一个不带虚拟节点,一个带有虚拟节点
其中带有虚拟节点的如下:
@Setter @Getterpublic class ConsistHashWithVirtualNodeimpl extends AbstractConsistHash implements IConsistHash{ //虚拟节点,方便测试用 private int vnode = 0; @Override public SortedMap<Integer, String> putNodetoHashring(List<Server> serverList) { serverList.forEach(server -> { int hash = ConsistentUtil.getHash(server.getDomainName()); SERVER_SORTED_MAP.put(hash,server.getDomainName()); //int hash = 0; if (this.getVnode() !=0){ for (int i=0;i< vnode ;i++){ String vnName = getVirtualNode(server.getDomainName(),i); hash = ConsistentUtil.getHash(vnName); SERVER_SORTED_MAP.put(hash,server.getDomainName()); } }else { for (int i=0;i< VIRTUAL_NODE_NUM;i++){ String vnName = getVirtualNode(server.getDomainName(),i); hash = ConsistentUtil.getHash(vnName); SERVER_SORTED_MAP.put(hash,server.getDomainName()); } } }); return SERVER_SORTED_MAP; } public static String getVirtualNode(String realName, int num) { return realName + "#" + String.valueOf(num); }}
4、ServerConfigImpl 实现IServerConfig 接口,实现对服务节点添加和删除。操作的时候同时刷新hash环
public class ServerConfigImpl implements IServerConfig { private List<Server> groups = Lists.newArrayList(new Server("198.168.10.1") ); private IConsistHash consistHash; public ServerConfigImpl (IConsistHash consistHash){ this.consistHash = consistHash; refreshHashring(); } @Override public List<Server> getAllServer(){ return groups; } @Override public void add(Server server) { if (groups.contains(server)){ return; } groups.add(server); refreshHashring(); } @Override public void remove(Server server) { groups.remove(server); refreshHashring(); } private void refreshHashring(){ IConsistHash.SERVER_SORTED_MAP.clear(); // 统计结果清除 IConsistHash.SERVER_HIT_RET.clear(); consistHash.putNodetoHashring(groups); }}
5、抽象类AbstractConsistHash实现对hash的读取所在的服务器
public abstract class AbstractConsistHash implements IConsistHash{ @Override public String getServer(String key){ int keyHash = ConsistentUtil.getHash(key); // 只取出大于等于该hash值的部分,从中获取第一个server SortedMap<Integer,String> sortedMap = SERVER_SORTED_MAP.tailMap(keyHash); // hash值在最尾部,应该在第一个server上 String node ; if (CollectionUtil.isEmpty(sortedMap)){ node = SERVER_SORTED_MAP.get(SERVER_SORTED_MAP.firstKey()); }else { node = sortedMap.get(sortedMap.firstKey()); } // 加入统计 addStat(node); return node; } @Override public void setVnode(int vnode) { }}
6、测试结果
测试类:
public class ConsisHashingWithVirtualNodeTest { private static double printStd(IConsistHash consistHash){ int requestCount = 100*10000; for (int i = 0; i < requestCount; i++) { consistHash.getServer(""+i); } return ConsistentUtil.computeStd(IConsistHash.SERVER_HIT_RET); } public static void main(String[] args) { //serverConfig.remove(new Server("memcache1")); //printStd(consistHash);// int vnode = 200;// for (int i=1;i<vnode;i++){// IConsistHash consistHash = new ConsistHashWithVirtualNodeimpl();// consistHash.setVnode(i);// IServerConfig serverConfig = new ServerConfigImpl(consistHash);// log.info("vnode num:{},std:{}",i,printStd(consistHash));// } IConsistHash consistHash = new ConsistHashWithVirtualNodeimpl(); IServerConfig serverConfig = new ServerConfigImpl(consistHash); for (int i=1;i<11;i++){ serverConfig.add(new Server("198.168.10."+i)); System.out.println("服务器数量:"+i+",标准差:"+printStd(consistHash)+", 每个服务器上的key数量:"+ IConsistHash.SERVER_HIT_RET.values()); } }}
结果如下:
服务器数量:1,标准差:0.0, 每个服务器上的key数量:[1000000]服务器数量:2,标准差:16627.0, 每个服务器上的key数量:[516627, 483373]服务器数量:3,标准差:14155.14088787376, 每个服务器上的key数量:[313638, 346283, 340079]服务器数量:4,标准差:14865.209500710038, 每个服务器上的key数量:[229803, 245650, 271099, 253448]服务器数量:5,标准差:15007.06248404397, 每个服务器上的key数量:[192299, 185260, 219258, 216979, 186204]服务器数量:6,标准差:14202.605320464583, 每个服务器上的key数量:[157092, 158440, 160552, 186063, 186634, 151219]服务器数量:7,标准差:10015.700680553957, 每个服务器上的key数量:[136393, 134230, 139076, 143512, 155872, 159393, 131524]服务器数量:8,标准差:9190.120687455634, 每个服务器上的key数量:[126396, 116342, 110036, 130296, 127076, 136745, 136547, 116562]服务器数量:9,标准差:7092.908108251422, 每个服务器上的key数量:[114146, 99332, 114742, 98971, 113789, 114702, 121486, 114277, 108555]服务器数量:10,标准差:7164.8596078360115, 每个服务器上的key数量:[98090, 91868, 99125, 89392, 96058, 113547, 100062, 111314, 99035, 101509]
ashuai1106
还未添加个人签名 2017.10.20 加入
还未添加个人简介
评论 (1 条评论)