【笔记】Redis深度历险-------使用Redis来实现简单限流策略

12 篇文章 0 订阅

问题描述

限流的目的主要是控制用户行为,避免垃圾请求,比如在一些社区论坛中,用户的发帖,回复、点赞等行为都要严格受控。一般要严格限定某行为在规定时间内被运行的次数,超过了次数就是非法行为。对非法行为做相应的处理。
一般在应用场景中,会限制用户的某个行为在规定的时间内只能允许发生N次。

解决方案

使用滑动时间窗口(定宽),只需要保留这个时间窗口,窗口之外的数据都可以砍掉。zset中的value没有什么实际意义,只需要保证唯一性即可。

如果是冷用户滑动时间窗口内的行为是空记录,那么这个zset就可以从内存中移除,不再占用空间。

代码实现

SimpleRateLimiter.java

package com.ryz2593.happy.study.redis;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.Response;
import java.io.IOException;

/**
 * 使用Redis实现简单限流策略
 * 使用滑动时间窗口(定宽),只需要保留这个时间窗口,窗口之外的数据都可以砍掉。
 * 如果是冷用户滑动时间窗口内的行为是空记录,那么这个zset就可以从内存中移除,不再占用空间。
 * @author ryz2593
 */
public class SimpleRateLimiter {
    private Jedis jedis;

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

    /**
     * @param userId
     * @param actionKey
     * @param period
     * @param maxCount
     * @return 当前的行为是否被允许
     * @throws IOException
     */
    public boolean isActionAllowed(String userId, String actionKey, int period, int maxCount) throws IOException {
        String key = String.format("hist:%s:%s", userId, actionKey);
        long nowTs = System.currentTimeMillis();
        Pipeline pipe = jedis.pipelined();
        pipe.multi();
        //value 和 score 都使用毫秒时间戳
        pipe.zadd(key, nowTs, "" + nowTs);
        //移除时间窗口之前的行为记录,剩下的都是时间窗口内的
        pipe.zremrangeByScore(key, 0, nowTs - period * 1000);
        //获取窗口内的行为数量
        Response<Long> count = pipe.zcard(key);
        //设置zset过期时间,避免冷用户持续占用内存
        //过期时间应该等于时间窗口的长度,再多宽限1s
        pipe.expire(key, period + 1);
        pipe.exec();
        pipe.close();
        //比较数量是否超过允许的最大值
        return count.get() <= maxCount;
    }

    public static void main(String[] args) throws IOException {
        Jedis jedis = new Jedis("localhost");
        SimpleRateLimiter limiter = new SimpleRateLimiter(jedis);
        for (int i = 0; i < 20; i++) {
            System.out.println(limiter.isActionAllowed("ryz2593", "click", 60, 5));
        }
    }
}

运行结果

true
true
true
true
true
true
false
false
false
false
false
false
false
false
false
false
false
false
false
false

整体思路

每一个行为到来时,都维护一次时间窗口。将时间窗口之外的记录全部清理掉,只保留窗口内的记录。 zset集合中只有score值非常重要,value值没有特别的意义,只需要保证唯一的就可以。

因为这几个连续的Redis操作都是针对同一个key的,使用pipeline可以显著提升Redis的存取效率。

但这种方案也有缺点,因为要记录时间窗口内所有的行为记录,如果这个量很大,比如“限定1min内操作不超过10万次”之类,它是不适合做这样的限流的,因为会消耗大量的存储空间。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值