偶识一致性 Hash 算法

分布式 专栏收录该内容
1 篇文章 0 订阅

1 Hash 算法应用场景

主要的应用场景归纳起来有两个

  • 请求的负载均衡

比如 Nginx 的 ip_hash 可以在客户端 ip 不变的情况下,将其请求始终路由到同一台服务器上,实现了会话粘滞,避免了 session 共享问题。

原理:对 ip 地址或者 sessionId 进⾏计算哈希值,哈希值与服务器数量进⾏取模运算,得到的值就是当前请求应该被路由到的服务器编号

upstream lagouServer{
  ip_hash;
  server 111.229.248.243:8080;
  server 111.229.248.243:8082;
}

如果没有 ip_hash 策略,那么如何实现会话粘滞?

可以维护⼀张映射表,存储客户端 ip   或者  sessionId  与具体⽬标服务器的映射关系
<ip,tomcat1> 或 <sessionId,tomcat1>
缺点
1 )在客户端很多的情况下,映射表⾮常⼤,浪费内存空间
2 )客户端上下线,⽬标服务器上下线,都需要重新维护映射表,映射表维护成本⼤
  • 分布式存储

假设集群中现在有 3 台 Redis 服务器:reids1,redis2,redis3。那么,在进行数据存储时,<key1,value1> 存储到哪个服务器中呢?这时可以采用 hash 算法,对 key 进行 hash 处理。

hash(key1) % 3(服务器数量) = index,index 就是存储该数据的服务器编号。

2 普通 Hash 环算法存在的问题

以 ip_hash 为例,假定客户端 ip 没有发⽣改变,现在 tomcat3 机了,服务器数量由 个变为了 个,之前所有的求模都需要重新计算。

如果在⽣产环境下,后台服务器数量多,客户端也有很多,那么影响是很⼤的。服务器的缩容和扩容都会存在这样的问题,⼤量⽤户的请求会被路由到其他的⽬标服务器,⽤户在原来服务器中的会话都会丢失。

3 一致性 Hash 算法

3.1 基本思路

3.2 手写实现一致性 Hash 算法

(1) 普通 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 = 5;// (编号对应0,1,2)
        // hash(ip)%node_counts=index
        //根据index锁定应该路由到的tomcat服务器
        for(String client: clients) {
            int hash = Math.abs(client.hashCode());
            int index = hash%serverCount;
            System.out.println("客户端:" + client + " 被路由到服务器编号为:" + index);
        }
    }
}

(2) 一致性 Hash 算法实现(不含虚拟节点)

public class ConsistentHashNoVirtual {
    public static void main(String[] args) {
        //step1 初始化:把服务器节点IP的哈希值对应到哈希环上
        // 定义服务器ip
        String[] tomcatServers = new String[]{"123.111.0.0", "123.101.3.1", "111.20.35.2", "123.98.26.3"};
        SortedMap<Integer, String> hashServerMap = new TreeMap<>();
        for (String tomcatServer : tomcatServers) {
            // 求出每⼀个ip的hash值,对应到hash环上,存储hash值与ip的对应关系
            int serverHash = Math.abs(tomcatServer.hashCode());
            // 存储hash值与ip的对应关系
            hashServerMap.put(serverHash, tomcatServer);
        }
        //step2 针对客户端IP求出hash值
        // 定义客户端IP
        String[] clients = new String[]{"10.78.12.3", "113.25.63.1", "126.12.3.8"};
        for (String client : clients) {
            int clientHash = Math.abs(client.hashCode());
            //step3 针对客户端,找到能够处理当前客户端请求的服务器(哈希环上顺时针最近)
            // 根据客户端ip的哈希值去找出哪⼀个服务器节点能够处理()
            // tailMap 返回 key 大于或等于 fromKey 的数据
            SortedMap<Integer, String> integerStringSortedMap = hashServerMap.tailMap(clientHash);
            if (integerStringSortedMap.isEmpty()) {
                // 取哈希环上的顺时针第⼀台服务器
                Integer firstKey = hashServerMap.firstKey();
                System.out.println("==========>>>>客户端:" + client + " 被路由到服务器:" + hashServerMap.get(firstKey));
            } else {
                Integer firstKey = integerStringSortedMap.firstKey();
                System.out.println("==========>>>>客户端:" + client + " 被路由到服务器:" + hashServerMap.get(firstKey));
            }
        }
    }
}

(3) 一致性 Hash 算法实现(含虚拟节点)

public class ConsistentHashWithVirtual {
    public static void main(String[] args) {
        //step1 初始化:把服务器节点IP的哈希值对应到哈希环上
        // 定义服务器ip
        String[] tomcatServers = new String[]{"123.111.0.0", "123.101.3.1", "111.20.35.2", "123.98.26.3"};
        SortedMap<Integer, String> hashServerMap = new TreeMap<>();
        // 定义针对每个真实服务器虚拟出来⼏个节点
        int virtaulCount = 3;
        for (String tomcatServer : tomcatServers) {
            // 求出每⼀个ip的hash值,对应到hash环上,存储hash值与ip的对应关系
            int serverHash = Math.abs(tomcatServer.hashCode());
            // 存储hash值与ip的对应关系
            hashServerMap.put(serverHash, tomcatServer);
            // 处理虚拟节点
            for (int i = 0; i < virtaulCount; i++) {
                int virtualHash = Math.abs((tomcatServer + "#" + i).hashCode());
                hashServerMap.put(virtualHash, "----由虚拟节点" + i + "映射过来的请求:" + tomcatServer);
            }
        }
        //step2 针对客户端IP求出hash值
        // 定义客户端IP
        String[] clients = new String[]{"10.78.12.3", "113.25.63.1", "126.12.3.8"};
        for (String client : clients) {
            int clientHash = Math.abs(client.hashCode());
            //step3 针对客户端,找到能够处理当前客户端请求的服务器(哈希环上顺时针最近)
            // 根据客户端ip的哈希值去找出哪⼀个服务器节点能够处理()
            SortedMap<Integer, String> integerStringSortedMap = hashServerMap.tailMap(clientHash);
            if (integerStringSortedMap.isEmpty()) {
                // 取哈希环上的顺时针第⼀台服务器
                Integer firstKey = hashServerMap.firstKey();
                System.out.println("==========>>>>客户端:" + client + " 被路由到服务器:" + hashServerMap.get(firstKey));
            } else {
                Integer firstKey = integerStringSortedMap.firstKey();
                System.out.println("==========>>>>客户端:" + client + " 被路由到服务器:" + hashServerMap.get(firstKey));
            }
        }
    }
}

 

 

 

 

 

 

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 精致技术 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值