认识限流
限流在我们日常生活中也是很常见的,如早高峰地铁限流(排队等)、北上广深大城市的车牌限号(直接拒绝)等。
同比到我们软件系统,指的是在高并发场景下对高并发请求进行限速或者对一个单位时间内的请求进行限速来保护软件系统。
也就是对请求进行限速,比如1秒只允许100个请求。归根结底就是在一定频率上限制请求的数量
1、拒绝服务,对于超过限制的请求拒绝提示友好的信息或错误提示页等
2、排队等待,过多的请求直接放到队列里等待
限流的场景
限流一般都是对接口进行限流,理论上所有的接口都应该限流(毕竟每一个接口都可能突然暴增请求导致服务雪崩 )。但是一般实践过程中都是对一些热门接口、一级页面接口进行限流控制。常见的场景。
1、发验证码用户级别的限流,限制单个用户在多少秒之内不能重复请求。
2、对一些稀缺资源的抢购、秒杀等进行接口级别的限流
3、比较耗时的一些接口,对数据库的高并发读写操作比如下单、批量读写等高并发下可能打垮服务器
4、一些热门接口,如秒杀详情页、用户tab页、登录接口等
5、对内RPC服务,比如A服务被BCD服务调用,B服务突然暴增马上就会打垮A服务而导致CD不可用引起服务雪崩
注:限流的目的是为了防止瞬间高并发请求导致服务雪崩而牺牲部分请求保全服务可用,因此一定要评估好、测试好限流实现。不能因为限流导致正常流量下请求也被限流而影响用户体验
常见的限流算法
高并发场景下的限流方案:限制单位时间内的并发数(计数器、欢动窗口、令牌桶、漏桶、令牌桶等)、限制并发数(线程池+队列、Semaphone信号量、Nginx限制)
限制单位时间内的并发数
业务代码层面的限流算法:计数器、滑动窗口、漏桶、令牌桶。
计数器
计数器算法是一直简单粗暴的限流算法,在固定时间范围内对请求进行计数,再与阈值对比判断是否需要限流,一旦到达时间点就清零或者失效。
实现:限制单个接口qps为100,在第一次请求进来时设置redis key为最大值并设置超时时间,每进来一个请求就redis原子操作减1,然后和0判断。<=0则限流。
计数器限流主要主要用来限制总并发数。如数据库连接池、redis线程池、秒杀场景等,对全局或者或一定时间段的总请求数进行限流,不能保证平滑请求。
优点:实现简单,适合全局请求总量控制、一定时间内阈值较小的场景
缺点:计数法通过固定时间窗口来重置数据会导致双倍请求可能压垮服务器且请求速率不平均不是平滑的。
滑动窗口算法
固定时间计数法存在临界点问题,时间窗口一直在变动,在指定的时间范围内不能超过一定的值。避免了计算法的临界点双倍流量问题。时间片一直在变动,计算一个总量。在一定的时间间隔不能超过限制
简单实现,时间间隔保持在一个window范围内,统计次数不能超过size。
public class SlidingWindows {
private long window;
//窗口的size 用于计算总的流量上限
private long size;
private AtomicLong count;
private Long current;
public SlidingWindows(long window, long size) {
this.window = window;
this.size = size;
this.count = new AtomicLong(0);
}
public Boolean tryRate() {
Long now = System.currentTimeMillis();
if (current ==