2台服务器负载均衡后synchronized_一篇有趣的负载均衡算法实现
負載平衡(Load balancing)是一種在多個計算機(網(wǎng)絡(luò)、CPU、磁盤)之間均勻分配資源,以提高資源利用的技術(shù)。使用負載均衡可以最大化服務(wù)吞吐量,可能最小化響應(yīng)時間,同時由于使用負載均衡時,會使用多個服務(wù)器節(jié)點代單點服務(wù),也提高了服務(wù)的可用性。
負載均衡的實現(xiàn)可以軟件可以硬件,硬件如大名鼎鼎的 F5 負載均衡設(shè)備,軟件如 NGINX 中的負載均衡實現(xiàn),又如 Springcloud Ribbon 組件中的負載均衡實現(xiàn)。
如果看到這里你還不知道負載均衡是干嘛的,那么只能放一張圖了,畢竟沒圖說個啥。
正經(jīng)的負載均衡示例
負載均衡要做到在多次請求下,每臺服務(wù)器被請求的次數(shù)大致相同。但是實際生產(chǎn)中,可能每臺機器的性能不同,我們會希望性能好的機器承擔(dān)的請求更多一些,這也是正常需求。
如果這樣說下來你看不懂,那我就再舉個例子好了,一排可愛的小熊(服務(wù)器)站好。
一排要被訪問的服務(wù)器
這時有人(用戶)要過來打臉(請求訪問)。
用戶請求
那么怎么樣我們才能讓這每一個可愛的小熊被打的次數(shù)大致相同呢?
又或者熊 4 比較胖,抗擊打能力是別人的兩倍,我們怎么提高熊 4 被打的次數(shù)也是別人的兩倍呢?
又或者每次出手的力度不同,有重有輕,恰巧熊 4 總是承受這種大力度啪啪打臉,熊 4 即將不省熊事,還要繼續(xù)打它嗎?
這些都是值得思考的問題。
說了那么多,口干舌燥,我雙手已經(jīng)饑渴難耐了,迫不及待的想要擼起代碼了。
1. 隨機訪問
上面說了,為了負載均衡,我們必須保證多次出手后,熊 1 到熊 4 被打次數(shù)均衡。比如使用隨機訪問法,根據(jù)數(shù)學(xué)上的概率論,隨機出手次數(shù)越多,每只熊被打的次數(shù)就會越相近。代碼實現(xiàn)也比較簡單,使用一個隨機數(shù),隨機訪問一個就可以了。
/**?服務(wù)器列表?*/private?static?List?serverList?=?new?ArrayList<>();static?{????serverList.add("192.168.1.2");????serverList.add("192.168.1.3");????serverList.add("192.168.1.4");????serverList.add("192.168.1.5");}/**?*?隨機路由算法?*/public?static?String?random()?{????//?復(fù)制遍歷用的集合,防止操作中集合有變更????List?tempList?=?new?ArrayList<>(serverList.size());????tempList.addAll(serverList);????//?隨機數(shù)隨機訪問????int?randomInt?=?new?Random().nextInt(tempList.size());????return?tempList.get(randomInt);}因為使用了非線程安全的集合,所以在訪問操作時操作的是集合的拷貝,下面幾種輪詢方式中也是這種思想。
寫一個模擬請求方法,請求10w次,記錄請求結(jié)果。
public?static?void?main(String[]?args)?{????HashMap?serverMap?=?new?HashMap<>();????for?(int?i?=?0;?i??entry?:?serverMap.entrySet())?{????????System.out.println("IP:"?+?entry.getKey()?+?",次數(shù):"?+?entry.getValue());????}}運行得到請求結(jié)果。
IP:192.168.1.3,次數(shù):24979IP:192.168.1.2,次數(shù):24896IP:192.168.1.5,次數(shù):25043IP:192.168.1.4,次數(shù):25082每臺服務(wù)器被訪問的次數(shù)都趨近于 2.5w,有點負載均衡的意思。但是隨機畢竟是隨機,是不能保證訪問次數(shù)絕對均勻的。
2. 輪詢訪問
輪詢訪問就簡單多了,拿上面的熊1到熊4來說,我們一個接一個的啪啪 - 打臉,熊1打完打熊2,熊2打完打熊3,熊4打完打熊1,最終也是實現(xiàn)了被打均衡。但是保證均勻總是要付出代價的,隨機訪問中需要隨機,輪詢訪問中需要什么來保證輪詢呢?
/**?服務(wù)器列表?*/private?static?List?serverList?=?new?ArrayList<>();static?{????serverList.add("192.168.1.2");????serverList.add("192.168.1.3");????serverList.add("192.168.1.4");????serverList.add("192.168.1.5");}private?static?Integer?index?=?0;/**?*?隨機路由算法?*/public?static?String?randomOneByOne()?{????//?復(fù)制遍歷用的集合,防止操作中集合有變更????List?tempList?=?new?ArrayList<>(serverList.size());????tempList.addAll(serverList);????String?server?=?"";????synchronized?(index)?{????????index++;????????if?(index?==?tempList.size())?{????????????index?=?0;????????}????????server?=?tempList.get(index);;????}????return?server;}由代碼里可以看出來,為了保證輪詢,必須記錄上次訪問的位置,為了讓在并發(fā)情況下不出現(xiàn)問題,還必須在使用位置記錄時進行加鎖,很明顯這種互斥鎖增加了性能開銷。
依舊使用上面的測試代碼測試10w次請求負載情況。
IP:192.168.1.3,次數(shù):25000IP:192.168.1.2,次數(shù):25000IP:192.168.1.5,次數(shù):25000IP:192.168.1.4,次數(shù):250003. 輪詢加權(quán)
上面演示了輪詢方式,還記得一開始提出的熊4比較胖抗擊打能力強,可以承受別人2倍的挨打次數(shù)嘛?上面兩種方式都沒有體現(xiàn)出來熊 4 的這個特點,熊 4 竊喜,不痛不癢。但是熊 1 到 熊 3 已經(jīng)在崩潰的邊緣,不行,我們必須要讓胖著多打,能者多勞,提高整體性能。
/**?服務(wù)器列表?*/private?static?HashMap?serverMap?=?new?HashMap<>();static?{????serverMap.put("192.168.1.2",?2);????serverMap.put("192.168.1.3",?2);????serverMap.put("192.168.1.4",?2);????serverMap.put("192.168.1.5",?4);}private?static?Integer?index?=?0;/**?*?加權(quán)路由算法?*/public?static?String?oneByOneWithWeight()?{????List?tempList?=?new?ArrayList();????HashMap?tempMap?=?new?HashMap<>();????tempMap.putAll(serverMap);????for?(String?key?:?serverMap.keySet())?{????????for?(int?i?=?0;?i?這次記錄下了每臺服務(wù)器的整體性能,給出一個數(shù)值,數(shù)值越大,性能越好。可以承受的請求也就越多,可以看到服務(wù)器 192.168.1.5 的性能為 4,是其他服務(wù)器的兩倍,依舊 10 w 請求測試。
IP:192.168.1.3,次數(shù):20000IP:192.168.1.2,次數(shù):20000IP:192.168.1.5,次數(shù):40000IP:192.168.1.4,次數(shù):20000192.168.1.5 承擔(dān)了 2 倍的請求。
4. 隨機加權(quán)
隨機加權(quán)的方式和輪詢加權(quán)的方式大致相同,只是把使用互斥鎖輪詢的方式換成了隨機訪問,按照概率論來說,訪問量增多時,服務(wù)訪問也會達到負載均衡。
/**?服務(wù)器列表?*/private?static?HashMap?serverMap?=?new?HashMap<>();static?{????serverMap.put("192.168.1.2",?2);????serverMap.put("192.168.1.3",?2);????serverMap.put("192.168.1.4",?2);????serverMap.put("192.168.1.5",?4);}/**?*?加權(quán)路由算法?*/public?static?String?randomWithWeight()?{????List?tempList?=?new?ArrayList();????HashMap?tempMap?=?new?HashMap<>();????tempMap.putAll(serverMap);????for?(String?key?:?serverMap.keySet())?{????????for?(int?i?=?0;?i?依舊 10 w 請求測試,192.168.1.5 的權(quán)重是其他服務(wù)器的近似兩倍,
IP:192.168.1.3,次數(shù):19934IP:192.168.1.2,次數(shù):20033IP:192.168.1.5,次數(shù):39900IP:192.168.1.4,次數(shù):201335. IP-Hash
上面的幾種方式要么使用隨機數(shù),要么使用輪詢,最終都達到了請求的負載均衡。但是也有一個很明顯的缺點,就是同一個用戶的多次請求很有可能不是同一個服務(wù)進行處理的,這時問題來了,如果你的服務(wù)依賴于 session ,那么因為服務(wù)不同, session 也會丟失,不是我們想要的,所以出現(xiàn)了一種根據(jù)請求端的 ip 進行哈希計算來決定請求到哪一臺服務(wù)器的方式。這種方式可以保證同一個用戶的請求落在同一個服務(wù)上。
private?static?List?serverList?=?new?ArrayList<>();static?{????serverList.add("192.168.1.2");????serverList.add("192.168.1.3");????serverList.add("192.168.1.4");????serverList.add("192.168.1.5");}/**?*?ip?hash?路由算法?*/public?static?String?ipHash(String?ip)?{????//?復(fù)制遍歷用的集合,防止操作中集合有變更????List?tempList?=?new?ArrayList<>(serverList.size());????tempList.addAll(serverList);????//?哈希計算請求的服務(wù)器????int?index?=?ip.hashCode()?%?serverList.size();????return?tempList.get(Math.abs(index));}6. 總結(jié)
上面的四種方式看似不錯,那么這樣操作下來真的體現(xiàn)了一開始說的負載均衡嗎?答案是不一定的。就像上面的最后一個提問。
又或者每次出手的力度不同,有重有輕,恰巧熊 4 總是承受這種大力度啪啪打臉,熊 4 即將不省熊事,還要繼續(xù)打它嗎?
服務(wù)器也是這個道理,每次請求進行的操作對資源的消耗可能是不同的。比如說某些操作它對 CPU 的使用就是比較高,也很正常。所以負載均衡有時不能簡單的通過請求的負載來作為負載均衡的唯一依據(jù)。還可以結(jié)合服務(wù)的當(dāng)前連接數(shù)量、最近響應(yīng)時間等維度進行總體均衡,總而言之,就是為了達到資源使用的負載均衡。
總結(jié)
以上是生活随笔為你收集整理的2台服务器负载均衡后synchronized_一篇有趣的负载均衡算法实现的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python3精要(43)--变量注释
- 下一篇: cv2 imwrite中文路径_pyth