Redis 限流器


Posted in Redis onMay 15, 2022

方法一:基于Redis的setnx的操作

我们在使用Redis的分布式锁的时候,大家都知道是依靠了setnx的指令,在CAS(Compare and swap)的操作的时候,同时给指定的key设置了过期实践(expire),我们在限流的主要目的就是为了在单位时间内,有且仅有N数量的请求能够访问我的代码程序。所以依靠setnx可以很轻松的做到这方面的功能。

比如我们需要在10秒内限定20个请求,那么我们在setnx的时候可以设置过期时间10,当请求的setnx数量达到20时候即达到了限流效果。代码比较简单就不做展示了。

当然这种做法的弊端是很多的,比如当统计1-10秒的时候,无法统计2-11秒之内,如果需要统计N秒内的M个请求,那么我们的Redis中需要保持N个key等等问题。

在具体实现的时候,可以考虑使用拦截器HandlerInterceptor :

public class RequestCountInterceptor implements HandlerInterceptor {

    private LimitPolicy limitPolicy;

    public RequestCountInterceptor(LimitPolicy limitPolicy) {
        this.limitPolicy = limitPolicy;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (!limitPolicy.canDo()) {
            return false;
        }
        return true;
    }
}

同时添加一个配置LimitConfiguration:

@Configuration
public class LimitConfiguration implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new RequestCountInterceptor(new RedisLimit1())).addPathPatterns("/my/increase");
    }
}

这样每次在/my/increase请求到达Controller之前按策略RedisLimit1进行限流,原先Controller里面的代码就不用修改了:

@RestController
@RequestMapping("my")
public class MyController {
    int i = 0;
    @RequestMapping("/increase")
    public int increase() {
        return i++;
    }
}

具体的限流逻辑代码是在RedisLimit1类中:

/**
* 方法一:基于Redis的setnx的操作
*/
public class RedisLimit1 extends LimitPolicy {

    static {
        setNxExpire();
    }

    private static boolean setNxExpire() {
        SetParams setParams = new SetParams();
        setParams.nx();
        setParams.px(TIME);
        String result = jedis.set(KEY, COUNT + "", setParams);


        if (SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }

    @Override
    public boolean canDo() {

        if (setNxExpire()) {
            //设置成功,说明原先不存在,成功设置为COUNT
            return true;
        } else {
            //设置失败,说明已经存在,直接减1,并且返回
            return jedis.decrBy(KEY, 1) > 0;
        }
    }
}

public abstract class LimitPolicy {
    public static final int COUNT = 10; //10 request
    public static final int TIME= 10*1000 ; // 10s
    public static final String SUCCESS = "OK";
    static Jedis jedis = new Jedis();
    abstract boolean canDo();
}

这样实现的一个效果是每秒最多请求10次。

方法二:基于Redis的数据结构zset

其实限流涉及的最主要的就是滑动窗口,上面也提到1-10怎么变成2-11。其实也就是起始值和末端值都各+1即可。
而我们如果用Redis的list数据结构可以轻而易举的实现该功能
我们可以将请求打造成一个zset数组,当每一次请求进来的时候,value保持唯一,可以用UUID生成,而score可以用当前时间戳表示,因为score我们可以用来计算当前时间戳之内有多少的请求数量。而zset数据结构也提供了zrange方法让我们可以很轻易的获取到2个时间戳内有多少请求

/**
* 方法二:基于Redis的数据结构zset
*/
public class RedisLimit2 extends LimitPolicy {
    public static final String KEY2 = "LIMIT2";

    @Override
    public boolean canDo() {
        Long currentTime = new Date().getTime();
        System.out.println(currentTime);
        if (jedis.zcard(KEY2) > 0) { // 这里不能用get判断,会报错:WRONGTYPE Operation against a key holding the wrong kind of value
            Integer count = jedis.zrangeByScore(KEY2, currentTime - TIME, currentTime).size(); // 注意这里使用zrangeByScore,以时间作为score。zrange key start stop 命令的start和stop是序号。
            System.out.println(count);
            if (count != null && count > COUNT) {
                return false;
            }
        }
        jedis.zadd(KEY2, Double.valueOf(currentTime), UUID.randomUUID().toString());
        return true;

    }
}

通过上述代码可以做到滑动窗口的效果,并且能保证每N秒内至多M个请求,缺点就是zset的数据结构会越来越大。实现方式相对也是比较简单的。

方法三:基于Redis的令牌桶算法

提到限流就不得不提到令牌桶算法了。令牌桶算法提及到输入速率和输出速率,当输出速率大于输入速率,那么就是超出流量限制了。也就是说我们每访问一次请求的时候,可以从Redis中获取一个令牌,如果拿到令牌了,那就说明没超出限制,而如果拿不到,则结果相反。
依靠上述的思想,我们可以结合Redis的List数据结构很轻易的做到这样的代码,只是简单实现 依靠List的leftPop来获取令牌。

首先配置一个定时任务,通过redis的list的rpush方法每秒插入一个令牌:

@Configuration      //1.主要用于标记配置类,兼备Component的效果。
@EnableScheduling   // 2.开启定时任务
public class SaticScheduleTask {
    //3.添加定时任务
    @Scheduled(fixedRate = 1000)
    private void configureTasks() {
        LimitPolicy.jedis.rpush("LIMIT3", UUID.randomUUID().toString());
    }
}

限流时,通过list的lpop方法从redis中获取对应的令牌,如果获取成功表明可以执行请求:

/**
* 方法三:令牌桶
*/
public class RedisLimit3 extends LimitPolicy {
    public static final String KEY3 = "LIMIT3";

