前几天公司的统计出现了问题:大致是我们自己统计模块的数据和第三方的数据出现了比较大的偏差——公司的统计量级异常的大。我们怀疑有人直接拿上报接口去刷量,如果服务器性能撑的过去的话数据不准了还好,但万一刷量过大,击垮了服务器,这就是典型的ddos啊。于是我们把这个问题排上了日程。

我选择的是通过对单一IP进行限流,也就是标题所述,展开来讲就是通过nginx服务器自身的模块(ngx_http_limit_req_module/ngx_http_limit_conn_module),来对单个IP进行限流,达到溢出请求在nginx层直接过滤掉的效果。

ngx_http_limit_req_module模块

此模块下的功能是限制单一IP每秒访问速率,主要语法为:

limit_req_zone  key  zone  rate 
key: 定义限流对象,$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毫秒内又有请求到达,将拒绝处理 该请求。

这句话我们一般会选择在nginx.conf里的http去定义,定义好的参数我们会拿到单个项目的server下去使用,举例这样定义:

limit_req_zone $binary_remote_addr  zone=mylimit:10m rate=2r/s;

这句话我们翻译过来就是:定义一个叫mylimit的共享内存区,这个内存区大约10m,大约能存16w的ip,这个规则ip访问速率不能超过每秒2次。

那么我们定义好了这个共享内存区,怎么使用呢,这就是我上面说的“拿到单个项目的server下去使用”,第二句的语法如下:

limit_req zone= zone_name (burst=n nodely)
zone_name:  http中定义好的共享内存区名
burst:  在超过设定的处理速率后能额外处理的请求数,这个参数相当于一个漏桶(后期讲解),等到有突发流量的进入这个桶之
后再实时传输。=n为桶内可以最多存放的请求数
nodely:表示漏桶里面的请求会立马处理,不能延迟,相当于特事特办。不过,即使这20个突发请求立马处理结束,后续来了请求
也不会立马处理。burst=20 相当于缓存队列中占了20个坑,即使请求被处理了,这20个位置这只能按 100ms一个来释放。这就达
到了速率稳定,但突然流量也能正常处理的效果。

定义好了共享内存区之后,我们就可以在项目server里去执行这个规则:

limit_req zone=mylimit burst=5 nodely

设置完毕之后我们重启nginx服务器就会执行最新的规则:每个IP用户每秒钟最多访问2+7七个请求的速率进行请求

IP速率限流溢出是系统默认是报403错误,如果我们像自己设立返回不同的状态码可以使用limits_req_status xxx 比如limits_req_status 504(放在单个项目的server下),那么溢出后返回的状态码就是504。

这里我借用一个博主的抢购项目案例

limit_req_zone $server_name zone=sname:10m rate=1r/s;        #限制服务器每秒只能有一次访问成功

server {
listen 80;
server_name www.abc.com;
location / {
include host/proxy.cnf;
proxy_pass http://backend;
}
location /api/createOrder {
limit_req zone=sname; #不带突发,只能有一次正常请求
limit_req_status 503; #设置返回的状态码是503
#limit_req zone=sname burst=5 nodelay; #最大并发是5,并且实时处理
include host/proxy.cnf;
proxy_pass http://backend;
error_page 503 =200 /50x.html; #这里很重要,可以将错误的状态码503,返回结果的时候是200
}
location = /50x.html {
if ($http_user_agent ~* "mobile|android|iPhone|iphone|ios|iOS"){
#default_type application/json;
return 200 '{"msg": "活动过于火爆,请稍后重试!","data": {},"code": -1}'; #设置移动端返回错误的信息显示
}
root html; #如果是PC端返回一个HTML页面
}
}

正常情况下,如果设置了限流,返回是503的状态码,这对于移动端来说即便是你返回JSON数据但是客户端时不认的,这个时候巧妙的通过 error_page 403 =200 /50x.html;将状态码设置为200

案例参考:https://gist.github.com/simlegate/75b18359316cc33d8e20

ngx_http_limit_conn_module模块

此模块下的功能是限制单一IP并发连接数,主要语法为:

limit_conn_zone $binary_remote_addr zone=zone_name1:10m //定义

limit_conn_zone $server_name zone=zone_name2:10m //定义

server{

limit_conn perip 20 //使用 : 对应的key是 $binary_remote_addr,表示限制单个IP同时最多能持有20个连接。

limit_conn perserver 100 //使用 : 对应的key是 $server_name,表示虚拟主机(server) 同时能处理并发连接的总数。注意,只有当 request header 被后端server处理后,这个连接才进行计数。

}