分布式限流
分布式限流
限流就是针对超过预期的流量,通过预期设定的限流规则选择性的对某些请求进行限流「熔断」
合法性验证限流
通过限制并发处理的请求数目,可以限制任何时刻都不会有过多的请求在消耗资源
验证码、IP黑名单
网关上进行token校验、业务代码层进行逻辑判断校验
web容器限流
1.Tomcat
conf/server.xml文件中配置Tomcat最大线程数,当请求大于maxThreads,请求就会排队执行。
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000"maxThreads="150" redirectPort="8443" />
maxThreads 的值可以适当的调大一些,此值默认为 150(Tomcat 版本 8.5.42),但这个值也不是越大越好,要看具体的硬件配置,需要注意的是每开启一个线程需要耗用 1MB 的 JVM 内存空间用于作为线程栈之用,并且线程越多 GC 的负担也越重。最后需要注意一下,操作系统对于进程中的线程数有一定的限制,Windows 每个进程中的线程数不允许超过 2000,Linux 每个进程中的线程数不允许超过 1000
2.Nigix
控制速率
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s;
server { location / { limit_req zone=mylimit burst=4;}
}
以上配置表示,限制每个 IP 访问的速度为 2r/s,因为 Nginx 的限流统计是基于毫秒的,做时间切片的,我们设置的速度是 2r/s,转换一下就是 500ms 内单个 IP 只允许通过 1 个请求,从 501ms 开始才允许通过第 2 个请求。
burst=4 表示每个 IP 最多允许4个突发请求
控制并发连接数
limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;
server {...limit_conn perip 10;limit_conn perserver 100;
}
limit_conn perip 10 表示限制单个 IP 同时最多能持有 10 个连接;limit_conn perserver 100 表示 server 同时能处理并发连接的总数为 100 个
只有当 request header 被后端处理后,这个连接才进行计数
服务端限流
限制不同调用方、接口级细粒度限流
TPS:每秒处理的事务数,事务的开始是接收到接口的请求,事务的结束是处理完成返回
HPS:hits per second每秒请求数
1、时间窗口限流算法
固定时间窗口:选定固定的时间窗口,在该时间窗口内,每次请求访问时累加计算,如果在当前窗口内,累加值超过限流规则的值,则进行熔断处理。当进行到下一个时间窗口,计数器清零重新累加计算。该固定时间窗口,限流策略过于粗略,无法应对两个时间窗口之间的临界值。无法保证请求能够平稳访问。
滑动时间窗口:该时间窗口是持续滑动的,增加计数器计算请求数和记录时间窗口内每个接口请求到达的时间点,通过判断请求时间是否处理时间窗口内,否则时间窗口向后滑动固定大小的时间。同样无法应对细粒度的突发流量。
改进版本:多层次限流,多条规则同时限制。主要是为了让流量更加平滑
借助 Redis 的有序集合 ZSet 来实现时间窗口算法限流,实现的过程是先使用 ZSet 的 key 存储限流的 ID,score 用来存储请求的时间,每次有请求访问来了之后,先清空之前时间窗口的访问量,统计现在时间窗口的个数和最大允许访问量对比,如果大于等于最大访问量则返回 false 执行限流操作,负责允许执行业务逻辑,并且在 ZSet 中添加一条有效的访问记录
2、桶限流算法
令牌桶算法
在令牌桶算法中有一个程序以某种恒定的速度生成令牌,并存入令牌桶中,而每个请求需要先获取令牌才能执行,如果没有获取到令牌的请求可以选择等待或者放弃执行
import com.google.common.util.concurrent.RateLimiter;
import java.time.Instant;
/*** Guava 实现限流*/
public class RateLimiterExample {public static void main(String[] args) {// 每秒产生 10 个令牌(每 100 ms 产生一个)RateLimiter rt = RateLimiter.create(10);for (int i = 0; i < 11; i++) {new Thread(() -> {// 获取 1 个令牌rt.acquire();System.out.println("正常执行方法,ts:" + Instant.now());}).start();}}
}
漏桶算法
漏桶算法类似于生活中的漏斗,无论上面的水流倒入漏斗有多大,也就是无论请求有多少,它都是以均匀的速度慢慢流出的。当上面的水流速度大于下面的流出速度时,漏斗会慢慢变满,当漏斗满了之后就会丢弃新来的请求;当上面的水流速度小于下面流出的速度的话,漏斗永远不会被装满,并且可以一直流出
先声明一个队列用来保存请求,这个队列相当于漏斗,当队列容量满了之后就放弃新来的请求,然后重新声明一个线程定期从任务队列中获取一个或多个任务进行执行,这样就实现了漏桶算法
使用 Redis 4.0 版本中提供的 Redis-Cell 模块,该模块使用的是漏斗算法,并且提供了原子的限流指令,而且依靠 Redis 这个天生的分布式程序就可以实现比较完美的限流了。> cl.throttle mylimit 15 30 60
1)(integer)0 # 0 表示获取成功,1 表示拒绝
2)(integer)15 # 漏斗容量
3)(integer)14 # 漏斗剩余容量
4)(integer)-1 # 被拒绝之后,多长时间之后再试(单位:秒)-1 表示无需重试
5)(integer)2 # 多久之后漏斗完全空出来其中 15 为漏斗的容量,30 / 60s 为漏斗的速率
使用 guava 实现的令牌算法属于程序级别的单机限流方案,而上面使用 Redis-Cell 的是分布式的限流方案
3、分布式限流算法
分布式限流算法的是指: 算法可以分布式部署在多台机器上面,多台机器协同提供限流功能,可以对同一接口或者服务做限流。分布式限流算法相较于单机的限流算法,最大的区别就是接口请求计数器需要中心化存储
基于 Redis 中心计数器来实现分布式限流算法。
单机限流的初衷是防止突发流量压垮服务器,所以比较适合针对并发做限制。分布式限流适合做细粒度限流或者访问配额,不同的调用方对不同的接口执行不同的限流规则
1. 数据一致性问题:做好幂等性,保证接口的原子性操作。通过分布式锁来判断,来保证同一时间段内只有一个进程访问。Redis单线程模式+Lua脚本
2. 超时问题:防止Redis的访问超时,需要设定合理的超时时间,一旦超时,判断为限流失效,继续进行接口逻辑,减少对接口的响应时间的影响或导致接口响应超时
3. 性能问题:需要解决Redis等性能问题
分布式限流与微服务之间常见的部署架构有以下几种:
1. 在接入层(api-gateway)集成限流功能
这种集成方式是在微服务架构下,有 api-gateway 的前提下,最合理的架构模式。如果 api-gateway 是单实例部署,使用单机限流算法即可。如果 api-gateway 是多实例部署,为了做到服务级别的限流就必须使用分布式限流算法。
2. 限流功能封装为 RPC 服务
当微服务接收到接口请求之后,会先通过限流服务暴露的 RPC 接口来查询接口请求是否超过限流阈值。这种架构模式,需要部署一个限流服务,增加了运维成本。这种部署架构,性能瓶颈会出现在微服务与限流服务之间的 RPC 通信上,即便单机限流算法可以做到 200 万 TPS,但经过 RPC 框架之后,做到 10 万 TPS 的请求限流就已经不错了。
3. 限流功能集成在微服务系统内
这种架构模式不需要再独立部署服务,减少了运维成本,但限流代码会跟业务代码有一些耦合,不过,可以将限流功能集成在切面层,尽量跟业务代码解耦。如果做服务级的分布式限流,必须使用分布式限流算法,如果是针对每台微服务实例进行单机限流,使用单机限流算法就可以。
4、限流规则
时间粒度
接口粒度
最大限流值
高容错高性能开源限流框架:ratelimiter4j
参考地址:https://www.infoq.cn/article/microservice-interface-rate-limit
参考地址:https://mp.weixin.qq.com/s/cA_lG9uEdACUhD6PuZguxA