一致性Hash算法·Java篇

一致性哈希

-哈希取模是我们经常使用的负载均衡策略,但是这种策略有一个明显的弊端,当服务扩容或缩容时,大部分key都需要进行移动,当服务器资源不断增长,迁移必定是灾难性的。于是一致性哈希算法孕育而生

原理

-将服务端分片名称进行hash处理并将处理后的值打在一个大小为2^32的闭环圆上,记圆上的点为x
-客户端节点名称同上操作,记圆上的点为y
-y进行顺时针旋转,找到离自己最近的x(闭环圆一定能找到x),x即为客户端要找的服务器分片

问题点

-假设服务器分片较少且哈希分布不均匀,怎么办?
-有哪些哈希算法适用于一致性哈希?
-服务器分片个数变化后,如何快速找到受影响的key并进行key的迁移?


String hashCode源码分析


public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}

关键代码: h = 31 * h + val[i];
直观的看不太容易理解,这里举个例子:字符串”abc”公式转换:
hash = (31*0+a)*31 + b)*31 + c = a*31^2 + b*32 + c;(拆解方式便于理解,并不是数学公式推导)
到这里相信大家已经推敲出计算公式了:hash = [0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1]
那么为什么是31呢?原因其实很简单:31只能被1整除且基数够大,不容易产生hash冲突。
从源码分析String hashCode并不适用,因为如果两个字符串高位值相同,低位值相似的话,hash出来的值则非常接近,一致性Hash值离散度低
比如 ip:”192.168.1.131” 和ip:”192.168.1.131” 对应的hashcode分别是:-2051273165和-2051273195
这里抛一个问题,大家可以思考一下:int类型数值如果乘法溢出了会怎样呢?


代码实现

import com.xx.registry.util.Md5Util;
import javax.validation.constraints.Null;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

/**
* @Auther:
* @Despriction: 一致性hash算法实现, hash固长2^32
* @Date:Created by _chaoziliang
* @Modify By:
*/
public class UniformityHash {

private SortedMap<Integer, String> hashMap;//


private int fictitious = 0;//虚拟节点个数

/**
 * Init
 *
 * @param nodes      节点名称
 * @param fictitious 虚拟节点个数
 */
public UniformityHash(String[] nodes, final int fictitious) {
    hashMap = new TreeMap<>();
    this.fictitious = fictitious;
    for (String node : nodes) {
        addNode(node);
    }
}


/**
 * 从map中找到匹配的节点
 *
 * @param matchNode 匹配节点
 */
public String getMatchNode(String matchNode) {
    //获取大于此hash值节点往下的数据
    SortedMap<Integer, String> tailMap = hashMap.tailMap(getHashCode(matchNode));

    //○闭环
    if (tailMap.size() == 0) {
        return hashMap.get(hashMap.lastKey());
    }
    return tailMap.get(tailMap.firstKey());
}


/**
 * 创建节点
 *
 * @param node 节点名称
 */
public void addNode(String node) {
    for (int i = 0; i <= fictitious; i++) {
        hashMap.put(getHashCode(node + i), node);
    }

}

/**
 * 删除节点
 *
 * @param node 匹配节点
 */
public void removeNode(String node) {
    for (int i = 0; i <= fictitious; i++) {
        hashMap.remove(getHashCode(node + i));
    }
}

/**
 * 一致性hash,md5方式
 *
 * @param node 节点名称
 */
private int getHashCode(String node) {
    int hashResult = -1;
    try {
        String md5Str = Md5Util.encode(node);
        char[] md5Chars = md5Str.toCharArray();
        hashResult = 0;
        for (int i = 0, j = md5Chars.length; i < j; i++) {
            hashResult += ((md5Chars[i] & 1) << (i + 1));
        }
        return hashResult;
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    }
    return hashResult;
}


/**
 * 留一个问题:如何计算扩容、缩容后,变化key前后的node
 *
 * @param args
 */


public static void main(String[] args) {
    Map<String,Null> compareMap = new HashMap<>();//kv
    int change = 0;
    //
    String p_appr = "192.168.1.";
    String c_appr = "172.168.9.";
    String[] ips = {p_appr + "111", p_appr + "112", p_appr + "113", p_appr + "114", p_appr + "115", p_appr + "116", p_appr + "117"};
    UniformityHash uniformity = new UniformityHash(ips, 50);

    System.out.println("增加节点前==============");
    for (int i = 1; i < 5000; i++) {
        String mapping = c_appr + i+"=>"+ uniformity.getMatchNode(c_appr + i);
        compareMap.put(mapping,null);
    }
    System.out.println("ips增加节点前存储个数。。懒的写了");
    System.out.println("增加节点后==============");
    uniformity.addNode(p_appr+"18");
    for (int i = 1; i < 5000; i++) {
        String mapping = c_appr + i+"=>"+ uniformity.getMatchNode(c_appr + i);
        if(!compareMap.containsKey(mapping)){
            change++;
        }
    }
    System.out.println("ips增加节点后存储个数。。懒的写了");
    System.out.println("增加节点后,key值需迁移数:"+change);
}}

控制台结果输出

增加节点前==============
ips增加节点前存储个数。。懒的写了
增加节点后==============
ips增加节点后存储个数。。懒的写了
增加节点后,key值需迁移数:604

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值