Redis實踐 利用Redis實現簡單限流


利用Redis來限流,可以限定用戶的某個行為在指定的時間里只能允許發生N次。

場景: 某個用戶在一秒內只能回復5次,那么利用Redis如何實現呢。

思路:這個限流需求中存在一個滑動時間窗口,我們可以聯想到zset數據結構的score值,我們可以通過score來圈出這個時間窗口來。而且我們只需要維護這個時間窗口,窗口之外的數據都可以砍掉。那這個zset 的value填什么比較合適呢?它只需要保證唯一性即可,用 uuid 會比較浪費空間,改用毫秒時間戳比較好。
圖如下:

now_ts是當前的毫秒時間戳,我們只需要維護[now_s-period,now_s]這段時間里用戶的操作數即可。
具體代碼:

public class SimpleRateLimiter {

    private final Jedis jedis;

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

    public boolean isActionAllow(String userId,String actionKey,int period,int maxCount) throws IOException {
        String key=String.format("hist6:%s:%s",userId,actionKey);
        long nowTs=System.currentTimeMillis();
        //毫秒時間戳
        Pipeline pipeline=jedis.pipelined();
        pipeline.multi();//用了multi,也就是事務,能保證一系列指令的原子順序執行
        //value和score都使用毫秒時間戳
        pipeline.zadd(key,nowTs,nowTs+"");
        //移除時間窗口之前的行為記錄,剩下的都是時間窗口內的
        pipeline.zremrangeByScore(key,0,nowTs-period*1000);
        //獲得[nowTs-period*1000,nowTs]的key數量
        Response<Long> count=pipeline.zcard(key);
        //每次設置都能保持更新key的過期時間
        pipeline.expire(key,period);
        pipeline.exec();
        pipeline.close();
        return count.get()<=maxCount;
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        Jedis jedis=new Jedis("localhost",6379);
        jedis.auth("iostream");
        SimpleRateLimiter limiter=new SimpleRateLimiter(jedis);
        for (int i = 0; i < 20; i++) {
            //每個用戶在1秒內最多能做五次動作
            System.out.println(limiter.isActionAllow("viscu","reply",1,5));
        }
    }
}

由於毫秒時間戳的精度問題,1ms內可能有執行好幾次操作,有zset的去重操作,所以會看到true出現了超過5次,說明還不夠精確。
我下面用了Thread.sleep(1)來模擬了不同操作的間的時間間隔 可是這種方法並不提倡

public class SimpleRateLimiter {

    private final Jedis jedis;

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

    public boolean isActionAllow(String userId,String actionKey,int period,int maxCount) throws IOException {
        String key=String.format("hist6:%s:%s",userId,actionKey);
        long nowTs=System.currentTimeMillis();
        //毫秒時間戳
        Pipeline pipeline=jedis.pipelined();
        pipeline.multi();
        //value和score都使用毫秒時間戳
        pipeline.zadd(key,nowTs,nowTs+"");
        //移除時間窗口之前的行為記錄,剩下的都是時間窗口內的
        pipeline.zremrangeByScore(key,0,nowTs-period*1000);
        //獲得[nowTs-period*1000,nowTs]的key數量
        Response<Long> count=pipeline.zcard(key);
        //每次設置都能更新key的過期時間
        pipeline.expire(key,period);
        pipeline.exec();
        pipeline.close();
        return count.get()<=maxCount;
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        Jedis jedis=new Jedis("localhost",6379);
        jedis.auth("iostream");
        SimpleRateLimiter limiter=new SimpleRateLimiter(jedis);
        while (true){
            Thread.sleep(1000); //這里模擬每次經過1s限流之后  "viscu"這個用戶就可以重新進行"reply"行為的操作。 
            for (int i = 0; i < 20; i++) {
                //模擬每個動作之間的間隔時間為1ms 具體看情況8 這里只是簡單模擬一下。
                Thread.sleep(1);
                //這樣就確保了該用戶在1秒內最多能做五次動作
                System.out.println(limiter.isActionAllow("viscu","reply",1,5));
            }
        }
    }

}

或者我們可以使用納秒這種更加精確的數或者加上隨機數:

public class SimpleRateLimiter {

    private final Jedis jedis;

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

    public boolean isActionAllow(String userId,String actionKey,int period,int maxCount) throws IOException {
        String key=String.format("hist6:%s:%s",userId,actionKey);
        long nowTs=System.nanoTime();
        //納秒時間戳
        Pipeline pipeline=jedis.pipelined();
        pipeline.multi();
        //value和score都使用納秒時間戳
        pipeline.zadd(key,nowTs,nowTs+"");    
        pipeline.zremrangeByScore(key,0,nowTs-period*1000*1000*1000);     
        Response<Long> count=pipeline.zcard(key);      
        pipeline.expire(key,period);
        pipeline.exec();
        pipeline.close();
        return count.get()<=maxCount;
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        Jedis jedis=new Jedis("localhost",6379);
        jedis.auth("iostream");
        SimpleRateLimiter limiter=new SimpleRateLimiter(jedis); 
        for (int i = 0; i < 20; i++) {       
            System.out.println(limiter.isActionAllow("viscu","reply",1,5));
        }
    }

}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM