ngnix的限流模块主要有三个:

  • limit_conn 限制某个ip的tcp连接数目或者限制某个server(网站)整体的连接数目
  • limit_rate 现在每个请求的数据大小
  • limit_req 限制某个ip的请求次数

其中效果最明显的是第三个limit_req,但是宝塔面板中的流量限制只有前面两项的配置,所以之前一直流量限制没什么用。

tcp连接数目和请求数目的区别

tcp连接建立是需要三次握手的,是有一定的耗时的。就像打电话一样,得先拨通电话,两方才能讲话交流(请求资源),自然是tcp链接越少越好。

那访问一个网站,到底会进行几个tcp连接?

如果你的站点是http1.1,连接数目 = 请求数目/ <浏览器允许并发的数目>
如果你的站点是http2.0 连接数目 =1

http1.1 默认开启keep-alive 特性,支持tcp持久连接,但是由于浏览器限制了并发数目,所以连接太多,仍然会进行再次的tcp连接。1关于<浏览器允许的并发数目>可以参考浏览器允许的并发请求资源数是什么意思? - 王納米的回答

而http2引入了多路复用和二进制帧分层特性,允许所有的请求来自同一个tcp。后续有时间可以在详细的写写http2的优势。

需要注意的是http2.0只适用于https的站点,2并且需要在服务器端进行配置3
http站点仍然使用http1.1协议。

下面是nginx配置配置文件中配置http2的一个例子:

server
{
    listen 80;
    listen 443 ssl http2;
}

在chrome控制面板中可以看到当前网站建立的tcp连接的id,使用http2后,除了外部资源,本站资源的请求只建立一个tcp链接。

15986699997354.jpg

在你自己的服务器上,同样可以使用下面的命令查看tcp连接的连接情况:

netstat -anlp|grep tcp|awk '{print $5}'|awk -F: '{print $1}'|sort|uniq -c|sort -nr|head -n20 ;

netstat -anlp是linux查看网络情况的一个命令,过滤只显示与tcp相关的,但这里显示的是整个服务器的tcp连接,而不只是其中的某一个网站。


请求数目就是你在chrome面板中看到的request num。当然如果不是你自己站点的资源的请求数目不会占用你的服务器的带宽资源。


总结:如果你是https站点并且配置了http2,那个一个tcp连接 约等于 一个真实的人
而一个真实的人会产生很多个请求数目。

limit_req 模块

limit_req 模块用来限制一个ip的请求数目。

在nginx的http字段下配置:

limit_req_zone $binary_remote_addr zone=www_sym:10m rate=20r/s;

在某个网站下的server字段下配置,或者在下面的location字段配置:

limit_req zone=www_sym burst=20 nodelay;

这里面有几个变量需要说明一下:

  • zone: 后面的www_sym是域名称,可以随便取一个,下面对应就可以了,冒号后面的10m 表示了这个域的大小,即该模块需要开辟一个内存空间是缓存nginx的请求记录中的$binary_remote_addr,以便匹配出请求速率是否超过了指定的rate
  • rate: 这个是1秒中允许的请求数目,这个地方很容易被误解导致配置错误。比如rate设置为20r/s,即在0~50ms只允许有一个请求,因为nginx是毫秒级的速率控制,这个地方的rate实际上是毫秒级的匀速控制。但实际上我们的站点的请求都是突发流量,即在短时间内很多个请求并发的。所以需要使用到burst来承接突发流量。
  • burst: 可以理解为一个缓冲队列,假设值为20,假设0~50ms中有21个请求,那么其中20个请求就会进入该队列。
  • nodelay这个参数同样是非常容易错误理解的。nodelay即无延迟。以上面的例子为例,20个请求进入缓存队列后,会马上转发给nginx请求数据,获取到数据后返回。需要注意的是一个请求出队列后,该位置(插槽)并非立即释放的。 而是同样按照50ms的间隔(rate设置为20r/s)释放。也就是说,如果在51~100ms的期间又发送了21个请求,那只有2个请求可以成功返回(因为只有一个插槽被释放了),剩余的19个请求会立即返回503错误码。

也就是说,如果按照上面的配置(rate+burst+nodelay同时配置了),在0~1000ms中的任意时刻,有21个突发的并发请求都能够正常的处理,如果大于21个请求,就会直接返回503错误码,告诉客户端当前nginx无法处理该请求。

