做數學:使用編排器擴展微服務應用程序
已發表: 2022-03-11微服務應用程序架構繼續侵入軟件設計並不奇怪。 分配負載、創建高可用性部署和管理升級更加方便,同時簡化了開發和團隊管理。
但如果沒有容器編排器,情況肯定就不一樣了。
想要使用它們的所有關鍵功能很容易,尤其是自動縮放。 多麼幸運,看著容器部署一整天都在波動,大小適中以處理當前負載,從而騰出我們的時間來完成其他任務。 我們對我們的容器監控工具所顯示的內容感到自豪; 與此同時,我們剛剛配置了一些設置——是的,這就是(幾乎)創造魔法所需要的全部!
這並不是說沒有理由為此感到自豪:我們確信我們的用戶擁有良好的體驗,並且我們不會在超大的基礎設施上浪費任何資金。 這已經相當可觀了!
當然,到達那裡是多麼美妙的旅程啊! 因為即使最後需要配置的設置不多,但在開始之前,它比我們通常想像的要復雜得多。 最小/最大副本數、放大/縮小閾值、同步週期、冷卻延遲——所有這些設置都緊密相連。 修改一個很可能會影響另一個,但您仍然必須安排一個適合您的應用程序/部署和您的基礎架構的平衡組合。 然而,你不會在互聯網上找到任何食譜或任何神奇的公式,因為它在很大程度上取決於你的需求。
我們大多數人首先將它們設置為“隨機”或默認值,然後根據我們在監控時發現的內容進行調整。 這讓我想到:如果我們能夠建立一個更“數學”的程序來幫助我們找到獲勝的組合呢?
計算容器編排參數
當我們考慮為應用程序自動擴展微服務時,我們實際上是在關注兩個主要方面的改進:
- 確保在負載快速增加的情況下部署可以快速擴展(因此用戶不會面臨超時或 HTTP 500)
- 降低基礎架構的成本(即防止實例負載不足)
這基本上意味著優化容器軟件閾值以擴大和縮小規模。 (Kubernetes 的算法對這兩者只有一個參數)。
稍後我將展示所有與實例相關的參數都與 upscale-threshold 相關聯。 這是最難計算的——因此有這篇文章。
注意:關於在集群範圍內設置的參數,我沒有什麼好的程序,但是在本文的最後,我會介紹一個軟件(靜態網頁),在計算時會考慮到它們實例的自動縮放參數。 這樣,您將能夠改變它們的值以考慮它們的影響。
計算放大閾值
要使此方法起作用,您必須確保您的應用程序滿足以下要求:
- 負載必須均勻分佈在應用程序的每個實例中(以循環方式)
- 請求時間必須短於容器集群的負載檢查間隔。
- 您必須考慮在大量用戶(稍後定義)上運行該過程。
出現這些情況的主要原因是該算法不會將負載計算為每個用戶,而是作為分佈(稍後解釋)。
得到所有高斯
首先,我們必須為負載快速增加或最壞情況下的定義制定一個定義。 對我來說,一個好的翻譯方法是:讓大量用戶在短時間內執行消耗資源的操作——而且總是有可能在另一組用戶或服務正在執行其他任務時發生這種情況。 所以讓我們從這個定義開始,試著提取一些數學。 (準備好你的阿司匹林。)
引入一些變量:
- $N_{u}$,“大量用戶”
- $L_{u}(t)$,單個用戶執行“資源消耗操作”產生的負載($t=0$指向用戶開始操作的時刻)
- $L_{tot}(t)$,總負載(由所有用戶產生)
- $T_{tot}$,“短時間”
在數學世界中,談到大量用戶同時執行相同的事情,用戶隨時間的分佈遵循高斯(或正態)分佈,其公式為:
\[G(t) = \frac{1}{\sigma \sqrt{2 \pi}} e^{\frac{-(t-\mu)^2}{2 \sigma^2}}\]這裡:
- µ是期望值
- σ是標準差
它的圖形如下($µ=0$):
可能會讓人想起你上過的一些課——沒什麼新鮮的。 然而,我們在這裡確實面臨第一個問題:為了在數學上準確,我們必須考慮從 $-\infty$ 到 $+\infty$ 的時間範圍,這顯然無法計算。
但是查看圖表,我們注意到區間 $[-3σ, 3σ]$ 之外的值非常接近於零並且變化不大,這意味著它們的影響可以忽略不計,可以放在一邊。 更是如此,因為我們的目標是測試擴展我們的應用程序,所以我們正在尋找大量用戶的變化。
另外,由於區間 $[-3σ, 3σ]$ 包含了我們 99.7% 的用戶,它足夠接近總數來處理它,我們只需將 $N_{u}$ 乘以 1.003 即可彌補區別。 選擇這個區間給我們 $µ=3σ$(因為我們要從 $t=0$ 開始工作)。
關於與 $T_{tot}$ 的對應關係,選擇它等於 $6σ$ ($[-3σ, 3σ]$) 將不是一個好的近似值,因為 95.4% 的用戶都在區間 $[- 2σ, 2σ]$,持續 $4σ$。 所以選擇 $T_{tot}$ 等於 $6σ$ 只會為 4.3% 的用戶增加一半的時間,這並不真正具有代表性。 因此我們選擇$T_{tot}=4σ$,我們可以推導出:
\(σ=\frac{T_{tot}}{4}\) 和 \(µ=\frac{3}{4} * T_{tot}\)
這些價值觀只是從帽子裡拿出來的嗎? 是的。 但這就是他們的目的,這不會影響數學過程。 這些常數是為我們準備的,並定義了與我們的假設相關的概念。 這僅意味著現在我們已經設置了它們,我們最壞的情況可以翻譯為:
99.7% 的 $N{u}$ 產生的負載,執行消費操作 $L{u}(t)$,其中 95.4% 的人在 $T{tot}$ 期間執行此操作。
(在使用網絡應用程序時,這是值得記住的。)
將之前的結果注入到用戶分佈函數(高斯)中,我們可以將方程簡化如下:
\[G(t) = \frac{4 N_{u}}{T_{tot} \sqrt{2 \pi}} e^\frac{-(4t-3T_{tot})^2}{T_{tot }^2}\]從現在開始,定義了 $σ$ 和 $µ$,我們將處理區間 $t \in [0, \frac{3}{2}T_{tot}]$(持續 $6σ$)。
總用戶負載是多少?
自動擴展微服務的第二步是計算 $L_{tot}(t)$。
由於$G(t)$是一個分佈,要檢索某個時間點的用戶數,我們必須計算它的積分(或者使用它的累積分佈函數)。 但由於並非所有用戶都同時開始操作,因此試圖引入 $L_{u}(t)$ 並將方程簡化為可用公式將是一團糟。

