学习笔记一、springboot + aop + Lua分布式限流的最佳实践
一、什么是限流?为什么要限流?
不知道大家有没有做过帝都的地铁,就是进地铁站都要排队的那种,为什么要这样摆长龙转圈圈?答案就是为了限流
!因为一趟地铁的运力是有限的,一下挤进去太多人会造成站台的拥挤、列车的超载,存在一定的安全隐患。同理,我们的程序也是一样,它处理请求的能力也是有限的,一旦请求多到超出它的处理极限就会崩溃。为了不出现最坏的崩溃情况,只能耽误一下大家进站的时间。
限流是保证系统高可用的重要手段!!!
由于互联网公司的流量巨大,系统上线会做一个流量峰值的评估,尤其是像各种秒杀促销活动,为了保证系统不被巨大的流量压垮,会在系统流量到达一定阈值时,拒绝掉一部分流量。
限流会导致用户在短时间内(这个时间段是毫秒级的)系统不可用,一般我们衡量系统处理能力的指标是每秒的QPS
或者TPS
,假设系统每秒的流量阈值是1000,理论上一秒内有第1001个请求进来时,那么这个请求就会被限流。
二、限流方案
1、计数器
Java内部也可以通过原子类计数器AtomicInteger
、Semaphore
信号量来做简单的限流
|
2、漏桶算法
漏桶算法思路很简单,我们把水比作是请求
,漏桶比作是系统处理能力极限
,水先进入到漏桶里,漏桶里的水按一定速率流出,当流出的速率小于流入的速率时,由于漏桶容量有限,后续进入的水直接溢出(拒绝请求),以此实现限流。
3、令牌桶算法
令牌桶算法的原理也比较简单,我们可以理解成医院的挂号看病,只有拿到号以后才可以进行诊病。
系统会维护一个令牌(token
)桶,以一个恒定的速度往桶里放入令牌(token
),这时如果有请求进来想要被处理,则需要先从桶里获取一个令牌(token
),当桶里没有令牌(token
)可取时,则该请求将被拒绝服务。令牌桶算法通过控制桶的容量、发放令牌的速率,来达到对请求的限制。
4、Redis + Lua
很多同学不知道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 Gateway
等,而像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
。
|