關于Java中常見的負載均衡算法
負載均衡

負載平衡(Load balancing)是一種電子計算機技術,用來在多個計算機(計算機集群)、網絡連接、CPU、磁盤驅動器或其他資源中分配負載,以達到優(yōu)化資源使用、最大化吞吐率、最小化響應時間、同時避免過載的目的。
使用帶有負載平衡的多個服務器組件,取代單一的組件,可以通過冗余提高可靠性。
負載平衡服務通常是由專用軟件和硬件來完成。
主要作用是將大量作業(yè)合理地分攤到多個操作單元上進行執(zhí)行,用于解決互聯(lián)網架構中的高并發(fā)和高可用的問題。
常見的負載均衡算法

1.輪詢(Round Robin)
輪詢算法按照順序將新的請求分配給下一個服務器,最終實現(xiàn)平分請求。
實例:已知服務器: s1 ,s2, s3
請求1 -> s1
請求2-> s2
請求3 -> s3
請求4 -> s1
請求5 -> s2
請求6 -> s3
…
優(yōu)點:
- ? 實現(xiàn)簡單,無需記錄各種服務的狀態(tài),是一種無狀態(tài)的負載均衡策略。
- ? 實現(xiàn)絕對公平
缺點:
- 當各個服務器性能不一致的情況,無法根據(jù)服務器性能去分配,無法合理利用服務器資源。
java實現(xiàn)輪詢算法:
思路:根據(jù)上面的介紹,依次的選擇下一個服務器,輪詢算法具有周期性的特性,這就是典型的周期性概念,我們第一想法應該就是取余了。
這里推薦大家《程序員的數(shù)學1》里面介紹了一些數(shù)學和編程思維的一些案例,其中就有介紹周期和分組的思想,個人感覺這本書還是不錯的,推薦給大家。
public class RoundRobin {
@Data
public static class Server {
private int serverId;
private String name;
private int weight;
public Server(int serverId, String name) {
this.serverId = serverId;
this.name = name;
}
public Server(int serverId, String name, int weight) {
this.serverId = serverId;
this.name = name;
this.weight = weight;
}
}
private static AtomicInteger NEXT_SERVER_COUNTER = new AtomicInteger(0);
private static int select(int modulo) {
for (; ; ) {
int current = NEXT_SERVER_COUNTER.get();
int next = (current + 1) % modulo;
boolean compareAndSet = NEXT_SERVER_COUNTER.compareAndSet(current, next);
if (compareAndSet) {
return next;
}
}
}
public static Server selectServer(List<Server> serverList) {
return serverList.get(select(serverList.size()));
}
public static void main(String[] args) {
List<Server> serverList = new ArrayList<>();
serverList.add(new Server(1, "服務器1"));
serverList.add(new Server(2, "服務器2"));
serverList.add(new Server(3, "服務器3"));
for (int i = 0; i < 10; i++) {
Server selectedServer = selectServer(serverList);
System.out.format("第%d次請求,選擇服務器%s\n", i + 1, selectedServer.toString());
}
}
}
2.加權輪詢(WeightedRound-Robin)
由于不同的服務器配置不同,因此它們處理請求的能力也不同,給配置高的機器配置相對較高的權重,讓其處理更多的請求,給配置較低的機器配置較低的權重減輕期負載壓力。
加權輪詢可以較好的解決這個問題。
思路:
根據(jù)權重的大小讓其獲得相應被輪詢到的機會。
已知:
| 服務器 | 權重 |
| s1 | 1 |
| s2 | 2 |
| s3 | 3 |
可以根據(jù)權重我們在內存中創(chuàng)建一個這樣的數(shù)組{s1,s2,s2,s3,s3,s3},然后再按照輪詢的方式選擇相應的服務器。
缺點:
- 請求被分配到三臺服務器上機會不夠平滑。
- 前3次請求都不會落在server3上。
Nginx實現(xiàn)了一種平滑的加權輪詢算法,可以將請求平滑(均勻)的分配到各個節(jié)點上。
下面我們用Java實現(xiàn)一下這個算法。
實現(xiàn)思路
我們以當前節(jié)點權重作為被選中的概率
public void incrCurrentWeight() {
this.currentWeight += weight;
}為了避免權重大的被連續(xù)選中,所以再被選中的時候我們應該讓其的當前權重變小。我們可以采用
//當前權重 = 當前權重 - 總權重
1-6 =-5
3-6 =-3
可得權重越大下次當前權重變成最大的可能性也越大
public void selected(int total) {
this.currentWeight -= total;
}我們選取當前當前權重最大的一個服務器
public class WeightRoundRobin {
@Data
public static class Server {
private int serverId;
private String name;
private int weight;
private int currentWeight;
public Server(int serverId, String name) {
this.serverId = serverId;
this.name = name;
}
public Server(int serverId, String name, int weight) {
this.serverId = serverId;
this.name = name;
this.weight = weight;
}
public void selected(int total) {
this.currentWeight -= total;
}
public void incrCurrentWeight() {
this.currentWeight += weight;
}
}
public static Server selectServer(List<Server> serverList) {
int total = 0;
Server selectedServer = null;
int maxWeight = 0;
for (Server server : serverList) {
total += server.getWeight();
server.incrCurrentWeight();
//選取當前權重最大的一個服務器
if (selectedServer == null || maxWeight < server.getCurrentWeight()) {
selectedServer = server;
maxWeight = server.getCurrentWeight();
}
}
if (selectedServer == null){
Random random = new Random();
int next = random.nextInt(serverList.size());
return serverList.get(next);
}
selectedServer.selected(total);
return selectedServer;
}
public static void main(String[] args) {
List<Server> serverList = new ArrayList<>();
serverList.add(new Server(1, "服務器1", 1));
serverList.add(new Server(2, "服務器2", 3));
serverList.add(new Server(3, "服務器3", 10));
for (int i = 0; i < 10; i++) {
Server server = selectServer(serverList);
System.out.format("第%d次請求,選擇服務器%s\n", i + 1, server.toString());
}
}
3.隨機(Random)
思路:利用隨機數(shù)從所有服務器中隨機選取一臺,可以用服務器數(shù)組下標獲取。
public class RandomLoadBalance {
@Data
public static class Server {
private int serverId;
private String name;
private int weight;
public Server(int serverId, String name) {
this.serverId = serverId;
this.name = name;
}
}
public static Server selectServer(List<Server> serverList) {
Random selector = new Random();
int next = selector.nextInt(serverList.size());
return serverList.get(next);
}
public static void main(String[] args) {
List<Server> serverList = new ArrayList<>();
serverList.add(new Server(1, "服務器1"));
serverList.add(new Server(2, "服務器2"));
serverList.add(new Server(3, "服務器3"));
for (int i = 0; i < 10; i++) {
Server selectedServer = selectServer(serverList);
System.out.format("第%d次請求,選擇服務器%s\n", i + 1, selectedServer.toString());
}
}
}
4.加權隨機(Weight Random)
思路:
這里我們是利用區(qū)間的思想,通過一個小于在此區(qū)間范圍內的一個隨機數(shù),選中對應的區(qū)間(服務器),區(qū)間越大被選中的概率就越大。
已知:
| 服務器 | 權重 |
| s1 | 1 |
| s2 | 2 |
| s3 | 3 |
那么:
s1:[0,1] s2:(1,3] s3 (3,6]
public class WeightRandom {
@Data
public static class Server {
private int serverId;
private String name;
private int weight;
public Server(int serverId, String name) {
this.serverId = serverId;
this.name = name;
}
public Server(int serverId, String name, int weight) {
this.serverId = serverId;
this.name = name;
this.weight = weight;
}
}
private static Server selectServer(List<Server> serverList) {
int sumWeight = 0;
for (Server server : serverList) {
sumWeight += server.getWeight();
}
Random serverSelector = new Random();
int nextServerRange = serverSelector.nextInt(sumWeight);
int sum = 0;
Server selectedServer = null;
for (Server server : serverList) {
if (nextServerRange >= sum && nextServerRange < server.getWeight() + sum) {
selectedServer = server;
}
sum += server.getWeight();
}
return selectedServer;
}
public static void main(String[] args) {
List<Server> serverList = new ArrayList<>();
serverList.add(new Server(1, "服務器1", 1));
serverList.add(new Server(2, "服務器2", 5));
serverList.add(new Server(3, "服務器3", 10));
for (int i = 0; i < 10; i++) {
Server selectedServer = selectServer(serverList);
System.out.format("第%d次請求,選擇服務器%s\n", i + 1, selectedServer.toString());
}
}
}
5.IPHash
思路:根據(jù)每個每個請求ip(也可以是某個標識)ip.hash() % server.size()
public class IpHash {
@Data
public static class Server {
private int serverId;
private String name;
public Server(int serverId, String name) {
this.serverId = serverId;
this.name = name;
}
}
public static Server selectServer(List<Server> serverList, String ip) {
int ipHash = ip.hashCode();
return serverList.get(ipHash % serverList.size());
}
public static void main(String[] args) {
List<Server> serverList = new ArrayList<>();
serverList.add(new Server(1, "服務器1"));
serverList.add(new Server(2, "服務器2"));
serverList.add(new Server(3, "服務器3"));
List<String> ips = Arrays.asList("192.168.9.5", "192.168.9.2", "192.168.9.3");
for (int i = 0; i < 10; i++) {
for (String ip : ips) {
Server selectedServer = selectServer(serverList, ip);
System.out.format("請求ip:%s,選擇服務器%s\n", ip, selectedServer.toString());
}
}
}
}
可以看到結果:同一ip肯定會命中同一臺機器。
到此這篇關于關于Java中常見的負載均衡算法的文章就介紹到這了,更多相關Java負載均衡算法內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
IDEA如何使用spring-Initializr快速搭建SpringBoot
這篇文章主要介紹了IDEA如何使用spring-Initializr快速搭建SpringBoot問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-05-05

