对于高并发的系统,有三大利器来保证系统的稳定可用:缓存、降级、限流
缓存即是提升系统访问速度和增大系统能处理的容量,可谓是抗高并发流量的银弹
降级即是把服务暂时屏蔽,直接越过,待高峰后再恢复
限流即流量限制,限流的目的是在遇到流量高峰期或者流量突增(流量尖刺)时,把流量速率限制在系统所能接受的合理范围之内,不至于让系统被高流量击垮
(熔断即是把服务直接短路掉,返回一个mock的值)
目前有几种常见的限流方式:
- 通过限制单位时间段内调用量来限流
- 基于redis的限速器来限流(分布式系统)
- 使用漏桶(Leaky Bucket)算法来进行限流
- 使用令牌桶(Token Bucket)算法来进行限流(Guava实现,RateLimiter)
具体分析:
第1种方案:
通过一个计数器统计单位时间段某个服务的访问量,如果超过了我们设定的阈值,则该单位时间段内则不允许继续访问、或者把接下来的请求放入队列中等待到下一个单位时间段继续访问。
这种方案的核心在于阈值的设置,缺点是需要依赖监控系统来分析调用量,可能存在“突刺消耗”,即单位时间段的前几秒内流量达到阈值,导致该时间段内剩余的时间内该服务“拒绝服务”,高并发下仍然可能压垮系统
第2种方案:
基于RedisAPI中的INCR,实现限速器,它将 API 的最大请求数限制在每个 IP 地址每秒钟多少个之内: 其中要考虑INCR命令和EXPIRE(过期时间)命令的原子性,最终解决是使用Lua脚本(Lua脚本是Redis2.6及以上才支持)
第3种方案:
漏桶算法是常用算法之一,不管遇到多大的流量,都以固定速率流出,超出部分直接丢弃,缺点显而易见:不能应对突发性流量,直接导致服务到达瓶颈,
第4种方案:
令牌桶算法是漏桶算法的改进,随着时间流逝,系统会按恒定1/QPS时间间隔(如果QPS=100,则间隔是10ms)往桶里加入Token,如果桶已经满了就不再加了.新请求来临时,会各自拿走一个Token,
如果没有Token可拿了就阻塞或者拒绝服务,它能应对突发的流量,可以改变放入桶中的令牌的速率。
(令牌桶的具体实现:Google开源工具包Guava提供了限流工具类RateLimiter,实现类似于java中的信号量Semaphore,它支持两种方式:一种是如果拿不到立刻返回false,一种会阻塞等待一段时间看能不能拿到。)
以上4种实现方式都是需要自行编码实现,在SpringCloud的组件中Spring Cloud Zuul RateLimiter对Guava的RateLimiter进行了封装,需要引入springCloud,完全基于配置来实现限流,并且提供了4种粒度的限流类型:
URL | 针对URL/接口进行限流 |
Service | 针对服务进行限流,如果没有配置限流类型,则此类型生效 |
Request Origin | 针对请求的Origin进行限流 |
Authenticated User | 针对请求的用户进行限流 |
结论:
经过讨论,
1,组件选型:先采用“令牌桶算法”的方案,如果后续需要引入“Spring Cloud Zuul”组件,此部分功能可直接由网关组件接管。
2,过流保护策略:当达到上限时,方案1:直接丢弃;方案2:等待。 出于高并发考虑,先选择直接丢弃方案。