作者:來自 vivo 網際網路伺服器團隊- Gao Meng
本文介紹了一種基於 sentinel 進行二次開發的動態限流解決方案,包括什麼是動態限流、為什麼需要引入動態限流、以及動態限流的實現原理。
一、背景
1.1 當前的限流方案
隨著網際網路的發展及業務的增長,系統的流量和請求量越來越大,針對高併發系統,如果不對請求量進行限制,在流量突增時可能會導致系統崩潰或者服務不可用,影響使用者體驗。因此,系統需要引入限流來控制請求的流量,保證系統的可用性和穩定性。當前推薦業務使用公司vsentinel 限流工具,主要使用 QPS 限流和熱點引數限流。
QPS 限流:對某個資源(通常為介面或方法,也可以自定義資源)的 QPS /併發數進行限流;熱點引數限流:對某些具體的引數值進行限流,避免因為熱點引數的過度訪問導致服務當機。
1.2 存在的問題
無論是 QPS 限流還是熱點引數限流,都是對資源/引數的定量限流,即對某個資源/引數設定固定閾值,超過閾值則進行限流。
回到業務,遊戲推薦系統作為遊戲分發的平臺,向公司內所有主要流量入口(包括遊戲中心、應用商店、瀏覽器等)分發遊戲、小遊戲、內容和評論,具有大流量、高負載的業務特點。同時,遊戲推薦系統對接的場景多(600+),單個性化介面有100+場景呼叫(場景可以理解為介面請求的一個基本請求引數)。當前的限流方案存在以下幾個問題:
-
引數級別的限流,600+場景,無法做到每個場景精細化限流;
-
介面級別的限流,不會區分具體的場景,無法保證核心場景的可用性;
-
如果場景流量有變更,需要及時調整限流閾值,不易維護;
-
場景的流量會實時變化,無法做到根據流量變化的動態限流。
鑑於以上限流問題,推薦系統需要一個能夠根據引數流量變化而動態調整限流閾值的精細化限流方案。
二、動態限流介紹
從配置方式上來看,動態限流和 QPS 限流、熱點引數限流最大的不同之處在於,動態限流不是透過配置固定閾值進行限流,而是配置每個引數的優先順序,根據引數的優先順序動態調整限流閾值。
動態限流將資源和引數進行繫結,首先配置資源(一般是介面/方法)總的限流閾值,進而配置資源下具體引數的優先順序,根據引數配置的優先順序和實時流量,決定當前請求pass or block。
下圖示例中,資源總的限流閾值為150,引數A、B、C、D的 QPS 均為100,且配置的引數優先順序 A>B>C>D。
-
引數A優先順序最高,且 QPS(A) = 100 < 限流閾值150,所以A的流量全部透過;
-
引數B優先順序僅次於引數A,且 QPS(A) = 100 < 限流閾值150、QPS(A) + QPS(B)= 200 > 限流閾值150,所以引數B部分流量透過,pass : 50,block:50;
-
引數C和其它引數的優先順序低於引數B,且 QPS(A) + QPS(B)= 200 > 限流閾值150,所以引數C和其它引數均被限流。
如果此時引數值A的 QPS 變為200,B、C的 QPS 仍為100,透過動態限流實現:A請求部分透過,B請求全部攔截,C請求全部攔截;根據各引數值流量的變化,動態適配各引數值透過/攔截的流量,從而實現根據引數值動態限流的效果。
總結:動態限流本質上是引數優先順序限流,支援對引數值配置優先順序,根據引數值的優先順序進行動態流控。當流量超過閾值後,優先保證高優先順序引數透過,攔截優先順序低的引數請求。
三、sentinel 介紹
由於動態限流是基於 sentinel 進行二次開發,且動態限流的實現演算法是基於 sentinel QPS 限流的最佳化,這裡首先介紹下 sentinel 實現原理和 sentinel QPS 限流的滑動視窗計數器限流演算法。
3.1 sentinel 原理介紹
sentinel 是阿里開源的一款面向分散式、多語言異構化服務架構的流量治理元件。主要以流量為切入點,從流量路由、流量控制、流量整形、熔斷降級、系統自適應過載保護、熱點流量防護等多個維度來幫助開發者保障微服務的穩定性。(官網描述)
sentinel 主要透過責任鏈模式實現不同模式的限流功能,責任鏈由一系列 ProcessorSlot 物件組成,每個 ProcessorSlot 物件負責不同的功能。
ProcessorSlot 物件可以分為兩類:一類是輔助完成資源指標資料統計的 slot,一類是實現限流降級功能的 slot。
輔助資源指標資料統計的 ProcessorSlot:
-
NodeSelectorSlot:負責收集資源路徑,並將呼叫路徑樹狀儲存,用於後續根據呼叫路徑來限流降級;
-
ClusterBuilderSlot:負責儲存資源的統計資訊以及呼叫者資訊,例如該資源的 RT、QPS、執行緒數等等,作為多維度限流、降級的依據;
-
StatisticSlot:負責實現指標資料統計,從多個維度(入口流量、呼叫者、資源)統計響應時間、併發執行緒數、處理失敗數量、處理成功數量等指標資訊。
實現限流降級功能的 slot:
-
ParamFlowSlot:用於根據請求引數進行限流(熱點引數限流),例如根據某個引數的 QPS 進行限流,或者根據某個引數的值進行限流;
-
SystemSlot:用於根據系統負載情況進行限流,例如 CPU 使用率、記憶體使用率等。
-
AuthoritySlot:用於根據呼叫者身份進行限流,例如根據呼叫者的 IP 地址、Token 等資訊進行限流。
-
FlowSlot:用於根據 QPS 進行限流,例如每秒最多隻能處理多少請求。
-
DegradeSlot:用於實現熔斷降級功能,例如當某個資源出現異常時,可以將其熔斷並降級處理。
除了上述原生 ProcessorSlot,sentinel 還支援 SPI 外掛功能,透過實現 ProcessorSlot 介面自定義 slot,從而能實現個性化功能擴充。動態限流正是基於 sentinel SPI 外掛方式實現。
3.2 滑動視窗計數器演算法
sentinel 的 QPS 限流採用滑動視窗計數器演算法,下面我們簡單介紹下這個演算法原理。
首先介紹一下計數器演算法。
3.2.1 計數器
計數器演算法:維護一個固定單位時間的計數器來統計請求數,在計數小於限流閾值時透過請求,計數到達限流閾值後攔截請求,直到下一個單位時間再重新計數。假設資源限制 1 秒內的訪問次數不能超過 100 次。
-
維護一個計數器,每次有新的請求過來,計數器加 1;
-
收到新請求後,如果計數器的值小於限流值,並且與上一次請求的時間間隔還在 1秒內,允許請求透過,否則拒絕請求;如果超出了時間間隔,要將計數器清零。
計數器演算法存在一個問題:視窗切換時可能會出現流量突刺(最高2倍)。極端情況下,假設每秒限流100,在第1s和第2s分別通100個請求,且第1s的請求集中在後半段,第2s的請求集中在前半段,那麼其實在500ms到1500ms這個1s的時間段,透過了200個請求。
為了解決這個問題,引入了基於滑動視窗的計數器演算法。
3.2.2 滑動視窗計數器
滑動視窗計數器演算法是計數器演算法的改進,解決了固定視窗的流量突刺問題。演算法原理:
-
將時間劃分為細粒度的區間,每個區間維持一個計數器,每進入一個請求則將計數器加1;
-
多個區間組成一個時間視窗,每到一個區間時間後,則拋棄最老的一個區間,納入新區間;
-
若當前視窗的區間計數器總和超過設定的限制數量,則本視窗內的後續請求都被丟棄。
滑動視窗本質上是固定視窗更細粒度的限流,將單位時間劃分多個視窗,劃分的視窗越多,資料越精確。
四、基於 sentinel 的動態限流方案
動態限流是基於 sentinel 的二次開發,具體實現流程和 sentinel 的 QPS 限流類似,可以歸納為三步:資料統計、規則管理、流量校驗。
-
資料統計:統計資源(介面/方法/引數)的流量;
-
規則管理:管理限流規則,維護資源的限流閾值及引數值優先順序;
-
流量校驗:對比統計到的流量和對應的限流規則,決定當前請求 pass or block。
4.1 資料統計
動態限流的資料統計同 sentinel 流量控制模組一樣,使用滑動視窗計數器演算法統計當前的流量。
具體來講,sentinel 流量控制中的資料統計,是將1s的時間窗細分為多個視窗,按視窗維度統計資源資訊,包括請求總數、成功總數、異常總數、總耗時、最小耗時、最大耗時等。
動態限流的資料統計,同樣是將1s的時間窗細分為多個視窗,不同的是視窗的統計維度是各個引數值透過的總流量。
具體實現上,每個資源有唯一的 bucket,bucket 內維護一個固定數量的滑動視窗,視窗中的 value 是一個 hash 結構,hash key 為限流引數的引數值,value 為引數值在當前時間視窗的請求量。
引數值流量統計流程:
-
系統收到請求後,首先找到當前資源的 bucket;
-
再根據當前時間戳對 bucket 內的視窗數量取餘,定位到當前時間窗;
-
當前時間窗內引數值的請求量+1。
4.2 規則管理
規則管理模組:配置和管理限流規則。
限流規則透過zk實現從後臺到端上的同步。後臺配置好限流規則後,將限流規則同步到zk;客戶端監聽zk訊息變更,同步最新的限流規則。
4.3 流量校驗
4.3.1 引數臨界點
對於動態限流而言,引數的限流閾值不是固定的,只有引數優先順序的概念,所以校驗的第一步是要找到限流閾值優先順序的臨界點。
如果引數優先順序臨界點已知,只需要判斷流量引數的優先順序大小。如果請求的優先順序高於閾值引數的優先順序,pass;反之,如果請求的優先順序低於閾值引數的優先順序,block;優先順序相等,按介面閾值限流。
那麼如何確認當前限流的優先順序呢?
4.3.2 細分視窗
當前限流閾值配置一般為秒級別的限流,細分滑動視窗,就是將1s的視窗劃分為N個更小的時間窗,只要N足夠大,就可以將前N-1個視窗已經統計到的引數流量近似當做這一秒的流量,進而就可以計算出臨界引數的優先順序。具體來講,每一個視窗中都記錄了引數的請求數量,所以只要將前N-1個視窗的流量累加,就可以得到各個引數在當前這1s內的總請求量;之後按照引數的優先順序從高到低,依次累加流量並與閾值比較,如果累加到某個引數時大於限流閾值,則這個引數對應的優先順序即為限流閾值優先順序的臨界點。
上面分析都是基於最理想情況:將1s的視窗無限細分。考慮到滑動視窗粒度越小,統計資料計算的越準確,但同時佔用的資源也越多,計算越複雜,時延也越高,所以在實際應用中,1s的視窗不可能無限細分,是否有更好的最佳化方案呢?
4.3.3 動態預測
上面是將1s的視窗劃分為N個更小的時間窗,將前N-1個視窗近似看成1s,利用前N-1個視窗的統計資料,來判斷當前視窗是否需要限流。
N-1→ N → 1s,N越大誤差越小,反之N越小誤差就越大,為了彌補N大小引起的計算誤差,將統計視窗朝前挪一個,即用最近1s已有的統計資料,來判斷當前視窗是否需要限流。
換一種說法:用最近1s已有的統計資料計算臨界點引數,預測當前視窗的請求是否需要限流。如果當前請求引數的優先順序高於臨界點引數,pass;低於臨界點引數,block;等於臨界點引數,部分透過。
綜上:動態限流採用細分視窗+動態預測的方法計算當前限流引數的優先順序閾值。
舉例說明:對方法 method(String param) 配置動態限流,限流閾值為120,配置 param 具體引數值的優先順序為A→ 1, B→ 2, C→ 3(按重要程度劃分 A > B > C);假設視窗大小為100ms,即1s細分為10個滑動視窗。每次開始新視窗流量計數時,先統計前10個視窗中各引數的請求量,繼而按照優先順序從高到低進行累加,確認優先順序閾值;比如統計到前10個視窗中引數A, B, C的請求量均為100,因為A的流量100 < 閾值120,A + B的流量200 > 閾值120,所以此時臨界引數為B;視窗接收到新請求後,比較請求引數和臨界引數的優先順序,比如引數A的請求,因為A的優先順序高於B, pass;引數B的臨界引數請求,允許部分流量透過;引數C的優先順序低於臨界引數,block。
4.3.4 double check
經過上面的分析可知,透過滑動視窗+動態預測的方案就可以找到臨界點引數,進而根據引數優先順序決定當前請求 pass or block。但是在實際時間窗和統計時間窗之間,有一個時間 gap,在這個時間窗內的流量計算有一定的滯後性,比如上面的例子,在新視窗中A的請求全部 pass,如果此時A的流量突刺到1000,那麼總體透過的流量就會超過閾值,如下圖所示。
由上圖可知,在流量突增的一個時間窗內,當前方案透過的流量會有突刺,為了解決流量突增帶來的突刺問題,使用 double check 進行校驗;check1 為細分視窗+動態預測方案,透過 check1 的流量可能會有突刺;增加 check2 對資源進行限流,保證被保護資源透過的總流量不超過閾值。
double check 流量在應對流量突增時的流量情況:
4.4 整體架構
複用 sentinel 責任鏈+ SPI 架構,使用獨立 SDK 打包方式嵌入動態限流模板,不影響原 sentinel 處理流程,按需引入。
4.5 實現效果
動態限流配置生效後,可透過監控檢視各配置引數透過/拒絕的請求量,實現限流功能的視覺化。
如上圖所示,配置某個資源的單機限流閾值為50,這一秒內的總請求量為74,透過50個請求,拒絕14個請求(其中配置限流的引數 xx.scene.priority1、xx.scene.priority2、xx.scene.priority3 在這一秒透過的請求數量分別為2個、16個、2個;其它引數透過30個請求,拒絕14個請求)。
解釋:在這一秒內,配置的三個引數總請求量為20(2+16+2),小於閾值50,全部透過;其它引數總流量為44,這一秒的總請求量為64(20+44),大於限流閾值50,所以其它引數共透過30個請求,拒絕14個請求。
五、總結
本文介紹了一種基於 sentinel 進行二次開發的動態限流解決方案,提供更細粒度、能夠根據流量動態調整限流閾值的引數級限流方法,是對 sentinel 限流功能的補充和擴充。
-
對比 sentinel 的 QPS 限流,動態限流方案提供了更細粒度的引數級別的限流;
-
對比 sentinel 的熱點引數限流,熱點引數限流是對引數的定量限流,適用於引數大流量場景,比如對某個頻繁請求的引數(id/商品)進行限流;動態限流根據配置的引數優先順序(重要程度)進行限流,限流的閾值引數會根據資源的流量動態調整,pass 高優引數,block 低優引數,限流重點是“保核心”。
綜上,動態限流是對 sentinel 限流功能的補充,使用者可以結合具體場景配置不同的限流方案。