平滑加權輪詢演算法
所有負載均衡的場景幾乎都會用到這個演算法:假設有2個伺服器A、B,其中A的分配權重為80,B的分配權重為20,當有5個請求過來時,A希望分到4次,B希望分到1次。
一個很自然的想法:A-A-A-A-B ,按權重順序依次分配,同時計數,每分配1次,計數減1,減到0後,再分配『次權重』的伺服器。
看上去好象也湊合能用,但如果A:B的權重是100:1,A-A...-A-...(100次後),才分到B,B要坐很長時間的冷板凳,這顯然不太好。
於是就有了個這個演算法,它的思路如下:
初始狀態時,配置的權重為:{A:80, B:20},然後給每個伺服器,加1個動態的當前權重(curWeight),預設為0,按以下步驟:
1、curWeight += weight (注:weight為配置的權重)
2、挑選curWeight最大的,做為本次分配的結果,然後將curWeight -= sum(weight) ,即:分到的伺服器,其動態權重- sum(配置權重)
3、開始下1次分配,分配前將每臺伺服器上的curWeight += weight(即:重複步驟1)
不斷重複上述過程即可,下面分解下具體過程:
weight 初始狀態:{80, 20},curWeight初始狀態:{0,0}
請求 次數 |
curWeight += weight | max(curWeight) | curWeight -= sum(weight) |
1 | {0,0}+{80,20} = {80,20} | 80,即A | {80 - 100 ,20} = {-20, 20} |
2 | {-20,20}+{80,20} ={60,40} | 60,即A | {60-100 ,40} = {-40, 40} |
3 | {-40, 40}+{80,20}={40,60} | 60,即B | {40, 60-100} = {40, -40} |
4 | {40, -40}+{80,20}={120,-20} | 120,即A | {120-100,-20} = {20, -20} |
5 | {20, -20}+{80,20}={100,0} | 100,即A | {100-100,0} = {0,0} 注:所有伺服器curWeight歸0時,這一輪分配就結束, 下次又回到原點,開始輪迴 |
所以,最終分配的順序就是 A - A - B - A - A,比原來的A - A - A - A - B,是不是更為合理? 這個演算法巧妙的地方在於,每一輪分配完成,所有伺服器的動態權重都會歸0,回到初始狀態!另外1個優勢在於,它能讓所有權重的伺服器,儘早分配到,而非等到高權重的伺服器分配完,才輪到自己。
想想這其中的數學原理也不復雜,每次分到的伺服器,其curWeight 減掉了 配置權重的總和sum(weight),然後下次分配前,又將配置權重加回來了,所以一減一加,正好抵消。
理解其中的原理後,用java程式碼來實現一把:
先定義一個伺服器類:
@Data @AllArgsConstructor @NoArgsConstructor public class ServerInfo { /** * 伺服器主機名 */ public String hostName; /** * (靜態)權重 */ public Integer weight; /** * 當前動態權重 */ public Integer curWeight; }
然後開幹:
/** * 平滑加權輪詢演算法 示例 * by 菩提樹下的楊過 yjmyzz.cnblogs.com * * @param args */ public static void main(String[] args) { List<ServerInfo> serverList = new ArrayList<>(); serverList.add(new ServerInfo("A", 80, 0)); serverList.add(new ServerInfo("B", 20, 0)); //模擬2輪請求 for (int i = 1; i <= 10; i++) { int sumWeight = 0; int maxWeight = 0; ServerInfo currentServer = null; //找出最大的動態權重 for (ServerInfo serverInfo : serverList) { serverInfo.curWeight += serverInfo.weight; sumWeight += serverInfo.curWeight; maxWeight = Math.max(maxWeight, serverInfo.curWeight); if (maxWeight == serverInfo.curWeight) { currentServer = serverInfo; } } //輸出本次請求的選中結果,並更新選中節點的動態權重 currentServer.curWeight -= sumWeight; //實際應用時,下面這行,應該是將請求,轉發到這臺伺服器 System.out.print(currentServer.hostName + " "); //(以下為輔助程式碼) 每輪結束時,輔助輸出換行 boolean roundEnd = true; for (ServerInfo serverInfo : serverList) { if (serverInfo.curWeight != 0) { roundEnd = false; } } if (roundEnd) { System.out.println(""); } } }
輸出:
A A B A A
A A B A A
最後擴充套件一下,這個演算法不僅僅可用於負載均衡,很多業務系統也能用到,比如:線上客服系統,當有客人來諮詢時,系統會從空閒的客服列表裡,分配一個適合的客服來為其服務。因為客服的業務能力不同,能力強的客服可以配置更高權重,多分一些進線給他,反之新人就少分配一些,只要為客服的權重標籤設定不同的值即可。
轉載於:https://www.cnblogs.com/yjmyzz/p/15916460.html