Java 一致性Hash算法的学习
目前我们很多时候都是在做分布式系统,但是我们需把客户端的请求均匀的分布到N个服务器中,一般我们可以考虑通过Object的HashCodeHash%N,通过取余,将客户端的请求分布到不同的的服务端。但是在分布式集群中我们通常需要添加或删除服务器,所以通过取余是不行的。一致性Hash就是为了解决这个问题。
Consistent Hashing 一致性Hash的原理
1、环型Hash空间
根据常用的Hash,是将key哈希到一个长为2^32的桶中,即0~2^32-1的数字空间,最后通过首尾相连,我们可以想象成一个闭合的圆。如图:
2、把数据通过一定的Hash算法处理后,映射到环上
例如:我们有Object1、Object2、Object3、Object4,通过Hash算法求出值如下:
Hash(Object1) = key1;
Hash(Object2) = key2;
Hash(Object3) = key3;
Hash(Object4) = key4;
3、将机器信息通过hash算法映射到环上
一般情况下是对机器的信息通过计算hash,然后以顺时针方向计算,将对象信息存储在相应的位置。
4、虚拟节点
上面是Hash算法的特性,但是Hash算法缺少一个平衡性。
Hash算法的平衡行就是为了尽可能使分配到每个数据桶里面的节点是均衡的,一个简单的例子:我们有3个分布式服务器,在大量客户端访问时,通过Hash算法,使得他们能在每个服务器均匀的访问。所以这里引入了“虚拟节点”节点,从而保证数据节点均衡。
“虚拟节点”就是真实节点的复制品,一个真实的节点对应多个“虚拟节点”,这样使得我们的节点能尽可能的在环形Hash空间均匀分布,这样我们再根据虚拟节点找到真实节点,从而保证每个真实节点上分配到的请求是均衡的。
具体的代码实现如下:
import java.util.LinkedList;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap; public class ConsistencyHashing { // 虚拟节点的个数
private static final int VIRTUAL_NUM = 5; // 虚拟节点分配,key是hash值,value是虚拟节点服务器名称
private static SortedMap<Integer, String> shards = new TreeMap<Integer, String>(); // 真实节点列表
private static List<String> realNodes = new LinkedList<String>(); //模拟初始服务器
private static String[] servers = { "192.168.1.1", "192.168.1.2", "192.168.1.3", "192.168.1.5", "192.168.1.6" }; static {
for (String server : servers) {
realNodes.add(server);
System.out.println("真实节点[" + server + "] 被添加");
for (int i = 0; i < VIRTUAL_NUM; i++) {
String virtualNode = server + "&&VN" + i;
int hash = getHash(virtualNode);
shards.put(hash, virtualNode);
System.out.println("虚拟节点[" + virtualNode + "] hash:" + hash + ",被添加");
}
}
} /**
* 获取被分配的节点名
*
* @param node
* @return
*/
public static String getServer(String node) {
int hash = getHash(node);
Integer key = null;
SortedMap<Integer, String> subMap = shards.tailMap(hash);
if (subMap.isEmpty()) {
key = shards.lastKey();
} else {
key = subMap.firstKey();
}
String virtualNode = shards.get(key);
return virtualNode.substring(0, virtualNode.indexOf("&&"));
} /**
* 添加节点
*
* @param node
*/
public static void addNode(String node) {
if (!realNodes.contains(node)) {
realNodes.add(node);
System.out.println("真实节点[" + node + "] 上线添加");
for (int i = 0; i < VIRTUAL_NUM; i++) {
String virtualNode = node + "&&VN" + i;
int hash = getHash(virtualNode);
shards.put(hash, virtualNode);
System.out.println("虚拟节点[" + virtualNode + "] hash:" + hash + ",被添加");
}
}
} /**
* 删除节点
*
* @param node
*/
public static void delNode(String node) {
if (realNodes.contains(node)) {
realNodes.remove(node);
System.out.println("真实节点[" + node + "] 下线移除");
for (int i = 0; i < VIRTUAL_NUM; i++) {
String virtualNode = node + "&&VN" + i;
int hash = getHash(virtualNode);
shards.remove(hash);
System.out.println("虚拟节点[" + virtualNode + "] hash:" + hash + ",被移除");
}
}
} /**
* FNV1_32_HASH算法
*/
private static int getHash(String str) {
final int p = 16777619;
int hash = (int) 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) { //模拟客户端的请求
String[] nodes = { "127.0.0.1", "10.9.3.253", "192.168.10.1" }; for (String node : nodes) {
System.out.println("[" + node + "]的hash值为" + getHash(node) + ", 被路由到结点[" + getServer(node) + "]");
} // 添加一个节点(模拟服务器上线)
addNode("192.168.1.7");
// 删除一个节点(模拟服务器下线)
delNode("192.168.1.2"); for (String node : nodes) {
System.out.println("[" + node + "]的hash值为" + getHash(node) + ", 被路由到结点[" + getServer(node) + "]");
}
}
}
测试结果:
从结果可以看出:服务器节点上线和下线并不会对我们服务有任何影响,除非所有的服务都下线。
当之前映射的服务器下线,我们可以切换到和它Hash临近的服务节点上,保证服务的负载均衡。
如果我们考虑没太服务器性能不一致,比如服务器内存有32G、16G、8G的,我们可以根据不同的服务器性能,分配不同的负载因子(就是上面程序的VIRTUAL_NUM),这样我们是不是可以想到和Dubbo里面的负载因子是一致的,我们可以手动的调整每台服务器的负载因子,从而控制根据每个服务器性能,分配不同权重的客户端请求负载量,就是俗话说的“吃多少饭,干多少活” 。
实现案例:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap; public class ConsistencyHashingLoadFactor { // 真实节点列表
private static List<Machine> realNodes = new ArrayList<Machine>(); // 虚拟节点,key是Hash值,value是虚拟节点信息
private static SortedMap<Integer, String> shards = new TreeMap<Integer, String>(); static {
realNodes.add(new Machine("192.168.1.1", LoadFactor.Memory8G));
realNodes.add(new Machine("192.168.1.2", LoadFactor.Memory16G));
realNodes.add(new Machine("192.168.1.3", LoadFactor.Memory32G));
realNodes.add(new Machine("192.168.1.4", LoadFactor.Memory16G));
for (Machine node : realNodes) {
for (int i = 0; i < node.getMemory().getVrNum(); i++) {
String server = node.getHost();
String virtualNode = server + "&&VN" + i;
int hash = getHash(virtualNode);
shards.put(hash, virtualNode);
}
}
} /**
* 获取被分配的节点名
*
* @param node
* @return
*/
public static Machine getServer(String node) {
int hash = getHash(node);
Integer key = null;
SortedMap<Integer, String> subMap = shards.tailMap(hash);
if (subMap.isEmpty()) {
key = shards.lastKey();
} else {
key = subMap.firstKey();
}
String virtualNode = shards.get(key);
String realNodeName = virtualNode.substring(0, virtualNode.indexOf("&&"));
for (Machine machine : realNodes) {
if (machine.getHost().equals(realNodeName)) {
return machine;
}
}
return null;
} /**
* 添加节点
*
* @param node
*/
public static void addNode(Machine node) {
if (!realNodes.contains(node)) {
realNodes.add(node);
System.out.println("真实节点[" + node + "] 上线添加");
for (int i = 0; i < node.getMemory().getVrNum(); i++) {
String virtualNode = node.getHost() + "&&VN" + i;
int hash = getHash(virtualNode);
shards.put(hash, virtualNode);
System.out.println("虚拟节点[" + virtualNode + "] hash:" + hash + ",被添加");
}
}
} /**
* 删除节点
*
* @param node
*/
public static void delNode(Machine node) {
String host = node.getHost();
Iterator<Machine> it = realNodes.iterator();
while(it.hasNext()) {
Machine machine = it.next();
if(machine.getHost().equals(host)) {
it.remove();
System.out.println("真实节点[" + node + "] 下线移除");
for (int i = 0; i < node.getMemory().getVrNum(); i++) {
String virtualNode = node.getHost() + "&&VN" + i;
int hash = getHash(virtualNode);
shards.remove(hash);
System.out.println("虚拟节点[" + virtualNode + "] hash:" + hash + ",被移除");
}
}
}
} /**
* FNV1_32_HASH算法
*/
private static int getHash(String str) {
final int p = 16777619;
int hash = (int) 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) { // 模拟客户端的请求
String[] nodes = { "127.0.0.1", "10.9.3.253", "192.168.10.1" }; for (String node : nodes) {
System.out.println("[" + node + "]的hash值为" + getHash(node) + ", 被路由到结点[" + getServer(node) + "]");
} // 添加一个节点(模拟服务器上线)
addNode(new Machine("192.168.1.7", LoadFactor.Memory16G));
// 删除一个节点(模拟服务器下线)
delNode(new Machine("192.168.1.1", LoadFactor.Memory8G)); for (String node : nodes) {
System.out.println("[" + node + "]的hash值为" + getHash(node) + ", 被路由到结点[" + getServer(node) + "]");
}
}
} /**
* 机器类
*
* @author yangkuanjun
*
*/
class Machine { private String host; private LoadFactor memory; public String getHost() {
return host;
} public void setHost(String host) {
this.host = host;
} public LoadFactor getMemory() {
return memory;
} public void setMemory(LoadFactor memory) {
this.memory = memory;
} public Machine(String host, LoadFactor memory) {
super();
this.host = host;
this.memory = memory;
} @Override
public String toString() {
return "Machine [host=" + host + ", memory=" + memory + "]";
}
} /**
* 负载因子
*
* @author yangkuanjun
*
*/
enum LoadFactor { Memory8G(5), Memory16G(10), Memory32G(20); private int vrNum; private LoadFactor(int vrNum) {
this.vrNum = vrNum;
} public int getVrNum() {
return vrNum;
} }
测试结果:
从运行结果可以看出:负载因子较大的被分配的概率就越大。
Java 一致性Hash算法的学习的更多相关文章
- 对一致性Hash算法,Java代码实现的深入研究
一致性Hash算法 关于一致性Hash算法,在我之前的博文中已经有多次提到了,MemCache超详细解读一文中"一致性Hash算法"部分,对于为什么要使用一致性Hash算法.一致性 ...
- Java实现一致性Hash算法深入研究
一致性Hash算法 关于一致性Hash算法,在我之前的博文中已经有多次提到了,MemCache超详细解读一文中”一致性Hash算法”部分,对于为什么要使用一致性Hash算法和一致性Hash算法的算法原 ...
- 【转载】对一致性Hash算法,Java代码实现的深入研究
原文地址:http://www.cnblogs.com/xrq730/p/5186728.html 一致性Hash算法 关于一致性Hash算法,在我之前的博文中已经有多次提到了,MemCache超详细 ...
- 一致性Hash算法原理,java实现,及用途
学习记录: 一致性Hash算法原理及java实现:https://blog.csdn.net/suifeng629/article/details/81567777 一致性Hash算法介绍,原理,及使 ...
- 一致性hash算法及java实现
一致性hash算法是分布式中一个常用且好用的分片算法.或者数据库分库分表算法.现在的互联网服务架构中,为避免单点故障.提升处理效率.横向扩展等原因,分布式系统已经成为了居家旅行必备的部署模式,所以也产 ...
- 对一致性Hash算法及java实现(转)
一致性Hash算法 关于一致性Hash算法,在我之前的博文中已经有多次提到了,MemCache超详细解读一文中"一致性Hash算法"部分,对于为什么要使用一致性Hash算法.一致性 ...
- 对一致性Hash算法,Java代码实现的深入研究(转)
转载:http://www.cnblogs.com/xrq730/p/5186728.html 一致性Hash算法 关于一致性Hash算法,在我之前的博文中已经有多次提到了,MemCache超详细解读 ...
- 【策略】一致性Hash算法(Hash环)的java代码实现
[一]一致性hash算法,基本实现分布平衡. package org.ehking.quartz.curator; import java.util.SortedMap; import java.ut ...
- Java实现一致性Hash算法
Java代码实现了一致性Hash算法,并加入虚拟节点.,具体代码为: package com.baijob.commonTools; import java.util.Collection; im ...
随机推荐
- html冲刺
html知识点回顾与面试题<!--1.<DOCTYPE>告诉浏览器当前文档要以何种HTML或者XHTML规范解析2.语义标签strong 粗体em 斜体del 删除线ins 下划线 ...
- MUI学习02-顶部导航栏
建议:先看一下MUI注意事项 连接:http://ask.dcloud.net.cn/article/122 固定栏靠前 所谓的固定栏,也就是带有.mui-bar属性的节点,都是基于fixed定位的元 ...
- 异常 Exception 知识点总结 MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
- oracle显示一个月的所有天数
效率高: ) s_date from dual connect 效率低: select to_date() s_date from all_tables where rownum <= ( fr ...
- 【JVM】垃圾收集器
程序计数器.Java虚拟机栈.本地方法栈分配的内存是确定的,生命周期与线程同样.所以不须要过多考虑回收问题. 而Java堆和方法区仅仅有运行时才知道有哪些对象被创建,须要多少内存,这部分的内存分配和回 ...
- html学习笔记之2——多媒体
一:插件 插件可以通过 <object> 标签或者 <embed> 标签添加在页面中. <object width="400" height=&quo ...
- Effective Java 第三版—— 86. 非常谨慎地实现SERIALIZABLE接口
Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...
- Spring Boot系列——日志配置
日志,通常不会在需求阶段作为一个功能单独提出来,也不会在产品方案中看到它的细节.但是,这丝毫不影响它在任何一个系统中的重要的地位. 为了保证服务的高可用,发现问题一定要即使,解决问题一定要迅速,所以生 ...
- 从零开始unity特效(持续追加中)
打算重拾3d渲染了,计划把主要理论过一遍,每部分琢磨一个言之有物的demo. 因为很多东西要现学,再加上上班-8h,更新会比较慢,但会坚持. (待续) -------houdini+unity河流(2 ...
- iptables转发技术
NAT 一. 什么是 NAT NAT(Network Address Translation)译为网络地址转换.通常路由器在转发我们的数据包时,仅仅会将源MAC地址换成自己的MAC地址,但是NAT技术 ...