欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-03-20 21:08:16
当缓存很多时,搭建集群根据数据的key取hash存储到redis集群中。 但是这种无法扩展。当增加redis节点或者减少节点时,hash值会变化。造成缓存失效。还可能引起缓存雪崩。使得请求压力都到了应用程序查库上面,使服务器崩溃。 hash一致性算法是将计算hash的数据和redis服务器抽取出来。 ......
当缓存很多时,搭建集群根据数据的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]) + "]");
}
}