spring boot:用redis+lua实现基于ip地址的分布式流量限制(限流/简单计数器算法)(spring boot 2.2.0)

时间:2022-02-24 06:04:57

一,限流有哪些环节?

1,为什么要限流?

目的:通过对并发请求进行限速或者一个时间单位内的的请求进行限速,目的是保护系统可正常提供服务,避免被压力太大无法响应服务.

如果达到限制速率则可以采取预定的处理:

例如:

拒绝服务(定向到错误页面或返回错误提示信息)

排队或等待(秒杀/评论/下单)

降级(只返回兜底数据或默认数据)

2,需要应用限流的环节

防火墙:firewalld/iptables层的限流,针对每台机器

接入层:nginx的limit_req模块,对每单位时间的平均速率限流,针对某个站点或某个url

应用层:可以针对某个url或某个方法

说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/architectforest

对应的源码可以访问这里获取: https://github.com/liuhongdi/

说明:作者:刘宏缔 邮箱: 371125307@qq.com

二,演示项目的说明

1,项目的原理:

如果仅仅是单机上对某个接口做限流,

可以直接使用google的guava包中的流量限制功能,

但如果是有多台机器上统一做限流,

则需要借助redis的功能

当后端接收到请求时,会把限流的方法名和ip做为key值保存到redis,

每次接收到请求,对key值加1,

当请求数量在指定时间内超过了限制数量,

则返回'访问过于频繁'的提示信息

2,项目在github上的地址:

https://github.com/liuhongdi/ratelimiter

3,项目的目录结构:

spring boot:用redis+lua实现基于ip地址的分布式流量限制(限流/简单计数器算法)(spring boot 2.2.0)

三,lua代码的说明:

ratelimit.lua

local key = KEYS[1]
local limit = tonumber(KEYS[2])
local length = tonumber(KEYS[3])
--redis.log(redis.LOG_NOTICE,' length: '..length)
local current = redis.call('GET', key)
if current == false then
--redis.log(redis.LOG_NOTICE,key..' is nil ')
redis.call('SET', key,1)
redis.call('EXPIRE',key,length)
--redis.log(redis.LOG_NOTICE,' set expire end')
return '1'
else
--redis.log(redis.LOG_NOTICE,key..' value: '..current)
local num_current = tonumber(current)
if num_current+1 > limit then
return '0'
else
redis.call('INCRBY',key,1)
return '1'
end
end

说明:

key:在redis中记录访问次数的index,在这里我们用method的名字加ip地址进行限制

limit:  单ip对此method最多可以访问的次数

length: 限制次数生效的时长

原理:

key不存在时,新建一个key,value设置为1,并设定过期时间

如果key存在,看是否超过单位时间内允许访问的最高次数,

如果超过,返回0,

如果不超过,返回1

说明:为什么使用lua脚本?

redis上的lua脚本的执行是原子性的,不存在多个线程的并发问题,

使用lua脚本能保证高并发时也不会出现超出流量限制

四,java代码的说明:

1,RedisLuaUtil.java

@Service
public class RedisLuaUtil {
@Resource
private StringRedisTemplate stringRedisTemplate;
private static final Logger logger = LogManager.getLogger("bussniesslog");
/*
run a lua script
luaFileName: lua file name,no path
keyList: list for redis key
return 0: fail
1: success
*/
public String runLuaScript(String luaFileName,List<String> keyList) {
DefaultRedisScript<String> redisScript = new DefaultRedisScript<>();
redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/"+luaFileName)));
redisScript.setResultType(String.class);
String result = "";
String argsone = "none";
try {
result = stringRedisTemplate.execute(redisScript, keyList,argsone);
} catch (Exception e) {
logger.error("发生异常",e);
}
return result;
}
}

说明:

DefaultRedisScript:负责封装lua脚本

luaFileName: lua文件名

keyList:   redis中的key列表,我们把参数放在这里面传递

stringRedisTemplate:负责执行脚本

