一致性Hash算法在分布式场景的应用

普通hash算法在集群中的应用

Nginx ip_hash策略示意图
在这里插入图片描述
下面是上面策略的简单代码实现,模拟出它是如何将客户端请求分配到不同节点,而且还保证session的一致性。

package demo;

import java.util.ArrayList;
import java.util.List;

/**
 * 
 * @author qiu
 *
 */
public class HashUtils {

	public static void main(String[] args) {
          //模拟客户端IP
		 List<String> clientList = new ArrayList<>();
		 clientList.add("10.20.12.1");
		 clientList.add("126.10.53.3");
		 clientList.add("101.0.2.4");

		 //定义服务器数量(编号对应0,1,2)
		 int serverCount = 3; 
		 
		 //路由计算
		 clientList.forEach( c-> {
			int hash = Math.abs(c.hashCode());  //hash值自定义
			int index = hash % serverCount; 
			System.out.println("客户端:"+c+"被路由到编号为》》》》"+index+"的服务器"); 
		 });
		 
		
	}

}

print

客户端:10.20.12.1被路由到编号为》》》》0的服务器
客户端:126.10.53.3被路由到编号为》》》》1的服务器
客户端:101.0.2.4被路由到编号为》》》》1的服务器

问题

以上hash算法思想也存在一些问题,比如当服务器节点,扩容或宕机时由于服务器的节点改变,在重新求模运算时,会导致客户端 落到和之前不同的节点服务器上,session会话就消失了。在真实环境中会有大量请求命中不到原来目标服务器上。

解决方案(一致性Hash算法)

