限流


令牌桶算法

是一個存放固定容量令牌的桶,按照固定速率往桶里添加令牌。令牌桶算法的描述如下:

  • 假設限制2r/s,則按照500毫秒的固定速率往桶中添加令牌。
  • 桶中最多存放 b 個令牌,當桶滿時,新添加的令牌被丟棄或拒絕。
  • 當一個 n 個字節大小的數據包到達,將從桶中刪除n 個令牌,接着數據包被發送到網絡上。
  • 如果桶中的令牌不足 n 個,則不會刪除令牌,且該數據包將被限流(要么丟棄,要么緩沖區等待)。

令牌桶速率限制算法: golang.org/x/time/rate

漏桶算法

作為計量工具(The Leaky Bucket Algorithm as a Meter)時,可以用於流量整形(Traffic Shaping)和流量控制(TrafficPolicing),漏桶算法的描述如下:

  • 一個固定容量的漏桶,按照常量固定速率流出水滴。
  • 如果桶是空的,則不需流出水滴。
  • 可以以任意速率流入水滴到漏桶。
  • 如果流入水滴超出了桶的容量,則流入的水滴溢出了(被丟棄),而漏桶容量是不變的。

漏桶率限制算法: go.uber.org/ratelimit

過載保護

令牌桶與漏桶的缺點

漏斗桶/令牌桶確實能夠保護系統不被拖垮, 但不管漏斗桶還是令牌桶, 其防護思路都是設定一個指標, 當超過該指標后就阻止或減少流量的繼續進入,當系統負載降低到某一水平后則恢復流量的進入。但其通常都是被動的,其實際效果取決於限流閾值設置是否合理,但往往設置合理不是一件容易的事情。

  • 集群增加機器或者減少機器限流閾值是否要重新設置?
  • 設置限流閾值的依據是什么?
  • 人力運維成本是否過高?
  • 當調用方反饋429時, 這個時候重新設置限流, 其實流量高峰已經過了重新評估限流是否有意義?

這些其實都是采用漏斗桶/令牌桶的缺點, 總體來說就是太被動, 不能快速適應流量變化。
因此我們需要一種自適應的限流算法,即: 過載保護,根據系統當前的負載自動丟棄流量。

過載保護方法

計算系統臨近過載時的峰值吞吐作為限流的閾值來進行流量控制,達到系統保護。

  • 服務器臨近過載時,主動拋棄一定量的負載,目標是自保。
  • 在系統穩定的前提下,保持系統的吞吐量。

利特爾法則

計算吞吐量:利特爾法則 L = λ * W

利特爾法則由麻省理工大學斯隆商學院(MIT Sloan School of Management)的教授 John Little﹐於 1961 年所提出與證明。它是一個有關提前期與在制品關系的簡單數學公式,這一法則為精益生產的改善方向指明了道路。 —- MBA 智庫百科 (mbalib.com)

如上圖所示,如果我們開一個小店,平均每分鍾進店 2 個客人(λ),每位客人從等待到完成交易需要 4 分鍾(W),那我們店里能承載的客人數量就是 2 * 4 = 8 個人

同理,我們可以將 λ 當做 QPS, W 呢是每個請求需要花費的時間,那我們的系統的吞吐就是 L = λ * W ,所以我們可以使用利特爾法則來計算系統的吞吐量。

什么時候系統的吞吐量就是最大的吞吐量?

首先我們可以通過統計過去一段時間的數據,獲取到平均每秒的請求量,也就是 QPS,以及請求的耗時時間,為了避免出現前面 900ms 一個請求都沒有最后 100ms 請求特別多的情況,我們可以使用滑動窗口算法來進行統計。

