前言
19年我幾乎把阿里很多部門都面過,那一年也是行情最好的一年,icbu、uc、淘系、烏鶇、ae、上海支付寶(剛好有朋友在那),那會真的慘烈,被摁在地上摩擦,不過我也因此全方面的對自己有了一次認識,知道了基礎知識(八股文)不熟,在技術深度上欠缺。最后是拿了淘系的offer,不容易啊~
每次面試都是對過往的review,也是自我提升的墊腳石
最近在搞流量負載相關的東西,順便就溫故下負載均衡算法,然后也玩了一下ChatGPT,發現很強大,給的代碼demo都很詳細,感覺百度都可有可無了,hhh
負載均衡算法有哪些

我感覺可以讓gpt幫我寫篇文章了,笑死。
- 輪訓
- 隨機
- 最小活躍數
- 權重
- 自適應 6.so on...
前兩個我們就不用說了,從第三點來聊聊
最小活躍數
什么是活躍數?它儲存在哪里?為什么要這么做?
這里的活躍數指的是dubbo連接數,就是客戶端對服務端的連接數有多少,它會優先指向連接數小的。它儲存在客戶端,也就是說每次請求會給連接+1,釋放的時候又會-1。為什么這么做呢?為了分攤壓力,讓空閑的服務端多接點流量。
邏輯:從客戶端的記錄里面摟出哪個服務端連接數最小,如果有并列則看權重,如果權重一樣則隨機。
public class LeastActiveLoadBalance extends AbstractLoadBalance {
public static final String NAME = "leastactive";
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
int length = invokers.size(); // 總個數
int leastActive = -1; // 最小的活躍數
int leastCount = 0; // 相同最小活躍數的個數
int[] weights = new int[length]; // 權重
int totalWeight = 0; // 總權重
for (int i = 0; i < length; i++) {
Invoker<T> invoker = invokers.get(i);
int active = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName()).getActive(); // 活躍數
int weight = invoker.getUrl().getMethodParameter(invocation.getMethodName(), WEIGHT_KEY, DEFAULT_WEIGHT); // 權重
if (leastActive == -1 || active < leastActive) { // 發現更小的活躍數,重新開始
leastActive = active; // 記錄最小活躍數
leastCount = 1; // 重新統計相同最小活躍數的個數
weights[0] = weight; // 將當前權重記錄下來,留作備選
totalWeight = weight; // 初始化總權重
} else if (active == leastActive) { // 累計相同最小的活躍數
weights[leastCount++] = weight; // 將當前權重記錄下來,留作備選
totalWeight += weight; // 累加權重
}
}
if (leastCount == 1) { // 如果只有一個最小則直接返回
return invokers.get(0);
}
// 如果權重不相等且權重大于0則按權重分配,否則隨機分配
return selectByRandomWeight(invokers, weights, totalWeight);
}
}
復制代碼
加權負載
這個就是我們人為給這些dubbo服務端加上權重,這樣做的好處就是我們本來就是知道那臺服務器配置比較牛,它可以承受更多的請求,這樣我們讓流量更多的打到它上面,更好利用資源。
基于rt自適應負載均衡
這個一開始我在博客看到的,就是講dubbo3對這塊會有關于cpu還有類似rt相關自適應的負載均衡
我看阿里的天池比賽中,看到這樣一個根據rt來做負載均衡的,大家可以觀摩一哈
- [complone/dubbo-loadbalance](Github.com/complone/du…)
package com.aliware.tianchi;
import org.Apache.dubbo.common.URL;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.cluster.LoadBalance;
import com.aliware.tianchi.comm.CustomerInfo;
import JAVA.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author complone
*/
public class UserLoadBalance implements LoadBalance {
@Override
public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException {
//1、所有服務器爭取每個打一次請求
Invoker invoker = doSelectInFreeInvokers(invokers);
//2、根據服務端信息分配權重
invoker = invoker != null ? invoker : doSelectWithWeigth(invokers);
return invoker;
}
/**
* 落實優先每個機器都有流量請求
*/
private <T> Invoker<T> doSelectInFreeInvokers(List<Invoker<T>> invokers) {
if (CustomerInfoManager.LOAD_INFO.size() < invokers.size()) {
for (Invoker invoker : invokers) {
CustomerInfo customerInfo = CustomerInfoManager.getServerLoadInfo(invoker);
if (customerInfo != null) break;
return invoker;
}
}
return null;
}
/**
* 根據服務端配置和平均耗時計算權重
*/
private <T> Invoker<T> doSelectWithWeigth(List<Invoker<T>> invokers) {
// 重新分配權重的<服務,權重>映射
int[] serviceWeight = new int[invokers.size()];
// 總權重
int totalWeight = 0;
// 1、計算總權重
for (int index = 0, size = invokers.size(); index < size; ++index) {
Invoker<T> invoker = invokers.get(index);
CustomerInfo customerInfo = CustomerInfoManager.getServerLoadInfo(invoker);
AtomicInteger availThreadAtomic = CustomerInfoManager.getAvailThread(invoker);
if (customerInfo != null) {
if (availThreadAtomic.get() > 0) {
int weight = customerInfo.getServerWeight();
//根據耗時重新計算權重(基本權重*(1秒/單個請求耗時))
int clientTimeAvgSpendCurr = customerInfo.getAvgSpendTime();
if (clientTimeAvgSpendCurr == 0) {
// 耗時為0,性能優,請求直接打到該機器
// 也有可能是性能差,采用隨機
return invokers.get(ThreadLocalRandom.current().nextInt(invokers.size()));
}
// 計算權重
weight = weight * (500 / clientTimeAvgSpendCurr);
serviceWeight[index] = weight;
totalWeight = totalWeight + weight;
}
}
}
// 2、按照新的權重選擇服務,權重加權隨機算法
int oneWeight = ThreadLocalRandom.current().nextInt(totalWeight);
for (int i = 0, size = invokers.size(); i < size; ++i) {
oneWeight -= serviceWeight[i];
if (oneWeight < 0) {
return invokers.get(i);
}
}
//兜底采用隨機算法
return invokers.get(ThreadLocalRandom.current().nextInt(invokers.size()));
}
}
復制代碼
首先是
customerInfo.getAvgSpendTime(),就是客戶端會保持每個服務端請求次數、總的請求時間,這樣平均響應時間就出來了,這里為什么要用500去除,而不是其他數字呢?我沒有仔細看內部細節,當分母越大,這個數越小,也就是rt越長,服務節點性能越差,對應的權重會越小,這個思想我們還是能理解的。
int oneWeight = ThreadLocalRandom.current().nextInt(totalWeight);
for (int i = 0, size = invokers.size(); i < size; ++i) {
oneWeight -= serviceWeight[i];
if (oneWeight < 0) {
return invokers.get(i);
}
}
復制代碼
這塊有點意思了,大家看得懂嗎?讓我想起之前抽獎算法,比如說a商品有40%,b商品有60%幾率,怎么去抽獎呢?
同比比例擴大,比如說a有4個商品,b有6個商品,你去隨機抽一個,是不是有那味了,哈哈。同樣這里也是,就是我們把所有服務器節點的權重拿到了,總計一個總的權重,然后我們生成一個隨機數,那么看它最后是命中那個節點,當權重大的它會更有幾率命中,是這么個意思~
那基于cpu呢?
基于cpu自適應負載均衡

最重要就這些數據指標怎么拿到,chatgpt已經跟我講了,可以通過grafana類似運維監控工具來提供,如何跟dubbo結合起來?如果這些數據沒有拿到怎么兜底?
這個思路可以參考服務注冊中心,其實就是pingpong,異步定期去拉取數據然后存到本地緩存里面,具體拉取的頻率怎樣,根據實際情況決定要多久去拉取,其次是訪問量,比如說有很多節點請求次數頻繁,那么就啟用緩存,先把數據存緩存,dubbo直接讀取緩存數據即可。最后兜底方案比如說讀不到服務cpu值,那么直接換負載算法兜底。
總結
流量負載算法是一種常用的負載均衡策略,它可以將網絡流量分配到多個服務器上,從而提高系統的性能和可靠性。常見的流量負載算法包括輪詢、加權輪詢、最小連接數等。在選擇合適的負載均衡算法時,需要考慮服務器的性能、網絡拓撲結構以及用戶訪問模式等因素。同時,為了保證系統的可靠性,需要采取故障轉移和容錯機制。
原文鏈接:
https://juejin.cn/post/7212616585768009787