一,限流有哪些环节?

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,项目的目录结构:

三,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篇最初级用法之三种虚拟主机基于域名\基于端口\基于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. Java链接db2相关

    端口一般是50000或者60000 后面跟的可能是库名(个人猜测) 还有db2jcc.jar安装db2的可以从相应目录下载未安装的可以…(啥时候有空再传上来)

  2. Java学习预热

    预备知识 什么是计算机 点击进入百度百科(计算机) 计算机硬件组成 主要部件 CPU 内存 主板 IO设备 键盘 鼠标 麦克风 显示器 音箱 冯·诺依曼体系结构 计算机软件 系统软件 DOS wind ...

  3. nginx如何写日志

    写日志函数为ngx_log_error_core,位于src/core/ngx_log.c:89行核心代码如下:while (log) { if (log->log_level < lev ...

  4. SpringBoot-03-配置之yaml语法学习

    3. SpringBoot配置 3.1 yaml语法学习 配置文件 Springboot使用一个全局的配置文件,配置文件名称固定 spplication.properties 语法结构:key=val ...

  5. 【Processing-日常3】等待动画1

    之前在CSDN上发表过: https://blog.csdn.net/fddxsyf123/article/details/79755976

  6. Dledger的是如何实现主从自动切换的

    前言 hello小伙伴们,今天王子又来继续和大家聊RocketMQ了,之前的文章我们一直说Broker的主从切换是可以基于Dledger实现自动切换的,那么小伙伴们是不是很好奇它究竟是如何实现的呢?今 ...

  7. 利用new Object方式创建对象

    var obj = new Object();      //创建了一个空的对象obj.uname = 'zhangsanfeng';obj.name = 18;       //字面量方式创建对象不 ...

  8. Hibernate4.3 QBC查询

    一.基本查询 1 Session session = HibernateUtils.getSession(); 2 //创建QBC查询接口的实现类 3 Criteria criteria = sess ...

  9. Python-类属性查询协议-__getattr__ __getattribute__

    __getattr__ 查找不到类属性的时候调用 class BeiMenChuiXue: def __init__(self, name): self.name = name def __getat ...

  10. 用JTable 实现日历

    效果图: 主要思想:日历最核心的功能就是能显示某年某月对应的日期和星期几.因此只要实现传入具体的年份和月份,得到一组存放了日期的数组a[ ]即可.其中数组的大小设置成42,要考虑的问题是当月的第一天对 ...