一致性Hash算法

资料参考来源拉钩Java高薪训练营


前言

在分布式和集群环境下负载均衡和分布式存储时,常规的Hash算法在服务器扩容和缩容、或者某个节点宕机的情况下影响很大,所以出现了一致性Hash算法,可以在服务器扩容和缩容情况下把影响降到最低。

一、常规Hash算法

1.Hash算法应用

Hash算法主要应用于安全加密领域、数据存储和查找的Hash表。平时使用最多的比如说MD5加密密码、HashMap的使用等。

2.Hash表

这里我们主要说下Hash表,它的查询速度非常的快,只要其中的Hash算法设计的合理的话,查询数据的时间复杂度接近于O(1)。

比如说我们现在有这样一个需求:保存一组数据1,3,4,7,9。

在不使用Hash表的情况下直接使用一个数组保存:[1,3,4,7,9],那么查询只能使用顺序查找法(循环数组一个一个对比查找),或者使用二分查找法,这样在数据量很大的情况下效率就不那么好了。

针对上面的情况我们可以进行一个优化,我们可以定义一个10个长度的数组,然后每个数据都对数组长度10进行取余数,那么这个余数就是这个数据在这个数组中存放的下标。
在这里插入图片描述
从上图可以看到,这几个数字就存放到了对应的下标中了,当我们需要查询某个值时,也可以与数组长度10取余得出下标,直接通过数组下标取出数据,这种方式叫做直接寻址法

与数组长度10取余得出下标这个过程就是Hash算法,这个Hash算法比较简单,计算出来的下标大概率会重复,比如再加上一个数字11,计算出来的下标是1,这个时候就和数据1的位置重复,那这种情况就叫做Hash冲突,怎么解决Hash冲突呢:

开放寻址法:下标为1的位置有数据了,那就向前或者向后找下一个位置,直到找到空闲位置为止进行存储。这个方法也有确定,比如数据量有11个,不管会不会产生Hash冲突,肯定是存放不下的。
拉链法:数组元素存放一个链表,发生Hash冲突时就往这个链表插入就行了。
在这里插入图片描述

Hash表的查询效率⾼不⾼取决于Hash算法,hash算法能够让数据平均分布,既能够节省空间⼜能提⾼查询效率。Hash算法的研究是很深的⼀⻔学问,⽐较复杂,⻓久以来,Hash表内部的Hash算法也⼀直在更新,很多数学家也在研究。

3.Hash算法在分布式架构的应用场景

Hash算法在很多分布式集群产品中都有应用,比如说分布式集群架构Redis、Hadoop、ElasticSearch等。
主要应用场景归纳为两个:

  • 请求负载均衡
    比如说Nginx的IP_hash策略,可以在客户端ip不变的情况下,讲请求始终路由到同一台服务器上,可以避免Session共享问题。
  • 分布式存储
    比如说一个集群中有redis1、redis2、redis3三台Redis服务器,那么可以针对key进行Hash处理hash(key)%3来定位存储到那台服务器上。

4.Hash算法简单代码实现

//服务器ip
String[] services = new String[]{"110.135.13.12","123.111.24.13","10.211.12.14"};
//客户端ip
String[] clientIps = new String[]{"192.168.1.10","192.168.1.11","192.168.1.12","192.168.1.13","192.168.1.14","192.168.1.15"};

for (String clientIp : clientIps) {
    //取余计算下标
    int index = clientIp.hashCode()%services.length;
    String service = services[index];
    System.out.println("客户端:"+clientIp+" 被路由到服务器:"+service);
}

5.Hash算法存在的问题

普通的Hash算法在服务器数量变动下,之前数据的所有计算都需要重新Hash才行。

比如说ip_hash策略为例:假设有3台tomcat服务器,在客户端ip不变的情况下,那么之前负载计算是hash(ip)%3,当tomcat3挂了,计算策略就会变成hash(ip)%2,那么之前在tomcat3的客户端会话都会路由到其他服务器,当然其他客户端的路由服务器也可能会变,当服务器和客户端都很多的情况下。这个影响就很大了。这种情况在服务器扩容和缩容都可能会出现。

针对这种情况,就出现了一致性Hash算法来解决。

二、一致性Hash算法

1.一致性Hash算法思路

在这里插入图片描述
首先是一条直线,相当于一个地址,这条直线弯过来就构成了一个圆环形,这个圆环形就可以称为Hash环。我们首先把服务器ip通过Hash算法定位在这个环上面,然后把客户端ip也通过Hash算法对应到Hash环上,从客户端在Hash环的位置开始,顺时针查找到的第一个服务器就是需要路由的服务器。
在这里插入图片描述
当增加一个服务器节点5后,如下图所示,原先路由到服务器3的客户端有一部分就路由到服务器5上面了,受影响的就只有这一小部分,这样就把服务器扩容缩容的影响讲到最低了。

在这里插入图片描述

2.虚拟节点

但是一致性Hash算法在服务器节点非常少或者Hash算法计算的不均匀的情况下,很容易发生数据倾斜,如下图:
在这里插入图片描述
这时服务器1接收到了大量的请求,二服务器2只能复制少量范围的请求,为了解决数据倾斜的问题,我们可以使用虚拟节点,就是把真实节点计算多个Hash值,每个结算结果都在Hash环上放置这个服务器节点,这个就称为虚拟节点。
在这里插入图片描述

上图所示,服务器1和服务器2都计算三个虚拟节点,当客户端被路由到虚拟节点的时候其实是被路由到对应的真实服务器上的。

3.手写实现简单的一致性Hash算法

//服务器ip
String[] services = new String[]{"110.135.13.12","123.111.24.13","10.211.12.14"};

//每个真实节点的虚拟节点数量
int virtualNum = 3;

SortedMap<Integer,String> serviceMap = new TreeMap();
//计算服务器在Hash环的位置
for (String service : services) {
    //可能为负数,取绝对值
    int hash = Math.abs(service.hashCode());
    serviceMap.put(hash,service);

    for (int i = 0; i < virtualNum; i++) {
        int virtualHash = Math.abs((service+"#"+i).hashCode());
        serviceMap.put(virtualHash,"----由虚拟节点"+ i + "映射过来的请求:"+ service);
    }
}

//客户端ip
String[] clientIps = new String[]{"12.168.122.10","192.112.14.11","34.132.1.12","44.118.12.13","53.144.1.14","23.183.1.15"};

for (String clientIp : clientIps) {
    //计算客户端ip Hash
    int hash = Math.abs(clientIp.hashCode());
    //获取大于客户端ip hash的服务器
    SortedMap<Integer,String> sortedMap = serviceMap.tailMap(hash);
    if (sortedMap.isEmpty()){
        //如果为空,则取第一个服务器
        String service = serviceMap.get(serviceMap.firstKey());
        System.out.println("客户端:"+clientIp+" 被路由到服务器:"+service);
    }else {
        //如果有值,取结果集的第一个
        String service = sortedMap.get(sortedMap.firstKey());
        System.out.println("客户端:"+clientIp+" 被路由到服务器:"+service);
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值