argsone:值参数,我们传一个空字串即可

2,RedisRateLimiterAspect,它负责调用RedisLuaUtil类,执行lua脚本:

    /*
* check is reach limit in time
* run by lua
* */
private boolean checkByRedis(RedisRateLimiter limit, String key) {
List<String> keyList = new ArrayList();
keyList.add(key);
keyList.add(String.valueOf(limit.count()));
keyList.add(String.valueOf(limit.time()));
String res = redisLuaUtil.runLuaScript("ratelimit.lua",keyList);
System.out.println("------------------lua res:"+res);
if (res.equals("1")) {
return true;
} else {
return false;
}
}

说明:

keyList中我们添加了三个变量:

key:   在redis中记录访问次数的index,在这里我们用method的名字加ip地址进行限制

count:  同一个ip限制访问的次数

time: 限制访问的时间段

3,其他java代码的说明:

RedisRateLimiter:定义了一个注解

RedisRateLimiterAspect:AOP的切面程序,使限流不侵入业务代码

RateController: 控制器

在spring框架中使用AOP或Interceptor可以使通用的一些功能例如安全、检验等不影响业务代码,

我们在这个例子中使用的是AOP,也可以选择Interceptor,这里仅供参考

五,测试限流的效果:

1,查看controller中定义的值:

@RestController
@RequestMapping("/rate")
public class RateController {
@RequestMapping("/redislimit")
@RedisRateLimiter(count = 3, time = 1)
public Object redisLimit() {
return ServerResponseUtil.success();
}
}

可以看到流量限制的值为:对redisLimit方法,同一个ip在1秒钟内最多可访问3次

2,用ab测试并发情况下的流量限制是否生效?

#-c:指定请求的并发数量

#-n:指定请求的总数量

[liuhongdi@localhost ~]$ ab -c 20 -n 20 http://127.0.0.1:8080/rate/redislimit

查看代码运行中打印出的数据:

------------------lua res:1
------------------lua res:1
------------------lua res:1
------------------lua res:0
------------------lua res:0
------------------lua res:0
------------------lua res:0
------------------lua res:0
------------------lua res:0
------------------lua res:0
------------------lua res:0
------------------lua res:0
------------------lua res:0
------------------lua res:0
------------------lua res:0
------------------lua res:0
------------------lua res:0
------------------lua res:0
------------------lua res:0
------------------lua res:0

可见在20个并发中,只有3个是生效的,允许正常访问,其他的超出了访问的数量限制

六,查看spring boot的版本

  .   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.2.0.RELEASE)

spring boot:用redis+lua实现基于ip地址的分布式流量限制(限流/简单计数器算法)(spring boot 2.2.0)的更多相关文章

  1. Redis绑定多个ip地址

    Redis绑定多个ip地址 学习了:https://www.zhihu.com/question/20346112/answer/17157379 注意,用空格进行分隔 bind 127.0.0.1 ...

  2. nginx篇最初级用法之三种虚拟主机基于域名&bsol;基于端口&bsol;基于IP地址端口的虚拟主机

    在nginx中虚拟主机的类型与apache一样也有三种 1.基于域名的虚拟主机 2.基于端口的虚拟主机 3.基于IP地址端口的虚拟主机 在nginx配置文件中每一个server为一个虚拟主机如果需要多 ...

  3. Apache(基于IP地址)

    1.Apache的安装 (1)先用命令“cat /etc/passwd|grep apache”,查看有有没有Apache ①如果有Apache,我们就不用再安装 ②如果没有,我们就安装一下 (2)使 ...

  4. Apache服务(基于IP地址,主机名,端口号)

    安装Apache服务程序 需要注意apache服务程序的软件包名称叫做httpd,因此直接执行yum install apache则是错误的. [root@liuxuanke-hbza ~]# yum ...

  5. Apache的虚拟主机功能(基于IP地址、基于虚拟主机、基于端口)

    1. 安装Apache服务程序(系统用户,1-199之间) 第一步:在虚拟机软件里选中光盘镜像: 第二步:将光盘设备挂载到/media/cdrom目录 输入:mkdir -p /media/cdrom ...

  6. 基于令牌桶算法实现的SpringBoot分布式无锁限流插件

    本文档不会是最新的,最新的请看Github! 1.简介 基于令牌桶算法和漏桶算法实现的纳秒级分布式无锁限流插件,完美嵌入SpringBoot.SpringCloud应用,支持接口限流.方法限流.系统限 ...

  7. StreamDM:基于Spark Streaming、支持在线学习的流式分析算法引擎

    StreamDM:基于Spark Streaming.支持在线学习的流式分析算法引擎 streamDM:Data Mining for Spark Streaming,华为诺亚方舟实验室开源了业界第一 ...

  8. spring cloud 入门系列七:基于Git存储的分布式配置中心

    我们前面接触到的spring cloud组件都是基于Netflix的组件进行实现的,这次我们来看下spring cloud 团队自己创建的一个全新项目:Spring Cloud Config.它用来为 ...

  9. spring cloud 入门系列七:基于Git存储的分布式配置中心--Spring Cloud Config

    我们前面接触到的spring cloud组件都是基于Netflix的组件进行实现的,这次我们来看下spring cloud 团队自己创建的一个全新项目:Spring Cloud Config.它用来为 ...

