滑動視窗限流
滑動視窗限流是一種常用的限流演算法,透過維護一個固定大小的視窗,在單位時間內允許透過的請求次數不超過設定的閾值。具體來說,滑動視窗限流演算法通常包括以下幾個步驟:
- 初始化:設定視窗大小、請求次數閾值和時間間隔。
- 維護視窗:將請求按照時間順序放入視窗中,並保持視窗內請求數量不超過閾值。
- 檢查透過:每當有新的請求到達時,檢查視窗內請求的總數是否超過閾值,如果未超過則允許透過,同時移除視窗最老的請求。
- 更新視窗:隨著時間的推移,更新視窗內的請求情況,確保視窗內的請求符合限流條件。
滑動視窗限流演算法可以有效控制系統的請求流量,避免系統被大量請求壓垮。同時,由於其簡單高效的特點,被廣泛應用於介面限流、流量控制等場景中。需要注意的是,滑動視窗限流演算法對於突發請求並不能完全解決問題,因此在實際應用中可能需要結合其他策略進行綜合考慮。
基於redis-zset實現的滑動視窗演算法流程
核心程式碼
/**
* 滑動視窗限流. 需要注意的是,我們要定期清楚過期的key,否則會導致記憶體洩漏,可以使用ZREMRANGEBYSCORE方法實現.
* @param key 限流的key
* @param timeWindow 單位時間,秒
* @param limit 視窗大小,單位時間最大容許的令牌數
* @param runnable 成功後的回撥方法
*/
public void slidingWindow(String key, int timeWindow, int limit, Runnable runnable) {
Long currentTime = System.currentTimeMillis();
if (redisTemplate.hasKey(key)) {
Long intervalTime = timeWindow * 1000L;
Long from = currentTime - intervalTime;
Integer count = redisTemplate.opsForZSet().rangeByScore(key, from, currentTime).size();
if (count != null && count >= limit) {
throw new RedisLimitException("每" + timeWindow + "秒最多隻能訪問" + limit + "次.");
}
log.info("from key:{}~{},current count:{}", from, currentTime, count);
}
redisTemplate.opsForZSet().add(key, UUID.randomUUID().toString(), currentTime);
Optional.ofNullable(runnable).ifPresent(o -> o.run());
}
上面實現了一個基於時間戳為主要視窗依據的滑動視窗限流邏輯,由於zset的資料量會隨著時間的流失而變大,所以我們需要定期再根據score來清理它。
/**
* 清期昨天的zset元素,這塊應該寫個任務排程,每天執行一次,清量需要的zset元素.
* @param key
*/
public void delByYesterday(String key) {
Instant currentInstant = Instant.now();
Instant oneDayAgoInstant = currentInstant.minusSeconds(86400);
long oneDayAgoTimeMillis = oneDayAgoInstant.toEpochMilli();
redisTemplate.opsForZSet().removeRangeByScore(key, 0, oneDayAgoTimeMillis);
}
上面程式碼邏輯,事實上,我們可以透過其它語言去實現,比較透過go可以實現相關的邏輯,從新可以在MSE閘道器上實現限流功能。