基于Springboot + aop + Lua 的Redis 分布式限流器
一、什么是限流?为什么要限流?
限流是保证系统高可用的重要手段!!!
由于互联网公司的流量巨大,系统上线会做一个流量峰值的评估,尤其是像各种秒杀促销活动,为了保证系统不被巨大的流量压垮,会在系统流量达到一定阈(yu)值时,拒绝掉一部分流量。
限流会导致用户在短时间内(这个时间段是毫秒级别的)系统不可用,一般我们衡量系统处理能力的指标是每秒的QPS或者TPS,假设系统每秒的流量阈值是1000,理论上一秒内有第1001个请求进来时,那么这个请求就会被限流。
二、限流方案
1、计数器
Java内部也可以通过原子类计数器AtomicInteger、Semaphore信号量来做简单的限流。
2、漏桶算法
漏桶算法思路很简单,我们把水比作请求,漏桶比作是系统处理能力极限,水先进入到漏桶里,漏桶里的水按一定速率流出,当流出的速率小于流入的速率时,由于漏桶容量有限,后续进入的水直接溢出(拒绝请求),以实现限流。
3、令牌桶算法
令牌桶算法的原理也比较简单,我们可以理解成医院的挂号看病,只有拿到号以后才可以进行诊病。
系统会维护一个令牌(token)桶,以一个恒定的速度往桶里放入令牌(token),这时如果有请求进来想要被处理,则需要先从桶里获取一个令牌(token),当桶里没有令牌(token)可取时,则该请求将被拒绝服务。令牌桶算法通过控制桶的容量、发放令牌的速率,来达到对请求的限制。
4、Redis + Lua
个人理解,Lua脚本和MySQL数据库的存储过程比较相似,他们执行一组命令,所有命令的执行要么全部成功或者失败,以此达到原子性。也可以把Lua脚本理解为,一段具有业务逻辑的代码块。
而Lua本身就是一种编程语言,虽然redis官方没有直接提供限流相应的API,但却支持了Lua脚本的功能,可以使用它实现复杂的令牌桶或漏桶算法,也是分布式系统中实现限流的主要方法之一。
相比Redis事务,Lua脚本的优点:
-
减少网络开销:使用Lua脚本,无需向Redis发送多次请求,执行一次即可,减少网络传输。
-
原子操作:Redis将整个Lua脚本作为一个命令执行,原子,无需担心并发。
-
复用:Lua脚本一旦执行,会永久保存Redis中,其他客户端可复用。
Lua脚本大致逻辑如下:
- 通过KEYS[1]获取传入的key参数。
- 通过ARGV[1]获取传入的limit参数。
- redis.call方法,从缓存中get和key相关的值,如果为null那么就返回0。
- 接着判断缓存中记录的数值是否会大于限制大小,如果超出则表示该被限流,返回0。
- 如果未超过,那么key的缓存值+1,并设置过期时间为1秒钟以后,并返回缓存值+1。
5、网关限流
限流常在网关这一层做,比如Nginx、Openresty、kong、zuul、Spring Cloud Geteway等,而像Spring Cloud Gateway网关限流底层的实现原理,就是基于Redis + Lua,通过内置Lua限流脚本的方式。
三、Redis + Lua 限流实现
下面通过 自定义注解、aop、Redis + Lua 实现限流。
1、环境准备
SpringBoot项目创建:https://start.spring.io,很方便实用的一个工具。
2、引入依赖包
pom文件中添加如下依赖包,比较关键的是spring-boot-starter-data-redis和spring-boot-starter-aop。
3、配置application.yml
在application.yml文件中配置提前搭建好的redis服务地址和端口。
4、配置RedisTemplate实例
限流类型枚举类
5、自定义注解
我们自定义一个@Limit注解,注解类型为ElementType.MEHTOD即作用于方法上。
period表示请求限制时间段,count表示在period这个时间段内允许放行请求的次数。limitType代表限流类型,可以根据IP、自定义key,如果不传limitType属性则默认用方法名作为默认key。
6、切面代码实现
7、控制层实现
将@Limit注解作用在需要进行限流的接口方法上,下边我们给方法设置@Limit注解,在10秒内允许放行三个请求,为了直观一点用AtomicInteger计数。
8、测试
注:因为适用了Lua编程语言,需要安装EmmyLua插件,大家自行百度,这里不做详细介绍了。
测试[预期]:10秒之内连续请求3次均可成功,第四次请求被拒绝。直接适用postman进行测试。
十秒之内,第4次请求时,应用直接决绝了请求,说明 SpringBoot + aop + Lua 限流方案搭建成功。
四、源码
git源码地址
|
请发表评论