又拍雲張聰:OpenResty 動態流控的幾種姿式

2019 年 1 月 12 日,由又拍雲、OpenResty 中國社區主辦的 OpenResty × Open Talk 全國巡迴沙龍·深圳站圓滿結束,又拍雲首席架構師張聰在活動上作了《 OpenResty 動態流控的幾種姿式 》的分享。
OpenResty x Open Talk 全國巡迴沙龍是由 OpenResty 社區、又拍雲發起的,爲促進 OpenResty 在技術圈的發展,增進 OpenResty 使用者的交流與學習的系列活動,活動將會陸續在深圳、北京、上海、廣州、杭州、成都、武漢等地舉辦,歡迎你們關注。
張聰,又拍雲首席架構師,多年 CDN 行業產品設計、技術開發和團隊管理相關經驗,我的技術方向集中在 Nginx、OpenResty 等高性能 Web 服務器方面,國內 OpenResty 技術早期推廣者之一;目前擔任又拍雲內容加速部技術負責人,主導又拍雲 CDN 技術平臺的建設和發展。html

 

如下是分享全文:node

 

你們下午好,今天我主要和你們分享「在 OpenResty 上如何作動態的流量控制」,將會從如下幾個方面來介紹:git

  • Nginx 如何作流控,介紹幾種經典的速率和流量控制的指令和方法;
  • OpenResty 如何動態化作流控;
  • OpenResty 動態流控在又拍雲的業務應用。

又拍雲與 OpenResty 結緣

我目前在又拍雲負責 CDN 的架構設計和開發工做,又拍雲早在 2012 年就開始接觸 OpenResty ,當時咱們作調研選型,部分項目考慮用 Lua 來實現,在此以前是基於 Nginx C 模塊來作業務開發,一個防盜鏈模塊就好幾千行代碼,改爲 Lua 以後大量減小了代碼,而且整個開發的效率、維護的複雜度都下降了。此外咱們經過測試和性能對比,幾乎沒有多的損耗,由於在這一層主要是字符串的處理,甚至在 LuaJIT 加速的狀況下有不少的調用,比咱們原先用 C 寫的函數還高效得多。github

 

目前又拍雲整個 CDN 代理層系統、對外開放的 API 系統、數據中心的網關係統、分佈式雲存儲代理層、邏輯層所有用 ngx_lua 進行了深度的改造,又拍雲內部幾個不一樣業務的團隊都在 OpenResty 技術棧上有多年的實踐和經驗積累。redis

