關於負載均衡的一切:總結與思考

古人云,不患寡而患不均。nginx

在計算機的世界,這就是你們耳熟能詳的負載均衡(load balancing),所謂負載均衡,就是說若是一組計算機節點(或者一組進程)提供相同的(同質的)服務,那麼對服務的請求就應該均勻的分攤到這些節點上。負載均衡的前提必定是「provide a single Internet service from multiple servers」, 這些提供服務的節點被稱之爲server farm、server pool或者backend servers。web

這裏的服務是廣義的,能夠是簡單的計算,也多是數據的讀取或者存儲。負載均衡也不是新事物,這種思想在多核CPU時代就有了,只不過在分佈式系統中,負載均衡更是無處不在,這是分佈式系統的自然特性決定的,分佈式就是利用大量計算機節點完成單個計算機沒法完成的計算、存儲服務,既然有大量計算機節點,那麼均衡的調度就很是重要。算法

負載均衡的意義在於,讓全部節點以最小的代價、最好的狀態對外提供服務,這樣系統吞吐量最大,性能更高,對於用戶而言請求的時間也更小。並且,負載均衡加強了系統的可靠性,最大化下降了單個節點過載、甚至crash的機率。不難想象,若是一個系統絕大部分請求都落在同一個節點上,那麼這些請求響應時間都很慢,並且萬一節點降級或者崩潰,那麼全部請求又會轉移到下一個節點,形成雪崩。sql

事實上,網上有不少文章介紹負載均衡的算法,大多都是大同小異。本文更多的是本身對這些算法的總結與思考。數據庫

一分鐘瞭解負載均衡的一切安全

本章節的標題和內容都來自一分鐘瞭解負載均衡的一切這一篇文章。固然,原文的標題是誇張了點,不過文中列出了在一個大型web網站中各層是如何用到負載均衡的,一目瞭然。服務器

常見互聯網分佈式架構如上,分爲客戶端層、反向代理nginx層、站點層、服務層、數據層。能夠看到,每個下游都有多個上游調用,只須要作到,每個上游都均勻訪問每個下游,就能實現「將請求/數據【均勻】分攤到多個操做單元上執行」。cookie

(1)【客戶端層】到【反向代理層】的負載均衡,是經過「DNS輪詢」實現的;網絡

(2)【反向代理層】到【站點層】的負載均衡,是經過「nginx」實現的;session

(3)【站點層】到【服務層】的負載均衡,是經過「服務鏈接池」實現的;

(4)【數據層】的負載均衡,要考慮「數據的均衡」與「請求的均衡」兩個點,常見的方式有「按照範圍水平切分」與「hash水平切分」。

數據層的負載均衡,在我以前的《帶着問題學習分佈式系統之數據分片》中有詳細介紹。

算法衡量

在我看來,當咱們提到一個負載均衡算法,或者具體的應用場景時,應該考慮如下問題:

第一,是否意識到不一樣節點的服務能力是不同的,好比CPU、內存、網絡、地理位置;

第二,是否意識到節點的服務能力是動態變化的,高配的機器也有可能因爲一些突發緣由致使處理速度變得很慢;

第三,是否考慮將同一個客戶端,或者說一樣的請求分發到同一個處理節點,這對於「有狀態」的服務很是重要,好比session,好比分佈式存儲;

第四,誰來負責負載均衡,即誰充當負載均衡器(load balancer),balancer自己是否會成爲瓶頸。

下面會結合具體的算法來考慮這些問題。

負載均衡算法

輪詢算法(round-robin)

思想很簡單,就是提供同質服務的節點逐個對外提供服務,這樣能作到絕對的均衡。Python示例代碼以下:

 

能夠看到,全部的節點都是以一樣的機率提供服務,即沒有考慮到節點的差別,也許一樣數目的請求,高配的機器CPU才20%,低配的機器CPU已經80%了。

加權輪詢算法(weight round-robin)

加權輪訓算法就是在輪訓算法的基礎上,考慮到機器的差別性,分配給機器不一樣的權重,能者多勞。注意,這個權重的分配依賴於請求的類型,好比計算密集型,那就考慮CPU、內存;若是是IO密集型,那就考慮磁盤性能。Python示例代碼以下:

 

隨機算法(random)

這個就更好理解了,隨機選擇一個節點服務,按照機率,只要請求數量足夠多,那麼也能達到絕對均衡的效果。並且實現簡單不少

 

加權隨機算法(random)

如同加權輪訓算法至於輪訓算法同樣,也是在隨機的時候引入不一樣節點的權重,實現也很相似。

 

固然,若是節點列表以及權重變化不大,那麼也能夠對全部節點歸一化,而後按機率區間選擇:

 

哈希法(hash)

根據客戶端的IP,或者請求的「Key」,計算出一個hash值,而後對節點數目取模。好處就是,同一個請求可以分配到一樣的服務節點,這對於「有狀態」的服務頗有必要:

 

只要hash結果足夠分散,也是能作到絕對均衡的。

一致性哈希

哈希算法的缺陷也很明顯,當節點的數目發生變化的時候,請求會大機率分配到其餘的節點,引起到一系列問題,好比sticky session。並且在某些狀況,好比分佈式存儲,是絕對的不容許的。

