1. 程式人生 > >阿里巴巴王發康:阿里七層流量入口負載均衡演算法演變之路

阿里巴巴王發康:阿里七層流量入口負載均衡演算法演變之路

2019 年 10 月 27 日,又拍雲聯合 Apache APISIX 社群舉辦 API 閘道器與高效能服務最佳實踐丨Open Talk 杭州站活動,來自阿里巴巴的技術專家王發康做了題為《阿里七層流量入口負載均衡演算法演變之路》的分享。本次活動,邀請了來自阿里巴巴、螞蟻金服、Apache APISIX、PolarisTech、又拍雲等企業的技術專家,分享閘道器和高效能服務的實戰經驗。

王發康,阿里巴巴 Tengine 開源專案 maintainer,負責阿里集團 WEB 統一接入層的開發及維護。

以下是分享全文:

前言

大家下午好,我叫王發康,來自阿里巴巴 Tengine 團隊,目前主要負責阿里七層流量入口的開發與維護。今天演講的主題是《阿里七層流量入口負載均衡演算法演變之路》,主要從四個方面介紹:

  • 統一接入架構介紹

  • 負載均衡改造背景

  • VNSWRR 演進過程

  • 效果及總結

從 2011 年至今,Tengine 在開源的道路上已走過第八個年頭,感謝社群貢獻者及廣大使用者的支援。下面先介紹下 Tengine 與 Nginx 的區別:

大家都知道 Nginx 的效能非常高,C1000K 都不成問題;同時,Nginx 的生態也比較豐富,不僅可以作為 HTTP 伺服器,也可以作為 TCP 和 UDP,功能強大;它還可以和 K8s、Mesh、Serverless等其他生態打通,也包括 Lua、Js 語言支援;Nginx 模組化做的很好,支援動態載入,可以很方便的將自己寫模組擴充套件進去。

Tengine 是 100% 基於 Nginx 開發的,也就是說 Nginx 有的,Tengine 都有,Nginx 沒有的,Tengine 也可以有。相容幷包是 Tengine 研發的重要思路, 除了 100% 繼承 Nginx,也結合阿里大規模場景應用開發了眾多高階特性:比如秉承軟硬體結合的優化思想,通過 QAT 硬體解除安裝 HTTPS、Gzip;使用 AliUstack 使用者態協議棧繞過核心、避免軟中斷、減少資料拷貝等操作來提高效能,另外包括一些動態服務發現 、後端的 RPC 通訊如 Dubbo 協議的實現。同時,我們還有大規模的流量驗證場景,即每年雙十一的洪峰流量,使得其穩定性等方面得到充分驗證。

根據 W3C 資料顯示,目前 Tengine 和 Nginx 在 Top 1000 到 4000大型網站中使用佔比已接近 50%,這得益於其在高效能、穩定、易用性、功能強大等方面都做的比較極致。

統一接入層架構介紹

統一接入層
Tengine 作為阿里集團七層流量入口核心系統,支撐著阿里巴巴歷年雙 11 等大促活動平穩度過。基於 Tengine,阿里研發了新的產品——統一接入層。

統一接入層,指的是設定專屬一層,統一接入所有流量,包括 PC 流量、無線流量、IoT 流量。從入口進來,經過四層的 SLB,直接到達七層的 Tengine 組成一個叢集,通過它進行 HTTPS 的解除安裝、鏈路追蹤、單元化、灰度分流,以及一些安全清洗等。

如果沒有統一接入層,之前的業務方,例如購物車、商品等都要自己維護一個閘道器,這就涉及到維護成本和機器成本,例如解除安裝 HTTPS,如果所有業務方都要申請證書,那造成的應用成本是非常高的。可如果將所有功能全放在這一層進行,好處非常明顯:一方面是機器集中管理節省成本;另外一方面,如果遇到新的瓶頸可以在統一接入層集中優化,如請求響應 Body 統一在這一層進行壓縮減少頻寬消耗,壓縮會消耗 CPU,可以在這一層通過硬體加速的方式集中優化等。

Nginx SWRR 演算法

