微服務-如何作好集羣中服務器的負載均衡

那些負載均衡的面試題

簡單說一下什麼是負載均衡?不少人最怕這種概念性問題前端

大家公司負載均衡用的什麼?git

爲何用這種?github

它的優缺點面試

有更好的選擇嗎?算法

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

什麼是負載均衡?

俗話解釋一下負載均衡:你要在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機器,那麼當後端集羣新增或者減小一臺機器時,客戶端散列後對服務器總數取餘後就再也不是原來的那臺機器了,這樣原來全部的請求散列後對應的後臺機器都發生了變化,一致性散列就是解決這種問題的.

實現一個負載均衡算法

咱們挑選上面一種策略用代碼來實現一下,以便讓你們更深刻的理解,選擇一個面試常問的策略,一、加權輪詢算法,這個也比較多,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

任何一種算法深刻研究後都能引出一堆問題來,均可以單獨寫一篇文章出來,本篇重點是在讓你們知道這些算法,以致於見到後不會陌生,須要你們在工做中不斷探索,不斷升級本身的認知,提升思惟能力。

相關文章
相關標籤/搜索