随机推荐

  1. 工作随笔——CentOS6&period;4支持rz sz操作

    yum一句话解决: yum -y install lrzsz

  2. 机器学习&lpar;Machine Learning&rpar;&amp&semi;深入学习&lpar;Deep Learning&rpar;资料

    <Brief History of Machine Learning> 介绍:这是一篇介绍机器学习历史的文章,介绍很全面,从感知机.神经网络.决策树.SVM.Adaboost 到随机森林. ...

  3. Visual Studio 当前不会命中断点的问题

    这个问题一般有两个版本 1.当前不会命中断点,还没有为该文档加载任何符号. 2.当前不会命中断点,源代码与原始版本不同. 要解决第一个问题,就要了解一种文件格式“PDB(Program DataBas ...

  4. 使用VIEWER&period;JS进行简单的图片预览

    <script src="../res/js/viewer.min.js"></script><script type="text/java ...

  5. python&lowbar;为被装饰的函数保留元数据

    案例: 在函数对象中保存着一些函数的元数据,如: f.__name__           函数名 f.__doc__              函数文档 f.__moudle__       函数所 ...

  6. thymeleaf &colon; input&sol;select&sol;radio回显

    thymeleaf中不用自己去写checked="checked" selected="selected"这种代码,他自己会选. input <input ...

  7. React系列文章:JSX生成真实DOM结点

    在上一篇文章中,我们介绍了Babel是如何将JSX代码编译成可执行代码的,随后也实现了一个自己的解析器,模拟了Babel编译的过程. 现在我们再来回顾一下,假定有如下业务代码: const style ...

  8. 我是该学JAVA呢,还是学IOS开发呢?

    摘要: iOS就像Andriod一样,不是编程语言,而是操作系统.学iOS开发,其实学的是如何用Objective-C在苹果操作系统上进行各种应用程序的开发.就像学Andriod开发,其实是学如何用J ...

  9. android设置GridView高度自适应,实现全屏铺满效果

    使GridView每个item的高度自适应拉伸,达到整个GridView刚好铺满全屏的效果. public static void setGridViewMatchParent(GridView gr ...

  10. 2018&period;09&period;24 bzoj4977&colon; &lbrack;&lbrack;Lydsy1708月赛&rsqb;跳伞求生(贪心&plus;线段树)

    传送门 线段树好题. 这题一看我就想贪心. 先把a,b数组排序. 然后我们选择a数组中最大的b个数(不足b个就选a个数),分别贪心出在b数组中可以获得的最大贡献. 这时可以用线段树优化. 然后交上去只 ...