對於做開發的同學來說,負載均衡算法已經不陌生了,今天一起來盤點一下分佈式系統中都是有哪些負載均衡算法以及它的優缺點;
1.輪詢法(Round Robin)
思想: 將請求按順序輪流地分配到後端服務器上,它均衡地對待後端每一臺服務器,而不關心服務實際的連接數和當前系統的負載;
代碼實現:
private List<String> list = new CopyOnWriteArrayList();
private volatile Integer pos = 0;
{
list.add("127.0.0.1");
list.add("127.0.0.2");
list.add("127.0.0.3");
}
public String getServer() {
if (null == list || list.size() <= 0) {
return null;
}
String server = null;
synchronized (pos) {
if (pos >= list.size()) {
pos = 0;
}
server = list.get(pos++);
}
return server;
}
總結:
這種算法簡單,依次轉發,每個服務器的請求數量平均;
缺點:當集羣中的服務器的性能有差別時,無法區別對待的情況下會造成資源浪費;
2.隨機法(Random)
思想: 通過系統隨機函數,根據後端服務器列表大小值來隨機選取其中一臺進行訪問;隨着調用量的增大,效果接近輪詢算法;
代碼實現:
private List<String> list = new CopyOnWriteArrayList();
{
list.add("127.0.0.1");
list.add("127.0.0.2");
list.add("127.0.0.3");
list.add("127.0.0.4");
}
public String getServerRandom() {
if (null == list || list.size() <= 0) {
return null;
}
Random random = new Random();
String server = list.get(random.nextInt(list.size()));
return server;
}
總結:
算法雖然簡單,但是在大請求量的情況下才能保證均衡
3.源地址哈希法(Hash)
思想: 源地址哈希的思想是獲取客戶端訪問的ip地址,通過hash函數計算得到一個值,用該值從服務器列表中進行取模運算;當服務器列表不變時,同一ip總是請求到同一臺服務器中;
代碼實現:
private List<String> list = new CopyOnWriteArrayList();
{
list.add("127.0.0.1");
list.add("127.0.0.2");
list.add("127.0.0.3");
list.add("127.0.0.4");
}
public String getServerHash(String hostIp) {
if (null == list || list.size() <= 0 || null == hostIp) {
return null;
}
int code = hostIp.hashCode();
int serverPos = list.size() % code;
return list.get(serverPos);
}
4.加權輪詢法(Weight Round Robin)
思想: 與輪詢算法相比,它加了權重,權重超高的服務,接收到有請求越多;
代碼實現:
private ConcurrentMap<String, Integer> hosts = new ConcurrentHashMap<>();
private volatile Integer pos = 0;
{
hosts.put("127.0.0.1", 1);
hosts.put("127.0.0.2", 2);
hosts.put("127.0.0.3", 2);
hosts.put("127.0.0.4", 1);
}
public String getServerRoundRobin() {
List<String> list = new ArrayList<>();
for (Map.Entry<String, Integer> entry : hosts.entrySet()) {
Integer value = entry.getValue();
for (int i = 0; i < value; i++) {
list.add(entry.getKey());
}
}
String server = null;
synchronized (pos) {
if (pos >= list.size()) {
pos = 0;
}
server = list.get(pos++);
}
return server;
}
5.加權輪隨機(Weight Random)
思想: 是在隨機的基礎上,加上權值;
代碼實現:
private ConcurrentMap<String, Integer> hosts = new ConcurrentHashMap<>();
private volatile Integer pos = 0;
{
hosts.put("127.0.0.1", 1);
hosts.put("127.0.0.2", 2);
hosts.put("127.0.0.3", 2);
hosts.put("127.0.0.4", 1);
}
public String getServerRandomWeight() {
List<String> list = new ArrayList<>();
for (Map.Entry<String, Integer> entry : hosts.entrySet()) {
Integer value = entry.getValue();
for (int i = 0; i < value; i++) {
list.add(entry.getKey());
}
}
Random random = new Random();
String server = list.get(random.nextInt(list.size()));
return server;
}
上面兩種實現加權的方式都是權重爲幾,就往list裏面add幾次,如果服務器數量之龐大,會導致list列表過大;有另外一種實現加權的方式,把每臺服務和權重劃分爲一段,權重越大,佔的段長越長:
代碼實現:
private Map<String, Integer> hosts = new ConcurrentHashMap<>();
private volatile Integer pos = 0;
{
hosts.put("127.0.0.1", 1);
hosts.put("127.0.0.2", 2);
hosts.put("127.0.0.3", 2);
hosts.put("127.0.0.4", 1);
}
public String getServerRandomWeight2() {
// 累加所有權重
int sum = 0;
for (Map.Entry<String, Integer> entry : hosts.entrySet()) {
sum += entry.getValue();
}
Random random = new Random();
int index = random.nextInt(sum);
for (Map.Entry<String, Integer> entry : hosts.entrySet()) {
Integer value = entry.getValue();
if(value >= index){
return entry.getKey();
}
index -= entry.getValue();
}
return null;
}
6.最小連接數(Least Connections)
思想: 最小連接數法是根據服務器當前的連接情況進行負載均衡的,它會選擇一臺連接數最少的機器來提供服務;
代碼實現:
// key:機器ip value:當前訪問量
private Map<String, Integer> hosts = new ConcurrentHashMap<>();
private volatile Integer pos = 0;
{
hosts.put("127.0.0.1", 6);
hosts.put("127.0.0.2", 2);
hosts.put("127.0.0.3", 3);
hosts.put("127.0.0.4", 8);
}
public String getServerLeastConnection() {
// 尋找最小連接
int min = 0;
String key = null;
for (Map.Entry<String, Integer> entry : hosts.entrySet()) {
if (entry.getValue() < min) {
min = entry.getValue();
key = entry.getKey();
}
}
hosts.put(key, min + 1);
return key;
}
注:文中代碼實現不適合真實場景,只是爲了簡單易懂理解算法思想;