FIN_WAIT_2状态下的连接与Apache

[ 2020-06-01 16:03:10 | 作者: admin ]
字号: | |
从Apache 1.2 beta测试版开始,人们就一直在报告说是处于 FIN_WAIT_2状态的连接(就是netstat所报告的)比使用旧版本的要多得多。 当服务器关闭一个TCP连接时,它发送一个设置了FIN位标记的包给客户端, 客户端以返回一个设置了ACK位标记的包来响应。 然后由客户端发送一个设置了FIN位标记的包给服务器, 服务器响应以一个设置了ACK位标记的包,这样连接就关闭了。 连接处于服务器接收到客户的ACK和FIN信号之间的阶段这时候的状态就叫做FIN_WAIT_2。 参看TCP RFC了解状态过渡的技术细节。

FIN_WAIT_2状态有点不寻常,在关于它的标准上没有超时的定义。 这意味着在许多操作系统上,一个处在FIN_WAIT_2状态的连接会保留直到系统重起。 如果系统没有超时限制并且建立了太多的FIN_WAIT_2连接的话,这些连接会填满分配来存储信息的空间并使内核崩溃。 FIN_WAIT_2连接不与一个httpd进程相关。

为什么会发生?

对此有许多原因,其中一些至今也不很清楚。已知的原因如下:

有缺陷的客户端与持久连接
有一些客户端在处理持久连接(aka keepalives)时存在问题。当连接空闲下来服务器关闭连接时(基于KeepAliveTimeout指令), 客户端的程序编制使它不发送FIN和ACK回服务器。这样就意味着这个连接 将停留在FIN_WAIT_2状态直到以下之一发生:

客户端为同一个或者不同的站点打开新的连接,这样会使它在该个套接字上完全关闭以前的连接。
用户退出客户端程序,这样在一些(也许是大多数?)客户端上会使操作系统完全关闭连接。
FIN_WAIT_2超时,在那些具有FIN_WAIT_2状态超时设置的服务器上。
如果你够幸运,这样意味着那些有缺陷的客户端会完全关闭连接并释放你服务器的资源。 然而,有一些情况下套接字永远不会完全关闭,比如一个拨号客户端在关闭客户端程序之前从ISP断开。 此外,有的客户端有可能空置好几天不创建新连接,并且这样在好几天里保持着套接字的有效即使已经不再使用。 这是浏览器或者操作系统的TCP实现的Bug。

已经被证实存在这个问题的客户端有:

Mozilla/3.01 (X11; I; FreeBSD 2.1.5-RELEASE i386)
Mozilla/2.02 (X11; I; FreeBSD 2.1.5-RELEASE i386)
Mozilla/3.01Gold (X11; I; SunOS 5.5 sun4m)
MSIE 3.01 on the Macintosh
MSIE 3.01 on Windows 95
没有出现问题的有:

Mozilla/3.01 (Win95; I)
预计许多别的客户端也有同样的问题。客户端应该做的工作 是周期性地检查自己打开的套接字看是否已经被服务器所关闭,并关闭那些已在服务器端被关闭的套接字。 这个检查只需要每几秒一次,在某些系统上甚至可以被操作系统信号检测到。 (例如,Win95和NT客户端有这种能力,但是它们似乎忽视了这一点)。

Apache 不能 避免这些FIN_WAIT_2状态出现,除非对那些客户端禁止持久连接, 就像因为其他问题我们对Navigator 2.x 客户端坐的建议那样。然而,非持久连接增加了每个客户端 需要的连接的数量并使得访问具有大量图片的页面速度降低。由于非持久连接具有自己的资源消耗与 短暂的每次关闭后的等待时期,为了更好地提供服务,一个繁忙的服务器会需要持续性。

就我们所知,客户端导致的FIN_WAIT_2问题对所有支持持久连接的服务器都存在, 包括 Apache 1.1.x 和 1.2.

在 1.2 版中引入的一点必需的代码
尽管上述bug是个问题,但还不是全部的问题。一些用户在Apache 1.1.x 上没有观察到FIN_WAIT_2问题, 但在 1.2b 上建立了足够多的FIN_WAIT_2状态连接就会崩溃内核。 这些附加FIN_WAIT_2状态最有可能的源头是一个叫做lingering_close()的函数调用, 它在 1.1 和 1.2版本之间被加入。这个函数对于正确处理持久连接和任何含有内容的请求(例如 ,PUTs和POSTs)是必需的。它的工作是在服务器关闭连接后的特定时刻读取任何客户端发送的数据。 这样做的确切理由有点复杂,但是涉及到在客户端于服务器发送响应并关闭连接的同一时刻提出请求的情况。 没有延迟的情况下,客户端在有机会读取服务器响应之前可能被强制重置其TCP输入缓冲区, 如此就可以理解连接关闭的原因了。参看附录获得更详细的了解。

lingering_close()的代码看起来似乎是引起一系列问题,包括它引起的 traffic patterns(译注:字典说是“起落航线”,我感觉像流量模式之类的东西,请分析过源码的朋友解释一下)的改变. 这些代码已被彻底地复查过了,我们没有在其中发现任何bug。 除了缺乏FIN_WAIT_2状态的超时机制,有可能在BSD TCP栈中有点问题,被引起我们观察的这些问题的 lingering_close代码暴露出来了。


我能对它做什么?

对于这个问题有几种可能的变通办法,其中一些工作得更好。

为 FIN_WAIT_2 增加 超时机制
明显的变通办法是简单地为FIN_WAIT_2状态加上超时机制。 这不是由RFC明确规定的,还有可能与RFC有所冲突,但是这个办法被广泛公认是必需的。 以下系统已知具有超时机制:

FreeBSD 从 2.0 或者更早版本开始。
NetBSD 版本 1.2(?)
OpenBSD 所有版本(?)
BSD/OS 2.1, 安装了 the K210-027 补丁。
Solaris 从大约 2.2 版本开始。 可以用ndd调节超时来改变tcp_fin_wait_2_flush_interval, 但是缺省值应该适合大多数的服务器,并且不正确的调节会产生反面作用。
Linux 2.0.x 与更早版本(?)
HP-UX 10.x 缺省值是在超过普通持活超时限制以后 终止the FIN_WAIT_2状态的连接。这不涉及到持久连接或HTTP保持活动的超时,但是跟套结字选项 SO_LINGER有关——Apache把它激活了。这个参数可以用nettune来调校修改 像tcp_keepstart和tcp_keepstop这样的参数。 在较晚的修订版中,有一个用于FIN_WAIT_2态连接的可以被修改的显式时钟; 接洽HP支持可以获得细节。
SGI IRIX可以打补丁来支持超时。 对于IRIX 5.3、6.2、和 6.3,分别使用补丁1654、1703 和 1778。如果寻找补丁遇到困难, 请向你的SGI支持渠道寻求帮助。
NCR's MP RAS Unix 2.xx 和 3.xx 版都有FIN_WAIT_2超时支持。在 2.xx 版中是600秒不可调, 而在 3.xx 版中缺省为 600 秒,由可调节的"max keep alive probes" (缺省为8)乘以 "keep alive interval"(缺省为75秒)得来。
Sequent's ptx/TCP/IP for DYNIX/ptx 自从1994年中的4.1发行半以来都有FIN_WAIT_2超时支持。
以下系统是已知没有超时支持的:

SunOS 4.x 没有而且几乎可以肯定以后也不会有这个功能, 因为对于Sun公司它已经处于其开发生命周期的最后时刻了。如果你有内核的源码它补丁应该很容易。
有一个 可用的补丁,能够给它加入FIN_WAIT_2状态的超时支持。最早是为BSD/OS开发的, 但是应该能够适合使用BSD网络代码的大多数系统。你需要内核源码才能使用它。

不使用lingering_close()的编译
编译不使用lingering_close()函数的Apache 1.2 是可能的。 这会使得那一部分的代码更接近 1.1 版的相应部分。如果你这样做, 要意识到这样会引起PUTs、POSTs 和持久连接方面的问题,特别是在客户端使用管道的情况下。 那是说,情况不会比1.1版更坏,而且我们明白保持你的服务器的运行是相当重要的。

