開放API網關實踐(三) —— 限流

如何設計實現一個輕量的開放API網關之限流java

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

前言

開發高併發系統時有多重系統保護手段, 如緩存、限流、降級等. 在網關層, 限流的應用比較普遍. 不少狀況下咱們能夠認爲網關上的限流與業務沒有很強的關聯(與系統的承載能力有關), 且各個子系統都有限流這種需求, 將部分限流功能放到網關會比較合適.算法

什麼是限流

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

舉個栗子🌰, 節假日網上購票, 經常會遇到排隊中系統繁忙請稍後再試等提示, 這即是服務端對單位時間處理請求的數量進行了限制, 超出限制就會排隊、降級甚至拒絕服務, 不然若是把系統搞崩了, 你們都買不到票了╮( ̄▽ ̄)╭.緩存

12306系統繁忙

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

其最終目的是: 在扛不住太高併發的狀況下作到有損服務而不是不服務.服務器

經常使用限流玩法

令牌桶

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

令牌桶算法

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

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

漏桶

漏桶是一個底部破洞的桶, 水能夠勻速流出(這時候不考慮壓強, 不要槓( ̄. ̄)), 因此與令牌桶不同的是, 漏桶算法是勻速消費, 能夠用來進行流量整形流量控制. 如圖:分佈式

漏桶算法

  • 固定容量的漏桶, 按照固定速率流出水(不要槓水深和壓強的問題).
  • 流入水的速率固定, 溢出則被丟棄.
  • 特色: 平滑處理速率.
[版權聲明]
本文發佈於 樸瑞卿的博客, 容許非商業用途轉載, 但轉載必須保留原做者 樸瑞卿 及連接: 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集羣)

結語

網關做爲內部系統外的一層屏障, 對內起到必定的保護做用, 限流即是其中之一. 網關層的限流能夠簡單地針對不一樣業務的接口進行限流, 也可考慮將限流功能作成網關的一個功能模塊(如限流規則的配置、統計、針對用戶維度進行統計和限流等)

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

系列文章:

歡迎關注公衆號:

[版權聲明]
本文發佈於 樸瑞卿的博客, 容許非商業用途轉載, 但轉載必須保留原做者 樸瑞卿 及連接: blog.piaoruiqing.com. 若有受權方面的協商或合做, 請聯繫郵箱: piaoruiqing@gmail.com.
相關文章
相關標籤/搜索