最容易想到的就是我們從系統啟動開始,就把這些值給保存下來,然后計算一個吞吐的最大值,用這個來表示我們的最大吞吐量就可以了。但是這樣存在一個問題是,我們很多系統其實都不是獨占一台機器的,一個物理機上面往往有很多服務,並且一般還存在一些超賣,所以可能第一個小時最大處理能力是 100,但是這台節點上其他服務實例同時都在搶占資源的時候,這個處理能力最多就只能到 80 了

所以我們需要一個數據來做啟發閾值,只要這個指標達到了閾值那我們就進入流控當中。常見的選擇一般是 CPU、Memory、System Load,這里我們以 CPU 為例

只要我們的 CPU 負載超過 80% 的時候,獲取過去 5s 的最大吞吐數據,然后再統計當前系統中的請求數量,只要當前系統中的請求數大於最大吞吐那么我們就丟棄這個請求。

如何計算接近峰值時的系統吞吐?

  • CPU: 使用一個獨立的線程采樣,每隔 250ms 觸發一次。在計算均值時,使用了簡單滑動平均去除峰值的影響。
  • Inflight: 當前服務中正在進行的請求的數量。
  • Pass&RT: 最近5s,pass 為每100ms采樣窗口內成功請求的數量,rt 為單個采樣窗口中平均響應時間。

  • 我們使用 CPU 的滑動均值(CPU > 800)作為啟發閾值,一旦觸發進入到過載保護階段,算法為:(pass* rt) < inflight
  • 限流效果生效后,CPU 會在臨界值(800)附近抖動,如果不使用冷卻時間,那么一個短時間的 CPU 下降就可能導致大量請求被放行,嚴重時會打滿 CPU。
  • 在冷卻時間后,重新判斷閾值(CPU > 800 ),是否持續進入過載保護。

什么是限流

限流是指在一段時間內,定義某個客戶或應用可以接收或處理多少個請求的技術。例如,通過限流,你可以過濾掉產生流量峰值的客戶和微服務,或者可以確保你的應用程序在自動擴展(Auto Scaling)失效前都不會出現過載的情況。

  • 令牌桶、漏桶 針對單個節點,無法分布式限流。
  • QPS 限流
    • 不同的請求可能需要數量迥異的資源來處理。
    • 某種靜態 QPS 限流不是特別准。
  • 給每個用戶設置限制
    • 全局過載發生時候,針對某些“異常”進行控制。
    • 一定程度的“超賣”配額。
  • 按照優先級丟棄。
  • 拒絕請求也需要成本。

分布式限流

分布式限流,是為了控制某個應用全局的流量,而非真對單個節點緯度。

  • 單個大流量的接口,使用 redis 容易產生熱點。
  • pre-request 模式對性能有一定影響,高頻的網絡往返。

思考:

  • 從獲取單個 quota 升級成批量 quota。quota: 表示速率,獲取后使用令牌桶算法來限制。

  • 每次心跳后,異步批量獲取 quota,可以大大減少請求 redis 的頻次,獲取完以后本地消費,基於令牌桶攔截。
  • 每次申請的配額需要手動設定靜態值略欠靈活,比如每次要20,還是50。

如何基於單個節點按需申請,並且避免出現不公平的現象?
初次使用默認值,一旦有過去歷史窗口的數據,可以基於歷史窗口數據進行 quota 請求。
思考:

  • 我們經常面臨給一組用戶划分稀有資源的問題,他們都享有等價的權利來獲取資源,但是其中一些用戶實際上只需要比其他用戶少的資源。

那么我們如何來分配資源呢?一種在實際中廣泛使用的分享技術稱作“最大最小公平分享”(Max-Min Fairness)。
直觀上,公平分享分配給每個用戶想要的可以滿足的最小需求,然后將沒有使用的資源均勻的分配給需要‘大資源’的用戶。
最大最小公平分配算法的形式化定義如下:

  • 資源按照需求遞增的順序進行分配。
  • 不存在用戶得到的資源超過自己的需求。
  • 未得到滿足的用戶等價的分享資源。

限流的重要性