要不带lingering_close()函数进行编译, ,在你的Configuration文件的EXTRA_CFLAGS 行的最后加上-DNO_LINGCLOSE,重新运行 Configure并重新编译服务器。

使用SO_LINGER作为lingering_close()之外另一个选择
在许多系统上,有一个可以由setsockopt(2)设置的叫做 SO_LINGER的选项。它完成与lingering_close()相似的工作, 除了这一点:它在许多系统上会终止以致引起比lingering_close多得多的问题。 在某些系统上它也会工作的很好,如果你没有别的选择也值得一试。

要试试它,在你的Configuration文件的EXTRA_CFLAGS 行的最后加上-DUSE_SO_LINGER -DNO_LINGCLOSE,重新运行 Configure并重新编译服务器。

注意
试图同时使用SO_LINGER和lingering_close() 非常可能会出现很糟的结果,所以不要这样做。
增加存储连接状态的内存数量
基于BSD的网络代码:
BSD 把网络数据存储在一个叫做mbuf的区域中,比如连接状态数据。 当你有太多的连接以至于内核没有足够的mbuf来容纳它们全部的时候, 你的内核就很可能崩溃。你可以通过增加可用的mbuf数量来减少这个问题的影响; 这样不能防止问题出现,只是让服务器在崩溃之前运行得久一点。
正确的增加方法与你的操作系统有关;找一找关于"mbufs"或者"mbuf clusters"数量的参考资料。 在许多系统上,可以这样做:在你的内核配置文件中添加一行NMBCLUSTERS="n", n是你想要的mbuf簇的数量,再重新编译内核。

禁止KeepAlive
如果你无法做上述任何一项修改,那么作为最后的手段,你应该禁止KeepAlive。 编辑你的httpd.conf并把"KeepAlive On"改为"KeepAlive Off"。


附录

下面是来自Roy Fielding的文章,他是HTTP/1.1的作者之一:

为什么延迟关闭功能对HTTP是必需的
服务器延续一个已关闭连接的需要在HTTP规范中被提到好几次但没有解释。 这里的解释是基于我以前在W3C的时候我、Henrik Frystyk、Robert S.Thau、Dave Raggett和 John C. Mallery之间在MIT的走廊上的讨论。

如果服务器在客户端发送数据时关闭连接的输入端(或者准备发送数据),那么 服务器的TCP栈会给客户端发一个RST信号(重置)。当接收到RST,客户端会刷新他自己的TCP输入缓冲区, 使之回到亦由RST包的参数所指定的未确认的包的状态。如果恰好在连接关闭之前, 服务器发送了一条消息给客户——通常是一个错误信息,客户端在其应用程序代码从TCP输入缓冲区读取 此错误消息之前接收了RST包并且服务器也已在接受缓冲收到客户端的ACK信号,那么, 这个RST信号将会在客户应用程序有机会读取这个错误信息之前把它刷新掉。 其结果就是客户端被留在那里考虑网络连接无缘无故的失败。

在两种情况下很可能会引发这样的情况:

在没有正确授权的情况下POST或者PUT数据
在接收到响应之前发送多个请求(管线)并且其中一个请求导致了错误或者其它引起连接中断的结果。
其解决办法都是:发送响应,只关闭连接中写操作的那一半(本来是停止运转), 并继续从套接字读取直到客户关闭连接(表示读取响应已经完成)或者出现超时。 这就是如果SO_LINGER被设置的话内核应该做的事。不幸,SO_LINGER在某些系统上不起作用; 在一些其它的系统上,它又没有自己的超时以致TCP内存段只好一直堆积连接直到下一次重起(计划中的及之外的)。

请注意简单地移走延迟代码并不能解决问题 -- 这样只是把问题转移到另一个检测起来困难的多的地方。
[最后修改由 admin, 于 2020-06-06 09:08:05]
评论Feed 评论Feed: http://blog.xg98.com/feed.asp?q=comment&id=2675

这篇日志没有评论。

此日志不可发表评论。