爲了解決這個哈希算法的問題,又引入了一致性哈希算法,簡單來講,一個物理節點與多個虛擬節點映射,在hash的時候,使用虛擬節點數目而不是物理節點數目。當物理節點變化的時候,虛擬節點的數目無需變化,只涉及到虛擬節點的從新分配。並且,調整每一個物理節點對應的虛擬節點數目,也就至關於每一個物理節點有不一樣的權重。

最少鏈接算法(least connection)

以上的諸多算法,要麼沒有考慮到節點間的差別(輪訓、隨機、哈希),要麼節點間的權重是靜態分配的(加權輪訓、加權隨機、一致性hash)。

考慮這麼一種狀況,某臺機器出現故障,沒法及時處理請求,但新的請求仍是會以必定的機率源源不斷的分配到這個節點,形成請求的積壓。所以,根據節點的真實負載,動態地調整節點的權重就很是重要。固然,要得到接節點的真實負載也不是一律而論的事情,如何定義負載,負載的收集是否及時,這都是須要考慮的問題。

每一個節點當前的鏈接數目是一個很是容易收集的指標,所以lease connection是最常被人提到的算法。也有一些側重不一樣或者更復雜、更客觀的指標,好比最小響應時間(least response time)、最小活躍數(least active)等等。

一點思考

有狀態的請求  

首先來看看「算法衡量」中提到的第三個問題:同一個請求是否分發到一樣的服務節點,同一個請求指的是同一個用戶或者一樣的惟一標示。何時同一請求最好(必須)分發到一樣的服務節點呢?那就是有狀態 -- 請求依賴某些存在於內存或者磁盤的數據,好比web請求的session,好比分佈式存儲。怎麼實現呢,有如下幾種辦法:

(1)請求分發的時候,保證同一個請求分發到一樣的服務節點

這個依賴於負載均衡算法,好比簡單的輪訓,隨機確定是不行的,哈希法在節點增刪的時候也會失效。可行的是一致性hash,以及分佈式存儲中的按範圍分段(即記錄哪些請求由哪一個服務節點提供服務),代價是須要在load balancer中維護額外的數據。

(2)狀態數據在backend servers之間共享

保證同一個請求分發到一樣的服務節點,這個只是手段,目的是請求能使用到對應的狀態數據。若是狀態數據可以在服務節點之間共享,那麼也能達到這個目的。好比服務節點鏈接到共享數據庫,或者內存數據庫如memcached

(3)狀態數據維護在客戶端

這個在web請求中也有使用,即cookie,不過要考慮安全性,須要加密。

關於load balancer

接下來回答第四個問題:關於load balancer,其實就是說,在哪裏作負載均衡,是客戶端仍是服務端,是請求的發起者仍是請求的3。具體而言,要麼是在客戶端,根據服務節點的信息自行選擇,而後將請求直接發送到選中的服務節點;要麼是在服務節點集羣以前放一個集中式代理(proxy),由代理負責請求求分發。無論哪種,至少都須要知道當前的服務節點列表這一基礎信息。

若是在客戶端實現負載均衡,客戶端首先得知道服務器列表,要麼是靜態配置,要麼有簡單接口查詢,但backend server的詳細負載信息,就不適用經過客戶端來查詢。所以,客戶端的負載均衡算法要麼是比較簡單的,好比輪訓(加權輪訓)、隨機(加權隨機)、哈希這幾種算法,只要每一個客戶端足夠隨機,按照大數定理,服務節點的負載也是均衡的。要在客戶端使用較爲複雜的算法,好比根據backend的實際負載,那麼就須要去額外的負載均衡服務(external load balancing service)查詢到這些信息,在grpc中,就是使用的這種辦法。

能夠看到,load balancer與grpc server通訊,得到grpc server的負載等具體詳細,而後grpc client從load balancer獲取這些信息,最終grpc client直連到被選擇的grpc server。

而基於Proxy的方式是更爲常見的,好比7層的Nginx,四層的F五、LVS,既有硬件路由,也有軟件分發。集中式的特色在於方便控制,並且能容易實現一些更精密,更復雜的算法。但缺點也很明顯,一來負載均衡器自己可能成爲性能瓶頸;二來可能引入額外的延遲,請求必定先發到達負載均衡器,而後到達真正的服務節點。

load balance proxy對於請求的響應(response),要麼不通過proxy,如LVS;要麼通過Proxy,如Nginx。下圖是LVS示意圖(來源見水印)

而若是response也是走load balancer proxy的話,那麼整個服務過程對客戶端而言就是徹底透明的,也防止了客戶端去嘗試鏈接後臺服務器,提供了一層安全保障!

值得注意的是,load balancer proxy不能成爲單點故障(single point of failure),所以通常會設計爲高可用的主從結構

歡迎工做一到五年的Java工程師朋友們加入Java架構開發:744677563

羣內提供免費的Java架構學習資料(裏面有高可用、高併發、高性能及分佈式、Jvm性能調優、Spring源碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)合理利用本身每一分每一秒的時間來學習提高本身,不要再用"沒有時間「來掩飾本身思想上的懶惰!趁年輕,使勁拼,給將來的本身一個交代!

相關文章
相關標籤/搜索