浏览 2.6k
上篇我们分析了NGINX速率限制的原理,本篇我们继续分析NGINX并发限制的原理,后面一篇,我们将分析NGINX带宽限制的原理。
速率限制用来限制单个客户端在单位时间的请求数目,而并发限制用来限制同一时间的连接总数。本篇文章我们对并发限制功能的原理和源代码进行分析,从而可以更好地理解和使用此功能。
并发限制实现原理相对简单。它不像速率限制那样需要采用漏桶或者令牌头算法进行流量的控制和整形。它只需维护一个计数表示当前的连接数,然后在建立连接时增加计数,连接断开时减少计数。当新的连接到来时比较当前计数和配置参数的大小关系就可以决定是否可以建立连接。
NGINX通过limit_conn_zone和limit_conn两条指令来实现并发限制。指令limit_conn_zone定义了并发限制的参数,指令limit_conn在所在的location使能并发限制。
http {
limit_conn_zone $binary_remote_addr zone=addr:10m;
...
server {
...
location /download/ {
limit_conn addr 1;
}
}
配置语法:
Syntax:limit_conn_zone key zone=name:;
Default:—
Context:http
参数key定义了基于什么样的参数进行速率控制。比如下面的例子中的$binary_remote_addr就是表示使用客户端的ip地址(remote_addr)进行速率控制。而且为了节省存储空间采用了二进制的方式表示。
参数zone定义一块共享内存区域。此共享区域用来存储各种key相关的状态信息特别是当前的连接数。参数key可以包含text,变量或者是他们的组合,但是不能为空。因为采用了共享内存的方式(关于NGINX共享内存可以参考这篇文章),所以,这些信息被所有的worker进程共用。
通过此参数可以定义共享内存的名字和大小。在上述采用$binary_remote_addr作为key的例子中,所需要的存储空间比速率控制所需要的空间要少,所以在此配置下,相同大小的内存存储的节点数目要把速率控制的节点数目多。
配置语法:
Syntax: limit_conn zone number;
Default: —
Context: http, server, location
设置共享内存zone已经基于特定key值可允许的最多连接数。当到达最大连接数,服务器将会返回错误码。而不会像速率控制那样还会试图去释放内存。
Syntax:limit_conn_dry_run on | off;
Default:limit_conn_dry_run off;
Context:http, server, location
使能演习(dry run)模式。在这种模式下,只会把请求的状态信息保存在共享内存中,并发限制功能不会生效。
Syntax:limit_conn_log_level info | notice | warn | error;
Default:limit_conn_log_level error;
Context:http, server, location
设置与并发限制功能相关的log级别。比如设置为limit_conn_log_level notice 则并发限制相关的log会按照notice的级别进行记录。
Syntax:limit_conn_status code;
Default:limit_conn_status 503;
Context:http, server, location
设置NGINX拒绝连接请求时发送给客户端的HTTP代码,默认是503。
可以同时配置多条limit_conn指令。比如下面的配置会不仅会限制单个客户端到服务器的并发连接数目为10,还会限制所有客户端到server并发的连接数是100。
http {
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;
}
}
现在我们通过实验来验证NGINX在不同的配置情况下的请求并发限制行为。
客户端通过脚本发送HTTP请求到 jikui.test.com服务器的path1路径上,然后返回一个比较大的index.html文件,这样可以让一个请求持续时间相对较长,有利于我们测试观察。然后我们通过NGINX服务器的access.log来观察NGINX在不同的配置策略下的处理行为,
用来发送HTTP请求的脚本存放在https://github.com/pei-jikui/limit_req中。
指令limit_conn_zone定义了一个大小是10m的名称为one的区域用来根据客户端地址存储状态信息。
指令limit_conn引用zone one然后针对每一个客户端并发限制为1个连接。
http {
limit_conn_zone $binary_remote_addr zone=one:10m;
server {
listen 2048;
location^~/path1 {
limit_conn one 1;
alias /usr/local/nginx/html/index.html;
index index.html;
}
}
预期效果是,同一个客户只能有一个并发连接。
观察NGINX的access.log日志,我们可以看到在第一个连接没有断开时,第二个连接被拒绝。
指令limit_conn_zone分定义了一个大小是10m的名称为one的区域用来根据客户端地址来限制并发连接和另外一个大小是10m名称为two的区域根据server_name来限制并发连接。
指令limit_conn引用zone one然后针对每一个客户端并发限制为1个连接。
第二个limit_conn引用zone two限制针对path1的并发请求总数为2个连接。
http {
limit_conn_zone $binary_remote_addr zone=one:10m;
limit_conn_zone $server_name zone=tw10m;
server {
listen 2048;
location^~/path1 {
limit_conn one 1;
limit_conn two 2;
alias /usr/local/nginx/html/index.html;
index index.html;
}
}
预期效果是,如果多个不同的客户端同时访问服务器的path1路径,每个客户只能有一个请求,总共只能有两个并行连接。
观察NGINX的access.log日志,我们可以看到在第一个客户端的只被允许了一个请求,第二个客户端也只被允许了一个请求,而第三个客户端因为针对server URI的并行总量已经达到限制,所以,第三个客户端所有的连接都被拒绝。
本节,我们从源代码角度去分析并发控制功能是如何实现的。
速率控制只要是通过limit_conn_zone和limit_conn两条指令完成的。在NGINX的启动阶段对这两条指令进行解析。
指令limit_conn_zone解析流程:
1. 解析指令limit_conn_zone 的函数为ngx_http_limit_conn_zone。它会首先解析key属性以及zone的名称和大小参数,然后通过函数ngx_shared_memory_add 申请一片共享内存。并且把函数ngx_http_limit_conn_init_zone 设置为这片共享内存的初始化函数。
2. NGINX解析完成以后,会生成系统申请的共享内存,并且调用对应的初始化函数进行初始化。对于为并发限制生成的内存会调用ngx_http_limit_conn_init_zone完成初始化工作。
3. 函数ngx_http_limit_conn_init_zone会把上述的共享内存区域初始化为红黑树用来存储连接信息。
指令limit_conn解析流程:
指令解析完毕以后相关数据结构的关系如下图所示:
NGINX并发限制对应的模块是ngx_http_limit_conn_module,它处于HTTP的11个处理阶段的NGX_HTTP_PREACCESS_PHASE阶段,处理函数是ngx_http_limit_conn_handler。
我们忽略NGINX是如何处理HTTP请求的,直接从函数ngx_http_limit_conn_handler的处理逻辑开始分析。
并发控制是NGINX流量管理功能中一个很重要的模块。通过它可以有效地减缓上游服务器的负载,合理分配各个用户的连接数目从而有效地对客户流量进行管理。从代码实现的角度上来看,它和速率控制有非常多的相似的地方,但是因为只需要简答的计数维护和比较不需要相对复杂的流控算法,所以它又比速率控制要简单很多。下篇,我们讲试着分析流量管理的最后一个功能,也就是带宽控制的原理。
按点赞数排序
按时间排序