一致性hash算法一般在分布式系统中运用比较广泛,例如多节点的数据存储,dubbo框架中也实现了一致性hash算法来选择服务提供者。还有在mysql集群,分库分表时,一致性hash也是比较常用的用来划分数据区域的算法。
举个例子,电商公司的订单表,一般都会用数据库集群或者分库分表,用多个数据库实例在承载订单信息,这么大数据量的信息,在查询的时候,首先必须要保证幂等性,就是多次调用hash算法得出的hash值都必须一样,这样才能保证在去查询订单信息的时候,都会到对应的数据库实例中去查询,否则就可能会查不到订单信息,之前经历的就是将订单表拆分为了512张表,按照订单时间来经行hash一致性算法
一致性hash的数据结构:
蓝色表示机器节点,橘黄色表示数据节点
真实节点:A B C D
虚拟节点:A1 A2 B1 B2 C1 C2 D1 D2
数据存放规则:计算出各个虚拟节点的hash值(不一定非要hash算法,可以是符合自己业务逻辑的算法,只要保证有效的数据通过hash算法能够在hash环内都有落点并且具有幂等性),例如:K1的hash值∈(A1,C2),则得出K1应该存放在C2节点
本章用java实现了两种hash一致性算法;
1.没有虚拟节点的hash环(逻辑环)
2.带虚拟节点的hash环(逻辑环)
备注:我这里为了方便测试,直接给对应的真是节点的hash值写死了
实现一:
package com.dubbo.algorithm;
import java.util.SortedMap;
import java.util.TreeMap;
public class ShardHash {
//真实机器节点 <hash值,节点信息>
private TreeMap<Long,String> realNodes = new TreeMap<Long,String>();
public ShardHash(){
init();
}
public void init(){
// A B C D 4个真实节点
realNodes.put(1L, "A");
realNodes.put(6L, "B");
realNodes.put(14L, "C");
realNodes.put(25L, "D");
/*
realNodes.put(hash("A"), "A");
realNodes.put(hash("B"), "B");
realNodes.put(hash("C"), "C");
realNodes.put(hash("D"), "D");
*/
}
public String getNode(String key){
//方便测试
Long keyHash = Long.valueOf(key);
// Long keyHash = hash(key);
//根据key的hash值,找到hash环上面比此hash值大的节点
SortedMap<Long,String> sorted = realNodes.tailMap(keyHash);
if(sorted.isEmpty()){
//没有比key的hash大的节点,则该key存放在第一个节点上面
String node = realNodes.get(realNodes.firstKey());
return node;
}
String node = sorted.get(sorted.firstKey());
return node;
}
/**
* 网上copy的一个hash算法,可以选择适合自己场景的hash算法
*/
private Long hash(String str) {
final int p = 16777619;
Long hash = 2166136261L;
for (int i = 0; i < str.length(); i++)
hash = (hash ^ str.charAt(i)) * p;
hash += hash << 13;
hash ^= hash >> 7;
hash += hash << 3;
hash ^= hash >> 17;
hash += hash << 5;
// 如果算出来的值为负数则取其绝对值
if (hash < 0)
hash = Math.abs(hash);
return hash;
}
public static void main(String[] args) {
ShardHash s = new ShardHash();
String node = s.getNode("33");
System.out.println("数据存放节点为:"+node);
}
}
实现二:
package com.dubbo.algorithm;
import java.util.LinkedList;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
public class ShardHashV {
//真实节点,4个真实节点
private List<String> realNodes = new LinkedList<String>();
//虚拟节点
private TreeMap<Long,String> vNode = new TreeMap<Long,String>();
//每个真实节点对应多少个虚拟节点
private int VNODE_NUM = 4;
//区间大小
private static Long count = 0L;
public ShardHashV(){
init();
}
//0--5--10--15....
public void init(){
realNodes.add("A1");
realNodes.add("B1");
realNodes.add("C1");
realNodes.add("D1");
for(int i=0;i<realNodes.size();i++){
for(int j=0;j<VNODE_NUM;j++){
vNode.put(hash(), "A"+(j+1));
vNode.put(hash(), "B"+(j+1));
vNode.put(hash(), "C"+(j+1));
vNode.put(hash(), "D"+(j+1));
}
}
}
public String getNodes(String key){
//找到所有比key大的节点
SortedMap<Long,String> sm = vNode.tailMap(Long.valueOf(key));
if(sm.isEmpty()){
String node =vNode.get(vNode.firstKey()) ;
return node;
}
String node =vNode.get(sm.firstKey()) ;
return node;
}
public void remove(String key){
vNode.remove(Long.valueOf(key));
}
public static void main(String[] args) {
ShardHashV sv = new ShardHashV();
System.out.println(sv.getNodes("0"));
System.out.println(sv.getNodes("7"));
System.out.println(sv.getNodes("11"));
System.out.println(sv.getNodes("16"));
System.out.println(sv.getNodes("54"));
//删除B1节点
sv.remove("10");
System.out.println(sv.getNodes("7"));
sv.remove("5");
System.out.println(sv.getNodes("3"));
}
/**
* 网上copy的一个hash算法,可以选择适合自己场景的hash算法
*/
private Long hash() {
Long key = this.count+5L;
count=count+5L;
return key;
}
}