写点什么

一致性哈希实现

用户头像
elfkingw
关注
发布于: 2020 年 07 月 08 日
一致性哈希实现

一致性哈希是分布式系统中实现复杂均衡的关键技术,负载均衡的要求是把资源分布在不同的服务器节点上,一致性哈希就解决了改问题。

一致型哈希的原理是,先构造出一个长度为232整数环,根据N0-3的节点名称的hash值(分布为[0,232-1])放到这个环上,数据也放在环上,数据落在就近的服务上。由于服务个数未知,当服务器个数比较少时,会出现负载不均衡。当服务节点增加或则减少时,也会出现大量的数据失效,这时就需要增加虚拟节点来实现。

实现一直哈希只有两个点:

1.哈希函数

2.数据哈希离服务节点哈希最近距离

代码实现如下:

服务节点 Node.java

public class Node {
private String serverName;
private Long hashCode;
List<VisualNode> visualNodes;

public Node(String serverName) {
this.serverName = serverName;
this.hashCode = HashMethod.hash(serverName, 0);
}

public void addVisualNode(VisualNode visualNode) {
if (visualNodes == null) {
visualNodes = new ArrayList<VisualNode>();
}
visualNodes.add(visualNode);
}

public String getServerName() {
return serverName;
}

public void setServerName(String serverName) {
this.serverName = serverName;
}

public Long getHashCode() {
return hashCode;
}

public void setHashCode(Long hashCode) {
this.hashCode = hashCode;
}

public List<VisualNode> getVisualNodes() {
return visualNodes;
}

@Override
public String toString() {
return "Node{" +
"serverName='" + serverName + '\'' +
", hashCode=" + hashCode +
", visualNodes=" + visualNodes +
'}';
}

虚拟节点VisualNode.java

public class VisualNode {
private String serverName;
private long hashCode;

public VisualNode(String serverName) {
this.serverName = serverName;
this.hashCode = HashMethod.hash(serverName, 0);
}

public String getServerName() {
return serverName;
}

public void setServerName(String serverName) {
this.serverName = serverName;
}

public long getHashCode() {
return hashCode;
}

public void setHashCode(long hashCode) {
this.hashCode = hashCode;
}
}

hash函数类 HashMethod.java

public class HashMethod {

public static Long hash(String nodeName, int number) {
byte[] digest = md5(nodeName);
return (((long) (digest[3 + number * 4] & 0xFF) << 24)
| ((long) (digest[2 + number * 4] & 0xFF) << 16)
| ((long) (digest[1 + number * 4] & 0xFF) << 8)
| (digest[number * 4] & 0xFF))
& 0xFFFFFFFFL;
}

private static byte[] md5(String str) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.reset();
md.update(str.getBytes("UTF-8"));
return md.digest();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
}
}

}


一致性hash类 HashBalance.java