    @Override
    public boolean canDo() {

        Object result = jedis.lpop(KEY3);
        if (result == null) {
            return false;
        }
        return true;
    }
}

到此这篇关于Redis实现限流器的三种方法(小结)的文章就介绍到这了,更多相关Redis 限流器内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!


Tags in this post...

Redis 相关文章推荐
redis限流的实际应用
Apr 24 Redis
Redis6.0搭建集群Redis-cluster的方法
May 08 Redis
Redis基于Bitmap实现用户签到功能
Jun 20 Redis
Redis中一个String类型引发的惨案
Jul 25 Redis
Redis RDB技术底层原理详解
Sep 04 Redis
聊聊redis-dump工具安装问题
Jan 18 Redis
Redis高可用集群redis-cluster详解
Mar 20 Redis
Redis基本数据类型哈希Hash常用操作命令
Jun 01 Redis
Redis批量生成数据的实现
Jun 05 Redis
Redis实现主从复制方式(Master&Slave)
Jun 21 Redis
一文教你快速生成MySQL数据库关系图
Jun 28 Redis
Redis高并发缓存架构性能优化
详解Redis的三种常用的缓存读写策略步骤
windows安装 redis 6.2.6最新步骤详解
muduo TcpServer模块源码分析
Redis数据同步之redis shake的实现方法
Apr 21 #Redis
Grafana可视化监控系统结合SpringBoot使用
Redis官方可视化工具RedisInsight安装使用教程
You might like
php中定时计划任务的实现原理
2013/01/08 PHP
php插入排序法实现数组排序实例
2015/02/16 PHP
laravel 自定义常量的两种方案
2019/10/14 PHP
TP5(thinkPHP5)框架使用ajax实现与后台数据交互的方法小结
2020/02/10 PHP
关于使用 jBox 对话框的提交不能弹出问题解决方法
2012/11/07 Javascript
javascript实现根据iphone屏幕方向调用不同样式表的方法
2015/07/13 Javascript
Bootstrap carousel轮转图的使用实例详解
2016/05/17 Javascript
JS动态给对象添加事件的简单方法
2016/07/19 Javascript
使用do...while的方法输入一个月中所有的周日(实例代码)
2016/07/22 Javascript
JS基于面向对象实现的多个倒计时器功能示例
2017/02/28 Javascript
JQuery中Ajax的操作完整例子
2017/03/07 Javascript
js判断输入框不能为空格或null值的实现方法
2018/03/02 Javascript
React如何解决fetch跨域请求时session失效问题
2018/11/02 Javascript
ES6数组与对象的解构赋值详解
2019/06/14 Javascript
详细分析JavaScript中的深浅拷贝
2020/09/17 Javascript
Python 专题三 字符串的基础知识
2017/03/19 Python
python递归打印某个目录的内容(实例讲解)
2017/08/30 Python
Windows系统下多版本pip的共存问题详解
2017/10/10 Python
安装python3的时候就是输入python3死活没有反应的解决方法
2018/01/24 Python
使用python存储网页上的图片实例
2018/05/22 Python
将Django项目部署到CentOs服务器中
2018/10/18 Python
Python基础之条件控制操作示例【if语句】
2019/03/23 Python
selenium+python自动化测试环境搭建步骤
2019/06/03 Python
python系列 文件操作的代码
2019/10/06 Python
Python pandas RFM模型应用实例详解
2019/11/20 Python
基于Python爬取京东双十一商品价格曲线
2020/10/23 Python
Python字节单位转换(将字节转换为K M G T)
2021/03/02 Python
Css3圆角边框制作代码
2015/11/18 HTML / CSS
公休请假条
2014/04/11 职场文书
导师工作推荐信范文
2014/05/17 职场文书
我为党旗添光彩演讲稿
2014/09/10 职场文书
2014年信访维稳工作总结
2014/12/08 职场文书
房贷收入证明范本
2015/06/12 职场文书
Java输出Hello World完美过程解析
2021/06/13 Java/Android
在js中修改html body的样式
2021/11/11 Javascript
mysql创建存储过程及函数详解
2021/12/04 MySQL