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 27 Redis
Redis6.0搭建集群Redis-cluster的方法
May 08 Redis
为Java项目添加Redis缓存的方法
May 18 Redis
redis哨兵常用命令和监控示例详解
May 27 Redis
浅谈Redis的几个过期策略
May 27 Redis
Redis可视化客户端小结
Jun 10 Redis
redis requires ruby version2.2.2的解决方案
Jul 15 Redis
使用redis生成唯一编号及原理示例详解
Sep 15 Redis
CentOS8.4安装Redis6.2.6的详细过程
Nov 20 Redis
Redis中缓存穿透/击穿/雪崩问题和解决方法
Dec 04 Redis
Redis安装使用RedisJSON模块的方法
Mar 23 Redis
关于Redis的主从复制及哨兵问题
Jun 16 Redis
Redis高并发缓存架构性能优化
详解Redis的三种常用的缓存读写策略步骤
windows安装 redis 6.2.6最新步骤详解
muduo TcpServer模块源码分析
Redis数据同步之redis shake的实现方法
Apr 21 #Redis
Grafana可视化监控系统结合SpringBoot使用
Redis官方可视化工具RedisInsight安装使用教程
You might like
连接到txt文本的超链接,不直接打开而是点击后下载的处理方法
2009/07/01 PHP
php写一个函数,实现扫描并打印出自定目录下(含子目录)所有jpg文件名
2017/05/26 PHP
phpcms配置列表页以及获得文章发布时间
2017/07/04 PHP
Thinkphp3.2简单解决多文件上传只上传一张的问题
2017/09/26 PHP
PHP异步进程助手async-helper
2018/02/05 PHP
支持汉转拼和拼音分词的PHP中文工具类ChineseUtil
2018/02/23 PHP
Laravel Validator 实现两个或多个字段联合索引唯一
2019/05/08 PHP
IE和firefox浏览器的event事件兼容性汇总
2009/12/06 Javascript
容易被忽略的JS脚本特性
2011/09/13 Javascript
node.js中的emitter.on方法使用说明
2014/12/10 Javascript
jquery中radio checked问题
2015/03/16 Javascript
理解JavaScript中worker事件api
2015/12/25 Javascript
Vue.js路由组件vue-router使用方法详解
2016/12/02 Javascript
Angular组件化管理实现方法分析
2017/03/17 Javascript
基于JavaScript实现的顺序查找算法示例
2017/04/14 Javascript
js学习总结之DOM2兼容处理this问题的解决方法
2017/07/27 Javascript
d3绘制基本的柱形图的实现代码
2018/12/12 Javascript
vue+AI智能机器人回复功能实现
2020/07/16 Javascript
[01:59]DOTA2首部纪录片《Free to play》预告片
2014/03/12 DOTA
[02:31]2014DOTA2国际邀请赛2009专访:干爹表现出乎意料 看好DK杀回决赛
2014/07/20 DOTA
[06:20]2015国际邀请赛第三日top10
2015/08/08 DOTA
[01:22:19]EG vs TNC Supermajor小组赛B组败者组第一轮 BO3 第二场 6.2
2018/06/03 DOTA
[13:16]INFAMOUS vs VGJ T BO3
2018/06/07 DOTA
python3+PyQt5使用数据库窗口视图
2018/04/24 Python
python集合比较(交集,并集,差集)方法详解
2018/09/13 Python
Python面向对象程序设计之类的定义与继承简单示例
2019/03/18 Python
HTML5获取当前地理位置并在百度地图上展示的实例
2020/07/10 HTML / CSS
消防先进事迹材料
2014/02/10 职场文书
培训专员岗位职责
2014/02/26 职场文书
表彰会主持词
2014/03/26 职场文书
售票员岗位职责
2015/02/15 职场文书
财政局个人年终总结
2015/03/03 职场文书
党员转正党支部意见
2015/06/02 职场文书
爱护公物主题班会
2015/08/17 职场文书
JavaScript实现外溢动态爱心的效果的示例代码
2022/03/21 Javascript
向Spring IOC 容器动态注册bean实现方式
2022/07/15 Java/Android