1. 程式人生 > >微服務-如何做好叢集中伺服器的負載均衡

微服務-如何做好叢集中伺服器的負載均衡

那些負載均衡的面試題

簡單說一下什麼是負載均衡?很多人最怕這種概念性問題

你們公司負載均衡用的什麼?

為什麼用這種?

它的優缺點

有更好的選擇嗎?

你說這5聯問,誰受得了啊,叢淺到深,一環扣一環,簡直不要了,別怕,仔細閱讀本文,這些問題都會迎刃而解。

什麼是負載均衡?

俗話解釋一下負載均衡:你要在10個餐廳中選一個吃午餐,那麼你選的這個過程就是負載均衡的過程,(面試也是可以這麼說的)。
正規的行話:負載均衡指的是在一個叢集中通過某種硬體裝置或者軟體演算法來選擇叢集中的一臺機器處理當前請求,以達到大量請求的分散給後端叢集不同機器處理,從而提升高併發能力和容災能力。
百度百科:負載均衡建立在現有網路結構之上,它提供了一種廉價有效透明的方法擴充套件網路裝置和伺服器的頻寬、增加吞吐量、加強網路資料處理能力、提高網路的靈活性和可用性

軟硬體負載均衡詳解

目前負載均衡總的來說分為三大類:1 硬體裝置負載均衡,2 軟體演算法負載均衡,3 基於DNS的負載均衡 分別介紹一下這三大類的不同和優缺點。

硬體負載均衡解決方案是直接在伺服器和外部網路間安裝負載均衡裝置,這種裝置通常稱之為負載均衡器,由於專門的裝置完成專門的任務,獨立於作業系統,整體效能得到大量提高,加上多樣化的負載均衡策略,智慧化的流量管理,可達到最佳的負載均衡需求,其主要應用在大型伺服器叢集中,比如F5負載均衡器。

軟體負載均衡指的是在伺服器的作業系統上安裝負載均衡軟體,從此伺服器發出的請求經軟體負載均衡演算法路由到後端叢集的某一臺機器上。

DNS負載均衡一般用於地理位置上的負載均衡,比如你的網站在全國範圍內都有海量使用者,那麼當不同使用者訪問網站域名時經過DNS判斷返回給不同地理位置的使用者的不同IP,從而達到就近訪問,流量分擔,提升使用者體驗。

他們的優缺點是什麼呢?

硬體負載均衡一般只是關注網路流量的負載,至於後端伺服器的狀態等他不操心,而且成本貴,往往也是單點,但它也有優點,就是效能好,處理能力強,與作業系統無關性。

軟體負載均衡比較靈活,可調整性大,與軟體演算法實現有關係,能夠關注應用伺服器的狀態做彙總統計試別的能力,價效比較高,但受軟體安裝的伺服器效能影響,同時也沒硬體的效能好,DNS負載均衡也屬於軟體負載均衡的一種。

本文主要分析的也是軟體負載均衡。

常用的負載均衡演算法和實現原理

負載均衡中介軟體現在很多,大家最熟悉的,也是最出名的就屬Nginx了,其次也有很多,比如百度前段時間開源了bfe(百度統一前端),是百度7層流量轉發平臺,還有apache,各種微服務中介軟體中的負載均衡演算法等

我們主要分析下這些中介軟體負載均衡策略是怎麼實現的?用的什麼演算法,重點來了

  1. Random 隨機
  2. Round Robin 輪詢
  3. Weighted Round Robin 加權輪詢
  4. Least Connections 最少連線
  5. Latency-Aware 延遲感知(最小延遲,也就是說那臺機器效能最好,就用那臺)
  6. Source Hashing 源地址雜湊
  7. Consistency hash 一致性雜湊(一般在分散式快取中比較常見 )

隨機策略指的是在後端叢集機器的IP列表中根據隨機數選擇一個IP作為此次請求的應答者,當隨機演算法足夠好,足夠公平時,在海量請求下,最終後端叢集各個機器承載的流量是均衡, 隨機策略會導致配置較低的機器Down機,從而可能引起雪崩,一般採用隨機演算法時建議後端叢集機器配置最好同等的,隨機策略的效能取決與隨機演算法的效能。

輪詢策略指的是在叢集中對所有機器編號,假設10臺機器,從0-9,請求來臨時從0號機器開始,後續每來一次請求對編號加1,這樣一直迴圈,上面的隨機策略其實最後就變成輪詢了,這兩種策略都不關心機器的負載和執行情況,而且對變數操作會引入鎖操作,效能也會下會下降。

加權輪詢策略指的是回給後端叢集每臺機器都分配一個權重,權重高得會承擔更多的流量,相反權重低的分配的流量也會少,這種策略允許後端叢集機器配置差異化,假設有3臺機器(a,b,c),他們的權重分別是(7,2,1),那麼10次請求a機器承擔7次,b機器承擔2次,c機器承擔1次,但是這種承擔法到底怎麼分配呢?有兩種情況如下,我們可以看到第一種請求在a的時候,bc完全空閒,而第二種情況相對均勻一些,Nginx的加權輪詢策略採用的就是第二種情況

  1. (aaaaaaa,bb,c)
  2. (aabaabaaca)

最少連線策略會關注後端叢集各個伺服器當前的連線數,選擇一個最少連線數的機器應答當前請求,這種策略實際上關注各個伺服器的負載情況,選擇負載最低的機器處理請求,儘可能的提高各個機器的利用率,相對來說比較靈活和智慧,實現上也會複雜一些。

延遲感知策略和最少連線是一樣的思想,延遲感知追求極致的效能或者說使用者體驗,總是挑選能夠最快的返回執行結果的機器來訪問,但壞處是當都所有客戶端都認為某臺伺服器最快時,那麼所有請求都發送這臺服務反而可能造成服務壓力過大,效能降低。

