開放API閘道器實踐(三) —— 限流

草堂箋發表於2019-08-25

如何設計實現一個輕量的開放API閘道器之限流

文章地址: blog.piaoruiqing.com/2019/08/26/…

前言

開發高併發系統時有多重系統保護手段, 如快取、限流、降級等. 在閘道器層, 限流的應用比較廣泛. 很多情況下我們可以認為閘道器上的限流與業務沒有很強的關聯(與系統的承載能力有關), 且各個子系統都有限流這種需求, 將部分限流功能放到閘道器會比較合適.

什麼是限流

眾所周知, 伺服器、網站應用的處理能力是有上限的, 不論配置有多高總會有一個極限, 超過極限如果放任繼續接收請求, 可能會發生不可控的後果.

舉個例子?, 節假日網上購票, 常常會遇到排隊中系統繁忙請稍後再試等提示, 這便是服務端對單位時間處理請求的數量進行了限制, 超出限制就會排隊、降級甚至拒絕服務, 否則如果把系統搞崩了, 大家都買不到票了╮( ̄▽ ̄)╭.

12306系統繁忙

我們先給出限流的定義: 限流是高併發系統保護保護手段之一, 在閘道器層的應用很廣泛. 其目的是對併發請求進行限速或限制一個時間視窗內請求的數量, 一旦達到閾值就排隊等待或降級甚至拒絕服務.

其最終目的是: 在扛不住過高併發的情況下做到有損服務而不是不服務.

常用限流玩法

令牌桶

令牌桶演算法, 是一個存放固定數量令牌的桶按照固定速率新增令牌. 如圖:

令牌桶演算法

  • 按照固定速率向桶中新增令牌.
  • 桶滿時拒絕增加新令牌.
  • 每次請求消耗一個令牌(也可根據資料包大小來消耗對應的令牌數).
  • 當令牌不足時, 拒絕請求(或等待).
  • 特點: 可以應對一定程度的突發.

舉個現實生活中比較常見的例子來理解, 電影院售票, 每場電影所售出的票數是一定的, 如果來晚了(後面的請求)就沒票了, 要麼等待下一場(等待新的令牌發放), 要麼不看了(被拒絕).

漏桶

漏桶是一個底部破洞的桶, 水可以勻速流出(這時候不考慮壓強, 不要槓( ̄. ̄)), 所以與令牌桶不一樣的是, 漏桶演算法是勻速消費, 可以用來進行流量整形流量控制. 如圖:

漏桶演算法

  • 固定容量的漏桶, 按照固定速率流出水(不要槓水深和壓強的問題).
  • 流入水的速率固定, 溢位則被丟棄.
  • 特點: 平滑處理速率.
[版權宣告]
本文釋出於樸瑞卿的部落格, 允許非商業用途轉載, 但轉載必須保留原作者樸瑞卿 及連結:blog.piaoruiqing.com. 如有授權方面的協商或合作, 請聯絡郵箱: piaoruiqing@gmail.com.

應用級限流

一個單體的應用程式有其承受極限, 在高併發情況下, 有必要進行過載保護, 以防過多的請求將系統弄崩. 最簡單粗暴的方式就是使用計數器進行控制, 處理請求時+1, 處理完畢後-1, 除此之外我們還可以利用前文提到的令牌桶和漏桶來進行更精細的限流.如果閘道器是單體應用, 我們完全可以不借助其他介質, 直接在應用級別進行限流.

計數器

這種方式實現最簡單粗暴,

try {
    if (counter.incrementAndGet() > limit) {
        throw new SomeException();
    }
    // do something
} finally {
    counter.decrementAndGet();
}
複製程式碼

令牌桶

Guava提供了令牌桶演算法的實現.

@Test
public void testGuavaRateLimiter() throws InterruptedException {
    RateLimiter limiter = RateLimiter.create(5);
    TimeUnit.SECONDS.sleep(1);	// 等待一秒鐘發幾個令牌
    for (int index = 0; index < 10; index++) {
        System.out.println(limiter.acquire()); // 列印等待時間
    }
}
複製程式碼

輸出為:

0.0
0.0
0.0
0.0
0.0
0.0
0.196108
0.194372
0.19631
0.198373
複製程式碼

在令牌用盡後, 後面的請求都要等待有新的令牌後才能繼續執行.

應用級限流實現簡單, 但其侷限性在於無法進行全侷限流, 對於叢集就無能為力了.

分散式限流

想要在叢集中進行全侷限流, 其關鍵在於將限流資訊記錄在共享介質中, 如Redismemcached等. 為了將限流做的精確, 寫必須是原子操作.

分散式限流

Redis+Lua是一個不錯的選擇, 示例Lua指令碼如下:

local key = KEYS[1] -- 限流的KEY
local limit = tonumber(ARGV[1])	-- 限流大小
local current = tonumber(redis.call('get', key) or '0')
if current + 1 > limit then
    return 0
else
    redis.call('INCRBY', key,'1')
    redis.call('expire', key,ARGV[2])	-- 過期時間
    return current + 1
end
複製程式碼
  • 分散式限流將令牌的發放放到共享介質中.
  • 獲取(消費)令牌操作必須是原子的.
  • 共享介質要高可用(Redis叢集)

結語

閘道器作為內部系統外的一層屏障, 對內起到一定的保護作用, 限流便是其中之一. 閘道器層的限流可以簡單地針對不同業務的介面進行限流, 也可考慮將限流功能做成閘道器的一個功能模組(如限流規則的配置、統計、針對使用者維度進行統計和限流等)

如果這篇文章對您有幫助,請點個贊吧 ( ̄▽ ̄)"

系列文章:

歡迎關注公眾號:

開放API閘道器實踐(三) —— 限流

[版權宣告]
本文釋出於樸瑞卿的部落格, 允許非商業用途轉載, 但轉載必須保留原作者樸瑞卿 及連結:blog.piaoruiqing.com. 如有授權方面的協商或合作, 請聯絡郵箱: piaoruiqing@gmail.com.

相關文章