NGINX和NGINX Plus是功能非常强大的HTTP、TCP和UDP负载均衡器。它们可以高效代理突发的大量请求,并维持大量并发连接。但这些特性也使NGINX和NGINX Plus尤其容易出现临时端口耗尽,即操作系统已用尽为建立新的本地套接字而分配的端口号,导致无法创建新连接。(临时端口耗尽对这两款产品均有影响,但为简洁起见,本文后续仅提及NGINX Plus。)
在本博客中,我们将回顾TCP连接的组成部分,以及在建立连接之前如何确定其内容。然后,我们将介绍如何判断NGINX Plus何时受到临时端口耗尽的影响。最后,我们将探讨如何通过调整Linux内核和使用NGINX Plus指令来应对这些限制。
网络套接字概述
通过TCP建立连接时,本地主机和远程主机上都会创建一个套接字。随后,这两个套接字相互连接,形成一个套接字对。该套接字对由唯一的四元组描述,包括本地IP地址和端口,以及远程IP地址和端口。
远程IP地址和端口属于连接的服务器端,客户端必须先确定二者,才能发起连接。大多数情况下,客户端会自动选择用于连接的本地IP地址,但有时也会由建立连接的软件选择。最后,系统会从操作系统提供的指定范围内随机选择本地端口。该端口仅在连接期间与客户端关联,因此称为“临时端口”。连接终止后,该临时端口可供重复使用。
识别临时端口耗尽
如引言所述,NGINX Plus本身可能出现临时端口耗尽,并引发相关问题。当NGINX Plus将请求代理到上游服务器时,它在上述套接字创建过程中充当客户端。默认情况下,它会自动将代理请求的套接字绑定到运行主机上的本地IP地址和一个可用的临时端口。如果连接速率很高,导致新建套接字进入等待状态的速度快于现有已打开套接字的关闭速度,可用端口最终会耗尽,无法创建新的套接字。这会导致操作系统和NGINX Plus均报错。以下是NGINX Plus错误日志中由此产生的错误示例。
2016/03/18 09:08:37 [crit] 1888#1888: *13 connect() to 10.2.2.77:8081 failed (99: Cannot assign requested address) while connecting to upstream, client: 10.2.2.42, server: , request: "GET / HTTP/1.1", upstream: "http://10.2.2.77:8081/", host: "10.2.2.77"端口耗尽还会导致由NGINX Plus而非上游服务器产生的500错误激增。以下是NGINX Plus访问日志中的一个示例条目。
10.2.2.42 - - [18/Mar/2016:09:14:20 -0700] "GET / HTTP/1.1" 500 192 "-" "curl/7.35.0"要检查NGINX Plus服务器上处于TIME-WAIT状态的套接字数量,请在Linux shell中运行以下ss命令。示例显示有143个处于TIME-WAIT状态的套接字处于打开状态。在此示例中,我们使用wc命令统计命令输出的行数,从而得出套接字数量。
# ss -a | grep TIME-WAIT | wc -l143调整内核以增加可用临时端口的数量
减少临时端口耗尽的一种方法是使用Linux内核的net.ipv4.ip_local_port_range设置。默认范围通常为32768至61000。
如果发现临时端口即将耗尽,可将默认范围调整为 1024 至 65000,这是一种将可用临时端口数量增加一倍的实用方法。有关更改内核设置的更多信息,请参阅我们的《NGINX 性能调优》博客文章。
在NGINX Plus中启用Keepalive连接
另一种减少临时端口耗尽的方法是启用NGINX Plus与上游服务器之间的keepalive连接。在最简单的HTTP实现中,客户端会建立新连接、写入请求、读取响应,然后关闭连接以释放相关资源。

客户端读取响应后,keepalive连接会保持打开状态,以便后续请求复用。