源地址雜湊策略能夠讓同一客戶端的請求或者同一使用者的請求總是請求在後端同一臺機器上,這種演算法根據客戶端IP求出Hash值然後對端叢集總數求餘得到值就是伺服器集合的下標,一般這種演算法用於快取命中,或者同一會話請求等,但這種演算法也有一定的缺點,某一使用者訪問量(黑產)非常高時可能造成服務端壓力過大或者後端服務Down掉,那麼客戶端就會無法訪問,所以也需要一定的降級策略。

一致性雜湊是在源地址雜湊的基礎上發展得來的,什麼意思呢?後端叢集有是個3臺機器(a,b,c),客戶端經過雜湊對伺服器總數取餘後總是請求到a機器,那麼當後端叢集新增或者減少一臺機器時,客戶端雜湊後對伺服器總數取餘後就不再是原來的那臺機器了,這樣原來所有的請求雜湊後對應的後臺機器都發生了變化,一致性雜湊就是解決這種問題的.

實現一個負載均衡演算法

我們挑選上面一種策略用程式碼來實現一下,以便讓大家更深入的理解,選擇一個面試常問的策略,1、加權輪詢演算法,這個也比較多,Nginx中預設的演算法

加權輪詢演算法每臺伺服器有三個權重:初始配置的權重,當前權重,有效權重,其中初始配置權重和有效權重是不變的,預設情況有效權重等於初始配置權重,當配置檔案的初始配置權重改變時,會觸發有效權重改變,只有當前權重是動態變化的。

每次請求到來時都從伺服器列表中選擇一個當前權重最高的,之後將選擇出來的伺服器當前權重減去所有伺服器權重的和重新賦值給該伺服器當前權重,這總演算法通過不斷遞減當前權重使得所有伺服器都有機會服務請求,比較平滑,程式碼實現如下

首先定義一個結構體,加權輪詢演算法的核心要素必須有伺服器初始配置權重,當前權重(權重在實際執行時可能發生變化)

type SeverWeight struct {
   //配置的權重
   ConfigWeight int
   //當前權重
   CurrentWeight int
   //有效權重(值等於ConfigWeight,不過該欄位是用一個配置屬性,供前端修改使用)
   EffectiveWeight int
   //伺服器ip
   Ip string
}
//加權輪詢演算法
type WeightedRoundRobin struct {
   //機器ip和對應的權重
   IpAndWeightedConfig map[string]int
   //伺服器和權重資訊
   SwSlice []*SeverWeight
}

根據配置資訊建立負責均衡物件,初始化各個欄位的值

//初始化加權輪詢物件
func NewWeightedRoundRobin(iwc map[string]int) *WeightedRoundRobin {
   if iwc == nil {
      return nil
   }
   SwSlice := make([]*SeverWeight, 0)
   for k, v := range iwc {
      sw := &SeverWeight{ConfigWeight: v, CurrentWeight: 0,
                                 EffectiveWeight: v, Ip: k}
      SwSlice = append(SwSlice, sw)
   }
   return &WeightedRoundRobin{IpAndWeightedConfig: iwc, SwSlice: SwSlice}
}

這個方法是核心,呼叫這個方法來決定選擇哪個伺服器提供服務,方法的核心邏輯是選擇當前權重最大的伺服器提供服務,當前權重不斷在變化,每次當前權重的值都等於當前值加上有效值減去所有伺服器的有效權重和(這個演算法就是不斷遞減當前伺服器的當前權重值,使得按照均勻的變化讓所有伺服器都能提供服務)

func (wrr *WeightedRoundRobin) Select() (sw *SeverWeight) {
   total := 0 //統計所有伺服器權重和
   for _, v := range wrr.SwSlice { //遍歷伺服器
      //當前權重加上有效權重
      v.CurrentWeight += v.EffectiveWeight
      total += v.EffectiveWeight
      //當配置值修改的時候的,有效權重循序漸進的增加
      if v.EffectiveWeight < v.ConfigWeight {
         v.EffectiveWeight++
      }
      //把權重最大的賦值給sw(sw是需要返回的物件)
      if sw == nil || v.CurrentWeight > sw.CurrentWeight {
         sw = v
      }
   }
   //當前返回物件的權重-所有伺服器權重和
   sw.CurrentWeight = sw.CurrentWeight - total
   return sw
}

我們再來看一下執行的測試結果,根據測試結果相信大家就能夠明白了,根據下面結果我們確實能夠看到返回的伺服器IP是均勻的,比較平滑,不會讓權重低的伺服器一直等待。

func TestNewWeightedRoundRobin(t *testing.T) {
   //伺服器ip和權重配置 
   config :=map[string]int{"10.1": 7, "10.2": 2, "10.3": 1}
   wrr := NewWeightedRoundRobin(config)
   //傳送10次請求
   for i := 0; i < 10; i++ {
      sw := wrr.Select()
      t.Log(sw.Ip)//列印每次請求IP
   }
}
//結果:[10.1,10.1,10.2,10.1,10.1,10.3,10.1,10.1,10.2,10.1]

整個程式碼我已提交到github上,大家可以github上下載下來實際執行一下,加深理解,我得github地址如下:

https://github.com/sunpengwei1992/go_common/blob/master/algorithm/load_balance.go

任何一種演算法深入研究後都能引出一堆問題來,都可以單獨寫一篇文章出來,本篇重點是在讓大家知道這些演算法,以至於見到後不會陌生,需要大家在工作中不斷探索,不斷升級自己的認知,提高思維能力