天天看點

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

古人雲,不患寡而患不均。

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

這裡的服務是廣義的,可以是簡單的計算,也可能是資料的讀取或者存儲。負載均衡也不是新事物,這種思想在多核CPU時代就有了,隻不過在分布式系統中,負載均衡更是無處不在,這是分布式系統的天然特性決定的,分布式就是利用大量計算機節點完成單個計算機無法完成的計算、存儲服務,既然有大量計算機節點,那麼均衡的排程就非常重要。

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

事實上,網上有很多文章介紹負載均衡的算法,大多都是大同小異。本文更多的是自己對這些算法的總結與思考。

一分鐘了解負載均衡的一切

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

常見網際網路分布式架構如上,分為用戶端層、反向代理nginx層、站點層、服務層、資料層。可以看到,每一個下遊都有多個上遊調用,隻需要做到,每一個上遊都均勻通路每一個下遊,就能實作“将請求/資料【均勻】分攤到多個操作單元上執行”。

(1)【用戶端層】到【反向代理層】的負載均衡,是通過“DNS輪詢”實作的;

(2)【反向代理層】到【站點層】的負載均衡,是通過“nginx”實作的;

(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,四層的F5、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等多個知識點的架構資料)合理利用自己每一分每一秒的時間來學習提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的自己一個交代!