负载均衡算法: 简单轮询算法, 平滑加权轮询, 一致性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());
int totalWeight = weightList.stream().mapToInt(e->e.intValue()).sum();
//2: 产生一个随机数
int index = new Random().nextInt(totalWeight);
for (String ip : getServerMap().keySet()) {
//2.1 获取这个ip对应的权重
Integer weight = getServerMap().get(ip);
if(index < weight){
return ip;
}
index = index - weight;
}
return "";
}
/**
* 6 : 最小活跃数算法 (这里基于 dubbo的最小活跃数算法)
* 什么是最小活跃数算法:
* 一个服务器有一个活跃数active, 当处理一个请求时,active+1, 请求处理完active-1, 并发情况下,这个active的值越小,说明这台服务器性能高, 那么这台服务器就应该多处理些请求
* <p>
* 有三种情况
* 1: 只有一个ip 活跃数最低,那么就返回这个ip
* 2: 有多个ip 活跃数最低,且权重不同,返回权重最大的那个ip(权重最大的ip 有可能是多个, 这里返回第一个)
* 3: 有多个ip 活跃数最低,且权重相同, 随机返回
* <p>
* 也有特殊情况,加入第一台服务器A,2000年买的,处理请求最大数为100, 第二台机器B,2010年买的处理请求最大数为500, 第三台机器C,2020年买的处理最大请求数为2000,对应的活跃数分别为:A-90, B-400,C-800,如果按照最小活跃数应该A机器
* 处理请求会更多,但实际上C机器还能够处理1200个请求,所以最小活跃数算法,适用于各个机器性能相近, 处理请求的时间长短,取决于某个请求的计算复杂度
* <p>
* 实现思路:
* 1: 循环list, 如果
*/
public String leastActiveLoadBalance(List<Ip_active_weight> invokers) {
int length = invokers.size();
// 所有invoker活跃数中 最小的活跃数, 初始为-1
int leastActive = -1;
// 所有invoker活跃数中,活跃数最小,且相同的个数, 默认0个
int leastCount = 0;
// leastIndexes 这个数组存的是最小活跃数对应的下标, leastIndexes的大小不一定为1, 因为有可能有多个ip对应的活跃数最小,且相同
int[] leastIndexes = new int[length];
// 用来存 每个ip对应的权重
int[] weights = new int[length];
// 所有 ip的 权重之和, 初始为0
int totalWeight = 0;
// 这是一个标准
int firstWeight = 0;
// 这是一个标志, 默认true 每一个最小活跃数相同的ip 权重都相同
boolean sameWeight = true;
for (int i = 0; i < length; i++) {
Ip_active_weight invoker = invokers.get(i);
//获取对应的活跃数
int active = invoker.getActive();
//获取对应的权重
int afterWarmup = invoker.getWeight();
// 先将权重保存起来
weights[i] = afterWarmup;
if (leastActive == -1 || active < leastActive) {
//更新 最小活跃数
leastActive = active;
// 最小的活跃数的个数 加1
leastCount = 1;
//将 当前invoker就是最小活跃数,将他对应的 下标存入 leastIndexes数组中
leastIndexes[0] = i;
//叠加总权重
totalWeight = afterWarmup;
//设置第一个权重,用来作比较,
firstWeight = afterWarmup;
sameWeight = true;
} else if (active == leastActive) {
leastIndexes[leastCount++] = i;
totalWeight += afterWarmup;
if (sameWeight && afterWarmup != firstWeight) {
//权重不同,将标志位 设置为false, 根据权重大小返回,说明有多个ip,最小活跃数相同,权重不同,置为false, 按照权重大小返回
sameWeight = false;
}
}
}
//跳出循环时候, 已经找到了 最小活跃数,的集合
if (leastCount == 1) {
//活跃数最小的只有一个,直接返回
return invokers.get(leastIndexes[0]).getIp();
}
// sameWeight为false ,说明有多个ip 最小活跃数相同, 权重不同,
if (!sameWeight && totalWeight > 0) {
//这里用到的思想是: X轴, 随机出来一个权重a,落到哪个区域内(a-权重 < 0, 就返回这个权重对应的 ip),
int offsetWeight = new Random().nextInt(totalWeight);
for (int i = 0; i < leastCount; i++) {
int leastIndex = leastIndexes[i];
offsetWeight -= weights[leastIndex];
if (offsetWeight < 0) {
return invokers.get(leastIndex).getIp();
}
}
}
//所有的最小活跃数相同,且权重相同,随机返回
return invokers.get(leastIndexes[new Random().nextInt(leastCount)]).getIp();
}
public List<Ip_active_weight> buildIpActiveWeightList() {
Map<String, Integer> serverMap = getServerMap();
List<Ip_active_weight> list = new ArrayList<>(serverMap.keySet().size());
getServerMap().forEach((key, value) -> {
Ip_active_weight ip_active_weight = new Ip_active_weight();
ip_active_weight.setIp(key);
//这里随机权重(1-10)
ip_active_weight.setActive(new Random().nextInt(3) + 1);
ip_active_weight.setWeight(value);
list.add(ip_active_weight);
});
return list;
}
class Ip_active_weight {
String ip;
//活跃数
int active;
//权重
int weight;
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public int getActive() {
return active;
}
public void setActive(int active) {
this.active = active;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
@Override
public String toString() {
return "Ip_active_weight{" +
"ip='" + ip + '\'' +
", active=" + active +
", weight=" + weight +
'}';
}
}
@Test
public static void test01() {
LoadBlance loadBlance = new LoadBlance();
List<Ip_active_weight> invokes = loadBlance.buildIpActiveWeightList();
for (Ip_active_weight invoke : invokes) {
System.out.println(invoke);
}
System.out.println("_----------------------");
for (int i = 0; i < 50; i++) {
//模拟请求50次
System.out.println(loadBlance.leastActiveLoadBalance(invokes));
}
}
public static void main(String[] args) {
LoadBlance loadBlance = new LoadBlance();
Map<String, Integer> serverMap = new HashMap<>();
serverMap.put("A",5);
serverMap.put("B",3);
serverMap.put("C",2);
List<ServerNode> serverNodeList = loadBlance.getServerNodeList(serverMap);
for (int i = 0; i < 50; i++) {//模拟请求50次
System.out.println(loadBlance.weightRound(serverNodeList));
}
}
}