限流的常見算法有以下三種:
時間窗口算法
所謂的滑動時間算法指的是以當前時間爲截止時間,往前取一定的時間,比如往前取 60s 的時間,在這 60s 之內運行最大的訪問數爲 100,此時算法的執行邏輯爲,先清除 60s 之前的所有請求記錄,再計算當前集合內請求數量是否大於設定的最大請求數 100,如果大於則執行限流拒絕策略,否則插入本次請求記錄並返回可以正常執行的標識給客戶端。
滑動時間窗口如下圖所示:
其中每一小個表示 10s,被紅色虛線包圍的時間段則爲需要判斷的時間間隔,比如 60s 秒允許 100 次請求,那麼紅色虛線部分則爲 60s。
我們可以藉助 Redis 的有序集合 ZSet 來實現時間窗口算法限流,實現的過程是先使用 ZSet 的 key 存儲限流的 ID,score 用來存儲請求的時間,每次有請求訪問來了之後,先清空之前時間窗口的訪問量,統計現在時間窗口的個數和最大允許訪問量對比,如果大於等於最大訪問量則返回 false 執行限流操作,負責允許執行業務邏輯,並且在 ZSet 中添加一條有效的訪問記錄,具體實現代碼如下。
我們藉助 Jedis 包來操作 Redis,實現在 pom.xml 添加 Jedis 框架的引用,配置如下:
redis.clients jedis 3.3.0 具體的 Java 實現代碼如下:import redis.clients.jedis.Jedis;
public class RedisLimit {
// Redis 操作客戶端
static Jedis jedis = new Jedis(“127.0.0.1”, 6379);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 15; i++) {
boolean res = isPeriodLimiting("java", 3, 10);
if (res) {
System.out.println("正常執行請求:" + i);
} else {
System.out.println("被限流:" + i);
}
}
// 休眠 4s
Thread.sleep(4000);
// 超過最大執行時間之後,再從發起請求
boolean res = isPeriodLimiting("java", 3, 10);
if (res) {
System.out.println("休眠後,正常執行請求");
} else {
System.out.println("休眠後,被限流");
}
}
/**
* 限流方法(滑動時間算法)
* @param key 限流標識
* @param period 限流時間範圍(單位:秒)
* @param maxCount 最大運行訪問次數
* @return
*/
private static boolean isPeriodLimiting(String key, int period, int maxCount) {
long nowTs = System.currentTimeMillis(); // 當前時間戳
// 刪除非時間段內的請求數據(清除老訪問數據,比如 period=60 時,標識清除 60s 以前的請求記錄)
jedis.zremrangeByScore(key, 0, nowTs - period * 1000);
long currCount = jedis.zcard(key); // 當前請求次數
if (currCount >= maxCount) {
// 超過最大請求次數,執行限流
return false;
}
// 未達到最大請求數,正常執行業務
jedis.zadd(key, nowTs, "" + nowTs); // 請求記錄 +1
return true;
}
}
接下來我們分別看來。
1.時間窗口算法
所謂的滑動時間算法指的是以當前時間爲截止時間,往前取一定的時間,比如往前取 60s 的時間,在這 60s 之內運行最大的訪問數爲 100,此時算法的執行邏輯爲,先清除 60s 之前的所有請求記錄,再計算當前集合內請求數量是否大於設定的最大請求數 100,如果大於則執行限流拒絕策略,否則插入本次請求記錄並返回可以正常執行的標識給客戶端。
令牌算法思想是:
令牌以固定速率產生,並緩存到令牌桶中;
令牌桶放滿時,多餘的令牌被丟棄;
請求要消耗等比例的令牌才能被處理;
令牌不夠時,請求被緩存。
漏桶算法思想是:
水(請求)從上方倒入水桶,從水桶下方流出(被處理);
來不及流出的水存在水桶中(緩衝),以固定速率流出;
水桶滿後水溢出(丟棄)。
這個算法的核心是:緩存請求、勻速處理、多餘的請求直接丟棄。
相比漏桶算法,令牌桶算法不同之處在於它不但有一隻“桶”,還有個隊列,這個桶是用來存放令牌的,隊列纔是用來存放請求的
從作用上來說,漏桶和令牌桶算法最明顯的區別就是是否允許突發流量(burst)的處理,漏桶算法能夠強行限制數據的實時傳輸(處理)速率,對突發流量不做額外處理;而令牌桶算法能夠在限制數據的平均傳輸速率的同時允許某種程度的突發傳輸。
兩種算法基本上運用在網關服務器上,比如NGNIX,spring cloud zuul