当前位置: 移动技术网 > IT编程>开发语言>Java > 哈希一致性算法以及代码实现

哈希一致性算法以及代码实现

2019年05月06日  | 移动技术网IT编程  | 我要评论

首套房贷政策,微信密码找回,曼娜回忆录千禧版

当缓存很多时,搭建集群根据数据的key取hash存储到redis集群中。
但是这种无法扩展。当增加redis节点或者减少节点时,hash值会变化。造成缓存失效。还可能引起缓存雪崩。使得请求压力都到了应用程序查库上面,使服务器崩溃。
hash一致性算法是将计算hash的数据和redis服务器抽取出来。
比如利用redis服务器的ip地址或者名称当做key。
以0开始,2的32次方为终点,组成一个圆环。
对redis服务器取hash,使其定位在圆环的某处。
对数据取hash,当数据的hash与某个服务器hash一致时,表示此数据存储在此服务器上。
当不一致时。根据此数据在圆环上的hash位置,顺时针到哪一个最近的redis服务器,数据就存储在那一台redis服务器中。
这样就避免了加减redis节点造成的hash不一致问题。
当增加或者减少redis节点时,只会对部分数据有影响,去查库。减轻服务器压力。
另外,可能因为redis服务器hash有偏移,造成某一台压力比较大。可以制造虚拟节点。使得分布均匀一些。
 
代码参考于:https://blog.csdn.net/suifeng629/article/details/81567777
 
 
代码:
import java.util.sortedmap;
import java.util.treemap;
/**
* 不带虚拟节点的一致性hash算法
*/
public class consistenthashingwithoutvirtualnode {
//待添加入hash环的服务器列表
private static string[] servers = { "192.168.0.0:111", "192.168.0.1:111",
"192.168.0.2:111", "192.168.0.3:111", "192.168.0.4:111" };
//key表示服务器的hash值,value表示服务器
private static sortedmap<integer, string> sortedmap = new treemap<integer, string>();
//程序初始化,将所有的服务器放入sortedmap中
static {
for (int i=0; i<servers.length; i++) {
int hash = gethash(servers[i]);
system.out.println("[" + servers[i] + "]加入集合中, 其hash值为" + hash);
sortedmap.put(hash, servers[i]);
}
system.out.println();
}
//得到应当路由到的结点
private static string getserver(string key) {
//得到该key的hash值
int hash = gethash(key);
//得到大于该hash值的所有map
sortedmap<integer, string> submap = sortedmap.tailmap(hash);
if(submap.isempty()){
//如果没有比该key的hash值大的,则从第一个node开始
integer i = sortedmap.firstkey();
//返回对应的服务器
return sortedmap.get(i);
}else{
//第一个key就是顺时针过去离node最近的那个结点
integer i = submap.firstkey();
//返回对应的服务器
return submap.get(i);
}
}
 
//使用fnv1_32_hash算法计算服务器的hash值,这里不使用重写hashcode的方法,最终效果没区别
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[] keys = {"太阳", "月亮", "星星"};
for(int i=0; i<keys.length; i++)
system.out.println("[" + keys[i] + "]的hash值为" + gethash(keys[i])
+ ", 被路由到结点[" + getserver(keys[i]) + "]");
}
}
带虚拟节点的:
/**
* 带虚拟节点的一致性hash算法
*/
public class consistenthashingwithoutvirtualnode2 {
//待添加入hash环的服务器列表
private static string[] servers = {"192.168.0.0:111", "192.168.0.1:111", "192.168.0.2:111",
"192.168.0.3:111", "192.168.0.4:111"};
//真实结点列表,考虑到服务器上线、下线的场景,即添加、删除的场景会比较频繁,这里使用linkedlist会更好
private static list<string> realnodes = new linkedlist<string>();
//虚拟节点,key表示虚拟节点的hash值,value表示虚拟节点的名称
private static sortedmap<integer, string> virtualnodes = new treemap<integer, string>();
//虚拟节点的数目,这里写死,为了演示需要,一个真实结点对应5个虚拟节点
private static final int virtual_nodes = 5;
static{
//先把原始的服务器添加到真实结点列表中
for(int i=0; i<servers.length; i++)
realnodes.add(servers[i]);
//再添加虚拟节点,遍历linkedlist使用foreach循环效率会比较高
for (string str : realnodes){
for(int i=0; i<virtual_nodes; i++){
string virtualnodename = str + "&&vn" + string.valueof(i);
int hash = gethash(virtualnodename);
system.out.println("虚拟节点[" + virtualnodename + "]被添加, hash值为" + hash);
virtualnodes.put(hash, virtualnodename);
}
}
system.out.println();
}
//使用fnv1_32_hash算法计算服务器的hash值,这里不使用重写hashcode的方法,最终效果没区别
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;
}
//得到应当路由到的结点
private static string getserver(string key){
//得到该key的hash值
int hash = gethash(key);
// 得到大于该hash值的所有map
sortedmap<integer, string> submap = virtualnodes.tailmap(hash);
string virtualnode;
if(submap.isempty()){
//如果没有比该key的hash值大的,则从第一个node开始
integer i = virtualnodes.firstkey();
//返回对应的服务器
virtualnode = virtualnodes.get(i);
}else{
//第一个key就是顺时针过去离node最近的那个结点
integer i = submap.firstkey();
//返回对应的服务器
virtualnode = submap.get(i);
}
//virtualnode虚拟节点名称要截取一下
if(stringutils.isnotblank(virtualnode)){
return virtualnode.substring(0, virtualnode.indexof("&&"));
}
return null;
}
public static void main(string[] args){
string[] keys = {"太阳", "月亮", "星星"};
for(int i=0; i<keys.length; i++)
system.out.println("[" + keys[i] + "]的hash值为" +
gethash(keys[i]) + ", 被路由到结点[" + getserver(keys[i]) + "]");
}
}

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网