负载均衡算法: 简单轮询算法, 平滑加权轮询, 一致性hash算法, 随机轮询, 加权随机轮询, 最小活跃数算法(基于dubbo) java代码实现
直接上干活
/**
* @version 1.0.0
* @@menu <p>
* @date 2020/11/17 16:28
*/
public class LoadBlance {
static Map<String, Integer> serverWeightMap = new HashMap<>();
static {
serverWeightMap.put("192.168.1.100", 1);
serverWeightMap.put("192.168.1.101", 8);
// 权重为4
serverWeightMap.put("192.168.1.102", 4);
serverWeightMap.put("192.168.1.103", 9);
serverWeightMap.put("192.168.1.104", 2);
// 权重为3
serverWeightMap.put("192.168.1.105", 3);
serverWeightMap.put("192.168.1.106", 4);
// 权重为2
serverWeightMap.put("192.168.1.107", 2);
serverWeightMap.put("192.168.1.108", 8);
serverWeightMap.put("192.168.1.109", 6);
serverWeightMap.put("192.168.1.110", 1);
}
public List<String> MapCasttoList() {
//这里重新一个map,作为缓冲, 目的是避免服务器上下线带来的问题
Map<String, Integer> serverMap = getServerMap();
Set<String> strings = serverMap.keySet();
List<String> list = new ArrayList<>();
list.addAll(strings);
return list;
}
private Map<String, Integer> getServerMap() {
Map<String, Integer> serverMap = new HashMap<>();
serverMap.putAll(serverWeightMap);
return serverMap;
}
static AtomicInteger index = new AtomicInteger();
//1: 简单的轮询算法:
public String Round() {
List<String> ipList = MapCasttoList();
if (index.get() >= ipList.size()) {
index.set(0);
}
return ipList.get(index.getAndIncrement() % ipList.size());
}
//2: 随机算法
public String RandomLoadBlance() {
List<String> ipList = MapCasttoList();
int index = new Random().nextInt(ipList.size());
return ipList.get(index);
}
//3: ip 的hash法: 将ip hash,
public String IpHashLoadBlance() {
List<String> ipList = MapCasttoList();
//获取ipList, 然后计算HashCode, 之后和 size计算出对应的index
List<Long> ipHashList = ipList.stream().map(ip -> myHashCode(ip)).collect(Collectors.toList());
int i = new Random().nextInt(ipList.size());
Long index = ipHashList.get(i) % ipList.size();
return ipList.get(index.intValue());
}
//因为 hashCode方法会出现负数,所以这里使用自写的hashCode方法
private static long myHashCode(String str) {
long h = 0;
if (h == 0) {
int off = 0;
char val[] = str.toCharArray();
long len = str.length();
for (long i = 0; i < len; i++) {
h = 31 * h + val[off++];
}
}
return h;
}
/**
* 4: 一致性hash 负载轮询算法
* https://blog.csdn.net/weixin_43925277/article/details/107989585
* 原理: http://www.zsythink.net/archives/1182
*/
static TreeMap<Integer, String> treeMap = new TreeMap<>();
static {
//这里使用的hash环,有1000个虚拟节点, 构建hash环
List<String> serverIpList = MapCasttoList();
for (String ip : serverIpList) {
for (int i = 0; i < 1000; i++) {
//这里的hash算法,是对 2 ^ 32 次方 取模
int ipHashCode = FNVHash(ip + i);
treeMap.put(ipHashCode, ip);
}
}
}
//str 这里的str 是指使用某个请求的标志(请求名,或者别的),来hash,最终命中hash环对应的ip
public String consistencyHashLoadBlance(String str) {
int hashCode = FNVHash(str);
//找到比这个 hashCode 值大的所有子树
SortedMap<Integer, String> tailSubMap = treeMap.tailMap(hashCode);
if (tailSubMap == null) {
//返回hash环的第一个节点 对应的ip
return treeMap.get(treeMap.firstKey());
}
//否则返回 hash环中,这个 子树的第一个节点对应的ip
return treeMap.get(tailSubMap.firstKey());
}
// 32位的 Fowler-Noll-Vo 哈希算法
// https://en.wikipedia.org/wiki/Fowler–Noll–Vo_hash_function
public static int FNVHash(String key) {
final int p = 16777619;
Long hash = 2166136261L;
for (int idx = 0, num = key.length(); idx < num; ++idx) {
hash = (hash ^ key.charAt(idx)) * 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.intValue();
}
/**
* 5: 基于权重的轮询算法: 平滑加权轮询算法, Nginx的默认轮询算法就是 加权轮询算法
* https://my.oschina.net/u/1267069/blog/4437331
* <p>
* 思路: 比如 A : 5 , B : 3 , C : 2 (服务器 A,B,C 对应权重分别是 5,3,2)
* ip: A,B,C
* weight: 5,3,2 (计算得到 totalWeight = 10)
* currentWeight: 0,0,0 (当前ip的初始权重都为0)
* <p>
* 请求次数: | currentWeight = currentWeight + weight | 最大权重为 | 返回的ip为 | 最大的权重 - totalWeight,其余不变
* 1 | 5,3,2 (0,0,0 + 5,3,2) | 5 | A | -5,3,2
* 2 | 0,6,4 (-5,3,2 + 5,3,2) | 6 | B | 0,-4,4
* 3 | 5,-1,6 (0,-4,4 + 5,3,2) | 6 | C | 5,-1,-4
* 4 | 10,2,-2 (5,-1,-4 + 5,3,2) | 10 | A | 0,2,-2
* 5 | 5,5,0 | 5 | A | -5,5,0
* 6 | 0,8,2 | 8 | B | 0,-2,2
* 7 | 5,1,4 | 5 | A | -5,1,4
* 8 | 0,4,6 | 6 | C | 0,4,-4
* 9 | 5,7,-2 | 7 | B | 5,-3,-2
* 10 | 10,0,0 | 10 | A | 0,0,0
* <p>
* 至此结束: 可以看到负载轮询的策略是: A,B,C,A,A,B,A,C,B,A,
*
* 循环获取到权重最大的节点,和总权重, 之后将这个权重最大节点的权重 - 总权重, 最后返回这个权重最大节点对应的ip
*/
public String weightRound(List<ServerNode> serverNodeList) {
ServerNode selectedServerNode = null;
int maxWeight = 0;
int totalWeight = 0;
for (ServerNode serverNode : serverNodeList) {
serverNode.incrementWeight();//获取节点的当前权重
if (serverNode.currentWeight > maxWeight) {
//节点的当前权重 大于最大权重, 就将该节点当做是选中的节点
maxWeight = serverNode.currentWeight;
selectedServerNode = serverNode;
}
//计算总的权重
totalWeight += serverNode.weight;
}
// 当循环结束的时候, selectedServerNode就是权重最大的节点,对应的权重为maxWeight, 总的权重为totalWeight
if (selectedServerNode != null) {
//将权重最大的这个节点,的权重值减去 总权重
selectedServerNode.decrementTotal(totalWeight);
//并返回这个权重对应的 ip
return selectedServerNode.ip;
}
return "";
}
private List<ServerNode> getServerNodeList(Map<String, Integer> serverMap) {
List<ServerNode> list = new ArrayList<>();
for (Map.Entry<String, Integer> entry : serverMap.entrySet()) {
ServerNode serverNode = new ServerNode(entry.getKey(), entry.getValue());
list.add(serverNode);
}
return list;
}
private class ServerNode {
String ip;
int weight;//初始配置好的权重
int currentWeight;//当前的权重,初始为 0
public ServerNode(String ip, int weight) {
this.ip = ip;
this.weight = weight;
}
public void decrementTotal(int totalWeight) {
currentWeight = currentWeight - totalWeight;
}
public void incrementWeight() {
currentWeight = currentWeight + weight;
}
public void setCurrentWeight(int currentWeight) {
this.currentWeight = currentWeight;
}
}
/**
* 6: 加权随机
* 思路: 1: 一个ip的权重为5, 就将这个ip存到list中五次, 然后随机, 简单,但是有缺点 list会很大,执行效率低
*
* 思路: 2: 转换思路: 将每一个ip的权重转化为 X轴上的数字, 随机的数字,落在X轴的那个位置, 就返回这个位置对应的 ip
* 比如: 192.168.1.100 , 3
* 192.168.1.102 , 6
* 192.168.1.105 , 4
* 转换为X轴 为: 0--3-----9---13 可以看到这里需要获取所有权重之和
* 产生的随机数为: 12 ,
* 12 和 第一个ip的权重3 比较, 12 >= 3
* 然后 12 - 3 = 9, 9 和第二个ip的权重6比较, 9 >= 6
* 然后 9 - 6 =3 , 3 和第三个ip的权重4比较, 3 < 4 跳出---->说明随机的ip 是 192.168.1.105
*
* 这里实现思路2
*/
public String weightRandom(){
//1: 获取所有ip权重之和
List<Integer> weightList = new ArrayList<>(getServerMap().values());