浅析Nginx限流

冰岩作坊 September 25, 2024

限流的思想

在保证可用的情况下尽可能多增加进入的人数,其余的人在排队等待,或者返回友好提示,保证里面的进行系统的用户可以正常使用,防止系统雪崩。

比如,在校园里面体测的时候,每次只放一部分的人进去,保证里面的人可以顺利完成体测,体现可用性,同时还有很多在外面等待的同学,这体现了限流。

那么在日常生活中,还有哪些需要限流的地方呢?

比如有一个国家景区,平时可能根本没什么人前往,但是一到五一或者春节就人满为患,这时候景区管理人员就会实行一系列的政策来限制进入人流量,那么为什么要限流呢?

假如景区能容纳一万人,现在进去了三万人,势必摩肩接踵,整不好还会有事故发生,这样的结果就是所有人的体验都不好,如果发生了事故景区可能还要关闭,导致对外不可用,这样的后果就是所有人都觉得体验糟糕透了。

限流的算法

限流算法很多,常见的有三类,分别是计数器算法、漏桶算法、令牌桶算法,下面逐一讲解。

(1)计数器:

在一段时间间隔内(时间窗/时间区间),处理请求的最大数量固定,超过部分不做处理。

(2)漏桶:

漏桶大小固定,处理速度固定,但请求进入速度不固定(在突发情况请求过多时,会丢弃过多的请求)。

(3)令牌桶:

令牌桶的大小固定,令牌的产生速度固定,但是消耗令牌(即请求)速度不固定(可以应对一些某些时间请求过多的情况);每个请求都会从令牌桶中取出令牌,如果没有令牌则丢弃该次请求。

计数器算法

计数器限流定义

在一段时间间隔内(时间窗/时间区间),处理请求的最大数量固定,超过部分不做处理。

简单粗暴,比如指定线程池大小,指定数据库连接池大小、nginx 连接数等,这都属于计数器算法。

计数器算法是限流算法里最简单也是最容易实现的一种算法。

举个例子,比如我们规定对于 A 接口,我们 1 分钟的访问次数不能超过 100 个。

那么我们可以这么做:

在一开始的时候,我们可以设置一个计数器 counter,每当一个请求过来的时候,counter 就加 1,如果 counter 的值大于 100 并且该请求与第一个请求的间隔时间还在 1 分钟之内,那么说明请求数过多,拒绝访问;

如果过了这个时间戳,那么就重置 counter,就是这么简单粗暴。

漏桶算法

漏桶算法限流的基本原理为:水(对应请求)从进水口进入到漏桶里,漏桶以一定的速度出水(请求放行),当水流入速度过大,桶内的总水量大于桶容量会直接溢出,请求被拒绝。

大致的漏桶限流规则如下:

漏桶算法原理

漏桶算法思路很简单:

