限流主要的作用是保护服务节点或者集群后面的数据节点,防止瞬时流量过大使服务和数据崩溃(如前端缓存大量实效),造成不可用;还可用于平滑请求。
限流算法
限流算法有两种,一种就是简单的请求总量计数,一种就是时间窗口限流(一般为1s),如令牌桶算法和漏牌桶算法就是时间窗口的限流算法。
令牌桶算法(token Bucket)
- 系统以固定速率将令牌放入一个固定容量的令牌桶中,当令牌桶满时,系统将放弃添加新的令牌到桶中
- 客户的每一次请求都会消耗桶中的令牌,当桶中没有令牌时则放弃这次请求或者等待直到桶有新的令牌添加。
漏桶算法(Leaky Bucket)
漏桶算法和令牌桶算法思路一样
- 每一次请求都将令牌放入令牌桶中,如果桶满则放弃这次请求或者等到桶为空
- 系统以固定的速度消耗令牌直到桶为空
可以看出来和令牌桶思路一样,只是角色互换,方向相反,实现上可以使用第三方框架来学习,也可以使用Java阻塞队列来实现,或者使用Redis的List来实现。
令牌桶拓展之令牌桶外预借算法应对突发流量
当令牌桶中的令牌不够时,允许没有获得令牌的请求最多预支整个桶容量的令牌,所以突发量能达到桶容量的2倍,但是预支的令牌必须在接下来的时间内补上(系统先将欠的的令牌填不上,才能再将令牌放入桶中)。
并发控制
框架连接/线程的限制
各种数据连接池,HTTP连接池
服务、方法级别的信号量Semaphore计数器的限制
对于一个应用系统来说一定会有极限并发/请求数,即总有一个TPS/QPS阀值,如果超了阀值则系统就会不响应用户请求或响应的非常慢,因此我们最好进行过载保护,防止大量请求涌入击垮系统。
如果你使用过Tomcat,其Connector 其中一种配置有如下几个参数:
acceptCount:如果Tomcat的线程都忙于响应,新来的连接会进入队列排队,如果超出排队大小,则拒绝连接;
maxConnections: 瞬时最大连接数,超出的会排队等待;
maxThreads:Tomcat能启动用来处理请求的最大线程数,如果请求处理量一直远远大于最大线程数则可能会僵死。
详细的配置请参考官方文档。另外如Mysql(如max_connections)、Redis(如tcp-backlog)都会有类似的限制连接数的配置,nginx的limit_conn模块,用来限制瞬时并发连接数,nginx的limit_req模块,限制每秒的平均速率。限流总资源数
如果有的资源是稀缺资源(如数据库连接、线程),而且可能有多个系统都会去使用它,那么需要限制应用;可以使用池化技术来限制总资源数:连接池、线程池。比如分配给每个应用的数据库连接是100,那么本应用最多可以使用100个资源,超出了可以等待或者抛异常。限流某个接口的总并发/请求数
如果接口可能会有突发访问情况,但又担心访问量太大造成崩溃,如抢购业务;这个时候就需要限制这个接口的总并发/请求数总请求数了;因为粒度比较细,可以为每个接口都设置相应的阀值。可以使用Java中的AtomicLong进行限流。
平滑限流某个接口的请求数
之前的限流方式都不能很好地应对突发请求,即瞬间请求可能都被允许从而导致一些问题;因此在一些场景中需要对突发请求进行整形,整形为平均速率请求处理(比如5r/s,则每隔200毫秒处理一个请求,平滑了速率)。这个时候有两种算法满足我们的场景:令牌桶和漏桶算法。Guava RateLimiter提供了令牌桶算法实现:平滑突发限流(SmoothBursty)和平滑预热限流(SmoothWarmingUp)实现。在RateLimiter的实现里,还多了个桶外预借
服务过载处理
立刻返回拒绝错误
由客户端进行降级处理。进行短暂的等待
短暂等待,期待有容量空余,直到超时,依然是客户端降级。
限流触发条件
触发条件的设定,难点在于服务的容量,受着本服务节点的能力,背后的资源的能力,下游服务的响应的多重约束。
- 静态配置固定值
当然,这个固定值可以被动态更新。
- 根据预设规则触发
规则的条件可以是服务平均时延,可以是背后数据库的CPU情况等。比如平时不限流,当服务时延大于100ms,则触发限流500 QPS。还可以是多级条件,比如>100ms 限流500 QPS, >200ms 限流200 QPS。
参考链接
http://calvin1978.blogcn.com/articles/ratelimiter.html
https://mp.weixin.qq.com/s?__biz=MzIwODA4NjMwNA==&mid=2652897781&idx=1&sn=ae121ce4c3c37b7158bc9f067fa024c0#rd