思想:
1.服务器节点定位
每台服务器节点都会有主机名(这里就取节点服务器的ip地址)下图中hash环中绿色的点就代表着节点服务器ip经过某个hash算法后得到的一个整数,这个整数自然就会落到上面直线的区间中的某个位置。每个服务器节点都经过一次取值后落到下面hash环上。
2.客户端请求处理定位
客户端(它们也都有自己的IP地址)每一次请求经过hash算法取值后也都会落到hash环上如下图(蓝色笑脸节点),定位到hash环上之后,客户端就会路由到离自己节点最近的服务器节点上(注意:顺势针方向搜索
在这里插入图片描述

3.客服端扩容和缩容
缩容
假设服务器3下线后,原来路由到3的客户端,重新(它会去找离它最近的服务器节点4)路由到服务器4,对于其它客户端没有影响,只是一小部分受到影响(请求迁移达到最小,这样的算法对分布式集群来说非常适合,避免了大量请求迁移)
在这里插入图片描述
扩容
在这里插入图片描述

总结

由此可见以上一致性hash算法解决了服务器节点扩容或宕机时避免大量请求迁移

问题

当服务器节点比较少时,容易因为节点分布不均匀而造成数据倾斜问题
节点1只负责一小段,大量请求落到节点2上。
在这里插入图片描述

代码模拟

package demo;

import java.util.ArrayList;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;

/**
 * 
 * @author qiu
 *
 */
public class HashUtils {

	public static void main(String[] args) {
		 
		 //初始化:把服务器节点IP的哈希值对应到哈希环上
		 List<String> tomcatServerList = new ArrayList<>();
		 tomcatServerList.add("10.20.12.1");
		 tomcatServerList.add("126.10.53.3");
		 tomcatServerList.add("101.0.2.4");
		 
		 //模拟hash环(有序)
		 SortedMap<Integer,String> hashServermap = new TreeMap<>(); 
		 
		 tomcatServerList.forEach( t-> {
			 //求出每一个ip的hash值,对应到hash环上,存储hash值与ip的对应关系
				int serverHash = Math.abs(t.hashCode());  //hash值自定义
				hashServermap.put(serverHash, t);
			 });
		 
		 //针对客户端IP求hash值
		 List<String> clientList = new ArrayList<>();
		 clientList.add("101.40.12.7");
		 clientList.add("226.30.53.6");
		 clientList.add("301.10.2.4");
		 
		 clientList.forEach(c->{
			 
			 int clientHash = Math.abs(c.hashCode());  //hash值自定义
			  //客户端找到能够处理当前请求的服务器(hash环上顺时针最近)
			  //根据客户端ip的hash值去找出哪一个服务器节点能够处理
			 //tailMap方法返回比当前key大的新集合
			 SortedMap<Integer,String> tailHashServermap =  hashServermap.tailMap(clientHash);
			 if (tailHashServermap.isEmpty()) {
				 //取hash环上的顺时针第一台服务器
				Integer firstKey =  hashServermap.firstKey();
				System.out.println("客户端:"+c+"被路由到编号为》》》》"+hashServermap.get(firstKey)+"的服务器");
				
			 } else {
				    Integer firstKey =  tailHashServermap.firstKey();
					System.out.println("客户端:"+c+"被路由到编号为》》》》"+hashServermap.get(firstKey)+"的服务器");
			 }
		 });
		
	}

}

解决方案(一致性Hash算法+虚拟节点)

为了解决一致性Hash算法中的数据倾斜问题,一致性hash算法引入了虚拟节点机制,即对每一个服务器节点计算多个哈希,每个计算结果位置都放置一个此服务节点,称为虚拟节点。当请求落到某个虚拟节点负责的段时,它就会去找真实节点,来处理请求,这样就会让请求处理的均匀了。
如下图:分别对节点1和节点2计算三个虚拟节点:#1,#2,#3.
在这里插入图片描述

代码模拟

package demo;

import java.util.ArrayList;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;

/**
 * 
 * @author qiu
 *
 */
public class HashUtils {

	public static void main(String[] args) {
		 
		 //初始化:把服务器节点IP的哈希值对应到哈希环上
		 List<String> tomcatServerList = new ArrayList<>();
		 tomcatServerList.add("10.20.12.1");
		 tomcatServerList.add("126.10.53.3");
		 tomcatServerList.add("101.0.2.4");
		 
		 //模拟hash环(有序)
		 SortedMap<Integer,String> hashServermap = new TreeMap<>(); 
		 
		 
		 //定义针对每个真实服务器虚拟出来几个节点
		 int virtaulCount = 3;
		 
		 
		 
		 
		 tomcatServerList.forEach( t-> {
			 //求出每一个ip的hash值,对应到hash环上,存储hash值与ip的对应关系
				int serverHash = Math.abs(t.hashCode());  //hash值自定义
				hashServermap.put(serverHash, t);
				
				//处理虚拟节点
				for(int i=0;i<virtaulCount;i++) {
					
					int virtaulHash = Math.abs((t+"#"+i).hashCode());
					hashServermap.put(virtaulHash, "由虚拟节点转换成的真实节点》》"+i+":"+t);
					
				}
			 });
		 
		 //针对客户端IP求hash值
		 List<String> clientList = new ArrayList<>();
		 clientList.add("101.40.12.7");
		 clientList.add("226.30.53.6");
		 clientList.add("301.10.2.4");
		 
		 clientList.forEach(c->{
			 
			 int clientHash = Math.abs(c.hashCode());  //hash值自定义
			  //客户端找到能够处理当前请求的服务器(hash环上顺时针最近)
			  //根据客户端ip的hash值去找出哪一个服务器节点能够处理
			 //tailMap方法返回比当前key大的新集合
			 SortedMap<Integer,String> tailHashServermap =  hashServermap.tailMap(clientHash);
			 if (tailHashServermap.isEmpty()) {
				 //取hash环上的顺时针第一台服务器
				Integer firstKey =  hashServermap.firstKey();
				System.out.println("客户端:"+c+"被路由到编号为》》》》"+hashServermap.get(firstKey)+"的服务器");
				
			 } else {
				    Integer firstKey =  tailHashServermap.firstKey();
					System.out.println("客户端:"+c+"被路由到编号为》》》》"+hashServermap.get(firstKey)+"的服务器");
			 }
		 });
		
	}

}

print

客户端:101.40.12.7被路由到编号为》》》》由虚拟节点转换成的真实节点》》2:10.20.12.1的服务器
客户端:226.30.53.6被路由到编号为》》》》由虚拟节点转换成的真实节点》》2:101.0.2.4的服务器
客户端:301.10.2.4被路由到编号为》》》》101.0.2.4的服务器

总结

由此可见一致性Hash算法+虚拟节点可以解决数据倾斜问题

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值