负载均衡算法
一、随机法(Random)
完全随机:通过系统的随机算法,根据后端服务器的列表大小值来随机选取其中的一台服务器进行访问。由概率统计理论可以得知,随着客户端调用服务端的次数增多,其实际效果越来越接近于平均分配调用量到后端的每一台服务器,也就是轮询的结果。
代码实现:
public class Servers {
public List<String> list = new ArrayList<>() {
{
add("192.168.1.1");
add("192.168.1.2");
add("192.168.1.3");
}
};
}
public class FullRandom {
static Servers servers = new Servers();
static Random random = new Random();
public static String go() {
var number = random.nextInt(servers.list.size()); // 创建一个0 ~ list.size() 之间的一个随机数
return servers.list.get(number); // 随机获取list中的值
}
public static void main(String[] args) {
for (var i = 0; i < 10; i++) {
System.out.println(go());
}
}
}
当有充足的请求次数,就会越来越平均;完全随机是最简单的负载均衡算法了,缺点比较明显,因为服务器有好有坏,处理能力是不同的,我们希望性能好的服务器多处理些请求,性能差的服务器少处理一些请求,所以就有了加权随机。
加权随机:虽然还是采用的随机算法,但是为每台服务器设置了权重,权重大的服务器获得的概率大一些,权重小的服务器获得的概率小一些。
方法一:服务器的权重是多少就加入多少次服务器的 ip 到 List 中
代码实现:
public class Servers {
public HashMap<String, Integer> map = new HashMap<>() {
{
put("192.168.1.1", 2); // map 中的值代表该服务器对应的权重
put("192.168.1.2", 7);
put("192.168.1.3", 1);
}
};
}
public class WeightRandom {
static Servers servers = new Servers();
static Random random = new Random();
public static String go() {
var ipList = new ArrayList<String>();
for (var item : servers.map.entrySet()) {
for (var i = 0; i < item.getValue(); i++) {
ipList.add(item.getKey()); // 服务器权重对应多少就将该服务器的ip加入 ipList多少次
}
}
int allWeight = servers.map.values().stream().mapToInt(a -> a).sum();
var number = random.nextInt(allWeight);
return ipList.get(number); // 随机从 ipList 获取服务器ip
}
public static void main(String[] args) {
for (var i = 0; i < 15; i++) {
System.out.println(go());
}
}
}
该方法的优点:简单
缺点:服务器数量很大将会占用很多内存
方法二实现:
如果A服务器的权重是2,B服务器的权重是7,C服务器的权重是1,如图:
所以如果生成的随机数是4就会选择服务器B,以此类推
代码实现:
public class WeightRandom {
static Servers servers = new Servers();
static Random random = new Random();
public static String go() {
int allWeight = servers.map.values().stream().mapToInt(a -> a).sum();
var number = random.nextInt(allWeight);
for (var item : servers.map.entrySet()) {
if (item.getValue() >= number) {
return item.getKey();
}
number -= item.getValue();
}
return "";
}
public static void main(String[] args) {
for (var i = 0; i < 15; i++) {
System.out.println(go());
}
}
}
优点:对内存友好
二、轮询法(Round Robin)
完全轮询:将请求按顺序轮流地分配到后端服务器上,它均衡地对待后端的每一台服务器,而不关心服务器实际的连接数和当前的系统负载
代码实现:
public class FullRound {
static Servers servers = new Servers();
static int index;
public static String go() {
if (index == servers.list.size()) {
index = 0; // 走到头了从0开始
}
return servers.list.get(index++); // 依次访问
}
public static void main(String[] args) {
for (var i = 0; i < 15; i++) {
System.out.println(go());
}
}
}
加权轮询法(Weight Round Robin):
不同的后端服务器可能机器的配置和当前系统的负载并不相同,因此它们的抗压能力也不相同。给配置高、负载低的机器配置更高的权重,让其处理更多的请;而配置低、负载高的机器,给其分配较低的权重,降低其系统负载,加权轮询能很好地处理这一问题,并将请求顺序且按照权重分配到后端
代码实现:
public class WeightRound {
static Servers servers = new Servers();
static int index;
public static String go() {
int allWeight = servers.map.values().stream().mapToInt(a -> a).sum();
int number = (index++) % allWeight;
for (var item : servers.map.entrySet()) {
if (item.getValue() > number) {
return item.getKey();
}
number -= item.getValue();
}
return "";
}
public static void main(String[] args) {
for (var i = 0; i < 15; i++) {
System.out.println(go());
}
}
}
三、源地址哈希法(Hash)
源地址哈希的思想是根据获取客户端的IP地址,通过哈希函数计算得到的一个数值,用该数值对服务器列表的大小进行取模运算,得到的结果便是客服端要访问服务器的序号。采用源地址哈希法进行负载均衡,同一IP地址的客户端,当后端服务器列表不变时,它每次都会映射到同一台后端服务器进行访问,这样可以解决负载均衡下 Session 共享的问题
现在我们看它是如何实现的吧
如图所示:我们可以想象这个圆由无数个点组成,当有一个请求过来时,会把这个请求交给按照顺时针旋转的第一个服务器处理,比如请求在A和B中,那个这个请求就会交给服务器B处理。
如果服务器C宕机了,那么服务器D的服务范围就会变大,此时服务器D的压力就会过大,而服务器B比较空闲,这种情况叫做”哈希倾斜“,那如何解决呢?其实我们只需要在这些服务器之间插入虚拟节点即可,如图:
图中三角形是虚拟节点,圆圈是真实的节点,当有请求在如图所示时,此时按照顺时针算法,请求会交给虚拟的D处理,但是最后会交由真实的服务器D处理,所以这样就可以减小上面的一个服务器非常忙绿,一个服务器非常空闲的情况
代码实现:
private static String go(String client) {
int nodeCount = 20;
TreeMap<Integer, String> treeMap = new TreeMap();
for (String s : new Servers().list) {
for (int i = 0; i < nodeCount; i++)
treeMap.put((s + "--服务器---" + i).hashCode(), s);
}
int clientHash = client.hashCode();
SortedMap<Integer, String> subMap = treeMap.tailMap(clientHash);
Integer firstHash;
if (subMap.size() > 0) {
firstHash = subMap.firstKey();
} else {
firstHash = treeMap.firstKey();
}
String s = treeMap.get(firstHash);
return s;
}
public static void main(String[] args) {
System.out.println(go("今天天气不错啊"));
System.out.println(go("192.168.5.258"));
System.out.println(go("0"));
System.out.println(go("-110000"));
System.out.println(go("风雨交加"));
}
四、最小连接数法(Least Connections)
最小连接数算法比较灵活和智能,由于后端服务器的配置不尽相同,对于请求的处理有快有慢,它是根据后端服务器当前的连接情况,动态地选取其中当前积压连接数最少的一台服务器来处理当前的请求,尽可能地提高后端服务的利用效率,将负责合理地分流到每一台服务器
例如:如果A服务器有100个请求,B服务器有5个请求,而C服务器只有3个请求,那么毫无疑问会选择C服务器,这种负载均衡算法是比较科学的。但是遗憾的在当前的场景下无法模拟出来“原汁原味”的最小压力负载均衡算法的。
参考:https://juejin.cn/post/6844903793012768781
https://www.pdai.tech/md/algorithm/alg-domain-load-balance.html#%E9%9A%8F%E6%9C%BA%E6%B3%95random