演算法~利用zset實現滑動視窗限流

张占岭發表於2024-04-29

滑動視窗限流

滑動視窗限流是一種常用的限流演算法,透過維護一個固定大小的視窗,在單位時間內允許透過的請求次數不超過設定的閾值。具體來說,滑動視窗限流演算法通常包括以下幾個步驟:

  1. 初始化:設定視窗大小、請求次數閾值和時間間隔。
  2. 維護視窗:將請求按照時間順序放入視窗中,並保持視窗內請求數量不超過閾值。
  3. 檢查透過:每當有新的請求到達時,檢查視窗內請求的總數是否超過閾值,如果未超過則允許透過,同時移除視窗最老的請求。
  4. 更新視窗:隨著時間的推移,更新視窗內的請求情況,確保視窗內的請求符合限流條件。

滑動視窗限流演算法可以有效控制系統的請求流量,避免系統被大量請求壓垮。同時,由於其簡單高效的特點,被廣泛應用於介面限流、流量控制等場景中。需要注意的是,滑動視窗限流演算法對於突發請求並不能完全解決問題,因此在實際應用中可能需要結合其他策略進行綜合考慮。

基於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閘道器上實現限流功能。

相關文章