因此,為了使這更容易,我們將使用黎曼和,這是一種使用小形狀的有限和來近似積分的數學方法(我們將在這裡使用矩形)。 形狀(細分)越多,結果就越準確。 使用細分的另一個好處來自於我們可以認為一個細分中的所有用戶同時開始他們的操作。
回到黎曼和,它具有以下與積分相關的性質:
\[\int_{a}^{b} f( x )dx = \lim_{n \rightarrow \infty } \sum_{k=1}^{n} ( x_{k} - x_{k-1} ) f( x_{k} )\]$x_k$ 定義如下:
\[x_{ k } = a + k\frac{ b - a }{ n }, 0 \leq k \leq n\]這是真的:
- $n$ 是細分的數量。
- $a$ 是下限,這裡是 0。
- $b$ 是上限,這裡是 $\frac{3}{2}*T_{tot}$。
- $f$ 是近似其面積的函數(此處為 $G$)。
注意:細分中存在的用戶數不是整數。 這是兩個先決條件的原因:擁有大量用戶(因此小數部分不會造成太大影響),以及需要將負載均勻分佈在每個實例上。
另請注意,我們可以在黎曼和定義的右側看到細分的矩形形狀。
現在我們有了黎曼和公式,我們可以說時間 $t$ 的負載值是每個細分的用戶數乘以相應時間的用戶負載函數的總和。 這可以寫成:
\[L_{ tot }( t ) = \lim_{n \rightarrow \infty} \sum_{ k=1 }^{ n } ( x_{k} - x_{k-1} )G( x_{k} ) L_{ u }( t - x_{k} )\]替換變量並簡化公式後,變為:
\[L_{ tot }( t ) = \frac{6 N_{u}}{\sqrt{2 \pi}} \lim_{n \rightarrow \infty} \sum_{ k=1 }^{ n } (\ frac{1}{n}) e^{-{(\frac{6k}{n} - 3)^{2}}} L_{ u }( t - k \frac{3 T_{tot}}{2n })\]瞧! 我們創建了加載函數!
尋找放大閾值
最後,我們只需要運行一個二分法算法,該算法會改變閾值,以找到每個實例的負載在整個負載函數中永遠不會超過其最大限制的最大值。 (這是應用程序所做的。)
推導其他編排參數
一旦找到放大閾值 ($S_{up}$),其他參數就很容易計算。
從 $S_{up}$ 您將知道您的最大實例數。 (您還可以在負載函數上查找最大負載,然後除以每個實例的最大負載,四捨五入。)
必鬚根據您的基礎架構定義實例的最小數量 ($N_{min}$)。 (我建議每個 AZ 至少有一個副本。)但它還需要考慮負載函數:由於高斯函數增加得非常快,負載分佈在開始時(每個副本)更加強烈,所以你可能需要增加最小副本數來緩衝這種影響。 (這很可能會增加您的 $S_{up}$。)
最後,一旦定義了最小副本數,就可以計算縮減閾值 ($S_{down}$),考慮以下因素: 因為縮減單個副本對其他實例的影響比縮減$N_{min}+1$ 到 $N_{min}$,我們必須確保在縮小後不會立即觸發放大閾值。 如果允許,這將產生溜溜球效果。 換一種說法:
\[( N_{ min } + 1) S_{ down } < N_{ min }S_{ up }\]要么:
\[S_{ 下 } < \frac{N_{ 分鐘 }}{N_{min}+1}S_{ 上 }\]此外,我們可以承認,您的集群配置為在縮減之前等待的時間越長,將 $S_{down}$ 設置為更接近上限就越安全。 再一次,你必須找到一個適合你的平衡點。
請注意,當使用帶有自動縮放器的 Mesosphere Marathon 編排系統時,可以從縮減中一次刪除的最大實例數與AS_AUTOSCALE_MULTIPLIER
($A_{mult}$) 相關聯,這意味著:
用戶加載功能呢?
是的,這有點問題,而且不是最容易用數學解決的問題——如果它甚至可能的話。
要解決此問題,想法是運行應用程序的單個實例,並增加重複執行相同任務的用戶數量,直到服務器負載達到分配的最大值(但不超過)。 然後除以用戶數,計算請求的平均時間。 對要集成到用戶加載功能中的每個操作重複此過程,添加一些時間,然後就可以了。
我知道這個過程意味著考慮到每個用戶請求在其處理過程中都有一個恆定的負載(這顯然是不正確的),但是大量用戶會產生這種效果,因為他們每個人都不是同時在同一個處理步驟. 所以我想這是一個可以接受的近似值,但它再次暗示你正在與大量用戶打交道。
您也可以嘗試使用其他方法,例如 CPU 火焰圖。 但我認為創建一個將用戶操作與資源消耗聯繫起來的準確公式將非常困難。
介紹app-autoscaling-calculator
現在,對於貫穿始終提到的小型 Web 應用程序:它將您的加載函數、容器編排器配置和其他一些通用參數作為輸入,並返回擴展閾值和其他與實例相關的數據。
該項目託管在 GitHub 上,但它也有一個可用的實時版本。
這是 Web 應用程序給出的結果,針對測試數據運行(在 Kubernetes 上):
擴展微服務:不再在黑暗中摸索
當涉及到微服務應用架構時,容器部署成為整個基礎設施的中心點。 並且編排器和容器配置得越好,運行時就會越流暢。
我們這些 DevOps 服務領域的人一直在尋找更好的方法來為我們的應用程序調整編排參數。 讓我們採用更數學的方法來自動擴展微服務!