第十一章 自己实现一致性hash算法
关于一致性hash算法的意义以及其相对于简单求余法(除数求余法)的好处,查看第六章 memcached剖析
注意:真实的hash环的数据结构是二叉树,这里为了简便使用了列表List
1、一致性hash算法的使用地方
- memcached服务器
- Jedis分片机制
2、真实服务器节点没有虚拟化的一致性hash算法实现
ServerNode:真实服务器节点
package hash; /**
* server节点
*/
public class ServerNode {
private String serverName;
private long serverHash; public String getServerName() {
return serverName;
} public void setServerName(String serverName) {
this.serverName = serverName;
} public long getServerHash() {
return serverHash;
} public void setServerHash(long serverHash) {
this.serverHash = serverHash;
} /**
* 下边重写hashcode()和equals()是为了在删除节点的时候只根据传入的serverName删除即可
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((serverName == null) ? 0 : serverName.hashCode());
return result;
} @Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ServerNode other = (ServerNode) obj;
if (serverName == null) {
if (other.serverName != null)
return false;
} else if (!serverName.equals(other.serverName))
return false;
return true;
} }
注意:
- serverName可以自己取名,这里取名为"ip:port"
- 对于hashCode()和equals()方法的重写仅仅是为了删除服务器节点的时候,只根据serverName就可以删除,而不需要再计算服务器节点的hash值
ServerComparator:真实服务器比较器
package hash; import java.util.Comparator; /**
* 服务器排序比较器
*/
public class ServerComparator implements Comparator<ServerNode> { public int compare(ServerNode node1, ServerNode node2) {
if(node1.getServerHash() <= node2.getServerHash()) {
return -1;//node1<node2
}
return 1;//node1>node2
} }
注意:
- 关于java的比较器,有两种:(后者用的多一些)
- javabean实现comparable接口,实现compareTo()方法
- 另外建一个类实现comparator接口,实现其中的compare()方法
ConsistentHash:一致性hash实现类
package hash; import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.zip.CRC32; /**
* 一致性hash实现(数据结构:list)(服务器没有虚拟化)
* 一致性hash的真正数据结构是二叉树
*/
public class ConsistentHash {
private List<ServerNode> servers = new ArrayList<ServerNode>();//存放服务器 /** 计算服务器和存储的键的hash值 */
public long hash(String str){
CRC32 crc32 = new CRC32();
crc32.update(str.getBytes());
return crc32.getValue();
} /**
* 添加server到环上
* @param serverName ip:port
*/
public void addServer(String serverName){ ServerNode node = new ServerNode();
node.setServerName(serverName);
node.setServerHash(hash(serverName)); servers.add(node);
Collections.sort(servers, new ServerComparator());
} /**
* 从环上删除server节点
*/
public void deleteServer(String serverName){ ServerNode node = new ServerNode();
node.setServerName(serverName); servers.remove(node);
} /**
* 获取一个缓存key应该存放的位置
* @param cachekey 缓存的key
* @return 缓存的服务器节点
*/
public ServerNode getServer(String cachekey){
long keyHash = hash(cachekey); for(ServerNode node : servers){
if(keyHash<=node.getServerHash()){
return node;
}
} return servers.get(0);//如果node没有合适放置位置,放在第一台服务器上去
} /****************测试*******************/
public void printServers(){
for(ServerNode server : servers){
System.out.println(server.getServerName()+"-->"+server.getServerHash());
}
} public static void main(String[] args) {
ConsistentHash ch = new ConsistentHash();
ch.addServer("127.0.0.1:11211");
ch.addServer("127.0.0.1:11212");
ch.addServer("127.0.0.2:11211");
ch.addServer("127.0.0.2:11212"); ch.printServers(); ServerNode node = ch.getServer("hello");
System.out.println(ch.hash("hello")+"-->"+node.getServerName()+"-->"+node.getServerHash()); ServerNode node2 = ch.getServer("name");
System.out.println(ch.hash("name")+"-->"+node2.getServerName()+"-->"+node2.getServerHash()); ServerNode node3 = ch.getServer("a");
System.out.println(ch.hash("a")+"-->"+node3.getServerName()+"-->"+node3.getServerHash()); /********************删除节点*********************/
ch.deleteServer("127.0.0.1:11212");
ch.printServers(); ServerNode node0 = ch.getServer("hello");
System.out.println(ch.hash("hello")+"-->"+node0.getServerName()+"-->"+node0.getServerHash()); ServerNode node02 = ch.getServer("name");
System.out.println(ch.hash("name")+"-->"+node02.getServerName()+"-->"+node02.getServerHash()); ServerNode node03 = ch.getServer("a");
System.out.println(ch.hash("a")+"-->"+node03.getServerName()+"-->"+node03.getServerHash()); }
}
注意:
- 在计算服务器节点和存储的key的hash值的时候,不仅仅可以使用crc32算法,还可以使用MD5算法等等,只要是最后得出的结果是一个>=0&&<=232的数就好
- 在这个实现中,并没有将真实服务器节点进行虚拟化
3、真实服务器节点虚拟化后的一致性hash算法实现
为什么要虚拟化,查看第六章 memcached剖析 ,这里只列出几条原因:
- 在memcached服务器较少的情况下,很难平均的分布到hash环上,这样就会造成负载不均衡--引入虚拟化节点,可以解决这个问题
- 当一台memcached宕机时,其原先所承受的压力全部给了其下一个节点,为了将其原先所承受的压力尽可能的分布给所有剩余的memcached节点,引入虚拟化节点可以达到这个目的
- 当新添加了一台memcached服务器server1时,server1只会缓解其中的一台服务器(即server1插入环后,server1的下一个节点)的压力,为了可以让server1尽可能的缓解所有的memcached服务器的压力,引入虚拟节点可以达到这个目的
VirtualServerNode:虚拟节点
package hash2; /**
* 虚拟节点
*/
public class VirtualServerNode {
private String serverName;//真实节点名称
private long virtualServerHash;//虚拟节点hash public String getServerName() {
return serverName;
} public void setServerName(String serverName) {
this.serverName = serverName;
} public long getVirtualServerHash() {
return virtualServerHash;
} public void setVirtualServerHash(long virtualServerHash) {
this.virtualServerHash = virtualServerHash;
} }
注意:
- 该类中的serverName是该虚拟节点对应的真实节点的名称,这里就是"ip:port"
- 真正的虚拟节点的名称是serverName-i(其中,i是0~virtualCount的整数值),这一块儿请查看ConsistentHashWithVirtualNode的addServer(String serverName)
VirtualServerComparator:虚拟节点比较器
package hash2; import java.util.Comparator; /**
* 虚拟节点比较器
*/
public class VirtualServerComparator implements Comparator<VirtualServerNode> { public int compare(VirtualServerNode node1, VirtualServerNode node2) {
if(node1.getVirtualServerHash() <= node2.getVirtualServerHash()) {
return -1;
}
return 1;
} }
ConsistentHashWithVirtualNode:真实节点虚拟化后的一致性hash算法
package hash2; import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.zip.CRC32; /**
* 具有虚拟节点的一致性hash实现(数据结构:list)
* 一致性hash的真正数据结构是二叉树
*/
public class ConsistentHashWithVirtualNode {
private List<VirtualServerNode> virtualServers = new ArrayList<VirtualServerNode>();//存放虚拟节点
private static final int virtualCount = 8;//每个真实节点虚拟成8个虚拟节点 /** 计算服务器和存储的键的hash值 */
public long hash(String str){
CRC32 crc32 = new CRC32();
crc32.update(str.getBytes());
return crc32.getValue();
} /**
* 添加server的虚拟节点到环上
* @param serverName ip:port
*/
public void addServer(String serverName){ for(int count=0;count<virtualCount;count++){
VirtualServerNode node = new VirtualServerNode();
node.setServerName(serverName);
node.setVirtualServerHash(hash(serverName+"-"+count));//虚拟节点的名字:serverName+"-"+count
virtualServers.add(node);
} Collections.sort(virtualServers, new VirtualServerComparator());
} /**
* 从环上删除server节点(需要删除所有的该server节点对应的虚拟节点)
*/
public void deleteServer(String serverName){ /*
* 在这种删除的时候,会出现java.util.ConcurrentModificationException
* 这是因为此处的遍历方式为使用ArrayList内部类Itr进行遍历,
* 在遍历的过程中发生了remove、add等操作,导致modCount发生了变化,
* 产生并发修改异常,
* 可以使用下边的那一种方式来进行遍历(遍历方式不是Itr),
* 再这样的遍历过程中,add和remove都是没有问题的
*/
/*for(VirtualServerNode node : virtualServers){
if(node.getServerName().equals(serverName)){
virtualServers.remove(node);
}
}*/
for(int i=0;i<virtualServers.size();i++) {
VirtualServerNode node = virtualServers.get(i);
if(node.getServerName().equals(serverName)) {
virtualServers.remove(node);
}
} } /**
* 获取一个缓存key应该存放的位置
* @param cachekey 缓存的key
* @return 缓存的服务器节点
*/
public VirtualServerNode getServer(String cachekey){
long keyHash = hash(cachekey); for(VirtualServerNode node : virtualServers){
if(keyHash<=node.getVirtualServerHash()){
return node;
}
} return virtualServers.get(0);//如果node没有合适放置位置,放在第一台服务器上去
} /****************测试*******************/
public void printServers(){
for(VirtualServerNode server : virtualServers){
System.out.println(server.getServerName()+"-->"+server.getVirtualServerHash());
}
} public static void main(String[] args) {
ConsistentHashWithVirtualNode ch = new ConsistentHashWithVirtualNode();
ch.addServer("127.0.0.1:11211");
ch.addServer("127.0.0.1:11212");
ch.addServer("127.0.0.2:11211");
ch.addServer("127.0.0.2:11212"); ch.printServers(); VirtualServerNode node = ch.getServer("hello");
System.out.println(ch.hash("hello")+"-->"+node.getServerName()+"-->"+node.getVirtualServerHash()); VirtualServerNode node2 = ch.getServer("name");
System.out.println(ch.hash("name")+"-->"+node2.getServerName()+"-->"+node2.getVirtualServerHash()); VirtualServerNode node3 = ch.getServer("a");
System.out.println(ch.hash("a")+"-->"+node3.getServerName()+"-->"+node3.getVirtualServerHash()); /*********************删除节点之后**********************/
ch.deleteServer("127.0.0.1:11212");
ch.printServers(); VirtualServerNode node0 = ch.getServer("hello");
System.out.println(ch.hash("hello")+"-->"+node0.getServerName()+"-->"+node0.getVirtualServerHash()); VirtualServerNode node02 = ch.getServer("name");
System.out.println(ch.hash("name")+"-->"+node02.getServerName()+"-->"+node02.getVirtualServerHash()); VirtualServerNode node03 = ch.getServer("a");
System.out.println(ch.hash("a")+"-->"+node03.getServerName()+"-->"+node03.getVirtualServerHash()); }
}
注意:
- 在实际操作中,一台memcached服务器虚拟成台比较合适(100~200)
- 从环上删除节点的算法写的较差,但是考虑到删除节点的操作在实际使用中用的比较少(宕机比较少,人为的删除节点也较少),也无所谓
- 删除节点的时候,注意使用foreach语法糖去遍历的时候,在遍历的过程中不可以做删除、增加操作,否则会抛出并发修改异常,具体的原因见注释和第二章 ArrayList源码解析;想要实现在遍历的过程中进行删除、增加操作,使用简单for循环,见如上代码
第十一章 自己实现一致性hash算法的更多相关文章
- 【转载】对一致性Hash算法,Java代码实现的深入研究
原文地址:http://www.cnblogs.com/xrq730/p/5186728.html 一致性Hash算法 关于一致性Hash算法,在我之前的博文中已经有多次提到了,MemCache超详细 ...
- 对一致性Hash算法,Java代码实现的深入研究
一致性Hash算法 关于一致性Hash算法,在我之前的博文中已经有多次提到了,MemCache超详细解读一文中"一致性Hash算法"部分,对于为什么要使用一致性Hash算法.一致性 ...
- 一致性hash算法详解
转载请说明出处:http://blog.csdn.net/cywosp/article/details/23397179 一致性哈希算法在1997年由麻省理工学院提出的一种分布式哈希(DHT) ...
- 一致性hash算法简介
一致性哈希算法在1997年由麻省理工学院提出的一种分布式哈希(DHT)实现算法,设计目标是为了解决因特网中的热点(Hot spot)问题,初衷和CARP十分类似.一致性哈希修正了CARP使用的简单哈希 ...
- 分布式缓存技术memcached学习(四)—— 一致性hash算法原理
分布式一致性hash算法简介 当你看到“分布式一致性hash算法”这个词时,第一时间可能会问,什么是分布式,什么是一致性,hash又是什么.在分析分布式一致性hash算法原理之前,我们先来了解一下这几 ...
- 一致性 hash 算法( consistent hashing )a
一致性 hash 算法( consistent hashing ) 张亮 consistent hashing 算法早在 1997 年就在论文 Consistent hashing and rando ...
- 一致性hash算法简介与代码实现
一.简介: 一致性hash算法提出了在动态变化的Cache环境中,判定哈希算法好坏的四个定义: 1.平衡性(Balance) 2.单调性(Monotonicity) 3.分散性(Spread) 4.负 ...
- memcache的一致性hash算法使用
一.概述 1.我们的memcache客户端(这里我看的spymemcache的源码),使用了一致性hash算法ketama进行数据存储节点的选择.与常规的hash算法思路不同,只是对我们要存储数据的k ...
- 一致性Hash算法在Redis分布式中的使用
由于redis是单点,但是项目中不可避免的会使用多台Redis缓存服务器,那么怎么把缓存的Key均匀的映射到多台Redis服务器上,且随着缓存服务器的增加或减少时做到最小化的减少缓存Key的命中率呢? ...
随机推荐
- webview内部跳转判断
重写webview内的方法 webView.setWebViewClient(new WebViewClient() { @Override // 在点击请求的是链接是才会调用,重写此方法返回true ...
- React Native性能优化之可取消的异步操作
前沿 在前端的项目开发中,异步操作是一个不可获取的,从用户的角度来说,异步操作所带来的体验是美妙的,但有时候也会带来一些性能隐患.比如说:有一个异步请求还没有返回结果,但是页面却关闭了,这时由于异步操 ...
- Onenet学习笔记
中国移动物联网开放平台:https://open.iot.10086.cn/ 一.平台概述 简介 OneNET是中国移动物联网有限公司响应“大众创新.万众创业”以及基于开放共赢的理念,面向公共服务自主 ...
- 四、redis系列之主从复制与哨兵机制
1. 绪言 在现实应用环境中,出于数据容量.容灾.性能等因素的考虑,往往不会只使用一台服务器,而是使用集群的方式.Redis 中也有类似的维持一主多从的方式提高 Redis 集群的高可用性的方案,而其 ...
- Oracle 默认的几个登陆用户名和密码
默认用户有这么几个,system,sys,scott,hr ,一般scott 和hr 作为你的练习用户.system的默认密码是 manager sys的默认密码是 change_on_install ...
- Java内存是怎么管理的
JAVA 内存管理总结 1. java是如何管理内存的 Java的内存管理就是对象的分配和释放问题.(两部分) 分配 :内存的分配是由程序完成的,程序员需要通过关键字new 为每个对象申请内存空间 ( ...
- 安装部署VMware vSphere 5.5文档 (6-4) 安装配置DB数据库
部署VMware vSphere 5.5 实施文档 ########################################################################## ...
- [ 转载 ] Java基础11--Java总结篇系列:Java泛型
一. 泛型概念的提出(为什么需要泛型)? 首先,我们看下下面这段简短的代码: 1 public class GenericTest { 2 3 public static void main(Stri ...
- bzoj 2961
根据“点在圆内”关系,列出点P(x0,y0)在圆C(x,y)内的关系: (x-x0)^2+(y-y0)^2 <= x^2+y^2 化简得: 2*x0*x+2*y0*y >= x0^2+y0 ...
- Python文件类型
Python的文件类型分为三种:源代码.字节代码.优化代码. 1. 源代码 Python源代码文件,即py脚本文件,由 python.exe 解释,可在控制台下运行.pyw脚本文件是图形用户接口 ...