又拍雲開放了一個 upyun-resty 的倉庫(https://github.com/upyun/upyun-resty),咱們內部孵化的開源項目以及對社區的補丁修復等都會發布在這個倉庫。你們若是對又拍雲這塊的工做感興趣能夠關注這個倉庫,咱們今年還會陸續把內部使用很是成熟的一些庫放出來,包括今天講的兩個與限速有關的 Lua 庫也已經開源出來了。算法

什麼是流控以及爲何要作流控

一、什麼是流控

今天的主題,首先是針對應用層,尤爲是 7 層的 HTTP 層,在業務流量進來的時候如何作流量的疏導和控制。我我的對「流控」的理解(針對應用層):後端

(1) 流控一般意義下是經過一些合理的技術手段對入口請求或流量進行有效地疏導和控制,從而使得有限資源的上游服務和整個系統能始終在健康的設計負荷下工做,同時在不影響絕大多數用戶體驗的狀況下,整個系統的「利益」最大化。安全

由於後端資源有限,不管考慮成本、機器或者系統自己的瓶頸,不可能要求上游系統可以承受突發的流量,而須要在前面作好流量的控制和管理。服務器

有時候咱們不得不犧牲少數的用戶體驗,拒絕部分請求來保證絕大多數的請求正常地服務,其實沒有完美可以解決全部問題的方案,因此這個在流量控制中要結合咱們對業務的理解須要學會作取捨。網絡

(2) 流控有時候也是在考慮安全和成本時的一個手段。

除了上面的通用場景,流控也在安全和成本上作控制。好比敏感帳號的登陸頁面,密碼失敗次數多了就禁掉它,不容許反覆暴力嘗試;好比咱們的上游帶寬有限,須要確保傳輸的帶寬在較低的水平中進行,不要把線路跑滿,由於跑滿有可能涉及到一些成本超支的問題等。

二、爲何要流控

針對上面的描述,下面介紹一些流控跟速率限制的方法。

(1)爲了業務數據安全,針對關鍵密碼認證請求進行有限次數限制,避免他人經過字典攻擊暴力破解。

爲了數據安全,咱們會對一些敏感的請求嘗試訪問作累計次數的限制,好比必定時間內你輸錯了三次密碼,接下來的幾個小時內就不讓你來嘗試了,這是一種很常見的手段。若是沒有這樣的保護,攻擊者會不斷試你的密碼,調用這個敏感的接口,最終可能會讓他試出來,因此這是須要保護的。

(2)在保障正經常使用戶請求頻率的同時,限制非正常速率的惡意 DDoS 攻擊請求,拒絕非人類訪問。

咱們須要保障一個 API 服務正常的請求流量,可是拒絕徹底惡意的 DDoS 的攻擊、大量非人類訪問。這也是在最前面這層須要作的事情,不然這些請求串進上游,不少後端的服務器確定是抗不住的。

(3)控制上游應用在同一時刻處理的用戶請求數量,以避免出現併發資源競爭致使體驗降低。

咱們須要控制上游只能同時併發處理幾個任務或幾個請求,此時關心的是「同時」,由於它可能有內部的資源競爭,或者有一些衝突,必須保證這個服務「同時」只能知足幾個用戶的處理。

(4)上游業務處理能力有限,若是某一時刻累計未完成任務超過設計最大容量,會致使總體系統出現不穩定甚至持續惡化,須要時刻保持在安全負荷下工做。

當咱們整個上游系統的彈性伸縮能力還不錯,它會有一個設計好的最大容量空間,即最多累計可以承受多大量的請求流入。若是超過它最大可處理範圍性能就會降低。例如一個任務系統每小時可以完成 10 萬個任務,若是一個小時內任務沒有堆積超過 10 萬,它都可以正常處理;但某一個小時出現了 20 萬請求,那它處理能力就會降低,它本來一小時能處理 10 萬,此時可能只能處理 5 萬或 2 萬甚至更少,性能變得不好,持續惡化,甚至最終致使崩潰。

所以,咱們須要對這樣的流量進行疏導,確保後端系統可以健康地運行,若是它每小時最多隻能跑 10 萬的任務,那麼不管多大的任務量,每小時最多都應只讓它跑 10 萬的量,而不是由於量超過了,反而最後連 10 萬都跑不到。

(5)集羣模式下,負載均衡也是流控最基礎的一個環節,固然也有些業務沒法精確進行前置負載均衡,例如圖片處理等場景就容易出現單點資源瓶頸,此時須要根據上游節點實時負載狀況進行主動調度。

在作流量管理時,負載均衡是很基礎的。若是一個集羣基本負載均衡都沒作好,流量仍是偏的,上游某個節點很容易在集羣中出現單點,這時去作流量控制就有點不合適。流量控制,首先在集羣模式下要先作好負載均衡,在流量均衡的狀況下再去作流量控制,識別惡意的流量。而不要前面的負載均衡都沒作好,流量都集中在某一臺機器上,那你在這一臺上去作控制,吃力不討好。

(6)在實際的業務運營中,每每出於成本考慮,還須要進行流量整形和帶寬控制,包括下載限速和上傳限速,以及在特定領域例如終端設備音視頻播放場景下,根據實際碼率進行鍼對性速率限制等。

出於成本的考慮,咱們會對一些流量進行控制。好比下載限速是一個很常見的場景,終端用戶尤爲是移動端,在進行視頻的播放,按正常的碼率播放已經足夠流暢。若是是家庭帶寬,下載速度很快,打開沒一下子就把電影下載完成了,但實際上沒有必要,由於電影播放已經足夠流暢,一會兒把它下載完浪費了不少流量。特別地對於音視頻的內容提供商,他以爲浪費了流量並且用戶體驗差很少,因此此時通常會對這些文件進行下載限速。

經典的 Nginx 方式實現流量控制

 

Nginx 你們都很是熟悉,特別是數據中心或後端的服務,無論是什麼語言寫的,可能你也不明白爲何要這麼作,但前面套一個 Nginx 老是讓人放心一點,由於在這麼多年的發展中,Nginx 已經默默變成一個很是基礎可靠的反頂流量最外層入口的在向代理服務器,基本上不少開發者甚至感知不到它的存在,只知道運維幫忙前面架了一個轉發的服務。

因此,若是咱們要作流量管理,應該儘可能往前作,不要等流量轉發到後面,讓應用服務去作可能已經來不及了,應用服務只須要關心業務,這些通用的事情就讓上層的代理服務器完成。

一、Nginx 請求速率限制

(1)limit_req 模塊

△ ngx_http_limit_req_module

limit_req 是 Nginx 最經常使用的限速的模塊,上圖是一個簡單的配置,它基於來源 IP 做爲惟一的 Key,針對某個惟一的來源 IP 作速率控制,這裏的速率控制配置是 5r/s( 1 秒內容許 5 個請求進來),基於這個模塊的實現,再解釋一下 5r/s,即每隔 200ms 可以容許進來一個請求,每一個請求的間隔必須大於 200ms,若是小於 200ms 它就會幫你拒絕。

使用起來很簡單,配置一個共享內存,爲了多個 worker 能共享狀態。具體能夠在 location 作這樣的配置,配完以後就會產生限速的效果。

 
 

△ limit_req 請求示意圖

上圖能夠更加直觀地瞭解這個機制,圖中整個灰色的時間條跨度是 1s,我按 200ms 切割成了五等份,時間條上面的箭頭表明一個請求。0s 的時候第一次請求過來直接轉發到後面去了;第二次間隔 200ms 過來的請求也是直接轉發到上游;第三個請求也同樣;第四個請求(第一個紅色箭頭)在 500ms 左右過來,它跟前一個請求的時間間隔只有 100ms,此時模塊就發揮做用,幫你拒絕掉,後面也是相似的。

總結一下 limit_req 模塊的特色:

  • 針對來源IP,限制其請求速率爲 5r/s。
  • 意味着,相鄰請求間隔至少 200ms,不然拒絕。

但實際業務中,偶爾有些突增也是正常的。

這樣簡單地使用,不少時候在實際的業務中是用得不舒服的,由於實際業務中不少場景是須要有一些偶爾的突增的,這樣操做會過於敏感,一超過 200ms 就彈,絕大多數系統都須要容許偶爾的突發,而不能那麼嚴格地去作速率限制。

(2)brust 功能參數

這樣就引出了 limit_req 模塊中的一個功能參數 brust(突發),爲了方便演示,這裏設置 brust=4,表示在超過限制速率 5r/s 的時候,同時最多容許額外有 4 個請求排隊等候,待平均速率迴歸正常後,隊列最前面的請求會優先被處理。

 

△ brust=4,limit_req 請求示意圖

在 brust 參數的配合下,請求頻率限制容許必定程度的突發請求。設置爲 4 次後,表示在超過 5r/s 的瞬間,原本要直接彈掉的請求,如今系統容許額外有 4 個位置的排隊等候,等到總體的平均速率迴歸到正常後,排隊中的 4 個請求會挨個放進去。對於上游的業務服務,感知到的始終是 200ms 一個間隔進來一個請求,部分提早到達的請求在 Nginx 這側進行排隊,等到請求能夠進來了就放進來,這樣就容許了必定程度的突發。

如上圖,時間條上面第四個請求跟第三個,間隔明顯是小於 200ms ,按原來的設置應該就直接拒絕了,但如今咱們容許必定程度的突發,因此第四個請求被排隊了,等時間慢慢流轉到 600ms 的時候,就會讓它轉發給後端,實際它等待了 100ms。下面也是挨個進來排隊,第五個請求進來它排隊了 200ms,由於前面的時間片已經被第四個請求佔用了,它必須等到下一個時間片才能轉發。能夠看到上游服務接收到的請求間隔永遠是恆定的 200ms。

在已經存在 4 個請求同時等候的狀況下,此時「馬上」過來的請求就會被拒絕。上圖中能夠看到從第五個請求到第九個請求,一共排隊了 5 個請求,第十個請求才被拒絕。由於時間一直是在流動的,它總體是一個動態排隊的過程,解決了必定程度的突發,固然太多突發了仍是會處理的。

雖然容許了必定程度的突發,但有些業務場景中,排隊致使的請求延遲增長是不可接受的,例如上圖中突發隊列隊尾的那個請求被滯後了 800ms 才進行處理。對於一些敏感的業務,咱們不容許排隊過久,由於這些延時根本就不是在進行有效處理,它只是等候在 Nginx 這側,這時不少業務場景可能就接受不了,這樣的機制咱們也須要結合新的要求再優化。可是若是你對延時沒有要求,容許必定的突發,用起來已經比較舒服了。

(3)nodealy 功能參數

limit_req 模塊引入了 nodelay 的功能參數,配合 brust 參數使用。nodelay 參數配合 brust=4 就可使得突發時須要等待的請求當即獲得處理,與此同時,模擬一個插槽個數爲 4 的「令牌」隊列(桶)。

△ brust=4 配合 nodelay 的 limit_req 請求示意圖

原本突發的請求是須要等待的,有了 nodelay 參數後,本來須要等待的 4 個請求一旦過來就直接轉發給後端,落到後端的請求不會像剛剛那樣存在嚴格的 200ms 間隔,在比較短的時間內就會落下去,它實際上沒有在排隊,請求進來直接往上游就轉發,不事後續超出隊列突發的請求仍然是會被限制的。

爲了可以比較好理解這個場景,引入一個虛擬「桶」。從抽象的角度描述下這個過程,該「令牌」桶會每隔 200ms 釋放一個「令牌」,空出的槽位等待新的「令牌」進來,若桶槽位被填滿,隨後突發的請求就會被拒絕。

原本第六到第九這 4 個請求是排隊等候在 Nginx 一側,如今它們沒有等待直接下去了,能夠理解爲咱們拿出了 4 個虛擬的令牌放入一個「桶」,4 個令牌模擬這 4 個請求在排隊。「桶」每隔 200ms 就會釋放出一個令牌,而一旦它釋放出一個,新的虛擬令牌就能夠過來,若是它還沒釋放出,「桶」是滿的,這時請求過來仍是會被拒絕。總而言之就是真實的請求沒有在排隊,而是引入了 4 個虛擬的令牌在排隊,在它滿的狀況下是不容許其它請求進來。

如此,能夠保證這些排隊的請求不須要消耗在無謂的等待上,能夠直接進去先處理,而對於後面超過突發值的請求仍是拒絕的。這樣就達到了折中,對於上游,它須要更短的時間間隔來處理請求,固然這須要結合業務來考慮,這裏只是提供了一種方式和特定的案例。

總結,在這個模式下,在控制請求速率的同時,容許了必定程度的突發,而且這些突發的請求因爲不須要排隊,它可以當即獲得處理,改善了延遲體驗。

(4)delay 功能參數

Nginx 最新的版本 1.15.7 增長了 delay 參數,**支持 delay=number 和 brust=number 參數配合使用。 **delay 也是一個獨立的參數,它支持 number(數量)的配置,和突發的數量配置是同樣的,這兩個參數解決的問題更加細緻,通用場景中遇到的可能會少一點。

這個功能參數是這樣描述的:在有些特定場景下,咱們既須要保障正常的少許關聯資源可以快速地加載,同時也須要對於突發請求及時地進行限制,而 delay 參數能更精細地來控制這類限制效果。

好比網站的頁面,它下面有 4-6 個 JS 、CSS 文件,加載頁面時須要同時快速地加載完這幾個文件,才能確保整個頁面的渲染沒有問題。但若是同時超過十個併發請求在這個頁面上出現,那可能就會是非預期的突發,由於一個頁面總共才 4-6 個資源,若是刷一下同時過來的是 12 個請求,說明用戶很快地刷了屢次。在這種狀況下,業務上是要控制的,就能夠引入了 delay 參數,它可以更精細地來控制限制效果。

在上面的例子中,一個頁面併發加載資源加載這個頁面的時候會跑過來 4-6 個請求,某個用戶點一下頁面,服務端收到的是關於這個頁面的 4-6 個併發請求,返回給它;若是他很快地點了兩下,咱們以爲須要禁止他很快地刷這個頁面刷兩次,就須要把超過併發數的這部分請求限制掉,但 burst 設置過小又擔憂有誤傷,設置太大可能就起不到任何效果。

此時,咱們能夠配置一個策略,總體突發配置成 12,超過 12 個確定是須要拒絕的。而在 12 範圍內,咱們但願前面過來的 4-6 個併發請求可以更快地加載,不要進行無效地等待,這裏設置 delay=8 ,隊列中前 8 個等候的請求會直接傳給上游,而不會排隊,而第 8 個以後的請求仍然會排隊,但不會被直接拒絕,只是會慢一些,避免在這個尺度內出現一些誤傷,同時也起到了必定限制效果(增大時延)。

上面 4 點都是講 Nginx 怎麼進行請求速率限制,簡單總結一下,速率就是針對連續兩個請求間的請求頻率的控制,包括容許必定程度的突發,以及突發排隊是否須要延後處理的優化,還有後面提到的 delay 和 brust 的配合使用。

二、Nginx 併發鏈接數限制

 

△ ngx_http_limit_conn_module

Nginx 有一個模塊叫 limit_conn,在下載的場景中,會出現幾個用戶同時在下載同一個資源,對於處理中的請求,該模塊是在讀完請求頭所有內容後纔開始計數,好比同時容許在線 5 人下載,那就限制 5 個,超過的 503 拒絕。特別地,在 HTTP/2 和 SPDY 協議下,每個併發請求都會看成一個獨立的計數項。

三、Nginx 下載帶寬限制

 

△ ngx_http_core_module

在 ngx_http_core_module 模塊裏面有 limit_rate_after 和 limit_rate 參數,這個是下載帶寬限制。如上圖,意思是在下載完前面 500KB 數據後,對接下來的數據以每秒 20KB 速度進行限制,這個在文件下載、視頻播放等業務場景中應用比較多,能夠避免沒必要要的浪費。例如視頻播放,第一個畫面可以儘快看到,對用戶體驗來講很重要,若是用戶第一個頁面看不到,那他的等待忍耐程度是不好的,因此這個場景下前面的幾個字節不該該去限速,在看到第一個畫面以後,後面畫面是按照必定視頻碼率播放,因此不必下載很快,並且快了也沒用,它照樣是流暢的,但卻多浪費了流量資源,若是用戶看到一半就關掉,整個視頻下載完成,對於用戶和內容提供商都是資源浪費。

OpenResty 動態流控

相比 Nginx ,OpenResty 具備不少的優點。

  • 咱們須要更加豐富的流控策略!Nginx 只有經典的幾種。
  • 咱們須要更加靈活的配置管理!限速的策略配置規則是多樣化的,咱們須要更加靈活。
  • 咱們須要在 Nginx 請求生命週期的更多階段進行控制!前面提到的的 limit_req 模塊,它只能在 PREACCESS 階段進行控制,咱們可能須要在 SSL 的卸載過程當中對握手的鏈接頻率進行控制,咱們也可能須要在其它任意階段進行請求頻率控制,那 Nginx 這個模塊就作不到了。
  • 咱們須要跨機器進行狀態同步!

請求速率限制 / 併發鏈接數限制

OpenResty 官方有一個叫作 lua-resty-limit-traffic 的模塊,裏面有三種限速的策略。

(1) resty.limit.req 模塊

 

△ lua-resty-limit-traffic (resty.limit.req)

resty.limit.req 模塊的設計與 NGINX limit_req 實現的效果和功能同樣,固然它用 Lua 來表達限速邏輯,能夠在任何的代碼裏面去引入,幾乎能夠在任意上下⽂中使⽤。

(2)resty.limit.conn 模塊

功能和 NGINX limit_conn 一致,但 Lua 版本容許突發鏈接進行短暫延遲等候。

(3)resty.limit.count 模塊

 

△ lua-resty-limit-traffic (resty.limit.count)

第三個是 resty.limit.count 模塊,請求數量限制,這個目前 Nginx 沒有,用一句話歸納這個模塊,就是在單位時間內確保累計的請求數量不超過一個最大的值。好比在 1 分鐘以內容許累計有 100 個請求,累計超過 100 就拒絕。這個模塊和 Github API Rate Limiting(https://developer.github.com/v3/#rate-limiting)的接口設計相似,也是一個比較經典的限制請求的方式。

跨機器速率限制

 

△ lua-resty-redis-ratelimit (resty.redis.ratelimit)

有了 OpenResty,能夠作一些更加有意思的事情。好比咱們有多臺機器,想把限制的狀態共享,又拍雲以前開放了一個簡單的模塊叫 lua-resty-redis-ratelimit(resty.redis.ratelimit),顧名思義就是把這個狀態扔到 Redis 保存。它和 Nginx limit req 以及 resty.limit.req 同樣,都是基於漏桶算法對平均請求速率進行限制。不一樣的是,該模塊將信息保存在 Redis 從而實現多 Nginx 實例狀態共享。

藉助於 Redis Lua Script 機制 ,Redis 有一個支持寫 Lua 腳本的功能,這個腳本可以讓一些操做在 Redis 執行的時候保證原子性,依賴這個機制,咱們把一次狀態的變動用 Lua Script 就可以徹底原子性地在 Redis 裏面作完。

同時,該模塊支持在整個集羣層⾯禁⽌某個非法⽤用戶一段時間,可實現全局自動拉⿊功能。由於是全局共享,一旦全網有一個客戶觸發了設置的請求頻率限制,咱們能夠在整個集羣內瞬間把他拉黑幾個小時。

固然這個模塊是有代價的,並且代價也比較大,由於 Nginx 和 Redis 交互須要網絡 IO,會帶來必定延遲開銷,僅適合請求量不大,但須要很是精確限制全局請求速率或單位統計時間跨度很是大的場景。

固然,這個模塊也能夠作一些本身的優化,不必定全部的狀態都須要跟 Redis 同步,能夠根據本身的業務狀況作一些局部計算,而後定時作全局同步,犧牲一些精確性和及時性,這些均可以去抉擇,這邊只是多提供了一個手段。

知識點-漏桶算法

 

△ 漏桶算法示意圖

前面提到的多個模塊都是基於漏桶算法的思想達到頻率限速的效果,如上圖,一個水桶,水滴一滴一滴往下滴,咱們但願水往下滴的速度儘量是恆定的,這樣下游可以承載的處理能力是比較健康的,不要一會兒桶就漏了一個大洞衝下去,但願它均衡地按序地往下滴,同時前面會有源源不斷的水進來。

 

 

這個漏桶算法思想的核心就是上圖中這個簡單的公式,咱們怎麼把請求的 5r/s,即每 200ms 一個請求的頻次限制代到這個公式呢?

首先,在具體實現中,通常定義最小速率爲 0.001r/s,即最小的請求刻度是 0.001 個請求,爲了直觀計算,咱們用 1 個水滴(假設單位t)來表達 0.001 個請求,那麼 rate=5r/s 至關於 5000t/s。

前面提到該算法是計算兩個相鄰請求的頻率,因此要計算當前請求和上一個請求的時間間隔,假設是 100 ms,單位是毫秒,下面公式中除以 1000 轉換成秒等於 0.1s,即 0.1s 可以往下滴 500 個水滴,由於速率是 5000t/s,時間過去了 0.1 秒,固然只滴下去 500 滴水。

500 水滴下去的同時,速率一直是恆定的,可是同時又有請求進來,由於新的請求進來纔會去計算這個公式,因此後面加了 1000,1000 個水滴表明當前這一個請求。就能夠計算出當前桶的剩餘水滴數。

excess 表示上一次超出的水滴數(延遲經過),一開始是 0 。特別地,若是 excess<0,說明這個桶空了,就會把 excess 重置爲 0 ;若是 excess>0,說明這個桶有水滴堆積,這時水滴的流入速度比它的流出速度快了,返回 BUSY,表示繁忙。經過這樣動態的標記就能夠把這個速率給控制起來。

前面提到的突發,只要把這裏的 0 換成 4 ,就是容許必定程度的突發了。

令牌桶限速

令牌桶和漏桶從一些特殊的角度(特別是從效果)上是有一些類似的,可是它們在設計思想上有比較明顯的差別。

 

△ 令牌桶

令牌桶是指令牌以必定的速率往桶裏進令牌,進來的請求是恆定的速率來補充這個桶,只要桶沒有滿就能夠一直往裏面放,若是是補充滿了就不會再補充了。每處理一個請求就從令牌桶拿出一塊,若是沒有令牌能夠拿那麼請求就沒法往下走。

 

△ lua-resty-limit-rate (resty.limit.rate)

lua-resty-limit-rate(resty.limit.rate)是又拍雲最近開源的一個庫,基於令牌桶實現。

上圖是個簡化的演示,首先申請兩個令牌桶,一個是全局的令牌桶,一個是針對某個用戶的令牌桶,由於系統內確定有不少用戶調用,全局是一個桶,每一個用戶是一個桶,能夠作一個組合的設置。若是全局的桶沒有滿,單個用戶超過了用戶單獨的頻次限制,咱們通常會容許其突發,後端對於處理 A 用戶、B 用戶的消耗通常是相同的,只是業務邏輯上分了 A 用戶和 B 用戶。

所以,總體容量沒有超過限制,單個用戶即使超過了他的限制配置,也容許他突發。只有全局桶拿不出令牌,此時再來判斷每一個用戶的桶,看是否能夠拿出令牌,若是它拿不出來了就拒絕掉。此時總體系統達到瓶頸,爲了用戶體驗,咱們不可能無差異地去彈掉任意用戶的請求,而是挑出當前突發較大的用戶將其請求拒絕而保障其餘正常的用戶請求不受任何影響,這是基於用戶體驗的角度來考慮限速的方案配置。

相比 limit.req 基於漏桶的設計,令牌桶的思想更關注容量的變化,而非相鄰請求間的速率的限制,它適合有必定彈性容量設計的系統,只有在全局資源不夠的時候纔去作限制,而非兩個請求之間頻率超了就限制掉,速率容許有較⼤大的波動。

相比 limit.count 對單位窗口時間內累計請求數量進行限制,該模塊在特定配置下,也能達到相似效果,而且能避免在單位時間窗口切換瞬間致使可能雙倍的限制請求狀況出現。 limit.count 模塊在單位時間內,好比在 1 分鐘內限制 100 次,在下一個 1 分鐘統計時,上一個 1 分鐘統計的計數是清零的,固定的時間窗口在切換的時候,在這個切換的瞬間,可能前 1 分鐘的最後 1 秒上來了 99 個請求,下一個 1 分鐘的第 1 秒上來 99 個請求,在這 2 秒內,它超過了設計的單位時間最多 100 個請求的限制,它的切換瞬間會有一些邊界的重疊。而基於令牌桶後,由於它的流入流出有一個桶的容量在保護,因此它切換是比較平滑的,流入速度和流出速度中間有一個緩衝。

除了請求速率限制(一個令牌一個請求),還可以對字節傳輸進行流量整形,此時,一個令牌至關於一個字節。由於流量都是由一個個字節組成的。若是把字節變成令牌,那流量的流出流入也能夠經過令牌桶來給流量作一些整形。整形就是流量按你指望設計的形狀帶寬(單位時間內的流量)進行傳輸。

OpenResty 動態流控在又拍雲的業務應用

  • 海外代理進行上傳流量整形,避免跑滿傳輸線路帶寬(流量整形);
  • 某 API 請求基於令牌桶針對不一樣帳戶進行請求速率控制(令牌桶應用);
  • CDN 特性:IP 訪問限制,支持階梯策略升級(IP訪問限制);
  • CDN 特性:碼率適配限速

又拍雲和 KONG

 

 

KONG 是一個很是著名的 OpenResty 的應用,又拍雲在 2018 年在網關層引入了 KONG ,內部也維護了一個 KONG 的 Fork 版本,作了一些插件的改造和適配。

流量整形

咱們在 KONG 上怎麼去作流量呢?由於香港到國內數據中心的傳輸線路價格很是昂貴,咱們購買線路帶寬是有必定限制的。可是咱們在這條線路傳輸有不少 API ,若是有一個 API 突發流量,就會影響到其餘,因此咱們在 KONG 上作了改造。

 

KONG 的設計不容許管控請求的 socket 字節流,也是用 Nginx 的核心模塊來轉發字節流,咱們須要去管控全部從 req socket 進來的字節流,由於要作字節流限制,因此咱們這裏用純 Lua 接管了。

Lua 接管以後,能夠看到每 8192 個字節,都會拿 8192 個令牌,若是能拿出來,就讓這 8192 個字節日後端傳;若是拿不出來,說明當時已經日後傳太多字節了,就讓它等一等,起到一些限制效果。

令牌桶應用

 

咱們在某一個 API 系統中用令牌桶怎麼作策略的限制呢?上圖是一個簡單的配置示例,咱們針對全局有一個桶,一個令牌的添加速度是 40r/s,令牌的容量是 12000,每次是 4 個令牌一塊兒添加,這是全局桶的策略;每一個用戶空間的策略是:桶的容量是 6000,每次 2 個令牌一塊兒添加,它的限制大概是 10r/s ;對於一些特殊的操做,好比 delete,咱們會限制得更加嚴格一點,引入了第三個,專門針對 delete 操做的桶。

因此這裏能夠有好多桶來配合,全局的,局部的以及特殊的操做,你們的限制等級都不太同樣,策略均可以靈活去配置。

 

△ 限制效果圖

上圖是咱們實際的限制效果,藍色部分是經過令牌桶屏蔽掉的,綠色的是健康的,這部分被彈的,看業務數據的話,不是任意空間被彈掉,它被彈的時候都是那麼幾個空間被彈掉,會比較集中那幾個空間,特別出頭的被彈掉。而不是說一大堆的空間,甚至請求流量很小的,你隨機去彈幾個。確定要挑出那些搗亂的把它彈掉,從而保護整個後端的請求能維持在一個健康的水位下。

IP 訪問限制

 

△ IP 訪問限制

又拍雲的產品中有一個 IP 訪問的限制的功能,針對單位時間內的 IP 進行頻率的保護。當你的網站或者靜態資源被一些惡意的 IP 瘋狂下載,浪費你不少流量的時候是有幫助的。並且咱們支持階梯的配置,達到第一個階梯禁止多少時間,若是繼續達到第二個階梯,階梯升級禁用的力度就會更大。

碼率適配限速

 

△ 碼率適配限速

針對視頻播放,咱們須要對碼率進行適配。這個功能能夠動態讀取 MP4 的元數據,讀到它的碼率狀況,從而作出相應的下載帶寬控制的策略,使得這個文件在播放的時候看到的是很流暢的,用戶體驗沒有受到任何影響,可是不會由於客戶端網速較快而多浪費流量資源。這是下載帶寬限速,結合實際應用的一個例子。

 

分享視頻及PPT可前往:

OpenResty 動態流控的幾種姿式 - 又拍雲

相關文章
相關標籤/搜索