每個接口配置閾值,運營工作繁重,最簡單的我們配置服務級別 quota,更細粒度的,我們可以根據不同重要性設定 quota,我們引入了重要性(criticality):

  • 最重要 CRITICAL_PLUS,為最終的要求預留的類型,拒絕這些請求會造成非常嚴重的用戶可見的問題。
  • 重要 CRITICAL,生產任務發出的默認請求類型。拒絕這些請求也會造成用戶可見的問題。但是可能沒那么嚴重。
  • 可丟棄的 SHEDDABLE_PLUS 這些流量可以容忍某種程度的不可用性。這是批量任務發出的請求的默認值。這些請求通常可以過幾分鍾、幾小時后重試。
  • 可丟棄的 SHEDDABLE 這些流量可能會經常遇到部分不可用情況,偶爾會完全不可用。

gRPC 系統之間,需要自動傳遞重要性信息。如果后端接受到請求 A,在處理過程中發出了請求 B 和 C 給其他后端,請求 B 和 C 會使用與 A 相同的重要性屬性。

  • 全局配額不足時,優先拒絕低優先級的。
  • 全局配額,可以按照重要性分別設置。
  • 過載保護時,低優先級的請求先被拒絕。

熔斷

斷路器(Circuit Breakers): 為了限制操作的持續時間,我們可以使用超時,超時可以防止掛起操作並保證系統可以響應。因為我們處於高度動態的環境中,幾乎不可能確定在每種情況下都能正常工作的准確的時間限制。斷路器以現實世界的電子元件命名,因為它們的行為是都是相同的。斷路器在分布式系統中非常有用,因為重復的故障可能會導致雪球效應,並使整個系統崩潰。

  • 服務依賴的資源出現大量錯誤。
  • 某個用戶超過資源配額時,后端任務會快速拒絕請求,返回“配額不足”的錯誤,但是拒絕回復仍然會消耗一定資源。有可能后端忙着不停發送拒絕請求,導致過載。

如上圖所示,熔斷器存在三個狀態:

  1. 關閉(closed): 關閉狀態下沒有觸發斷路保護,所有的請求都正常通行
  2. 打開(open): 當錯誤閾值觸發之后,就進入開啟狀態,這個時候所有的流量都會被節流,不運行通行
  3. 半打開(half-open): 處於打開狀態一段時間之后,會嘗試嘗試放行一個流量來探測當前 server 端是否可以接收新流量,如果這個沒有問題就會進入關閉狀態,如果有問題又會回到打開狀態

Google SRE 過載保護算法

max(0, (requests - K*accepts) / (requests + 1))

算法如上所示,這個公式計算的是請求被丟棄的概率[3]

  • requests: 一段時間的請求數量
  • accepts: 成功的請求數量
  • K: 倍率,K 越小表示越激進,越小表示越容易被丟棄請求

這個算法的好處是不會直接一刀切的丟棄所有請求,而是計算出一個概率來進行判斷,當成功的請求數量越少,K越小的時候 requests−K∗accepts 的值就越大,計算出的概率也就越大,表示這個請求被丟棄的概率越大

Gutter

基於熔斷的 gutter kafka ,用於接管自動修復系統運行過程中的負載,這樣只需要付出10%的資源就能解決部分系統可用性問題。
我們經常使用 failover 的思路,但是完整的 failover 需要翻倍的機器資源,平常不接受流量時,資源浪費。高負載情況下接管流量又不一定完整能接住。所以這里核心利用熔斷的思路,是把拋棄的流量轉移到 gutter 集群,如果 gutter 也接受不住的流量,重新回拋到主集群,最大力度來接受。

客戶端流控

positive feedback: 用戶總是積極重試,訪問一個不可達的服務。

  • 客戶端需要限制請求頻次,retry backoff 做一定的請求退讓。
  • 可以通過接口級別的error_details,掛載到每個 API 返回的響應里。

參考文章


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM