由于互联网公司的流量巨大,系统上线会做一个流量峰值的评估,尤其是像各种秒杀促销活动,为了保证系统不被巨大的流量压垮,会在系统流量到达一定阈值时,拒绝掉一部分流量。
限流会导致用户在短时间内(这个时间段是毫秒级的)系统不可用,一般我们衡量系统处理能力的指标是每秒的QPS或者TPS,假设系统每秒的流量阈值是1000,理论上一秒内有第1001个请求进来时,那么这个请求就会被限流。
限流方案
计数器
Java内部可以通过原子类计数器AtomicInteger、Semaphore信号量来做简单的限流。
1 | // 限流的个数 |
漏桶算法
漏桶算法思路很简单,我们把水比做请求,漏桶比做是系统处理能力极限,水先进入到漏桶里,漏桶里的水按一定速率流出,当流出速率小于流入的速率时,由于漏桶容量有限,后续进入的水直接溢出(拒绝请求),以此实现限流。

令牌桶算法
令牌桶算法的原理也比较简单,可以理解成医院的挂号看病,只要拿到号以后才可以进行诊病。
系统会维护 一个令牌(token)桶,以一个恒定的速度往桶里放入令牌(token),这时如果有请求进来想要被处理,则需要先从桶里获取一个令牌(token), 当桶里没有令牌(token)可取时,则该请求将被拒绝服务。令牌桶算法通过控制桶的容量,发放令牌的速率,来达到对请求的限制。

Redis + Lua
个人理解,Lua脚本和 MySQL数据库的存储过程比较相似,他们执行一组命令,所有命令的执行要么全部成功或者失败,以此达到原子性。也可以把Lua脚本理解为,一段具有业务逻辑的代码块。
而Lua本身就是一种编程语言,虽然redis 官方没有直接提供限流相应的API,但却支持了 Lua 脚本的功能,可以使用它实现复杂的令牌桶或漏桶算法,也是分布式系统中实现限流的主要方式之一。
相比Redis事务,Lua脚本的优点:
- 减少网络开销:使用
Lua脚本,无需向Redis发送多次请求,执行一次即可,减少网络传输 - 原子操作:
Redis将整个Lua脚本作为一个命令执行,原子,无需担心并发 - 复用:
Lua脚本一旦执行,会永久保存Redis中,,其他客户端可复用
Lua脚本大致逻辑如下:
1 | -- 获取调用脚本时传入的第一个key值(用作限流的 key) |
- 通过
KEYS[1]获取传入的key参数 - 通过
ARGV[1]获取传入的limit参数 redis.call方法,从缓存中get和key相关的值,如果为null那么就返回0- 接着判断缓存中记录的数值是否会大于限制大小,如果超出表示该被限流,返回0
- 如果未超过,那么该key的缓存值+1,并设置过期时间为1秒钟以后,并返回缓存值+1
这种方式是本文推荐的方案,具体实现会在后边做细说。
网关层限流
限流常在网关这一层做,比如Nginx、Openresty、kong、zuul、Spring Cloud Gateway等,而像spring cloud - gateway网关限流底层实现原理,就是基于Redis + Lua,通过内置Lua限流脚本的方式。







