2019 年 10 月 27 日,又拍雲聯合 Apache APISIX 社區舉辦 API 網關與高性能服務最佳實踐丨Open Talk 杭州站活動,來自阿里巴巴的技術專家王發康作了題爲《阿里七層流量入口負載均衡算法演變之路》的分享。本次活動,邀請了來自阿里巴巴、螞蟻金服、Apache APISIX、PolarisTech、又拍雲等企業的技術專家,分享網關和高性能服務的實戰經驗。html
王發康,阿里巴巴 Tengine 開源項目 maintainer,負責阿里集團 WEB 統一接入層的開發及維護。程序員
如下是分享全文:算法
你們下午好,我叫王發康,來自阿里巴巴 Tengine 團隊,目前主要負責阿里七層流量入口的開發與維護。今天演講的主題是《阿里七層流量入口負載均衡算法演變之路》,主要從四個方面介紹:小程序
從 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,權重分別是 五、一、1。請求編號指的是第一個請求、第二個請求、第三個請求。第一個請求 Nginx SWRR 算法會選擇權重最大的 a 機器。選中 a 機器後,就會對其進行降權處理,這樣能夠下降下一次被選中的機率。而 b、c 兩臺機器本次沒被選中,下一次就要提升它的權重。降權過程爲:a 機器被選中後,當前的權重減去總權重,即 5 減 7 得負 2;下一輪選擇開始時,還須要加上上一輪機器自己的權重 5,負 2 加 5 就是 3,1 加 1 變成 2,由此本來權重 五、一、1 的 a、b、c 三臺機器本輪權重分別是 三、二、2。
第二個請求進來時,依然選擇權重最大的機器,仍是 a。接下去的流程沒必要細說,算法流程是同樣的,總體過程均按照被選中機器須要減去總權重的規則,爲了下降其下次被選擇的機率。所有請求走完,被選中的機器順序就是 a、a、b、c、b、a、c、a、a。
WRR 算法實現有很是多種,最多見的就是隨機數算法。普通 WRR 選擇的順序就是 c、b、a、a、a、a、a。這就產生了一個問題:當請求流量進來時會一直選擇權重高的機器,可能致使流量不均衡,流量大部分分散在權重比較高的機器。而 Nginx SWRR 的算法特色就是平滑、分散,至關於把 a 間隔打散在列表中。
流量調度
壓測平臺
請求流量進來後,在接入層按照權重作負載均衡算法,如此一來在接入層又孵化了一個新產品——流量調度,主要的應用場景是壓測平臺。
通常新版本發佈後都須要用線上真實流量進行壓測,經過壓測平臺調整後端某幾臺機器的權重,把須要進行壓測的機器權重調高,接入層能夠動態感知到機器權重被調高,使得更多的線上流量被引向那些機器,以此達到線上壓測的效果。
異常檢測平臺
咱們還有個異常檢測平臺,經過實時監測各應用機器以及服務狀態,按照必定的算法下降異常機器的權重,從而規避一些異常問題。咱們會檢測後端的每臺機器,主要考量三個層面:
若是某一臺機器負載比較高,檢測平臺經過服務發現後會下降權重,接入層動態感知到後能下降該臺機器的流量。這種作法的好處在於若是線上有異常機器,無需人工介入,能夠直接智能化感知並自動摘除。
須要注意的是在一些特殊的場景裏,異常檢測平臺也會出現問題,例如不少臺機器都出現了故障,系統把這些機器權重所有下降,假設 100 臺機器下降了 50 臺,可能會引發系統雪崩。所以這部分也須要把控,設置在必定的範圍內容許調整機器權重的數量。
接入層 VNSWRR 算法改造背景
調低權重,機器的流量會減小,但若是調高權重呢?你們都知道 Nginx 是多進程、單線程模式,例如 CPU 是 32 核,它就會起 32 個worker,每個 worker 都是獨立的 SWRR 算法。
如上圖所示,是採用原生的 Nginx 官方算法進行線上壓測的真實案例,壓測平臺把某臺機器的權重從 1 調整爲 2,機器流量瞬間衝高 50 倍,基本上單個集羣的流量所有引向該臺機器了,這是原生的 Nginx 官方算法。而在接入層中,後端機器權重是動態感知的,實時性很是高,因此會出現上圖中的問題。
這裏有一點值得注意:權重從 1 調到 2,爲何衝高的流量不是預期的 2 倍,而是衝高了 50 倍?若是想解決這個問題,咱們通常會從運維的角度或者開發的角度出發,不過既然已經有監控數據,就先不看代碼,直接從運維的角度分析數據監控圖,如下是調高權重後對應機器的 QPS 變化特徵:
第 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,權重分別是 一、二、3。須要考慮兩個問題:
解決上述兩個問題有兩個關鍵點:第一個關鍵點是機器列表如何初始化,如何打散機器,不讓權重高的機器集中在一塊兒。另外一個關鍵點是如何初始化虛擬列表,一次性初始化會發生 CPU 作密集型計算的問題。基於這些,咱們引入一種新思路——初始化虛擬節點列表的順序徹底和 SWRR 的選取一致,嚴格按照數學模型的算法初始化。另外,龐大的虛擬節點列表若是按照 Nginx 官網的權重初始化算法,是很是消耗 CPU 的,因此咱們決定在運行時分批初始化。
舉個例子,一個應用有 3 臺機器,權重分配是 一、二、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,代碼入侵越小,出問題就越少,同時也容易發現。
咱們作任何一個方案,大到一個系統的設計,小到每一行代碼,都須要考慮到小程序、大流量場景。若是程序員能作到這一步,相信必定會有很大的成長。
以上就是王發康老師在 Open Talk 杭州站現場分享整理,舒適提示 12 月 14 日 API 網關與高性能服務最佳實踐·廣州站活動正在報名中 http://hdxu.cn/jB9KO
演講視頻觀看及演講 PPT 下載: