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

滑動窗口限流

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

  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網關上實現限流功能。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章