這種限制接口調用次數的方式,我們通常稱之為限流,那么為什么要做限流呢,一般有兩種原因:
1. 首先是防止服務提供方被大量的請求擊垮
我們開發一個項目,最理想的狀況是有多少請求,都可以正常地響應,但是在現在的互聯網環境,我們很難評估用戶的增長,很難評估訪問量有多少,甚至有些時候會遇到惡意攻擊;那么相比于項目被流量擊垮,【限制流量,只滿足部分訪問的正常響應】要好一些。
簡單說就是:滿足所有請求 > 滿足部分請求 > 項目被擊垮,所有請求無法響應。
2. 計費
現在很多平臺對外開發的接口,并不全是免費的,比如普通會員每天只能調用 1000 次接口,高級會員每天可以調用 10 萬次接口,或者按照調用量計費。

那么如何限制服務接口的調用次數呢?
使用限流算法
通常我們可以通過限流算法達到限制接口調用次數,比如計數器法、滑動窗口法、漏桶算法、令牌桶算法,這里我們就用令牌桶算法舉例。
令牌桶算法,我們可以看做有一個桶,桶里面有 N 個令牌,并且系統會以一個恒定的速度往桶里投放令牌,每次處理之前先要獲取令牌,如果獲取不到的話,就拒絕服務;在這里我們使用 google 出品的 Guava 工具庫,里面提供了一個開箱即用的令牌桶 RateLimiter。
如圖,我們編寫了一個簡單的接口,省略了業務邏輯,只返回一個字符串;我們設置 RateLimiter.create(2),表示每秒不超過 2 個任務被提交。

讓我們用接口工具模擬一下并發調用:

他強任他強,我自巍然不動。因為我們使用了限流算法,每秒只處理 2 個請求,所以從日志中我們可以看到這樣的效果:每秒只有兩條日志。

分布式架構下的限流
因為使用開源的組件,限流的實現看起來非常簡單,但是這里也有一個比較大的問題,就是實例中是一個應用包,但在實際的項目中,我們通常會是用集群部署的方式,將我們的應用部署在多臺機器上,那么這時候該如何限流呢?
每臺服務器上的應用自己控制自己的響應數量?比如每天只能調 100 次,那部署 10 臺的話,總量就變成了 1000 次了;
反推?因為每天總量只能調 100 次,部署 10 臺,那就是每臺每天只能調 10 次?這是個很差的辦法,先不說流量一定可以平均分配到每臺機器上,如果有一臺機器掛掉了,是不是今天只能支持調用 90 次了?
通常的解決方案,可以把令牌桶中的令牌,不要放在本地,而是放在一個公共的地方,比如 redis 中,每次請求過來,就計算是否超過限制的總量,如果未超過,則正常處理,如果已超過,則返回錯誤信息。

具體做法是,用 Redis 中的 key-100 作為令牌桶,其中 100 表示一分鐘可以調用 100 次,每次處理前對 value 進行減 1,返回的值大于 0 表示可以處理;每分鐘將 value 設置回 100;或計數累加,開始是 0 ,不斷累加,最后超過單位時間的總量限制;
不過這個方法要有一個定時任務,去設置令牌的數量,另外這種方法是不能應對突發流量的,比如前 59 秒一次請求也沒有,第 60 秒來了 100 次,第 61 秒進入了一個新的周期,又來了 100 次請求 ,這樣實際上是在兩秒內處理了 200 次請求。
另外一種方案是使用 Redis 中的有序隊列 Sorted Set ,存儲近 100 次的調用時間,每次有新請求的時候,對比隊列中第一個元素的時間和當前時間,如果相差超過 1 分鐘,表示還沒有超過流量限制,進行處理,并將第一個元素壓出隊列,將新的請求時間壓入隊列。






