如何設計實現一個輕量的開放API網關之限流java
文章地址: blog.piaoruiqing.com/2019/08/26/…redis
開發高併發系統時有多重系統保護手段, 如緩存、限流、降級等. 在網關層, 限流的應用比較普遍. 不少狀況下咱們能夠認爲網關上的限流與業務沒有很強的關聯(與系統的承載能力有關), 且各個子系統都有限流這種需求, 將部分限流功能放到網關會比較合適.算法
衆所周知, 服務器、網站應用的處理能力是有上限的, 不論配置有多高總會有一個極限, 超過極限若是聽任繼續接收請求, 可能會發生不可控的後果.api
舉個栗子🌰, 節假日網上購票, 經常會遇到排隊中
、系統繁忙請稍後再試
等提示, 這即是服務端對單位時間處理請求的數量進行了限制, 超出限制就會排隊、降級甚至拒絕服務, 不然若是把系統搞崩了, 你們都買不到票了╮( ̄▽ ̄)╭.緩存
咱們先給出限流
的定義: 限流
是高併發系統保護保護手段之一, 在網關層的應用很普遍. 其目的是對併發請求進行限速或限制一個時間窗口內請求的數量, 一旦達到閾值就排隊等待或降級甚至拒絕服務.bash
其最終目的是: 在扛不住太高併發的狀況下作到有損服務
而不是不服務.服務器
令牌桶算法, 是一個存放固定數量令牌的桶按照固定速率添加令牌. 如圖:markdown
舉個現實生活中比較常見的例子來理解, 電影院售票, 每場電影所售出的票數是必定的, 若是來晚了(後面的請求)就沒票了, 要麼等待下一場(等待新的令牌發放), 要麼不看了(被拒絕).併發
漏桶是一個底部破洞的桶, 水能夠勻速流出(這時候不考慮壓強, 不要槓( ̄. ̄)), 因此與令牌桶不同的是, 漏桶算法是勻速消費, 能夠用來進行流量整形
和流量控制
. 如圖:分佈式
一個單體的應用程序有其承受極限, 在高併發狀況下, 有必要進行過載保護, 以防過多的請求將系統弄崩. 最簡單粗暴的方式就是使用計數器進行控制, 處理請求時+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
複製代碼
在令牌用盡後, 後面的請求都要等待有新的令牌後才能繼續執行.
應用級限流實現簡單, 但其侷限性在於沒法進行全侷限流, 對於集羣就無能爲力了.
想要在集羣中進行全侷限流, 其關鍵在於將限流信息記錄在共享介質中, 如Redis
、memcached
等. 爲了將限流作的精確, 寫必須是原子操做.
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 複製代碼
網關做爲內部系統外的一層屏障, 對內起到必定的保護做用, 限流即是其中之一. 網關層的限流能夠簡單地針對不一樣業務的接口進行限流, 也可考慮將限流功能作成網關的一個功能模塊(如限流規則的配置、統計、針對用戶維度進行統計和限流等)
若是這篇文章對您有幫助,請點個贊吧 ( ̄▽ ̄)"
歡迎關注公衆號: