负载均衡就是通过在多个服务器间分配工作流(将用户请求分摊到各个服务器上),有助于确保高可用性响应速度和可扩展性(提高系统整体的并发能力及好的可靠性)。
常见的负载均衡算法有:
本文讲根据自己学习负载均衡算法用到过的视频讲解及文档知识进行总结,并附代码实现。
理论讲解:
每个开发人员都应该知道的6种负载均衡算法_哔哩哔哩_bilibili
负载均衡原理及算法详解 | JavaGuide算法实现:
面试官看完我写的负载均衡算法,立马给了offer_哔哩哔哩_bilibili
B站面试挺难的,现场手写负载均衡—一致性哈希环算法_哔哩哔哩_bilibili
代码实现:
基础服务类
先造一个基础的serverIp类,用于存放服务器的ip、权重、以及动态权重。
import jdk.nashorn.internal.codegen.ObjectClassGenerator;
/**
* 服务器ip类,用于存放服务器的ip、权重、以及动态权重
*/
public class ServerIp implements Comparable{
/**
* 服务器ip
*/
private String ip;
/**
* 服务器权重
*/
private int weight;
/**
* 动态实时权重
*/
private int currentWeight;
public ServerIp(){
}
public ServerIp(String ip, int weight, int currentWeight) {
this.ip = ip;
this.weight = weight;
this.currentWeight = currentWeight;
}
public String getIp() {
return ip;
}
public int getWeight() {
return weight;
}
public int getCurrentWeight() {
return currentWeight;
}
public void setIp(String ip) {
this.ip = ip;
}
public void setWeight(int weight) {
this.weight = weight;
}
public void setCurrentWeight(int currentWeight) {
this.currentWeight = currentWeight;
}
/**
* 重写对list排序的方法,看看谁的currentWeight大
* @param o the object to be compared.
* @return 负数,0,正数,分别代表小于,等于,大于,如果是想要从大到小,就是obj - this,如果是从小到大,就是this - obj
*/
@Override
public int compareTo(Object o) {
return ((ServerIp) o).getCurrentWeight() - this.getCurrentWeight();
}
}
还有一个基础的SercerIps类,用于初始化/存放所有的服务对象。
import java.util.*;
/**
* 存放/初始化服务器ip
*/
public class ServerIps {
public static final List<ServerIp> LIST = new ArrayList<>();
public static final Map<String, Integer> WEIGHT_LIST = new LinkedHashMap<>();
static {
LIST.add(new ServerIp("a", 6, 0));
LIST.add(new ServerIp("b", 3, 0));
LIST.add(new ServerIp("c", 1, 0));
for(ServerIp server: LIST){
WEIGHT_LIST.put(server.getIp(),server.getWeight());
}
}
}
随机、加权轮询、加权随机轮询和 平滑加权轮询算法实现
在这个类中,实现了4种算法。
import java.util.*;
/**
* 负载均衡中 轮询算法的实现:随机轮询、权重轮询、权重随机轮询、平滑权重轮询
* 还有最简单的轮询算法,就不写了,
* 一致性哈希环算法,在另一个类里
*/
public class Select {
/**
* 随机轮询算法
*
*/
public static String getServer(){
Random random = new Random();
int rand = random.nextInt(ServerIps.LIST.size()); // random.nextInt(n) 生成[0,n)之间的随机数
return ServerIps.LIST.get(rand).getIp();
}
/**
* 权重轮询 和 加权随机算法 方法1:存储ip
* 主要方法:
* 如果 a b c权重分别是 6 3 1,那就是把ip做成一个新的集合:
* a a a a a a b b b c
* 然后遍历就可以了
* 缺点:
* 如果我有10000台服务器呢,各自的权重再组合一下,这可不是玩的
* */
public static String getServerByWeight() {
List<String> ips = new ArrayList<>();
// 第一个循环,循环权重的列表,取出其权重
for(String ip : ServerIps.WEIGHT_LIST.keySet()){
Integer weight = ServerIps.WEIGHT_LIST.get(ip);// 获取到ip的权重
// 有多少个权重,就往ips 里存几个,然后遍历即可
for(int i = 0; i < weight;i++){
ips.add(ip);
}
}
// 如果是加权轮询的话,直接就遍历ips集合进行输出了,
// 如果是随机加权轮询,同 随机轮询,就是在ips的里边 随机某个值
Random random = new Random();
int ran = random.nextInt(ips.size());
return ips.get(ran);
}
/**
* 权重轮询 方法2:按顺序对比权重
* 主要方法
* 1. 先算出来总的权重,即 6+3+1 = 10
* 2. 接下来,用 请求次数 % 总权重,
* // 这样不管请求多少次,算出来的都是再这个总权重的区间内。也就是0-9
* 3. 最后遍历每个ip的权重,看看这个请求的余数,是落在哪个ip的区间上,返回对应的ip
* 缺点:
* 按顺序遍历所有ip的权重,如果每个ip权重很大的话,就会导致,前边的服务器满载,但是后边的没用
*/
public static String getServerByWeightPlus(Integer num){
// 先计算出总的权重
Integer totalWeight = ServerIps.WEIGHT_LIST.values().stream().mapToInt(w -> w).sum();
// .stream() 将集合转换为一个Stream对象,Stream是Java 8中处理数据集合的一种新方式,它允许进行声明式的数据处理。
// .mapToInt( w -> w) 是一个中间操作,将Stream中的每个元素转换成另一种形式。
// 这里的Lambda表达式w -> w实际上是一个转换函数,它接收一个参数w(代表集合中的元素),并将其转换为int类型。
// 由于ServerIps.WEIGHT_LIST中的元素已经假设是Integer类型,所以这里的转换可能是多余的,除非有特殊的转换逻辑需要应用。
// .sum():将Stream中的所有元素进行求和,返回一个单一的数值。
// 由于mapToInt()操作将Stream中的Integer对象转换成了int类型,.sum()操作将会对这些int类型的数值进行求和。
Integer pos = num % totalWeight;
for(String ip: ServerIps.WEIGHT_LIST.keySet()){
Integer weight = ServerIps.WEIGHT_LIST.get(ip);
// 如果余数落在这个区间内,就给他
if(pos < weight){
return ip;
}else{
// 否则,给pos减去此weight
pos -= weight;
}
}
return "";
}
/**
* 平滑加权轮询
* 主要方法:
* 初始每个ip的动态权重为0,(初始化的方法,在serverIp类构建的时候就有)
* 每来一个请求,就给每个ip更新 动态权重+=设定权重,
* 然后取所有动态权重的最大值,返回该ip,并更新这个ip 的动态权重 -= 总设定权重之和
*
*/
public static String getServerByWeightSmooth(){
Integer sumWeight = ServerIps.WEIGHT_LIST.values().stream().mapToInt(w -> w).sum();
// 更新每个serverIp的 动态权重
for(ServerIp serverIp: ServerIps.LIST){
serverIp.setCurrentWeight(serverIp.getCurrentWeight() + serverIp.getWeight());
}
// 这里不再重新设定一个 currentWeight的list,直接对serverIp进行排序,在serverIp的类里实现了Comparable接口,重写了比较方法
Collections.sort(ServerIps.LIST);
ServerIp maxserverIp = ServerIps.LIST.get(0);
maxserverIp.setCurrentWeight(maxserverIp.getCurrentWeight()-sumWeight);
return maxserverIp.getIp();
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
// System.out.println(getServer()); // 随机轮询算法
// System.out.println(getServerByWeight()); // 加权随机轮训算法
// System.out.println(getServerByWeightPlus(i)); // 加权轮询plus
System.out.println(getServerByWeightSmooth()); // 平滑的加权轮询算法
}
}
}
一致性哈希环算法实现
import java.util.SortedMap;
import java.util.TreeMap;
/**
* 一致性哈希算法
*/
public class HashSelect {
/**
* 存放服务器的hash环,treemap基于红黑树,可以排序
*/
private static final TreeMap<Integer,String> virtualNodes = new TreeMap<>(); // 存放虚拟节点的hash值和真实节点的映射关系
private static final int VIRTUAL_NODES = 100; // 虚拟节点数
public static String getServerByHash(String clientInfo){
// 先生成hash值
int hash = getHash(clientInfo);
// 取出大于等于hash值的子树
SortedMap<Integer, String> sortedMap = virtualNodes.tailMap(hash); // tailMap用于取出 >= 传入的参数 的子树
if (sortedMap.isEmpty()){
// 如果没有大于等于hash值的子树,就取出第一个节点
return virtualNodes.firstEntry().getValue();
}
Integer integer = sortedMap.firstKey(); // 取出其第一个节点。
return virtualNodes.get(integer);
}
/**
* 生成hash值
* 1. 选择一个大的质数,比如16777619
* 2. 用这个质数,对字符串的每个字符进行异或运算
* 3. 最后再进行一些位移运算
* 4. 最后取绝对值,因为有可能是负数
* @param str
* @return
*/
public 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);
}
// System.out.println("hash值为:"+hash);
return hash;
}
public static void addServerNode(){
// 对每个节点进行虚拟节点的复制,并根据hash值进行散列
for(ServerIp ip : ServerIps.LIST){
for(int i = 0;i < VIRTUAL_NODES;i++){
String virtualIp = ip.getIp() + "&&VN" + i;
int hash = getHash(virtualIp);
virtualNodes.put(hash,virtualIp);
}
}
}
public static void main(String[] args) {
addServerNode();
for(int i = 0;i < 20;i++){
System.out.println(getServerByHash("userId"+i));
}
}
}