前面提到,請求流量進來後需要通過負載均衡的演算法進行排程,均勻地分配到後端。負載均衡演算法非常多,例如一致性雜湊,IP 雜湊、Session、Cookie 等各種演算法,今天主要講的是 WRR 演算法,即加強輪詢演算法。

WRR 演算法是 Nginx 官方的,我們日常用的 Upstream 中,如果不配置演算法,那麼預設的就是 SWRR 演算法。最早的 Nginx 官方其實是 WRR 演算法,後來經過改造演變成 SWRR 演算法,“S” 即 Smooth,是平滑的意思。

如上圖,我們簡單看下 Nginx SWRR 演算法的請求處理過程。假設有三臺機器 a、b、c,權重分別是 5、1、1。請求編號指的是第一個請求、第二個請求、第三個請求。第一個請求 Nginx SWRR 演算法會選擇權重最大的 a 機器。選中 a 機器後,就會對其進行降權處理,這樣可以降低下一次被選中的概率。而 b、c 兩臺機器本次沒被選中,下一次就要提高它的權重。 降權過程為:a 機器被選中後,當前的權重減去總權重,即 5 減 7 得負 2;下一輪選擇開始時,還需要加上上一輪機器本身的權重 5,負 2 加 5 就是 3,1 加 1 變成 2,由此原本權重 5、1、1 的 a、b、c 三臺機器本輪權重分別是 3、2、2。

第二個請求進來時,依然選擇權重最大的機器,還是 a。接下去的流程不必細說,演算法流程是一樣的,整體過程均按照被選中機器需要減去總權重的規則,為了降低其下次被選擇的概率。全部請求走完,被選中的機器順序就是 a、a、b、c、b、a、c、a、a。

WRR 演算法實現有非常多種,最常見的就是隨機數演算法。普通 WRR 選擇的順序就是 c、b、a、a、a、a、a。這就產生了一個問題:當請求流量進來時會一直選擇權重高的機器,可能導致流量不均衡,流量大部分分散在權重比較高的機器。而 Nginx SWRR 的演算法特點就是平滑、分散,相當於把 a 間隔打散在列表中。

流量排程

壓測平臺

請求流量進來後,在接入層按照權重做負載均衡演算法,如此一來在接入層又孵化了一個新產品——流量排程,主要的應用場景是壓測平臺。

一般新版本釋出後都需要用線上真實流量進行壓測,通過壓測平臺調整後端某幾臺機器的權重,把需要進行壓測的機器權重調高,接入層可以動態感知到機器權重被調高,使得更多的線上流量被引向那些機器,以此達到線上壓測的效果。

異常檢測平臺

我們還有個異常檢測平臺,通過實時監測各應用機器以及服務狀態,按照一定的演算法降低異常機器的權重,從而規避一些異常問題。我們會檢測後端的每臺機器,主要考量三個層面:

  • 硬體層面;

  • 系統資源狀態;

  • 應用層面上的 RT、狀態碼、執行緒數各種狀態。

如果某一臺機器負載比較高,檢測平臺通過服務發現後會降低權重,接入層動態感知到後能降低該臺機器的流量。這種做法的好處在於如果線上有異常機器,無需人工介入,可以直接智慧化感知並自動摘除。

需要注意的是在一些特殊的場景裡,異常檢測平臺也會出現問題,例如很多臺機器都出現了故障,系統把這些機器權重全部降低,假設 100 臺機器降低了 50 臺,可能會引起系統雪崩。因此這部分也需要把控,設定在一定的範圍內允許調整機器權重的數量。

接入層 VNSWRR 演算法改造背景及演進

接入層 VNSWRR 演算法改造背景

調低權重,機器的流量會減少,但如果調高權重呢?大家都知道 Nginx 是多程序、單執行緒模式,例如 CPU 是 32 核,它就會起 32 個worker,每一個 worker 都是獨立的 SWRR 演算法。

如上圖所示,是採用原生的 Nginx 官方演算法進行線上壓測的真實案例,壓測平臺把某臺機器的權重從 1 調整為 2,機器流量瞬間衝高 50 倍,基本上單個叢集的流量全部引向該臺機器了,這是原生的 Nginx 官方演算法。而在接入層中,後端機器權重是動態感知的,實時性非常高,所以會出現上圖中的問題。

