nginx|Nginx之访问限制模块之 limit_conn 模块、limit_req 模块


文章目录

  • 一、ngx_http_limit_conn_module 模块
  • 二、ngx_http_limit_req_module 模块
  • 三、关于 limit_req 和 limit_conn 的区别

经常会遇到这种情况,服务器流量异常,负载过大等等。对于大流量恶意攻击访问,会带来带宽的浪费,服务器压力,影响业务,往往考虑对同一个IP的连接数,请求数、进行限制。
一、ngx_http_limit_conn_module 模块 ngx_http_limit_conn_module 模块用于限制定义key在同一时间的并发连接数,特别是来自单个IP地址的连接数。并非所有连接都被计算在内,仅当连接已经读取了整个请求头时才计算连接。
用法:
该模块提供了四个指令,limit_conn_zone 、 limit_conn 、 limit_conn_log_level 、limit_conn_status
limit_conn_zone 指令
语法:limit_conn_zone $variable zone=name:size; 作用域:http 功能: 定义了一个会话状态存储区域,里边记录会话状态的信息 说明: $variable 使用Nginx内置变量作为键(一般经常使用客户端的IP地址作为键:① $remote_addr变量的长度为7字节到15字节,而存储状态在32位平台中占用32字节或64字节,在64位平台中占用64字节。 ② $binary_remote_addr变量的长度是固定的4字节,存储状态在32位平台中占用32字节或64字节,在64位平台中占用64字节。 1M共享空间可以保存3.2万个32位的状态,1.6万个64位的状态)。zone=name定义区域名称(名称随意起,在limit_conn配置项中调用时对应就好), size定义各个键共享内存空间大小。如果共享内存空间被耗尽,服务器将会对后续所有的请求返回 503 (Service Temporarily Unavailable) 错误。

limit_conn 指令
语法:limit_conn zone_name number; 作用域: http, server, location 功能: 使用由limit_conn_zone定义的拦截规则, 并设置具体的限制连接数量,当超过这个数字时返回503(Service )错误。即limit_conn是对某个在limit_conn_zone中 定义的存储区域key对应的总的网络连接数进行限流。可以按照IP来限制IP维度的总连接数,或者按照服务域名来限制某个域名的总的连接数。 (只有那些被nginx处理的且已经读取了整个请求头的请求连接才会被计数器统计)。 说明: zone_name是上面 limit_conn_zone 中的zone定义的,即由limit_conn_zone(zone=name)定义的名称; 表示使用定义的哪个限制规则;number:正整数; 表示具体的限制连接数量

limit_conn_log_level 指令
语法: limit_conn_log_levelinfo|notice|warn|error 作用域: http, server, location 默认值: limit_conn_log_levelerror 功能: 当达到最大限制规则的连接数后,记录日志的等级。

limit_conn_status指令
语法: limit_conn_status code 作用域: http, server, location 默认值: limit_conn_status 503 功能: 当超过限制规则后,返回的响应状态码,默认是503

