欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

普通Hash与一致性Hash

程序员文章站 2022-07-09 19:54:28
Hash算法应⽤场景Hash算法在分布式集群架构中的应⽤场景Hash算法在很多分布式集群产品中都有应⽤,⽐如分布式集群架构Redis、 Hadoop、 ElasticSearch,Mysql分库分表, Nginx负载均衡等应⽤场景主要有2个请求的负载均衡(⽐如nginx的ip_hash策略)Nginx的IP_hash策略可以在客户端ip不变的情况下,将其发出的请求始终路由到同⼀个⽬标服务器上,实现会话粘滞,避免处理session共享问题如果没有IP_hash策略,那么如何实现会话粘滞?可...

Hash算法应⽤场景

Hash算法在分布式集群架构中的应⽤场景
Hash算法在很多分布式集群产品中都有应⽤,⽐如分布式集群架构Redis、 Hadoop、 ElasticSearch,
Mysql分库分表, Nginx负载均衡等

应⽤场景主要有2个

  • 请求的负载均衡(⽐如nginx的ip_hash策略)
    Nginx的IP_hash策略可以在客户端ip不变的情况下,将其发出的请求始终路由到同⼀个⽬标服务
    器上,实现会话粘滞,避免处理session共享问题
    如果没有IP_hash策略,那么如何实现会话粘滞?
    可以维护⼀张映射表,存储客户端IP或者sessionid与具体⽬标服务器的映射关系
    <ip,tomcat1>
    缺点
    1)那么,在客户端很多的情况下,映射表⾮常⼤,浪费内存空间
    2)客户端上下线,⽬标服务器上下线,都会导致重新维护映射表,映射表维护成本很⼤

如果使⽤哈希算法,事情就简单很多,我们可以对ip地址或者sessionid进⾏计算哈希值,哈希值与服务
器数量进⾏取模运算,得到的值就是当前请求应该被路由到的服务器编号,如此,同⼀个客户端ip发送
过来的请求就可以路由到同⼀个⽬标服务器,实现会话粘滞

  • 分布式存储
    以分布式内存数据库Redis为例,集群中有redis1, redis2, redis3 三台Redis服务器
    那么,在进⾏数据存储时,<key1,value1>数据存储到哪个服务器当中呢?针对key进⾏hash处理
    hash(key1)%3=index, 使⽤余数index锁定存储的具体服务器节点

普通Hash算法存在的问题

普通Hash算法存在⼀个问题,以ip_hash为例,假定下载⽤户ip固定没有发⽣改变,现在tomcat3出现
了问题, down机了,服务器数量由3个变为了2个,之前所有的求模都需要重新计算
普通Hash与一致性Hash
如果其中一个节点当机了
普通Hash与一致性Hash
如果在真实⽣产情况下,后台服务器很多台,客户端也有很多,那么影响是很⼤的,缩容和扩容都会存
在这样的问题,⼤量⽤户的请求会被路由到其他的⽬标服务器处理,⽤户在原来服务器中的会话都会丢
失。

⼀致性Hash算法

普通Hash与一致性Hash

⾸先有⼀条直线,直线开头和结尾分别定为为1和2的32次⽅减1,这相当于⼀个地址,对于这样⼀条
线,弯过来构成⼀个圆环形成闭环,这样的⼀个圆环称为hash环。我们把服务器的ip或者主机名求
hash值然后对应到hash环上,那么针对客户端⽤户,也根据它的ip进⾏hash求值,对应到环上某个位
置,然后如何确定⼀个客户端路由到哪个服务器处理呢?按照顺时针⽅向找最近的服务器节点

缩容

假如将服务器3下线,服务器3下线后,原来路由到3的客户端重新路由到服务器4,对于其他客户端没有
影响只是这⼀⼩部分受影响(请求的迁移达到了最⼩,这样的算法对分布式集群来说⾮常合适的,避免
了⼤量请求迁移 )

普通Hash与一致性Hash

扩容

增加服务器5之后,原来路由到3的部分客户端路由到新增服务器5上,对于其他客户端没有影响只是这
⼀⼩部分受影响(请求的迁移达到了最⼩,这样的算法对分布式集群来说⾮常合适的,避免了⼤量请求
迁移 )
普通Hash与一致性Hash

问题
  1. 如前所述,每⼀台服务器负责⼀段,⼀致性哈希算法对于节点的增减都只需重定位环空间中的⼀⼩
    部分数据,具有较好的容错性和可扩展性。

但是,⼀致性哈希算法在服务节点太少时,容易因为节点分部不均匀⽽造成数据倾斜问题。例如系统中
只有两台服务器,其环分布如下,节点2只能负责⾮常⼩的⼀段,⼤量的客户端
请求落在了节点1上,这就是数据(请求)倾斜问题

  1. 为了解决这种数据倾斜问题,⼀致性哈希算法引⼊了虚拟节点机制,即对每⼀个服务节点计算多个
    哈希,每个计算结果位置都放置⼀个此服务节点,称为虚拟节点。

