负载均衡这东西,简单说就是把客户端的请求分摊到好几台服务器上搞定,这样系统跑得更快、更稳,还能轻松扩展,简直不要太爽!
常用的负载均衡算法有轮询、加权轮询、随机、加权随机、源IP哈希还有最少连接法啥的,个个都有自己的绝活儿。下面我挨个儿给你们唠唠。
- 轮询法
轮询法真是简单得不能再简单,用的也多。啥思路呢?就是按顺序把请求挨个儿扔给后端服务器,特别直白。一般得保证服务器是无状态的。
就像这样:第一个请求给服务器A,第二个给B,第三个给C,第四个呢,三台都轮了一圈,那就从头再来,又给A。
循环往复,so easy!
这法子好处是简单到飞起,可靠得一批。但有个坑,它不管服务器忙不忙,可能有的忙得要死,有的闲得抠脚。
我给你整个轮询的小代码瞅瞅:
public class RoundRobinDemo {
private static AtomicInteger index = new AtomicInteger(-1);
private static List<String> serverList = new ArrayList<>();
public static String roundRobin() {
int serverCount = serverList.size();
int currentServerIndex = index.incrementAndGet() % serverCount;
return serverList.get(currentServerIndex);
}
public static void main(String[] args) {
serverList.add("Server A");
serverList.add("Server B");
serverList.add("Server C");
System.out.println(roundRobin());
System.out.println(roundRobin());
System.out.println(roundRobin());
System.out.println(roundRobin());
}
}
输出是这样的:
Server A
Server B
Server C
Server A
- 加权轮询法
加权轮询是轮询的升级版。咋玩儿呢?就是给服务器按处理能力或者负载情况加个权重,能干活儿的或者闲着的多接点活儿。
比如服务器A、B、C的权重是4、3、1,那A就得多出力,前三个请求都归它,第四个才轮到B。
这招牛在能稍微照顾一下服务器的实际情况,但还是有点糙,只看权重不管实时负载,可能还是有点不平衡。
我自己整了个加权轮询的代码,给你看看:
public class WeightRoundRobinDemo {
private static AtomicInteger atomicInteger = new AtomicInteger(0);
private static Map<String, Integer> serverMap = new TreeMap<>();
private static int totalWeight = 0;
public static void main(String[] args) {
serverMap.put("Server A", 4);
serverMap.put("Server B", 3);
serverMap.put("Server C", 1);
for (Map.Entry<String, Integer> entry : serverMap.entrySet()) {
totalWeight += entry.getValue();
}
System.out.println(weightRoundRobin());
System.out.println(weightRoundRobin());
System.out.println(weightRoundRobin());
System.out.println(weightRoundRobin());
}
public static String weightRoundRobin() {
int serverCount = serverMap.size();
if (serverCount == 0) return null;
synchronized (serverMap) {
int currentServerIndex = atomicInteger.incrementAndGet() % totalWeight;
for (Map.Entry<String, Integer> entry : serverMap.entrySet()) {
String serverAddress = entry.getKey();
Integer weight = entry.getValue();
currentServerIndex -= weight;
if (currentServerIndex < 0) return serverAddress;
}
}
return null;
}
}
输出:
Server A
Server A
Server A
Server B
- 随机法
随机法就更直接了,请求随便扔给一台后端服务器。简单是真简单,但效果完全看脸,负载均衡啥的基本靠运气。所以一般测试或者压测的时候临时用用还行。
比如第一个请求扔给A,第二第四个给了C,第三个给了B,完全随缘。
代码这么写:
private static List<String> serverList = new ArrayList<>();
public static String random() {
int serverCount = serverList.size();
if (serverCount == 0) return null;
int randomIndex = new Random().nextInt(serverCount);
return serverList.get(randomIndex);
}
- 加权随机法
加权随机是随机法的加强版。跟加权轮询差不多,按服务器的能力或者负载给权重,能干的多分点活儿。
代码是这样的:
private static Map<String, Integer> serverMap = new ConcurrentHashMap<>();
public static String weightRandom() {
int serverCount = serverMap.size();
if (serverCount == 0) return null;
synchronized (serverMap) {
int totalWeight = 0;
for (Map.Entry<String, Integer> entry : serverMap.entrySet()) {
totalWeight += entry.getValue();
}
int randomWeight = new Random().nextInt(totalWeight);
for (Map.Entry<String, Integer> entry : serverMap.entrySet()) {
String serverAddress = entry.getKey();
Integer weight = entry.getValue();
randomWeight -= weight;
if (randomWeight < 0) return serverAddress;
}
}
return null;
}
- 源IP哈希法
源IP哈希法拿请求的IP地址做文章。用哈希函数算个值,再对服务器总数取模,决定扔给谁。
简单说就是IP不变,服务器就不变。比如客户端A的请求全给服务器A,客户端B的全给C。
好处是同一个IP总去同一个服务器,缓存命中率能蹭蹭往上涨。但要是某个IP请求太多,可能把一台服务器压榨得够呛。
代码这么搞:
private static List<String> serverList = new ArrayList<>();
public static String hash(String clientIP) {
int serverCount = serverList.size();
if (serverCount == 0) return null;
int hashCode = clientIP.hashCode();
int serverIndex = hashCode % serverCount;
return serverList.get(serverIndex);
}
- 最少连接法
最少连接法有点聪明,能动态调整。咋弄呢?尽量把请求扔给当前空闲连接最少的服务器,达到负载均衡的效果。得定期瞅瞅每台服务器的连接数,灵活切换。
比如现在C连接最少,请求就全奔着它去。
代码示例:
private static List<String> serverList = new ArrayList<>();
private static Map<String, Integer> connectionsMap = new ConcurrentHashMap<>();
public static String leastConnections() {
int serverCount = serverList.size();
if (serverCount == 0) return null;
String selectedServerAddress = serverList.get(0);
int minConnections = connectionsMap.getOrDefault(selectedServerAddress, 0);
for (int i = 1; i < serverCount; i++) {
String serverAddress = serverList.get(i);
int connections = connectionsMap.getOrDefault(serverAddress, 0);
if (connections < minConnections) {
selectedServerAddress = serverAddress;
minConnections = connections;
}
}
return selectedServerAddress;
}
提醒一句,要是服务器挂了或者网络断了,负载均衡器得重新算连接数,响应时间可能会拖长,影响体验。
好了,这回就聊到这儿,下次再约!🤭