基于系統(tǒng)負載的動態(tài)限流組件 dynamic-limiter
來源:Qunar 技術沙龍
背景
一個系統(tǒng)的處理能力是有限的,當請求量超過處理能力時,通常會引起排隊,造成響應時間迅速提升。如果對服務占用的資源量沒有約束,還可能因為系統(tǒng)資源占用過多而宕機。因此,為了保證系統(tǒng)在遭遇突發(fā)流量時,能夠正常運行,需要為你的服務加上限流。
通常限流可以分為兩類:單機限流、全局限流。常見的單機限流工具有 Guava RateLimiter 和 Java Semaphore,全局限流可以用 Redis 做全局計數(shù)器來實現(xiàn),基礎架構(gòu)組也提供了一個靈活的全局限流組件 common-blocking。這些限流工具有一個共同的缺點:都需要手動設置一個固定的限流閾值。
首先,手動設置固定閾值需要做容量評估,準確的容量評估是比較難的。其次,在每次系統(tǒng)更新升級后,閾值會變得不再準確,需要重新調(diào)整,比較繁瑣。再次,固定閾值也不能應對服務器性能波動的情況,對于一些日志量比較大的應用,整點日志壓縮時,會消耗較多性能,此時系統(tǒng)的處理能力肯定比其他時候要稍差一些。最后,應用大多運行在虛擬機上,同一個實體機上的虛擬機之間也會相互影響,這個體現(xiàn)在監(jiān)控上就是 CPU 使用率里的 steal 值了。
既然固定閾值有這么多缺點,我們就想有沒有什么辦法能夠自動計算限流閾值呢?下面介紹一下:基于系統(tǒng)負載的動態(tài)限流。
動態(tài)限流原理
為什么叫動態(tài)限流呢?因為我們希望在系統(tǒng)運行時,限流閾值能夠根據(jù)實際情況做動態(tài)調(diào)整。具體根據(jù)什么來調(diào)整呢?系統(tǒng)負載,這里我們使用了最常見的三種監(jiān)控指標:CPU 使用率、Load 和服務的響應時間。
動態(tài)限流的目標是,計算一個合理的閾值,讓系統(tǒng)在提供最大處理能力的同時,保持健壯,不被壓垮。
動態(tài)限流的基本思路可以看下面這幅圖,系統(tǒng)負載反過來說就是系統(tǒng)的健康程度。當系統(tǒng)負載較低,處于健康狀態(tài)時不限流。當系統(tǒng)負載稍高,處于不健康狀態(tài)時,以最近幾秒處理請求的 QPS 計算限流閾值。當系統(tǒng)負載過高,狀態(tài)惡化時,讓限流閾值以一定的系數(shù)進行衰減,直到系統(tǒng)負載降低,系統(tǒng)狀態(tài)由惡化變?yōu)椴唤】担罱K讓系統(tǒng)負載收斂在兩個負載閾值之間。
前面提到在健康狀態(tài)下不限流,那么系統(tǒng)在從健康狀態(tài)變?yōu)椴唤】祷驉夯癄顟B(tài)時,就需要計算一個初始限流閾值,初始限流閾值的計算參考了健康狀態(tài)的 QPS 和當前處理請求的 QPS。具體的計算公式如下圖所示,其中 H 表示健康狀態(tài)下的 QPS,C 表示當前處理請求的 QPS。
其中的重點是系統(tǒng)狀態(tài)從健康變成惡化時的閾值計算,限流閾值等于 H 乘以一個系數(shù),這個系數(shù)是 C 除以 H 的二分之一次方,也就是流量暴漲倍數(shù)的二分之一次方。這樣計算的目的,是避免像下圖這樣的情況,初始閾值設置的不合理時,限流閾值收斂到合理區(qū)間太慢,浪費系統(tǒng)資源。
初始閾值設定之后,還需要根據(jù)系統(tǒng)負載進行動態(tài)調(diào)整,如何動態(tài)調(diào)整呢?可以先看下面這幅閾值調(diào)整示意圖,相比之前的基本思路圖,這里多了一個負載閾值 0,設置它的目的是希望當初始閾值設置不合理導致系統(tǒng)負載變得很低時,能夠快速提升閾值。當系統(tǒng)負載接近收斂區(qū)間時,進行細微調(diào)整,避免步子邁得太大,把系統(tǒng)搞垮了。簡單說就是當系統(tǒng)負載低的時候,快速調(diào)整,當系統(tǒng)負載高的時候,細微調(diào)整。
在實際中,負載閾值 1 和 2 可以靈活配置,為了減少配置工作量,負載閾值 0 固定為負載閾值 1 的 70%。
最后再說一下,何時不再限流,恢復正常呢?當突發(fā)流量消失,系統(tǒng)能夠處理全部請求,并且處于健康狀態(tài)時,不再限流。
到這里動態(tài)限流的原理就講完了,下面我們看一下線上測試效果。
測試效果
最初我們做了基于 Load 的動態(tài)限流,服務器 CPU 是 4 核的,所以兩個負載閾值分別設置成 3 和 5,限流閾值更新頻率為 1 秒一次。
實際效果請看下面的監(jiān)控圖,左邊是 10 倍流量壓測而未開限流的情況,未開限流時 CPU 使用率高達 99%,Load 也高達 20。中間打開限流之后,Load 降到 5 左右,CPU 使用率也降了下去,但是波動很大,為什么呢?想到之前看過的文章里提到 Load 是 5 秒采樣一次,而這里閾值 1 秒更新一次,更新太快了,更新之后還沒有體現(xiàn)在 Load 計算上就又更新了。
當我們將閾值更新頻率改為 10 秒一次時,從下圖可以看出來,CPU 和 Load 的波動小了很多。
看監(jiān)控我們發(fā)現(xiàn) Load 在 3 到 5 之間波動時,CPU 使用率才 60%,還有提高的空間。我們知道 Load 和 CPU 不同步的原因是,Load 不僅和計算有關,也和 IO 有關。而報價是計算密集型的應用,所以我們又試驗了基于 CPU 使用率的動態(tài)限流。
我們將閾值設定為 70 到 90,看下面的監(jiān)控圖,CPU 使用率基本穩(wěn)定在 70 到 90 之間,Load 稍微高一些。壓測之后搜索耗時從 70 漲到了 150 并保持穩(wěn)定,穩(wěn)定就表示服務是正常的。
下面我們再看一下,基于 CPU 和基于 Load 限流時搜索成功量的對比,分別是 164 和 134,說明基于 CPU 的限流的確提升了系統(tǒng)處理能力,提高了資源利用效率。
一些服務可能對響應時間比較敏感,所以我們又做了基于時間的動態(tài)限流,當我們將閾值設定在 140 到 200 之間時,看監(jiān)控壓測之后搜索耗時也基本穩(wěn)定在這個 140 到 200 之間,CPU 和 Load 監(jiān)控也保持穩(wěn)定。
總結(jié)
我們將上述講的基于負載的動態(tài)限流封裝到了一個 API dynamic-limiter 中,供各個系統(tǒng)使用。最后總結(jié)一下,動態(tài)限流適合什么樣的場景呢?
- 如果你的系統(tǒng)內(nèi)單個服務占用大部分資源,就可以使用基于 CPU 或 Load 的動態(tài)限流。
- 如果你的服務對響應時間要求比較高,可以使用基于時間的動態(tài)限流。
實際中,也可以同時參考多種因素來進行動態(tài)限流,起到一個多重約束的作用。比如同時使用 CPU 和 TIME 時,表示既對 CPU 使用率有一個硬約束,又對服務響應時間有一個硬約束。