水(请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会超过桶可接纳的容量时直接溢出。

可以看出漏桶算法能强行限制数据的传输速率。

漏桶算法其实很简单,可以粗略的认为就是注水漏水过程,往桶中以任意速率流入水,以一定速率流出水,当水超过桶容量则丢弃,因为桶容量是不变的,保证了整体的速率。

算法特点:

漏桶算法通过限制流量的入口速率,能有效应对突发流量,将突发流量平滑化,起到平滑突发流量(整流)的作用,以防止资源被瞬间耗尽。

令牌桶限流

令牌桶算法以一个设定的速率产生令牌并放入令牌桶,每次用户请求都得申请令牌,如果令牌不足,则拒绝请求。

令牌桶算法中新请求到来时会从桶里拿走一个令牌,如果桶内没有令牌可拿,就拒绝服务。当然,令牌的数量也是有上限的。令牌的数量与时间和发放速率强相关,时间流逝的时间越长,会不断往桶里加入越多的令牌,如果令牌发放的速度比申请速度快,令牌桶会放满令牌,直到令牌占满整个令牌桶。

令牌桶限流大致的规则如下:

总之,令牌的发送速率可以设置,从而可以对突发的出口流量进行有效的应对。

令牌桶算法

令牌桶与漏桶相似,不同的是令牌桶桶中放了一些令牌,服务请求到达后,要获取令牌之后才会得到服务,举个例子,我们平时去食堂吃饭,都是在食堂内窗口前排队的,这就好比是漏桶算法,大量的人员聚集在食堂内窗口外,以一定的速度享受服务,如果涌进来的人太多,食堂装不下了,可能就有一部分人站到食堂外了,这就没有享受到食堂的服务,称之为溢出,溢出可以继续请求,也就是继续排队,那么这样有什么问题呢?

如果这时候有特殊情况,比如有些人赶时间,如果也用漏桶算法那也得慢慢排队,这也就没有解决我们的需求,对于很多应用场景来说,除了要求能够限制数据的平均传输速率外,还要求允许某种程度的突发传输。这时候漏桶算法可能就不合适了,令牌桶算法更为适合。

令牌桶的好处

令牌桶的好处之一就是可以方便地应对 突发出口流量(后端能力的提升)。

比如,可以改变令牌的发放速度,算法能按照新的发送速率调大令牌的发放数量,使得出口突发流量能被处理。

Nginx 漏桶限流

Nginx 限流的简单配置如下:

1
limit_req_zone  $binary_remote_addr  zone=test:10m      rate=6r/s; #每一秒处理六次请求

在 location 块中使用限流 zone,参考如下:

1

1
2
3
4
location  = / {
limit_req zone=test;
echo "正常的响应";
}
1

Nginx 漏桶限流的三个细分类型,即 burst、nodelay 参数详解

不带缓冲队列的漏桶限流

仔细看的话,就会发现这个命名空间后面的:没有了,那个是用来设置缓冲空间大小的。

1
limit_req $binary_remote_addr zone=test rate=1r/s;
1

严格依照在 test 中配置的 rate 来处理请求,超过 rate 处理能力范围的,直接 drop,表现为对收到的请求无延时,假设 1 秒内提交 10 个请求,可以看到一共 10 个请求,9 个请求都失败了,直接返回 503。

带缓冲队列的漏桶限流

1
limit_req $binary_remote_addr zone=test burst=5 rate=1r/s;
1

依照在 test 中配置的 rate 来处理请求,同时设置了一个大小为 5 的缓冲队列,在缓冲队列中的请求会等待慢慢处理,超过了 burst 缓冲队列长度和 rate 处理能力的请求被直接丢弃,表现为对收到的请求有延时。

假设 1 秒内提交 10 个请求,则可以发现在 1s 内,在服务器接收到 10 个并发请求后,先处理 1 个请求,同时将 5 个请求放入 burst 缓冲队列中,等待处理。而超过(burst+1)数量的请求就被直接抛弃了,即直接抛弃了 4 个请求。burst 缓存的 5 个请求每隔 6s 处理一次。

带瞬时处理能力的漏桶限流

1
limit_req $binary_remote_addr zone=test burst=5 rate=1r/s nodelay;

如果设置 nodelay,会在瞬时提供处理(burst + rate)个请求的能力,请求数量超过(burst + rate)的时候就会直接返回 503,峰值范围内的请求,不存在请求需要等待的情况。

简单的说就是,接受的请求处理的时候 nodelay,不接受的请求拒绝的时候也是 nodelay。

假设 1 秒内提交 10 个请求,则可以发现在 1s 内,服务器端处理了 6 个请求(峰值速度:burst + 1s 内一个请求)。对于剩下的 4 个请求,直接返回 503,在下一秒如果继续向服务端发送 10 个请求,服务端会直接拒绝这 10 个请求并返回 503。

总数额度要和速度时间保持一致, 就是额度用完了,需要等到一个有额度的时间段,才开始接收新的请求。如果一次处理了 5 个请求,相当于占了 5s 的额度。因为设定了 1s 处理 1 个请求,所以直到 5s 之后,才可以再处理一个请求,即如果此时向服务端发送 10 个请求,会返回 9 个 503,一个 200。

尾声

什么什么,还想知道更多关于 Nginx 的有趣小知识,那么不妨去官网看看吧。

https://nginx.org/en/docs/

什么什么,你觉得 Nginx 的功能不是很多,每次都要自己加模块太麻烦,那么不妨试试 OpenResty 吧,一个使用 Lua 脚本的增强版 Nginx,你还可以自己使用 Lua 脚本给 Nginx 来做二次开发,编写更多适合业务的小功能哦。

https://openresty.org/cn/

#负载均衡