强烈推荐设置nodelay参数,也可以不设置,则20个请求进入缓冲队列后并不会马上全部转发,而是按照50ms的间隔进行请求和响应(此时该缓冲队列的位置也是按照这个间隔释放)**,那么客户端收到所有21个请求的响应至少是1s后了,这个延迟就大大增加了,是非常不可取的。

如果不设置burst参数,对我们这种突发性请求的应用是非常不适合的。21个请求其中20个请求就会直接返回503,导致站点的资源无法加载的问题。

这部分如果有更深入学习,可以参考下面的文章:

在http字段下配置下面的设置,会将超流的内容记录到error.log 下。

limit_req_log_level error;

某个超流的日志如下:

2020/08/25 18:09:49 [error] 10215#0: *2445032 limiting requests, excess: 10.700 by zone "www_sym", client: 120.*.3*.29, server: ***.com, request: "GET /RelatedObjectLookups.js HTTP/2.0", host: "***.com", referrer: "https://***/admin"

如果该网站是反向代理,当上游网站无法访问,此时的日志如下:

2022/04/24 20:44:50 [error] 12985#0: *9662214 upstream timed out (110: Connection timed out) while connecting to upstream, client: 111.27.24.183, server: auth.ihewro.com, request: "GET /notice/version HTTP/2.0", upstream: "http://*****:8000/notice/version", host: "auth.ihewro.com", referrer: ""

如果大规模的ip访问会导致php无法建立socket,因此错误日志如下:

2022/04/24 21:49:21 [error] 12985#0: *9953796 connect() to unix:/tmp/php-cgi-74.sock failed (11: Resource temporarily unavailable) while connecting to upstream, client: 23.12.64.219, server: www.ihewro.com, request: "GET / HTTP/1.1", upstream: "fastcgi://unix:/tmp/php-cgi-74.sock:", host: "www.ihewro.com", referrer: "https://www.ihewro.com"

limit_conn 模块

limit_conn 用来限制一个ip或者整个站点的tcp连接数目。

注意,在http/2 中,每个并发的请求被认为是一个连接!!!这点非常重要

http_limit_conn 官方文档

在http字段配置:

limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;

在server字段下配置:

limit_conn perserver 200;
limit_conn perip 20;

上面中200表示一个server站点最多连接数。20表示一个ip的最多连接数目。

如果你的站点配置了http2,正常情况下一个用户的tcp连接数目不会超过5个。可以根据具体请求进行配置。

在http字段下配置下面的设置,会将超流的内容记录到error.log 下。

limit_conn_log_level error;

某个超流的日志如下:

2020/08/25 18:09:49 [error] 10216#0: *2445033 limiting connections by zone "www_sym", client: 120.2.33.29, server: ***.com, request: "GET /RelatedObjectLookups.js HTTP/2.0", host: "***.com", referrer: "https://***/admin"

limit_rate 模块

在server字段下配置:

limit_rate 512k;

上面的512k表示限制一个请求的大小不超过512kB。

fail2ban的使用

fail2ban 软件如其名,就是根据错误日志的匹配次数来进行ban的操作。不仅仅可以用来扫描nginx的日志,可以扫描任何日志,可以自定义filter正则表达式匹配上就可以。ban的操作也不仅仅是iptabels 来禁止,可以自定义action来进行处理。

安装

# CentOS
yum install -y fail2ban
# ubuntu使用apt的系统
sudo apt-get install -y fail2ban

配置

安装完成后,进入/etc/fail2ban,可以看到下面的目录,分别介绍他们的作用如下4

  • action.d: 符合ban的条件后的操作
  • filter.d: 过滤器,即告诉fail2ban 如何匹配上日志中的某一行
  • jail.local: jail即监狱,在该文件里面配置一个或监狱,定义该监狱的名称,监视的log文件列表,filer,以及符合条件后的action
  • jail.conf: 这个是官方提供的一份多个监狱例子,你可以直接复制你需要的部分到jail.local中即可。

使用

编辑文件,nano /etc/fail2ban/jail.local,增加一个新的监狱:

