【限流-】高併發限流+分佈式限流

文案摘抄自網絡與同事分享。

一、爲何要限流:

在開發高併發系統時有三把利器用來保護系統:緩存、降級和限流。本文結合做者的一些經驗介紹限流的相關概念、算法和常規的實現方式。redis

緩存

緩存比較好理解,在大型高併發系統中,若是沒有緩存數據庫將分分鐘被爆,系統也會瞬間癱瘓。使用緩存不僅僅可以提高系統訪問速度、提升併發訪問量,也是保護數據庫、保護系統的有效方式。大型網站通常主要是「讀」,緩存的使用很容易被想到。在大型「寫」系統中,緩存也經常扮演者很是重要的角色。好比累積一些數據批量寫入,內存裏面的緩存隊列(生產消費),以及HBase寫數據的機制等等也都是經過緩存提高系統的吞吐量或者實現系統的保護措施。甚至消息中間件,你也能夠認爲是一種分佈式的數據緩存。算法

降級

服務降級是當服務器壓力劇增的狀況下,根據當前業務狀況及流量對一些服務和頁面有策略的降級,以此釋放服務器資源以保證核心任務的正常運行。降級每每會指定不一樣的級別,面臨不一樣的異常等級執行不一樣的處理。根據服務方式:能夠拒接服務,能夠延遲服務,也有時候能夠隨機服務。根據服務範圍:能夠砍掉某個功能,也能夠砍掉某些模塊。總之服務降級須要根據不一樣的業務需求採用不一樣的降級策略。主要的目的就是服務雖然有損可是總比沒有好。數據庫

限流

限流能夠認爲服務降級的一種,限流就是限制系統的輸入和輸出流量已達到保護系統的目的。通常來講系統的吞吐量是能夠被測算的,爲了保證系統的穩定運行,一旦達到的須要限制的閾值,就須要限制流量並採起一些措施以完成限制流量的目的。好比:延遲處理,拒絕處理,或者部分拒絕處理等等。緩存

 

 

常見算法:服務器

1.計數器法:

原理:網絡

系統維護一個計數器,來一個請求就加1,請求解決完成就減1,當計數器大於指定的閾值,就拒絕新的請求。數據結構

基於這個簡單的方法,能夠再延伸出少許高級功能,比方閾值能夠不是固定值,是動態調整的。另外,還能夠有多組計數器分別管理不一樣的服務,以保證互不影響等。併發

缺點:app

惡意用戶經過在時間窗口的重置節點處突發請求, 能夠瞬間超過咱們的速率限制。用戶有可能經過算法的這個漏洞,瞬間壓垮咱們的應用。分佈式

二、隊列:

就是基於FIFO隊列,全部請求都進入隊列,後臺程序從隊列中取出待解決的請求依次解決。

基於隊列的方法,也能夠延伸出更多的玩法來,比方能夠設置多個隊列以配置不一樣的優先級。

三、滑動窗口,又稱rolling window(隊列的升級版)

好比某個服務最多隻能每秒鐘處理100個請求。咱們能夠設置一個1秒鐘的滑動窗口,窗口中有10個格子,每一個格子100毫秒,每100毫秒移動一次,每次移動都須要記錄當前服務請求的次數。內存中須要保存10次的次數。能夠用數據結構LinkedList來實現。格子每次移動的時候判斷一次,當前訪問次數和LinkedList中最後一個相差是否超過100,若是超過就須要限流了。

當滑動窗口的格子劃分的越多,那麼滑動窗口的滾動就越平滑,限流的統計就會越精確。

這種模式的實現的方式更加契合流控的本質意義,理解較爲簡單。但因爲訪問量的不可預見性,會發生單位時間的前半段大量請求涌入,然後半段則拒絕全部請求的狀況(一般,須要能夠將單位時間切的足夠的小來緩解);其次,很難肯定這個閾值設置在多少比較合適,只能經過經驗或者模擬(如壓測)來進行估計,即便是壓測也很難估計的準確。集羣部署中每臺機器的硬件參數不一樣,可能致使須要對每臺機器的閾值設置的都不盡相同。同一臺機子在不一樣的時間點的系統壓力也不同(好比晚上還有一些任務,或其餘的一些業務操做的影響),可以承受的最大閾值也不盡相同,沒法考慮的周全。

  因此滑窗模式一般適用於對某一資源的保護的需求上,如對db的保護,對某一服務的調用的控制上。

四、漏桶算法

漏桶(Leaky Bucket)算法思路很簡單,(請求)先進入到漏桶裏,漏桶以必定的速度出水(接口有響應速率),當水流入速度過大會直接溢出(訪問頻率超過接口響應速率),而後就拒絕請求,能夠看出漏桶算法能強行限制數據的傳輸速率。

由於漏桶的漏出速率是固定的參數,因此,即便網絡中不存在資源衝突(沒有發生擁塞),漏桶算法也不能使流突發(burst)到端口速率.所以,漏桶算法對於存在突發特性的流量來講缺少效率

五、令牌桶算法

首先仍是要基於一個隊列,請求放到隊列裏面。但除了隊列之外,還要設置一個令牌桶,另外有一個腳本以持續恆定的速度往令牌桶裏面放令牌,後臺解決程序每解決一個請求就必需從桶裏拿出一個令牌,假如令牌拿完了,那就不能解決請求了。咱們能夠控制腳本放令牌的速度來達到控制後臺解決的速度,以實現動態流控。

1.每秒會有 r 個令牌放入桶中,或者說,每過 1/r 秒桶中增長一個令牌

2.桶中最多存放 b 個令牌,若是桶滿了,新放入的令牌會被丟棄

3.當一個 n 字節的數據包到達時,消耗 n 個令牌,而後發送該數據包

4.若是桶中可用令牌小於 n,則該數據包將被緩存或丟棄

Google開源工具包Guava提供了限流工具類RateLimiter,該類基於令牌桶算法(Token Bucket)來完成限流, 將一秒鐘切割爲令牌數的時間片斷,每一個時間片斷等同於一個token。很是易於使用.RateLimiter常常用於限制對一些物理資源或者邏輯資源的訪問速率.

六、分佈式

實際生產環境下最快捷且有效的方式是使用RateLimiter實現,可是這很容易踩到一個坑單節點模式下,使用RateLimiter進行限流一點問題都沒有。但線上是分佈式系統,佈署了多個節點,並且多個節點最終調用的是同一個API/服務商接口。雖然咱們對單個節點能作到將QPS限制在400/s,可是多節點條件下,若是每一個節點均是400/s,那麼到服務商那邊的總請求就是節點數x400/s,因而限流效果失效。使用該方案對單節點的閾值控制是難以適應分佈式環境的

方式一:redis

@GetMapping("/")
public void index(HttpServletResponse response) throws IOException {
    Jedis jedis = jedisPool.getResource();
    String token = RedisRateLimiter.acquireTokenFromBucket(jedis, LIMIT, TIMEOUT);
    if (token == null) {
        response.sendError(500);
    }else{
        //TODO 你的業務邏輯
    }
    jedisPool.returnResource(jedis);
}

方式二:方式二 Reids+Lua腳本 (保證操做的原子性)

local key = "rate.limit:" .. KEYS[1]
local limit = tonumber(ARGV[1])
local expire_time = ARGV[2]

local is_exists = redis.call("EXISTS", key)
if is_exists == 1 then
    if redis.call("INCR", key) > limit then
        return 0
    else
        return 1
    end
else
    redis.call("SET", key, 1)
    redis.call("EXPIRE", key, expire_time)
    return 1
end
相關文章
相關標籤/搜索