配置
http{ # http块中定义访问限制 limit_conn_zone $remote_addr zone=conn_zone:10m; server{ # server块中调用。连接限制,限制同时最高1个连接 limit_conn conn_zone 1; limit_conn_status 503; limit_conn_log_level notice; location PATTERN { ... } ... } }

二、ngx_http_limit_req_module 模块 ngx_http_limit_req_module 模块用于限制定义key的请求的处理速度。
特别的,可以限制来自单个IP地址的请求处理频率。 限制的方法如同漏斗,每秒固定处理请求数,推迟过多请求。例如,单客户端IP的每秒请求数。
漏斗算法(Leaky Bucket),该算法有两种处理方式Traffic Shaping和Traffic Policing

在桶满水之后,常见的两种处理方式为:
  1. 暂时拦截住上方水的向下流动,等待桶中的一部分水漏走后,再放行上方水。
  2. 溢出的上方水直接抛弃。

将水看作网络通信中数据包的抽象,则方式1起到的效果称为Traffic Shaping,方式2起到的效果称为Traffic Policing
由此可见,Traffic Shaping的核心理念是"等待",Traffic Policing的核心理念是"丢弃"。它们是两种常见的流速控制方法
限制请求数能一定程度上降低nginx以及nginx作为代理时对恶意的请求访问压力。同时,对于非恶意的高频率请求,一定程度上降低了nginx或者后端服务器的负载。
nginx为了实现对请求IP进行请求限制的功能,需要能做到对请求IP已经对应时间段的请求次数进行保存及高效的查找判断。由于nginx采用的是多进程的架构,因此,需要将这些信息保存在共享内存中,以便于所有的worker进程均能获知这些信息;而且,为了能达到高效地将对这些信息进行检索及管理,nginx使用了红黑树作为处理这些信息的数据结构。
用法:
该模块提供了四个指令,limit_req_zone 、 limit_req 、 limit_req_log_level 、limit_req_status
limit_req_zone 指令
语法:limit_req_zone $variable zone=name:size rate=rate; 作用域:http 功能: 定义了一个会话状态存储区域,里边记录会话状态的信息说明: $variable 使用Nginx内置变量作为键 zone=name 定义区域名称(名称随意起,在limit_conn指令中调用时对应就好), size 定义各个键共享内存空间大小。如果共享内存空间被耗尽,服务器将会对后续所有的请求返回 503 (Service Temporarily Unavailable) 错误。 rate 速度可以设置为每秒处理请求数和每分钟处理请求数,其值必须是整数,所以如果你需要指定每秒处理少于1个的请求,2秒处理一个请求,可以使用 “30r/m”。例:limit_req_zone $binary_remote_addr zone=test:10m rate=10r/m; # 定义一个通过binary_remote_addr这个标识来做限制,生成一个大小为10M,名字为test的内存区域,用来存储访问的频次信息,限制相同标识的客户端的访问频次为1分钟10次(即6秒一次)

limit_req 指令
语法:limit_req zone=name [burst=number] [nodelay]; 作用域: http, server, location功能: 设置使用哪个共享内存限制域和允许被处理的最大请求数阀值。如果请求的频率超过了限制域配置的值,请求处理会被延迟,所以所有的请求都是以定义的频率被处理的。超过频率限制的请求会被延迟,直到被延迟的请求数超过了定义的阀值,这时,这个请求会被终止,并返回503(Service Tempporarily Unavailable)错误,这个阀值的默认值为0说明: burst可选项。后面接整数,表示最大允许超过频率限制的请求数(这个配置的意思是设置一个大小为number的缓冲区,当有大量请求(爆发)过来时,超过了访问频次限制的请求可以先放到这个缓冲区内等待,但是这个等待区里的位置只有number个,超过的请求会直接报503的错误然后返回。) nodelay 可选项。如果不希望超过的请求被延迟,可以使用 nodelay 参数(如果设置,会在瞬时提供处理(burst + rate)个请求的能力,请求超过(burst + rate)的时候就会直接返回503,永远不存在请求需要等待的情况;如果没有设置,则所有请求会依次等待排队)例:第一种:不加burst和不加nodelay的情况: limit_req zone=test; # 使用上面例子中定义的名为test的limit_req_zone(其定义的限制频率为每分钟的请求数为10个,即每6秒1次),假设同一客户端在同一时刻发起100个请求(前提这100个请求在服务器在6秒内收到),那么,服务器只会成功响应一次请求,对于其余99次请求服务器均不予响应并直接返回了503。 第二种:只加burst和不加nodelay的情况: limit_req zone=test burst=5; # 使用上面例子中定义的名为test的limit_req_zone(其定义的限制频率为每分钟的请求数为10个,即每6秒1次),假设同一客户端在同一时刻发起100个请求,那么,服务器只会成功响应5+1=6次请求,但是这6次成功的请求会延时限制(其中第一次成功被服务器处理的请求是在6秒内,第二次是在大于6秒小于12秒内请求成功的,第三次则为大于十二秒小于十八秒内请求成功的,以此类推),对于其余94次请求服务器均不予响应并直接返回了503(这是因为设置了burst=5,在服务器接收到100个并发请求后,先处理1个请求,同时将5个请求放入burst缓冲队列中,等待处理。而超过(burst+1)数量的请求就被直接抛弃了,即直接抛弃了94个请求。)。 第三种:加burst和加nodelay的情况: limit_req zone=test burst=5 nodelay; # 使用上面例子中定义的名为test的limit_req_zone(其定义的限制频率为每分钟的请求数为10个,即每6秒1次),假设同一客户端在同一时刻发起100个请求(前提这100个请求在服务器在30秒内收到),那么,服务器只会成功响应5+1=6次请求,但是没有时间的限制(即只要服务器处理速度够快,可以在1秒内处理完这6个请求),对于剩下的94个请求,直接返回503,在下一秒如果继续向服务端发送10个请求,服务端会直接拒绝这10个请求并返回503。因为设定了每6s处理1个请求,所以直到30s之后,才可以再处理一个请求,即如果此时向服务端发送10个请求,会返回9个503,一个200总结: limit_req zone=req_zone; 严格依照在limti_req_zone中配置的rate来处理请求 超过rate处理能力范围的,直接drop 表现为对收到的请求无延时 limit_req zone=req_zone burst=number; 依照在limti_req_zone中配置的rate来处理请求 同时设置了一个大小为number的缓冲队列,在缓冲队列中的请求会等待慢慢处理 超过了burst缓冲队列长度和rate处理能力的请求被直接丢弃 表现为对收到的请求有延时 limit_req zone=req_zone burst=number nodelay; 依照在limti_req_zone中配置的rate来处理请求 同时设置了一个大小为number的缓冲队列,当请求到来时,会爆发出一个峰值处理能力,对于峰值处理数量之外的请求,直接丢弃 在完成峰值请求之后,缓冲队列不能再放入请求。如果rate=10r/m,且这段时间内没有请求再到来,则每6s缓冲队列就能回复一个缓冲请求的能力,直到恢复到能缓冲number个请求位置。burst参数解释: 这里的burst参数主要采用了令牌桶算法。令牌桶算法是网络流量整形(Traffic Shaping)和速率限制(Rate Limiting)中最常用的一种算法。典型情况下,令牌桶算法用来控制发送到网络上的数据数目,并允许突发数据的发送。令牌桶这种控制机制基于令牌桶中是否存在令牌来指示什么时候可以发送流量。令牌桶中的每一个令牌都代表一个字节。如果令牌桶中存在令牌,则允许发送流量;如果令牌桶中不存在令牌,则不允许发送流量。因此,如果突发门限被合理地配置并且令牌桶中有足够的令牌,那么流量就可以以峰值速率发送。令牌桶算法的基本过程如下: (1)假如用户配置的平均发送速率为10r/s,则每隔0.1秒一个令牌被加入到桶中; (2)假设桶最多可以存发b个令牌。如果令牌到达时令牌桶已经满了,那么这个令牌会被丢弃; (3)当一个n个字节的数据包到达时,就从令牌桶中删除n个令牌,并且数据包被发送到网络; (4)如果令牌桶中少于n个令牌,那么不会删除令牌,并且认为这个数据包在流量限制之外;算法允许最长b个字节的突发,但从长期运行结果看,数据包的速率被限制成常量r。对于在流量限制外的数据包可以以不同的方式处理: 它们可以被丢弃; 它们可以排放在队列中以便当令牌桶中累积了足够多的令牌时再传输; 它们可以继续发送,但需要做特殊标记,网络过载的时候将这些特殊标记的包丢弃。注意: 令牌桶算法不能与另外一种常见算法“漏斗算法(Leaky Bucket)”相混淆。这两种算法的主要区别在于“漏斗算法”能够强行限制数据的传输速率,而“令牌桶算法”在能够限制数据的平均传输数据外,还允许某种程度的突发传输。在“令牌桶算法”中,只要令牌桶中存在令牌,那么就允许突发地传输数据直到达到用户配置的门限,因此它适合于具有突发特性的流量。

limit_req_log_level 指令
语法: limit_req_log_levelinfo|notice|warn|error 作用域: http, server, location 默认值: limit_req_log_levelerror功能: 当达到最大限制规则的连接数后,记录日志的等级。

limit_req_status指令
语法: limit_req_status code 作用域: http, server, location 默认值: limit_req_status 503功能: 当超过限制规则后,返回的响应状态码,默认是503

三、关于 limit_req 和 limit_conn 的区别 首先,limit_req和limit_conn两个模块都是为了来限流的,但是两者不在一个层面,为了搞清楚这个,必须先要弄清楚request和connection的区别,因为在很多情况下,我们把他们混淆了。
connection是连接,即常说的tcp连接,通过三次握手而建立的一个完整状态机。建立一个连接,必须得要三次握手。
request是指请求,即http请求,(注意,tcp连接是有状态的,而构建在tcp之上的http却是无状态的协议)。
【nginx|Nginx之访问限制模块之 limit_conn 模块、limit_req 模块】通过打开一个网页,然后通过wireshark可以看到,一个连接建立后(即三次握手后),在这个连接断开之前(即四次挥手之前),会有很多的 http request,
这就是它们的区别:即一个连接的生命周期中,会存在一个或者多个请求,这是为了加快效率,避免每次请求都要三次握手建立连接,现在的HTTP/1.1协议都支持这种特性,称为长连接(keepalive)。
那么在Nginx中,对于连接限制模块:
limit_conn_module来看:
limit_conn_zone $binanry_remote_addr zone=conn_zone:1m; location /limit.html { limit_conn conn_zone 1; }

这样的配配置,表明以ip为key,来限制每个ip访问lmit.html文件时候,最多只能有一个在线,否则其余的都要返回不可用。这种情况就是一个静止状态的计数可以实现,而无关乎多长时间。
举个例子,如果这个连接一直不释放,即使通过这1个连接发送出再多的request请求,只要服务器能够应付,那么就会处理。但是,如果只需要处理2个请求,但是这两个请求是分别用2个连接同时发送过来的,那么,服务器就只能处理其中一个,另外一个就不行。
可以用ab命令来测试:
ab -n100 -c100 -k http://yoursit/limit.html

注意:ab命令的-n -c都是指的发送多少请求,即-n一共发送多少请求,-c同时发送多少请求,他并不关心需要多少连接来发送,默认情况下是每个请求都建立一个连接来发送。上面这个命令,由于默认是一个连接发送一个请求,那么这将会同时建立100个连接,而这样的话,就会导致限制超过(对于一个ip最多只能一个连接)。99个请求都会失败。
而如果你只开一个连接:
ab -n100 -c100 -k http://yousit/limt.html

这里的-k选项就是表示keepalive,只开一个连接来发送这100个请求,即使是同时发送,那么server也不会认为你超过了,因为在一个时间你只是建立一个连接,这样这100个请求都会干净利落的处理完成。
再看limit_req_module:
limit_req_zone $binary_remtoe_addr zone=req_zone:1m rate=1r/s; location /limit.html { limit_conn zone=req_zone; }

注意和上面的limit_conn_module区别:这里需要为共享内存配置一个速率rate表明:对于每个ip来说,处理请求的速度不超过每秒1个请求。
用ab命令来测试:
ab -n100 -c100 http://yoursit/limit.html 和ab -n100 -c100 -k http://yousit/limt.html

那么这时候的区别就不是那么明显了,因为这总是同时发送出100个请求(不管是通过100个连接还是1个连接),只要客户端请求的速度超过每秒1个,那么服务器就会拒绝。
Nginx连接限制没有请求限制有效?
http协议的连接与请求,首先HTTP是建立在TCP基础之上,在完成HTTP请求需要先建立TCP三次握手(称为TCP连接),在连接的基础上再完成HTTP的请求。

所以多个HTTP请求可以建立在一次TCP连接之上,那么我们对请求的精度限制,当然比对一个连接的限制会更加的有效,因为同一时刻只允许一个TCP连接进入,但是同一时刻多个HTTP请求可以通过一个TCP连接进入。所以针对HTTP的请求限制才是比较优的解决方案。

    推荐阅读