這裡有一點值得注意:權重從 1 調到 2,為什麼衝高的流量不是預期的 2 倍,而是衝高了 50 倍?如果想解決這個問題,我們一般會從運維的角度或者開發的角度出發,不過既然已經有監控資料,就先不看程式碼,直接從運維的角度分析資料監控圖,以下是調高權重後對應機器的 QPS 變化特徵:

  • QPS 持續上漲峰值是 1300 左右;

  • QPS 持續上升 7s 左右後開始下降,隨後穩定在平均值的 2 倍左右;

  • 權重被調高機器在 A 機房,接入層 Tengine 是優先同機房轉發、其 CPU 是 32 核;

  • 當時該應用 A 機房的 QPS 是 K 左右,接入層 A 機房 N 臺;可以算出(N * 32 / K) * 1 / 2 約等於 7.68s

第 4 點中的 K 是應用的 QPS,可以看成速度,即每秒能進來多少量,而用接入層的總 worker 數(Nginx 是多程序,單執行緒模式,32 核的 CPU 就是 32 個 Worker)除以速度(K),取一箇中間時間,算出來大概是 7.68 秒左右,和 7 秒基本上是接近的。

接入層 VNSWRR 演算法演進歷程

接入層 VNSWRR 演算法(V1)

Nginx SWRR 演算法有一個缺陷:第一個請求進來時,必然選擇權重高的機器。這是因為接入層是無狀態的,每臺機器 worker 的演算法都是獨立的,請求分到任意一臺機器的 worker 上,初始狀態都是選擇權重最高的。找到原因後就需要解決這個問題,那有什麼辦法可以不讓它選擇權重最高的呢?方法很簡單,我們當初只用四五行程式碼就解決了這個問題。

我們抽象一個演算法模型,讓它預調整機器權重,調整的範圍是多少?如左閉右開一個區間 [0,min(N,16)),N 代表應用的機器數,從 1 到 16 去取一個最小值。這意味著最多可以預丟 16 次,最少可以不丟。“不丟”指的是請求來了直接送到後端權重被調高的機器。改造完後,看一下效果如何。

同樣的場景,我們把機器權重從 1 調整為 2,流量瞬間衝高了 3 倍。這裡又出現了一樣的問題,在改造前,流量是衝高了 50 倍,現在只衝高了 3 倍,所以問題還是沒有解決,只是緩和了。同時出現了一個新的問題,機器權重調到 2後,按照最原始的演算法,其流量基本上 7 秒過後就能恢復到預期的 2 倍,但是現在需要 15 分鐘才能恢復到預期的 2 倍,這肯定是不能容忍的。

任何一個問題背後都是因為程式碼的改動或者邏輯設計的不合理導致的。先思考流量瞬間衝高 3 倍是怎麼來的,這和前文提到的“丟”有關。“丟”指的是第一個請求進來時,並不直接把它送到後端,而是先偽造一次,動態調整權重,調整過後在把請求轉發到後端。這個請求就相當於後面會重新觸發 SWRR 演算法,如此一來可以避免第一個請求都被選中到權重調高的機器上。

再看設定的區間 [0,min(N,16)),線上應用機器肯定超過 16 臺,而區間範圍是 0 到 16。0 代表請求來了直接送到後端,這就有 1/16 的概率是不丟的,意味著有 1/16 的概率會遇到 Nginx SWRR 演算法本身缺陷造成的的問題。開始衝高 50 倍,1/16 基本上就是 3 倍,這是概率問題。

那為什麼持續了 15 分鐘才達到預期的 2 倍呢?這是因為 1/16 的概率不丟,還有其它的可能丟 1 次,丟 2 次,丟 3 次……丟 15 次。但一個機器被選中後,它的權重會減去當前所有機器的權重總和,這樣做的目標是為了降低下次被選中的概率。所以當請求再次進來時選擇權重被調高的那臺機器概率也很低。根據此前的演算法可以算出需要持續 15 分鐘才能恢復。

接入層 VNSWRR 演算法(V2)

基於前文流量衝高 3 倍及持續時間較長的問題,我們又進一步演進演算法,通過引入虛擬節點的新思路解決問題。

