作者:大鱼炖海棠
链接:https://www.jianshu.com/p/2c6e7852e732
系统设计时一般会预估负载,当系统遭受恶意攻击或正常突发流量等都可能导致系统被压垮,而限流就是保护措施之一。
一、限流算法介绍
令牌桶算法
image算法思想是:
- 令牌以固定速率产生,并缓存到令牌桶中;
- 令牌桶放满时,多余的令牌被丢弃;
- 请求要消耗等比例的令牌才能被处理;
- 令牌不够时,请求被缓存。
漏桶算法
image算法思想是:
- 水(请求)从上方倒入水桶,从水桶下方流出(被处理);
- 来不及流出的水存在水桶中(缓冲),以固定速率流出;
- 水桶满后水溢出(丢弃)。
这个算法的核心是:缓存请求、匀速处理、多余的请求直接丢弃。
漏桶和令牌桶算法最明显的区别在于是否允许突发流量(burst)的处理,漏桶算法能够强行限制数据的实时传输(处理)速率,对突发流量不做额外处理;而令牌桶算法能够在限制数据的平均传输速率的同时允许某种程度的突发传输。
二、Nginx 限流
Nginx 提供两种限流方式,一是控制速率,二是控制并发连接数:
-
limit_req_zone
模块用来限制单位时间内的请求数,即速率限制,采用的漏桶算法 "leaky bucket"。 -
limit_req_conn
模块用来限制同一时间连接数,即并发限制。
控制速率
ngx_http_limit_req_module 模块提供限制请求处理速率能力,它能够有效针对同一个 IP 反复请求服务器,如洪水攻击或者 DDos 攻击。下面例子使用 nginx 的 limit_req_zone 和 limit_req 两个指令,限制单个IP的请求处理速率。
在 nginx.conf 的 http 域中添加限流配置:
# 格式:limit_req_zone key zone rate
http {
limit_req_zone $binary_remote_addr zone=myRateLimit:10m rate=10r/s;
}
# 配置 server,使用 limit_req 指令应用限流。
server {
location / {
limit_req zone=myRateLimit;
proxy_pass http://my_upstream;
}
}
- $binary_remote_addr:定义限流对象,binary_remote_addr 是一种 key,表示基于 remote_addr(客户端 IP) 来做限流,binary_ 的目的是压缩内存占用量。
- zone:定义共享内存区来存储访问信息, myRateLimit:10m 表示一个大小为10M,名字为myRateLimit的内存区域。1M 内存能存储16000个 IP 地址的访问信息,10M 内存可以存储 16W个 IP地址访问信息。
- rate:用于设置最大访问速率,rate=10r/s 表示每秒最多处理10个请求。Nginx 实际上以毫秒为粒度来跟踪请求信息,因此 10r/s 实际上是限制:每100毫秒处理一个请求。这意味着,自上一个请求处理完后,若后续100毫秒内又有请求到达,将拒绝处理该请求。
-
zone=myRateLimit
设置使用哪个配置区域来做限制,与 limit_req_zone 里的配置对应
处理突发流量
上面例子限制 10r/s,正常流量稍微增大,请求就会被拒绝,面对突发流量,可以结合 burst 参数使用来解决该问题。
server {
location / {
limit_req zone=myRateLimit burst=20;
proxy_pass http://my_upstream;
}
}
burst 译为突发、爆发,表示在超过设定的处理速率后能额外处理的请求数。当 rate=10r/s 时,将1s拆成10份,即每100ms可处理1个请求。此处,burst=20,若同时有20个请求到达,Nginx 会处理第一个请求,剩余19个请求将放入队列,然后每隔100ms从队列中获取一个请求进行处理。若请求数大于20,将拒绝处理多余的请求,直接返回 503
不过,单独使用 burst 参数并不实用。假设 burst=50,rate依然为10r/s,排队中的50个请求虽然每100ms会处理一个,但第50个请求却需要等待 50 * 100ms即 5s,这么长的处理时间自然难以接受。因此,burst 往往结合 nodelay 一起使用。
server {
location / {
limit_req zone=myRateLimit burst=20 nodelay;
proxy_pass http://my_upstream;
}
}
nodelay
针对的是 burst 参数,burst=20 nodelay 表示这20个请求立马处理,不能延迟,相当于特事特办。不过,即使这20个突发请求立马处理结束,后续来了请求也不会立马处理。burst=20 相当于缓存队列中占了20个坑,即使请求被处理了,这20个位置这只能按 100ms 一个来释放。
这就达到了速率稳定,但突然流量也能正常处理的效果。
限制连接数
ngx_http_limit_conn_module 提供了限制连接数的能力,利用 limit_conn_zone 和 limit_conn 两个指令即可。下面是 Nginx 官方例子:
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
作用的 key 是 $binary_remote_addr,表示限制单个 IP 同时最多能持有10个连接。 -
limit_conn perserver 100
作用的 key 是 $server_name,表示虚拟主机(server) 同时能处理并发连接的总数。
需要注意的是:只有当 request header 被后端 server 处理后,这个连接才进行计数。
设置白名单
限流主要针对外部访问,内网访问相对安全,可以不做限流,通过设置白名单即可。利用 Nginx ngx_http_geo_module 和 ngx_http_map_module 两个工具模块即可搞定。
在 nginx.conf 的 http 部分中配置白名单:
geo $limit {
default 1; // key=default, value=1
10.0.0.0/8 0;
192.168.0.0/24 0;
172.20.0.35 0;
include conf/whiteip.conf; // 支持白名单以 key:value的形式存储在配置文件中
}
map $limit $limit_key {
0 "";
1 $binary_remote_addr; // value=1则返回 $binary_remote_addr,也就是IP,否则返回空字符串
}
limit_req_zone $limit_key zone=myRateLimit:10m rate=10r/s;
- geo 定义了子网或 IP 与 0、1 的映射关系。上述配置中,10.0.0.0/8 网段的 IP 映射到 0,而其他 IP 缺省映射到 1;
- 来访 IP 通过 geo 进行映射,映射结果是 0,表明该 IP 被列入白名单,经 map 转换返回 "" 空字符串;如果映射结果是1,则返回 $binary_remote_addr,即客户端实际 IP。
- limit_req_zone 限流采用的 key 不再是 binary_remote_addr,而是采用 $limit_key 动态获取值。如果是白名单,limit_req_zone 的限流 key 则为空字符串,将不会限流;若不是白名单,将会对客户端真实 IP 进行限流。
网友评论