方法一:基于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;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {if (!limitPolicy.canDo()) {return false;}return true;}
}

同时添加一个配置LimitConfiguration:

@Configuration
public class LimitConfiguration implements WebMvcConfigurer {@Overridepublic 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;}@Overridepublic boolean canDo() {if (setNxExpire()) {//设置成功,说明原先不存在,成功设置为COUNTreturn true;} else {//设置失败,说明已经存在,直接减1,并且返回return jedis.decrBy(KEY, 1) > 0;}}
}public abstract class LimitPolicy {public static final int COUNT = 10; //10 requestpublic static final int TIME= 10*1000 ; // 10spublic 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";@Overridepublic 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 valueInteger 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来获取令牌。

  1. 首先配置一个定时任务,通过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());}
}
  1. 限流时,通过list的lpop方法从redis中获取对应的令牌,如果获取成功表明可以执行请求:
/**
* 方法三:令牌桶
*/
public class RedisLimit3 extends LimitPolicy {public static final String KEY3 = "LIMIT3";@Overridepublic boolean canDo() {Object result = jedis.lpop(KEY3);if (result == null) {return false;}return true;}
}

Redis 实现限流器的三种方法相关推荐

  1. python 实现账号封禁30分钟---(Redis,Mysql,文件)三种方法

    下面写三种方法来实现,用户在规定时间内,输入次数上限,封禁账号的功能. 第一种:使用redis完成用户封禁状态 第二种:使用mysql 第三种:使用文件 1.封禁用户流程图  第一种方法: Redis ...

  2. 下载合适的python-python下载文件的三种方法

    Python开发中时长遇到要下载文件的情况,最常用的方法就是通过Http利用urllib或者urllib2模块,此外Python还提供了另外一种方法requests. 下面来看看三种方法是如何来下载文 ...

  3. python 下载文件-python下载文件的三种方法

    Python开发中时长遇到要下载文件的情况,最常用的方法就是通过Http利用urllib或者urllib2模块,此外Python还提供了另外一种方法requests. 下面来看看三种方法是如何来下载文 ...

  4. Redis高可用的三种实现方式

    Redis高可用的三种实现方式 1. 高可用的概念 ​ 高可用(High Availability,即HA),指的是通过尽量缩短日常维护操作和突发的系统崩溃所导致的停机时间,以提高系统和应用的可用性. ...

  5. android写入文件方法,Android 追加写入文件的三种方法

    一.使用FileOutputStream 使用FileOutputStream,在构造FileOutputStream时,把第二个参数设为true public static void method1 ...

  6. java中如何启动一个新的线程三种方法

    java开启新线程的三种方法: 方法1:继承Thread类 1):定义bai一个继承自Java.lang.Thread类的du类A. 2):覆盖zhiA类Thread类中的run方法. 3):我们编写 ...

  7. java数据输入的步骤_Java学习日志1.4 Scanner 数据输入的三种方法

    Scanner sc = new Scanner(System.in); /注意in 是InputStream的缩写,是字节输入流的意思. 整句话的含义就是: new 一个对象,接受从键盘输入的数据, ...

  8. RedHat 7.0及CentOS 7.0禁止Ping的三种方法

    作者:荒原之梦 原文链接:http://zhaokaifeng.com/?p=538 前言: "Ping"属于ICMP协议(即"Internet控制报文协议") ...

  9. 结构成员访问的三种方法

    结构成员访问的三种方法 #include "stdio.h" #include "string.h" #include <stdlib.h> mai ...

最新文章

  1. Go 知识点(16)— 将枚举值转换为字符串
  2. ubuntu14.04 安装 bcm43142无线网卡
  3. 【组合数学】指数生成函数 ( 指数生成函数求解多重集排列示例 )
  4. 14 款免费漂亮的 BuddyPress 主题
  5. 阿里云前端周刊 - 第 13 期
  6. c语言给一个函数添加功能,【C语言】请编写实现以下功能函数:实现对一个8bit数据(unsigned char)的指定位(例如第8位)的置0或置1操作,并保持其他位不变...
  7. EMR 配置纪录(不断更新)
  8. FZU 1054 阅读顺序
  9. [Jmeter][基础]Jmeter连接IMPALA
  10. 开发核酸检测软件方案书
  11. “计算机诺贝尔奖”的首位华人得主姚期智
  12. springboot供应商管理系统毕业设计源码121518
  13. 使用神器vscode代替beyond compare进行文本比较高亮显示
  14. 马铃薯凝集素(STL,PL)
  15. 面试秘籍大放送,编测编学独家秘籍遭外泄?!
  16. 基于ListView的滑动删除、添加、修改
  17. uniapp实现微信小程序websocket+背景音频语音播报
  18. English words page one
  19. 写给程序员的数理科普:混沌与三体
  20. 斑马GK888T打印机跑纸(打印半张以及闪红灯)解决办法

热门文章

  1. 求电缆最小长度——最小生成树
  2. 查询Products中单价(UnitPrice)最高的Products的资料;
  3. 应用分发平台之苹果超级签名流程分析及API错误
  4. 免费APP内测分发托管平台,支持应用合并、内测分发、扫码下载,下载量安装量统计,版本记录和应用在线封装打包app
  5. 把 Win 8.1 升级成 Windows 2012 R2 (再续)
  6. 解决在uniapp项目中小程序调用获取微信绑定手机号
  7. cesium 学习笔记(三) 在地图上放置3D建筑模型
  8. 渗透测试-Python安全工具编程基础
  9. 硬盘安装工具cgi3.2_笔记本旧硬盘改造移动硬盘,满足媳妇办公存储需求
  10. js常用的正则匹配(一般不用修改)