目的:为了解决分布式系统中缓存key的雪崩问题。
一致性哈希算法在 1997 年由麻省理工学院提出,是一种特殊的哈希算法,在移除或者添加一个服务器时,能够尽可能小地改变已存在的服务请求与处理请求服务器之间的映射关系;
一致性哈希解决了简单哈希算法在分布式哈希表(Distributed Hash Table,DHT)中存在的动态伸缩等问题;
一致性hash是对固定值2^32取模;
一致性Hash的工作原理或算法
① hash环
② 服务器映射到hash环
使用hash(服务器ip)% 2^32
③ 对象key映射到服务器
Key的Hash值:hash(key)% 2^32
此处的Hash函数可以和之前计算服务器映射至Hash环的函数不同
从缓存对象key的位置开始,沿顺时针方向遇到的第一个服务器,便是当前对象将要缓存到的服务器;
**①②③便是一致性Hash的工作原理:**将原本单个点的Hash映射,转变为了在一个环上的某个片段上的映射!
服务器扩缩容场景
① 服务器减少
把缩减服务器上的所有key顺时针移到下一个服务器即可
② 服务器增加
对相邻的两个服务器上的key进行重分配
数据偏斜&服务器性能平衡问题
原因:在Hash环上分配的点太少。导致把环分配的不均衡。
点多,则相应会把环分配的比较均衡。则引入虚拟节点的计算
虚拟节点的计算
hash(10.24.23.227#1)% 2^32hash(10.24.23.227#2)% 2^32hash(10.24.23.227#3)% 2^32
三、一致性Hash算法java实现
一致性Hash算法实现
下面我们根据上面的讲述,实现一个一致性Hash算法,这个算法具有一些下面的功能特性:
- 一致性Hash核心算法;
- 支持自定义Hash算法;
- 支持自定义虚拟节点个数;
java
package com.job.hash;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
/**
* 一致性hash算法
*/
public class HashUtil {
private static Map<Integer,String> nodeMap = new HashMap<Integer, String>();
//1024个虚拟节点
private static int V_NODES = 1024;
private static TreeMap<Integer,String> vNodeMap = new TreeMap<Integer, String>();
private static final int REAL_NODE_COUNT = 8;
static {
nodeMap.put(0,"node_0");
nodeMap.put(1,"node_1");
nodeMap.put(2,"node_2");
nodeMap.put(3,"node_3");
nodeMap.put(4,"node_4");
nodeMap.put(5,"node_5");
nodeMap.put(6,"node_6");
nodeMap.put(7,"node_7");
/**
* 虚拟节点和真实节点的映射
*/
for(int i=0;i<V_NODES;i++){
vNodeMap.put(i,nodeMap.get(i % REAL_NODE_COUNT));
}
}
/**
* 获取真实节点
* @param value
* @return
*/
public static String getRealServerNode(String value){
Integer code = (value.hashCode()& 0x7FFFFFFF) % 1024;
return vNodeMap.ceilingEntry(code).getValue();
}
/**
* 删除一个节点
* @param nodeName
*/
public static void dropNode(String nodeName){
int node = -1;
for(Map.Entry<Integer,String> entry : nodeMap.entrySet()){
if(entry.getValue().equalsIgnoreCase(nodeName)){
node = entry.getKey();
break;
}
}
if(node == -1){
System.out.println("未找到节点:"+nodeName);
return;
}
Iterator<Map.Entry<Integer,String>> iter = vNodeMap.entrySet().iterator();
while (iter.hasNext()){
Map.Entry<Integer,String> entry = iter.next();
Integer key = entry.getKey();
if(key % REAL_NODE_COUNT == node){
System.out.println("删除节点:"+key+",value:"+entry.getValue());
iter.remove();;
}
}
}
public static void main(String[] args) {
String requestValue = "hello jason";
System.out.println("映射关系"+vNodeMap);
System.out.println(getRealServerNode(requestValue));
System.out.println("删除节点");
dropNode("node_3");
System.out.println("映射关系"+vNodeMap);
System.out.println(getRealServerNode(requestValue));
}
}