一致性哈希实现负载均衡
1 为什么需要哈希算法
解决同一个用户访问服务器是,访问的是不同的服务器的问题
场景:集群造成的session没有同步
当一个用户访问服务器A的时候,该台服务器A会保存这台服务器的session,但是当下次再访问的时候,被负载均衡算法可能算到了不同的服务器B,服务器B中没有用户的session,会要求用户再次登录。
解决:
- 加入redis,将session存到redis中
- Tomcat同步session
- 一致性哈希算法
2 什么是一致性哈希算法
服务器集群接收到一次请求调用时,可以根据请求的信息,比如客户端的ip地址,或请求路径与请求参数等信息进行哈希,可以得出一个哈希值,特点是对于相同的ip地址,或请求路径和请求参数哈希出来的值是一样的,只要能再增加一个算法,能够把这个哈希值映射成一个服务端ip地址,就可以使用相同的请求(相同的ip地址,或请求路径和请求参数)落到同一服务器上。
因为客户端发起的请求是无穷无尽的(客户端地址不同,请求参数不同等等),所以对于的哈希值也是无穷大的,所以我们不能把所有的哈希值都进行映射到服务端ip上,所有这里需要用到哈希环。
3 虚拟节点
- 解决一个服务器挂掉造成的服务不均匀问题
- 使得哈希环更加平滑
当时当一台服务器挂掉的话,会造成服务器服务不均匀的情况
会发现,ip3和ip1直接的范围是比较大的,会有更多的请求落到ip1上,这是“不公平”,解决这个问题需要加入虚拟节点,比如:
其中,ip2-1,ip3-1就是虚拟节点,并不能处理节点,而是等同于对应的服务器。
实际上,这只是处理这种不均衡性的一种思路,实际上就算哈希环本身是均衡的,你也可以增加更多的虚拟节点来使得这个环更加的平滑。
上面的哈希环更加的散列,平滑
只需要找到大于hashcode的以一个虚拟节点即可
由于哈希环上的点是有序的,那么采用的数据结构是TreeMap
4 代码实现
public class ServerIps {
public static final List LIST = (List) Arrays.asList(
"192.168.0.1",
"192.168.0.2",
"192.168.0.3",
"192.168.0.4",
"192.168.0.5",
"192.168.0.6",
"192.168.0.7",
"192.168.0.8",
"192.168.0.9",
"192.168.0.10"
);
}
======================================
import java.util.SortedMap;
import java.util.TreeMap;
public class ConsistentHash {
private static TreeMap virtualNodes = new TreeMap<>();
private static final int VIRTUAL_NODES = 160;
static {
for(String ip: ServerIps.LIST) {
for (int i = 0; i < VIRTUAL_NODES; i++) {
int hash = getHash(ip+i);
virtualNodes.put(hash, ip);
}
}
}
private static String getServer(String client) {
int hash = getHash(client);
//大于hash,virtualNodes的子树的firstkey
//tailMap,能获得大于等于hash值的一棵子红黑树
SortedMap subMap = virtualNodes.tailMap(hash);
Integer firstKey = null;
if(subMap == null) {
firstKey = virtualNodes.firstKey();
} else {
firstKey = (Integer) subMap.firstKey();
}
return virtualNodes.get(firstKey);
}
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) {
for (int i = 0; i < 12; i++) {
System.out.println(getServer("client"+i));
}
}
}
5 最小活跃数
由于服务的时间并不是固定,如果平均分配服务器有的时候并不合理
比如:
有3个消息,分别请求了A、B、C三台服务器,处理的时间依次为3s、2s、1s,当1s后有一个新的请求过来,按道理说应该A服务器来服务,但是C台服务器已经空闲了,所有这样的分配方式有时候并不合理。
解决:
采用最小活跃数,哪台机器服务最少,用哪台机器,当都大致相等的时候可以根据前面的随机、轮询等。
6 算法的魅力
- 将程序“复杂化”,提升程序效率到极致