[nginxcc]
enabled  = true
filter   = nginx-limit-req
logpath  = /www/wwwlogs/***1.com.error.log
        /www/wwwlogs/***2.com.error.log
maxretry = 120
findtime = 60
bantime  = 120000
action   = iptables-allports[name=nginxcc]
           sendmail-whois-lines[name=nginxcc, dest=ihewro@163.com]
  • 最开头的nginxcc是监狱的名称
  • filter 使用的是fail2ban自带的一个过滤器,文件路径为/etc/fail2ban/filter.d/nginx-limit-req.conf
  • logpath 即监视的日志列表,这里我是监视errorlog,而不是access.log的,原因是当流量很大的时候,access.log日志刷新的很快,会导致fail2ban跟不上(之前发现明明已经ban掉了,但是日志上还是显示在匹配已经ban掉的ip的访问的日志,就很奇怪,理论上匹配的速度应该很快,但不知道为什么会出现这种情况)(所以我们需要设置好limit_req_log_levellimit_conn_log_level为error)
  • findtime=60 maxretry=120 指在60s的时间段内如果有某个ip120次超流的记录,就会封禁
  • bantime的单位是s
  • action 封禁的操作是使用iptables工具,这个action,fail2ban 已经为我们写好了,路径在/etc/fail2ban/action.d/iptables-allports.conf

还需要注意的是,/etc/fail2ban/filter.d/nginx-limit-req.conf只匹配了limit_req模块限流的日志,并没有匹配limit_conn模块限流的日志,所以我们编辑该文件,增加一个新的正则匹配,一个匹配规则一行,如果是多个匹配规则,则为多行(官方文档filter例子)。将failregex的值改为:

failregex = ^\s*\[[a-z]+\] \d+#\d+: \*\d+ .*, client: <HOST>,

此时可以通过下面的命令,测试你的regex能否正常的匹配到你的日志内容:

fail2ban-regex /etc/fail2ban/filter.d/test.log /etc/fail2ban/filter.d/nginx-limit-req.conf --print-all-matched

--print-all-matched参数用来显示所有匹配上的行,去掉该参数可以显示总体的匹配情况。


除此之外,你也许注意到了action中还有一个sendmail的配置。配置了该项后,当该监狱启用、停止或者ban某个ip的时候,都会给你的dest邮箱地址发送一个邮件。前提是你的服务器配置好了sendemail模块。

15986868197773.jpg

默认的邮件地址是fail2ban@<hostname>,邮件的格式在/etc/fail2ban/action.d/sendmail-whois-lines.conf 文件中。

还有一个需要注意的问题是,iptables 重启会失效,重启后的你的封禁的ip都会丢失,可以修改/etc/fail2ban/action.d/iptables-allports.conf,修改后的代码如下:

[INCLUDES]
before = iptables-common.conf

[Definition]

actionstart = <iptables> -N f2b-<name>
              <iptables> -A f2b-<name> -j <returntype>
              <iptables> -I <chain> -p <protocol> -j f2b-<name>


actionstop = <iptables> -D <chain> -p <protocol> -j f2b-<name>
             <actionflush>
             <iptables> -X f2b-<name>

actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'

actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype> && service iptables save


actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype> && service iptables save

[Init]

即在iptables 操作后面增加service iptables save 保存记录。

查看

# 查看监狱工作时候的filter和ban的日志
tail -f /var/log/fail2ban.log
#启动
systemctl start fail2ban
#停止
systemctl stop fail2ban
# 重新启动
systemctl restart fail2ban
#开机启动
systemctl enable fail2ban
# 查看fail2ban模块的工作状况(一般排查错误原因的时候使用)
journalctl -r -u fail2ban.service

# 查看监狱列表
fail2ban-client status
# 查看某个监狱下的封禁情况
fail2ban-client status nginxcc
#删除某个监狱下的某个ip
fail2ban-client set nginxcc unbanip 192.168.1.115
#手动封禁某个ip
fail2ban-client set nginxcc banip 192.168.1.115

最后记得定时清理你的error.log以及fail2ban.log的日志,可以写一个定时任务,每隔半小时清理一次

最后修改:2022 年 05 月 14 日
喜欢我的文章吗?
别忘了点赞或赞赏,让我知道创作的路上有你陪伴。