public class HashBalance {

private List<Node> servers;
private String[] serverNames;
/**
* 每台服务器设置的虚拟服务器个数
*/
private int visualNodeNum ;
/**
* 虚拟服务器和主服务器的哈希值 value对应的主服务器信息
*/
private TreeMap<Long, Node> serverMap;
/**
* 统计命中信息
*/
private Map<Node, Long> countMap;
/**
* 测试数据的总个数
*/
private long totalCount = 0;

public HashBalance(String[] serverNames, int visualNodeNum) {
this.serverNames = serverNames;
this.visualNodeNum = visualNodeNum;
//初始化服务器信息
initialization();
}

private void initialization() {
countMap = new HashMap<>(10);
serverMap = new TreeMap<>();
servers = new ArrayList<>(serverNames.length);
for (String serverName : serverNames) {
Node node = new Node(serverName);
serverMap.put(node.getHashCode(), node);
for (int i = 0; i < visualNodeNum; i++) {
String visualServerName = serverName + i;
VisualNode visualNode = new VisualNode(visualServerName);
node.addVisualNode(visualNode);
serverMap.put(visualNode.getHashCode(), node);
}
servers.add(node);
}
}

public List<Node> getServers() {
return servers;
}


public void printNodes() {
List<Node> nodes = getServers();
for (Node node : nodes) {
System.out.println(node.getServerName() + ":" + node.getHashCode());
List<VisualNode> visualNodes = node.getVisualNodes();
for (VisualNode visualNode : visualNodes) {
System.out.println("\t" + visualNode.getServerName() + ":" + visualNode.getHashCode());
}
}
}

/**
* 测试放数据到服务,统计信息
* @param key 数据库的key
*/
public void putKeyCount(String key) {
Node node = selectNodeByKey(key);
totalCount++;
if (countMap.containsKey(node)) {
long count = countMap.get(node);
countMap.put(node, count + 1);
}else{
countMap.put(node,1L);
}
}

/**
* 根据数据的key值来选择数据落在哪台服务器,
* hash值离服务器的Hash值最近
* @param key 数据的Key值
* @return 服务器节点
*/
public Node selectNodeByKey(String key) {
Long hashcode = HashMethod.hash(key, 0);
// 所有大于 hash 的节点
SortedMap<Long, Node> tailMap = serverMap.tailMap(hashcode);
//取大于数据key的哈希值的第一个服务器
Long fistKey = tailMap.isEmpty() ? serverMap.firstKey() : tailMap.firstKey();
return serverMap.get(fistKey);
}

/**
* 打印统计信息
*/
public void printStatistics() {
for (Map.Entry<Node, Long> entry : countMap.entrySet()) {
Node node = entry.getKey();
long hitsCount = entry.getValue();
long percent = (int) (100 * hitsCount / totalCount);
System.out.println("服务器节点【"+node.getServerName()+"】数据命中个数:【"+hitsCount+"】 分布率:【"+percent+"%】");
}
}


private Node getNodeByHashCode(Long hashCode) {
return serverMap.get(hashCode);
}


public static void main(String[] args) {
long count = 10000000;
String[] serverNames = new String[]{"192.168.1:8080", "192.168.2:8080", "192.168.3:8080",
"192.168.4:8080", "192.168.5:8080", "192.168.6:8080", "192.168.7:8080", "192.168.8:8080", "192.168.9:8080", "192.168.10:8080"};
HashBalance hashBalance = new HashBalance(serverNames, 150);
hashBalance.printNodes();
for(int i=0;i<count;i++){
hashBalance.putKeyCount(i+"asf");
}
hashBalance.printStatistics();

}
}


测试数据100w条数据,10台服务器,每台服务器有160个虚拟服务器测试结果如下:

服务器节点【192.168.1:8080】数据命中个数:【1010415】 分布率:【10%】
服务器节点【192.168.1:8080】数据命中个数:【1018206】 分布率:【10%】
服务器节点【192.168.9:8080】数据命中个数:【1044003】 分布率:【10%】
服务器节点【192.168.7:8080】数据命中个数:【1066576】 分布率:【10%】
服务器节点【192.168.6:8080】数据命中个数:【1032550】 分布率:【10%】
服务器节点【192.168.10:8080】数据命中个数:【908494】 分布率:【9%】
服务器节点【192.168.8:8080】数据命中个数:【859942】 分布率:【8%】
服务器节点【192.168.4:8080】数据命中个数:【1055379】 分布率:【10%】
服务器节点【192.168.5:8080】数据命中个数:【1029161】 分布率:【10%】
服务器节点【192.168.2:8080】数据命中个数:【931117】 分布率:【9%】
服务器节点【192.168.3:8080】数据命中个数:【1054572】 分布率:【10%】



用户头像

elfkingw

关注

还未添加个人签名 2018.02.04 加入

还未添加个人简介

评论 (1 条评论)

发布
用户头像
Should be VirtualNode

VisualNode

2020 年 10 月 19 日 08:47
回复
没有更多了
一致性哈希实现