具体做法可以在服务器ip或主机名的后⾯增加编号来实现。⽐如,可以为每台服务器计算三个虚拟节
点,于是可以分别计算 “节点1的ip#1”、 “节点1的ip#2”、 “节点1的ip#3”、 “节点2的ip#1”、 “节点2的
ip#2”、 “节点2的ip#3”的哈希值,于是形成六个虚拟节点,当客户端被路由到虚拟节点的时候其实是被
路由到该虚拟节点所对应的真实节点

普通Hash与一致性Hash

代码简写

普通hash
public class GeneralHash {

    public static void main(String[] args) {
        // 定义客户端IP
        String[] clients = new String[]{"10.78.12.3","113.25.63.1","126.12.3.8"};

        int serverCount = 3;

        for (String client : clients) {
            int hashCode = Math.abs(client.hashCode());
            int index=hashCode%serverCount;
            System.out.println("client "+client+ " hash "+hashCode+"  index : "+index);
        }
    }
}

一致Hash没有虚拟节点
public class ConsitentHashNoVirtural {
    public static void main(String[] args) {
        //step1 初始化:把服务器节点IP的哈希值对应到哈希环上
        // 定义服务器ip
        String[] tomcatServers = new String[]{"123.111.0.240","123.101.3.1","192.20.35.2","10.18.200.1"};
        //对服务端hash

        Integer hash = Integer.MAX_VALUE;

        SortedMap<Integer,String> serversHashMap = new TreeMap<>();

        for (String tomcatServer : tomcatServers) {
            int hashCode = Math.abs(tomcatServer.hashCode());
            int index = hashCode%hash;
            serversHashMap.put(index, tomcatServer);
        }
        System.err.println(serversHashMap);
        //step2 针对客户端IP求出hash值
        // 定义客户端IP
        String[] clients = new String[]{"10.78.12.3","113.25.63.1","126.12.3.8","10.18.200.149"};

        for (String client : clients) {

            int hashCode = Math.abs(client.hashCode());
            int clientIndex = hashCode%hash;


            SortedMap<Integer, String> tailMap = serversHashMap.tailMap(clientIndex);

            if(tailMap.isEmpty()){
               //取哈希环上的顺时针第一台服务器
                Integer integer = serversHashMap.firstKey();
                String serverIndex = serversHashMap.get(integer);
                System.out.println("tailMap null  "+client+ " 获取第一个元素 "+ integer+"  服务器: "+serverIndex);
            }else{

                Integer integer = tailMap.firstKey();
                String serverIndex = serversHashMap.get(integer);

                System.out.println("tailMap 不是空  "+client+ " 获取元素 "+ integer+"  服务器: "+serverIndex);
            }


        }
    }




}
一致Hash有虚拟节点
public class ConsitentHashHasVirtural {
    public static void main(String[] args) {
        //step1 初始化:把服务器节点IP的哈希值对应到哈希环上
        // 定义服务器ip
        String[] tomcatServers = new String[]{"123.111.0.240","123.101.3.1","192.20.35.2","10.18.200.1"};
        //对服务端hash

        Integer hash = Integer.MAX_VALUE;

        Integer virCount=3;

        SortedMap<Integer,String> serversHashMap = new TreeMap<>();

        for (String tomcatServer : tomcatServers) {
            int hashCode = Math.abs(tomcatServer.hashCode());
            int index = hashCode%hash;
            //存储hash值与ip的对应关系
            serversHashMap.put(index, tomcatServer);
            for (Integer i = 0; i < virCount; i++) {

                int virHash=Math.abs((tomcatServer+"#"+i).hashCode());
                int virIndex = virHash%hash;
            //存储虚拟节点hash值与ip的对应关系
                serversHashMap.put(virIndex, tomcatServer);
            }


        }
        serversHashMap.forEach((k,v)-> {
            System.out.println(k+"->"+v);
        });
        //step2 针对客户端IP求出hash值
        // 定义客户端IP
        String[] clients = new String[]{"10.78.12.3","113.25.63.1","126.12.3.8","10.18.200.149"};

        for (String client : clients) {

            int hashCode = Math.abs(client.hashCode());
            int clientIndex = hashCode%hash;


            SortedMap<Integer, String> tailMap = serversHashMap.tailMap(clientIndex);

            if(tailMap.isEmpty()){
               //取哈希环上的顺时针第一台服务器
                Integer integer = serversHashMap.firstKey();
                String serverIndex = serversHashMap.get(integer);
                System.out.println("tailMap null  "+client+ " 获取第一个元素 "+ integer+"服务的key"+serverIndex+"  服务器: "+serverIndex);
            }else{

                Integer integer = tailMap.firstKey();
                String serverIndex = serversHashMap.get(integer);

                System.out.println("tailMap 不是空  "+client+ " 获取元素 "+ integer+"服务的key"+serverIndex+"  服务器: "+serverIndex);
            }


        }
    }




}

本文地址:https://blog.csdn.net/ko0491/article/details/107480972