使用keepalive指令可启用从NGINX Plus到上游服务器的keepalive连接,并定义每个工作进程的缓存中保留的上游服务器空闲keepalive连接数上限。超过此数量时,将关闭最近最少使用的连接。如果不使用keepalive,会增加额外开销,并降低连接和临时端口的使用效率。
以下示例配置指示NGINX Plus与名为upstream的backend块中定义的服务器保持至少128个keepalive连接。
upstream backend { server 10.0.0.100:1234;
server 10.0.0.101:1234;
keepalive 128;
}为上游服务器启用长连接时,还必须使用proxy_http_version指令,指定NGINX Plus使用HTTP 1.1,并使用proxy_set_header指令移除所有名为Connection的标头。这两个指令均可放在http、server或location配置块中。
proxy_http_version 1.1; proxy_set_header Connection "";将连接动态绑定到指定的本地IP地址列表
优化内核并启用长连接,可以更有效地控制临时端口的可用性和使用情况,但在某些情况下,这些更改仍不足以解决临时端口使用过多的问题。此时,可以使用一些NGINX Plus指令和参数,将一定比例的连接绑定到静态本地IP地址。这样,实际可用的临时端口数量将按配置中定义的IP地址数量成倍增加。为此,我们使用proxy_bind和split_clients指令。
在以下示例配置中,proxy_bind指令位于location块中,会在每次请求期间根据$split_ip变量的值设置本地IP地址。该变量会动态设置为split_clients块生成的值,而该块使用哈希函数确定此值。
The first parameter to the split_clients指令的第一个参数是字符串("$remote_addr$remote_port"),每次请求时都会使用MurmurHash2函数对其进行哈希处理。第二个参数($split_ip)是根据第一个参数的哈希值动态设置的变量。
花括号内的语句将哈希表划分为多个“桶”,每个桶包含一定比例的哈希值。此处将哈希表划分为10个大小相同的桶,但也可以创建任意数量的桶,并且各桶的大小不必相同。(最后一个桶的百分比始终以星号[*]表示,而不是具体数字,因为哈希值的数量可能无法按指定百分比均匀划分。)
哈希值的可能范围为0至4294967295,因此在本例中,每个桶包含约429496700个值(占总数的10%):第一个桶包含0至429496700的值,第二个桶包含429496701至858993400的值,依此类推。变量$split_ip会被设置为相应桶所关联的IP地址,该桶包含字符串$remote_addr$remote_port的哈希值。例如,哈希值150000000落在第四个桶中,因此在这种情况下,变量$split_ip会被动态设置为10.0.0.213。
http { upstream backend {
server 10.0.0.100:1234;
server 10.0.0.101:1234;
}
server {
# ...
location / {
# ...
proxy_pass http://backend;
proxy_bind $split_ip;
proxy_set_header X-Forwarded-For $remote_addr;
}
}
split_clients "$remote_addr$remote_port" $split_ip { 10% 10.0.0.210; 10% 10.0.0.211; 10% 10.0.0.212; 10% 10.0.0.213; 10% 10.0.0.214; 10% 10.0.0.215; 10% 10.0.0.216; 10% 10.0.0.217; 10% 10.0.0.218; * 10.0.0.219; }
}结论
调整Linux内核中的临时端口数量设置,可以提高临时端口的可用性和使用效率。启用从NGINX Plus到上游服务器的长连接,可以提高临时端口的使用效率。最后,在NGINX Plus配置中使用split_clients和proxy_bind指令,可以将出站连接动态绑定到预定义的本地IP地址列表,从而大幅增加NGINX Plus可用的临时端口数量。
如需试用NGINX Plus,立即开始30天免费试用,或联系我们,探讨您的使用场景。
关于作者

相关博客文章
NGINX Plus R26 正式发布
NGINX Plus R26通过缓存JSON Web密钥集加快JWT验证,并强化TLS握手,从而提升安全性。NGINX JavaScript的新功能包括增强对异步函数的支持,以及实现WebCrypto API。
使用NGINX App Protect DoS保护gRPC应用,抵御严重的DoS攻击
gRPC具备开发人员设计和部署现代应用所需的速度与灵活性,但其开放性也使其极易受到第7层DoS攻击。NGINX App Protect DoS可保护gRPC应用免受此类破坏性攻击。
Ingress Controller 选型指南(第 2 部分):风险与面向未来的规划
了解选择错误的Ingress控制器可能带来的风险,以及如何从关键方面着手,让您的选择更好地适应未来需求。
NGINX Plus R19 正式发布
NGINX Plus R19扩展了监控功能,不仅提供更多指标,还新增了指标分析方式;增加试运行模式,用于测试限速效果;增强键值存储,以支持IP地址范围;并使限速更加动态。
NGINX现已正式加入F5
NGINX现已正式成为F5的一部分。Gus Robertson现任F5 NGINX业务部门负责人。他阐述了两家公司在人才、文化和愿景方面融合的价值,以及双方将如何携手增强NGINX的开源产品和商业产品。
