当前位置: 首页 > Linux

Linux下玩转nginx系列(七)---nginx如何实现限流功能

时间:2023-04-06 06:01:56 Linux

nginx是最流行的负载均衡工具之一。非常有必要。速率限制是nginx中一个非常有用的功能。流量限制可用于安全目的,例如减慢暴力破解密码的速度。它还可以通过将传入请求的速率限制为真实用户的典型值并识别目标URL地址(通过日志)来防御DDOS攻击。更常见的是,此功能用于保护上游应用程序服务器不被太多并发用户请求淹没。下面介绍如何在nginx中限制请求的流量。限流算法令牌桶算法算法的思想是:令牌以固定的速率产生并缓存在令牌桶中;当令牌桶满时,丢弃多余的令牌;请求需要消耗等比例的token才能被接受处理;当令牌不够时,请求被缓存。漏桶算法的算法思想是:水(请求)从上方倒入桶中,从桶底流出(处理);来不及流出的水存储在桶中(缓冲),并以固定的速度流出;桶装满后水溢出(丢弃);这个算法的核心是:缓存请求,匀速处理,直接丢弃多余的请求。与漏桶算法相比,令牌桶算法的不同之处在于它不仅有“桶”,还有队列。这个bucket是用来存放token的,queue是用来存放request的。在功能上,漏桶算法和令牌桶算法最明显的区别是是否允许处理突发流量(burst)。漏桶算法可以强制限制数据的实时传输(处理)速率,不做额外的处理;而令牌桶算法可以限制平均数据传输速率,同时允许一定程度的突发传输。Nginx请求速率限速模块采用漏桶算法,可以强制保证请求的实时处理速度不会超过设定的阈值。Nginx正式版有两个限制IP连接和并发的模块:limit_req_zone用于限制单位时间内的请求数,即ratelimit,使用的是漏桶算法“leakybucket”。limit_req_conn用于限制同时连接数,即并发限制。limit_req_zone参数配置语法:limit_reqzone=name[burst=number][nodelay];默认值:—上下文:http,server,locationlimit_req_zone$binary_remote_addrzone=one:10mrate=1r/s;第一个参数:$binary_remote_addr表示通过remote_addrflag来限制,“binary_”的目的是为了简化内存使用,限制相同的clientip地址。第二个参数:zone=one:10m表示生成一块大小为10M,名称为one的内存区域,用于存放访问频率信息。第三个参数:rate=1r/s表示允许相同标识的客户端访问频率。这里限制为每秒1次,也可以是30r/m。limit_reqzone=oneburst=5nodelay;第一个参数:zone=one设置使用哪个配置zone进行限制,对应上面limit_req_zone中的name。第二个参数:burst=5,重点解释一下这个配置,burstburst的意思,这个配置的意思是在大量请求(突发)过来的时候,设置一个缓冲区大小为5,超过访问频率限制的请求吧可以先放入这个缓冲区。第三个参数:nodelay,如果设置了,当访问频率超过,缓冲区已满时,会直接返回503。如果未设置,所有请求将排队等待。示例:http{limit_req_zone$binary_remote_addrzone=one:10mrate=1r/s;server{location/search/{limit_reqzone=oneburst=5nodelay;}}下面的配置可以限制特定UA(比如搜索引擎)的访问:limit_req_zone$anti_spiderzone=one:10mrate=10r/s;limit_reqzone=oneburst=100nodelay;if($http_user_agent~*"googlebot|bingbot|Feedfetcher-Google"){set$anti_spider$http_user_agent;}其他参数语法:limit_req_log_levelinfo|公告|警告|error;Default:limit_req_log_levelerror;Context:http,server,location当服务器被限速或因限制缓存时,配置写入日志。延迟记录比拒绝记录低一级。例子:limit_req_log_level通知的延迟基本上是info。Syntax:limit_req_statuscode;Default:limit_req_status503;Context:http,server,location设置拒绝请求的返回值。值只能设置在400到599之间。ngx_http_limit_conn_module参数配置该模块限制单个IP的请求数。并非所有连接都被计算在内。仅当服务器已处理请求并已读取整个请求标头时才计算连接数。语法:limit_conn区域编号;默认值:—上下文:http,服务器,locationlimit_conn_zone$binary_remote_addrzone=addr:10m;server{location/download/{limit_connaddr1;一次只允许每个IP地址建立一个连接。limit_conn_zone$binary_remote_addrzone=perip:10m;limit_conn_zone$server_namezone=perserver:10m;server{...limit_connperip10;limit_connperserver100;}可以配置多个limit_conn指令。例如,上面的配置将限制每个客户端IP到服务器的连接数,同时限制到虚拟服务器的连接总数。语法:limit_conn_zonekeyzone=name:size;默认值:—上下文:httplimit_conn_zone$binary_remote_addrzone=addr:10m;此处,客户端IP地址用作密钥。请注意,使用$binary_remote_addr$变量而不是$remote_addr$。$remote_addr$变量的大小可以从7到15个字节不等。存储状态在32位平台上占用32或64字节的内存,在64位平台上始终占用64字节。$binary_remote_addr$变量的大小对于IPv4地址始终为4字节,对于IPv6地址始终为16字节。存储状态在32位平台上始终占用32或64字节,在64位平台上始终占用64字节。一个兆字节区域可以容纳大约32000个32字节状态或大约16000个64字节状态。如果区域存储已用完,服务器将向所有其他请求返回错误。语法:limit_conn_log_level公告|警告|error;Default:limit_conn_log_levelerror;Context:http,server,location当服务器限制连接数时设置想要的日志级别。Syntax:limit_conn_statuscode;Default:limit_conn_status503;Context:http,server,location设置拒绝请求的返回值。限流示例1.限制访问速率limit_req_zone$binary_remote_addrzone=mylimit:10mrate=2r/s;server{location/{limit_reqzone=mylimit;}}上述规则限制每个IP访问速度为2r/s,并将此规则应用到根目录。如果一个IP在很短的时间内并发发送多个请求会怎样?我们用单个IP在10ms内发送发送了6次请求,只有1次成功,其余5次全部被拒绝。我们设置的速度是2r/s,为什么只有一个成功?Nginx的限制是不是错了?当然不是,因为Nginx的限流统计是以毫秒为单位的,而我们设置的速度是2r/s。转换后,单个IP在500ms内只允许通过1次请求,从501ms开始允许通过第二次请求。2.突发缓存处理短时间内发送大量请求,Nginx以毫秒级精度统计,超过限制的请求直接拒绝。这在实际场景中太苛刻了。在真实的网络环境中,请求并不是以统一的速度到达的。请求很可能会“爆”,即“一个接一个”。Nginx考虑到这种情况,可以使用burst关键字来开启对突发请求的缓存处理,而不是直接拒绝。看配置:limit_req_zone$binary_remote_addrzone=mylimit:10mrate=2r/s;服务器{位置/{limit_reqzone=mylimitburst=4;}}我们添加了burst=4,这意味着每个key(这里是每个IP)最多允许4个burst请求。如果单个IP在10ms内发送6个请求会怎样?与实例1相比,成功次数增加了4次,与我们设置的连发次数一致。具体处理流程为:1个请求立即处理,4个请求放入突发队列,另一个请求拒绝。通过burst参数,我们让Nginx限流具有缓存和处理突发流量的能力。但是请注意:burst的作用是让多余的请求先入队,慢慢处理。如果不加nodelay参数,队列中的请求不会立即处理,而是按照rate设置的速度,以毫秒级的精准速度慢慢处理。3、nodelay减少排队时间在第二个例子中我们可以看到,通过设置burst参数,可以让Nginx缓存一定程度的burst,冗余的请求可以先放入队列,慢慢处理,起到起到疏通交通的作用。但是如果队列设置的比较大,请求排队的时间会比较长。从用户的角度来看,RT变长了,对用户很不友好。解决办法是什么?nodelay参数可以让请求在入队时立即处理,也就是说只要请求能进入突发队列,就会立即被后台worker处理。请注意,这意味着当burst设置为nodelay时,系统的瞬时QPS可能会超过rate设置的阈值。nodelay参数只有和burst一起使用时才有效。继续示例2的配置,我们添加nodelay选项:limit_req_zone$binary_remote_addrzone=mylimit:10mrate=2r/s;server{location/{limit_reqzone=mylimitburst=4nodelay;}}单个IP在10ms内并发发送6个请求,结果如下:与示例2相比,请求成功率没有变化,但整体耗时变短了。这怎么解释呢?例2中,4个请求放入突发队列,工作进程每500ms(rate=2r/s)取一个请求进行处理,最后一个请求会排队2s再处理;例3中,请求入队和例2一样,不同的是队列中的请求是同时有资格被处理的,所以例3中的5个请求可以说是在处理同样的时间,花费的时间自然就缩短了。但是请注意,虽然设置burst和nodelay可以减少burst请求的处理时间,但从长远来看并不会增加吞吐量的上限。长期吞吐量的上限是由速率决定的,因为nodelay只能保证突发请求被立即处理,但是Nginx会限制队列元素释放的速度,就像限制token在队列中产生的速度一样令牌桶。看到这里,你可能会问,加入nodelay参数后的限速算法是个什么“桶”,是漏桶算法还是令牌桶算法?当然,它仍然是一个漏桶算法。考虑一种情况,当令牌桶算法的令牌耗尽时会发生什么?由于它有一个请求队列,它会缓存下一个请求,缓存受队列大小的限制。但是此时缓存这些请求有意义吗?如果服务器过载,缓存队列会越来越长,RT会越来越高。即使请求在很长时间后得到处理,对用户来说也没有什么价值。所以当token不够的时候,最明智的做法就是直接拒绝用户的请求,这就变成了漏桶算法。4.自定义返回值limit_req_zone$binary_remote_addrzone=mylimit:10mrate=2r/s;server{location/{limit_reqzone=mylimitburst=4nodelay;limit_req_status598;}}默认不配置status返回值的状态:自定义status返回值状态:参考nginx-proxy_pass官网文档FullExampleConfigurationNginx限制访问速率和最大并发连接数模块--limit(防止DDOS攻击)Nginx限流关于nginx限速模块Modulengx_http_limit_conn_moduleModulengx_http_limit_req_moduleNginx限速模块限流算法初探