有两种限流方式:
- tomcat限流:配置最大线程数
- nginx限流:一是控制速率,二是控制并发连接数
tomcat限流
spring项目直接可以在配置文件中设置
- 使用
application.properties:src/main/resources/application.properties
文件中添加以下配置server.tomcat.max-threads=200 # 设置最大线程数为 200
- 使用 application.yml:在
src/main/resources/application.yml
中添加:server: tomcat: max-threads: 200 # 设置最大线程数为 200
nginx限流
控制速率:
- 使用
limit_req_zone
用来限制单位时间内的请求数,即速率限制,- 示例配置如下:
- 配置表示,限制每个 IP 访问的速度为 2r/s,因为 Nginx 的限流统计是基于毫秒的,我们设置的速度是 2r/s,转换一下就是 500ms 内单个 IP 只允许通过 1 个请求,从 501ms 开始才允许通过第 2 个请求。
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s; server { location / { limit_req zone=mylimit; } }
-
burst 关键字
:真实情况下我们应该控制一个 IP 单位总时间内的总访问次数,而不是像上面那么精确但毫秒,我们可以使用 burst 关键字开启此设置,它表示在限速时,允许的额外请求数量。 - 示例配置如下:
-
burst=4 表示每个 IP 最多允许4个突发请求
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s; server { location / { limit_req zone=mylimit burst=4; } }
控制并发数
- 利用 limit_conn_zone 和 limit_conn 两个指令即可控制并发数
- 示例配置如下:
- 其中 limit_conn perip 10 表示限制单个 IP 同时最多能持有 10 个连接;
- 只有当 request header 被后端处理后,这个连接才进行计数。
- limit_conn perserver 100 表示 server 同时能处理并发连接的总数为 100 个。
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;
}
服务端限流算法
常见的限流算法有三种:
- 计数器限流:主要用来限制总并发数,比如数据库连接池大小、线程池大小、接口访问并发数等都是使用计数器算法
-
漏桶算法:
- 思路:漏桶算法思路很简单,我们把水比作是请求,漏桶比作是系统处理能力极限,水先进入到漏桶里,漏桶里的水按一定速率流出,当流出的速率小于流入的速率时,由于漏桶容量有限,后续进入的水直接溢出(拒绝请求),以此实现限流
-
令牌桶算法:可以理解成医院的挂号看病,只有拿到号以后才可以进行诊病
- 系统会维护一个令牌(token)桶,以一个恒定的速度往桶里放入令牌(token),这时如果有请求进来想要被处理,则需要先从桶里获取一个令牌(token),当桶里没有令牌(token)可取时,则该请求将被拒绝服务。
- 令牌桶算法通过控制桶的容量、发放令牌的速率,来达到对请求的限制。
- 和漏桶算法区别
- 漏桶的天然特性决定了它不会发生突发流量,就算每秒1000个请求到来,那么它对后台服务输出的访问速率永远恒定。
- 而令牌桶则不同,其特性可以“预存”一定量的令牌,因此在应对突发流量的时候可以在短时间消耗所有令牌,其突发流量处理效率会比漏桶高,导向后台系统的压力也会相应增多。
- 滑动窗口:
计数器限流(固定窗口算法)
计数器限流算法也是比较常用的,主要用来限制总并发数,比如数据库连接池大小、线程池大小、程序访问并发数等都是使用计数器算法。也是最简单粗暴的算法。
常用的三个方法如下:
-
采用AtomicInteger:使用AomicInteger来进行统计当前正在并发执行的次数,如果超过域值就简单粗暴的直接响应给用户,说明系统繁忙,请稍后再试或其它跟业务相关的信息。
- 弊端:使用 AomicInteger 简单粗暴超过域值就拒绝请求,可能只是瞬时的请求量高,也会拒绝请求。
-
采用令牌Semaphore:使用Semaphore信号量来控制并发执行的次数,如果超过域值信号量,则进入阻塞队列中排队等待获取信号量进行执行。如果阻塞队列中排队的请求过多超出系统处理能力,则可以在拒绝请求。
- 相对Atomic优点:如果是瞬时的高并发,可以使请求在阻塞队列中排队,而不是马上拒绝请求,从而达到一个流量削峰的目的。
- 采用ThreadPoolExecutor java线程池:固定线程池大小,超出固定先线程池和最大的线程数,拒绝线程请求
滑动窗口
滑动窗口算法是对固定窗口算法的改进
滑动窗口计数器(Sliding Window)算法限流:解决固定窗口临界值的问题。它将单位时间周期分为n个小周期,分别记录每个小周期内接口的访问次数,并且根据时间滑动删除过期的小周期。
如下图
- 每 500ms 滑动一次窗口,可以发现窗口滑动的间隔越短,时间窗口的临界突变问题发生的概率也就越小,
- 不过只要有时间窗口的存在,还是有可能发生时间窗口的临界突变问题。
漏桶算法
漏桶的桶有大小,就如队列的容量,当请求堆积超过指定容量时,会触发拒绝策略。
漏桶模式中的消费处理总是能以恒定的速度进行,可以很好的保护自身系统不被突如其来的流量冲垮
令牌桶算法
最为常用的 Google 的 Java 开发工具包 Guava 中的限流工具类 RateLimiter 就是令牌桶的一个实现。令牌桶的实现思路类似于生产者和消费之间的关系。
系统服务作为生产者,按照指定频率向桶(容器)中添加令牌,如 QPS 为 2,每 500ms 向桶中添加一个令牌,如果桶中令牌数量达到阈值,则不再添加。
- 1s / 阈值(QPS) = 令牌添加时间间隔。
请求执行作为消费者,每个请求都需要去桶中拿取一个令牌,取到令牌则继续执行;如果桶中无令牌可取,就触发拒绝策略,可以是超时等待,也可以是直接拒绝本次请求,由此达到限流目的