假設有三臺機器 A、B、C,權重分別是 1、2、3。需要考慮兩個問題:

  • 虛擬節點的機器怎麼填充?是填 CBA、ABC 還是 CBC 呢?

  • 虛擬節點如何初始化。例如有一個節點機器權重是 10000,其它機器節點權重都是 1 。三臺機器權重分配是 1、1、10000,相當於引出 10002 個虛擬節點。如果一次性初始化,等同於做密集型計算,如果在初始化列表做 for 迴圈 10000 多次或者幾萬次的話,那在雙十一零點高峰時肯定就直接掛了。

解決上述兩個問題有兩個關鍵點:第一個關鍵點是機器列表如何初始化,如何打散機器,不讓權重高的機器集中在一起。另一個關鍵點是如何初始化虛擬列表,一次性初始化會發生 CPU 做密集型計算的問題。基於這些,我們引入一種新思路——初始化虛擬節點列表的順序完全和 SWRR 的選取一致,嚴格按照數學模型的演算法初始化。另外,龐大的虛擬節點列表如果按照 Nginx 官網的權重初始化演算法,是非常消耗 CPU 的,所以我們決定在執行時分批初始化。

舉個例子,一個應用有 3 臺機器,權重分配是 1、2、3,那麼它共有 6 個的虛擬節點,而真實節點只有 3 個。則第一批先初始化3個虛擬節點(即真實機器數),當第一批虛擬節點輪訓使用完後則進行初始化下一批虛擬節點,同時虛擬列表中機器節點的順序嚴格按照 SWRR 演算法順序填充進去。

如上圖,當第一個請求進來時,就從虛擬機器器列表中的一個隨機位置開始輪詢。如 Step 1,抽一個隨機數,有可能是從 C 開始去輪詢,也有可能從 B 開始輪詢。通過這種方式使得 Tengine 的每個 worker 以及每臺機器的 Tengine 都可以被打散,有的機器從 C 開始,有的機器從 B 開始。這樣就可以避免所有流量都選擇權重最大的那臺機器,通過隨機數打散流量被分配到各臺機器。而當執行時輪詢到 A 機器後,則需初始化第二批虛擬節點列表(如 Step2中橙色部分),當虛擬節點全部填充好後(如 Step3 中狀態),後續不在做初始化,直接輪訓列表就好。

演進後的接入層 VNSWRR 演算法上線後效果非常明顯,如上圖所示,機器權重從 1 調整為 2 時,流量基本上是平穩地上升到 2 倍左右。

接入層 VNSWRR 演算法演進效果對比

圖1 是 Nginx 原生的 SWRR 演算法,圖 2 是改造 V1 版本的效果。圖 3 是終版改造的演算法,從這個版本開始,我們引入虛擬節點,使得流量在分鐘級別內平穩的達到 2 倍。這不僅解決了 Nginx 加權輪詢演算法在權重調高時流量全部集中在一臺機器上的問題,並且在引用虛擬節點過後,我們的演算法時間複雜度基本上變成 O(1),而 Nginx 官方的演算法現在目前還是 O(n)。

之前我們有發過一篇文章,在純壓測負載均衡演算法的場景下,改造過後的終版演算法相較於SWRR 演算法有 60% 的效能提升。

總結與思考

  • 解決問題優先於效能優化

剛開始遇到問題時,既想把問題解決掉,又想把效能優化到極致。但事實上還是在解決問題的過程中逐步演進到最優狀態。

  • 任何一個特殊現象,背後必有原因

譬如看到權重調高後機器的 QPS 變化趨勢圖,每一個特殊峰值點以及變化趨勢會給挖掘問題的本質帶來很大的幫助。

  • 簡單、高效、可靠

簡單並不代表 low,程式碼入侵越小,出問題就越少,同時也容易發現。

  • 小程式大流量場景,相容幷包

我們做任何一個方案,大到一個系統的設計,小到每一行程式碼,都需要考慮到小程式、大流量場景。如果程式設計師能做到這一步,相信一定會有很大的成長。

推薦閱讀

Apache APISIX 微服務閘道器極致效能架構解析

全球 43 億 IPv4 地址已耗盡!IPv6,刻不容緩