Redis 實(shí)現(xiàn)滑動窗口

1、前言

一般我們做在指定時間內(nèi)只允許做 n 次都用,一個 key 設(shè)置過期時間 t 秒,然后在 key 過期時間內(nèi)只需要做 n 次。然而這個思路有問題,最明顯的就是跨時間段的問題。所以這個問題很顯然用滑動窗口來做。

指定時間T內(nèi),只允許發(fā)生N次。我們可以將這個指定時間T,看成一個滑動時間窗口(定寬)。我們采用Redis的zset基本數(shù)據(jù)類型的score來圈出這個滑動時間窗口。在實(shí)際操作zset的過程中,我們只需要保留在這個滑動時間窗口以內(nèi)的數(shù)據(jù),其他的數(shù)據(jù)不處理即可。

  • 每個用戶的行為采用一個zset存儲,score為毫秒時間戳,value也使用毫秒時間戳(比UUID更加節(jié)省內(nèi)存)
  • 只保留滑動窗口時間內(nèi)的行為記錄,如果zset為空,則移除zset,不再占用內(nèi)存(節(jié)省內(nèi)存)

2、代碼

package com.example.demo.redis;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.Response;

import java.io.IOException;

/**
 * <p>
 *     通過zset實(shí)現(xiàn)滑動窗口算法限流
 * </p>
 *
 */
public class SimpleSlidingWindowByZSet {

    private Jedis jedis;

    public SimpleSlidingWindowByZSet(Jedis jedis) {
        this.jedis = jedis;
    }

    /**
     * 判斷行為是否被允許
     *
     * @param userId        用戶id
     * @param actionKey     行為key
     * @param period        限流周期
     * @param maxCount      最大請求次數(shù)(滑動窗口大小)
     * @return
     */
    public boolean isActionAllowed(String userId, String actionKey, int period, int maxCount) throws IOException {
        String key = this.key(userId, actionKey);
        long ts = System.currentTimeMillis();
        Pipeline pipe = jedis.pipelined();
        pipe.multi();

        // 每個用戶一個 zset,這里是 user + key 組合
        pipe.zadd(key, ts, String.valueOf(ts));
        // 移除滑動窗口之外的數(shù)據(jù)
        pipe.zremrangeByScore(key, 0, ts - (period * 1000));
        // 獲取窗口內(nèi)的數(shù)量
        Response<Long> count = pipe.zcard(key);
        // 設(shè)置行為的過期時間,如果數(shù)據(jù)為冷數(shù)據(jù),zset將會刪除以此節(jié)省內(nèi)存空間(不是必須,算是優(yōu)化)
        pipe.expire(key, period);
        pipe.exec();
        pipe.close();
        return count.get() <= maxCount;
    }


    /**
     * 限流key
     *
     * @param userId
     * @param actionKey
     * @return
     */
    public String key(String userId, String actionKey) {
        return String.format("limit:%s:%s", userId, actionKey);
    }

}
public class FirstTool {

    static JedisPool pool = null;

    static {
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxIdle(8);
        config.setMaxTotal(18);
        pool = new JedisPool(config, "127.0.0.1", 6379, 2000);
    }

    public static void main(String[] args) throws Exception {
        Jedis jedis = pool.getResource();

        SimpleSlidingWindowByZSet slidingWindow = new SimpleSlidingWindowByZSet(jedis);
        for (int i = 1; i <= 15; i++) {
            boolean actionAllowed = slidingWindow.isActionAllowed("liziba", "view", 60, 5);

            System.out.println("第" + i +"次操作" + (actionAllowed ? "成功" : "失敗"));
            TimeUnit.MILLISECONDS.sleep(1);
        }


        jedis.close();
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容