限流是什么?
在生活中,其实限流的日子随处可见,比如姜云升的演唱会只能容纳500人,那么在演唱会中就不能出现超过500人,因为场地只能容纳这么多,如果多了就会发生拥挤或者各种问题。
在程序中的体现则是: 每秒请求数,每秒事务处理数,网络流量等等。我们常说的限流指代的是: 限制达到系统的并发请求数,能让系统正常处理部分用户的请求,保证系统的稳定性。但是不可避免的会让一些用户的请求变慢或者被拒绝影响用户体验的情况。
为什么需要限流?
前面说到 限流是为了保证演唱会能正常进行,保证系统能够稳定的运行。那么假设 姜云升在上海举办演唱会,场地只能容纳500人,但是有5000人想要去看,这就会造成场地的拥挤从而导致演唱会不能正常进行。 我们系统也是一样 假设只能容纳100人的共同请求,如果超过了这个并发数,系统就会崩溃。在我们日常的业务中 有秒杀活动,双十一大促销,如果我们不能处理好这些突发的流量,后端服务可能会被直接打垮。
常用的限流算法
计数限流
计数限流是最简单的限流算法了,
假设我们系统能容纳500个请求,我们就保存一个计数器,开始处理一个请求就+1,处理完成就-1,如果请求过来,发现超过了阈值 就直接拒绝访问。
优点: 实现简单,单机可以直接使用java中的原子类来实现,分布式可以使用 Redis Incr
缺点: 无法控制突发流量,如果使用分布式锁或者单机锁,就会造成系统吞吐量降低。
boolean tryAcquire(){ if(count < threshold){ count++; return ture; } return false; } boolean tryRelease(){ if(count > 0){ count--; return ture; } return false; } |
固定窗口限流
固定窗口限流 相较计数离限流多了一个时间窗口的概念,让计数器每过了一个时间窗口就重置
- 请求次数小于阈值,允许访问并且计数器 +1;
- 请求次数大于阈值,拒绝访问;
- 这个时间窗口过了之后,计数器清零;
缺点: 在窗口临界的时候,突然涌入大流量就会导致系统崩溃,例如 系统每秒100个请求, 在0.8S中 涌入了100个请求,然后过了一秒,窗口清零了,此时在1.0秒的时候 又涌入了100个请求,从全局上来看 0.8-1s 之间系统需要处理200个请求。
滑动窗口限流
滑动窗口限流 解决了固定窗口的临界值的问题,可以保证在任意时间窗口都不会超过阈值,相较固定窗口,滑动窗口引入了计数器之外,还需记录时间窗口内每个请求达到的时间
规则如下:
- 记录每次请求的时间
- 统计每次请求的时间 至 往前推1秒这个时间窗口内请求数,并且 1 秒前的数据可以删除。
- 统计的请求数小于阈值就记录这个请求的时间,并允许通过,反之拒绝。
缺点: 无法解决短时间之内集中流量的突击
漏桶算法
请求来了放入桶中
桶内请求量满了就拒绝请求
服务定速从桶内拿请求处理
它的特点就是宽进严出,无论请求多少,请求的速率有多大,都按照固定的速率流出,对应的就是服务按照固定的速率处理请求。(消息队列 削峰填谷)
缺点: 在突发请求的时候,服务处理速度和平时一样,导致用户体验差。
令牌桶算法
令牌桶其实和漏桶的原理类似,只不过漏桶是定速地流出,而令牌桶是定速地往桶里塞入令牌,然后请求只有拿到了令牌才能通过,之后再被服务器处理。
当然令牌桶的大小也是有限制的,假设桶里的令牌满了之后,定速生成的令牌会丢弃。
规则:
- 定速的往桶内放入令牌
- 令牌数量超过桶的限制,丢弃
- 请求来了先向桶内索要令牌,索要成功则通过被处理,反之拒绝
优点: 应对突发流量的时候令牌桶表现的更好,因为 假设有100个令牌,流量直接取走这100个令牌不用等待而平滑处理